github.com/coming-chat/gomobile@v0.0.0-20220601074111-56995f7d7aba/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/mobile/internal/sdkpath" 20 "golang.org/x/mod/modfile" 21 "golang.org/x/tools/go/packages" 22 ) 23 24 var cmdBind = &command{ 25 run: runBind, 26 Name: "bind", 27 Usage: "[-target android|" + strings.Join(applePlatforms, "|") + "] [-bootclasspath <path>] [-classpath <path>] [-o output] [build flags] [package]", 28 Short: "build a library for Android and iOS", 29 Long: ` 30 Bind generates language bindings for the package named by the import 31 path, and compiles a library for the named target system. 32 33 The -target flag takes either android (the default), or one or more 34 comma-delimited Apple platforms (` + strings.Join(applePlatforms, ", ") + `). 35 36 For -target android, the bind command produces an AAR (Android ARchive) 37 file that archives the precompiled Java API stub classes, the compiled 38 shared libraries, and all asset files in the /assets subdirectory under 39 the package directory. The output is named '<package_name>.aar' by 40 default. This AAR file is commonly used for binary distribution of an 41 Android library project and most Android IDEs support AAR import. For 42 example, in Android Studio (1.2+), an AAR file can be imported using 43 the module import wizard (File > New > New Module > Import .JAR or 44 .AAR package), and setting it as a new dependency 45 (File > Project Structure > Dependencies). This requires 'javac' 46 (version 1.7+) and Android SDK (API level 16 or newer) to build the 47 library for Android. The ANDROID_HOME and ANDROID_NDK_HOME environment 48 variables can be used to specify the Android SDK and NDK if they are 49 not in the default locations. Use the -javapkg flag to specify the Java 50 package prefix for the generated classes. 51 52 By default, -target=android builds shared libraries for all supported 53 instruction sets (arm, arm64, 386, amd64). A subset of instruction sets 54 can be selected by specifying target type with the architecture name. E.g., 55 -target=android/arm,android/386. 56 57 For Apple -target platforms, gomobile must be run on an OS X machine with 58 Xcode installed. The generated Objective-C types can be prefixed with the 59 -prefix flag. 60 61 For -target android, the -bootclasspath and -classpath flags are used to 62 control the bootstrap classpath and the classpath for Go wrappers to Java 63 classes. 64 65 The -v flag provides verbose output, including the list of packages built. 66 67 The build flags -a, -n, -x, -gcflags, -ldflags, -tags, -trimpath, and -work 68 are shared with the build command. For documentation, see 'go help build'. 69 `, 70 } 71 72 func runBind(cmd *command) error { 73 cleanup, err := buildEnvInit() 74 if err != nil { 75 return err 76 } 77 defer cleanup() 78 79 args := cmd.flag.Args() 80 81 targets, err := parseBuildTarget(buildTarget) 82 if err != nil { 83 return fmt.Errorf(`invalid -target=%q: %v`, buildTarget, err) 84 } 85 86 if isAndroidPlatform(targets[0].platform) { 87 if bindPrefix != "" { 88 return fmt.Errorf("-prefix is supported only for Apple targets") 89 } 90 if _, err := ndkRoot(targets[0]); err != nil { 91 return err 92 } 93 } else { 94 if bindJavaPkg != "" { 95 return fmt.Errorf("-javapkg is supported only for android target") 96 } 97 } 98 99 var gobind string 100 if !buildN { 101 gobind, err = exec.LookPath("gobind") 102 if err != nil { 103 return errors.New("gobind was not found. Please run gomobile init before trying again") 104 } 105 } else { 106 gobind = "gobind" 107 } 108 109 if len(args) == 0 { 110 args = append(args, ".") 111 } 112 113 // TODO(ydnar): this should work, unless build tags affect loading a single package. 114 // Should we try to import packages with different build tags per platform? 115 pkgs, err := packages.Load(packagesConfig(targets[0]), args...) 116 if err != nil { 117 return err 118 } 119 120 // check if any of the package is main 121 for _, pkg := range pkgs { 122 if pkg.Name == "main" { 123 return fmt.Errorf(`binding "main" package (%s) is not supported`, pkg.PkgPath) 124 } 125 } 126 127 switch { 128 case isAndroidPlatform(targets[0].platform): 129 return goAndroidBind(gobind, pkgs, targets) 130 case isApplePlatform(targets[0].platform): 131 if !xcodeAvailable() { 132 return fmt.Errorf("-target=%q requires Xcode", buildTarget) 133 } 134 return goAppleBind(gobind, pkgs, targets) 135 default: 136 return fmt.Errorf(`invalid -target=%q`, buildTarget) 137 } 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 := sdkpath.AndroidAPIPath(buildAndroidAPI) 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(t targetInfo) *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="+t.arch, "GOOS="+platformOS(t.platform), "CGO_ENABLED=1") 219 tags := append(buildTags[:], platformTags(t.platform)...) 220 221 if len(tags) > 0 { 222 config.BuildFlags = []string{"-tags=" + strings.Join(tags, ",")} 223 } 224 return config 225 } 226 227 // getModuleVersions returns a module information at the directory src. 228 func getModuleVersions(targetPlatform string, targetArch string, src string) (*modfile.File, error) { 229 cmd := exec.Command("go", "list") 230 cmd.Env = append(os.Environ(), "GOOS="+platformOS(targetPlatform), "GOARCH="+targetArch) 231 232 tags := append(buildTags[:], platformTags(targetPlatform)...) 233 234 // TODO(hyangah): probably we don't need to add all the dependencies. 235 cmd.Args = append(cmd.Args, "-m", "-json", "-tags="+strings.Join(tags, ","), "all") 236 cmd.Dir = src 237 238 output, err := cmd.Output() 239 if err != nil { 240 // Module information is not available at src. 241 return nil, nil 242 } 243 244 type Module struct { 245 Main bool 246 Path string 247 Version string 248 Dir string 249 Replace *Module 250 } 251 252 f := &modfile.File{} 253 f.AddModuleStmt("gobind") 254 e := json.NewDecoder(bytes.NewReader(output)) 255 for { 256 var mod *Module 257 err := e.Decode(&mod) 258 if err != nil && err != io.EOF { 259 return nil, err 260 } 261 if mod != nil { 262 if mod.Replace != nil { 263 p, v := mod.Replace.Path, mod.Replace.Version 264 if modfile.IsDirectoryPath(p) { 265 // replaced by a local directory 266 p = mod.Replace.Dir 267 } 268 f.AddReplace(mod.Path, mod.Version, p, v) 269 } else { 270 // When the version part is empty, the module is local and mod.Dir represents the location. 271 if v := mod.Version; v == "" { 272 f.AddReplace(mod.Path, mod.Version, mod.Dir, "") 273 } else { 274 f.AddRequire(mod.Path, v) 275 } 276 } 277 } 278 if err == io.EOF { 279 break 280 } 281 } 282 return f, nil 283 } 284 285 // writeGoMod writes go.mod file at $WORK/src when Go modules are used. 286 func writeGoMod(dir, targetPlatform, targetArch string) error { 287 m, err := areGoModulesUsed() 288 if err != nil { 289 return err 290 } 291 // If Go modules are not used, go.mod should not be created because the dependencies might not be compatible with Go modules. 292 if !m { 293 return nil 294 } 295 296 return writeFile(filepath.Join(dir, "src", "go.mod"), func(w io.Writer) error { 297 f, err := getModuleVersions(targetPlatform, targetArch, ".") 298 if err != nil { 299 return err 300 } 301 if f == nil { 302 return nil 303 } 304 bs, err := f.Format() 305 if err != nil { 306 return err 307 } 308 if _, err := w.Write(bs); err != nil { 309 return err 310 } 311 return nil 312 }) 313 } 314 315 func areGoModulesUsed() (bool, error) { 316 out, err := exec.Command("go", "env", "GOMOD").Output() 317 if err != nil { 318 return false, err 319 } 320 outstr := strings.TrimSpace(string(out)) 321 if outstr == "" { 322 return false, nil 323 } 324 return true, nil 325 }