diff --git a/cmd/gomobile/bind.go b/cmd/gomobile/bind.go index 0a21d8e..5a270d8 100644 --- a/cmd/gomobile/bind.go +++ b/cmd/gomobile/bind.go @@ -35,43 +35,39 @@ import ( var cmdBind = &command{ run: runBind, Name: "bind", - Usage: "[package]", + Usage: "[-target android|ios] [-o output] [build flags] [package]", Short: "build a shared library for android APK and iOS app", Long: ` -Bind generates language bindings like gobind (golang.org/x/mobile/cmd/gobind) -for a package and builds a shared library for each platform from the go binding -code. +Bind generates language bindings for the package named by the import +path, and compiles a library for the named target system. -For Android, the bind command produces an AAR (Android ARchive) file that -archives the precompiled Java API stub classes, the compiled shared libraries, -and all asset files in the /assets subdirectory under the package directory. -The output AAR file name is '.aar'. +The -target flag takes a target system name, either android (the +default) or ios. -The AAR file is commonly used for binary distribution of an Android library -project and most Android IDEs support AAR import. For example, in Android -Studio (1.2+), an AAR file can be imported using the module import wizard -(File > New > New Module > Import .JAR or .AAR package), and setting it as -a new dependency (File > Project Structure > Dependencies). +For -target android, the bind command produces an AAR (Android ARchive) +file that archives the precompiled Java API stub classes, the compiled +shared libraries, and all asset files in the /assets subdirectory under +the package directory. The output is named '.aar' by +default. This AAR file is commonly used for binary distribution of an +Android library project and most Android IDEs support AAR import. For +example, in Android Studio (1.2+), an AAR file can be imported using +the module import wizard (File > New > New Module > Import .JAR or +.AAR package), and setting it as a new dependency +(File > Project Structure > Dependencies). This requires 'javac' +(version 1.7+) and Android SDK (API level 9 or newer) to build the +library for Android. The environment variable ANDROID_HOME must be set +to the path to Android SDK. -This command requires 'javac' (version 1.7+) and Android SDK (API level 9 -or newer) to build the library for Android. The environment variable -ANDROID_HOME must be set to the path to Android SDK. +For -target ios, gomobile must be run on an OS X machine with Xcode +installed. Support is not complete. The -v flag provides verbose output, including the list of packages built. -These build flags are shared by the build command. -For documentation, see 'go help build': - -a - -i - -n - -x - -tags 'tag list' +The build flags -a, -i, -n, -x, and -tags are shared with the build command. +For documentation, see 'go help build'. `, } -// TODO: -mobile -// TODO: reuse the -o option to specify the output file name? - func runBind(cmd *command) error { cwd, err := os.Getwd() if err != nil { @@ -93,6 +89,15 @@ func runBind(cmd *command) error { return err } + switch *buildTarget { + case "android": + // implementation is below + 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)") } @@ -318,8 +323,14 @@ func main() { // 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, ".apk") { + return fmt.Errorf("output file name %q does not end in '.aar'", *buildO) + } if !buildN { - f, err := os.Create(pkg.Name + ".aar") + f, err := os.Create(*buildO) if err != nil { return err } diff --git a/cmd/gomobile/build.go b/cmd/gomobile/build.go index c666caf..02d3436 100644 --- a/cmd/gomobile/build.go +++ b/cmd/gomobile/build.go @@ -30,37 +30,36 @@ var tmpdir string var cmdBuild = &command{ run: runBuild, Name: "build", - Usage: "[-o output] [-i] [build flags] [package]", + Usage: "[-target android|ios] [-o output] [build flags] [package]", Short: "compile android APK and iOS app", Long: ` Build compiles and encodes the app named by the import path. The named package must define a main function. -If an AndroidManifest.xml is defined in the package directory, it is -added to the APK file. Otherwise, a default manifest is generated. +The -target flag takes a target system name, either android (the +default) or ios. + +For -target android, if an AndroidManifest.xml is defined in the +package directory, it is added to the APK output. Otherwise, a default +manifest is generated. + +For -target ios, gomobile must be run on an OS X machine with Xcode +installed. Support is not complete. If the package directory contains an assets subdirectory, its contents -are copied into the APK file. +are copied into the output. The -o flag specifies the output file name. If not specified, the -output file name depends on the package built. The output file must end -in '.apk'. +output file name depends on the package built. The -v flag provides verbose output, including the list of packages built. -These build flags are shared by the build, install, and test commands. -For documentation, see 'go help build': - -a - -i - -n - -x - -tags 'tag list' +The build flags -a, -i, -n, -x, and -tags are shared with the build command. +For documentation, see 'go help build'. `, } -// TODO: -mobile - func runBuild(cmd *command) (err error) { cwd, err := os.Getwd() if err != nil { @@ -81,6 +80,15 @@ func runBuild(cmd *command) (err error) { return err } + switch *buildTarget { + case "android": + // implementation is below + case "ios": + return fmt.Errorf(`-target=ios not yet supported`) + 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, "") @@ -108,7 +116,7 @@ func runBuild(cmd *command) (err error) { } defer removeAll(tmpdir) if buildX { - fmt.Fprintln(os.Stderr, "WORK="+tmpdir) + fmt.Fprintln(xout, "WORK="+tmpdir) } libName := path.Base(pkg.ImportPath) @@ -306,15 +314,19 @@ func printcmd(format string, args ...interface{}) { // "Build flags", used by multiple commands. var ( - buildA bool // -a - buildI bool // -i - buildN bool // -n - buildV bool // -v - buildX bool // -x - buildO *string // -o + buildA bool // -a + buildI bool // -i + buildN bool // -n + buildV bool // -v + buildX bool // -x + buildO *string // -o + buildTarget *string // -target ) func addBuildFlags(cmd *command) { + buildO = cmd.flag.String("o", "", "") + buildTarget = cmd.flag.String("target", "android", "") + cmd.flag.BoolVar(&buildA, "a", false, "") cmd.flag.BoolVar(&buildI, "i", false, "") cmd.flag.Var((*stringsFlag)(&ctx.BuildTags), "tags", "") @@ -438,7 +450,6 @@ func pkgImportsAudio(pkg *build.Package) bool { } func init() { - buildO = cmdBuild.flag.String("o", "", "output file") addBuildFlags(cmdBuild) addBuildFlagsNVX(cmdBuild) diff --git a/cmd/gomobile/build_test.go b/cmd/gomobile/build_test.go new file mode 100644 index 0000000..6ebc61b --- /dev/null +++ b/cmd/gomobile/build_test.go @@ -0,0 +1,53 @@ +// 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" + "os" + "path/filepath" + "testing" + "text/template" +) + +func TestBuild(t *testing.T) { + buf := new(bytes.Buffer) + defer func() { + xout = os.Stderr + buildN = false + buildX = false + }() + xout = buf + buildN = true + buildX = true + gopath = filepath.SplitList(os.Getenv("GOPATH"))[0] + if goos == "windows" { + os.Setenv("HOMEDRIVE", "C:") + } + cmdBuild.flag.Parse([]string{"golang.org/x/mobile/example/basic"}) + if *buildTarget != "android" { + t.Fatalf("-target=%q, want android", *buildTarget) + } + err := runBuild(cmdBuild) + if err != nil { + t.Log(buf.String()) + t.Fatal(err) + } + + diff, err := diffOutput(buf.String(), buildTmpl) + if err != nil { + t.Fatalf("computing diff failed: %v", err) + } + if diff != "" { + t.Errorf("unexpected output:\n%s", diff) + } + +} + +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=$HOME GOMOBILEPATH=$GOMOBILE/android-{{.NDK}}/arm/bin go build -tags="" -toolexec=$GOMOBILE/android-{{.NDK}}/arm/bin/toolexec -x -buildmode=c-shared -o $WORK/libbasic.so golang.org/x/mobile/example/basic +rm -r -f "$WORK" +`)) diff --git a/cmd/gomobile/init.go b/cmd/gomobile/init.go index fb71c50..0fb1d54 100644 --- a/cmd/gomobile/init.go +++ b/cmd/gomobile/init.go @@ -76,7 +76,7 @@ func init() { func runInit(cmd *command) error { version, err := goVersion() if err != nil { - return err + return fmt.Errorf("%v: %s", err, version) } gopaths := filepath.SplitList(goEnv("GOPATH")) @@ -426,7 +426,7 @@ func goVersion() ([]byte, error) { if !bytes.Contains(buildHelp, []byte("-toolexec")) { return nil, fmt.Errorf("installed Go tool does not support -toolexec") } - return exec.Command(gobin, "version").Output() + return exec.Command(gobin, "version").CombinedOutput() } // checkVersionMatch makes sure that the go command in the path matches diff --git a/cmd/gomobile/init_test.go b/cmd/gomobile/init_test.go index 1e41de1..ce8dae7 100644 --- a/cmd/gomobile/init_test.go +++ b/cmd/gomobile/init_test.go @@ -13,20 +13,23 @@ import ( "text/template" ) +var gopath string + func TestInit(t *testing.T) { buf := new(bytes.Buffer) - gopath := os.Getenv("GOPATH") + gopathorig := os.Getenv("GOPATH") defer func() { xout = os.Stderr buildN = false buildX = false - os.Setenv("GOPATH", gopath) + os.Setenv("GOPATH", gopathorig) }() xout = buf buildN = true buildX = true // Test that first GOPATH element is chosen correctly. - paths := []string{"GOPATH1", "/path2", "/path3"} + gopath = "/GOPATH1" + paths := []string{"/GOPATH1", "/path2", "/path3"} os.Setenv("GOPATH", strings.Join(paths, string(os.PathListSeparator))) os.Setenv("GOROOT_BOOTSTRAP", "go1.4") if goos == "windows" { @@ -55,6 +58,7 @@ func diffOutput(got string, wantTmpl *template.Template) (string, error) { NDK: ndkVersion, GOOS: goos, GOARCH: goarch, + GOPATH: gopath, NDKARCH: ndkarch, BuildScript: unixBuildScript, } @@ -76,6 +80,7 @@ type outputData struct { NDK string GOOS string GOARCH string + GOPATH string NDKARCH string EXE string // .extension for executables. (ex. ".exe" for windows) BuildScript string @@ -86,9 +91,9 @@ const ( windowsBuildScript = `TEMP=$WORK TMP=$WORK HOMEDRIVE=C: HOMEPATH=$HOMEPATH GOROOT_BOOTSTRAP=go1.4 $WORK/go/src/make.bat --no-clean` ) -var initTmpl = template.Must(template.New("output").Parse(`GOMOBILE=GOPATH1/pkg/gomobile +var initTmpl = template.Must(template.New("output").Parse(`GOMOBILE={{.GOPATH}}/pkg/gomobile mkdir -p $GOMOBILE/android-{{.NDK}} -WORK=GOPATH1/pkg/gomobile/work +WORK=/GOPATH1/pkg/gomobile/work mkdir -p $WORK/go/pkg cp -a $GOROOT/lib $WORK/go/lib cp -a $GOROOT/src $WORK/go/src diff --git a/cmd/gomobile/install.go b/cmd/gomobile/install.go index 94511ec..89a8d6a 100644 --- a/cmd/gomobile/install.go +++ b/cmd/gomobile/install.go @@ -13,15 +13,16 @@ import ( var cmdInstall = &command{ run: runInstall, Name: "install", - Usage: "[package]", + Usage: "[-target android] [build flags] [package]", Short: "compile android APK and iOS app and install on device", Long: ` Install compiles and installs the app named by the import path on the attached mobile device. -This command requires the 'adb' tool on the PATH. +Only -target android is supported. The 'adb' tool must be on the PATH. -See the build command help for common flags and common behavior. +The build flags -a, -i, -n, -x, and -tags are shared with the build command. +For documentation, see 'go help build'. `, }