github.com/acrespo/mobile@v0.0.0-20190107162257-dc0771356504/cmd/gomobile/bind_androidapp.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 "archive/zip" 9 "errors" 10 "fmt" 11 "go/build" 12 "io" 13 "io/ioutil" 14 "os" 15 "os/exec" 16 "path/filepath" 17 "strconv" 18 "strings" 19 ) 20 21 func goAndroidBind(gobind string, pkgs []*build.Package, androidArchs []string) error { 22 if sdkDir := os.Getenv("ANDROID_HOME"); sdkDir == "" { 23 return fmt.Errorf("this command requires ANDROID_HOME environment variable (path to the Android SDK)") 24 } 25 26 if !hasNDK() { 27 return errors.New("no Android NDK path is set. Please run gomobile init with the ndk-bundle installed through the Android SDK manager or with the -ndk flag set.") 28 } 29 30 // Run gobind to generate the bindings 31 cmd := exec.Command( 32 gobind, 33 "-lang=go,java", 34 "-outdir="+tmpdir, 35 ) 36 cmd.Env = append(cmd.Env, "GOOS=android") 37 cmd.Env = append(cmd.Env, "CGO_ENABLED=1") 38 if len(ctx.BuildTags) > 0 { 39 cmd.Args = append(cmd.Args, "-tags="+strings.Join(ctx.BuildTags, ",")) 40 } 41 if bindJavaPkg != "" { 42 cmd.Args = append(cmd.Args, "-javapkg="+bindJavaPkg) 43 } 44 if bindClasspath != "" { 45 cmd.Args = append(cmd.Args, "-classpath="+bindClasspath) 46 } 47 if bindBootClasspath != "" { 48 cmd.Args = append(cmd.Args, "-bootclasspath="+bindBootClasspath) 49 } 50 for _, p := range pkgs { 51 cmd.Args = append(cmd.Args, p.ImportPath) 52 } 53 if err := runCmd(cmd); err != nil { 54 return err 55 } 56 57 androidDir := filepath.Join(tmpdir, "android") 58 59 // Generate binding code and java source code only when processing the first package. 60 for _, arch := range androidArchs { 61 env := androidEnv[arch] 62 // Add the generated packages to GOPATH 63 gopath := fmt.Sprintf("GOPATH=%s%c%s", tmpdir, filepath.ListSeparator, goEnv("GOPATH")) 64 env = append(env, gopath) 65 toolchain := ndk.Toolchain(arch) 66 67 err := goBuild( 68 "gobind", 69 env, 70 "-buildmode=c-shared", 71 "-o="+filepath.Join(androidDir, "src/main/jniLibs/"+toolchain.abi+"/libgojni.so"), 72 ) 73 if err != nil { 74 return err 75 } 76 } 77 78 jsrc := filepath.Join(tmpdir, "java") 79 if err := buildAAR(jsrc, androidDir, pkgs, androidArchs); err != nil { 80 return err 81 } 82 return buildSrcJar(jsrc) 83 } 84 85 func buildSrcJar(src string) error { 86 var out io.Writer = ioutil.Discard 87 if !buildN { 88 ext := filepath.Ext(buildO) 89 f, err := os.Create(buildO[:len(buildO)-len(ext)] + "-sources.jar") 90 if err != nil { 91 return err 92 } 93 defer func() { 94 if cerr := f.Close(); err == nil { 95 err = cerr 96 } 97 }() 98 out = f 99 } 100 101 return writeJar(out, src) 102 } 103 104 // AAR is the format for the binary distribution of an Android Library Project 105 // and it is a ZIP archive with extension .aar. 106 // http://tools.android.com/tech-docs/new-build-system/aar-format 107 // 108 // These entries are directly at the root of the archive. 109 // 110 // AndroidManifest.xml (mandatory) 111 // classes.jar (mandatory) 112 // assets/ (optional) 113 // jni/<abi>/libgojni.so 114 // R.txt (mandatory) 115 // res/ (mandatory) 116 // libs/*.jar (optional, not relevant) 117 // proguard.txt (optional) 118 // lint.jar (optional, not relevant) 119 // aidl (optional, not relevant) 120 // 121 // javac and jar commands are needed to build classes.jar. 122 func buildAAR(srcDir, androidDir string, pkgs []*build.Package, androidArchs []string) (err error) { 123 var out io.Writer = ioutil.Discard 124 if buildO == "" { 125 buildO = pkgs[0].Name + ".aar" 126 } 127 if !strings.HasSuffix(buildO, ".aar") { 128 return fmt.Errorf("output file name %q does not end in '.aar'", buildO) 129 } 130 if !buildN { 131 f, err := os.Create(buildO) 132 if err != nil { 133 return err 134 } 135 defer func() { 136 if cerr := f.Close(); err == nil { 137 err = cerr 138 } 139 }() 140 out = f 141 } 142 143 aarw := zip.NewWriter(out) 144 aarwcreate := func(name string) (io.Writer, error) { 145 if buildV { 146 fmt.Fprintf(os.Stderr, "aar: %s\n", name) 147 } 148 return aarw.Create(name) 149 } 150 w, err := aarwcreate("AndroidManifest.xml") 151 if err != nil { 152 return err 153 } 154 const manifestFmt = `<manifest xmlns:android="http://schemas.android.com/apk/res/android" package=%q> 155 <uses-sdk android:minSdkVersion="%d"/></manifest>` 156 fmt.Fprintf(w, manifestFmt, "go."+pkgs[0].Name+".gojni", minAndroidAPI) 157 158 w, err = aarwcreate("proguard.txt") 159 if err != nil { 160 return err 161 } 162 fmt.Fprintln(w, `-keep class go.** { *; }`) 163 164 w, err = aarwcreate("classes.jar") 165 if err != nil { 166 return err 167 } 168 if err := buildJar(w, srcDir); err != nil { 169 return err 170 } 171 172 files := map[string]string{} 173 for _, pkg := range pkgs { 174 assetsDir := filepath.Join(pkg.Dir, "assets") 175 assetsDirExists := false 176 if fi, err := os.Stat(assetsDir); err == nil { 177 assetsDirExists = fi.IsDir() 178 } else if !os.IsNotExist(err) { 179 return err 180 } 181 182 if assetsDirExists { 183 err := filepath.Walk( 184 assetsDir, func(path string, info os.FileInfo, err error) error { 185 if err != nil { 186 return err 187 } 188 if info.IsDir() { 189 return nil 190 } 191 f, err := os.Open(path) 192 if err != nil { 193 return err 194 } 195 defer f.Close() 196 name := "assets/" + path[len(assetsDir)+1:] 197 if orig, exists := files[name]; exists { 198 return fmt.Errorf("package %s asset name conflict: %s already added from package %s", 199 pkg.ImportPath, name, orig) 200 } 201 files[name] = pkg.ImportPath 202 w, err := aarwcreate(name) 203 if err != nil { 204 return nil 205 } 206 _, err = io.Copy(w, f) 207 return err 208 }) 209 if err != nil { 210 return err 211 } 212 } 213 } 214 215 for _, arch := range androidArchs { 216 toolchain := ndk.Toolchain(arch) 217 lib := toolchain.abi + "/libgojni.so" 218 w, err = aarwcreate("jni/" + lib) 219 if err != nil { 220 return err 221 } 222 if !buildN { 223 r, err := os.Open(filepath.Join(androidDir, "src/main/jniLibs/"+lib)) 224 if err != nil { 225 return err 226 } 227 defer r.Close() 228 if _, err := io.Copy(w, r); err != nil { 229 return err 230 } 231 } 232 } 233 234 // TODO(hyangah): do we need to use aapt to create R.txt? 235 w, err = aarwcreate("R.txt") 236 if err != nil { 237 return err 238 } 239 240 w, err = aarwcreate("res/") 241 if err != nil { 242 return err 243 } 244 245 return aarw.Close() 246 } 247 248 const ( 249 javacTargetVer = "1.7" 250 minAndroidAPI = 15 251 ) 252 253 func buildJar(w io.Writer, srcDir string) error { 254 var srcFiles []string 255 if buildN { 256 srcFiles = []string{"*.java"} 257 } else { 258 err := filepath.Walk(srcDir, func(path string, info os.FileInfo, err error) error { 259 if err != nil { 260 return err 261 } 262 if filepath.Ext(path) == ".java" { 263 srcFiles = append(srcFiles, filepath.Join(".", path[len(srcDir):])) 264 } 265 return nil 266 }) 267 if err != nil { 268 return err 269 } 270 } 271 272 dst := filepath.Join(tmpdir, "javac-output") 273 if !buildN { 274 if err := os.MkdirAll(dst, 0700); err != nil { 275 return err 276 } 277 } 278 279 bClspath, err := bootClasspath() 280 281 if err != nil { 282 return err 283 } 284 285 args := []string{ 286 "-d", dst, 287 "-source", javacTargetVer, 288 "-target", javacTargetVer, 289 "-bootclasspath", bClspath, 290 } 291 if bindClasspath != "" { 292 args = append(args, "-classpath", bindClasspath) 293 } 294 295 args = append(args, srcFiles...) 296 297 javac := exec.Command("javac", args...) 298 javac.Dir = srcDir 299 if err := runCmd(javac); err != nil { 300 return err 301 } 302 303 if buildX { 304 printcmd("jar c -C %s .", dst) 305 } 306 return writeJar(w, dst) 307 } 308 309 func writeJar(w io.Writer, dir string) error { 310 if buildN { 311 return nil 312 } 313 jarw := zip.NewWriter(w) 314 jarwcreate := func(name string) (io.Writer, error) { 315 if buildV { 316 fmt.Fprintf(os.Stderr, "jar: %s\n", name) 317 } 318 return jarw.Create(name) 319 } 320 f, err := jarwcreate("META-INF/MANIFEST.MF") 321 if err != nil { 322 return err 323 } 324 fmt.Fprintf(f, manifestHeader) 325 326 err = filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { 327 if err != nil { 328 return err 329 } 330 if info.IsDir() { 331 return nil 332 } 333 out, err := jarwcreate(filepath.ToSlash(path[len(dir)+1:])) 334 if err != nil { 335 return err 336 } 337 in, err := os.Open(path) 338 if err != nil { 339 return err 340 } 341 defer in.Close() 342 _, err = io.Copy(out, in) 343 return err 344 }) 345 if err != nil { 346 return err 347 } 348 return jarw.Close() 349 } 350 351 // androidAPIPath returns an android SDK platform directory under ANDROID_HOME. 352 // If there are multiple platforms that satisfy the minimum version requirement 353 // androidAPIPath returns the latest one among them. 354 func androidAPIPath() (string, error) { 355 sdk := os.Getenv("ANDROID_HOME") 356 if sdk == "" { 357 return "", fmt.Errorf("ANDROID_HOME environment var is not set") 358 } 359 sdkDir, err := os.Open(filepath.Join(sdk, "platforms")) 360 if err != nil { 361 return "", fmt.Errorf("failed to find android SDK platform: %v", err) 362 } 363 defer sdkDir.Close() 364 fis, err := sdkDir.Readdir(-1) 365 if err != nil { 366 return "", fmt.Errorf("failed to find android SDK platform (min API level: %d): %v", minAndroidAPI, err) 367 } 368 369 var apiPath string 370 var apiVer int 371 for _, fi := range fis { 372 name := fi.Name() 373 if !fi.IsDir() || !strings.HasPrefix(name, "android-") { 374 continue 375 } 376 n, err := strconv.Atoi(name[len("android-"):]) 377 if err != nil || n < minAndroidAPI { 378 continue 379 } 380 p := filepath.Join(sdkDir.Name(), name) 381 _, err = os.Stat(filepath.Join(p, "android.jar")) 382 if err == nil && apiVer < n { 383 apiPath = p 384 apiVer = n 385 } 386 } 387 if apiVer == 0 { 388 return "", fmt.Errorf("failed to find android SDK platform (min API level: %d) in %s", 389 minAndroidAPI, sdkDir.Name()) 390 } 391 return apiPath, nil 392 }