github.com/c-darwin/mobile@v0.0.0-20160313183840-ff625c46f7c9/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(pkg *build.Package) 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 binder, err := newBinder(pkg) 27 if err != nil { 28 return err 29 } 30 31 if err := binder.GenGo(tmpdir); err != nil { 32 return err 33 } 34 35 mainFile := filepath.Join(tmpdir, "androidlib/main.go") 36 err = writeFile(mainFile, func(w io.Writer) error { 37 return androidMainTmpl.Execute(w, "../go_"+binder.pkg.Name()) 38 }) 39 if err != nil { 40 return fmt.Errorf("failed to create the main package for android: %v", err) 41 } 42 43 androidDir := filepath.Join(tmpdir, "android") 44 45 err = goBuild( 46 mainFile, 47 androidArmEnv, 48 "-buildmode=c-shared", 49 "-o="+filepath.Join(androidDir, "src/main/jniLibs/armeabi-v7a/libgojni.so"), 50 ) 51 if err != nil { 52 return err 53 } 54 55 p, err := ctx.Import("github.com/c-darwin/mobile/bind", cwd, build.ImportComment) 56 if err != nil { 57 return fmt.Errorf(`"github.com/c-darwin/mobile/bind" is not found; run go get github.com/c-darwin/mobile/bind`) 58 } 59 repo := filepath.Clean(filepath.Join(p.Dir, "..")) // github.com/c-darwin/mobile directory. 60 61 // TODO(crawshaw): use a better package path derived from the go package. 62 if err := binder.GenJava(filepath.Join(androidDir, "src/main/java/go/"+binder.pkg.Name())); err != nil { 63 return err 64 } 65 66 dst := filepath.Join(androidDir, "src/main/java/go/LoadJNI.java") 67 genLoadJNI := func(w io.Writer) error { 68 _, err := io.WriteString(w, loadSrc) 69 return err 70 } 71 if err := writeFile(dst, genLoadJNI); err != nil { 72 return err 73 } 74 75 src := filepath.Join(repo, "bind/java/Seq.java") 76 dst = filepath.Join(androidDir, "src/main/java/go/Seq.java") 77 rm(dst) 78 if err := symlink(src, dst); err != nil { 79 return err 80 } 81 82 return buildAAR(androidDir, pkg) 83 } 84 85 var loadSrc = `package go; 86 87 public class LoadJNI { 88 static { 89 System.loadLibrary("gojni"); 90 } 91 } 92 ` 93 94 var androidMainTmpl = template.Must(template.New("android.go").Parse(` 95 package main 96 97 import ( 98 _ "github.com/c-darwin/mobile/bind/java" 99 _ "{{.}}" 100 ) 101 102 func main() {} 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(androidDir string, pkg *build.Package) (err error) { 124 var out io.Writer = ioutil.Discard 125 if buildO == "" { 126 buildO = pkg.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 fmt.Fprintf(w, manifestFmt, "go."+pkg.Name+".gojni") 157 158 w, err = aarwcreate("proguard.txt") 159 if err != nil { 160 return err 161 } 162 fmt.Fprintln(w, `-keep class go.** { *; }`) 163 164 w, err = aarwcreate("classes.jar") 165 if err != nil { 166 return err 167 } 168 src := filepath.Join(androidDir, "src/main/java") 169 if err := buildJar(w, src); err != nil { 170 return err 171 } 172 173 assetsDir := filepath.Join(pkg.Dir, "assets") 174 assetsDirExists := false 175 if fi, err := os.Stat(assetsDir); err == nil { 176 assetsDirExists = fi.IsDir() 177 } else if !os.IsNotExist(err) { 178 return err 179 } 180 181 if assetsDirExists { 182 err := filepath.Walk( 183 assetsDir, func(path string, info os.FileInfo, err error) error { 184 if err != nil { 185 return err 186 } 187 if info.IsDir() { 188 return nil 189 } 190 f, err := os.Open(path) 191 if err != nil { 192 return err 193 } 194 defer f.Close() 195 name := "assets/" + path[len(assetsDir)+1:] 196 w, err := aarwcreate(name) 197 if err != nil { 198 return nil 199 } 200 _, err = io.Copy(w, f) 201 return err 202 }) 203 if err != nil { 204 return err 205 } 206 } 207 208 lib := "armeabi-v7a/libgojni.so" 209 w, err = aarwcreate("jni/" + lib) 210 if err != nil { 211 return err 212 } 213 if !buildN { 214 r, err := os.Open(filepath.Join(androidDir, "src/main/jniLibs/"+lib)) 215 if err != nil { 216 return err 217 } 218 defer r.Close() 219 if _, err := io.Copy(w, r); err != nil { 220 return err 221 } 222 } 223 224 // TODO(hyangah): do we need to use aapt to create R.txt? 225 w, err = aarwcreate("R.txt") 226 if err != nil { 227 return err 228 } 229 230 w, err = aarwcreate("res/") 231 if err != nil { 232 return err 233 } 234 235 return aarw.Close() 236 } 237 238 const ( 239 javacTargetVer = "1.7" 240 minAndroidAPI = 9 241 ) 242 243 func buildJar(w io.Writer, srcDir string) error { 244 var srcFiles []string 245 if buildN { 246 srcFiles = []string{"*.java"} 247 } else { 248 err := filepath.Walk(srcDir, func(path string, info os.FileInfo, err error) error { 249 if err != nil { 250 return err 251 } 252 if filepath.Ext(path) == ".java" { 253 srcFiles = append(srcFiles, filepath.Join(".", path[len(srcDir):])) 254 } 255 return nil 256 }) 257 if err != nil { 258 return err 259 } 260 } 261 262 dst := filepath.Join(tmpdir, "javac-output") 263 if !buildN { 264 if err := os.MkdirAll(dst, 0700); err != nil { 265 return err 266 } 267 } 268 269 apiPath, err := androidAPIPath() 270 if err != nil { 271 return err 272 } 273 274 args := []string{ 275 "-d", dst, 276 "-source", javacTargetVer, 277 "-target", javacTargetVer, 278 "-bootclasspath", filepath.Join(apiPath, "android.jar"), 279 } 280 args = append(args, srcFiles...) 281 282 javac := exec.Command("javac", args...) 283 javac.Dir = srcDir 284 if err := runCmd(javac); err != nil { 285 return err 286 } 287 288 if buildX { 289 printcmd("jar c -C %s .", dst) 290 } 291 if buildN { 292 return nil 293 } 294 jarw := zip.NewWriter(w) 295 jarwcreate := func(name string) (io.Writer, error) { 296 if buildV { 297 fmt.Fprintf(os.Stderr, "jar: %s\n", name) 298 } 299 return jarw.Create(name) 300 } 301 f, err := jarwcreate("META-INF/MANIFEST.MF") 302 if err != nil { 303 return err 304 } 305 fmt.Fprintf(f, manifestHeader) 306 307 err = filepath.Walk(dst, func(path string, info os.FileInfo, err error) error { 308 if err != nil { 309 return err 310 } 311 if info.IsDir() { 312 return nil 313 } 314 out, err := jarwcreate(filepath.ToSlash(path[len(dst)+1:])) 315 if err != nil { 316 return err 317 } 318 in, err := os.Open(path) 319 if err != nil { 320 return err 321 } 322 defer in.Close() 323 _, err = io.Copy(out, in) 324 return err 325 }) 326 if err != nil { 327 return err 328 } 329 return jarw.Close() 330 } 331 332 // androidAPIPath returns an android SDK platform directory under ANDROID_HOME. 333 // If there are multiple platforms that satisfy the minimum version requirement 334 // androidAPIPath returns the latest one among them. 335 func androidAPIPath() (string, error) { 336 sdk := os.Getenv("ANDROID_HOME") 337 if sdk == "" { 338 return "", fmt.Errorf("ANDROID_HOME environment var is not set") 339 } 340 sdkDir, err := os.Open(filepath.Join(sdk, "platforms")) 341 if err != nil { 342 return "", fmt.Errorf("failed to find android SDK platform: %v", err) 343 } 344 defer sdkDir.Close() 345 fis, err := sdkDir.Readdir(-1) 346 if err != nil { 347 return "", fmt.Errorf("failed to find android SDK platform (min API level: %d): %v", minAndroidAPI, err) 348 } 349 350 var apiPath string 351 var apiVer int 352 for _, fi := range fis { 353 name := fi.Name() 354 if !fi.IsDir() || !strings.HasPrefix(name, "android-") { 355 continue 356 } 357 n, err := strconv.Atoi(name[len("android-"):]) 358 if err != nil || n < minAndroidAPI { 359 continue 360 } 361 p := filepath.Join(sdkDir.Name(), name) 362 _, err = os.Stat(filepath.Join(p, "android.jar")) 363 if err == nil && apiVer < n { 364 apiPath = p 365 apiVer = n 366 } 367 } 368 if apiVer == 0 { 369 return "", fmt.Errorf("failed to find android SDK platform (min API level: %d) in %s", 370 minAndroidAPI, sdkDir.Name()) 371 } 372 return apiPath, nil 373 }