bind.go 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355
  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. "encoding/json"
  8. "errors"
  9. "fmt"
  10. "io"
  11. "os"
  12. "os/exec"
  13. "path/filepath"
  14. "strings"
  15. "sync"
  16. "golang.org/x/mobile/internal/sdkpath"
  17. "golang.org/x/mod/modfile"
  18. "golang.org/x/tools/go/packages"
  19. )
  20. var cmdBind = &command{
  21. run: runBind,
  22. Name: "bind",
  23. Usage: "[-target android|" + strings.Join(applePlatforms, "|") + "] [-bootclasspath <path>] [-classpath <path>] [-o output] [build flags] [package]",
  24. Short: "build a library for Android and iOS",
  25. Long: `
  26. Bind generates language bindings for the package named by the import
  27. path, and compiles a library for the named target system.
  28. The -target flag takes either android (the default), or one or more
  29. comma-delimited Apple platforms (` + strings.Join(applePlatforms, ", ") + `).
  30. For -target android, the bind command produces an AAR (Android ARchive)
  31. file that archives the precompiled Java API stub classes, the compiled
  32. shared libraries, and all asset files in the /assets subdirectory under
  33. the package directory. The output is named '<package_name>.aar' by
  34. default. This AAR file is commonly used for binary distribution of an
  35. Android library project and most Android IDEs support AAR import. For
  36. example, in Android Studio (1.2+), an AAR file can be imported using
  37. the module import wizard (File > New > New Module > Import .JAR or
  38. .AAR package), and setting it as a new dependency
  39. (File > Project Structure > Dependencies). This requires 'javac'
  40. (version 1.8+) and Android SDK (API level 16 or newer) to build the
  41. library for Android. The ANDROID_HOME and ANDROID_NDK_HOME environment
  42. variables can be used to specify the Android SDK and NDK if they are
  43. not in the default locations. Use the -javapkg flag to specify the Java
  44. package prefix for the generated classes.
  45. By default, -target=android builds shared libraries for all supported
  46. instruction sets (arm, arm64, 386, amd64). A subset of instruction sets
  47. can be selected by specifying target type with the architecture name. E.g.,
  48. -target=android/arm,android/386.
  49. For Apple -target platforms, gomobile must be run on an OS X machine with
  50. Xcode installed. The generated Objective-C types can be prefixed with the
  51. -prefix flag.
  52. For -target android, the -bootclasspath and -classpath flags are used to
  53. control the bootstrap classpath and the classpath for Go wrappers to Java
  54. classes.
  55. The -v flag provides verbose output, including the list of packages built.
  56. The build flags -a, -n, -x, -gcflags, -ldflags, -tags, -trimpath, and -work
  57. are shared with the build command. For documentation, see 'go help build'.
  58. `,
  59. }
  60. func runBind(cmd *command) error {
  61. cleanup, err := buildEnvInit()
  62. if err != nil {
  63. return err
  64. }
  65. defer cleanup()
  66. args := cmd.flag.Args()
  67. targets, err := parseBuildTarget(buildTarget)
  68. if err != nil {
  69. return fmt.Errorf(`invalid -target=%q: %v`, buildTarget, err)
  70. }
  71. if isAndroidPlatform(targets[0].platform) {
  72. if bindPrefix != "" {
  73. return fmt.Errorf("-prefix is supported only for Apple targets")
  74. }
  75. if _, err := ndkRoot(targets[0]); err != nil {
  76. return err
  77. }
  78. } else {
  79. if bindJavaPkg != "" {
  80. return fmt.Errorf("-javapkg is supported only for android target")
  81. }
  82. }
  83. var gobind string
  84. if !buildN {
  85. gobind, err = exec.LookPath("gobind")
  86. if err != nil {
  87. return errors.New("gobind was not found. Please run gomobile init before trying again")
  88. }
  89. } else {
  90. gobind = "gobind"
  91. }
  92. if len(args) == 0 {
  93. args = append(args, ".")
  94. }
  95. // TODO(ydnar): this should work, unless build tags affect loading a single package.
  96. // Should we try to import packages with different build tags per platform?
  97. pkgs, err := packages.Load(packagesConfig(targets[0]), args...)
  98. if err != nil {
  99. return err
  100. }
  101. // check if any of the package is main
  102. for _, pkg := range pkgs {
  103. if pkg.Name == "main" {
  104. return fmt.Errorf(`binding "main" package (%s) is not supported`, pkg.PkgPath)
  105. }
  106. }
  107. switch {
  108. case isAndroidPlatform(targets[0].platform):
  109. return goAndroidBind(gobind, pkgs, targets)
  110. case isApplePlatform(targets[0].platform):
  111. if !xcodeAvailable() {
  112. return fmt.Errorf("-target=%q requires Xcode", buildTarget)
  113. }
  114. return goAppleBind(gobind, pkgs, targets)
  115. default:
  116. return fmt.Errorf(`invalid -target=%q`, buildTarget)
  117. }
  118. }
  119. var (
  120. bindPrefix string // -prefix
  121. bindJavaPkg string // -javapkg
  122. bindClasspath string // -classpath
  123. bindBootClasspath string // -bootclasspath
  124. )
  125. func init() {
  126. // bind command specific commands.
  127. cmdBind.flag.StringVar(&bindJavaPkg, "javapkg", "",
  128. "specifies custom Java package path prefix. Valid only with -target=android.")
  129. cmdBind.flag.StringVar(&bindPrefix, "prefix", "",
  130. "custom Objective-C name prefix. Valid only with -target=ios.")
  131. cmdBind.flag.StringVar(&bindClasspath, "classpath", "", "The classpath for imported Java classes. Valid only with -target=android.")
  132. cmdBind.flag.StringVar(&bindBootClasspath, "bootclasspath", "", "The bootstrap classpath for imported Java classes. Valid only with -target=android.")
  133. }
  134. func bootClasspath() (string, error) {
  135. if bindBootClasspath != "" {
  136. return bindBootClasspath, nil
  137. }
  138. apiPath, err := sdkpath.AndroidAPIPath(buildAndroidAPI)
  139. if err != nil {
  140. return "", err
  141. }
  142. return filepath.Join(apiPath, "android.jar"), nil
  143. }
  144. func copyFile(dst, src string) error {
  145. if buildX {
  146. printcmd("cp %s %s", src, dst)
  147. }
  148. return writeFile(dst, func(w io.Writer) error {
  149. if buildN {
  150. return nil
  151. }
  152. f, err := os.Open(src)
  153. if err != nil {
  154. return err
  155. }
  156. defer f.Close()
  157. if _, err := io.Copy(w, f); err != nil {
  158. return fmt.Errorf("cp %s %s failed: %v", src, dst, err)
  159. }
  160. return nil
  161. })
  162. }
  163. func writeFile(filename string, generate func(io.Writer) error) error {
  164. if buildV {
  165. fmt.Fprintf(os.Stderr, "write %s\n", filename)
  166. }
  167. if err := mkdir(filepath.Dir(filename)); err != nil {
  168. return err
  169. }
  170. if buildN {
  171. return generate(io.Discard)
  172. }
  173. f, err := os.Create(filename)
  174. if err != nil {
  175. return err
  176. }
  177. defer func() {
  178. if cerr := f.Close(); err == nil {
  179. err = cerr
  180. }
  181. }()
  182. return generate(f)
  183. }
  184. func packagesConfig(t targetInfo) *packages.Config {
  185. config := &packages.Config{}
  186. // Add CGO_ENABLED=1 explicitly since Cgo is disabled when GOOS is different from host OS.
  187. config.Env = append(os.Environ(), "GOARCH="+t.arch, "GOOS="+platformOS(t.platform), "CGO_ENABLED=1")
  188. tags := append(buildTags[:], platformTags(t.platform)...)
  189. if len(tags) > 0 {
  190. config.BuildFlags = []string{"-tags=" + strings.Join(tags, ",")}
  191. }
  192. return config
  193. }
  194. // getModuleVersions returns a module information at the directory src.
  195. func getModuleVersions(targetPlatform string, targetArch string, src string) (*modfile.File, error) {
  196. cmd := exec.Command("go", "list")
  197. cmd.Env = append(os.Environ(), "GOOS="+platformOS(targetPlatform), "GOARCH="+targetArch)
  198. tags := append(buildTags[:], platformTags(targetPlatform)...)
  199. // TODO(hyangah): probably we don't need to add all the dependencies.
  200. cmd.Args = append(cmd.Args, "-m", "-json", "-tags="+strings.Join(tags, ","), "all")
  201. cmd.Dir = src
  202. output, err := cmd.Output()
  203. if err != nil {
  204. // Module information is not available at src.
  205. return nil, nil
  206. }
  207. type Module struct {
  208. Main bool
  209. Path string
  210. Version string
  211. Dir string
  212. Replace *Module
  213. }
  214. f := &modfile.File{}
  215. if err := f.AddModuleStmt("gobind"); err != nil {
  216. return nil, err
  217. }
  218. e := json.NewDecoder(bytes.NewReader(output))
  219. for {
  220. var mod *Module
  221. err := e.Decode(&mod)
  222. if err != nil && err != io.EOF {
  223. return nil, err
  224. }
  225. if mod != nil {
  226. if mod.Replace != nil {
  227. p, v := mod.Replace.Path, mod.Replace.Version
  228. if modfile.IsDirectoryPath(p) {
  229. // replaced by a local directory
  230. p = mod.Replace.Dir
  231. }
  232. if err := f.AddReplace(mod.Path, mod.Version, p, v); err != nil {
  233. return nil, err
  234. }
  235. } else {
  236. // When the version part is empty, the module is local and mod.Dir represents the location.
  237. if v := mod.Version; v == "" {
  238. if err := f.AddReplace(mod.Path, mod.Version, mod.Dir, ""); err != nil {
  239. return nil, err
  240. }
  241. } else {
  242. if err := f.AddRequire(mod.Path, v); err != nil {
  243. return nil, err
  244. }
  245. }
  246. }
  247. }
  248. if err == io.EOF {
  249. break
  250. }
  251. }
  252. v, err := ensureGoVersion()
  253. if err != nil {
  254. return nil, err
  255. }
  256. // ensureGoVersion can return an empty string for a devel version. In this case, use the minimum version.
  257. if v == "" {
  258. v = fmt.Sprintf("go1.%d", minimumGoMinorVersion)
  259. }
  260. if err := f.AddGoStmt(strings.TrimPrefix(v, "go")); err != nil {
  261. return nil, err
  262. }
  263. return f, nil
  264. }
  265. // writeGoMod writes go.mod file at dir when Go modules are used.
  266. func writeGoMod(dir, targetPlatform, targetArch string) error {
  267. m, err := areGoModulesUsed()
  268. if err != nil {
  269. return err
  270. }
  271. // If Go modules are not used, go.mod should not be created because the dependencies might not be compatible with Go modules.
  272. if !m {
  273. return nil
  274. }
  275. return writeFile(filepath.Join(dir, "go.mod"), func(w io.Writer) error {
  276. f, err := getModuleVersions(targetPlatform, targetArch, ".")
  277. if err != nil {
  278. return err
  279. }
  280. if f == nil {
  281. return nil
  282. }
  283. bs, err := f.Format()
  284. if err != nil {
  285. return err
  286. }
  287. if _, err := w.Write(bs); err != nil {
  288. return err
  289. }
  290. return nil
  291. })
  292. }
  293. var (
  294. areGoModulesUsedResult struct {
  295. used bool
  296. err error
  297. }
  298. areGoModulesUsedOnce sync.Once
  299. )
  300. func areGoModulesUsed() (bool, error) {
  301. areGoModulesUsedOnce.Do(func() {
  302. out, err := exec.Command("go", "env", "GOMOD").Output()
  303. if err != nil {
  304. areGoModulesUsedResult.err = err
  305. return
  306. }
  307. outstr := strings.TrimSpace(string(out))
  308. areGoModulesUsedResult.used = outstr != ""
  309. })
  310. return areGoModulesUsedResult.used, areGoModulesUsedResult.err
  311. }