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