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