bind_test.go 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326
  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. package main
  5. import (
  6. "bytes"
  7. "io/ioutil"
  8. "os"
  9. "os/exec"
  10. "path/filepath"
  11. "runtime"
  12. "strings"
  13. "testing"
  14. "text/template"
  15. "golang.org/x/mobile/internal/sdkpath"
  16. )
  17. func TestBindAndroid(t *testing.T) {
  18. platform, err := sdkpath.AndroidAPIPath(minAndroidAPI)
  19. if err != nil {
  20. t.Skip("No compatible Android API platform found, skipping bind")
  21. }
  22. // platform is a path like "/path/to/Android/sdk/platforms/android-32"
  23. components := strings.Split(platform, string(filepath.Separator))
  24. if len(components) < 2 {
  25. t.Fatalf("API path is too short: %s", platform)
  26. }
  27. components = components[len(components)-2:]
  28. platformRel := filepath.Join("$ANDROID_HOME", components[0], components[1])
  29. defer func() {
  30. xout = os.Stderr
  31. buildN = false
  32. buildX = false
  33. buildO = ""
  34. buildTarget = ""
  35. bindJavaPkg = ""
  36. }()
  37. buildN = true
  38. buildX = true
  39. buildO = "asset.aar"
  40. buildTarget = "android/arm"
  41. tests := []struct {
  42. javaPkg string
  43. }{
  44. {
  45. // Empty javaPkg
  46. },
  47. {
  48. javaPkg: "com.example.foo",
  49. },
  50. }
  51. for _, tc := range tests {
  52. bindJavaPkg = tc.javaPkg
  53. buf := new(bytes.Buffer)
  54. xout = buf
  55. gopath = filepath.SplitList(goEnv("GOPATH"))[0]
  56. if goos == "windows" {
  57. os.Setenv("HOMEDRIVE", "C:")
  58. }
  59. cmdBind.flag.Parse([]string{"golang.org/x/mobile/asset"})
  60. err := runBind(cmdBind)
  61. if err != nil {
  62. t.Log(buf.String())
  63. t.Fatal(err)
  64. }
  65. got := filepath.ToSlash(buf.String())
  66. output, err := defaultOutputData("")
  67. if err != nil {
  68. t.Fatal(err)
  69. }
  70. data := struct {
  71. outputData
  72. AndroidPlatform string
  73. JavaPkg string
  74. }{
  75. outputData: output,
  76. AndroidPlatform: platformRel,
  77. JavaPkg: tc.javaPkg,
  78. }
  79. wantBuf := new(bytes.Buffer)
  80. if err := bindAndroidTmpl.Execute(wantBuf, data); err != nil {
  81. t.Errorf("%+v: computing diff failed: %v", tc, err)
  82. continue
  83. }
  84. diff, err := diff(got, wantBuf.String())
  85. if err != nil {
  86. t.Errorf("%+v: computing diff failed: %v", tc, err)
  87. continue
  88. }
  89. if diff != "" {
  90. t.Errorf("%+v: unexpected output:\n%s", tc, diff)
  91. }
  92. }
  93. }
  94. func TestBindApple(t *testing.T) {
  95. if !xcodeAvailable() {
  96. t.Skip("Xcode is missing")
  97. }
  98. defer func() {
  99. xout = os.Stderr
  100. buildN = false
  101. buildX = false
  102. buildO = ""
  103. buildTarget = ""
  104. bindPrefix = ""
  105. }()
  106. buildN = true
  107. buildX = true
  108. buildO = "Asset.xcframework"
  109. buildTarget = "ios/arm64"
  110. tests := []struct {
  111. prefix string
  112. out string
  113. }{
  114. {
  115. // empty prefix
  116. },
  117. {
  118. prefix: "Foo",
  119. },
  120. {
  121. out: "Abcde.xcframework",
  122. },
  123. }
  124. for _, tc := range tests {
  125. bindPrefix = tc.prefix
  126. if tc.out != "" {
  127. buildO = tc.out
  128. }
  129. buf := new(bytes.Buffer)
  130. xout = buf
  131. gopath = filepath.SplitList(goEnv("GOPATH"))[0]
  132. if goos == "windows" {
  133. os.Setenv("HOMEDRIVE", "C:")
  134. }
  135. cmdBind.flag.Parse([]string{"golang.org/x/mobile/asset"})
  136. if err := runBind(cmdBind); err != nil {
  137. t.Log(buf.String())
  138. t.Fatal(err)
  139. }
  140. got := filepath.ToSlash(buf.String())
  141. output, err := defaultOutputData("")
  142. if err != nil {
  143. t.Fatal(err)
  144. }
  145. data := struct {
  146. outputData
  147. Output string
  148. Prefix string
  149. }{
  150. outputData: output,
  151. Output: buildO[:len(buildO)-len(".xcframework")],
  152. Prefix: tc.prefix,
  153. }
  154. wantBuf := new(bytes.Buffer)
  155. if err := bindAppleTmpl.Execute(wantBuf, data); err != nil {
  156. t.Errorf("%+v: computing diff failed: %v", tc, err)
  157. continue
  158. }
  159. diff, err := diff(got, wantBuf.String())
  160. if err != nil {
  161. t.Errorf("%+v: computing diff failed: %v", tc, err)
  162. continue
  163. }
  164. if diff != "" {
  165. t.Errorf("%+v: unexpected output:\n%s", tc, diff)
  166. }
  167. }
  168. }
  169. var bindAndroidTmpl = template.Must(template.New("output").Parse(`GOMOBILE={{.GOPATH}}/pkg/gomobile
  170. WORK=$WORK
  171. GOOS=android CGO_ENABLED=1 gobind -lang=go,java -outdir=$WORK{{if .JavaPkg}} -javapkg={{.JavaPkg}}{{end}} golang.org/x/mobile/asset
  172. mkdir -p $WORK/src-android-arm
  173. 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
  174. 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
  175. PWD=$WORK/java javac -d $WORK/javac-output -source 1.7 -target 1.7 -bootclasspath {{.AndroidPlatform}}/android.jar *.java
  176. jar c -C $WORK/javac-output .
  177. `))
  178. var bindAppleTmpl = template.Must(template.New("output").Parse(`GOMOBILE={{.GOPATH}}/pkg/gomobile
  179. WORK=$WORK
  180. rm -r -f "{{.Output}}.xcframework"
  181. GOOS=ios CGO_ENABLED=1 gobind -lang=go,objc -outdir=$WORK/ios -tags=ios{{if .Prefix}} -prefix={{.Prefix}}{{end}} golang.org/x/mobile/asset
  182. mkdir -p $WORK/ios/src-arm64
  183. 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
  184. 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
  185. mkdir -p $WORK/ios/iphoneos/{{.Output}}.framework/Versions/A/Headers
  186. ln -s A $WORK/ios/iphoneos/{{.Output}}.framework/Versions/Current
  187. ln -s Versions/Current/Headers $WORK/ios/iphoneos/{{.Output}}.framework/Headers
  188. ln -s Versions/Current/{{.Output}} $WORK/ios/iphoneos/{{.Output}}.framework/{{.Output}}
  189. xcrun lipo $WORK/{{.Output}}-ios-arm64.a -create -o $WORK/ios/iphoneos/{{.Output}}.framework/Versions/A/{{.Output}}
  190. cp $WORK/ios/src/gobind/{{.Prefix}}Asset.objc.h $WORK/ios/iphoneos/{{.Output}}.framework/Versions/A/Headers/{{.Prefix}}Asset.objc.h
  191. mkdir -p $WORK/ios/iphoneos/{{.Output}}.framework/Versions/A/Headers
  192. cp $WORK/ios/src/gobind/Universe.objc.h $WORK/ios/iphoneos/{{.Output}}.framework/Versions/A/Headers/Universe.objc.h
  193. mkdir -p $WORK/ios/iphoneos/{{.Output}}.framework/Versions/A/Headers
  194. cp $WORK/ios/src/gobind/ref.h $WORK/ios/iphoneos/{{.Output}}.framework/Versions/A/Headers/ref.h
  195. mkdir -p $WORK/ios/iphoneos/{{.Output}}.framework/Versions/A/Headers
  196. mkdir -p $WORK/ios/iphoneos/{{.Output}}.framework/Versions/A/Headers
  197. mkdir -p $WORK/ios/iphoneos/{{.Output}}.framework/Versions/A/Resources
  198. ln -s Versions/Current/Resources $WORK/ios/iphoneos/{{.Output}}.framework/Resources
  199. mkdir -p $WORK/ios/iphoneos/{{.Output}}.framework/Resources
  200. mkdir -p $WORK/ios/iphoneos/{{.Output}}.framework/Versions/A/Modules
  201. ln -s Versions/Current/Modules $WORK/ios/iphoneos/{{.Output}}.framework/Modules
  202. xcodebuild -create-xcframework -framework $WORK/ios/iphoneos/{{.Output}}.framework -output {{.Output}}.xcframework
  203. `))
  204. func TestBindAppleAll(t *testing.T) {
  205. if !xcodeAvailable() {
  206. t.Skip("Xcode is missing")
  207. }
  208. defer func() {
  209. xout = os.Stderr
  210. buildN = false
  211. buildX = false
  212. buildO = ""
  213. buildTarget = ""
  214. bindPrefix = ""
  215. }()
  216. buildN = true
  217. buildX = true
  218. buildO = "Asset.xcframework"
  219. buildTarget = "ios"
  220. buf := new(bytes.Buffer)
  221. xout = buf
  222. gopath = filepath.SplitList(goEnv("GOPATH"))[0]
  223. if goos == "windows" {
  224. os.Setenv("HOMEDRIVE", "C:")
  225. }
  226. cmdBind.flag.Parse([]string{"golang.org/x/mobile/asset"})
  227. if err := runBind(cmdBind); err != nil {
  228. t.Log(buf.String())
  229. t.Fatal(err)
  230. }
  231. }
  232. func TestBindWithGoModules(t *testing.T) {
  233. if runtime.GOOS == "android" || runtime.GOOS == "ios" {
  234. t.Skipf("gomobile and gobind are not available on %s", runtime.GOOS)
  235. }
  236. dir, err := ioutil.TempDir("", "gomobile-test")
  237. if err != nil {
  238. t.Fatal(err)
  239. }
  240. defer os.RemoveAll(dir)
  241. if out, err := exec.Command("go", "build", "-o="+dir, "golang.org/x/mobile/cmd/gobind").CombinedOutput(); err != nil {
  242. t.Fatalf("%v: %s", err, string(out))
  243. }
  244. if out, err := exec.Command("go", "build", "-o="+dir, "golang.org/x/mobile/cmd/gomobile").CombinedOutput(); err != nil {
  245. t.Fatalf("%v: %s", err, string(out))
  246. }
  247. path := dir
  248. if p := os.Getenv("PATH"); p != "" {
  249. path += string(filepath.ListSeparator) + p
  250. }
  251. for _, target := range []string{"android", "ios"} {
  252. t.Run(target, func(t *testing.T) {
  253. switch target {
  254. case "android":
  255. if _, err := sdkpath.AndroidAPIPath(minAndroidAPI); err != nil {
  256. t.Skip("No compatible Android API platform found, skipping bind")
  257. }
  258. case "ios":
  259. if !xcodeAvailable() {
  260. t.Skip("Xcode is missing")
  261. }
  262. }
  263. var out string
  264. switch target {
  265. case "android":
  266. out = filepath.Join(dir, "cgopkg.aar")
  267. case "ios":
  268. out = filepath.Join(dir, "Cgopkg.xcframework")
  269. }
  270. tests := []struct {
  271. Name string
  272. Path string
  273. Dir string
  274. }{
  275. {
  276. Name: "Absolute Path",
  277. Path: "golang.org/x/mobile/bind/testdata/cgopkg",
  278. },
  279. {
  280. Name: "Relative Path",
  281. Path: "./bind/testdata/cgopkg",
  282. Dir: filepath.Join("..", ".."),
  283. },
  284. }
  285. for _, tc := range tests {
  286. tc := tc
  287. t.Run(tc.Name, func(t *testing.T) {
  288. cmd := exec.Command(filepath.Join(dir, "gomobile"), "bind", "-target="+target, "-o="+out, tc.Path)
  289. cmd.Env = append(os.Environ(), "PATH="+path, "GO111MODULE=on")
  290. cmd.Dir = tc.Dir
  291. if out, err := cmd.CombinedOutput(); err != nil {
  292. t.Errorf("gomobile bind failed: %v\n%s", err, string(out))
  293. }
  294. })
  295. }
  296. })
  297. }
  298. }