github.com/graybobo/golang.org-package-offline-cache@v0.0.0-20200626051047-6608995c132f/x/mobile/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 "text/template" 19 ) 20 21 func goAndroidBind(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 var androidArgs []string 26 if goVersion == go1_6 { 27 // Ideally this would be -buildmode=c-shared. 28 // https://golang.org/issue/13234. 29 androidArgs = []string{"-gcflags=-shared", "-ldflags=-shared"} 30 } 31 32 paths := make([]string, len(pkgs)) 33 for i, p := range pkgs { 34 paths[i] = p.ImportPath 35 } 36 37 androidDir := filepath.Join(tmpdir, "android") 38 mainFile := filepath.Join(tmpdir, "androidlib/main.go") 39 40 // Generate binding code and java source code only when processing the first package. 41 first := true 42 for _, arch := range androidArchs { 43 env := androidEnv[arch] 44 toolchain := ndk.Toolchain(arch) 45 46 if !first { 47 if err := goInstall(paths, env, androidArgs...); err != nil { 48 return err 49 } 50 err := goBuild( 51 mainFile, 52 env, 53 "-buildmode=c-shared", 54 "-o="+filepath.Join(androidDir, "src/main/jniLibs/"+toolchain.abi+"/libgojni.so"), 55 ) 56 if err != nil { 57 return err 58 } 59 60 continue 61 } 62 first = false 63 64 typesPkgs, err := loadExportData(pkgs, env, androidArgs...) 65 if err != nil { 66 return fmt.Errorf("loadExportData failed %v", err) 67 } 68 69 binder, err := newBinder(typesPkgs) 70 if err != nil { 71 return err 72 } 73 74 for _, pkg := range typesPkgs { 75 if err := binder.GenGo(pkg, tmpdir); err != nil { 76 return err 77 } 78 } 79 80 err = writeFile(mainFile, func(w io.Writer) error { 81 return androidMainTmpl.Execute(w, binder.pkgs) 82 }) 83 if err != nil { 84 return fmt.Errorf("failed to create the main package for android: %v", err) 85 } 86 87 err = goBuild( 88 mainFile, 89 env, 90 "-buildmode=c-shared", 91 "-o="+filepath.Join(androidDir, "src/main/jniLibs/"+toolchain.abi+"/libgojni.so"), 92 ) 93 if err != nil { 94 return err 95 } 96 97 p, err := ctx.Import("golang.org/x/mobile/bind", cwd, build.ImportComment) 98 if err != nil { 99 return fmt.Errorf(`"golang.org/x/mobile/bind" is not found; run go get golang.org/x/mobile/bind`) 100 } 101 repo := filepath.Clean(filepath.Join(p.Dir, "..")) // golang.org/x/mobile directory. 102 103 for _, pkg := range binder.pkgs { 104 pkgpath := strings.Replace(bindJavaPkg, ".", "/", -1) 105 if bindJavaPkg == "" { 106 pkgpath = "go/" + pkg.Name() 107 } 108 if err := binder.GenJava(pkg, filepath.Join(androidDir, "src/main/java/"+pkgpath)); err != nil { 109 return err 110 } 111 } 112 113 dst := filepath.Join(androidDir, "src/main/java/go/LoadJNI.java") 114 genLoadJNI := func(w io.Writer) error { 115 _, err := io.WriteString(w, loadSrc) 116 return err 117 } 118 if err := writeFile(dst, genLoadJNI); err != nil { 119 return err 120 } 121 122 src := filepath.Join(repo, "bind/java/Seq.java") 123 dst = filepath.Join(androidDir, "src/main/java/go/Seq.java") 124 rm(dst) 125 if err := symlink(src, dst); err != nil { 126 return err 127 } 128 } 129 130 return buildAAR(androidDir, pkgs, androidArchs) 131 } 132 133 var loadSrc = `package go; 134 135 import android.app.Application; 136 import android.content.Context; 137 138 import java.util.logging.Logger; 139 140 public class LoadJNI { 141 private static Logger log = Logger.getLogger("GoLoadJNI"); 142 143 public static final Object ctx; 144 145 static { 146 System.loadLibrary("gojni"); 147 148 Object androidCtx = null; 149 try { 150 // TODO(hyangah): check proguard rule. 151 Application appl = (Application)Class.forName("android.app.AppGlobals").getMethod("getInitialApplication").invoke(null); 152 androidCtx = appl.getApplicationContext(); 153 } catch (Exception e) { 154 log.warning("Global context not found: " + e); 155 } finally { 156 ctx = androidCtx; 157 } 158 } 159 } 160 ` 161 162 var androidMainTmpl = template.Must(template.New("android.go").Parse(` 163 package main 164 165 import ( 166 _ "golang.org/x/mobile/bind/java" 167 {{range .}} _ "../go_{{.Name}}" 168 {{end}} 169 ) 170 171 func main() {} 172 `)) 173 174 // AAR is the format for the binary distribution of an Android Library Project 175 // and it is a ZIP archive with extension .aar. 176 // http://tools.android.com/tech-docs/new-build-system/aar-format 177 // 178 // These entries are directly at the root of the archive. 179 // 180 // AndroidManifest.xml (mandatory) 181 // classes.jar (mandatory) 182 // assets/ (optional) 183 // jni/<abi>/libgojni.so 184 // R.txt (mandatory) 185 // res/ (mandatory) 186 // libs/*.jar (optional, not relevant) 187 // proguard.txt (optional) 188 // lint.jar (optional, not relevant) 189 // aidl (optional, not relevant) 190 // 191 // javac and jar commands are needed to build classes.jar. 192 func buildAAR(androidDir string, pkgs []*build.Package, androidArchs []string) (err error) { 193 var out io.Writer = ioutil.Discard 194 if buildO == "" { 195 buildO = pkgs[0].Name + ".aar" 196 } 197 if !strings.HasSuffix(buildO, ".aar") { 198 return fmt.Errorf("output file name %q does not end in '.aar'", buildO) 199 } 200 if !buildN { 201 f, err := os.Create(buildO) 202 if err != nil { 203 return err 204 } 205 defer func() { 206 if cerr := f.Close(); err == nil { 207 err = cerr 208 } 209 }() 210 out = f 211 } 212 213 aarw := zip.NewWriter(out) 214 aarwcreate := func(name string) (io.Writer, error) { 215 if buildV { 216 fmt.Fprintf(os.Stderr, "aar: %s\n", name) 217 } 218 return aarw.Create(name) 219 } 220 w, err := aarwcreate("AndroidManifest.xml") 221 if err != nil { 222 return err 223 } 224 const manifestFmt = `<manifest xmlns:android="http://schemas.android.com/apk/res/android" package=%q> 225 <uses-sdk android:minSdkVersion="%d"/></manifest>` 226 fmt.Fprintf(w, manifestFmt, "go."+pkgs[0].Name+".gojni", minAndroidAPI) 227 228 w, err = aarwcreate("proguard.txt") 229 if err != nil { 230 return err 231 } 232 fmt.Fprintln(w, `-keep class go.** { *; }`) 233 234 w, err = aarwcreate("classes.jar") 235 if err != nil { 236 return err 237 } 238 src := filepath.Join(androidDir, "src/main/java") 239 if err := buildJar(w, src); err != nil { 240 return err 241 } 242 243 files := map[string]string{} 244 for _, pkg := range pkgs { 245 assetsDir := filepath.Join(pkg.Dir, "assets") 246 assetsDirExists := false 247 if fi, err := os.Stat(assetsDir); err == nil { 248 assetsDirExists = fi.IsDir() 249 } else if !os.IsNotExist(err) { 250 return err 251 } 252 253 if assetsDirExists { 254 err := filepath.Walk( 255 assetsDir, func(path string, info os.FileInfo, err error) error { 256 if err != nil { 257 return err 258 } 259 if info.IsDir() { 260 return nil 261 } 262 f, err := os.Open(path) 263 if err != nil { 264 return err 265 } 266 defer f.Close() 267 name := "assets/" + path[len(assetsDir)+1:] 268 if orig, exists := files[name]; exists { 269 return fmt.Errorf("package %s asset name conflict: %s already added from package %s", 270 pkg.ImportPath, name, orig) 271 } 272 files[name] = pkg.ImportPath 273 w, err := aarwcreate(name) 274 if err != nil { 275 return nil 276 } 277 _, err = io.Copy(w, f) 278 return err 279 }) 280 if err != nil { 281 return err 282 } 283 } 284 } 285 286 for _, arch := range androidArchs { 287 toolchain := ndk.Toolchain(arch) 288 lib := toolchain.abi + "/libgojni.so" 289 w, err = aarwcreate("jni/" + lib) 290 if err != nil { 291 return err 292 } 293 if !buildN { 294 r, err := os.Open(filepath.Join(androidDir, "src/main/jniLibs/"+lib)) 295 if err != nil { 296 return err 297 } 298 defer r.Close() 299 if _, err := io.Copy(w, r); err != nil { 300 return err 301 } 302 } 303 } 304 305 // TODO(hyangah): do we need to use aapt to create R.txt? 306 w, err = aarwcreate("R.txt") 307 if err != nil { 308 return err 309 } 310 311 w, err = aarwcreate("res/") 312 if err != nil { 313 return err 314 } 315 316 return aarw.Close() 317 } 318 319 const ( 320 javacTargetVer = "1.7" 321 minAndroidAPI = 15 322 ) 323 324 func buildJar(w io.Writer, srcDir string) error { 325 var srcFiles []string 326 if buildN { 327 srcFiles = []string{"*.java"} 328 } else { 329 err := filepath.Walk(srcDir, func(path string, info os.FileInfo, err error) error { 330 if err != nil { 331 return err 332 } 333 if filepath.Ext(path) == ".java" { 334 srcFiles = append(srcFiles, filepath.Join(".", path[len(srcDir):])) 335 } 336 return nil 337 }) 338 if err != nil { 339 return err 340 } 341 } 342 343 dst := filepath.Join(tmpdir, "javac-output") 344 if !buildN { 345 if err := os.MkdirAll(dst, 0700); err != nil { 346 return err 347 } 348 } 349 350 apiPath, err := androidAPIPath() 351 if err != nil { 352 return err 353 } 354 355 args := []string{ 356 "-d", dst, 357 "-source", javacTargetVer, 358 "-target", javacTargetVer, 359 "-bootclasspath", filepath.Join(apiPath, "android.jar"), 360 } 361 args = append(args, srcFiles...) 362 363 javac := exec.Command("javac", args...) 364 javac.Dir = srcDir 365 if err := runCmd(javac); err != nil { 366 return err 367 } 368 369 if buildX { 370 printcmd("jar c -C %s .", dst) 371 } 372 if buildN { 373 return nil 374 } 375 jarw := zip.NewWriter(w) 376 jarwcreate := func(name string) (io.Writer, error) { 377 if buildV { 378 fmt.Fprintf(os.Stderr, "jar: %s\n", name) 379 } 380 return jarw.Create(name) 381 } 382 f, err := jarwcreate("META-INF/MANIFEST.MF") 383 if err != nil { 384 return err 385 } 386 fmt.Fprintf(f, manifestHeader) 387 388 err = filepath.Walk(dst, func(path string, info os.FileInfo, err error) error { 389 if err != nil { 390 return err 391 } 392 if info.IsDir() { 393 return nil 394 } 395 out, err := jarwcreate(filepath.ToSlash(path[len(dst)+1:])) 396 if err != nil { 397 return err 398 } 399 in, err := os.Open(path) 400 if err != nil { 401 return err 402 } 403 defer in.Close() 404 _, err = io.Copy(out, in) 405 return err 406 }) 407 if err != nil { 408 return err 409 } 410 return jarw.Close() 411 } 412 413 // androidAPIPath returns an android SDK platform directory under ANDROID_HOME. 414 // If there are multiple platforms that satisfy the minimum version requirement 415 // androidAPIPath returns the latest one among them. 416 func androidAPIPath() (string, error) { 417 sdk := os.Getenv("ANDROID_HOME") 418 if sdk == "" { 419 return "", fmt.Errorf("ANDROID_HOME environment var is not set") 420 } 421 sdkDir, err := os.Open(filepath.Join(sdk, "platforms")) 422 if err != nil { 423 return "", fmt.Errorf("failed to find android SDK platform: %v", err) 424 } 425 defer sdkDir.Close() 426 fis, err := sdkDir.Readdir(-1) 427 if err != nil { 428 return "", fmt.Errorf("failed to find android SDK platform (min API level: %d): %v", minAndroidAPI, err) 429 } 430 431 var apiPath string 432 var apiVer int 433 for _, fi := range fis { 434 name := fi.Name() 435 if !fi.IsDir() || !strings.HasPrefix(name, "android-") { 436 continue 437 } 438 n, err := strconv.Atoi(name[len("android-"):]) 439 if err != nil || n < minAndroidAPI { 440 continue 441 } 442 p := filepath.Join(sdkDir.Name(), name) 443 _, err = os.Stat(filepath.Join(p, "android.jar")) 444 if err == nil && apiVer < n { 445 apiPath = p 446 apiVer = n 447 } 448 } 449 if apiVer == 0 { 450 return "", fmt.Errorf("failed to find android SDK platform (min API level: %d) in %s", 451 minAndroidAPI, sdkDir.Name()) 452 } 453 return apiPath, nil 454 }