github.com/thommil/tge-mobile@v0.0.0-20190308225214-66a08abd51aa/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 159 w, err = aarwcreate("classes.jar") 160 if err != nil { 161 return err 162 } 163 if err := buildJar(w, srcDir); err != nil { 164 return err 165 } 166 167 files := map[string]string{} 168 for _, pkg := range pkgs { 169 assetsDir := filepath.Join(pkg.Dir, "assets") 170 assetsDirExists := false 171 if fi, err := os.Stat(assetsDir); err == nil { 172 assetsDirExists = fi.IsDir() 173 } else if !os.IsNotExist(err) { 174 return err 175 } 176 177 if assetsDirExists { 178 err := filepath.Walk( 179 assetsDir, func(path string, info os.FileInfo, err error) error { 180 if err != nil { 181 return err 182 } 183 if info.IsDir() { 184 return nil 185 } 186 f, err := os.Open(path) 187 if err != nil { 188 return err 189 } 190 defer f.Close() 191 name := "assets/" + path[len(assetsDir)+1:] 192 if orig, exists := files[name]; exists { 193 return fmt.Errorf("package %s asset name conflict: %s already added from package %s", 194 pkg.ImportPath, name, orig) 195 } 196 files[name] = pkg.ImportPath 197 w, err := aarwcreate(name) 198 if err != nil { 199 return nil 200 } 201 _, err = io.Copy(w, f) 202 return err 203 }) 204 if err != nil { 205 return err 206 } 207 } 208 } 209 210 for _, arch := range androidArchs { 211 toolchain := ndk.Toolchain(arch) 212 lib := toolchain.abi + "/libgojni.so" 213 w, err = aarwcreate("jni/" + lib) 214 if err != nil { 215 return err 216 } 217 if !buildN { 218 r, err := os.Open(filepath.Join(androidDir, "src/main/jniLibs/"+lib)) 219 if err != nil { 220 return err 221 } 222 defer r.Close() 223 if _, err := io.Copy(w, r); err != nil { 224 return err 225 } 226 } 227 } 228 229 // TODO(hyangah): do we need to use aapt to create R.txt? 230 w, err = aarwcreate("R.txt") 231 if err != nil { 232 return err 233 } 234 235 w, err = aarwcreate("res/") 236 if err != nil { 237 return err 238 } 239 240 return aarw.Close() 241 } 242 243 const ( 244 javacTargetVer = "1.7" 245 minAndroidAPI = 15 246 ) 247 248 func buildJar(w io.Writer, srcDir string) error { 249 var srcFiles []string 250 if buildN { 251 srcFiles = []string{"*.java"} 252 } else { 253 err := filepath.Walk(srcDir, func(path string, info os.FileInfo, err error) error { 254 if err != nil { 255 return err 256 } 257 if filepath.Ext(path) == ".java" { 258 srcFiles = append(srcFiles, filepath.Join(".", path[len(srcDir):])) 259 } 260 return nil 261 }) 262 if err != nil { 263 return err 264 } 265 } 266 267 dst := filepath.Join(tmpdir, "javac-output") 268 if !buildN { 269 if err := os.MkdirAll(dst, 0700); err != nil { 270 return err 271 } 272 } 273 274 bClspath, err := bootClasspath() 275 276 if err != nil { 277 return err 278 } 279 280 args := []string{ 281 "-d", dst, 282 "-source", javacTargetVer, 283 "-target", javacTargetVer, 284 "-bootclasspath", bClspath, 285 } 286 if bindClasspath != "" { 287 args = append(args, "-classpath", bindClasspath) 288 } 289 290 args = append(args, srcFiles...) 291 292 javac := exec.Command("javac", args...) 293 javac.Dir = srcDir 294 if err := runCmd(javac); err != nil { 295 return err 296 } 297 298 if buildX { 299 printcmd("jar c -C %s .", dst) 300 } 301 return writeJar(w, dst) 302 } 303 304 func writeJar(w io.Writer, dir string) error { 305 if buildN { 306 return nil 307 } 308 jarw := zip.NewWriter(w) 309 jarwcreate := func(name string) (io.Writer, error) { 310 if buildV { 311 fmt.Fprintf(os.Stderr, "jar: %s\n", name) 312 } 313 return jarw.Create(name) 314 } 315 f, err := jarwcreate("META-INF/MANIFEST.MF") 316 if err != nil { 317 return err 318 } 319 fmt.Fprintf(f, manifestHeader) 320 321 err = filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { 322 if err != nil { 323 return err 324 } 325 if info.IsDir() { 326 return nil 327 } 328 out, err := jarwcreate(filepath.ToSlash(path[len(dir)+1:])) 329 if err != nil { 330 return err 331 } 332 in, err := os.Open(path) 333 if err != nil { 334 return err 335 } 336 defer in.Close() 337 _, err = io.Copy(out, in) 338 return err 339 }) 340 if err != nil { 341 return err 342 } 343 return jarw.Close() 344 } 345 346 // androidAPIPath returns an android SDK platform directory under ANDROID_HOME. 347 // If there are multiple platforms that satisfy the minimum version requirement 348 // androidAPIPath returns the latest one among them. 349 func androidAPIPath() (string, error) { 350 sdk := os.Getenv("ANDROID_HOME") 351 if sdk == "" { 352 return "", fmt.Errorf("ANDROID_HOME environment var is not set") 353 } 354 sdkDir, err := os.Open(filepath.Join(sdk, "platforms")) 355 if err != nil { 356 return "", fmt.Errorf("failed to find android SDK platform: %v", err) 357 } 358 defer sdkDir.Close() 359 fis, err := sdkDir.Readdir(-1) 360 if err != nil { 361 return "", fmt.Errorf("failed to find android SDK platform (min API level: %d): %v", minAndroidAPI, err) 362 } 363 364 var apiPath string 365 var apiVer int 366 for _, fi := range fis { 367 name := fi.Name() 368 if !fi.IsDir() || !strings.HasPrefix(name, "android-") { 369 continue 370 } 371 n, err := strconv.Atoi(name[len("android-"):]) 372 if err != nil || n < minAndroidAPI { 373 continue 374 } 375 p := filepath.Join(sdkDir.Name(), name) 376 _, err = os.Stat(filepath.Join(p, "android.jar")) 377 if err == nil && apiVer < n { 378 apiPath = p 379 apiVer = n 380 } 381 } 382 if apiVer == 0 { 383 return "", fmt.Errorf("failed to find android SDK platform (min API level: %d) in %s", 384 minAndroidAPI, sdkDir.Name()) 385 } 386 return apiPath, nil 387 }