diff --git a/bind/java/Seq.java b/bind/java/Seq.java index 5ac8250..3c92d1c 100644 --- a/bind/java/Seq.java +++ b/bind/java/Seq.java @@ -21,6 +21,7 @@ public class Seq { Class.forName("go.LoadJNI"); } catch (ClassNotFoundException e) { // Ignore, assume the user will load JNI for it. + Log.w("GoSeq", "LoadJNI class not found"); } initSeq(); @@ -239,7 +240,7 @@ public class Seq { if (refnum <= 0) { // We don't keep track of the Go object. // This must not happen. - Log.wtf("Seq", "dec request for Go object "+ refnum); + Log.wtf("GoSeq", "dec request for Go object "+ refnum); return; } // Java objects are removed on request of Go. diff --git a/cmd/gomobile/bind.go b/cmd/gomobile/bind.go index 4a98edd..9f7d883 100644 --- a/cmd/gomobile/bind.go +++ b/cmd/gomobile/bind.go @@ -5,8 +5,6 @@ package main import ( - "archive/zip" - "bytes" "errors" "fmt" "go/ast" @@ -17,11 +15,7 @@ import ( "io" "io/ioutil" "os" - "os/exec" "path/filepath" - "strconv" - "strings" - "text/template" "unicode" "unicode/utf8" @@ -30,7 +24,7 @@ import ( "golang.org/x/tools/go/types" ) -// ctx, pkg, ndkccpath, tmpdir in build.go +// ctx, pkg, tmpdir in build.go var cmdBind = &command{ run: runBind, @@ -69,18 +63,20 @@ For documentation, see 'go help build'. } func runBind(cmd *command) error { - cwd, err := os.Getwd() + cleanup, err := envInit() if err != nil { - panic(err) + return err } + defer cleanup() + args := cmd.flag.Args() - var bindPkg *build.Package + var pkg *build.Package switch len(args) { case 0: - bindPkg, err = ctx.ImportDir(cwd, build.ImportComment) + pkg, err = ctx.ImportDir(cwd, build.ImportComment) case 1: - bindPkg, err = ctx.Import(args[0], cwd, build.ImportComment) + pkg, err = ctx.Import(args[0], cwd, build.ImportComment) default: cmd.usage() os.Exit(1) @@ -91,89 +87,14 @@ func runBind(cmd *command) error { switch buildTarget { case "android": - // implementation is below + return goAndroidBind(pkg) case "ios": return fmt.Errorf(`-target=ios not yet supported`) default: return fmt.Errorf(`unknown -target, %q.`, buildTarget) } - - if sdkDir := os.Getenv("ANDROID_HOME"); sdkDir == "" { - return fmt.Errorf("this command requires ANDROID_HOME environment variable (path to the Android SDK)") - } - - if buildN { - tmpdir = "$WORK" - } else { - tmpdir, err = ioutil.TempDir("", "gomobile-bind-work-") - if err != nil { - return err - } - } - defer removeAll(tmpdir) - if buildX { - fmt.Fprintln(os.Stderr, "WORK="+tmpdir) - } - - binder, err := newBinder(bindPkg) - if err != nil { - return err - } - - if err := binder.GenGo(tmpdir); err != nil { - return err - } - - mainFile := filepath.Join(tmpdir, "androidlib/main.go") - err = writeFile(mainFile, func(w io.Writer) error { - return androidMainTmpl.Execute(w, "../go_"+binder.pkg.Name()) - }) - if err != nil { - return fmt.Errorf("failed to create the main package for android: %v", err) - } - - androidDir := filepath.Join(tmpdir, "android") - - err = goAndroidBuild(mainFile, filepath.Join(androidDir, "src/main/jniLibs/armeabi-v7a/libgojni.so")) - if err != nil { - return err - } - - p, err := ctx.Import("golang.org/x/mobile/app", cwd, build.ImportComment) - if err != nil { - return fmt.Errorf(`"golang.org/x/mobile/app" is not found; run go get golang.org/x/mobile/app`) - } - repo := filepath.Clean(filepath.Join(p.Dir, "..")) // golang.org/x/mobile directory. - - // TODO(crawshaw): use a better package path derived from the go package. - if err := binder.GenJava(filepath.Join(androidDir, "src/main/java/go/"+binder.pkg.Name())); err != nil { - return err - } - - dst := filepath.Join(androidDir, "src/main/java/go/LoadJNI.java") - if err := ioutil.WriteFile(dst, []byte(loadSrc), 0664); err != nil { - return err - } - - src := filepath.Join(repo, "bind/java/Seq.java") - dst = filepath.Join(androidDir, "src/main/java/go/Seq.java") - rm(dst) - if err := symlink(src, dst); err != nil { - return err - } - - return buildAAR(androidDir, bindPkg) } -var loadSrc = `package go; - -public class LoadJNI { - static { - System.loadLibrary("gojni"); - } -} -` - type binder struct { files []*ast.File fset *token.FileSet @@ -294,300 +215,3 @@ func newBinder(bindPkg *build.Package) (*binder, error) { } return b, nil } - -var androidMainTmpl = template.Must(template.New("android.go").Parse(` -package main - -import ( - _ "golang.org/x/mobile/bind/java" - _ "{{.}}" -) - -func main() {} -`)) - -// AAR is the format for the binary distribution of an Android Library Project -// and it is a ZIP archive with extension .aar. -// http://tools.android.com/tech-docs/new-build-system/aar-format -// -// These entries are directly at the root of the archive. -// -// AndroidManifest.xml (mandatory) -// classes.jar (mandatory) -// assets/ (optional) -// jni//libgojni.so -// R.txt (mandatory) -// res/ (mandatory) -// libs/*.jar (optional, not relevant) -// proguard.txt (optional) -// lint.jar (optional, not relevant) -// aidl (optional, not relevant) -// -// javac and jar commands are needed to build classes.jar. -func buildAAR(androidDir string, pkg *build.Package) (err error) { - var out io.Writer = ioutil.Discard - if buildO == "" { - buildO = pkg.Name + ".aar" - } - if !strings.HasSuffix(buildO, ".aar") { - return fmt.Errorf("output file name %q does not end in '.aar'", buildO) - } - if !buildN { - f, err := os.Create(buildO) - if err != nil { - return err - } - defer func() { - if cerr := f.Close(); err == nil { - err = cerr - } - }() - out = f - } - - aarw := zip.NewWriter(out) - aarwcreate := func(name string) (io.Writer, error) { - if buildV { - fmt.Fprintf(os.Stderr, "aar: %s\n", name) - } - return aarw.Create(name) - } - w, err := aarwcreate("AndroidManifest.xml") - if err != nil { - return err - } - const manifestFmt = `` - fmt.Fprintf(w, manifestFmt, "go."+pkg.Name+".gojni") - - w, err = aarwcreate("proguard.txt") - if err != nil { - return err - } - fmt.Fprintln(w, `-keep class go.** { *; }`) - - w, err = aarwcreate("classes.jar") - if err != nil { - return err - } - src := filepath.Join(androidDir, "src/main/java") - if err := buildJar(w, src); err != nil { - return err - } - - assetsDir := filepath.Join(pkg.Dir, "assets") - assetsDirExists := false - if fi, err := os.Stat(assetsDir); err == nil { - assetsDirExists = fi.IsDir() - } else if !os.IsNotExist(err) { - return err - } - - if assetsDirExists { - err := filepath.Walk( - assetsDir, func(path string, info os.FileInfo, err error) error { - if err != nil { - return err - } - if info.IsDir() { - return nil - } - f, err := os.Open(path) - if err != nil { - return err - } - defer f.Close() - name := "assets/" + path[len(assetsDir)+1:] - w, err := aarwcreate(name) - if err != nil { - return nil - } - _, err = io.Copy(w, f) - return err - }) - if err != nil { - return err - } - } - - lib := "armeabi-v7a/libgojni.so" - w, err = aarwcreate("jni/" + lib) - if err != nil { - return err - } - if !buildN { - r, err := os.Open(filepath.Join(androidDir, "src/main/jniLibs/"+lib)) - if err != nil { - return err - } - defer r.Close() - if _, err := io.Copy(w, r); err != nil { - return err - } - } - - // TODO(hyangah): do we need to use aapt to create R.txt? - w, err = aarwcreate("R.txt") - if err != nil { - return err - } - - w, err = aarwcreate("res/") - if err != nil { - return err - } - - return aarw.Close() -} - -const ( - javacTargetVer = "1.7" - minAndroidAPI = 9 -) - -func buildJar(w io.Writer, srcDir string) error { - var srcFiles []string - if buildN { - srcFiles = []string{"*.java"} - } else { - err := filepath.Walk(srcDir, func(path string, info os.FileInfo, err error) error { - if err != nil { - return err - } - if filepath.Ext(path) == ".java" { - srcFiles = append(srcFiles, filepath.Join(".", path[len(srcDir):])) - } - return nil - }) - if err != nil { - return err - } - } - - dst := filepath.Join(tmpdir, "javac-output") - if !buildN { - if err := os.MkdirAll(dst, 0700); err != nil { - return err - } - } - defer removeAll(dst) - - apiPath, err := androidAPIPath() - if err != nil { - return err - } - - args := []string{ - "-d", dst, - "-source", javacTargetVer, - "-target", javacTargetVer, - "-bootclasspath", filepath.Join(apiPath, "android.jar"), - } - args = append(args, srcFiles...) - - buf := new(bytes.Buffer) - javac := exec.Command("javac", args...) - javac.Dir = srcDir - if buildV { - javac.Stdout = os.Stdout - javac.Stderr = os.Stderr - } else { - javac.Stdout = buf - javac.Stderr = buf - } - if buildX { - printcmd("%s", strings.Join(javac.Args, " ")) - } - if !buildN { - if err := javac.Run(); err != nil { - buf.WriteTo(xout) - return err - } - } - - if buildX { - printcmd("jar c -C %s .", dst) - } - if buildN { - return nil - } - - jarw := zip.NewWriter(w) - jarwcreate := func(name string) (io.Writer, error) { - if buildV { - fmt.Fprintf(os.Stderr, "jar: %s\n", name) - } - return jarw.Create(name) - } - f, err := jarwcreate("META-INF/MANIFEST.MF") - if err != nil { - return err - } - fmt.Fprintf(f, manifestHeader) - - err = filepath.Walk(dst, func(path string, info os.FileInfo, err error) error { - if err != nil { - return err - } - if info.IsDir() { - return nil - } - out, err := jarwcreate(filepath.ToSlash(path[len(dst)+1:])) - if err != nil { - return err - } - in, err := os.Open(path) - if err != nil { - return err - } - defer in.Close() - _, err = io.Copy(out, in) - return err - }) - if err != nil { - return err - } - return jarw.Close() -} - -// androidAPIPath returns an android SDK platform directory under ANDROID_HOME. -// If there are multiple platforms that satisfy the minimum version requirement -// androidAPIPath returns the latest one among them. -func androidAPIPath() (string, error) { - sdk := os.Getenv("ANDROID_HOME") - if sdk == "" { - return "", fmt.Errorf("ANDROID_HOME environment var is not set") - } - sdkDir, err := os.Open(filepath.Join(sdk, "platforms")) - if err != nil { - return "", fmt.Errorf("failed to find android SDK platform: %v", err) - } - defer sdkDir.Close() - fis, err := sdkDir.Readdir(-1) - if err != nil { - return "", fmt.Errorf("failed to find android SDK platform (min API level: %d): %v", minAndroidAPI, err) - } - - var apiPath string - var apiVer int - for _, fi := range fis { - name := fi.Name() - if !fi.IsDir() || !strings.HasPrefix(name, "android-") { - continue - } - n, err := strconv.Atoi(name[len("android-"):]) - if err != nil || n < minAndroidAPI { - continue - } - p := filepath.Join(sdkDir.Name(), name) - _, err = os.Stat(filepath.Join(p, "android.jar")) - if err == nil && apiVer < n { - apiPath = p - apiVer = n - } - } - if apiVer == 0 { - return "", fmt.Errorf("failed to find android SDK platform (min API level: %d) in %s", - minAndroidAPI, sdkDir.Name()) - } - return apiPath, nil -} diff --git a/cmd/gomobile/bind_androidapp.go b/cmd/gomobile/bind_androidapp.go new file mode 100644 index 0000000..e4283c8 --- /dev/null +++ b/cmd/gomobile/bind_androidapp.go @@ -0,0 +1,386 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( + "archive/zip" + "bytes" + "fmt" + "go/build" + "io" + "io/ioutil" + "os" + "os/exec" + "path/filepath" + "strconv" + "strings" + "text/template" +) + +func goAndroidBind(pkg *build.Package) error { + if sdkDir := os.Getenv("ANDROID_HOME"); sdkDir == "" { + return fmt.Errorf("this command requires ANDROID_HOME environment variable (path to the Android SDK)") + } + + binder, err := newBinder(pkg) + if err != nil { + return err + } + + if err := binder.GenGo(tmpdir); err != nil { + return err + } + + mainFile := filepath.Join(tmpdir, "androidlib/main.go") + err = writeFile(mainFile, func(w io.Writer) error { + return androidMainTmpl.Execute(w, "../go_"+binder.pkg.Name()) + }) + if err != nil { + return fmt.Errorf("failed to create the main package for android: %v", err) + } + + androidDir := filepath.Join(tmpdir, "android") + + err = goBuild( + mainFile, + androidArmEnv, + "-buildmode=c-shared", + "-o="+filepath.Join(androidDir, "src/main/jniLibs/armeabi-v7a/libgojni.so"), + ) + if err != nil { + return err + } + + p, err := ctx.Import("golang.org/x/mobile/bind", cwd, build.ImportComment) + if err != nil { + return fmt.Errorf(`"golang.org/x/mobile/bind" is not found; run go get golang.org/x/mobile/bind`) + } + repo := filepath.Clean(filepath.Join(p.Dir, "..")) // golang.org/x/mobile directory. + + // TODO(crawshaw): use a better package path derived from the go package. + if err := binder.GenJava(filepath.Join(androidDir, "src/main/java/go/"+binder.pkg.Name())); err != nil { + return err + } + + dst := filepath.Join(androidDir, "src/main/java/go/LoadJNI.java") + if err := ioutil.WriteFile(dst, []byte(loadSrc), 0664); err != nil { + return err + } + + src := filepath.Join(repo, "bind/java/Seq.java") + dst = filepath.Join(androidDir, "src/main/java/go/Seq.java") + rm(dst) + if err := symlink(src, dst); err != nil { + return err + } + + return buildAAR(androidDir, pkg) +} + +var loadSrc = `package go; + +public class LoadJNI { + static { + System.loadLibrary("gojni"); + } +} +` + +var androidMainTmpl = template.Must(template.New("android.go").Parse(` +package main + +import ( + _ "golang.org/x/mobile/bind/java" + _ "{{.}}" +) + +func main() {} +`)) + +// AAR is the format for the binary distribution of an Android Library Project +// and it is a ZIP archive with extension .aar. +// http://tools.android.com/tech-docs/new-build-system/aar-format +// +// These entries are directly at the root of the archive. +// +// AndroidManifest.xml (mandatory) +// classes.jar (mandatory) +// assets/ (optional) +// jni//libgojni.so +// R.txt (mandatory) +// res/ (mandatory) +// libs/*.jar (optional, not relevant) +// proguard.txt (optional) +// lint.jar (optional, not relevant) +// aidl (optional, not relevant) +// +// javac and jar commands are needed to build classes.jar. +func buildAAR(androidDir string, pkg *build.Package) (err error) { + var out io.Writer = ioutil.Discard + if buildO == "" { + buildO = pkg.Name + ".aar" + } + if !strings.HasSuffix(buildO, ".aar") { + return fmt.Errorf("output file name %q does not end in '.aar'", buildO) + } + if !buildN { + f, err := os.Create(buildO) + if err != nil { + return err + } + defer func() { + if cerr := f.Close(); err == nil { + err = cerr + } + }() + out = f + } + + aarw := zip.NewWriter(out) + aarwcreate := func(name string) (io.Writer, error) { + if buildV { + fmt.Fprintf(os.Stderr, "aar: %s\n", name) + } + return aarw.Create(name) + } + w, err := aarwcreate("AndroidManifest.xml") + if err != nil { + return err + } + const manifestFmt = `` + fmt.Fprintf(w, manifestFmt, "go."+pkg.Name+".gojni") + + w, err = aarwcreate("proguard.txt") + if err != nil { + return err + } + fmt.Fprintln(w, `-keep class go.** { *; }`) + + w, err = aarwcreate("classes.jar") + if err != nil { + return err + } + src := filepath.Join(androidDir, "src/main/java") + if err := buildJar(w, src); err != nil { + return err + } + + assetsDir := filepath.Join(pkg.Dir, "assets") + assetsDirExists := false + if fi, err := os.Stat(assetsDir); err == nil { + assetsDirExists = fi.IsDir() + } else if !os.IsNotExist(err) { + return err + } + + if assetsDirExists { + err := filepath.Walk( + assetsDir, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + if info.IsDir() { + return nil + } + f, err := os.Open(path) + if err != nil { + return err + } + defer f.Close() + name := "assets/" + path[len(assetsDir)+1:] + w, err := aarwcreate(name) + if err != nil { + return nil + } + _, err = io.Copy(w, f) + return err + }) + if err != nil { + return err + } + } + + lib := "armeabi-v7a/libgojni.so" + w, err = aarwcreate("jni/" + lib) + if err != nil { + return err + } + if !buildN { + r, err := os.Open(filepath.Join(androidDir, "src/main/jniLibs/"+lib)) + if err != nil { + return err + } + defer r.Close() + if _, err := io.Copy(w, r); err != nil { + return err + } + } + + // TODO(hyangah): do we need to use aapt to create R.txt? + w, err = aarwcreate("R.txt") + if err != nil { + return err + } + + w, err = aarwcreate("res/") + if err != nil { + return err + } + + return aarw.Close() +} + +const ( + javacTargetVer = "1.7" + minAndroidAPI = 9 +) + +func buildJar(w io.Writer, srcDir string) error { + var srcFiles []string + if buildN { + srcFiles = []string{"*.java"} + } else { + err := filepath.Walk(srcDir, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + if filepath.Ext(path) == ".java" { + srcFiles = append(srcFiles, filepath.Join(".", path[len(srcDir):])) + } + return nil + }) + if err != nil { + return err + } + } + + dst := filepath.Join(tmpdir, "javac-output") + if !buildN { + if err := os.MkdirAll(dst, 0700); err != nil { + return err + } + } + defer removeAll(dst) + + apiPath, err := androidAPIPath() + if err != nil { + return err + } + + args := []string{ + "-d", dst, + "-source", javacTargetVer, + "-target", javacTargetVer, + "-bootclasspath", filepath.Join(apiPath, "android.jar"), + } + args = append(args, srcFiles...) + + buf := new(bytes.Buffer) + javac := exec.Command("javac", args...) + javac.Dir = srcDir + if buildV { + javac.Stdout = os.Stdout + javac.Stderr = os.Stderr + } else { + javac.Stdout = buf + javac.Stderr = buf + } + if buildX { + printcmd("%s", strings.Join(javac.Args, " ")) + } + if !buildN { + if err := javac.Run(); err != nil { + buf.WriteTo(xout) + return err + } + } + + if buildX { + printcmd("jar c -C %s .", dst) + } + if buildN { + return nil + } + + jarw := zip.NewWriter(w) + jarwcreate := func(name string) (io.Writer, error) { + if buildV { + fmt.Fprintf(os.Stderr, "jar: %s\n", name) + } + return jarw.Create(name) + } + f, err := jarwcreate("META-INF/MANIFEST.MF") + if err != nil { + return err + } + fmt.Fprintf(f, manifestHeader) + + err = filepath.Walk(dst, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + if info.IsDir() { + return nil + } + out, err := jarwcreate(filepath.ToSlash(path[len(dst)+1:])) + if err != nil { + return err + } + in, err := os.Open(path) + if err != nil { + return err + } + defer in.Close() + _, err = io.Copy(out, in) + return err + }) + if err != nil { + return err + } + return jarw.Close() +} + +// androidAPIPath returns an android SDK platform directory under ANDROID_HOME. +// If there are multiple platforms that satisfy the minimum version requirement +// androidAPIPath returns the latest one among them. +func androidAPIPath() (string, error) { + sdk := os.Getenv("ANDROID_HOME") + if sdk == "" { + return "", fmt.Errorf("ANDROID_HOME environment var is not set") + } + sdkDir, err := os.Open(filepath.Join(sdk, "platforms")) + if err != nil { + return "", fmt.Errorf("failed to find android SDK platform: %v", err) + } + defer sdkDir.Close() + fis, err := sdkDir.Readdir(-1) + if err != nil { + return "", fmt.Errorf("failed to find android SDK platform (min API level: %d): %v", minAndroidAPI, err) + } + + var apiPath string + var apiVer int + for _, fi := range fis { + name := fi.Name() + if !fi.IsDir() || !strings.HasPrefix(name, "android-") { + continue + } + n, err := strconv.Atoi(name[len("android-"):]) + if err != nil || n < minAndroidAPI { + continue + } + p := filepath.Join(sdkDir.Name(), name) + _, err = os.Stat(filepath.Join(p, "android.jar")) + if err == nil && apiVer < n { + apiPath = p + apiVer = n + } + } + if apiVer == 0 { + return "", fmt.Errorf("failed to find android SDK platform (min API level: %d) in %s", + minAndroidAPI, sdkDir.Name()) + } + return apiPath, nil +} diff --git a/cmd/gomobile/build.go b/cmd/gomobile/build.go index 00cc31a..f4a2401 100644 --- a/cmd/gomobile/build.go +++ b/cmd/gomobile/build.go @@ -6,26 +6,18 @@ package main import ( "bytes" - "crypto/x509" - "encoding/pem" - "errors" "fmt" "go/build" "io" - "io/ioutil" "os" "os/exec" - "path" - "path/filepath" "runtime" "strconv" "strings" ) var ctx = build.Default -var pkg *build.Package -var gomobilepath string // $GOPATH/pkg/gomobile -var ndkccpath string // $GOPATH/pkg/gomobile/android-{{.NDK}} +var pkg *build.Package // TODO(crawshaw): remove global pkg variable var tmpdir string var cmdBuild = &command{ @@ -62,10 +54,12 @@ For documentation, see 'go help build'. } func runBuild(cmd *command) (err error) { - cwd, err := os.Getwd() + cleanup, err := envInit() if err != nil { - panic(err) + return err } + defer cleanup() + args := cmd.flag.Args() switch len(args) { @@ -81,214 +75,43 @@ func runBuild(cmd *command) (err error) { return err } + if pkg.Name != "main" && buildO != "" { + return fmt.Errorf("cannot set -o when building non-main package") + } + switch buildTarget { case "android": - // implementation is below + if pkg.Name != "main" { + return goBuild(pkg.ImportPath, androidArmEnv) + } + if err := goAndroidBuild(pkg); err != nil { + return err + } case "ios": - if runtime.GOOS == "darwin" { - if pkg.Name != "main" { - return fmt.Errorf("cannot build non-main packages") - } - if err := importsApp(pkg); err != nil { + if runtime.GOOS != "darwin" { + return fmt.Errorf("-target=ios requires darwin host") + } + if pkg.Name != "main" { + if err := goBuild(pkg.ImportPath, darwinArmEnv); err != nil { return err } - return goIOSBuild(pkg.ImportPath) + return goBuild(pkg.ImportPath, darwinArm64Env) + } + if err := goIOSBuild(pkg); err != nil { + return err } - return fmt.Errorf("-target=ios requires darwin host") default: return fmt.Errorf(`unknown -target, %q.`, buildTarget) } - if pkg.Name != "main" { - // Not an app, don't build a final package. - return goAndroidBuild(pkg.ImportPath, "") - } - + // TODO(crawshaw): This is an incomplete package scan. + // A complete package scan would be too expensive. Instead, + // fake it. After the binary is built, scan its symbols + // with nm and look for the app and al packages. if err := importsApp(pkg); err != nil { return err } - if buildN { - tmpdir = "$WORK" - } else { - tmpdir, err = ioutil.TempDir("", "gobuildapk-work-") - if err != nil { - return err - } - } - defer removeAll(tmpdir) - if buildX { - fmt.Fprintln(xout, "WORK="+tmpdir) - } - - libName := path.Base(pkg.ImportPath) - manifestData, err := ioutil.ReadFile(filepath.Join(pkg.Dir, "AndroidManifest.xml")) - if err != nil { - if !os.IsNotExist(err) { - return err - } - buf := new(bytes.Buffer) - buf.WriteString(``) - err := manifestTmpl.Execute(buf, manifestTmplData{ - // TODO(crawshaw): a better package path. - JavaPkgPath: "org.golang.todo." + libName, - Name: libName, - LibName: libName, - }) - if err != nil { - return err - } - manifestData = buf.Bytes() - if buildV { - fmt.Fprintf(os.Stderr, "generated AndroidManifest.xml:\n%s\n", manifestData) - } - } else { - libName, err = manifestLibName(manifestData) - if err != nil { - return err - } - } - libPath := filepath.Join(tmpdir, "lib"+libName+".so") - - if err := goAndroidBuild(pkg.ImportPath, libPath); err != nil { - return err - } - block, _ := pem.Decode([]byte(debugCert)) - if block == nil { - return errors.New("no debug cert") - } - privKey, err := x509.ParsePKCS1PrivateKey(block.Bytes) - if err != nil { - return err - } - - if buildO == "" { - buildO = filepath.Base(pkg.Dir) + ".apk" - } - if !strings.HasSuffix(buildO, ".apk") { - return fmt.Errorf("output file name %q does not end in '.apk'", buildO) - } - var out io.Writer - if !buildN { - f, err := os.Create(buildO) - if err != nil { - return err - } - defer func() { - if cerr := f.Close(); err == nil { - err = cerr - } - }() - out = f - } - - var apkw *Writer - if !buildN { - apkw = NewWriter(out, privKey) - } - apkwcreate := func(name string) (io.Writer, error) { - if buildV { - fmt.Fprintf(os.Stderr, "apk: %s\n", name) - } - if buildN { - return ioutil.Discard, nil - } - return apkw.Create(name) - } - - w, err := apkwcreate("AndroidManifest.xml") - if err != nil { - return err - } - if _, err := w.Write(manifestData); err != nil { - return err - } - - w, err = apkwcreate("lib/armeabi/lib" + libName + ".so") - if err != nil { - return err - } - if !buildN { - r, err := os.Open(libPath) - if err != nil { - return err - } - if _, err := io.Copy(w, r); err != nil { - return err - } - } - - importsAL := pkgImportsAL(pkg) - if importsAL { - alDir := filepath.Join(ndkccpath, "openal/lib") - filepath.Walk(alDir, func(path string, info os.FileInfo, err error) error { - if err != nil { - return err - } - if info.IsDir() { - return nil - } - name := "lib/" + path[len(alDir)+1:] - w, err := apkwcreate(name) - if err != nil { - return err - } - if !buildN { - f, err := os.Open(path) - if err != nil { - return err - } - defer f.Close() - _, err = io.Copy(w, f) - } - return err - }) - } - - // Add any assets. - assetsDir := filepath.Join(pkg.Dir, "assets") - assetsDirExists := true - fi, err := os.Stat(assetsDir) - if err != nil { - if os.IsNotExist(err) { - assetsDirExists = false - } else { - return err - } - } else { - assetsDirExists = fi.IsDir() - } - if assetsDirExists { - filepath.Walk(assetsDir, func(path string, info os.FileInfo, err error) error { - if err != nil { - return err - } - if info.IsDir() { - return nil - } - name := "assets/" + path[len(assetsDir)+1:] - w, err := apkwcreate(name) - if err != nil { - return err - } - f, err := os.Open(path) - if err != nil { - return err - } - defer f.Close() - _, err = io.Copy(w, f) - return err - }) - } - - // TODO: add gdbserver to apk? - - if !buildN { - if err := apkw.Close(); err != nil { - return err - } - } - return nil } @@ -353,114 +176,9 @@ func addBuildFlagsNVX(cmd *command) { cmd.flag.BoolVar(&buildX, "x", false, "") } -// goAndroidBuild builds a package. -// If libPath is specified then it builds as a shared library. -func goAndroidBuild(src, libPath string) error { - version, err := goVersion() - if err != nil { - return err - } - - gopath := goEnv("GOPATH") - for _, p := range filepath.SplitList(gopath) { - gomobilepath = filepath.Join(p, "pkg", "gomobile") - if _, err = os.Stat(gomobilepath); err == nil { - break - } - } - if err != nil || gomobilepath == "" { - return errors.New("toolchain not installed, run:\n\tgomobile init") - } - verpath := filepath.Join(gomobilepath, "version") - installedVersion, err := ioutil.ReadFile(verpath) - if err != nil { - return errors.New("toolchain partially installed, run:\n\tgomobile init") - } - if !bytes.Equal(installedVersion, version) { - return errors.New("toolchain out of date, run:\n\tgomobile init") - } - - ndkccpath = filepath.Join(gomobilepath, "android-"+ndkVersion) - ndkccbin := filepath.Join(ndkccpath, "arm", "bin") - if buildX { - fmt.Fprintln(xout, "GOMOBILE="+gomobilepath) - } - - gocmd := exec.Command( - `go`, - `build`, - `-tags=`+strconv.Quote(strings.Join(ctx.BuildTags, ",")), - ) - if buildV { - gocmd.Args = append(gocmd.Args, "-v") - } - if buildI { - gocmd.Args = append(gocmd.Args, "-i") - } - if buildX { - gocmd.Args = append(gocmd.Args, "-x") - } - if libPath == "" { - if buildO != "" { - gocmd.Args = append(gocmd.Args, `-o`, buildO) - } - } else { - gocmd.Args = append(gocmd.Args, "-buildmode=c-shared", "-o", libPath) - } - - gocmd.Args = append(gocmd.Args, src) - - gocmd.Stdout = os.Stdout - gocmd.Stderr = os.Stderr - gocmd.Env = []string{ - `GOOS=android`, - `GOARCH=arm`, - `GOARM=7`, - `CGO_ENABLED=1`, - `CC=` + filepath.Join(ndkccbin, "arm-linux-androideabi-gcc"), - `CXX=` + filepath.Join(ndkccbin, "arm-linux-androideabi-g++"), - `GOGCCFLAGS="-fPIC -marm -pthread -fmessage-length=0"`, - `GOROOT=` + goEnv("GOROOT"), - `GOPATH=` + gopath, - } - if buildX { - printcmd("%s", strings.Join(gocmd.Env, " ")+" "+strings.Join(gocmd.Args, " ")) - } - if !buildN { - gocmd.Env = environ(gocmd.Env) - if err := gocmd.Run(); err != nil { - return err - } - } - return nil -} - -var importsALPkg = make(map[string]struct{}) - -// pkgImportsAL returns true if the given package or one of its -// dependencies imports the x/mobile/exp/audio/al package. -func pkgImportsAL(pkg *build.Package) bool { - for _, path := range pkg.Imports { - if path == "C" { - continue - } - if _, ok := importsALPkg[path]; ok { - continue - } - importsALPkg[path] = struct{}{} - if strings.HasPrefix(path, "golang.org/x/mobile/exp/audio/al") { - return true - } - dPkg, err := ctx.Import(path, "", build.ImportComment) - if err != nil { - fmt.Fprintf(os.Stderr, "error reading OpenAL library: %v", err) - os.Exit(2) - } - if pkgImportsAL(dPkg) { - return true - } - } - return false +type binInfo struct { + hasPkgApp bool + hasPkgAL bool } func init() { @@ -476,38 +194,6 @@ func init() { addBuildFlagsNVX(cmdBind) } -// A random uninteresting private key. -// Must be consistent across builds so newer app versions can be installed. -const debugCert = ` ------BEGIN RSA PRIVATE KEY----- -MIIEowIBAAKCAQEAy6ItnWZJ8DpX9R5FdWbS9Kr1U8Z7mKgqNByGU7No99JUnmyu -NQ6Uy6Nj0Gz3o3c0BXESECblOC13WdzjsH1Pi7/L9QV8jXOXX8cvkG5SJAyj6hcO -LOapjDiN89NXjXtyv206JWYvRtpexyVrmHJgRAw3fiFI+m4g4Qop1CxcIF/EgYh7 -rYrqh4wbCM1OGaCleQWaOCXxZGm+J5YNKQcWpjZRrDrb35IZmlT0bK46CXUKvCqK -x7YXHgfhC8ZsXCtsScKJVHs7gEsNxz7A0XoibFw6DoxtjKzUCktnT0w3wxdY7OTj -9AR8mobFlM9W3yirX8TtwekWhDNTYEu8dwwykwIDAQABAoIBAA2hjpIhvcNR9H9Z -BmdEecydAQ0ZlT5zy1dvrWI++UDVmIp+Ve8BSd6T0mOqV61elmHi3sWsBN4M1Rdz -3N38lW2SajG9q0fAvBpSOBHgAKmfGv3Ziz5gNmtHgeEXfZ3f7J95zVGhlHqWtY95 -JsmuplkHxFMyITN6WcMWrhQg4A3enKLhJLlaGLJf9PeBrvVxHR1/txrfENd2iJBH -FmxVGILL09fIIktJvoScbzVOneeWXj5vJGzWVhB17DHBbANGvVPdD5f+k/s5aooh -hWAy/yLKocr294C4J+gkO5h2zjjjSGcmVHfrhlXQoEPX+iW1TGoF8BMtl4Llc+jw -lKWKfpECgYEA9C428Z6CvAn+KJ2yhbAtuRo41kkOVoiQPtlPeRYs91Pq4+NBlfKO -2nWLkyavVrLx4YQeCeaEU2Xoieo9msfLZGTVxgRlztylOUR+zz2FzDBYGicuUD3s -EqC0Wv7tiX6dumpWyOcVVLmR9aKlOUzA9xemzIsWUwL3PpyONhKSq7kCgYEA1X2F -f2jKjoOVzglhtuX4/SP9GxS4gRf9rOQ1Q8DzZhyH2LZ6Dnb1uEQvGhiqJTU8CXxb -7odI0fgyNXq425Nlxc1Tu0G38TtJhwrx7HWHuFcbI/QpRtDYLWil8Zr7Q3BT9rdh -moo4m937hLMvqOG9pyIbyjOEPK2WBCtKW5yabqsCgYEAu9DkUBr1Qf+Jr+IEU9I8 -iRkDSMeusJ6gHMd32pJVCfRRQvIlG1oTyTMKpafmzBAd/rFpjYHynFdRcutqcShm -aJUq3QG68U9EAvWNeIhA5tr0mUEz3WKTt4xGzYsyWES8u4tZr3QXMzD9dOuinJ1N -+4EEumXtSPKKDG3M8Qh+KnkCgYBUEVSTYmF5EynXc2xOCGsuy5AsrNEmzJqxDUBI -SN/P0uZPmTOhJIkIIZlmrlW5xye4GIde+1jajeC/nG7U0EsgRAV31J4pWQ5QJigz -0+g419wxIUFryGuIHhBSfpP472+w1G+T2mAGSLh1fdYDq7jx6oWE7xpghn5vb9id -EKLjdwKBgBtz9mzbzutIfAW0Y8F23T60nKvQ0gibE92rnUbjPnw8HjL3AZLU05N+ -cSL5bhq0N5XHK77sscxW9vXjG0LJMXmFZPp9F6aV6ejkMIXyJ/Yz/EqeaJFwilTq -Mc6xR47qkdzu0dQ1aPm4XD7AWDtIvPo/GG2DKOucLBbQc2cOWtKS ------END RSA PRIVATE KEY----- -` - // environ merges os.Environ and the given "key=value" pairs. func environ(kv []string) []string { envs := map[string]string{} @@ -542,3 +228,48 @@ func environ(kv []string) []string { } return new } + +func goBuild(src string, env []string, args ...string) error { + cmd := exec.Command( + `go`, + `build`, + `-tags=`+strconv.Quote(strings.Join(ctx.BuildTags, ",")), + ) + if buildV { + cmd.Args = append(cmd.Args, "-v") + } + if buildI { + cmd.Args = append(cmd.Args, "-i") + } + if buildX { + cmd.Args = append(cmd.Args, "-x") + } + cmd.Args = append(cmd.Args, args...) + cmd.Args = append(cmd.Args, src) + cmd.Env = append(cmd.Env, env...) + cmd.Env = append(cmd.Env, + `CGO_ENABLED=1`, + `GOROOT=`+goEnv("GOROOT"), + `GOPATH=`+goEnv("GOPATH"), + ) + buf := new(bytes.Buffer) + buf.WriteByte('\n') + if buildV { + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + } else { + cmd.Stdout = buf + cmd.Stderr = buf + } + + if buildX { + printcmd("%s", strings.Join(cmd.Env, " ")+" "+strings.Join(cmd.Args, " ")) + } + if !buildN { + cmd.Env = environ(cmd.Env) + if err := cmd.Run(); err != nil { + return fmt.Errorf("go build failed: %v%s", err, buf) + } + } + return nil +} diff --git a/cmd/gomobile/build_androidapp.go b/cmd/gomobile/build_androidapp.go new file mode 100644 index 0000000..e366abe --- /dev/null +++ b/cmd/gomobile/build_androidapp.go @@ -0,0 +1,260 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( + "bytes" + "crypto/x509" + "encoding/pem" + "errors" + "fmt" + "go/build" + "io" + "io/ioutil" + "os" + "path" + "path/filepath" + "strings" +) + +func goAndroidBuild(pkg *build.Package) error { + libName := path.Base(pkg.ImportPath) + manifestData, err := ioutil.ReadFile(filepath.Join(pkg.Dir, "AndroidManifest.xml")) + if err != nil { + if !os.IsNotExist(err) { + return err + } + buf := new(bytes.Buffer) + buf.WriteString(``) + err := manifestTmpl.Execute(buf, manifestTmplData{ + // TODO(crawshaw): a better package path. + JavaPkgPath: "org.golang.todo." + libName, + Name: libName, + LibName: libName, + }) + if err != nil { + return err + } + manifestData = buf.Bytes() + if buildV { + fmt.Fprintf(os.Stderr, "generated AndroidManifest.xml:\n%s\n", manifestData) + } + } else { + libName, err = manifestLibName(manifestData) + if err != nil { + return err + } + } + libPath := filepath.Join(tmpdir, "lib"+libName+".so") + + err = goBuild( + pkg.ImportPath, + androidArmEnv, + "-buildmode=c-shared", + "-o", libPath, + ) + if err != nil { + return err + } + block, _ := pem.Decode([]byte(debugCert)) + if block == nil { + return errors.New("no debug cert") + } + privKey, err := x509.ParsePKCS1PrivateKey(block.Bytes) + if err != nil { + return err + } + + if buildO == "" { + buildO = filepath.Base(pkg.Dir) + ".apk" + } + if !strings.HasSuffix(buildO, ".apk") { + return fmt.Errorf("output file name %q does not end in '.apk'", buildO) + } + var out io.Writer + if !buildN { + f, err := os.Create(buildO) + if err != nil { + return err + } + defer func() { + if cerr := f.Close(); err == nil { + err = cerr + } + }() + out = f + } + + var apkw *Writer + if !buildN { + apkw = NewWriter(out, privKey) + } + apkwcreate := func(name string) (io.Writer, error) { + if buildV { + fmt.Fprintf(os.Stderr, "apk: %s\n", name) + } + if buildN { + return ioutil.Discard, nil + } + return apkw.Create(name) + } + + w, err := apkwcreate("AndroidManifest.xml") + if err != nil { + return err + } + if _, err := w.Write(manifestData); err != nil { + return err + } + + w, err = apkwcreate("lib/armeabi/lib" + libName + ".so") + if err != nil { + return err + } + if !buildN { + r, err := os.Open(libPath) + if err != nil { + return err + } + if _, err := io.Copy(w, r); err != nil { + return err + } + } + + if pkgImportsAL(pkg) { + alDir := filepath.Join(ndkccpath, "openal/lib") + filepath.Walk(alDir, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + if info.IsDir() { + return nil + } + name := "lib/" + path[len(alDir)+1:] + w, err := apkwcreate(name) + if err != nil { + return err + } + if !buildN { + f, err := os.Open(path) + if err != nil { + return err + } + defer f.Close() + _, err = io.Copy(w, f) + } + return err + }) + } + + // Add any assets. + assetsDir := filepath.Join(pkg.Dir, "assets") + assetsDirExists := true + fi, err := os.Stat(assetsDir) + if err != nil { + if os.IsNotExist(err) { + assetsDirExists = false + } else { + return err + } + } else { + assetsDirExists = fi.IsDir() + } + if assetsDirExists { + err = filepath.Walk(assetsDir, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + if info.IsDir() { + return nil + } + name := "assets/" + path[len(assetsDir)+1:] + w, err := apkwcreate(name) + if err != nil { + return err + } + f, err := os.Open(path) + if err != nil { + return err + } + defer f.Close() + _, err = io.Copy(w, f) + return err + }) + if err != nil { + return fmt.Errorf("asset %v", err) + } + } + + // TODO: add gdbserver to apk? + + if !buildN { + if err := apkw.Close(); err != nil { + return err + } + } + + return nil +} + +var importsALPkg = make(map[string]struct{}) + +// pkgImportsAL returns true if the given package or one of its +// dependencies imports the x/mobile/exp/audio/al package. +func pkgImportsAL(pkg *build.Package) bool { + for _, path := range pkg.Imports { + if path == "C" { + continue + } + if _, ok := importsALPkg[path]; ok { + continue + } + importsALPkg[path] = struct{}{} + if strings.HasPrefix(path, "golang.org/x/mobile/exp/audio/al") { + return true + } + dPkg, err := ctx.Import(path, "", build.ImportComment) + if err != nil { + fmt.Fprintf(os.Stderr, "error reading OpenAL library: %v", err) + os.Exit(2) + } + if pkgImportsAL(dPkg) { + return true + } + } + return false +} + +// A random uninteresting private key. +// Must be consistent across builds so newer app versions can be installed. +const debugCert = ` +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAy6ItnWZJ8DpX9R5FdWbS9Kr1U8Z7mKgqNByGU7No99JUnmyu +NQ6Uy6Nj0Gz3o3c0BXESECblOC13WdzjsH1Pi7/L9QV8jXOXX8cvkG5SJAyj6hcO +LOapjDiN89NXjXtyv206JWYvRtpexyVrmHJgRAw3fiFI+m4g4Qop1CxcIF/EgYh7 +rYrqh4wbCM1OGaCleQWaOCXxZGm+J5YNKQcWpjZRrDrb35IZmlT0bK46CXUKvCqK +x7YXHgfhC8ZsXCtsScKJVHs7gEsNxz7A0XoibFw6DoxtjKzUCktnT0w3wxdY7OTj +9AR8mobFlM9W3yirX8TtwekWhDNTYEu8dwwykwIDAQABAoIBAA2hjpIhvcNR9H9Z +BmdEecydAQ0ZlT5zy1dvrWI++UDVmIp+Ve8BSd6T0mOqV61elmHi3sWsBN4M1Rdz +3N38lW2SajG9q0fAvBpSOBHgAKmfGv3Ziz5gNmtHgeEXfZ3f7J95zVGhlHqWtY95 +JsmuplkHxFMyITN6WcMWrhQg4A3enKLhJLlaGLJf9PeBrvVxHR1/txrfENd2iJBH +FmxVGILL09fIIktJvoScbzVOneeWXj5vJGzWVhB17DHBbANGvVPdD5f+k/s5aooh +hWAy/yLKocr294C4J+gkO5h2zjjjSGcmVHfrhlXQoEPX+iW1TGoF8BMtl4Llc+jw +lKWKfpECgYEA9C428Z6CvAn+KJ2yhbAtuRo41kkOVoiQPtlPeRYs91Pq4+NBlfKO +2nWLkyavVrLx4YQeCeaEU2Xoieo9msfLZGTVxgRlztylOUR+zz2FzDBYGicuUD3s +EqC0Wv7tiX6dumpWyOcVVLmR9aKlOUzA9xemzIsWUwL3PpyONhKSq7kCgYEA1X2F +f2jKjoOVzglhtuX4/SP9GxS4gRf9rOQ1Q8DzZhyH2LZ6Dnb1uEQvGhiqJTU8CXxb +7odI0fgyNXq425Nlxc1Tu0G38TtJhwrx7HWHuFcbI/QpRtDYLWil8Zr7Q3BT9rdh +moo4m937hLMvqOG9pyIbyjOEPK2WBCtKW5yabqsCgYEAu9DkUBr1Qf+Jr+IEU9I8 +iRkDSMeusJ6gHMd32pJVCfRRQvIlG1oTyTMKpafmzBAd/rFpjYHynFdRcutqcShm +aJUq3QG68U9EAvWNeIhA5tr0mUEz3WKTt4xGzYsyWES8u4tZr3QXMzD9dOuinJ1N ++4EEumXtSPKKDG3M8Qh+KnkCgYBUEVSTYmF5EynXc2xOCGsuy5AsrNEmzJqxDUBI +SN/P0uZPmTOhJIkIIZlmrlW5xye4GIde+1jajeC/nG7U0EsgRAV31J4pWQ5QJigz +0+g419wxIUFryGuIHhBSfpP472+w1G+T2mAGSLh1fdYDq7jx6oWE7xpghn5vb9id +EKLjdwKBgBtz9mzbzutIfAW0Y8F23T60nKvQ0gibE92rnUbjPnw8HjL3AZLU05N+ +cSL5bhq0N5XHK77sscxW9vXjG0LJMXmFZPp9F6aV6ejkMIXyJ/Yz/EqeaJFwilTq +Mc6xR47qkdzu0dQ1aPm4XD7AWDtIvPo/GG2DKOucLBbQc2cOWtKS +-----END RSA PRIVATE KEY----- +` diff --git a/cmd/gomobile/ios.go b/cmd/gomobile/build_iosapp.go similarity index 87% rename from cmd/gomobile/ios.go rename to cmd/gomobile/build_iosapp.go index b6f046c..0da9215 100644 --- a/cmd/gomobile/ios.go +++ b/cmd/gomobile/build_iosapp.go @@ -7,38 +7,26 @@ package main import ( "bytes" "fmt" + "go/build" "io" "io/ioutil" "os" "os/exec" "path" "path/filepath" - "strconv" "strings" ) -func goIOSBuild(src string) error { +func goIOSBuild(pkg *build.Package) error { + src := pkg.ImportPath if buildO != "" && !strings.HasSuffix(buildO, ".app") { return fmt.Errorf("-o must have an .app for target=ios") } - dir := "$XCODEPROJ" - if !buildN { - tmp, err := ioutil.TempDir("", "xcodeproject") - if err != nil { - return err - } - dir = tmp - defer os.RemoveAll(dir) - } - if buildX { - printcmd("mkdir %s", dir) - } - layout := map[string][]byte{ - dir + "/main.xcodeproj/project.pbxproj": []byte(projPbxproj), - dir + "/main/Info.plist": []byte(infoPlist), - dir + "/main/Images.xcassets/AppIcon.appiconset/Contents.json": []byte(contentsJSON), + tmpdir + "/main.xcodeproj/project.pbxproj": []byte(projPbxproj), + tmpdir + "/main/Info.plist": []byte(infoPlist), + tmpdir + "/main/Images.xcassets/AppIcon.appiconset/Contents.json": []byte(contentsJSON), } for dst, v := range layout { @@ -55,20 +43,13 @@ func goIOSBuild(src string) error { } } - armPath := filepath.Join(dir, "arm") - if err := goBuild(src, armPath, []string{ - `GOOS=darwin`, - `GOARCH=arm`, - `GOARM=7`, - }); err != nil { + armPath := filepath.Join(tmpdir, "arm") + if err := goBuild(src, darwinArmEnv, "-o="+armPath); err != nil { return err } - arm64Path := filepath.Join(dir, "arm64") - if err := goBuild(src, arm64Path, []string{ - `GOOS=darwin`, - `GOARCH=arm64`, - }); err != nil { + arm64Path := filepath.Join(tmpdir, "arm64") + if err := goBuild(src, darwinArm64Env, "-o="+arm64Path); err != nil { return err } @@ -77,13 +58,13 @@ func goIOSBuild(src string) error { // TODO(jbd): Investigate the new announcements about iO9's fat binary // size limitations are breaking this feature. if buildX { - printcmd("xcrun lipo -create %s %s -o %s", armPath, arm64Path, filepath.Join(dir, "main/main")) + printcmd("xcrun lipo -create %s %s -o %s", armPath, arm64Path, filepath.Join(tmpdir, "main/main")) } if !buildN { cmd := exec.Command( "xcrun", "lipo", "-create", armPath, arm64Path, - "-o", filepath.Join(dir, "main/main"), + "-o", filepath.Join(tmpdir, "main/main"), ) cmd.Stderr = os.Stderr if err := cmd.Run(); err != nil { @@ -92,7 +73,7 @@ func goIOSBuild(src string) error { } // TODO(jbd): Set the launcher icon. - if err := iosCopyAssets(dir); err != nil { + if err := iosCopyAssets(pkg, tmpdir); err != nil { return err } @@ -100,7 +81,7 @@ func goIOSBuild(src string) error { cmd := exec.Command( "xcrun", "xcodebuild", "-configuration", "Release", - "-project", dir+"/main.xcodeproj", + "-project", tmpdir+"/main.xcodeproj", ) if buildX { @@ -128,21 +109,21 @@ func goIOSBuild(src string) error { buildO = path.Base(pkg.ImportPath) + ".app" } if buildX { - printcmd("mv %s %s", dir+"/build/Release-iphoneos/main.app", buildO) + printcmd("mv %s %s", tmpdir+"/build/Release-iphoneos/main.app", buildO) } if !buildN { // if output already exists, remove. if err := os.RemoveAll(buildO); err != nil { return err } - if err := os.Rename(dir+"/build/Release-iphoneos/main.app", buildO); err != nil { + if err := os.Rename(tmpdir+"/build/Release-iphoneos/main.app", buildO); err != nil { return err } } return nil } -func iosCopyAssets(xcodeProjDir string) error { +func iosCopyAssets(pkg *build.Package, xcodeProjDir string) error { dstAssets := xcodeProjDir + "/main/assets" if buildX { printcmd("mkdir -p %s", dstAssets) @@ -199,45 +180,6 @@ func iosCopyAssets(xcodeProjDir string) error { }) } -func goBuild(src, o string, env []string) error { - goroot := goEnv("GOROOT") - gopath := goEnv("GOPATH") - cmd := exec.Command( - `go`, - `build`, - `-tags=`+strconv.Quote(strings.Join(ctx.BuildTags, ",")), - ) - if buildV { - cmd.Args = append(cmd.Args, "-v") - } - if buildI { - cmd.Args = append(cmd.Args, "-i") - } - if buildX { - cmd.Args = append(cmd.Args, "-x") - } - cmd.Args = append(cmd.Args, "-o="+o) - cmd.Args = append(cmd.Args, src) - // TODO(jbd): Remove clangwrap.sh dependency by implementing clangwrap.sh - // in Go in this package. - cmd.Env = append(env, []string{ - `CGO_ENABLED=1`, - `GOROOT=` + goroot, - `GOPATH=` + gopath, - `CC=` + filepath.Join(goroot, "misc/ios/clangwrap.sh"), - `CCX=` + filepath.Join(goroot, "misc/ios/clangwrap.sh"), - }...) - cmd.Stderr = os.Stderr - if buildX { - printcmd("%s", strings.Join(cmd.Env, " ")+" "+strings.Join(cmd.Args, " ")) - } - if !buildN { - cmd.Env = environ(cmd.Env) - return cmd.Run() - } - return nil -} - const infoPlist = ` diff --git a/cmd/gomobile/build_test.go b/cmd/gomobile/build_test.go index 2a1807a..d209c8a 100644 --- a/cmd/gomobile/build_test.go +++ b/cmd/gomobile/build_test.go @@ -46,8 +46,8 @@ func TestBuild(t *testing.T) { } -var buildTmpl = template.Must(template.New("output").Parse(`WORK=$WORK -GOMOBILE={{.GOPATH}}/pkg/gomobile -GOOS=android GOARCH=arm GOARM=7 CGO_ENABLED=1 CC=$GOMOBILE/android-{{.NDK}}/arm/bin/arm-linux-androideabi-gcc{{.EXE}} CXX=$GOMOBILE/android-{{.NDK}}/arm/bin/arm-linux-androideabi-g++{{.EXE}} GOGCCFLAGS="-fPIC -marm -pthread -fmessage-length=0" GOROOT=$GOROOT GOPATH=$GOPATH go build -tags="" -x -buildmode=c-shared -o $WORK/libbasic.so golang.org/x/mobile/example/basic +var buildTmpl = template.Must(template.New("output").Parse(`GOMOBILE={{.GOPATH}}/pkg/gomobile +WORK=$WORK +GOOS=android GOARCH=arm GOARM=7 CC=$GOMOBILE/android-{{.NDK}}/arm/bin/arm-linux-androideabi-gcc{{.EXE}} CXX=$GOMOBILE/android-{{.NDK}}/arm/bin/arm-linux-androideabi-g++{{.EXE}} GOGCCFLAGS="-fPIC -marm -pthread -fmessage-length=0" CGO_ENABLED=1 GOROOT=$GOROOT GOPATH=$GOPATH go build -tags="" -x -buildmode=c-shared -o $WORK/libbasic.so golang.org/x/mobile/example/basic rm -r -f "$WORK" `)) diff --git a/cmd/gomobile/doc.go b/cmd/gomobile/doc.go index f1aae0e..fd9cead 100644 --- a/cmd/gomobile/doc.go +++ b/cmd/gomobile/doc.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// DO NOT EDIT. GENERATED BY 'gomobile help documentation'. +// DO NOT EDIT. GENERATED BY 'gomobile help documentation doc.go'. /* Gomobile is a tool for building and running mobile apps written in Go. diff --git a/cmd/gomobile/env.go b/cmd/gomobile/env.go new file mode 100644 index 0000000..cf25f3b --- /dev/null +++ b/cmd/gomobile/env.go @@ -0,0 +1,105 @@ +package main + +import ( + "bytes" + "errors" + "fmt" + "io/ioutil" + "os" + "path/filepath" +) + +// General mobile build environment. Initialized by envInit. +var ( + cwd string + gomobilepath string // $GOPATH/pkg/gomobile + ndkccpath string // $GOPATH/pkg/gomobile/android-{{.NDK}} + + darwinArmEnv []string + darwinArm64Env []string + androidArmEnv []string +) + +func envInit() (cleanup func(), err error) { + cwd, err = os.Getwd() + if err != nil { + return nil, err + } + + // Find gomobilepath. + gopath := goEnv("GOPATH") + for _, p := range filepath.SplitList(gopath) { + gomobilepath = filepath.Join(p, "pkg", "gomobile") + if _, err := os.Stat(gomobilepath); err == nil { + break + } + } + if buildX { + fmt.Fprintln(xout, "GOMOBILE="+gomobilepath) + } + + // Check the toolchain is in a good state. + version, err := goVersion() + if err != nil { + return nil, err + } + if gomobilepath == "" { + return nil, errors.New("toolchain not installed, run `gomobile init`") + } + verpath := filepath.Join(gomobilepath, "version") + installedVersion, err := ioutil.ReadFile(verpath) + if err != nil { + return nil, errors.New("toolchain partially installed, run `gomobile init`") + } + if !bytes.Equal(installedVersion, version) { + return nil, errors.New("toolchain out of date, run `gomobile init`") + } + + // Setup the cross-compiler environments. + + // TODO(crawshaw): Remove ndkccpath global. + ndkccpath = filepath.Join(gomobilepath, "android-"+ndkVersion) + ndkccbin := filepath.Join(ndkccpath, "arm", "bin") + + androidEnv := []string{ + "CC=" + filepath.Join(ndkccbin, "arm-linux-androideabi-gcc"), + "CXX=" + filepath.Join(ndkccbin, "arm-linux-androideabi-g++"), + `GOGCCFLAGS="-fPIC -marm -pthread -fmessage-length=0"`, + } + androidArmEnv = append([]string{ + "GOOS=android", + "GOARCH=arm", + "GOARM=7", + }, androidEnv...) + + // TODO(jbd): Remove clangwrap.sh dependency by implementing clangwrap.sh + // in Go in this package. + goroot := goEnv("GOROOT") + iosEnv := []string{ + "CC=" + filepath.Join(goroot, "misc/ios/clangwrap.sh"), + "CCX=" + filepath.Join(goroot, "misc/ios/clangwrap.sh"), + } + darwinArmEnv = append([]string{ + "GOOS=darwin", + "GOARCH=arm", + }, iosEnv...) + darwinArm64Env = append([]string{ + "GOOS=darwin", + "GOARCH=arm64", + }, iosEnv...) + + // We need a temporary directory when assembling an apk/app. + if buildN { + tmpdir = "$WORK" + } else { + tmpdir, err = ioutil.TempDir("", "gomobile-work-") + if err != nil { + return nil, err + } + } + if buildX { + fmt.Fprintln(xout, "WORK="+tmpdir) + } + + return func() { removeAll(tmpdir) }, nil +} diff --git a/cmd/gomobile/init.go b/cmd/gomobile/init.go index b6db7c7..f5f8cbc 100644 --- a/cmd/gomobile/init.go +++ b/cmd/gomobile/init.go @@ -4,7 +4,6 @@ package main -// TODO(crawshaw): build darwin/arm cross compiler on darwin/{386,amd64} // TODO(crawshaw): android/{386,arm64} import (