bind.go 9.1 KB

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