Recursive bound model discovery implemented

This commit is contained in:
Lea Anthony 2023-01-15 20:25:42 +11:00
commit 8bbf5ff3f1
No known key found for this signature in database
GPG key ID: 33DAF7BB90A58405
27 changed files with 350 additions and 594 deletions

View file

@ -4,23 +4,19 @@ import (
_ "embed"
"log"
"github.com/wailsapp/wails/exp/examples/binding/services"
"github.com/wailsapp/wails/exp/pkg/application"
"github.com/wailsapp/wails/exp/pkg/options"
)
type GreetService struct {
SomeVariable int
lowerCase string
}
func (*GreetService) Greet(name string) string {
return "Hello " + name
}
type localStruct struct{}
func main() {
app := application.New(options.Application{
Bind: []interface{}{
&GreetService{},
&localStruct{},
&services.GreetService{},
},
})

View file

@ -1,51 +0,0 @@
package main
import (
"github.com/wailsapp/wails/exp/pkg/application"
"github.com/wailsapp/wails/exp/pkg/options"
)
//func appOptions() options.Application {
// return options.Application{
// Bind: []interface{}{
// &GreetService{},
// },
// }
//}
//func appOptions() options.Application {
// result := options.Application{
// Bind: []interface{}{
// &GreetService{},
// },
// }
// return result
//}
func appOptions() options.Application {
result := options.Application{
Bind: []interface{}{
&GreetService{},
},
}
return result
}
func run() error {
//o := options.Application{
// Bind: []interface{}{
// &GreetService{},
// },
//}
//app := application.New(o)
//app := application.New(options.Application{
// Bind: []interface{}{
// &GreetService{},
// },
//})
app := application.New(appOptions())
return app.Run()
}

View file

@ -0,0 +1,13 @@
package services
import "github.com/wailsapp/wails/exp/examples/binding/models"
type GreetService struct {
SomeVariable int
lowercase string
Parent *models.Person
}
func (*GreetService) Greet(name string) string {
return "Hello " + name
}

View file

@ -11,6 +11,7 @@ require (
github.com/samber/lo v1.37.0
github.com/stretchr/testify v1.8.1
github.com/tc-hib/winres v0.1.6
golang.org/x/tools v0.1.12
)
require (
@ -37,6 +38,7 @@ require (
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 // indirect
golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9 // indirect
golang.org/x/image v0.0.0-20201208152932-35266b937fa6 // indirect
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
golang.org/x/sync v0.1.0 // indirect
golang.org/x/sys v0.3.0 // indirect
golang.org/x/term v0.3.0 // indirect

View file

@ -116,6 +116,8 @@ golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9/go.mod h1:cyybsKvd6eL0RnXn6p
golang.org/x/image v0.0.0-20200430140353-33d19683fad8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.0.0-20201208152932-35266b937fa6 h1:nfeHNc1nAqecKCy2FCy4HY+soOOe5sDLJ/gZLbx6GYI=
golang.org/x/image v0.0.0-20201208152932-35266b937fa6/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -145,6 +147,8 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=

View file

@ -1,47 +0,0 @@
package parser
import "go/ast"
type structInfo struct {
packageName string
structName string
}
func extractBindExprs(imp *ImportSpecInfo) {
// Check if the given expression is a composite literal
if comp, ok := (*imp.Options).(*ast.CompositeLit); ok {
// Iterate through the composite literal fields
for _, field := range comp.Elts {
// Check if the field key is "Bind"
if kv, ok := field.(*ast.KeyValueExpr); ok {
if ident, ok := kv.Key.(*ast.Ident); ok {
if ident.Name == "Bind" {
// Extract the expressions in the Bind field
if arr, ok := kv.Value.(*ast.CompositeLit); ok {
for _, elt := range arr.Elts {
if unaryExpr, ok := elt.(*ast.UnaryExpr); ok {
if addr, ok := unaryExpr.X.(*ast.CompositeLit); ok {
// Extract the type of the struct
if selExpr, ok := addr.Type.(*ast.SelectorExpr); ok {
if ident, ok := selExpr.X.(*ast.Ident); ok {
imp.BoundStructNames = append(imp.BoundStructNames, &structInfo{
packageName: ident.Name,
structName: selExpr.Sel.Name,
})
}
} else if ident, ok := addr.Type.(*ast.Ident); ok {
imp.BoundStructNames = append(imp.BoundStructNames, &structInfo{
packageName: "",
structName: ident.Name,
})
}
}
}
}
}
}
}
}
}
}
}

View file

@ -1,44 +0,0 @@
package parser
import (
"testing"
"github.com/stretchr/testify/require"
)
func Test_extractBindExprs(t *testing.T) {
tests := []struct {
name string
dir string
expectedBoundStructs []*structInfo
}{
{
name: "should find single bound struct literal",
dir: "testdata/boundstructs/struct_literal_single",
expectedBoundStructs: []*structInfo{{structName: "GreetService"}},
},
{
name: "should find multiple bound struct literal",
dir: "testdata/boundstructs/struct_literal_multiple",
expectedBoundStructs: []*structInfo{{structName: "GreetService"}, {structName: "OtherService"}},
},
{
name: "should find multiple bound struct literals over multiple files",
dir: "testdata/boundstructs/struct_literal_multiple_files",
expectedBoundStructs: []*structInfo{{structName: "GreetService"}, {structName: "OtherService"}},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
imports, err := findFilesImportingPackage(tt.dir, "github.com/wailsapp/wails/exp/pkg/application")
require.NoError(t, err)
imp := findNewCalls(imports)
require.NotNil(t, imp)
isOptions := isOptionsApplication(imp.Options)
require.True(t, isOptions)
extractBindExprs(imp)
require.ElementsMatchf(t, tt.expectedBoundStructs, imp.BoundStructNames, "expected %v, got %v", tt.expectedBoundStructs, imp.BoundStructNames)
})
}
}

View file

@ -1,66 +0,0 @@
package parser
import (
"go/ast"
"os"
)
func findNewCalls(imports []ImportSpecInfo) *ImportSpecInfo {
var result *ast.Expr
var inImport *ImportSpecInfo
for _, imp := range imports {
for _, decl := range imp.File.Decls {
ast.Inspect(decl, func(n ast.Node) bool {
// check if the current node is a function call
callExpr, ok := n.(*ast.CallExpr)
if !ok {
return true
}
// check if the function being called is .New()
selExpr, ok := callExpr.Fun.(*ast.SelectorExpr)
if !ok || selExpr.Sel.Name != "New" {
return true
}
// Get the name of the thing that New is being called on.
var receiverName string
switch rcv := selExpr.X.(type) {
case *ast.Ident:
receiverName = rcv.Name
case *ast.SelectorExpr:
receiverName = rcv.Sel.Name
default:
receiverName = "unknown"
}
// Check if the receiver is a package name
for _, i := range imports {
if i.Identifier() == receiverName {
if i.ImportSpec.Path.Value == `"github.com/wailsapp/wails/exp/pkg/application"` {
// We have a call to application.New()
// Parse out the first argument and check it is an option.Application struct
// check callExpr.Args[0] is a struct literal
if len(callExpr.Args) != 1 {
return true
}
if result != nil {
println("Found more than one call to application.New()")
os.Exit(1)
}
result = &(callExpr.Args[0])
inImport = &imp
return false
}
}
}
return true
})
}
}
inImport.Options = result
return inImport
}

View file

@ -1,34 +0,0 @@
package parser
import (
"testing"
"github.com/stretchr/testify/require"
)
func Test_findNewCalls(t *testing.T) {
tests := []struct {
name string
dir string
expectedExpressions int
}{
{
name: "should find single call to application.New",
dir: "testdata/boundstructs/struct_literal_single",
expectedExpressions: 1,
},
{
name: "should find single call to application.New in multiple files",
dir: "testdata/boundstructs/struct_literal_multiple_files",
expectedExpressions: 1,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
imports, err := findFilesImportingPackage(tt.dir, "github.com/wailsapp/wails/exp/pkg/application")
require.NoError(t, err)
got := findNewCalls(imports)
require.NotNil(t, got.Options)
})
}
}

View file

@ -1,69 +0,0 @@
package parser
import (
"go/ast"
"go/parser"
"go/token"
"os"
"path/filepath"
"strings"
)
type ImportSpecInfo struct {
FileName string
ImportSpec *ast.ImportSpec
File *ast.File
Alias string
Options *ast.Expr
BoundStructNames []*structInfo
}
func (i ImportSpecInfo) Identifier() string {
if i.Alias != "" {
return i.Alias
}
return strings.Trim(filepath.Base(i.ImportSpec.Path.Value), `"`)
}
func findFilesImportingPackage(dir, pkg string) ([]ImportSpecInfo, error) {
var importSpecInfo []ImportSpecInfo
// Recursively search for .go files in the given directory
err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if filepath.Ext(path) != ".go" {
return nil
}
fset := token.NewFileSet()
f, err := parser.ParseFile(fset, path, nil, parser.AllErrors)
if err != nil {
return err
}
// Search for import statement in the AST
for _, imp := range f.Imports {
if imp.Path.Value == "\""+pkg+"\"" {
var alias string
if imp.Name != nil {
alias = imp.Name.Name
}
importSpecInfo = append(importSpecInfo, ImportSpecInfo{
FileName: path,
ImportSpec: imp,
Alias: alias,
File: f,
})
}
}
return nil
})
if err != nil {
return nil, err
}
return importSpecInfo, nil
}

View file

@ -1,56 +0,0 @@
package parser
import (
"testing"
"github.com/samber/lo"
)
func Test_findFilesImportingPackage(t *testing.T) {
tests := []struct {
name string
dir string
pkg string
want []string
wantErr bool
}{
{
name: "should identify single file importing package",
dir: "testdata/imports/single_file",
pkg: "github.com/wailsapp/wails/exp/pkg/application",
want: []string{"testdata/imports/single_file/main.go"},
},
{
name: "should identify multiple files importing package",
dir: "testdata/imports/multiple_files",
pkg: "github.com/wailsapp/wails/exp/pkg/application",
want: []string{"testdata/imports/multiple_files/app.go"},
},
{
name: "should identify aliases",
dir: "testdata/imports/alias",
pkg: "github.com/wailsapp/wails/exp/pkg/application",
want: []string{"testdata/imports/alias/main.go"},
},
{
name: "should identify packages",
dir: "testdata/imports/other_package",
pkg: "github.com/wailsapp/wails/exp/pkg/application",
want: []string{"testdata/imports/other_package/subpackage/app.go"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := findFilesImportingPackage(tt.dir, tt.pkg)
if (err != nil) != tt.wantErr {
t.Errorf("findFilesImportingPackage() error = %v, wantErr %v", err, tt.wantErr)
return
}
for _, imp := range got {
if !lo.Contains(tt.want, imp.FileName) {
t.Errorf("findFilesImportingPackage() = %v, want %v", got, tt.want)
}
}
})
}
}

View file

@ -1,22 +0,0 @@
package parser
import (
"go/ast"
)
func isOptionsApplication(expr *ast.Expr) bool {
cl, ok := (*expr).(*ast.CompositeLit)
if ok {
se, ok := cl.Type.(*ast.SelectorExpr)
if ok {
_, ok := se.X.(*ast.Ident)
if ok {
if se.Sel.Name == "Application" && se.X.(*ast.Ident).Name == "options" {
return true
}
}
}
}
return false
}

View file

@ -1,31 +0,0 @@
package parser
import (
"testing"
"github.com/stretchr/testify/require"
)
func Test_isOptionsApplication(t *testing.T) {
tests := []struct {
name string
dir string
want bool
}{
{
name: "should return true when expr is a struct literal",
dir: "testdata/boundstructs/struct_literal_single",
want: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
imports, err := findFilesImportingPackage(tt.dir, "github.com/wailsapp/wails/exp/pkg/application")
require.NoError(t, err)
imp := findNewCalls(imports)
require.NotNil(t, imp)
isOptions := isOptionsApplication(imp.Options)
require.Equal(t, tt.want, isOptions)
})
}
}

View file

@ -0,0 +1,268 @@
package parser
import (
"go/ast"
"go/parser"
"go/token"
"strings"
"github.com/samber/lo"
"golang.org/x/tools/go/packages"
)
type parsedPackage struct {
name string
pkg *ast.Package
boundStructs []*ast.TypeSpec
}
type Context struct {
packages map[string]*parsedPackage
}
func ParseDirectory(dir string) (*Context, error) {
// Parse the directory
fset := token.NewFileSet()
pkgs, err := parser.ParseDir(fset, dir, nil, parser.AllErrors)
if err != nil {
return nil, err
}
context := &Context{
packages: make(map[string]*parsedPackage),
}
// Iterate through the packages
for _, pkg := range pkgs {
context.packages[pkg.Name] = &parsedPackage{
name: pkg.Name,
pkg: pkg,
}
}
findApplicationNewCalls(context)
return context, nil
}
func findApplicationNewCalls(context *Context) {
// Iterate through the packages
currentPackages := lo.Keys(context.packages)
for _, packageName := range currentPackages {
thisPackage := context.packages[packageName]
println("Parsing package", packageName)
// Iterate through the package's files
for _, file := range thisPackage.pkg.Files {
// Use an ast.Inspector to find the calls to application.New
ast.Inspect(file, func(n ast.Node) bool {
// Check if the node is a call expression
callExpr, ok := n.(*ast.CallExpr)
if !ok {
return true
}
// Check if the function being called is "application.New"
selExpr, ok := callExpr.Fun.(*ast.SelectorExpr)
if !ok {
return true
}
if selExpr.Sel.Name != "New" {
return true
}
if id, ok := selExpr.X.(*ast.Ident); !ok || id.Name != "application" {
return true
}
// Check there is only 1 argument
if len(callExpr.Args) != 1 {
return true
}
// Check argument 1 is a struct literal
structLit, ok := callExpr.Args[0].(*ast.CompositeLit)
if !ok {
return true
}
// Check struct literal is of type "options.Application"
selectorExpr, ok := structLit.Type.(*ast.SelectorExpr)
if !ok {
return true
}
if selectorExpr.Sel.Name != "Application" {
return true
}
if id, ok := selectorExpr.X.(*ast.Ident); !ok || id.Name != "options" {
return true
}
for _, elt := range structLit.Elts {
// Find the "Bind" field
kvExpr, ok := elt.(*ast.KeyValueExpr)
if !ok {
continue
}
if id, ok := kvExpr.Key.(*ast.Ident); !ok || id.Name != "Bind" {
continue
}
// Check the value is a slice of interfaces
sliceExpr, ok := kvExpr.Value.(*ast.CompositeLit)
if !ok {
continue
}
var arrayType *ast.ArrayType
if arrayType, ok = sliceExpr.Type.(*ast.ArrayType); !ok {
continue
}
// Check array type is of type "interface{}"
if _, ok := arrayType.Elt.(*ast.InterfaceType); !ok {
continue
}
// Iterate through the slice elements
for _, elt := range sliceExpr.Elts {
// Check the element is a unary expression
unaryExpr, ok := elt.(*ast.UnaryExpr)
if ok {
// Check the unary expression is a composite lit
boundStructLit, ok := unaryExpr.X.(*ast.CompositeLit)
if !ok {
continue
}
// Check if the composite lit is a struct
if _, ok := boundStructLit.Type.(*ast.StructType); ok {
// Parse struct
continue
}
// Check if the lit is an ident
ident, ok := boundStructLit.Type.(*ast.Ident)
if ok {
if ident.Obj == nil {
println("Ident.Obj is nil - check")
continue
}
// Check if the ident is a struct type
if _, ok := ident.Obj.Decl.(*ast.TypeSpec); ok {
thisPackage.boundStructs = append(thisPackage.boundStructs, ident.Obj.Decl.(*ast.TypeSpec))
continue
}
// Check the typespec decl is a struct
if _, ok := ident.Obj.Decl.(*ast.StructType); ok {
continue
}
}
// Check if the lit is a selector
selector, ok := boundStructLit.Type.(*ast.SelectorExpr)
if ok {
getStructsFromSelector(selector, file, context)
continue
}
}
}
}
return true
})
}
}
}
func getStructsFromSelector(selector *ast.SelectorExpr, file *ast.File, context *Context) {
// extract package name from selector
packageName := selector.X.(*ast.Ident).Name
if context.packages[packageName] == nil {
context.packages[packageName] = &parsedPackage{
name: packageName,
}
}
// extract struct name from selector
structName := selector.Sel.Name
// Find the package name from the imports
for _, imp := range file.Imports {
var match bool
if imp.Name == nil || imp.Name.Name == packageName {
match = true
}
if match == false {
pathSplit := strings.Split(imp.Path.Value, "/")
endPath := strings.Trim(pathSplit[len(pathSplit)-1], `"`)
match = endPath == packageName
}
if match {
// We have the import
cfg := &packages.Config{
Mode: packages.NeedName | packages.NeedFiles | packages.NeedImports | packages.NeedDeps | packages.NeedTypes | packages.NeedSyntax | packages.NeedTypesInfo | packages.NeedModule,
}
pkgs, err := packages.Load(cfg, strings.Trim(imp.Path.Value, `"`))
if err != nil {
panic(err)
}
foundPackage := pkgs[0]
// Iterate the files in the package and find struct types
for _, parsedFile := range foundPackage.Syntax {
ast.Inspect(parsedFile, func(n ast.Node) bool {
if n == nil {
return false
}
switch n.(type) {
case *ast.TypeSpec:
typeSpec := n.(*ast.TypeSpec)
if typeSpec.Name.Name == structName {
context.packages[packageName].boundStructs = append(context.packages[packageName].boundStructs, typeSpec)
findNestedStructs(typeSpec, parsedFile, packageName, context)
}
}
return true
})
}
continue
}
}
}
func findNestedStructs(t *ast.TypeSpec, parsedFile *ast.File, pkgName string, context *Context) (localStructs []*ast.TypeSpec, externalStructs []*ast.SelectorExpr) {
structType, ok := t.Type.(*ast.StructType)
if !ok {
return nil, nil
}
for _, field := range structType.Fields.List {
for _, ident := range field.Names {
switch t := ident.Obj.Decl.(*ast.Field).Type.(type) {
case *ast.Ident:
if t.Obj == nil {
continue
}
if t.Obj.Kind == ast.Typ {
if _, ok := t.Obj.Decl.(*ast.TypeSpec); ok {
context.packages[pkgName].boundStructs = append(context.packages[pkgName].boundStructs, t.Obj.Decl.(*ast.TypeSpec))
}
}
case *ast.SelectorExpr:
if ident, ok := t.X.(*ast.Ident); ok {
if ident.IsExported() {
getStructsFromSelector(t, parsedFile, context)
}
}
case *ast.StarExpr:
if sel, ok := t.X.(*ast.SelectorExpr); ok {
if _, ok := sel.X.(*ast.Ident); ok {
if ident.IsExported() {
getStructsFromSelector(sel, parsedFile, context)
}
}
}
}
}
}
return localStructs, externalStructs
}

View file

@ -0,0 +1,58 @@
package parser
import (
"testing"
"github.com/samber/lo"
"github.com/stretchr/testify/require"
)
func TestParseDirectory(t *testing.T) {
tests := []struct {
name string
dir string
want []string
wantErr bool
}{
{
name: "should find single bound service",
dir: "testdata/struct_literal_single",
want: []string{"GreetService"},
wantErr: false,
},
{
name: "should find multiple bound services",
dir: "testdata/struct_literal_multiple",
want: []string{"main.GreetService", "main.OtherService"},
wantErr: false,
},
{
name: "should find multiple bound services over multiple files",
dir: "testdata/struct_literal_multiple_files",
want: []string{"main.GreetService", "main.OtherService"},
wantErr: false,
},
{
name: "should find bound services from other packages",
dir: "../../examples/binding",
want: []string{"main.localStruct", "services.GreetService", "models.Person"},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := ParseDirectory(tt.dir)
if (err != nil) != tt.wantErr {
t.Errorf("ParseDirectory() error = %v, wantErr %v", err, tt.wantErr)
return
}
for name, pkg := range got.packages {
println("Got package", name)
for _, boundStruct := range pkg.boundStructs {
println("Got bound struct", boundStruct.Name.Name)
require.True(t, lo.Contains(tt.want, name+"."+boundStruct.Name.Name))
}
}
})
}
}

View file

@ -1,35 +0,0 @@
package main
import (
_ "embed"
"log"
bob "github.com/wailsapp/wails/exp/pkg/application"
"github.com/wailsapp/wails/exp/pkg/options"
)
type GreetService struct {
SomeVariable int
lowerCase string
}
func (*GreetService) Greet(name string) string {
return "Hello " + name
}
func main() {
app := bob.New(options.Application{
Bind: []interface{}{
&GreetService{},
},
})
app.NewWebviewWindow()
err := app.Run()
if err != nil {
log.Fatal(err)
}
}

View file

@ -1,35 +0,0 @@
package main
import (
_ "embed"
"log"
"github.com/wailsapp/wails/exp/pkg/application"
"github.com/wailsapp/wails/exp/pkg/options"
)
type GreetService struct {
SomeVariable int
lowerCase string
}
func (*GreetService) Greet(name string) string {
return "Hello " + name
}
func run() {
app := application.New(options.Application{
Bind: []interface{}{
&GreetService{},
},
})
app.NewWebviewWindow()
err := app.Run()
if err != nil {
log.Fatal(err)
}
}

View file

@ -1,9 +0,0 @@
package main
import (
_ "embed"
)
func main() {
run()
}

View file

@ -1,7 +0,0 @@
module other_package
require github.com/wailsapp/wails/exp v0
go 1.19
replace github.com/wailsapp/wails/exp v0 => ../../../../../../

View file

@ -1,9 +0,0 @@
package main
import (
"other_package/subpackage"
)
func main() {
subpackage.Run()
}

View file

@ -1,35 +0,0 @@
package subpackage
import (
_ "embed"
"log"
"github.com/wailsapp/wails/exp/pkg/application"
"github.com/wailsapp/wails/exp/pkg/options"
)
type GreetService struct {
SomeVariable int
lowerCase string
}
func (*GreetService) Greet(name string) string {
return "Hello " + name
}
func Run() {
app := application.New(options.Application{
Bind: []interface{}{
&GreetService{},
},
})
app.NewWebviewWindow()
err := app.Run()
if err != nil {
log.Fatal(err)
}
}

View file

@ -1,35 +0,0 @@
package main
import (
_ "embed"
"log"
"github.com/wailsapp/wails/exp/pkg/application"
"github.com/wailsapp/wails/exp/pkg/options"
)
type GreetService struct {
SomeVariable int
lowerCase string
}
func (*GreetService) Greet(name string) string {
return "Hello " + name
}
func main() {
app := application.New(options.Application{
Bind: []interface{}{
&GreetService{},
},
})
app.NewWebviewWindow()
err := app.Run()
if err != nil {
log.Fatal(err)
}
}