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