From 1d13e329d20086bbeb2f82c198a9fb08039dd202 Mon Sep 17 00:00:00 2001 From: Hajime Hoshi Date: Mon, 9 Dec 2019 16:01:06 +0900 Subject: [PATCH] cmd/gomobile: enable Go modules in gomobile-bind This CL enables Go modules in gomobile-bind command. This CL generates go.mod at $WORK/src based on the modules state of the working directory, and use it when executing go-build. Updates golang/go#27234 Change-Id: I6958f29a317c0d2fb9ffa373f6e3c4cabdc4e898 Reviewed-on: https://go-review.googlesource.com/c/mobile/+/210380 Run-TryBot: Hajime Hoshi TryBot-Result: Gobot Gobot Reviewed-by: Hyang-Ah Hana Kim --- cmd/gomobile/bind.go | 109 +++++++++++++++++++++++++++++--- cmd/gomobile/bind_androidapp.go | 6 +- cmd/gomobile/bind_iosapp.go | 6 +- cmd/gomobile/bind_test.go | 65 ++++++++++++++++++- go.mod | 3 +- go.sum | 10 ++- 6 files changed, 182 insertions(+), 17 deletions(-) diff --git a/cmd/gomobile/bind.go b/cmd/gomobile/bind.go index e26d51e..2676bfe 100644 --- a/cmd/gomobile/bind.go +++ b/cmd/gomobile/bind.go @@ -5,6 +5,8 @@ package main import ( + "bytes" + "encoding/json" "errors" "fmt" "io" @@ -15,6 +17,7 @@ import ( "path/filepath" "strings" + "golang.org/x/mod/modfile" "golang.org/x/tools/go/packages" ) @@ -102,13 +105,10 @@ func runBind(cmd *command) error { gobind = "gobind" } - var pkgs []*packages.Package - switch len(args) { - case 0: - pkgs, err = packages.Load(packagesConfig(targetOS), ".") - default: - pkgs, err = importPackages(args, targetOS) + if len(args) == 0 { + args = append(args, ".") } + pkgs, err := importPackages(args, targetOS) if err != nil { return err } @@ -196,8 +196,7 @@ func writeFile(filename string, generate func(io.Writer) error) error { fmt.Fprintf(os.Stderr, "write %s\n", filename) } - err := mkdir(filepath.Dir(filename)) - if err != nil { + if err := mkdir(filepath.Dir(filename)); err != nil { return err } @@ -230,3 +229,97 @@ func packagesConfig(targetOS string) *packages.Config { } return config } + +// getModuleVersions returns a module information at the directory src. +func getModuleVersions(targetOS string, targetArch string, src string) (*modfile.File, error) { + cmd := exec.Command(goBin(), "list") + cmd.Env = append(os.Environ(), "GOOS="+targetOS, "GOARCH="+targetArch) + + tags := buildTags + if targetOS == "darwin" { + tags = append(tags, "ios") + } + cmd.Args = append(cmd.Args, "-m", "-json", "-tags="+strings.Join(tags, ","), "all") + cmd.Dir = src + + output, err := cmd.Output() + if err != nil { + // Module information is not available at src. + return nil, nil + } + + type Module struct { + Path string + Version string + Dir string + Replace *Module + } + + f := &modfile.File{} + f.AddModuleStmt("gobind") + e := json.NewDecoder(bytes.NewReader(output)) + for { + var mod *Module + err := e.Decode(&mod) + if err != nil && err != io.EOF { + return nil, err + } + if mod != nil { + switch { + case mod.Replace != nil: + f.AddReplace(mod.Path, mod.Version, mod.Replace.Path, mod.Replace.Version) + case mod.Version == "": + // When the version part is empty, the module is local and mod.Dir represents the location. + f.AddReplace(mod.Path, "", mod.Dir, "") + default: + f.AddRequire(mod.Path, mod.Version) + } + } + if err == io.EOF { + break + } + } + return f, nil +} + +// writeGoMod writes go.mod file at $WORK/src when Go modules are used. +func writeGoMod(targetOS string, targetArch string) error { + m, err := areGoModulesUsed() + if err != nil { + return err + } + // If Go modules are not used, go.mod should not be created because the dependencies might not be compatible with Go modules. + if !m { + return nil + } + + return writeFile(filepath.Join(tmpdir, "src", "go.mod"), func(w io.Writer) error { + f, err := getModuleVersions(targetOS, targetArch, ".") + if err != nil { + return err + } + if f == nil { + return nil + } + bs, err := f.Format() + if err != nil { + return err + } + if _, err := w.Write(bs); err != nil { + return err + } + return nil + }) +} + +func areGoModulesUsed() (bool, error) { + out, err := exec.Command(goBin(), "env", "GOMOD").Output() + if err != nil { + return false, err + } + outstr := strings.TrimSpace(string(out)) + if outstr == "" { + return false, nil + } + return true, nil +} diff --git a/cmd/gomobile/bind_androidapp.go b/cmd/gomobile/bind_androidapp.go index 31b2e56..8fe9179 100644 --- a/cmd/gomobile/bind_androidapp.go +++ b/cmd/gomobile/bind_androidapp.go @@ -54,12 +54,14 @@ func goAndroidBind(gobind string, pkgs []*packages.Package, androidArchs []strin // Generate binding code and java source code only when processing the first package. for _, arch := range androidArchs { + if err := writeGoMod("android", arch); err != nil { + return err + } + env := androidEnv[arch] // Add the generated packages to GOPATH for reverse bindings. gopath := fmt.Sprintf("GOPATH=%s%c%s", tmpdir, filepath.ListSeparator, goEnv("GOPATH")) env = append(env, gopath) - // gomobile-bind does not support modules yet. - env = append(env, "GO111MODULE=off") toolchain := ndk.Toolchain(arch) err := goBuildAt( diff --git a/cmd/gomobile/bind_iosapp.go b/cmd/gomobile/bind_iosapp.go index 924a474..f49a068 100644 --- a/cmd/gomobile/bind_iosapp.go +++ b/cmd/gomobile/bind_iosapp.go @@ -57,6 +57,10 @@ func goIOSBind(gobind string, pkgs []*packages.Package, archs []string) error { cmd = exec.Command("xcrun", "lipo", "-create") for _, arch := range archs { + if err := writeGoMod("darwin", arch); err != nil { + return err + } + env := darwinEnv[arch] // Add the generated packages to GOPATH for reverse bindings. gopath := fmt.Sprintf("GOPATH=%s%c%s", tmpdir, filepath.ListSeparator, goEnv("GOPATH")) @@ -178,8 +182,6 @@ var iosModuleMapTmpl = template.Must(template.New("iosmmap").Parse(`framework mo func goIOSBindArchive(name string, env []string, gosrc string) (string, error) { arch := getenv(env, "GOARCH") archive := filepath.Join(tmpdir, name+"-"+arch+".a") - // gobind-bind does not support modules yet. - env = append(env, "GO111MODULE=off") err := goBuildAt(gosrc, "./gobind", env, "-buildmode=c-archive", "-o", archive) if err != nil { return "", err diff --git a/cmd/gomobile/bind_test.go b/cmd/gomobile/bind_test.go index d31c4fd..d6ca3f6 100644 --- a/cmd/gomobile/bind_test.go +++ b/cmd/gomobile/bind_test.go @@ -6,7 +6,9 @@ package main import ( "bytes" + "io/ioutil" "os" + "os/exec" "path" "path/filepath" "runtime" @@ -179,7 +181,8 @@ func TestBindIOS(t *testing.T) { var bindAndroidTmpl = template.Must(template.New("output").Parse(`GOMOBILE={{.GOPATH}}/pkg/gomobile WORK=$WORK GOOS=android CGO_ENABLED=1 gobind -lang=go,java -outdir=$WORK{{if .JavaPkg}} -javapkg={{.JavaPkg}}{{end}} golang.org/x/mobile/asset -PWD=$WORK/src GOOS=android GOARCH=arm CC=$NDK_PATH/toolchains/llvm/prebuilt/{{.NDKARCH}}/bin/armv7a-linux-androideabi16-clang CXX=$NDK_PATH/toolchains/llvm/prebuilt/{{.NDKARCH}}/bin/armv7a-linux-androideabi16-clang++ CGO_ENABLED=1 GOARM=7 GOPATH=$WORK:$GOPATH GO111MODULE=off go build -x -buildmode=c-shared -o=$WORK/android/src/main/jniLibs/armeabi-v7a/libgojni.so ./gobind +mkdir -p $WORK/src +PWD=$WORK/src GOOS=android GOARCH=arm CC=$NDK_PATH/toolchains/llvm/prebuilt/{{.NDKARCH}}/bin/armv7a-linux-androideabi16-clang CXX=$NDK_PATH/toolchains/llvm/prebuilt/{{.NDKARCH}}/bin/armv7a-linux-androideabi16-clang++ CGO_ENABLED=1 GOARM=7 GOPATH=$WORK:$GOPATH go build -x -buildmode=c-shared -o=$WORK/android/src/main/jniLibs/armeabi-v7a/libgojni.so ./gobind PWD=$WORK/java javac -d $WORK/javac-output -source 1.7 -target 1.7 -bootclasspath {{.AndroidPlatform}}/android.jar *.java jar c -C $WORK/javac-output . `)) @@ -187,7 +190,8 @@ jar c -C $WORK/javac-output . var bindIOSTmpl = template.Must(template.New("output").Parse(`GOMOBILE={{.GOPATH}}/pkg/gomobile WORK=$WORK GOOS=darwin CGO_ENABLED=1 gobind -lang=go,objc -outdir=$WORK -tags=ios{{if .Prefix}} -prefix={{.Prefix}}{{end}} golang.org/x/mobile/asset -PWD=$WORK/src GOARM=7 GOOS=darwin GOARCH=arm CC=iphoneos-clang CXX=iphoneos-clang++ CGO_CFLAGS=-isysroot=iphoneos -miphoneos-version-min=7.0 -fembed-bitcode -arch armv7 CGO_CXXFLAGS=-isysroot=iphoneos -miphoneos-version-min=7.0 -fembed-bitcode -arch armv7 CGO_LDFLAGS=-isysroot=iphoneos -miphoneos-version-min=7.0 -fembed-bitcode -arch armv7 CGO_ENABLED=1 GOPATH=$WORK:$GOPATH GO111MODULE=off go build -tags ios -x -buildmode=c-archive -o $WORK/asset-arm.a ./gobind +mkdir -p $WORK/src +PWD=$WORK/src GOARM=7 GOOS=darwin GOARCH=arm CC=iphoneos-clang CXX=iphoneos-clang++ CGO_CFLAGS=-isysroot=iphoneos -miphoneos-version-min=7.0 -fembed-bitcode -arch armv7 CGO_CXXFLAGS=-isysroot=iphoneos -miphoneos-version-min=7.0 -fembed-bitcode -arch armv7 CGO_LDFLAGS=-isysroot=iphoneos -miphoneos-version-min=7.0 -fembed-bitcode -arch armv7 CGO_ENABLED=1 GOPATH=$WORK:$GOPATH go build -tags ios -x -buildmode=c-archive -o $WORK/asset-arm.a ./gobind rm -r -f "Asset.framework" mkdir -p Asset.framework/Versions/A/Headers ln -s A Asset.framework/Versions/Current @@ -207,3 +211,60 @@ mkdir -p Asset.framework/Resources mkdir -p Asset.framework/Versions/A/Modules ln -s Versions/Current/Modules Asset.framework/Modules `)) + +func TestBindWithGoModules(t *testing.T) { + if runtime.GOOS == "android" { + t.Skipf("gomobile and gobind are not available on %s", runtime.GOOS) + } + + dir, err := ioutil.TempDir("", "") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(dir) + + if out, err := exec.Command(goBin(), "build", "-o="+dir, "golang.org/x/mobile/cmd/gobind").CombinedOutput(); err != nil { + t.Fatalf("%v: %s", err, string(out)) + } + if out, err := exec.Command(goBin(), "build", "-o="+dir, "golang.org/x/mobile/cmd/gomobile").CombinedOutput(); err != nil { + t.Fatalf("%v: %s", err, string(out)) + } + path := dir + if p := os.Getenv("PATH"); p != "" { + path += string(filepath.ListSeparator) + p + } + + for _, target := range []string{"android", "ios"} { + t.Run(target, func(t *testing.T) { + switch target { + case "android": + androidHome := os.Getenv("ANDROID_HOME") + if androidHome == "" { + t.Skip("ANDROID_HOME not found, skipping bind") + } + if _, err := androidAPIPath(); err != nil { + t.Skip("No android API platform found in $ANDROID_HOME, skipping bind") + } + case "ios": + if !xcodeAvailable() { + t.Skip("Xcode is missing") + } + } + + var out string + switch target { + case "android": + out = filepath.Join(dir, "cgopkg.aar") + case "ios": + out = filepath.Join(dir, "Cgopkg.framework") + } + cmd := exec.Command(filepath.Join(dir, "gomobile"), "bind", "-target="+target, "-o="+out, "golang.org/x/mobile/bind/testdata/cgopkg") + cmd.Env = append(os.Environ(), "PATH="+path, "GO111MODULE=on") + var b bytes.Buffer + cmd.Stderr = &b + if err := cmd.Run(); err != nil { + t.Errorf("%v: %s", err, string(b.Bytes())) + } + }) + } +} diff --git a/go.mod b/go.mod index 13a3b3b..807e703 100644 --- a/go.mod +++ b/go.mod @@ -5,5 +5,6 @@ go 1.11 require ( golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56 golang.org/x/image v0.0.0-20190802002840-cff245a6509b - golang.org/x/tools v0.0.0-20190909214602-067311248421 + golang.org/x/mod v0.1.1-0.20191209134235-331c550502dd + golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e ) diff --git a/go.sum b/go.sum index 664c53c..c387242 100644 --- a/go.sum +++ b/go.sum @@ -1,13 +1,17 @@ github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56 h1:estk1glOnSVeJ9tdEZZc5mAMDZk5lNJNyJ6DvrBkTEU= golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b h1:+qEpEAPhDZ1o0x3tHzZTQDArnOixOzGD9HUJfcg0mb4= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mod v0.1.0 h1:sfUMP1Gu8qASkorDVjnMuvgJzwFbTZSeXFiGBYAVdl4= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191209134235-331c550502dd h1:ePuNC7PZ6O5BzgPn9bZayERXBdfZjUYoXEf5BTfDfh8= +golang.org/x/mod v0.1.1-0.20191209134235-331c550502dd/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -17,6 +21,8 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5 golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190909214602-067311248421 h1:NmmWqJbt02YJHmp4A4gBXvsXXIzzixjzE1y6PKUyIjk= -golang.org/x/tools v0.0.0-20190909214602-067311248421/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e h1:aZzprAO9/8oim3qStq3wc1Xuxx4QmAGriC4VU4ojemQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898 h1:/atklqdjdhuosWIl6AIbOeHJjicWYPqR9bpxqxYG2pA= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=