gitlab.science.ru.nl/irma/gomobile.git@v0.0.0-20200320223732-da812b634d1f/cmd/gomobile/env.go (about)

     1  package main
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"io/ioutil"
     7  	"os"
     8  	"os/exec"
     9  	"path/filepath"
    10  	"runtime"
    11  	"strings"
    12  )
    13  
    14  // General mobile build environment. Initialized by envInit.
    15  var (
    16  	gomobilepath string // $GOPATH/pkg/gomobile
    17  
    18  	androidEnv map[string][]string // android arch -> []string
    19  
    20  	darwinEnv map[string][]string
    21  
    22  	androidArmNM string
    23  	darwinArmNM  string
    24  
    25  	allArchs = []string{"arm", "arm64", "386", "amd64"}
    26  
    27  	bitcodeEnabled bool
    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  	// Check the current Go version by go-list.
    79  	// An arbitrary standard package ('runtime' here) is given to go-list.
    80  	// This is because go-list tries to analyze the module at the current directory if no packages are given,
    81  	// and if the module doesn't have any Go file, go-list fails. See golang/go#36668.
    82  	cmd := exec.Command("go", "list", "-e", "-f", `{{range context.ReleaseTags}}{{if eq . "go1.14"}}{{.}}{{end}}{{end}}`, "runtime")
    83  	cmd.Stderr = os.Stderr
    84  	out, err := cmd.Output()
    85  	if err != nil {
    86  		return err
    87  	}
    88  	if len(strings.TrimSpace(string(out))) > 0 {
    89  		bitcodeEnabled = true
    90  	}
    91  
    92  	// Setup the cross-compiler environments.
    93  	if ndkRoot, err := ndkRoot(); err == nil {
    94  		androidEnv = make(map[string][]string)
    95  		if buildAndroidAPI < minAndroidAPI {
    96  			return fmt.Errorf("gomobile requires Android API level >= %d", minAndroidAPI)
    97  		}
    98  		for arch, toolchain := range ndk {
    99  			clang := toolchain.Path(ndkRoot, "clang")
   100  			clangpp := toolchain.Path(ndkRoot, "clang++")
   101  			if !buildN {
   102  				tools := []string{clang, clangpp}
   103  				if runtime.GOOS == "windows" {
   104  					// Because of https://github.com/android-ndk/ndk/issues/920,
   105  					// we require r19c, not just r19b. Fortunately, the clang++.cmd
   106  					// script only exists in r19c.
   107  					tools = append(tools, clangpp+".cmd")
   108  				}
   109  				for _, tool := range tools {
   110  					_, err = os.Stat(tool)
   111  					if err != nil {
   112  						return fmt.Errorf("No compiler for %s was found in the NDK (tried %s). Make sure your NDK version is >= r19c. Use `sdkmanager --update` to update it.", arch, tool)
   113  					}
   114  				}
   115  			}
   116  			androidEnv[arch] = []string{
   117  				"GOOS=android",
   118  				"GOARCH=" + arch,
   119  				"CC=" + clang,
   120  				"CXX=" + clangpp,
   121  				"CGO_ENABLED=1",
   122  			}
   123  			if arch == "arm" {
   124  				androidEnv[arch] = append(androidEnv[arch], "GOARM=7")
   125  			}
   126  		}
   127  	}
   128  
   129  	if !xcodeAvailable() {
   130  		return nil
   131  	}
   132  
   133  	darwinArmNM = "nm"
   134  	darwinEnv = make(map[string][]string)
   135  	for _, arch := range allArchs {
   136  		var env []string
   137  		var err error
   138  		var clang, cflags string
   139  		switch arch {
   140  		case "arm":
   141  			env = append(env, "GOARM=7")
   142  			fallthrough
   143  		case "arm64":
   144  			clang, cflags, err = envClang("iphoneos")
   145  			cflags += " -miphoneos-version-min=" + buildIOSVersion
   146  		case "386", "amd64":
   147  			clang, cflags, err = envClang("iphonesimulator")
   148  			cflags += " -mios-simulator-version-min=" + buildIOSVersion
   149  		default:
   150  			panic(fmt.Errorf("unknown GOARCH: %q", arch))
   151  		}
   152  		if err != nil {
   153  			return err
   154  		}
   155  
   156  		if bitcodeEnabled {
   157  			cflags += " -fembed-bitcode"
   158  		}
   159  		env = append(env,
   160  			"GOOS=darwin",
   161  			"GOARCH="+arch,
   162  			"CC="+clang,
   163  			"CXX="+clang+"++",
   164  			"CGO_CFLAGS="+cflags+" -arch "+archClang(arch),
   165  			"CGO_CXXFLAGS="+cflags+" -arch "+archClang(arch),
   166  			"CGO_LDFLAGS="+cflags+" -arch "+archClang(arch),
   167  			"CGO_ENABLED=1",
   168  		)
   169  		darwinEnv[arch] = env
   170  	}
   171  
   172  	return nil
   173  }
   174  
   175  func ndkRoot() (string, error) {
   176  	if buildN {
   177  		return "$NDK_PATH", nil
   178  	}
   179  
   180  	androidHome := os.Getenv("ANDROID_HOME")
   181  	if androidHome != "" {
   182  		ndkRoot := filepath.Join(androidHome, "ndk-bundle")
   183  		_, err := os.Stat(ndkRoot)
   184  		if err == nil {
   185  			return ndkRoot, nil
   186  		}
   187  	}
   188  
   189  	ndkRoot := os.Getenv("ANDROID_NDK_HOME")
   190  	if ndkRoot != "" {
   191  		_, err := os.Stat(ndkRoot)
   192  		if err == nil {
   193  			return ndkRoot, nil
   194  		}
   195  	}
   196  
   197  	return "", fmt.Errorf("no Android NDK found in $ANDROID_HOME/ndk-bundle nor in $ANDROID_NDK_HOME")
   198  }
   199  
   200  func envClang(sdkName string) (clang, cflags string, err error) {
   201  	if buildN {
   202  		return sdkName + "-clang", "-isysroot=" + sdkName, nil
   203  	}
   204  	cmd := exec.Command("xcrun", "--sdk", sdkName, "--find", "clang")
   205  	out, err := cmd.CombinedOutput()
   206  	if err != nil {
   207  		return "", "", fmt.Errorf("xcrun --find: %v\n%s", err, out)
   208  	}
   209  	clang = strings.TrimSpace(string(out))
   210  
   211  	cmd = exec.Command("xcrun", "--sdk", sdkName, "--show-sdk-path")
   212  	out, err = cmd.CombinedOutput()
   213  	if err != nil {
   214  		return "", "", fmt.Errorf("xcrun --show-sdk-path: %v\n%s", err, out)
   215  	}
   216  	sdk := strings.TrimSpace(string(out))
   217  	return clang, "-isysroot " + sdk, nil
   218  }
   219  
   220  func archClang(goarch string) string {
   221  	switch goarch {
   222  	case "arm":
   223  		return "armv7"
   224  	case "arm64":
   225  		return "arm64"
   226  	case "386":
   227  		return "i386"
   228  	case "amd64":
   229  		return "x86_64"
   230  	default:
   231  		panic(fmt.Sprintf("unknown GOARCH: %q", goarch))
   232  	}
   233  }
   234  
   235  // environ merges os.Environ and the given "key=value" pairs.
   236  // If a key is in both os.Environ and kv, kv takes precedence.
   237  func environ(kv []string) []string {
   238  	cur := os.Environ()
   239  	new := make([]string, 0, len(cur)+len(kv))
   240  
   241  	envs := make(map[string]string, len(cur))
   242  	for _, ev := range cur {
   243  		elem := strings.SplitN(ev, "=", 2)
   244  		if len(elem) != 2 || elem[0] == "" {
   245  			// pass the env var of unusual form untouched.
   246  			// e.g. Windows may have env var names starting with "=".
   247  			new = append(new, ev)
   248  			continue
   249  		}
   250  		if goos == "windows" {
   251  			elem[0] = strings.ToUpper(elem[0])
   252  		}
   253  		envs[elem[0]] = elem[1]
   254  	}
   255  	for _, ev := range kv {
   256  		elem := strings.SplitN(ev, "=", 2)
   257  		if len(elem) != 2 || elem[0] == "" {
   258  			panic(fmt.Sprintf("malformed env var %q from input", ev))
   259  		}
   260  		if goos == "windows" {
   261  			elem[0] = strings.ToUpper(elem[0])
   262  		}
   263  		envs[elem[0]] = elem[1]
   264  	}
   265  	for k, v := range envs {
   266  		new = append(new, k+"="+v)
   267  	}
   268  	return new
   269  }
   270  
   271  func getenv(env []string, key string) string {
   272  	prefix := key + "="
   273  	for _, kv := range env {
   274  		if strings.HasPrefix(kv, prefix) {
   275  			return kv[len(prefix):]
   276  		}
   277  	}
   278  	return ""
   279  }
   280  
   281  func archNDK() string {
   282  	if runtime.GOOS == "windows" && runtime.GOARCH == "386" {
   283  		return "windows"
   284  	} else {
   285  		var arch string
   286  		switch runtime.GOARCH {
   287  		case "386":
   288  			arch = "x86"
   289  		case "amd64":
   290  			arch = "x86_64"
   291  		default:
   292  			panic("unsupported GOARCH: " + runtime.GOARCH)
   293  		}
   294  		return runtime.GOOS + "-" + arch
   295  	}
   296  }
   297  
   298  type ndkToolchain struct {
   299  	arch        string
   300  	abi         string
   301  	minAPI      int
   302  	toolPrefix  string
   303  	clangPrefix string
   304  }
   305  
   306  func (tc *ndkToolchain) ClangPrefix() string {
   307  	if buildAndroidAPI < tc.minAPI {
   308  		return fmt.Sprintf("%s%d", tc.clangPrefix, tc.minAPI)
   309  	}
   310  	return fmt.Sprintf("%s%d", tc.clangPrefix, buildAndroidAPI)
   311  }
   312  
   313  func (tc *ndkToolchain) Path(ndkRoot, toolName string) string {
   314  	var pref string
   315  	switch toolName {
   316  	case "clang", "clang++":
   317  		pref = tc.ClangPrefix()
   318  	default:
   319  		pref = tc.toolPrefix
   320  	}
   321  	return filepath.Join(ndkRoot, "toolchains", "llvm", "prebuilt", archNDK(), "bin", pref+"-"+toolName)
   322  }
   323  
   324  type ndkConfig map[string]ndkToolchain // map: GOOS->androidConfig.
   325  
   326  func (nc ndkConfig) Toolchain(arch string) ndkToolchain {
   327  	tc, ok := nc[arch]
   328  	if !ok {
   329  		panic(`unsupported architecture: ` + arch)
   330  	}
   331  	return tc
   332  }
   333  
   334  var ndk = ndkConfig{
   335  	"arm": {
   336  		arch:        "arm",
   337  		abi:         "armeabi-v7a",
   338  		minAPI:      16,
   339  		toolPrefix:  "arm-linux-androideabi",
   340  		clangPrefix: "armv7a-linux-androideabi",
   341  	},
   342  	"arm64": {
   343  		arch:        "arm64",
   344  		abi:         "arm64-v8a",
   345  		minAPI:      21,
   346  		toolPrefix:  "aarch64-linux-android",
   347  		clangPrefix: "aarch64-linux-android",
   348  	},
   349  
   350  	"386": {
   351  		arch:        "x86",
   352  		abi:         "x86",
   353  		minAPI:      16,
   354  		toolPrefix:  "i686-linux-android",
   355  		clangPrefix: "i686-linux-android",
   356  	},
   357  	"amd64": {
   358  		arch:        "x86_64",
   359  		abi:         "x86_64",
   360  		minAPI:      21,
   361  		toolPrefix:  "x86_64-linux-android",
   362  		clangPrefix: "x86_64-linux-android",
   363  	},
   364  }
   365  
   366  func xcodeAvailable() bool {
   367  	err := exec.Command("xcrun", "xcodebuild", "-version").Run()
   368  	return err == nil
   369  }