github.com/graybobo/golang.org-package-offline-cache@v0.0.0-20200626051047-6608995c132f/x/mobile/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 "fmt" 9 "go/ast" 10 "go/build" 11 "go/importer" 12 "go/token" 13 "go/types" 14 "io" 15 "io/ioutil" 16 "os" 17 "path" 18 "path/filepath" 19 "strings" 20 21 "golang.org/x/mobile/bind" 22 ) 23 24 // ctx, pkg, tmpdir in build.go 25 26 var cmdBind = &command{ 27 run: runBind, 28 Name: "bind", 29 Usage: "[-target android|ios] [-o output] [build flags] [package]", 30 Short: "build a library for Android and iOS", 31 Long: ` 32 Bind generates language bindings for the package named by the import 33 path, and compiles a library for the named target system. 34 35 The -target flag takes a target system name, either android (the 36 default) or ios. 37 38 For -target android, the bind command produces an AAR (Android ARchive) 39 file that archives the precompiled Java API stub classes, the compiled 40 shared libraries, and all asset files in the /assets subdirectory under 41 the package directory. The output is named '<package_name>.aar' by 42 default. This AAR file is commonly used for binary distribution of an 43 Android library project and most Android IDEs support AAR import. For 44 example, in Android Studio (1.2+), an AAR file can be imported using 45 the module import wizard (File > New > New Module > Import .JAR or 46 .AAR package), and setting it as a new dependency 47 (File > Project Structure > Dependencies). This requires 'javac' 48 (version 1.7+) and Android SDK (API level 9 or newer) to build the 49 library for Android. The environment variable ANDROID_HOME must be set 50 to the path to Android SDK. The generated Java class is in the java 51 package 'go.<package_name>' unless -javapkg flag is specified. 52 53 For -target ios, gomobile must be run on an OS X machine with Xcode 54 installed. Support is not complete. The generated Objective-C types 55 are prefixed with 'Go' unless the -prefix flag is provided. 56 57 The -v flag provides verbose output, including the list of packages built. 58 59 The build flags -a, -n, -x, -gcflags, -ldflags, -tags, and -work 60 are shared with the build command. For documentation, see 'go help build'. 61 `, 62 } 63 64 func runBind(cmd *command) error { 65 cleanup, err := buildEnvInit() 66 if err != nil { 67 return err 68 } 69 defer cleanup() 70 71 args := cmd.flag.Args() 72 73 targetOS, targetArchs, err := parseBuildTarget(buildTarget) 74 if err != nil { 75 return fmt.Errorf(`invalid -target=%q: %v`, buildTarget, err) 76 } 77 78 ctx.GOARCH = "arm" 79 ctx.GOOS = targetOS 80 81 if bindJavaPkg != "" && ctx.GOOS != "android" { 82 return fmt.Errorf("-javapkg is supported only for android target") 83 } 84 if bindPrefix != "" && ctx.GOOS != "darwin" { 85 return fmt.Errorf("-prefix is supported only for ios target") 86 } 87 88 var pkgs []*build.Package 89 switch len(args) { 90 case 0: 91 pkgs = make([]*build.Package, 1) 92 pkgs[0], err = ctx.ImportDir(cwd, build.ImportComment) 93 default: 94 pkgs, err = importPackages(args) 95 } 96 if err != nil { 97 return err 98 } 99 100 switch targetOS { 101 case "android": 102 return goAndroidBind(pkgs, targetArchs) 103 case "darwin": 104 // TODO: use targetArchs? 105 return goIOSBind(pkgs) 106 default: 107 return fmt.Errorf(`invalid -target=%q`, buildTarget) 108 } 109 } 110 111 func importPackages(args []string) ([]*build.Package, error) { 112 pkgs := make([]*build.Package, len(args)) 113 for i, path := range args { 114 var err error 115 if pkgs[i], err = ctx.Import(path, cwd, build.ImportComment); err != nil { 116 return nil, fmt.Errorf("package %q: %v", path, err) 117 } 118 } 119 return pkgs, nil 120 } 121 122 var ( 123 bindPrefix string // -prefix 124 bindJavaPkg string // -javapkg 125 ) 126 127 func init() { 128 // bind command specific commands. 129 cmdBind.flag.StringVar(&bindJavaPkg, "javapkg", "", 130 "specifies custom Java package path used instead of the default 'go.<go package name>'. Valid only with -target=android.") 131 cmdBind.flag.StringVar(&bindPrefix, "prefix", "", 132 "custom Objective-C name prefix used instead of the default 'Go'. Valid only with -lang=ios.") 133 } 134 135 type binder struct { 136 files []*ast.File 137 fset *token.FileSet 138 pkgs []*types.Package 139 } 140 141 func (b *binder) GenObjc(pkg *types.Package, outdir string) (string, error) { 142 const bindPrefixDefault = "Go" 143 if bindPrefix == "" { 144 bindPrefix = bindPrefixDefault 145 } 146 name := strings.Title(pkg.Name()) 147 bindOption := "-lang=objc" 148 if bindPrefix != bindPrefixDefault { 149 bindOption += " -prefix=" + bindPrefix 150 } 151 152 fileBase := bindPrefix + name 153 mfile := filepath.Join(outdir, fileBase+".m") 154 hfile := filepath.Join(outdir, fileBase+".h") 155 156 generate := func(w io.Writer) error { 157 if buildX { 158 printcmd("gobind %s -outdir=%s %s", bindOption, outdir, pkg.Path()) 159 } 160 if buildN { 161 return nil 162 } 163 return bind.GenObjc(w, b.fset, pkg, bindPrefix, false) 164 } 165 if err := writeFile(mfile, generate); err != nil { 166 return "", err 167 } 168 generate = func(w io.Writer) error { 169 if buildN { 170 return nil 171 } 172 return bind.GenObjc(w, b.fset, pkg, bindPrefix, true) 173 } 174 if err := writeFile(hfile, generate); err != nil { 175 return "", err 176 } 177 178 objcPkg, err := ctx.Import("golang.org/x/mobile/bind/objc", "", build.FindOnly) 179 if err != nil { 180 return "", err 181 } 182 if err := copyFile(filepath.Join(outdir, "seq.h"), filepath.Join(objcPkg.Dir, "seq.h")); err != nil { 183 return "", err 184 } 185 return fileBase, nil 186 } 187 188 func (b *binder) GenJava(pkg *types.Package, outdir string) error { 189 className := strings.Title(pkg.Name()) 190 javaFile := filepath.Join(outdir, className+".java") 191 bindOption := "-lang=java" 192 if bindJavaPkg != "" { 193 bindOption += " -javapkg=" + bindJavaPkg 194 } 195 196 generate := func(w io.Writer) error { 197 if buildX { 198 printcmd("gobind %s -outdir=%s %s", bindOption, outdir, pkg.Path()) 199 } 200 if buildN { 201 return nil 202 } 203 return bind.GenJava(w, b.fset, pkg, bindJavaPkg) 204 } 205 if err := writeFile(javaFile, generate); err != nil { 206 return err 207 } 208 return nil 209 } 210 211 func (b *binder) GenGo(pkg *types.Package, outdir string) error { 212 pkgName := "go_" + pkg.Name() 213 outdir = filepath.Join(outdir, pkgName) 214 goFile := filepath.Join(outdir, pkgName+"main.go") 215 216 generate := func(w io.Writer) error { 217 if buildX { 218 printcmd("gobind -lang=go -outdir=%s %s", outdir, pkg.Path()) 219 } 220 if buildN { 221 return nil 222 } 223 return bind.GenGo(w, b.fset, pkg) 224 } 225 if err := writeFile(goFile, generate); err != nil { 226 return err 227 } 228 return nil 229 } 230 231 func copyFile(dst, src string) error { 232 if buildX { 233 printcmd("cp %s %s", src, dst) 234 } 235 return writeFile(dst, func(w io.Writer) error { 236 if buildN { 237 return nil 238 } 239 f, err := os.Open(src) 240 if err != nil { 241 return err 242 } 243 defer f.Close() 244 245 if _, err := io.Copy(w, f); err != nil { 246 return fmt.Errorf("cp %s %s failed: %v", src, dst, err) 247 } 248 return nil 249 }) 250 } 251 252 func writeFile(filename string, generate func(io.Writer) error) error { 253 if buildV { 254 fmt.Fprintf(os.Stderr, "write %s\n", filename) 255 } 256 257 err := mkdir(filepath.Dir(filename)) 258 if err != nil { 259 return err 260 } 261 262 if buildN { 263 return generate(ioutil.Discard) 264 } 265 266 f, err := os.Create(filename) 267 if err != nil { 268 return err 269 } 270 defer func() { 271 if cerr := f.Close(); err == nil { 272 err = cerr 273 } 274 }() 275 276 return generate(f) 277 } 278 279 func loadExportData(pkgs []*build.Package, env []string, args ...string) ([]*types.Package, error) { 280 // Compile the package. This will produce good errors if the package 281 // doesn't typecheck for some reason, and is a necessary step to 282 // building the final output anyway. 283 paths := make([]string, len(pkgs)) 284 for i, p := range pkgs { 285 paths[i] = p.ImportPath 286 } 287 if err := goInstall(paths, env, args...); err != nil { 288 return nil, err 289 } 290 291 goos, goarch := getenv(env, "GOOS"), getenv(env, "GOARCH") 292 293 // Assemble a fake GOPATH and trick go/importer into using it. 294 // Ideally the importer package would let us provide this to 295 // it somehow, but this works with what's in Go 1.5 today and 296 // gives us access to the gcimporter package without us having 297 // to make a copy of it. 298 fakegopath := filepath.Join(tmpdir, "fakegopath") 299 if err := removeAll(fakegopath); err != nil { 300 return nil, err 301 } 302 if err := mkdir(filepath.Join(fakegopath, "pkg")); err != nil { 303 return nil, err 304 } 305 typePkgs := make([]*types.Package, len(pkgs)) 306 for i, p := range pkgs { 307 importPath := p.ImportPath 308 src := filepath.Join(pkgdir(env), importPath+".a") 309 dst := filepath.Join(fakegopath, "pkg/"+goos+"_"+goarch+"/"+importPath+".a") 310 if err := copyFile(dst, src); err != nil { 311 return nil, err 312 } 313 if buildN { 314 typePkgs[i] = types.NewPackage(importPath, path.Base(importPath)) 315 continue 316 } 317 oldDefault := build.Default 318 build.Default = ctx // copy 319 build.Default.GOARCH = goarch 320 build.Default.GOPATH = fakegopath 321 p, err := importer.Default().Import(importPath) 322 build.Default = oldDefault 323 if err != nil { 324 return nil, err 325 } 326 typePkgs[i] = p 327 } 328 return typePkgs, nil 329 } 330 331 func newBinder(pkgs []*types.Package) (*binder, error) { 332 for _, pkg := range pkgs { 333 if pkg.Name() == "main" { 334 return nil, fmt.Errorf("package %q (%q): can only bind a library package", pkg.Name(), pkg.Path()) 335 } 336 } 337 b := &binder{ 338 fset: token.NewFileSet(), 339 pkgs: pkgs, 340 } 341 return b, nil 342 }