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 <hajimehoshi@gmail.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Hyang-Ah Hana Kim <hyangah@gmail.com>
This commit is contained in:
Hajime Hoshi 2019-12-09 16:01:06 +09:00
parent 1a1fef8273
commit 1d13e329d2
6 changed files with 182 additions and 17 deletions

View File

@ -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
}

View File

@ -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(

View File

@ -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

View File

@ -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()))
}
})
}
}

3
go.mod
View File

@ -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
)

10
go.sum
View File

@ -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=