github.com/adwpc/xmobile@v0.0.0-20231212131043-3f9720cf0e99/cmd/gomobile/bind_test.go (about)

     1  // Copyright 2015 The Go Authors.  All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package main
     6  
     7  import (
     8  	"bytes"
     9  	"os"
    10  	"os/exec"
    11  	"path/filepath"
    12  	"runtime"
    13  	"strings"
    14  	"testing"
    15  	"text/template"
    16  
    17  	"github.com/adwpc/xmobile/internal/sdkpath"
    18  )
    19  
    20  func TestBindAndroid(t *testing.T) {
    21  	platform, err := sdkpath.AndroidAPIPath(minAndroidAPI)
    22  	if err != nil {
    23  		t.Skip("No compatible Android API platform found, skipping bind")
    24  	}
    25  	// platform is a path like "/path/to/Android/sdk/platforms/android-32"
    26  	components := strings.Split(platform, string(filepath.Separator))
    27  	if len(components) < 2 {
    28  		t.Fatalf("API path is too short: %s", platform)
    29  	}
    30  	components = components[len(components)-2:]
    31  	platformRel := filepath.Join("$ANDROID_HOME", components[0], components[1])
    32  
    33  	defer func() {
    34  		xout = os.Stderr
    35  		buildN = false
    36  		buildX = false
    37  		buildO = ""
    38  		buildTarget = ""
    39  		bindJavaPkg = ""
    40  	}()
    41  	buildN = true
    42  	buildX = true
    43  	buildO = "asset.aar"
    44  	buildTarget = "android/arm"
    45  
    46  	tests := []struct {
    47  		javaPkg string
    48  	}{
    49  		{
    50  			// Empty javaPkg
    51  		},
    52  		{
    53  			javaPkg: "com.example.foo",
    54  		},
    55  	}
    56  	for _, tc := range tests {
    57  		bindJavaPkg = tc.javaPkg
    58  
    59  		buf := new(bytes.Buffer)
    60  		xout = buf
    61  		gopath = filepath.SplitList(goEnv("GOPATH"))[0]
    62  		if goos == "windows" {
    63  			os.Setenv("HOMEDRIVE", "C:")
    64  		}
    65  		cmdBind.flag.Parse([]string{"github.com/adwpc/xmobile/asset"})
    66  		err := runBind(cmdBind)
    67  		if err != nil {
    68  			t.Log(buf.String())
    69  			t.Fatal(err)
    70  		}
    71  		got := filepath.ToSlash(buf.String())
    72  
    73  		output, err := defaultOutputData("")
    74  		if err != nil {
    75  			t.Fatal(err)
    76  		}
    77  		data := struct {
    78  			outputData
    79  			AndroidPlatform string
    80  			JavaPkg         string
    81  		}{
    82  			outputData:      output,
    83  			AndroidPlatform: platformRel,
    84  			JavaPkg:         tc.javaPkg,
    85  		}
    86  
    87  		wantBuf := new(bytes.Buffer)
    88  		if err := bindAndroidTmpl.Execute(wantBuf, data); err != nil {
    89  			t.Errorf("%+v: computing diff failed: %v", tc, err)
    90  			continue
    91  		}
    92  
    93  		diff, err := diff(got, wantBuf.String())
    94  		if err != nil {
    95  			t.Errorf("%+v: computing diff failed: %v", tc, err)
    96  			continue
    97  		}
    98  		if diff != "" {
    99  			t.Errorf("%+v: unexpected output:\n%s", tc, diff)
   100  		}
   101  	}
   102  }
   103  
   104  func TestBindApple(t *testing.T) {
   105  	if !xcodeAvailable() {
   106  		t.Skip("Xcode is missing")
   107  	}
   108  	defer func() {
   109  		xout = os.Stderr
   110  		buildN = false
   111  		buildX = false
   112  		buildO = ""
   113  		buildTarget = ""
   114  		bindPrefix = ""
   115  	}()
   116  	buildN = true
   117  	buildX = true
   118  	buildO = "Asset.xcframework"
   119  	buildTarget = "ios/arm64"
   120  
   121  	tests := []struct {
   122  		prefix string
   123  		out    string
   124  	}{
   125  		{
   126  			// empty prefix
   127  		},
   128  		{
   129  			prefix: "Foo",
   130  		},
   131  		{
   132  			out: "Abcde.xcframework",
   133  		},
   134  	}
   135  	for _, tc := range tests {
   136  		bindPrefix = tc.prefix
   137  		if tc.out != "" {
   138  			buildO = tc.out
   139  		}
   140  
   141  		buf := new(bytes.Buffer)
   142  		xout = buf
   143  		gopath = filepath.SplitList(goEnv("GOPATH"))[0]
   144  		if goos == "windows" {
   145  			os.Setenv("HOMEDRIVE", "C:")
   146  		}
   147  		cmdBind.flag.Parse([]string{"github.com/adwpc/xmobile/asset"})
   148  		if err := runBind(cmdBind); err != nil {
   149  			t.Log(buf.String())
   150  			t.Fatal(err)
   151  		}
   152  		got := filepath.ToSlash(buf.String())
   153  
   154  		output, err := defaultOutputData("")
   155  		if err != nil {
   156  			t.Fatal(err)
   157  		}
   158  
   159  		data := struct {
   160  			outputData
   161  			Output string
   162  			Prefix string
   163  		}{
   164  			outputData: output,
   165  			Output:     buildO[:len(buildO)-len(".xcframework")],
   166  			Prefix:     tc.prefix,
   167  		}
   168  
   169  		wantBuf := new(bytes.Buffer)
   170  		if err := bindAppleTmpl.Execute(wantBuf, data); err != nil {
   171  			t.Errorf("%+v: computing diff failed: %v", tc, err)
   172  			continue
   173  		}
   174  
   175  		diff, err := diff(got, wantBuf.String())
   176  		if err != nil {
   177  			t.Errorf("%+v: computing diff failed: %v", tc, err)
   178  			continue
   179  		}
   180  		if diff != "" {
   181  			t.Errorf("%+v: unexpected output:\n%s", tc, diff)
   182  		}
   183  	}
   184  }
   185  
   186  var bindAndroidTmpl = template.Must(template.New("output").Parse(`GOMOBILE={{.GOPATH}}/pkg/gomobile
   187  WORK=$WORK
   188  GOOS=android CGO_ENABLED=1 gobind -lang=go,java -outdir=$WORK{{if .JavaPkg}} -javapkg={{.JavaPkg}}{{end}} github.com/adwpc/xmobile/asset
   189  mkdir -p $WORK/src-android-arm
   190  PWD=$WORK/src-android-arm GOMODCACHE=$GOPATH/pkg/mod 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 mod tidy
   191  PWD=$WORK/src-android-arm GOMODCACHE=$GOPATH/pkg/mod 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
   192  PWD=$WORK/java javac -d $WORK/javac-output -source 1.8 -target 1.8 -bootclasspath {{.AndroidPlatform}}/android.jar *.java
   193  jar c -C $WORK/javac-output .
   194  `))
   195  
   196  var bindAppleTmpl = template.Must(template.New("output").Parse(`GOMOBILE={{.GOPATH}}/pkg/gomobile
   197  WORK=$WORK
   198  rm -r -f "{{.Output}}.xcframework"
   199  GOOS=ios CGO_ENABLED=1 gobind -lang=go,objc -outdir=$WORK/ios -tags=ios{{if .Prefix}} -prefix={{.Prefix}}{{end}} github.com/adwpc/xmobile/asset
   200  mkdir -p $WORK/ios/src-arm64
   201  PWD=$WORK/ios/src-arm64 GOMODCACHE=$GOPATH/pkg/mod GOOS=ios GOARCH=arm64 GOFLAGS=-tags=ios CC=iphoneos-clang CXX=iphoneos-clang++ CGO_CFLAGS=-isysroot iphoneos -miphoneos-version-min=13.0 -fembed-bitcode -arch arm64 CGO_CXXFLAGS=-isysroot iphoneos -miphoneos-version-min=13.0 -fembed-bitcode -arch arm64 CGO_LDFLAGS=-isysroot iphoneos -miphoneos-version-min=13.0 -fembed-bitcode -arch arm64 CGO_ENABLED=1 DARWIN_SDK=iphoneos GOPATH=$WORK/ios:$GOPATH go mod tidy
   202  PWD=$WORK/ios/src-arm64 GOMODCACHE=$GOPATH/pkg/mod GOOS=ios GOARCH=arm64 GOFLAGS=-tags=ios CC=iphoneos-clang CXX=iphoneos-clang++ CGO_CFLAGS=-isysroot iphoneos -miphoneos-version-min=13.0 -fembed-bitcode -arch arm64 CGO_CXXFLAGS=-isysroot iphoneos -miphoneos-version-min=13.0 -fembed-bitcode -arch arm64 CGO_LDFLAGS=-isysroot iphoneos -miphoneos-version-min=13.0 -fembed-bitcode -arch arm64 CGO_ENABLED=1 DARWIN_SDK=iphoneos GOPATH=$WORK/ios:$GOPATH go build -x -buildmode=c-archive -o $WORK/{{.Output}}-ios-arm64.a ./gobind
   203  mkdir -p $WORK/ios/iphoneos/{{.Output}}.framework/Versions/A/Headers
   204  ln -s A $WORK/ios/iphoneos/{{.Output}}.framework/Versions/Current
   205  ln -s Versions/Current/Headers $WORK/ios/iphoneos/{{.Output}}.framework/Headers
   206  ln -s Versions/Current/{{.Output}} $WORK/ios/iphoneos/{{.Output}}.framework/{{.Output}}
   207  xcrun lipo $WORK/{{.Output}}-ios-arm64.a -create -o $WORK/ios/iphoneos/{{.Output}}.framework/Versions/A/{{.Output}}
   208  cp $WORK/ios/src/gobind/{{.Prefix}}Asset.objc.h $WORK/ios/iphoneos/{{.Output}}.framework/Versions/A/Headers/{{.Prefix}}Asset.objc.h
   209  mkdir -p $WORK/ios/iphoneos/{{.Output}}.framework/Versions/A/Headers
   210  cp $WORK/ios/src/gobind/Universe.objc.h $WORK/ios/iphoneos/{{.Output}}.framework/Versions/A/Headers/Universe.objc.h
   211  mkdir -p $WORK/ios/iphoneos/{{.Output}}.framework/Versions/A/Headers
   212  cp $WORK/ios/src/gobind/ref.h $WORK/ios/iphoneos/{{.Output}}.framework/Versions/A/Headers/ref.h
   213  mkdir -p $WORK/ios/iphoneos/{{.Output}}.framework/Versions/A/Headers
   214  mkdir -p $WORK/ios/iphoneos/{{.Output}}.framework/Versions/A/Headers
   215  mkdir -p $WORK/ios/iphoneos/{{.Output}}.framework/Versions/A/Resources
   216  ln -s Versions/Current/Resources $WORK/ios/iphoneos/{{.Output}}.framework/Resources
   217  mkdir -p $WORK/ios/iphoneos/{{.Output}}.framework/Resources
   218  mkdir -p $WORK/ios/iphoneos/{{.Output}}.framework/Versions/A/Modules
   219  ln -s Versions/Current/Modules $WORK/ios/iphoneos/{{.Output}}.framework/Modules
   220  xcodebuild -create-xcframework -framework $WORK/ios/iphoneos/{{.Output}}.framework -output {{.Output}}.xcframework
   221  `))
   222  
   223  func TestBindAppleAll(t *testing.T) {
   224  	if !xcodeAvailable() {
   225  		t.Skip("Xcode is missing")
   226  	}
   227  	defer func() {
   228  		xout = os.Stderr
   229  		buildN = false
   230  		buildX = false
   231  		buildO = ""
   232  		buildTarget = ""
   233  		bindPrefix = ""
   234  	}()
   235  	buildN = true
   236  	buildX = true
   237  	buildO = "Asset.xcframework"
   238  	buildTarget = "ios"
   239  
   240  	buf := new(bytes.Buffer)
   241  	xout = buf
   242  	gopath = filepath.SplitList(goEnv("GOPATH"))[0]
   243  	if goos == "windows" {
   244  		os.Setenv("HOMEDRIVE", "C:")
   245  	}
   246  	cmdBind.flag.Parse([]string{"github.com/adwpc/xmobile/asset"})
   247  	if err := runBind(cmdBind); err != nil {
   248  		t.Log(buf.String())
   249  		t.Fatal(err)
   250  	}
   251  }
   252  
   253  const ambiguousPathsGoMod = `module ambiguouspaths
   254  
   255  go 1.18
   256  
   257  require github.com/adwpc/xmobile v0.0.0-20230905140555-fbe1c053b6a9
   258  
   259  require (
   260  	golang.org/x/exp/shiny v0.0.0-20230817173708-d852ddb80c63 // indirect
   261  	golang.org/x/image v0.11.0 // indirect
   262  	golang.org/x/sys v0.11.0 // indirect
   263  )
   264  `
   265  
   266  const ambiguousPathsGoSum = `github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
   267  golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
   268  golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
   269  golang.org/x/exp/shiny v0.0.0-20230817173708-d852ddb80c63 h1:3AGKexOYqL+ztdWdkB1bDwXgPBuTS/S8A4WzuTvJ8Cg=
   270  golang.org/x/exp/shiny v0.0.0-20230817173708-d852ddb80c63/go.mod h1:UH99kUObWAZkDnWqppdQe5ZhPYESUw8I0zVV1uWBR+0=
   271  golang.org/x/image v0.11.0 h1:ds2RoQvBvYTiJkwpSFDwCcDFNX7DqjL2WsUgTNk0Ooo=
   272  golang.org/x/image v0.11.0/go.mod h1:bglhjqbqVuEb9e9+eNR45Jfu7D+T4Qan+NhQk8Ck2P8=
   273  github.com/adwpc/xmobile v0.0.0-20230905140555-fbe1c053b6a9 h1:LaLfQUz4L1tfuOlrtEouZLZ0qHDwKn87E1NKoiudP/o=
   274  github.com/adwpc/xmobile v0.0.0-20230905140555-fbe1c053b6a9/go.mod h1:2jxcxt/JNJik+N+QcB8q308+SyrE3bu43+sGZDmJ02M=
   275  golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
   276  golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
   277  golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
   278  golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
   279  golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
   280  golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
   281  golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
   282  golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
   283  golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
   284  golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
   285  golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
   286  golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
   287  golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
   288  golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
   289  golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
   290  golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
   291  golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
   292  golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
   293  golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
   294  golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
   295  golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
   296  golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
   297  golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
   298  golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
   299  golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
   300  golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
   301  golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
   302  golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
   303  golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
   304  golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
   305  `
   306  
   307  const ambiguousPathsGo = `package ambiguouspaths
   308  
   309  import (
   310  	_ "github.com/adwpc/xmobile/app"
   311  )
   312  
   313  func Dummy() {}
   314  `
   315  
   316  func TestBindWithGoModules(t *testing.T) {
   317  	if runtime.GOOS == "android" || runtime.GOOS == "ios" {
   318  		t.Skipf("gomobile and gobind are not available on %s", runtime.GOOS)
   319  	}
   320  
   321  	dir := t.TempDir()
   322  
   323  	if out, err := exec.Command("go", "build", "-o="+dir, "github.com/adwpc/xmobile/cmd/gobind").CombinedOutput(); err != nil {
   324  		t.Fatalf("%v: %s", err, string(out))
   325  	}
   326  	if out, err := exec.Command("go", "build", "-o="+dir, "github.com/adwpc/xmobile/cmd/gomobile").CombinedOutput(); err != nil {
   327  		t.Fatalf("%v: %s", err, string(out))
   328  	}
   329  	path := dir
   330  	if p := os.Getenv("PATH"); p != "" {
   331  		path += string(filepath.ListSeparator) + p
   332  	}
   333  
   334  	// Create a source package dynamically to avoid go.mod files in this repository. See golang/go#34352 for more details.
   335  	if err := os.Mkdir(filepath.Join(dir, "ambiguouspaths"), 0755); err != nil {
   336  		t.Fatal(err)
   337  	}
   338  	if err := os.WriteFile(filepath.Join(dir, "ambiguouspaths", "go.mod"), []byte(ambiguousPathsGoMod), 0644); err != nil {
   339  		t.Fatal(err)
   340  	}
   341  	if err := os.WriteFile(filepath.Join(dir, "ambiguouspaths", "go.sum"), []byte(ambiguousPathsGoSum), 0644); err != nil {
   342  		t.Fatal(err)
   343  	}
   344  	if err := os.WriteFile(filepath.Join(dir, "ambiguouspaths", "ambiguouspaths.go"), []byte(ambiguousPathsGo), 0644); err != nil {
   345  		t.Fatal(err)
   346  	}
   347  
   348  	for _, target := range []string{"android", "ios"} {
   349  		target := target
   350  		t.Run(target, func(t *testing.T) {
   351  			switch target {
   352  			case "android":
   353  				if _, err := sdkpath.AndroidAPIPath(minAndroidAPI); err != nil {
   354  					t.Skip("No compatible Android API platform found, skipping bind")
   355  				}
   356  			case "ios":
   357  				if !xcodeAvailable() {
   358  					t.Skip("Xcode is missing")
   359  				}
   360  			}
   361  
   362  			var out string
   363  			switch target {
   364  			case "android":
   365  				out = filepath.Join(dir, "cgopkg.aar")
   366  			case "ios":
   367  				out = filepath.Join(dir, "Cgopkg.xcframework")
   368  			}
   369  
   370  			tests := []struct {
   371  				Name string
   372  				Path string
   373  				Dir  string
   374  			}{
   375  				{
   376  					Name: "Absolute Path",
   377  					Path: "github.com/adwpc/xmobile/bind/testdata/cgopkg",
   378  				},
   379  				{
   380  					Name: "Relative Path",
   381  					Path: "./bind/testdata/cgopkg",
   382  					Dir:  filepath.Join("..", ".."),
   383  				},
   384  				{
   385  					Name: "Ambiguous Paths",
   386  					Path: ".",
   387  					Dir:  filepath.Join(dir, "ambiguouspaths"),
   388  				},
   389  			}
   390  
   391  			for _, tc := range tests {
   392  				tc := tc
   393  				t.Run(tc.Name, func(t *testing.T) {
   394  					cmd := exec.Command(filepath.Join(dir, "gomobile"), "bind", "-target="+target, "-o="+out, tc.Path)
   395  					cmd.Env = append(os.Environ(), "PATH="+path, "GO111MODULE=on")
   396  					cmd.Dir = tc.Dir
   397  					if out, err := cmd.CombinedOutput(); err != nil {
   398  						t.Errorf("gomobile bind failed: %v\n%s", err, string(out))
   399  					}
   400  				})
   401  			}
   402  		})
   403  	}
   404  }