| OLD | NEW |
| (Empty) |
| 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 "io" | |
| 16 "io/ioutil" | |
| 17 "os" | |
| 18 "path/filepath" | |
| 19 "text/template" | |
| 20 "unicode" | |
| 21 "unicode/utf8" | |
| 22 | |
| 23 "golang.org/x/mobile/bind" | |
| 24 "golang.org/x/tools/go/loader" | |
| 25 "golang.org/x/tools/go/types" | |
| 26 ) | |
| 27 | |
| 28 // ctx, pkg, ndkccpath, tmpdir in build.go | |
| 29 | |
| 30 var cmdBind = &command{ | |
| 31 run: runBind, | |
| 32 Name: "bind", | |
| 33 Usage: "[package]", | |
| 34 Short: "build a shared library for android APK and/or iOS app", | |
| 35 Long: ` | |
| 36 Bind generates language bindings like gobind (golang.org/x/mobile/cmd/gobind) | |
| 37 for a package and builds a shared library for each platform from the go binding | |
| 38 code. | |
| 39 | |
| 40 The -outdir flag specifies the output directory and is required. | |
| 41 | |
| 42 For Android, the bind command will place the generated Java API stubs and the | |
| 43 compiled shared libraries in the android subdirectory of the following layout. | |
| 44 | |
| 45 <outdir>/android | |
| 46 libs/ | |
| 47 armeabi-v7a/libgojni.so | |
| 48 ... | |
| 49 src/main/java/go/ | |
| 50 Seq.java | |
| 51 Go.java | |
| 52 mypackage/Mypackage.java | |
| 53 | |
| 54 The -v flag provides verbose output, including the list of packages built. | |
| 55 | |
| 56 These build flags are shared by the build command. | |
| 57 For documentation, see 'go help build': | |
| 58 -a | |
| 59 -i | |
| 60 -n | |
| 61 -x | |
| 62 -tags 'tag list' | |
| 63 `, | |
| 64 } | |
| 65 | |
| 66 // TODO: -mobile | |
| 67 | |
| 68 var bindOutdir *string // -outdir | |
| 69 func init() { | |
| 70 bindOutdir = cmdBind.flag.String("outdir", "", "output directory. Defaul
t is the current directory.") | |
| 71 } | |
| 72 | |
| 73 func runBind(cmd *command) error { | |
| 74 cwd, err := os.Getwd() | |
| 75 if err != nil { | |
| 76 panic(err) | |
| 77 } | |
| 78 args := cmd.flag.Args() | |
| 79 | |
| 80 var bindPkg *build.Package | |
| 81 switch len(args) { | |
| 82 case 0: | |
| 83 bindPkg, err = ctx.ImportDir(cwd, build.ImportComment) | |
| 84 case 1: | |
| 85 bindPkg, err = ctx.Import(args[0], cwd, build.ImportComment) | |
| 86 default: | |
| 87 cmd.usage() | |
| 88 os.Exit(1) | |
| 89 } | |
| 90 if err != nil { | |
| 91 return err | |
| 92 } | |
| 93 | |
| 94 if *bindOutdir == "" { | |
| 95 return fmt.Errorf("-outdir is required") | |
| 96 } | |
| 97 | |
| 98 if !buildN { | |
| 99 sentinel := filepath.Join(*bindOutdir, "gomobile-bind-sentinel") | |
| 100 if err := ioutil.WriteFile(sentinel, []byte("write test"), 0644)
; err != nil { | |
| 101 return fmt.Errorf("output directory %q not writable", *b
indOutdir) | |
| 102 } | |
| 103 os.Remove(sentinel) | |
| 104 } | |
| 105 | |
| 106 if buildN { | |
| 107 tmpdir = "$WORK" | |
| 108 } else { | |
| 109 tmpdir, err = ioutil.TempDir("", "gomobile-bind-work-") | |
| 110 if err != nil { | |
| 111 return err | |
| 112 } | |
| 113 } | |
| 114 defer removeAll(tmpdir) | |
| 115 if buildX { | |
| 116 fmt.Fprintln(os.Stderr, "WORK="+tmpdir) | |
| 117 } | |
| 118 | |
| 119 binder, err := newBinder(bindPkg) | |
| 120 if err != nil { | |
| 121 return err | |
| 122 } | |
| 123 | |
| 124 if err := binder.GenGo(tmpdir); err != nil { | |
| 125 return err | |
| 126 } | |
| 127 | |
| 128 pathJoin := func(a, b string) string { | |
| 129 return filepath.Join(a, filepath.FromSlash(b)) | |
| 130 } | |
| 131 | |
| 132 mainFile := pathJoin(tmpdir, "androidlib/main.go") | |
| 133 err = writeFile(mainFile, func(w io.Writer) error { | |
| 134 return androidMainTmpl.Execute(w, "../go_"+binder.pkg.Name()) | |
| 135 }) | |
| 136 if err != nil { | |
| 137 return fmt.Errorf("failed to create the main package for android
: %v", err) | |
| 138 } | |
| 139 | |
| 140 androidOutdir := pathJoin(*bindOutdir, "android") | |
| 141 | |
| 142 err = gobuild(mainFile, pathJoin(androidOutdir, "libs/armeabi-v7a/libgoj
ni.so")) | |
| 143 if err != nil { | |
| 144 return err | |
| 145 } | |
| 146 p, err := ctx.Import("golang.org/x/mobile/app", cwd, build.ImportComment
) | |
| 147 if err != nil { | |
| 148 return fmt.Errorf(`"golang.org/x/mobile/app" is not found; run g
o get golang.org/x/mobile/app`) | |
| 149 } | |
| 150 repo := filepath.Clean(filepath.Join(p.Dir, "..")) // golang.org/x/mobil
e directory. | |
| 151 | |
| 152 // TODO(crawshaw): use a better package path derived from the go package
. | |
| 153 if err := binder.GenJava(pathJoin(androidOutdir, "src/main/java/go/"+bin
der.pkg.Name())); err != nil { | |
| 154 return err | |
| 155 } | |
| 156 | |
| 157 src := pathJoin(repo, "app/Go.java") | |
| 158 dst := pathJoin(androidOutdir, "src/main/java/go/Go.java") | |
| 159 rm(dst) | |
| 160 if err := symlink(src, dst); err != nil { | |
| 161 return err | |
| 162 } | |
| 163 | |
| 164 src = pathJoin(repo, "bind/java/Seq.java") | |
| 165 dst = pathJoin(androidOutdir, "src/main/java/go/Seq.java") | |
| 166 rm(dst) | |
| 167 if err := symlink(src, dst); err != nil { | |
| 168 return err | |
| 169 } | |
| 170 | |
| 171 return nil | |
| 172 } | |
| 173 | |
| 174 type binder struct { | |
| 175 files []*ast.File | |
| 176 fset *token.FileSet | |
| 177 pkg *types.Package | |
| 178 } | |
| 179 | |
| 180 func (b *binder) GenJava(outdir string) error { | |
| 181 firstRune, size := utf8.DecodeRuneInString(b.pkg.Name()) | |
| 182 className := string(unicode.ToUpper(firstRune)) + b.pkg.Name()[size:] | |
| 183 javaFile := filepath.Join(outdir, className+".java") | |
| 184 | |
| 185 if buildX { | |
| 186 printcmd("gobind -lang=java %s > %s", b.pkg.Path(), javaFile) | |
| 187 } | |
| 188 | |
| 189 generate := func(w io.Writer) error { | |
| 190 return bind.GenJava(w, b.fset, b.pkg) | |
| 191 } | |
| 192 if err := writeFile(javaFile, generate); err != nil { | |
| 193 return err | |
| 194 } | |
| 195 return nil | |
| 196 } | |
| 197 | |
| 198 func (b *binder) GenGo(outdir string) error { | |
| 199 pkgName := "go_" + b.pkg.Name() | |
| 200 goFile := filepath.Join(outdir, pkgName, pkgName+".go") | |
| 201 | |
| 202 if buildX { | |
| 203 printcmd("gobind -lang=go %s > %s", b.pkg.Path(), goFile) | |
| 204 } | |
| 205 | |
| 206 generate := func(w io.Writer) error { | |
| 207 return bind.GenGo(w, b.fset, b.pkg) | |
| 208 } | |
| 209 if err := writeFile(goFile, generate); err != nil { | |
| 210 return err | |
| 211 } | |
| 212 return nil | |
| 213 } | |
| 214 | |
| 215 func writeFile(filename string, generate func(io.Writer) error) error { | |
| 216 if buildV { | |
| 217 fmt.Fprintf(os.Stderr, "write %s\n", filename) | |
| 218 } | |
| 219 | |
| 220 err := mkdir(filepath.Dir(filename)) | |
| 221 if err != nil { | |
| 222 return err | |
| 223 } | |
| 224 | |
| 225 if buildN { | |
| 226 return generate(ioutil.Discard) | |
| 227 } | |
| 228 | |
| 229 f, err := os.Create(filename) | |
| 230 if err != nil { | |
| 231 return err | |
| 232 } | |
| 233 defer func() { | |
| 234 if cerr := f.Close(); err == nil { | |
| 235 err = cerr | |
| 236 } | |
| 237 }() | |
| 238 | |
| 239 return generate(f) | |
| 240 } | |
| 241 | |
| 242 func newBinder(bindPkg *build.Package) (*binder, error) { | |
| 243 if bindPkg.Name == "main" { | |
| 244 return nil, fmt.Errorf("package %q: can only bind a library pack
age", bindPkg.Name) | |
| 245 } | |
| 246 | |
| 247 if len(bindPkg.CgoFiles) > 0 { | |
| 248 return nil, fmt.Errorf("cannot use cgo-dependent package as serv
ice definition: %s", bindPkg.CgoFiles[0]) | |
| 249 } | |
| 250 | |
| 251 fset := token.NewFileSet() | |
| 252 | |
| 253 hasErr := false | |
| 254 var files []*ast.File | |
| 255 for _, filename := range bindPkg.GoFiles { | |
| 256 p := filepath.Join(bindPkg.Dir, filename) | |
| 257 file, err := parser.ParseFile(fset, p, nil, parser.AllErrors) | |
| 258 if err != nil { | |
| 259 hasErr = true | |
| 260 if list, _ := err.(scanner.ErrorList); len(list) > 0 { | |
| 261 for _, err := range list { | |
| 262 fmt.Fprintln(os.Stderr, err) | |
| 263 } | |
| 264 } else { | |
| 265 fmt.Fprintln(os.Stderr, err) | |
| 266 } | |
| 267 } | |
| 268 files = append(files, file) | |
| 269 } | |
| 270 | |
| 271 if hasErr { | |
| 272 return nil, errors.New("package parsing failed.") | |
| 273 } | |
| 274 | |
| 275 conf := loader.Config{ | |
| 276 Fset: fset, | |
| 277 } | |
| 278 conf.TypeChecker.Error = func(err error) { | |
| 279 fmt.Fprintln(os.Stderr, err) | |
| 280 } | |
| 281 | |
| 282 conf.CreateFromFiles(bindPkg.ImportPath, files...) | |
| 283 program, err := conf.Load() | |
| 284 if err != nil { | |
| 285 return nil, err | |
| 286 } | |
| 287 b := &binder{ | |
| 288 files: files, | |
| 289 fset: fset, | |
| 290 pkg: program.Created[0].Pkg, | |
| 291 } | |
| 292 return b, nil | |
| 293 } | |
| 294 | |
| 295 var androidMainTmpl = template.Must(template.New("android.go").Parse(` | |
| 296 package main | |
| 297 | |
| 298 import ( | |
| 299 "golang.org/x/mobile/app" | |
| 300 "golang.org/x/mobile/bind/java" | |
| 301 | |
| 302 _ "{{.}}" | |
| 303 ) | |
| 304 | |
| 305 func main() { | |
| 306 app.Run(app.Callbacks{Start: java.Init}) | |
| 307 } | |
| 308 `)) | |
| OLD | NEW |