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