github.com/SkycoinProject/gomobile@v0.0.0-20190312151609-d3739f865fa6/cmd/gomobile/env.go (about)

     1  package main
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"io/ioutil"
     7  	"log"
     8  	"os"
     9  	"os/exec"
    10  	"path/filepath"
    11  	"runtime"
    12  	"strings"
    13  )
    14  
    15  // General mobile build environment. Initialized by envInit.
    16  var (
    17  	cwd          string
    18  	gomobilepath string // $GOPATH/pkg/gomobile
    19  
    20  	androidEnv map[string][]string // android arch -> []string
    21  
    22  	darwinEnv map[string][]string
    23  
    24  	androidArmNM string
    25  	darwinArmNM  string
    26  
    27  	allArchs = []string{"arm", "arm64", "386", "amd64"}
    28  )
    29  
    30  func buildEnvInit() (cleanup func(), err error) {
    31  	// Find gomobilepath.
    32  	gopath := goEnv("GOPATH")
    33  	for _, p := range filepath.SplitList(gopath) {
    34  		gomobilepath = filepath.Join(p, "pkg", "gomobile")
    35  		if _, err := os.Stat(gomobilepath); buildN || err == nil {
    36  			break
    37  		}
    38  	}
    39  
    40  	if buildX {
    41  		fmt.Fprintln(xout, "GOMOBILE="+gomobilepath)
    42  	}
    43  
    44  	// Check the toolchain is in a good state.
    45  	// Pick a temporary directory for assembling an apk/app.
    46  	if gomobilepath == "" {
    47  		return nil, errors.New("toolchain not installed, run `gomobile init`")
    48  	}
    49  
    50  	cleanupFn := func() {
    51  		if buildWork {
    52  			fmt.Printf("WORK=%s\n", tmpdir)
    53  			return
    54  		}
    55  		removeAll(tmpdir)
    56  	}
    57  	if buildN {
    58  		tmpdir = "$WORK"
    59  		cleanupFn = func() {}
    60  	} else {
    61  		tmpdir, err = ioutil.TempDir("", "gomobile-work-")
    62  		if err != nil {
    63  			return nil, err
    64  		}
    65  	}
    66  	if buildX {
    67  		fmt.Fprintln(xout, "WORK="+tmpdir)
    68  	}
    69  
    70  	if err := envInit(); err != nil {
    71  		return nil, err
    72  	}
    73  
    74  	return cleanupFn, nil
    75  }
    76  
    77  func envInit() (err error) {
    78  	// TODO(crawshaw): cwd only used by ctx.Import, which can take "."
    79  	cwd, err = os.Getwd()
    80  	if err != nil {
    81  		return err
    82  	}
    83  
    84  	// Setup the cross-compiler environments.
    85  	if ndkRoot, err := ndkRoot(); err == nil {
    86  		androidEnv = make(map[string][]string)
    87  		for arch, toolchain := range ndk {
    88  			clang := toolchain.Path(ndkRoot, "clang")
    89  			if !buildN {
    90  				_, err = os.Stat(clang)
    91  				if err != nil {
    92  					return fmt.Errorf("No compiler for %s was found in the NDK (tried %q). Make sure your NDK version is >= r19b. Use `sdkmanager --update` to update it.", arch, clang)
    93  				}
    94  			}
    95  			androidEnv[arch] = []string{
    96  				"GOOS=android",
    97  				"GOARCH=" + arch,
    98  				"CC=" + clang,
    99  				"CXX=" + toolchain.Path(ndkRoot, "clang++"),
   100  				"CGO_ENABLED=1",
   101  			}
   102  			if arch == "arm" {
   103  				androidEnv[arch] = append(androidEnv[arch], "GOARM=7")
   104  			}
   105  		}
   106  	}
   107  
   108  	if !xcodeAvailable() {
   109  		return nil
   110  	}
   111  
   112  	darwinArmNM = "nm"
   113  	darwinEnv = make(map[string][]string)
   114  	for _, arch := range allArchs {
   115  		var env []string
   116  		var err error
   117  		var clang, cflags string
   118  		switch arch {
   119  		case "arm":
   120  			env = append(env, "GOARM=7")
   121  			fallthrough
   122  		case "arm64":
   123  			clang, cflags, err = envClang("iphoneos")
   124  			cflags += " -miphoneos-version-min=" + buildIOSVersion
   125  		case "386", "amd64":
   126  			clang, cflags, err = envClang("iphonesimulator")
   127  			cflags += " -mios-simulator-version-min=" + buildIOSVersion
   128  		default:
   129  			panic(fmt.Errorf("unknown GOARCH: %q", arch))
   130  		}
   131  		if err != nil {
   132  			return err
   133  		}
   134  		env = append(env,
   135  			"GOOS=darwin",
   136  			"GOARCH="+arch,
   137  			"CC="+clang,
   138  			"CXX="+clang+"++",
   139  			"CGO_CFLAGS="+cflags+" -arch "+archClang(arch),
   140  			"CGO_CXXFLAGS="+cflags+" -arch "+archClang(arch),
   141  			"CGO_LDFLAGS="+cflags+" -arch "+archClang(arch),
   142  			"CGO_ENABLED=1",
   143  		)
   144  		darwinEnv[arch] = env
   145  	}
   146  
   147  	return nil
   148  }
   149  
   150  func ndkRoot() (string, error) {
   151  	if buildN {
   152  		return "$NDK_PATH", nil
   153  	}
   154  	androidHome := os.Getenv("ANDROID_HOME")
   155  	if androidHome == "" {
   156  		return "", errors.New("The Android SDK was not found. Please set ANDROID_HOME to the root of the Android SDK.")
   157  	}
   158  	ndkRoot := filepath.Join(androidHome, "ndk-bundle")
   159  	_, err := os.Stat(ndkRoot)
   160  	if err != nil {
   161  		return "", fmt.Errorf("The NDK was not found in $ANDROID_HOME/ndk-bundle (%q). Install the NDK with `sdkmanager 'ndk-bundle'`", ndkRoot)
   162  	}
   163  	return ndkRoot, nil
   164  }
   165  
   166  func envClang(sdkName string) (clang, cflags string, err error) {
   167  	if buildN {
   168  		return sdkName + "-clang", "-isysroot=" + sdkName, nil
   169  	}
   170  	cmd := exec.Command("xcrun", "--sdk", sdkName, "--find", "clang")
   171  	out, err := cmd.CombinedOutput()
   172  	if err != nil {
   173  		return "", "", fmt.Errorf("xcrun --find: %v\n%s", err, out)
   174  	}
   175  	clang = strings.TrimSpace(string(out))
   176  
   177  	cmd = exec.Command("xcrun", "--sdk", sdkName, "--show-sdk-path")
   178  	out, err = cmd.CombinedOutput()
   179  	if err != nil {
   180  		return "", "", fmt.Errorf("xcrun --show-sdk-path: %v\n%s", err, out)
   181  	}
   182  	sdk := strings.TrimSpace(string(out))
   183  	return clang, "-isysroot " + sdk, nil
   184  }
   185  
   186  func archClang(goarch string) string {
   187  	switch goarch {
   188  	case "arm":
   189  		return "armv7"
   190  	case "arm64":
   191  		return "arm64"
   192  	case "386":
   193  		return "i386"
   194  	case "amd64":
   195  		return "x86_64"
   196  	default:
   197  		panic(fmt.Sprintf("unknown GOARCH: %q", goarch))
   198  	}
   199  }
   200  
   201  // environ merges os.Environ and the given "key=value" pairs.
   202  // If a key is in both os.Environ and kv, kv takes precedence.
   203  func environ(kv []string) []string {
   204  	cur := os.Environ()
   205  	new := make([]string, 0, len(cur)+len(kv))
   206  
   207  	envs := make(map[string]string, len(cur))
   208  	for _, ev := range cur {
   209  		elem := strings.SplitN(ev, "=", 2)
   210  		if len(elem) != 2 || elem[0] == "" {
   211  			// pass the env var of unusual form untouched.
   212  			// e.g. Windows may have env var names starting with "=".
   213  			new = append(new, ev)
   214  			continue
   215  		}
   216  		if goos == "windows" {
   217  			elem[0] = strings.ToUpper(elem[0])
   218  		}
   219  		envs[elem[0]] = elem[1]
   220  	}
   221  	for _, ev := range kv {
   222  		elem := strings.SplitN(ev, "=", 2)
   223  		if len(elem) != 2 || elem[0] == "" {
   224  			panic(fmt.Sprintf("malformed env var %q from input", ev))
   225  		}
   226  		if goos == "windows" {
   227  			elem[0] = strings.ToUpper(elem[0])
   228  		}
   229  		envs[elem[0]] = elem[1]
   230  	}
   231  	for k, v := range envs {
   232  		new = append(new, k+"="+v)
   233  	}
   234  	return new
   235  }
   236  
   237  func getenv(env []string, key string) string {
   238  	prefix := key + "="
   239  	for _, kv := range env {
   240  		if strings.HasPrefix(kv, prefix) {
   241  			return kv[len(prefix):]
   242  		}
   243  	}
   244  	return ""
   245  }
   246  
   247  func archNDK() string {
   248  	if runtime.GOOS == "windows" && runtime.GOARCH == "386" {
   249  		return "windows"
   250  	} else {
   251  		var arch string
   252  		switch runtime.GOARCH {
   253  		case "386":
   254  			arch = "x86"
   255  		case "amd64":
   256  			arch = "x86_64"
   257  		default:
   258  			panic("unsupported GOARCH: " + runtime.GOARCH)
   259  		}
   260  		return runtime.GOOS + "-" + arch
   261  	}
   262  }
   263  
   264  type ndkToolchain struct {
   265  	arch        string
   266  	abi         string
   267  	toolPrefix  string
   268  	clangPrefix string
   269  }
   270  
   271  func (tc *ndkToolchain) Path(ndkRoot, toolName string) string {
   272  	var pref string
   273  	switch toolName {
   274  	case "clang", "clang++":
   275  		if runtime.GOOS == "windows" {
   276  			return tc.createNDKr19bWorkaroundTool(ndkRoot, toolName)
   277  		}
   278  		pref = tc.clangPrefix
   279  	default:
   280  		pref = tc.toolPrefix
   281  	}
   282  	return filepath.Join(ndkRoot, "toolchains", "llvm", "prebuilt", archNDK(), "bin", pref+"-"+toolName)
   283  }
   284  
   285  // createNDKr19bWorkaroundTool creates a Windows wrapper script for clang or clang++.
   286  // The scripts included in r19b are broken on Windows: https://github.com/android-ndk/ndk/issues/920.
   287  // TODO: Remove this when r19c is out; the code inside is hacky and panicky.
   288  func (tc *ndkToolchain) createNDKr19bWorkaroundTool(ndkRoot, toolName string) string {
   289  	toolCmd := filepath.Join(tmpdir, fmt.Sprintf("%s-%s.cmd", tc.arch, toolName))
   290  	tool, err := os.Create(toolCmd)
   291  	if err != nil {
   292  		log.Fatal(err)
   293  	}
   294  	defer func() {
   295  		if err := tool.Close(); err != nil {
   296  			log.Fatal(err)
   297  		}
   298  	}()
   299  	tcBin := filepath.Join(ndkRoot, "toolchains", "llvm", "prebuilt", archNDK(), "bin")
   300  	// Adapted from the NDK cmd wrappers.
   301  	toolCmdContent := fmt.Sprintf(`@echo off
   302  set _BIN_DIR=%s\
   303  %%_BIN_DIR%%%s.exe --target=%s -fno-addrsig %%*"`, tcBin, toolName, tc.clangPrefix)
   304  	if _, err = tool.Write([]byte(toolCmdContent)); err != nil {
   305  		log.Fatal(err)
   306  	}
   307  	return toolCmd
   308  }
   309  
   310  type ndkConfig map[string]ndkToolchain // map: GOOS->androidConfig.
   311  
   312  func (nc ndkConfig) Toolchain(arch string) ndkToolchain {
   313  	tc, ok := nc[arch]
   314  	if !ok {
   315  		panic(`unsupported architecture: ` + arch)
   316  	}
   317  	return tc
   318  }
   319  
   320  var ndk = ndkConfig{
   321  	"arm": {
   322  		arch:        "arm",
   323  		abi:         "armeabi-v7a",
   324  		toolPrefix:  "arm-linux-androideabi",
   325  		clangPrefix: "armv7a-linux-androideabi16",
   326  	},
   327  	"arm64": {
   328  		arch:        "arm64",
   329  		abi:         "arm64-v8a",
   330  		toolPrefix:  "aarch64-linux-android",
   331  		clangPrefix: "aarch64-linux-android21",
   332  	},
   333  
   334  	"386": {
   335  		arch:        "x86",
   336  		abi:         "x86",
   337  		toolPrefix:  "i686-linux-android",
   338  		clangPrefix: "i686-linux-android16",
   339  	},
   340  	"amd64": {
   341  		arch:        "x86_64",
   342  		abi:         "x86_64",
   343  		toolPrefix:  "x86_64-linux-android",
   344  		clangPrefix: "x86_64-linux-android21",
   345  	},
   346  }
   347  
   348  func xcodeAvailable() bool {
   349  	err := exec.Command("xcrun", "xcodebuild", "-version").Run()
   350  	return err == nil
   351  }