github.com/c-darwin/mobile@v0.0.0-20160313183840-ff625c46f7c9/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 "errors" 9 "fmt" 10 "go/ast" 11 "go/build" 12 "go/parser" 13 "go/scanner" 14 "go/token" 15 "go/types" 16 "io" 17 "io/ioutil" 18 "os" 19 "path/filepath" 20 "strings" 21 22 "github.com/c-darwin/mobile/bind" 23 "github.com/c-darwin/mobile/internal/loader" 24 ) 25 26 // ctx, pkg, tmpdir in build.go 27 28 var cmdBind = &command{ 29 run: runBind, 30 Name: "bind", 31 Usage: "[-target android|ios] [-o output] [build flags] [package]", 32 Short: "build a shared library for android APK and iOS app", 33 Long: ` 34 Bind generates language bindings for the package named by the import 35 path, and compiles a library for the named target system. 36 37 The -target flag takes a target system name, either android (the 38 default) or ios. 39 40 For -target android, the bind command produces an AAR (Android ARchive) 41 file that archives the precompiled Java API stub classes, the compiled 42 shared libraries, and all asset files in the /assets subdirectory under 43 the package directory. The output is named '<package_name>.aar' by 44 default. This AAR file is commonly used for binary distribution of an 45 Android library project and most Android IDEs support AAR import. For 46 example, in Android Studio (1.2+), an AAR file can be imported using 47 the module import wizard (File > New > New Module > Import .JAR or 48 .AAR package), and setting it as a new dependency 49 (File > Project Structure > Dependencies). This requires 'javac' 50 (version 1.7+) and Android SDK (API level 9 or newer) to build the 51 library for Android. The environment variable ANDROID_HOME must be set 52 to the path to Android SDK. 53 54 For -target ios, gomobile must be run on an OS X machine with Xcode 55 installed. Support is not complete. 56 57 The -v flag provides verbose output, including the list of packages built. 58 59 The build flags -a, -i, -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 ctx.GOARCH = "arm" 74 switch buildTarget { 75 case "android": 76 ctx.GOOS = "android" 77 case "ios": 78 ctx.GOOS = "darwin" 79 default: 80 return fmt.Errorf(`unknown -target, %q.`, buildTarget) 81 } 82 83 var pkg *build.Package 84 switch len(args) { 85 case 0: 86 pkg, err = ctx.ImportDir(cwd, build.ImportComment) 87 case 1: 88 pkg, err = ctx.Import(args[0], cwd, build.ImportComment) 89 default: 90 cmd.usage() 91 os.Exit(1) 92 } 93 if err != nil { 94 return err 95 } 96 97 switch buildTarget { 98 case "android": 99 return goAndroidBind(pkg) 100 case "ios": 101 return goIOSBind(pkg) 102 default: 103 return fmt.Errorf(`unknown -target, %q.`, buildTarget) 104 } 105 } 106 107 type binder struct { 108 files []*ast.File 109 fset *token.FileSet 110 pkg *types.Package 111 } 112 113 func (b *binder) GenObjc(outdir string) error { 114 name := strings.Title(b.pkg.Name()) 115 mfile := filepath.Join(outdir, "Go"+name+".m") 116 hfile := filepath.Join(outdir, "Go"+name+".h") 117 118 if buildX { 119 printcmd("gobind -lang=objc %s > %s", b.pkg.Path(), mfile) 120 } 121 122 generate := func(w io.Writer) error { 123 return bind.GenObjc(w, b.fset, b.pkg, false) 124 } 125 if err := writeFile(mfile, generate); err != nil { 126 return err 127 } 128 generate = func(w io.Writer) error { 129 return bind.GenObjc(w, b.fset, b.pkg, true) 130 } 131 if err := writeFile(hfile, generate); err != nil { 132 return err 133 } 134 135 objcPkg, err := ctx.Import("github.com/c-darwin/mobile/bind/objc", "", build.FindOnly) 136 if err != nil { 137 return err 138 } 139 return copyFile(filepath.Join(outdir, "seq.h"), filepath.Join(objcPkg.Dir, "seq.h")) 140 } 141 142 func (b *binder) GenJava(outdir string) error { 143 className := strings.Title(b.pkg.Name()) 144 javaFile := filepath.Join(outdir, className+".java") 145 146 if buildX { 147 printcmd("gobind -lang=java %s > %s", b.pkg.Path(), javaFile) 148 } 149 150 generate := func(w io.Writer) error { 151 return bind.GenJava(w, b.fset, b.pkg) 152 } 153 if err := writeFile(javaFile, generate); err != nil { 154 return err 155 } 156 return nil 157 } 158 159 func (b *binder) GenGo(outdir string) error { 160 pkgName := "go_" + b.pkg.Name() 161 goFile := filepath.Join(outdir, pkgName, pkgName+"main.go") 162 163 if buildX { 164 printcmd("gobind -lang=go %s > %s", b.pkg.Path(), goFile) 165 } 166 167 generate := func(w io.Writer) error { 168 return bind.GenGo(w, b.fset, b.pkg) 169 } 170 if err := writeFile(goFile, generate); err != nil { 171 return err 172 } 173 return nil 174 } 175 176 func copyFile(dst, src string) error { 177 if buildX { 178 printcmd("cp %s %s", src, dst) 179 } 180 return writeFile(dst, func(w io.Writer) error { 181 if buildN { 182 return nil 183 } 184 f, err := os.Open(src) 185 if err != nil { 186 return err 187 } 188 defer f.Close() 189 190 if _, err := io.Copy(w, f); err != nil { 191 return fmt.Errorf("cp %s %s failed: %v", src, dst, err) 192 } 193 return nil 194 }) 195 } 196 197 func writeFile(filename string, generate func(io.Writer) error) error { 198 if buildV { 199 fmt.Fprintf(os.Stderr, "write %s\n", filename) 200 } 201 202 err := mkdir(filepath.Dir(filename)) 203 if err != nil { 204 return err 205 } 206 207 if buildN { 208 return generate(ioutil.Discard) 209 } 210 211 f, err := os.Create(filename) 212 if err != nil { 213 return err 214 } 215 defer func() { 216 if cerr := f.Close(); err == nil { 217 err = cerr 218 } 219 }() 220 221 return generate(f) 222 } 223 224 func newBinder(bindPkg *build.Package) (*binder, error) { 225 if bindPkg.Name == "main" { 226 return nil, fmt.Errorf("package %q: can only bind a library package", bindPkg.Name) 227 } 228 229 fset := token.NewFileSet() 230 231 hasErr := false 232 var files []*ast.File 233 for _, filename := range bindPkg.GoFiles { 234 p := filepath.Join(bindPkg.Dir, filename) 235 file, err := parser.ParseFile(fset, p, nil, parser.AllErrors) 236 if err != nil { 237 hasErr = true 238 if list, _ := err.(scanner.ErrorList); len(list) > 0 { 239 for _, err := range list { 240 fmt.Fprintln(os.Stderr, err) 241 } 242 } else { 243 fmt.Fprintln(os.Stderr, err) 244 } 245 } 246 files = append(files, file) 247 } 248 249 if hasErr { 250 return nil, errors.New("package parsing failed.") 251 } 252 253 conf := loader.Config{ 254 Fset: fset, 255 AllowErrors: true, 256 } 257 conf.TypeChecker.IgnoreFuncBodies = true 258 conf.TypeChecker.FakeImportC = true 259 conf.TypeChecker.DisableUnusedImportCheck = true 260 var tcErrs []error 261 conf.TypeChecker.Error = func(err error) { 262 tcErrs = append(tcErrs, err) 263 } 264 265 conf.CreateFromFiles(bindPkg.ImportPath, files...) 266 program, err := conf.Load() 267 if err != nil { 268 for _, err := range tcErrs { 269 fmt.Fprintln(os.Stderr, err) 270 } 271 return nil, err 272 } 273 b := &binder{ 274 files: files, 275 fset: fset, 276 pkg: program.Created[0].Pkg, 277 } 278 return b, nil 279 }