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 <hyangah@gmail.com>
This commit is contained in:
David Crawshaw 2015-07-16 13:32:51 -04:00
parent d709f21793
commit 9300366e65
5 changed files with 180 additions and 116 deletions

View File

@ -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
}

View File

@ -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

View File

@ -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 {

View File

@ -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")
}

View File

@ -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()
}