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