From 9300366e6529f24ab11d145e0c81f146951d0805 Mon Sep 17 00:00:00 2001 From: David Crawshaw Date: Thu, 16 Jul 2015 13:32:51 -0400 Subject: [PATCH] cmd/gomobile: use new -pkgdir flag The go command now has a -pkgdir flag, which specifies a directory for all install output, including the standard library. Use it to build the mobile compilers under $GOMOBILE, so that targets like the iOS simulator (darwin/386) do not conflict with system targets. The result is we no longer need GOROOT to be writable. The iOS simulator now works with gomobile bind. Fixes golang/go#11342. Change-Id: I0bc6378e0cb82e3175b2a1efe355e3ce39533649 Reviewed-on: https://go-review.googlesource.com/12303 Reviewed-by: Hyang-Ah Hana Kim --- cmd/gomobile/bind.go | 2 +- cmd/gomobile/bind_iosapp.go | 52 +++-------- cmd/gomobile/build.go | 12 +-- cmd/gomobile/env.go | 173 ++++++++++++++++++++++++++++-------- cmd/gomobile/init.go | 57 ++++++------ 5 files changed, 180 insertions(+), 116 deletions(-) diff --git a/cmd/gomobile/bind.go b/cmd/gomobile/bind.go index 5a2058c..e5b7dda 100644 --- a/cmd/gomobile/bind.go +++ b/cmd/gomobile/bind.go @@ -62,7 +62,7 @@ For documentation, see 'go help build'. } func runBind(cmd *command) error { - cleanup, err := envInit() + cleanup, err := buildEnvInit() if err != nil { return err } diff --git a/cmd/gomobile/bind_iosapp.go b/cmd/gomobile/bind_iosapp.go index 89e362e..ad01dac 100644 --- a/cmd/gomobile/bind_iosapp.go +++ b/cmd/gomobile/bind_iosapp.go @@ -43,25 +43,19 @@ func goIOSBind(pkg *build.Package) error { return err } - armPath, err := goIOSBindArchive(name, mainFile, darwinArmEnv) - if err != nil { - return err - } - arm64Path, err := goIOSBindArchive(name, mainFile, darwinArm64Env) - if err != nil { - return err + cmd := exec.Command("xcrun", "lipo", "-create") + + // TODO(crawshaw): Build in parallel. + for _, env := range [][]string{darwinArmEnv, darwinArm64Env, darwinAmd64Env} { + arch := archClang(getenv(env, "GOARCH")) + path, err := goIOSBindArchive(name, mainFile, env) + if err != nil { + return fmt.Errorf("darwin-%s: %v", arch, err) + } + cmd.Args = append(cmd.Args, "-arch", arch, path) } - cmd := exec.Command( - "xcrun", "lipo", - "-create", - "-arch", "arm", - armPath, - "-arch", "arm64", - arm64Path, - "-o", buildO, - ) - // TODO(crawshaw): arch i386/x86_64 for iOS simulator + cmd.Args = append(cmd.Args, "-o", buildO) if buildX { printcmd(strings.Join(cmd.Args, " ")) } @@ -73,8 +67,6 @@ func goIOSBind(pkg *build.Package) error { } } - // TODO(crawshaw): seq.h - // Copy header file next to output archive. return copyFile( filepath.Join(buildO[:len(buildO)-2]+".h"), @@ -90,23 +82,15 @@ func goIOSBindArchive(name, path string, env []string) (string, error) { return "", err } - // Build env suitable for invoking $CC. - cmd := exec.Command("go", "env", "GOGCCFLAGS") - cmd.Env = environ(env) - ccflags, err := cmd.Output() - if err != nil { - panic(err) // the Go tool must work by now - } - env = append([]string{fmt.Sprintf("CCFLAGS=%q", string(ccflags))}, env...) - obj := "gobind-" + name + "-" + arch + ".o" - cmd = exec.Command( + cmd := exec.Command( getenv(env, "CC"), "-I", ".", "-g", "-O2", "-o", obj, "-c", "Go"+strings.Title(name)+".m", ) + cmd.Args = append(cmd.Args, strings.Split(getenv(env, "CGO_CFLAGS"), " ")...) cmd.Dir = filepath.Join(tmpdir, "objc") cmd.Env = env if buildX { @@ -138,16 +122,6 @@ func goIOSBindArchive(name, path string, env []string) (string, error) { return archive, nil } -func getenv(env []string, key string) string { - prefix := key + "=" - for _, kv := range env { - if strings.HasPrefix(kv, prefix) { - return kv[len(prefix):] - } - } - return "" -} - var iosBindTmpl = template.Must(template.New("ios.go").Parse(` package main diff --git a/cmd/gomobile/build.go b/cmd/gomobile/build.go index e4d8d13..8db66b1 100644 --- a/cmd/gomobile/build.go +++ b/cmd/gomobile/build.go @@ -56,7 +56,7 @@ For documentation, see 'go help build'. } func runBuild(cmd *command) (err error) { - cleanup, err := envInit() + cleanup, err := buildEnvInit() if err != nil { return err } @@ -198,9 +198,10 @@ func init() { func goBuild(src string, env []string, args ...string) error { cmd := exec.Command( - `go`, - `build`, - `-tags=`+strconv.Quote(strings.Join(ctx.BuildTags, ",")), + "go", + "build", + "-pkgdir="+pkgdir(env), + "-tags="+strconv.Quote(strings.Join(ctx.BuildTags, ",")), ) if buildV { cmd.Args = append(cmd.Args, "-v") @@ -213,8 +214,7 @@ func goBuild(src string, env []string, args ...string) error { } cmd.Args = append(cmd.Args, args...) cmd.Args = append(cmd.Args, src) - cmd.Env = []string{"CGO_ENABLED=1"} - cmd.Env = append(cmd.Env, env...) + cmd.Env = append([]string{}, env...) buf := new(bytes.Buffer) buf.WriteByte('\n') if buildV { diff --git a/cmd/gomobile/env.go b/cmd/gomobile/env.go index a434c7e..2c9e71e 100644 --- a/cmd/gomobile/env.go +++ b/cmd/gomobile/env.go @@ -6,7 +6,9 @@ import ( "fmt" "io/ioutil" "os" + "os/exec" "path/filepath" + "runtime" "strings" ) @@ -16,17 +18,14 @@ var ( gomobilepath string // $GOPATH/pkg/gomobile ndkccpath string // $GOPATH/pkg/gomobile/android-{{.NDK}} + androidArmEnv []string darwinArmEnv []string darwinArm64Env []string - androidArmEnv []string + darwin386Env []string + darwinAmd64Env []string ) -func envInit() (cleanup func(), err error) { - cwd, err = os.Getwd() - if err != nil { - return nil, err - } - +func buildEnvInit() (cleanup func(), err error) { // Find gomobilepath. gopath := goEnv("GOPATH") for _, p := range filepath.SplitList(gopath) { @@ -56,38 +55,9 @@ func envInit() (cleanup func(), err error) { 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"`, + if err := envInit(); err != nil { + return nil, err } - 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 { @@ -105,6 +75,119 @@ func envInit() (cleanup func(), err error) { return func() { removeAll(tmpdir) }, nil } +func envInit() (err error) { + // TODO(crawshaw): cwd only used by ctx.Import, which can take "." + cwd, err = os.Getwd() + if err != nil { + return err + } + + // Setup the cross-compiler environments. + + // TODO(crawshaw): Remove ndkccpath global. + ndkccpath = filepath.Join(gomobilepath, "android-"+ndkVersion) + ndkccbin := filepath.Join(ndkccpath, "arm", "bin") + + exe := "" + if goos == "windows" { + exe = ".exe" + } + androidArmEnv = []string{ + "GOOS=android", + "GOARCH=arm", + "GOARM=7", + "CC=" + filepath.Join(ndkccbin, "arm-linux-androideabi-gcc"+exe), + "CXX=" + filepath.Join(ndkccbin, "arm-linux-androideabi-g++"+exe), + "CGO_ENABLED=1", + } + + if runtime.GOOS != "darwin" { + return nil + } + + clang, cflags, err := envClang("iphoneos") + if err != nil { + return err + } + darwinArmEnv = []string{ + "GOOS=darwin", + "GOARCH=arm", + "GOARM=7", + "CC=" + clang, + "CXX=" + clang, + "CGO_CFLAGS=" + cflags + " -arch " + archClang("arm"), + "CGO_LDFLAGS=" + cflags + " -arch " + archClang("arm"), + "CGO_ENABLED=1", + } + darwinArm64Env = []string{ + "GOOS=darwin", + "GOARCH=arm64", + "CC=" + clang, + "CXX=" + clang, + "CGO_CFLAGS=" + cflags + " -arch " + archClang("arm64"), + "CGO_LDFLAGS=" + cflags + " -arch " + archClang("arm64"), + "CGO_ENABLED=1", + } + + clang, cflags, err = envClang("iphonesimulator") + if err != nil { + return err + } + darwin386Env = []string{ + "GOOS=darwin", + "GOARCH=386", + "CC=" + clang, + "CXX=" + clang, + "CGO_CFLAGS=" + cflags + " -mios-simulator-version-min=6.1 -arch " + archClang("386"), + "CGO_LDFLAGS=" + cflags + " -mios-simulator-version-min=6.1 -arch " + archClang("386"), + "CGO_ENABLED=1", + } + darwinAmd64Env = []string{ + "GOOS=darwin", + "GOARCH=amd64", + "CC=" + clang, + "CXX=" + clang, + "CGO_CFLAGS=" + cflags + " -mios-simulator-version-min=6.1 -arch x86_64", + "CGO_LDFLAGS=" + cflags + " -mios-simulator-version-min=6.1 -arch x86_64", + "CGO_ENABLED=1", + } + + return nil +} + +func envClang(sdkName string) (clang, cflags string, err error) { + cmd := exec.Command("xcrun", "--sdk", sdkName, "--find", "clang") + out, err := cmd.Output() + if err != nil { + return "", "", fmt.Errorf("xcrun --find: %v\n%s", err, out) + } + clang = strings.TrimSpace(string(out)) + + cmd = exec.Command("xcrun", "--sdk", sdkName, "--show-sdk-path") + out, err = cmd.Output() + if err != nil { + return "", "", fmt.Errorf("xcrun --show-sdk-path: %v\n%s", err, out) + } + sdk := strings.TrimSpace(string(out)) + + return clang, "-isysroot " + sdk, nil +} + +func archClang(goarch string) string { + switch goarch { + case "arm": + return "armv7" + case "arm64": + return "arm64" + case "386": + return "i386" + case "amd64": + return "x86_64" + default: + panic(fmt.Sprintf("unknown GOARCH: %q", goarch)) + } +} + // environ merges os.Environ and the given "key=value" pairs. // If a key is in both os.Environ and kv, kv takes precedence. func environ(kv []string) []string { @@ -140,3 +223,17 @@ func environ(kv []string) []string { } return new } + +func getenv(env []string, key string) string { + prefix := key + "=" + for _, kv := range env { + if strings.HasPrefix(kv, prefix) { + return kv[len(prefix):] + } + } + return "" +} + +func pkgdir(env []string) string { + return gomobilepath + "/pkg_" + getenv(env, "GOOS") + "_" + getenv(env, "GOARCH") +} diff --git a/cmd/gomobile/init.go b/cmd/gomobile/init.go index c18edbf..740c9fb 100644 --- a/cmd/gomobile/init.go +++ b/cmd/gomobile/init.go @@ -116,9 +116,13 @@ func runInit(cmd *command) error { return err } + if err := envInit(); err != nil { + return err + } + // Install standard libraries for cross compilers. start := time.Now() - if err := installAndroid(); err != nil { + if err := installStd(androidArmEnv); err != nil { return err } if err := installDarwin(); err != nil { @@ -140,56 +144,45 @@ func runInit(cmd *command) error { return nil } -func installAndroid() error { - exe := "" - if goos == "windows" { - exe = ".exe" - } - ndkccbin := filepath.Join(ndkccpath, "arm", "bin") - androidEnv := []string{ - `CC=` + filepath.Join(ndkccbin, "arm-linux-androideabi-gcc"+exe), - `CXX=` + filepath.Join(ndkccbin, "arm-linux-androideabi-g++"+exe), - } - return installStd("android", "arm", androidEnv) -} - func installDarwin() error { if goos != "darwin" { return nil // Only build iOS compilers on OS X. } - cc := filepath.Join(goEnv("GOROOT"), "misc/ios/clangwrap.sh") - darwinEnv := []string{`CC=` + cc, `CXX=` + cc} - if err := installStd("darwin", "arm", darwinEnv); err != nil { + if err := installStd(darwinArmEnv); err != nil { return err } - return installStd("darwin", "arm64", darwinEnv) + if err := installStd(darwinArm64Env); err != nil { + return err + } + // TODO(crawshaw): darwin/386 for the iOS simulator? + if err := installStd(darwinAmd64Env, "-tags=ios"); err != nil { + return err + } + return nil } -func installStd(tOS, tArch string, env []string) error { +func installStd(env []string, args ...string) error { + tOS := getenv(env, "GOOS") + tArch := getenv(env, "GOARCH") if buildV { fmt.Fprintf(os.Stderr, "\n# Building standard library for %s/%s.\n", tOS, tArch) } - removeAll(filepath.Join(goEnv("GOROOT"), "pkg", tOS+"_"+tArch)) envpath := os.Getenv("PATH") if buildN { envpath = "$PATH" } - cmd := exec.Command("go", "install") + cmd := exec.Command("go", "install", "-pkgdir="+pkgdir(env)) + cmd.Args = append(cmd.Args, args...) if buildV { cmd.Args = append(cmd.Args, "-v") } + if buildX { + cmd.Args = append(cmd.Args, "-x") + } cmd.Args = append(cmd.Args, "std") - cmd.Env = []string{ - `PATH=` + envpath, - `GOOS=` + tOS, - `GOARCH=` + tArch, - `CGO_ENABLED=1`, - } + cmd.Env = []string{"PATH=" + envpath} cmd.Env = append(cmd.Env, env...) - if tArch == "arm" { - cmd.Env = append(cmd.Env, "GOARM=7") - } if buildX { printcmd("%s", strings.Join(cmd.Env, " ")+" "+strings.Join(cmd.Args, " ")) } @@ -295,8 +288,8 @@ func goVersion() ([]byte, error) { } // TODO(crawshaw): this is a crude test for Go 1.5. After release, // remove this and check it is not an old release version. - if !bytes.Contains(buildHelp, []byte("-toolexec")) { - return nil, fmt.Errorf("installed Go tool does not support -toolexec") + if !bytes.Contains(buildHelp, []byte("-pkgdir")) { + return nil, fmt.Errorf("installed Go tool does not support -pkgdir") } return exec.Command(gobin, "version").CombinedOutput() }