github.com/SpiderOak/mobile@v0.0.0-20221129182558-6f541b59af45/cmd/gomobile/env.go (about)

     1  package main
     2  
     3  import (
     4  	"bufio"
     5  	"encoding/json"
     6  	"errors"
     7  	"fmt"
     8  	"io/fs"
     9  	"io/ioutil"
    10  	"os"
    11  	"os/exec"
    12  	"path/filepath"
    13  	"runtime"
    14  	"strings"
    15  
    16  	"github.com/SpiderOak/mobile/internal/sdkpath"
    17  )
    18  
    19  // General mobile build environment. Initialized by envInit.
    20  var (
    21  	gomobilepath string              // $GOPATH/pkg/gomobile
    22  	androidEnv   map[string][]string // android arch -> []string
    23  	appleEnv     map[string][]string
    24  	appleNM      string
    25  )
    26  
    27  func isAndroidPlatform(platform string) bool {
    28  	return platform == "android"
    29  }
    30  
    31  func isApplePlatform(platform string) bool {
    32  	return contains(applePlatforms, platform)
    33  }
    34  
    35  var applePlatforms = []string{"ios", "iossimulator", "macos", "maccatalyst"}
    36  
    37  func platformArchs(platform string) []string {
    38  	switch platform {
    39  	case "ios":
    40  		return []string{"arm64"}
    41  	case "iossimulator":
    42  		return []string{"arm64", "amd64"}
    43  	case "macos", "maccatalyst":
    44  		return []string{"arm64", "amd64"}
    45  	case "android":
    46  		return []string{"arm", "arm64", "386", "amd64"}
    47  	default:
    48  		panic(fmt.Sprintf("unexpected platform: %s", platform))
    49  	}
    50  }
    51  
    52  func isSupportedArch(platform, arch string) bool {
    53  	return contains(platformArchs(platform), arch)
    54  }
    55  
    56  // platformOS returns the correct GOOS value for platform.
    57  func platformOS(platform string) string {
    58  	switch platform {
    59  	case "android":
    60  		return "android"
    61  	case "ios", "iossimulator":
    62  		return "ios"
    63  	case "macos", "maccatalyst":
    64  		// For "maccatalyst", Go packages should be built with GOOS=darwin,
    65  		// not GOOS=ios, since the underlying OS (and kernel, runtime) is macOS.
    66  		// We also apply a "macos" or "maccatalyst" build tag, respectively.
    67  		// See below for additional context.
    68  		return "darwin"
    69  	default:
    70  		panic(fmt.Sprintf("unexpected platform: %s", platform))
    71  	}
    72  }
    73  
    74  func platformTags(platform string) []string {
    75  	switch platform {
    76  	case "android":
    77  		return []string{"android"}
    78  	case "ios", "iossimulator":
    79  		return []string{"ios"}
    80  	case "macos":
    81  		return []string{"macos"}
    82  	case "maccatalyst":
    83  		// Mac Catalyst is a subset of iOS APIs made available on macOS
    84  		// designed to ease porting apps developed for iPad to macOS.
    85  		// See https://developer.apple.com/mac-catalyst/.
    86  		// Because of this, when building a Go package targeting maccatalyst,
    87  		// GOOS=darwin (not ios). To bridge the gap and enable maccatalyst
    88  		// packages to be compiled, we also specify the "ios" build tag.
    89  		// To help discriminate between darwin, ios, macos, and maccatalyst
    90  		// targets, there is also a "maccatalyst" tag.
    91  		// Some additional context on this can be found here:
    92  		// https://stackoverflow.com/questions/12132933/preprocessor-macro-for-os-x-targets/49560690#49560690
    93  		// TODO(ydnar): remove tag "ios" when cgo supports Catalyst
    94  		// See golang.org/issues/47228
    95  		return []string{"ios", "macos", "maccatalyst"}
    96  	default:
    97  		panic(fmt.Sprintf("unexpected platform: %s", platform))
    98  	}
    99  }
   100  
   101  func contains(haystack []string, needle string) bool {
   102  	for _, v := range haystack {
   103  		if v == needle {
   104  			return true
   105  		}
   106  	}
   107  	return false
   108  }
   109  
   110  func buildEnvInit() (cleanup func(), err error) {
   111  	// Find gomobilepath.
   112  	gopath := goEnv("GOPATH")
   113  	for _, p := range filepath.SplitList(gopath) {
   114  		gomobilepath = filepath.Join(p, "pkg", "gomobile")
   115  		if _, err := os.Stat(gomobilepath); buildN || err == nil {
   116  			break
   117  		}
   118  	}
   119  
   120  	if buildX {
   121  		fmt.Fprintln(xout, "GOMOBILE="+gomobilepath)
   122  	}
   123  
   124  	// Check the toolchain is in a good state.
   125  	// Pick a temporary directory for assembling an apk/app.
   126  	if gomobilepath == "" {
   127  		return nil, errors.New("toolchain not installed, run `gomobile init`")
   128  	}
   129  
   130  	cleanupFn := func() {
   131  		if buildWork {
   132  			fmt.Printf("WORK=%s\n", tmpdir)
   133  			return
   134  		}
   135  		removeAll(tmpdir)
   136  	}
   137  	if buildN {
   138  		tmpdir = "$WORK"
   139  		cleanupFn = func() {}
   140  	} else {
   141  		tmpdir, err = ioutil.TempDir("", "gomobile-work-")
   142  		if err != nil {
   143  			return nil, err
   144  		}
   145  	}
   146  	if buildX {
   147  		fmt.Fprintln(xout, "WORK="+tmpdir)
   148  	}
   149  
   150  	if err := envInit(); err != nil {
   151  		return nil, err
   152  	}
   153  
   154  	return cleanupFn, nil
   155  }
   156  
   157  func envInit() (err error) {
   158  	// Setup the cross-compiler environments.
   159  	if ndkRoot, err := ndkRoot(); err == nil {
   160  		androidEnv = make(map[string][]string)
   161  		if buildAndroidAPI < minAndroidAPI {
   162  			return fmt.Errorf("gomobile requires Android API level >= %d", minAndroidAPI)
   163  		}
   164  		for arch, toolchain := range ndk {
   165  			clang := toolchain.Path(ndkRoot, "clang")
   166  			clangpp := toolchain.Path(ndkRoot, "clang++")
   167  			if !buildN {
   168  				tools := []string{clang, clangpp}
   169  				if runtime.GOOS == "windows" {
   170  					// Because of https://github.com/android-ndk/ndk/issues/920,
   171  					// we require r19c, not just r19b. Fortunately, the clang++.cmd
   172  					// script only exists in r19c.
   173  					tools = append(tools, clangpp+".cmd")
   174  				}
   175  				for _, tool := range tools {
   176  					_, err = os.Stat(tool)
   177  					if err != nil {
   178  						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)
   179  					}
   180  				}
   181  			}
   182  			androidEnv[arch] = []string{
   183  				"GOOS=android",
   184  				"GOARCH=" + arch,
   185  				"CC=" + clang,
   186  				"CXX=" + clangpp,
   187  				"CGO_ENABLED=1",
   188  			}
   189  			if arch == "arm" {
   190  				androidEnv[arch] = append(androidEnv[arch], "GOARM=7")
   191  			}
   192  			for _, name := range []string{"CFLAGS", "LDFLAGS"} {
   193  				env, ok := os.LookupEnv("CGO_" + name + "_FOR_" + arch)
   194  				if !ok {
   195  					continue
   196  				}
   197  				exist := os.Getenv("CGO_CFLAGS")
   198  				if exist != "" {
   199  					env = exist + " " + env
   200  				}
   201  				androidEnv[arch] = append(androidEnv[arch],
   202  					"CGO_"+name+"="+env)
   203  				appleEnv[arch] = append(appleEnv[arch],
   204  					"CGO_"+name+"="+env)
   205  			}
   206  		}
   207  	}
   208  
   209  	if !xcodeAvailable() {
   210  		return nil
   211  	}
   212  
   213  	appleNM = "nm"
   214  	appleEnv = make(map[string][]string)
   215  	for _, platform := range applePlatforms {
   216  		for _, arch := range platformArchs(platform) {
   217  			var env []string
   218  			var goos, sdk, clang, cflags string
   219  			var err error
   220  			switch platform {
   221  			case "ios":
   222  				goos = "ios"
   223  				sdk = "iphoneos"
   224  				clang, cflags, err = envClang(sdk)
   225  				cflags += " -miphoneos-version-min=" + buildIOSVersion
   226  				cflags += " -fembed-bitcode"
   227  			case "iossimulator":
   228  				goos = "ios"
   229  				sdk = "iphonesimulator"
   230  				clang, cflags, err = envClang(sdk)
   231  				cflags += " -mios-simulator-version-min=" + buildIOSVersion
   232  				cflags += " -fembed-bitcode"
   233  			case "maccatalyst":
   234  				// Mac Catalyst is a subset of iOS APIs made available on macOS
   235  				// designed to ease porting apps developed for iPad to macOS.
   236  				// See https://developer.apple.com/mac-catalyst/.
   237  				// Because of this, when building a Go package targeting maccatalyst,
   238  				// GOOS=darwin (not ios). To bridge the gap and enable maccatalyst
   239  				// packages to be compiled, we also specify the "ios" build tag.
   240  				// To help discriminate between darwin, ios, macos, and maccatalyst
   241  				// targets, there is also a "maccatalyst" tag.
   242  				// Some additional context on this can be found here:
   243  				// https://stackoverflow.com/questions/12132933/preprocessor-macro-for-os-x-targets/49560690#49560690
   244  				goos = "darwin"
   245  				sdk = "macosx"
   246  				clang, cflags, err = envClang(sdk)
   247  				// TODO(ydnar): the following 3 lines MAY be needed to compile
   248  				// packages or apps for maccatalyst. Commenting them out now in case
   249  				// it turns out they are necessary. Currently none of the example
   250  				// apps will build for macos or maccatalyst because they have a
   251  				// GLKit dependency, which is deprecated on all Apple platforms, and
   252  				// broken on maccatalyst (GLKView isn’t available).
   253  				// sysroot := strings.SplitN(cflags, " ", 2)[1]
   254  				// cflags += " -isystem " + sysroot + "/System/iOSSupport/usr/include"
   255  				// cflags += " -iframework " + sysroot + "/System/iOSSupport/System/Library/Frameworks"
   256  				switch arch {
   257  				case "amd64":
   258  					cflags += " -target x86_64-apple-ios" + buildIOSVersion + "-macabi"
   259  				case "arm64":
   260  					cflags += " -target arm64-apple-ios" + buildIOSVersion + "-macabi"
   261  					cflags += " -fembed-bitcode"
   262  				}
   263  			case "macos":
   264  				goos = "darwin"
   265  				sdk = "macosx" // Note: the SDK is called "macosx", not "macos"
   266  				clang, cflags, err = envClang(sdk)
   267  				if arch == "arm64" {
   268  					cflags += " -fembed-bitcode"
   269  				}
   270  			default:
   271  				panic(fmt.Errorf("unknown Apple target: %s/%s", platform, arch))
   272  			}
   273  
   274  			if err != nil {
   275  				return err
   276  			}
   277  
   278  			env = append(env,
   279  				"GOOS="+goos,
   280  				"GOARCH="+arch,
   281  				"GOFLAGS="+"-tags="+strings.Join(platformTags(platform), ","),
   282  				"CC="+clang,
   283  				"CXX="+clang+"++",
   284  				"CGO_CFLAGS="+cflags+" -arch "+archClang(arch),
   285  				"CGO_CXXFLAGS="+cflags+" -arch "+archClang(arch),
   286  				"CGO_LDFLAGS="+cflags+" -arch "+archClang(arch),
   287  				"CGO_ENABLED=1",
   288  				"DARWIN_SDK="+sdk,
   289  			)
   290  			appleEnv[platform+"/"+arch] = env
   291  		}
   292  	}
   293  
   294  	return nil
   295  }
   296  
   297  // abi maps GOARCH values to Android ABI strings.
   298  // See https://developer.android.com/ndk/guides/abis
   299  func abi(goarch string) string {
   300  	switch goarch {
   301  	case "arm":
   302  		return "armeabi-v7a"
   303  	case "arm64":
   304  		return "arm64-v8a"
   305  	case "386":
   306  		return "x86"
   307  	case "amd64":
   308  		return "x86_64"
   309  	default:
   310  		return ""
   311  	}
   312  }
   313  
   314  // checkNDKRoot returns nil if the NDK in `ndkRoot` supports the current configured
   315  // API version and all the specified Android targets.
   316  func checkNDKRoot(ndkRoot string, targets []targetInfo) error {
   317  	platformsJson, err := os.Open(filepath.Join(ndkRoot, "meta", "platforms.json"))
   318  	if err != nil {
   319  		return err
   320  	}
   321  	defer platformsJson.Close()
   322  	decoder := json.NewDecoder(platformsJson)
   323  	supportedVersions := struct {
   324  		Min int
   325  		Max int
   326  	}{}
   327  	if err := decoder.Decode(&supportedVersions); err != nil {
   328  		return err
   329  	}
   330  	if supportedVersions.Min > buildAndroidAPI ||
   331  		supportedVersions.Max < buildAndroidAPI {
   332  		return fmt.Errorf("unsupported API version %d (not in %d..%d)", buildAndroidAPI, supportedVersions.Min, supportedVersions.Max)
   333  	}
   334  	abisJson, err := os.Open(filepath.Join(ndkRoot, "meta", "abis.json"))
   335  	if err != nil {
   336  		return err
   337  	}
   338  	defer abisJson.Close()
   339  	decoder = json.NewDecoder(abisJson)
   340  	abis := make(map[string]struct{})
   341  	if err := decoder.Decode(&abis); err != nil {
   342  		return err
   343  	}
   344  	for _, target := range targets {
   345  		if !isAndroidPlatform(target.platform) {
   346  			continue
   347  		}
   348  		if _, found := abis[abi(target.arch)]; !found {
   349  			return fmt.Errorf("ndk does not support %s", target.platform)
   350  		}
   351  	}
   352  	return nil
   353  }
   354  
   355  // compatibleNDKRoots searches the side-by-side NDK dirs for compatible SDKs.
   356  func compatibleNDKRoots(ndkForest string, targets []targetInfo) ([]string, error) {
   357  	ndkDirs, err := ioutil.ReadDir(ndkForest)
   358  	if err != nil {
   359  		return nil, err
   360  	}
   361  	compatibleNDKRoots := []string{}
   362  	var lastErr error
   363  	for _, dirent := range ndkDirs {
   364  		ndkRoot := filepath.Join(ndkForest, dirent.Name())
   365  		lastErr = checkNDKRoot(ndkRoot, targets)
   366  		if lastErr == nil {
   367  			compatibleNDKRoots = append(compatibleNDKRoots, ndkRoot)
   368  		}
   369  	}
   370  	if len(compatibleNDKRoots) > 0 {
   371  		return compatibleNDKRoots, nil
   372  	}
   373  	return nil, lastErr
   374  }
   375  
   376  // ndkVersion returns the full version number of an installed copy of the NDK,
   377  // or "" if it cannot be determined.
   378  func ndkVersion(ndkRoot string) string {
   379  	properties, err := os.Open(filepath.Join(ndkRoot, "source.properties"))
   380  	if err != nil {
   381  		return ""
   382  	}
   383  	defer properties.Close()
   384  	// Parse the version number out of the .properties file.
   385  	// See https://en.wikipedia.org/wiki/.properties
   386  	scanner := bufio.NewScanner(properties)
   387  	for scanner.Scan() {
   388  		line := scanner.Text()
   389  		tokens := strings.SplitN(line, "=", 2)
   390  		if len(tokens) != 2 {
   391  			continue
   392  		}
   393  		if strings.TrimSpace(tokens[0]) == "Pkg.Revision" {
   394  			return strings.TrimSpace(tokens[1])
   395  		}
   396  	}
   397  	return ""
   398  }
   399  
   400  // ndkRoot returns the root path of an installed NDK that supports all the
   401  // specified Android targets. For details of NDK locations, see
   402  // https://github.com/android/ndk-samples/wiki/Configure-NDK-Path
   403  func ndkRoot(targets ...targetInfo) (string, error) {
   404  	if buildN {
   405  		return "$NDK_PATH", nil
   406  	}
   407  
   408  	// Try the ANDROID_NDK_HOME variable.  This approach is deprecated, but it
   409  	// has the highest priority because it represents an explicit user choice.
   410  	if ndkRoot := os.Getenv("ANDROID_NDK_HOME"); ndkRoot != "" {
   411  		if err := checkNDKRoot(ndkRoot, targets); err != nil {
   412  			return "", fmt.Errorf("ANDROID_NDK_HOME specifies %s, which is unusable: %w", ndkRoot, err)
   413  		}
   414  		return ndkRoot, nil
   415  	}
   416  
   417  	androidHome, err := sdkpath.AndroidHome()
   418  	if err != nil {
   419  		return "", fmt.Errorf("could not locate Android SDK: %w", err)
   420  	}
   421  
   422  	// Use the newest compatible NDK under the side-by-side path arrangement.
   423  	ndkForest := filepath.Join(androidHome, "ndk")
   424  	ndkRoots, sideBySideErr := compatibleNDKRoots(ndkForest, targets)
   425  	if len(ndkRoots) != 0 {
   426  		// Choose the latest version that supports the build configuration.
   427  		// NDKs whose version cannot be determined will be least preferred.
   428  		// In the event of a tie, the later ndkRoot will win.
   429  		maxVersion := ""
   430  		var selected string
   431  		for _, ndkRoot := range ndkRoots {
   432  			version := ndkVersion(ndkRoot)
   433  			if version >= maxVersion {
   434  				maxVersion = version
   435  				selected = ndkRoot
   436  			}
   437  		}
   438  		return selected, nil
   439  	}
   440  	// Try the deprecated NDK location.
   441  	ndkRoot := filepath.Join(androidHome, "ndk-bundle")
   442  	if legacyErr := checkNDKRoot(ndkRoot, targets); legacyErr != nil {
   443  		return "", fmt.Errorf("no usable NDK in %s: %w, %v", androidHome, sideBySideErr, legacyErr)
   444  	}
   445  	return ndkRoot, nil
   446  }
   447  
   448  func envClang(sdkName string) (clang, cflags string, err error) {
   449  	if buildN {
   450  		return sdkName + "-clang", "-isysroot " + sdkName, nil
   451  	}
   452  	cmd := exec.Command("xcrun", "--sdk", sdkName, "--find", "clang")
   453  	out, err := cmd.CombinedOutput()
   454  	if err != nil {
   455  		return "", "", fmt.Errorf("xcrun --find: %v\n%s", err, out)
   456  	}
   457  	clang = strings.TrimSpace(string(out))
   458  
   459  	cmd = exec.Command("xcrun", "--sdk", sdkName, "--show-sdk-path")
   460  	out, err = cmd.CombinedOutput()
   461  	if err != nil {
   462  		return "", "", fmt.Errorf("xcrun --show-sdk-path: %v\n%s", err, out)
   463  	}
   464  	sdk := strings.TrimSpace(string(out))
   465  	return clang, "-isysroot " + sdk, nil
   466  }
   467  
   468  func archClang(goarch string) string {
   469  	switch goarch {
   470  	case "arm":
   471  		return "armv7"
   472  	case "arm64":
   473  		return "arm64"
   474  	case "386":
   475  		return "i386"
   476  	case "amd64":
   477  		return "x86_64"
   478  	default:
   479  		panic(fmt.Sprintf("unknown GOARCH: %q", goarch))
   480  	}
   481  }
   482  
   483  // environ merges os.Environ and the given "key=value" pairs.
   484  // If a key is in both os.Environ and kv, kv takes precedence.
   485  func environ(kv []string) []string {
   486  	cur := os.Environ()
   487  	new := make([]string, 0, len(cur)+len(kv))
   488  
   489  	envs := make(map[string]string, len(cur))
   490  	for _, ev := range cur {
   491  		elem := strings.SplitN(ev, "=", 2)
   492  		if len(elem) != 2 || elem[0] == "" {
   493  			// pass the env var of unusual form untouched.
   494  			// e.g. Windows may have env var names starting with "=".
   495  			new = append(new, ev)
   496  			continue
   497  		}
   498  		if goos == "windows" {
   499  			elem[0] = strings.ToUpper(elem[0])
   500  		}
   501  		envs[elem[0]] = elem[1]
   502  	}
   503  	for _, ev := range kv {
   504  		elem := strings.SplitN(ev, "=", 2)
   505  		if len(elem) != 2 || elem[0] == "" {
   506  			panic(fmt.Sprintf("malformed env var %q from input", ev))
   507  		}
   508  		if goos == "windows" {
   509  			elem[0] = strings.ToUpper(elem[0])
   510  		}
   511  		envs[elem[0]] = elem[1]
   512  	}
   513  	for k, v := range envs {
   514  		new = append(new, k+"="+v)
   515  	}
   516  	return new
   517  }
   518  
   519  func getenv(env []string, key string) string {
   520  	prefix := key + "="
   521  	for _, kv := range env {
   522  		if strings.HasPrefix(kv, prefix) {
   523  			return kv[len(prefix):]
   524  		}
   525  	}
   526  	return ""
   527  }
   528  
   529  func archNDK() string {
   530  	if runtime.GOOS == "windows" && runtime.GOARCH == "386" {
   531  		return "windows"
   532  	} else {
   533  		var arch string
   534  		switch runtime.GOARCH {
   535  		case "386":
   536  			arch = "x86"
   537  		case "amd64":
   538  			arch = "x86_64"
   539  		case "arm64":
   540  			// Android NDK does not contain arm64 toolchains (until and
   541  			// including NDK 23), use use x86_64 instead. See:
   542  			// https://github.com/android/ndk/issues/1299
   543  			if runtime.GOOS == "darwin" {
   544  				arch = "x86_64"
   545  				break
   546  			}
   547  			fallthrough
   548  		default:
   549  			panic("unsupported GOARCH: " + runtime.GOARCH)
   550  		}
   551  		return runtime.GOOS + "-" + arch
   552  	}
   553  }
   554  
   555  type ndkToolchain struct {
   556  	arch        string
   557  	abi         string
   558  	minAPI      int
   559  	toolPrefix  string
   560  	clangPrefix string
   561  }
   562  
   563  func (tc *ndkToolchain) ClangPrefix() string {
   564  	if buildAndroidAPI < tc.minAPI {
   565  		return fmt.Sprintf("%s%d", tc.clangPrefix, tc.minAPI)
   566  	}
   567  	return fmt.Sprintf("%s%d", tc.clangPrefix, buildAndroidAPI)
   568  }
   569  
   570  func (tc *ndkToolchain) Path(ndkRoot, toolName string) string {
   571  	cmdFromPref := func(pref string) string {
   572  		return filepath.Join(ndkRoot, "toolchains", "llvm", "prebuilt", archNDK(), "bin", pref+"-"+toolName)
   573  	}
   574  
   575  	var cmd string
   576  	switch toolName {
   577  	case "clang", "clang++":
   578  		cmd = cmdFromPref(tc.ClangPrefix())
   579  	default:
   580  		cmd = cmdFromPref(tc.toolPrefix)
   581  		// Starting from NDK 23, GNU binutils are fully migrated to LLVM binutils.
   582  		// See https://android.googlesource.com/platform/ndk/+/master/docs/Roadmap.md#ndk-r23
   583  		if _, err := os.Stat(cmd); errors.Is(err, fs.ErrNotExist) {
   584  			cmd = cmdFromPref("llvm")
   585  		}
   586  	}
   587  	return cmd
   588  }
   589  
   590  type ndkConfig map[string]ndkToolchain // map: GOOS->androidConfig.
   591  
   592  func (nc ndkConfig) Toolchain(arch string) ndkToolchain {
   593  	tc, ok := nc[arch]
   594  	if !ok {
   595  		panic(`unsupported architecture: ` + arch)
   596  	}
   597  	return tc
   598  }
   599  
   600  var ndk = ndkConfig{
   601  	"arm": {
   602  		arch:        "arm",
   603  		abi:         "armeabi-v7a",
   604  		minAPI:      16,
   605  		toolPrefix:  "arm-linux-androideabi",
   606  		clangPrefix: "armv7a-linux-androideabi",
   607  	},
   608  	"arm64": {
   609  		arch:        "arm64",
   610  		abi:         "arm64-v8a",
   611  		minAPI:      21,
   612  		toolPrefix:  "aarch64-linux-android",
   613  		clangPrefix: "aarch64-linux-android",
   614  	},
   615  
   616  	"386": {
   617  		arch:        "x86",
   618  		abi:         "x86",
   619  		minAPI:      16,
   620  		toolPrefix:  "i686-linux-android",
   621  		clangPrefix: "i686-linux-android",
   622  	},
   623  	"amd64": {
   624  		arch:        "x86_64",
   625  		abi:         "x86_64",
   626  		minAPI:      21,
   627  		toolPrefix:  "x86_64-linux-android",
   628  		clangPrefix: "x86_64-linux-android",
   629  	},
   630  }
   631  
   632  func xcodeAvailable() bool {
   633  	err := exec.Command("xcrun", "xcodebuild", "-version").Run()
   634  	return err == nil
   635  }