627 lines
17 KiB
Go
627 lines
17 KiB
Go
|
// Copyright 2015 The Go Authors. All rights reserved.
|
|||
|
// Use of this source code is governed by a BSD-style
|
|||
|
// license that can be found in the LICENSE file.
|
|||
|
|
|||
|
package main
|
|||
|
|
|||
|
import (
|
|||
|
"bufio"
|
|||
|
"encoding/json"
|
|||
|
"errors"
|
|||
|
"fmt"
|
|||
|
"io/fs"
|
|||
|
"os"
|
|||
|
"os/exec"
|
|||
|
"path/filepath"
|
|||
|
"runtime"
|
|||
|
"strings"
|
|||
|
|
|||
|
"golang.org/x/mobile/internal/sdkpath"
|
|||
|
)
|
|||
|
|
|||
|
// General mobile build environment. Initialized by envInit.
|
|||
|
var (
|
|||
|
gomobilepath string // $GOPATH/pkg/gomobile
|
|||
|
androidEnv map[string][]string // android arch -> []string
|
|||
|
appleEnv map[string][]string
|
|||
|
appleNM string
|
|||
|
)
|
|||
|
|
|||
|
func isAndroidPlatform(platform string) bool {
|
|||
|
return platform == "android"
|
|||
|
}
|
|||
|
|
|||
|
func isApplePlatform(platform string) bool {
|
|||
|
return contains(applePlatforms, platform)
|
|||
|
}
|
|||
|
|
|||
|
var applePlatforms = []string{"ios", "iossimulator", "macos", "maccatalyst"}
|
|||
|
|
|||
|
func platformArchs(platform string) []string {
|
|||
|
switch platform {
|
|||
|
case "ios":
|
|||
|
return []string{"arm64"}
|
|||
|
case "iossimulator":
|
|||
|
return []string{"arm64", "amd64"}
|
|||
|
case "macos", "maccatalyst":
|
|||
|
return []string{"arm64", "amd64"}
|
|||
|
case "android":
|
|||
|
return []string{"arm", "arm64", "386", "amd64"}
|
|||
|
default:
|
|||
|
panic(fmt.Sprintf("unexpected platform: %s", platform))
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
func isSupportedArch(platform, arch string) bool {
|
|||
|
return contains(platformArchs(platform), arch)
|
|||
|
}
|
|||
|
|
|||
|
// platformOS returns the correct GOOS value for platform.
|
|||
|
func platformOS(platform string) string {
|
|||
|
switch platform {
|
|||
|
case "android":
|
|||
|
return "android"
|
|||
|
case "ios", "iossimulator":
|
|||
|
return "ios"
|
|||
|
case "macos", "maccatalyst":
|
|||
|
// For "maccatalyst", Go packages should be built with GOOS=darwin,
|
|||
|
// not GOOS=ios, since the underlying OS (and kernel, runtime) is macOS.
|
|||
|
// But, using GOOS=darwin with build-tag ios leads to corrupt builds: https://go.dev/issue/52299
|
|||
|
// => So we use GOOS=ios for now.
|
|||
|
// We also apply a "macos" or "maccatalyst" build tag, respectively.
|
|||
|
// See below for additional context.
|
|||
|
return "ios"
|
|||
|
default:
|
|||
|
panic(fmt.Sprintf("unexpected platform: %s", platform))
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
func platformTags(platform string) []string {
|
|||
|
switch platform {
|
|||
|
case "android":
|
|||
|
return []string{"android"}
|
|||
|
case "ios", "iossimulator":
|
|||
|
return []string{"ios"}
|
|||
|
case "macos":
|
|||
|
return []string{"macos"}
|
|||
|
case "maccatalyst":
|
|||
|
// Mac Catalyst is a subset of iOS APIs made available on macOS
|
|||
|
// designed to ease porting apps developed for iPad to macOS.
|
|||
|
// See
|
|||
|
// https://developer.apple.com/mac-catalyst/.
|
|||
|
// https://stackoverflow.com/questions/12132933/preprocessor-macro-for-os-x-targets/49560690#49560690
|
|||
|
//
|
|||
|
// Historically gomobile used GOOS=darwin with build tag ios when
|
|||
|
// targeting Mac Catalyst. However, this configuration is not officially
|
|||
|
// supported and leads to corrupt builds after go1.18: https://go.dev/issues/52299
|
|||
|
// Use GOOS=ios.
|
|||
|
// To help discriminate between darwin, ios, macos, and maccatalyst
|
|||
|
// targets, there is also a "maccatalyst" tag.
|
|||
|
return []string{"macos", "maccatalyst"}
|
|||
|
default:
|
|||
|
panic(fmt.Sprintf("unexpected platform: %s", platform))
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
func contains(haystack []string, needle string) bool {
|
|||
|
for _, v := range haystack {
|
|||
|
if v == needle {
|
|||
|
return true
|
|||
|
}
|
|||
|
}
|
|||
|
return false
|
|||
|
}
|
|||
|
|
|||
|
func buildEnvInit() (cleanup func(), err error) {
|
|||
|
// Find gomobilepath.
|
|||
|
gopath := goEnv("GOPATH")
|
|||
|
for _, p := range filepath.SplitList(gopath) {
|
|||
|
gomobilepath = filepath.Join(p, "pkg", "gomobile")
|
|||
|
if _, err := os.Stat(gomobilepath); buildN || err == nil {
|
|||
|
break
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
if buildX {
|
|||
|
fmt.Fprintln(xout, "GOMOBILE="+gomobilepath)
|
|||
|
}
|
|||
|
|
|||
|
// Check the toolchain is in a good state.
|
|||
|
// Pick a temporary directory for assembling an apk/app.
|
|||
|
if gomobilepath == "" {
|
|||
|
return nil, errors.New("toolchain not installed, run `gomobile init`")
|
|||
|
}
|
|||
|
|
|||
|
cleanupFn := func() {
|
|||
|
if buildWork {
|
|||
|
fmt.Printf("WORK=%s\n", tmpdir)
|
|||
|
return
|
|||
|
}
|
|||
|
removeAll(tmpdir)
|
|||
|
}
|
|||
|
if buildN {
|
|||
|
tmpdir = "$WORK"
|
|||
|
cleanupFn = func() {}
|
|||
|
} else {
|
|||
|
tmpdir, err = os.MkdirTemp("", "gomobile-work-")
|
|||
|
if err != nil {
|
|||
|
return nil, err
|
|||
|
}
|
|||
|
}
|
|||
|
if buildX {
|
|||
|
fmt.Fprintln(xout, "WORK="+tmpdir)
|
|||
|
}
|
|||
|
|
|||
|
if err := envInit(); err != nil {
|
|||
|
return nil, err
|
|||
|
}
|
|||
|
|
|||
|
return cleanupFn, nil
|
|||
|
}
|
|||
|
|
|||
|
func envInit() (err error) {
|
|||
|
// Setup the cross-compiler environments.
|
|||
|
if ndkRoot, err := ndkRoot(); err == nil {
|
|||
|
androidEnv = make(map[string][]string)
|
|||
|
if buildAndroidAPI < minAndroidAPI {
|
|||
|
return fmt.Errorf("gomobile requires Android API level >= %d", minAndroidAPI)
|
|||
|
}
|
|||
|
for arch, toolchain := range ndk {
|
|||
|
clang := toolchain.Path(ndkRoot, "clang")
|
|||
|
clangpp := toolchain.Path(ndkRoot, "clang++")
|
|||
|
if !buildN {
|
|||
|
tools := []string{clang, clangpp}
|
|||
|
if runtime.GOOS == "windows" {
|
|||
|
// Because of https://github.com/android-ndk/ndk/issues/920,
|
|||
|
// we require r19c, not just r19b. Fortunately, the clang++.cmd
|
|||
|
// script only exists in r19c.
|
|||
|
tools = append(tools, clangpp+".cmd")
|
|||
|
}
|
|||
|
for _, tool := range tools {
|
|||
|
_, err = os.Stat(tool)
|
|||
|
if err != nil {
|
|||
|
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)
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
androidEnv[arch] = []string{
|
|||
|
"GOOS=android",
|
|||
|
"GOARCH=" + arch,
|
|||
|
"CC=" + clang,
|
|||
|
"CXX=" + clangpp,
|
|||
|
"CGO_ENABLED=1",
|
|||
|
}
|
|||
|
if arch == "arm" {
|
|||
|
androidEnv[arch] = append(androidEnv[arch], "GOARM=7")
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
if !xcodeAvailable() {
|
|||
|
return nil
|
|||
|
}
|
|||
|
|
|||
|
appleNM = "nm"
|
|||
|
appleEnv = make(map[string][]string)
|
|||
|
for _, platform := range applePlatforms {
|
|||
|
for _, arch := range platformArchs(platform) {
|
|||
|
var env []string
|
|||
|
var goos, sdk, clang, cflags string
|
|||
|
var err error
|
|||
|
switch platform {
|
|||
|
case "ios":
|
|||
|
goos = "ios"
|
|||
|
sdk = "iphoneos"
|
|||
|
clang, cflags, err = envClang(sdk)
|
|||
|
cflags += " -miphoneos-version-min=" + buildIOSVersion
|
|||
|
cflags += " -fembed-bitcode"
|
|||
|
case "iossimulator":
|
|||
|
goos = "ios"
|
|||
|
sdk = "iphonesimulator"
|
|||
|
clang, cflags, err = envClang(sdk)
|
|||
|
cflags += " -mios-simulator-version-min=" + buildIOSVersion
|
|||
|
cflags += " -fembed-bitcode"
|
|||
|
case "maccatalyst":
|
|||
|
// See the comment about maccatalyst's GOOS, build tags configuration
|
|||
|
// in platformOS and platformTags.
|
|||
|
// Using GOOS=darwin with build-tag ios leads to corrupt builds: https://go.dev/issue/52299
|
|||
|
// => So we use GOOS=ios for now.
|
|||
|
goos = "ios"
|
|||
|
sdk = "macosx"
|
|||
|
clang, cflags, err = envClang(sdk)
|
|||
|
// TODO(ydnar): the following 3 lines MAY be needed to compile
|
|||
|
// packages or apps for maccatalyst. Commenting them out now in case
|
|||
|
// it turns out they are necessary. Currently none of the example
|
|||
|
// apps will build for macos or maccatalyst because they have a
|
|||
|
// GLKit dependency, which is deprecated on all Apple platforms, and
|
|||
|
// broken on maccatalyst (GLKView isn’t available).
|
|||
|
// sysroot := strings.SplitN(cflags, " ", 2)[1]
|
|||
|
// cflags += " -isystem " + sysroot + "/System/iOSSupport/usr/include"
|
|||
|
// cflags += " -iframework " + sysroot + "/System/iOSSupport/System/Library/Frameworks"
|
|||
|
switch arch {
|
|||
|
case "amd64":
|
|||
|
cflags += " -target x86_64-apple-ios" + buildIOSVersion + "-macabi"
|
|||
|
case "arm64":
|
|||
|
cflags += " -target arm64-apple-ios" + buildIOSVersion + "-macabi"
|
|||
|
cflags += " -fembed-bitcode"
|
|||
|
}
|
|||
|
case "macos":
|
|||
|
goos = "darwin"
|
|||
|
sdk = "macosx" // Note: the SDK is called "macosx", not "macos"
|
|||
|
clang, cflags, err = envClang(sdk)
|
|||
|
if arch == "arm64" {
|
|||
|
cflags += " -fembed-bitcode"
|
|||
|
}
|
|||
|
default:
|
|||
|
panic(fmt.Errorf("unknown Apple target: %s/%s", platform, arch))
|
|||
|
}
|
|||
|
|
|||
|
if err != nil {
|
|||
|
return err
|
|||
|
}
|
|||
|
|
|||
|
env = append(env,
|
|||
|
"GOOS="+goos,
|
|||
|
"GOARCH="+arch,
|
|||
|
"GOFLAGS="+"-tags="+strings.Join(platformTags(platform), ","),
|
|||
|
"CC="+clang,
|
|||
|
"CXX="+clang+"++",
|
|||
|
"CGO_CFLAGS="+cflags+" -arch "+archClang(arch),
|
|||
|
"CGO_CXXFLAGS="+cflags+" -arch "+archClang(arch),
|
|||
|
"CGO_LDFLAGS="+cflags+" -arch "+archClang(arch),
|
|||
|
"CGO_ENABLED=1",
|
|||
|
"DARWIN_SDK="+sdk,
|
|||
|
)
|
|||
|
appleEnv[platform+"/"+arch] = env
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
return nil
|
|||
|
}
|
|||
|
|
|||
|
// abi maps GOARCH values to Android ABI strings.
|
|||
|
// See https://developer.android.com/ndk/guides/abis
|
|||
|
func abi(goarch string) string {
|
|||
|
switch goarch {
|
|||
|
case "arm":
|
|||
|
return "armeabi-v7a"
|
|||
|
case "arm64":
|
|||
|
return "arm64-v8a"
|
|||
|
case "386":
|
|||
|
return "x86"
|
|||
|
case "amd64":
|
|||
|
return "x86_64"
|
|||
|
default:
|
|||
|
return ""
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// checkNDKRoot returns nil if the NDK in `ndkRoot` supports the current configured
|
|||
|
// API version and all the specified Android targets.
|
|||
|
func checkNDKRoot(ndkRoot string, targets []targetInfo) error {
|
|||
|
platformsJson, err := os.Open(filepath.Join(ndkRoot, "meta", "platforms.json"))
|
|||
|
if err != nil {
|
|||
|
return err
|
|||
|
}
|
|||
|
defer platformsJson.Close()
|
|||
|
decoder := json.NewDecoder(platformsJson)
|
|||
|
supportedVersions := struct {
|
|||
|
Min int
|
|||
|
Max int
|
|||
|
}{}
|
|||
|
if err := decoder.Decode(&supportedVersions); err != nil {
|
|||
|
return err
|
|||
|
}
|
|||
|
if supportedVersions.Min > buildAndroidAPI ||
|
|||
|
supportedVersions.Max < buildAndroidAPI {
|
|||
|
return fmt.Errorf("unsupported API version %d (not in %d..%d)", buildAndroidAPI, supportedVersions.Min, supportedVersions.Max)
|
|||
|
}
|
|||
|
abisJson, err := os.Open(filepath.Join(ndkRoot, "meta", "abis.json"))
|
|||
|
if err != nil {
|
|||
|
return err
|
|||
|
}
|
|||
|
defer abisJson.Close()
|
|||
|
decoder = json.NewDecoder(abisJson)
|
|||
|
abis := make(map[string]struct{})
|
|||
|
if err := decoder.Decode(&abis); err != nil {
|
|||
|
return err
|
|||
|
}
|
|||
|
for _, target := range targets {
|
|||
|
if !isAndroidPlatform(target.platform) {
|
|||
|
continue
|
|||
|
}
|
|||
|
if _, found := abis[abi(target.arch)]; !found {
|
|||
|
return fmt.Errorf("ndk does not support %s", target.platform)
|
|||
|
}
|
|||
|
}
|
|||
|
return nil
|
|||
|
}
|
|||
|
|
|||
|
// compatibleNDKRoots searches the side-by-side NDK dirs for compatible SDKs.
|
|||
|
func compatibleNDKRoots(ndkForest string, targets []targetInfo) ([]string, error) {
|
|||
|
ndkDirs, err := os.ReadDir(ndkForest)
|
|||
|
if err != nil {
|
|||
|
return nil, err
|
|||
|
}
|
|||
|
compatibleNDKRoots := []string{}
|
|||
|
var lastErr error
|
|||
|
for _, dirent := range ndkDirs {
|
|||
|
ndkRoot := filepath.Join(ndkForest, dirent.Name())
|
|||
|
lastErr = checkNDKRoot(ndkRoot, targets)
|
|||
|
if lastErr == nil {
|
|||
|
compatibleNDKRoots = append(compatibleNDKRoots, ndkRoot)
|
|||
|
}
|
|||
|
}
|
|||
|
if len(compatibleNDKRoots) > 0 {
|
|||
|
return compatibleNDKRoots, nil
|
|||
|
}
|
|||
|
return nil, lastErr
|
|||
|
}
|
|||
|
|
|||
|
// ndkVersion returns the full version number of an installed copy of the NDK,
|
|||
|
// or "" if it cannot be determined.
|
|||
|
func ndkVersion(ndkRoot string) string {
|
|||
|
properties, err := os.Open(filepath.Join(ndkRoot, "source.properties"))
|
|||
|
if err != nil {
|
|||
|
return ""
|
|||
|
}
|
|||
|
defer properties.Close()
|
|||
|
// Parse the version number out of the .properties file.
|
|||
|
// See https://en.wikipedia.org/wiki/.properties
|
|||
|
scanner := bufio.NewScanner(properties)
|
|||
|
for scanner.Scan() {
|
|||
|
line := scanner.Text()
|
|||
|
tokens := strings.SplitN(line, "=", 2)
|
|||
|
if len(tokens) != 2 {
|
|||
|
continue
|
|||
|
}
|
|||
|
if strings.TrimSpace(tokens[0]) == "Pkg.Revision" {
|
|||
|
return strings.TrimSpace(tokens[1])
|
|||
|
}
|
|||
|
}
|
|||
|
return ""
|
|||
|
}
|
|||
|
|
|||
|
// ndkRoot returns the root path of an installed NDK that supports all the
|
|||
|
// specified Android targets. For details of NDK locations, see
|
|||
|
// https://github.com/android/ndk-samples/wiki/Configure-NDK-Path
|
|||
|
func ndkRoot(targets ...targetInfo) (string, error) {
|
|||
|
if buildN {
|
|||
|
return "$NDK_PATH", nil
|
|||
|
}
|
|||
|
|
|||
|
// Try the ANDROID_NDK_HOME variable. This approach is deprecated, but it
|
|||
|
// has the highest priority because it represents an explicit user choice.
|
|||
|
if ndkRoot := os.Getenv("ANDROID_NDK_HOME"); ndkRoot != "" {
|
|||
|
if err := checkNDKRoot(ndkRoot, targets); err != nil {
|
|||
|
return "", fmt.Errorf("ANDROID_NDK_HOME specifies %s, which is unusable: %w", ndkRoot, err)
|
|||
|
}
|
|||
|
return ndkRoot, nil
|
|||
|
}
|
|||
|
|
|||
|
androidHome, err := sdkpath.AndroidHome()
|
|||
|
if err != nil {
|
|||
|
return "", fmt.Errorf("could not locate Android SDK: %w", err)
|
|||
|
}
|
|||
|
|
|||
|
// Use the newest compatible NDK under the side-by-side path arrangement.
|
|||
|
ndkForest := filepath.Join(androidHome, "ndk")
|
|||
|
ndkRoots, sideBySideErr := compatibleNDKRoots(ndkForest, targets)
|
|||
|
if len(ndkRoots) != 0 {
|
|||
|
// Choose the latest version that supports the build configuration.
|
|||
|
// NDKs whose version cannot be determined will be least preferred.
|
|||
|
// In the event of a tie, the later ndkRoot will win.
|
|||
|
maxVersion := ""
|
|||
|
var selected string
|
|||
|
for _, ndkRoot := range ndkRoots {
|
|||
|
version := ndkVersion(ndkRoot)
|
|||
|
if version >= maxVersion {
|
|||
|
maxVersion = version
|
|||
|
selected = ndkRoot
|
|||
|
}
|
|||
|
}
|
|||
|
return selected, nil
|
|||
|
}
|
|||
|
// Try the deprecated NDK location.
|
|||
|
ndkRoot := filepath.Join(androidHome, "ndk-bundle")
|
|||
|
if legacyErr := checkNDKRoot(ndkRoot, targets); legacyErr != nil {
|
|||
|
return "", fmt.Errorf("no usable NDK in %s: %w, %v", androidHome, sideBySideErr, legacyErr)
|
|||
|
}
|
|||
|
return ndkRoot, nil
|
|||
|
}
|
|||
|
|
|||
|
func envClang(sdkName string) (clang, cflags string, err error) {
|
|||
|
if buildN {
|
|||
|
return sdkName + "-clang", "-isysroot " + sdkName, nil
|
|||
|
}
|
|||
|
cmd := exec.Command("xcrun", "--sdk", sdkName, "--find", "clang")
|
|||
|
out, err := cmd.Output()
|
|||
|
if err != nil {
|
|||
|
if ee := (*exec.ExitError)(nil); errors.As(err, &ee) {
|
|||
|
out = append(out, ee.Stderr...)
|
|||
|
}
|
|||
|
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 {
|
|||
|
if ee := (*exec.ExitError)(nil); errors.As(err, &ee) {
|
|||
|
out = append(out, ee.Stderr...)
|
|||
|
}
|
|||
|
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 {
|
|||
|
cur := os.Environ()
|
|||
|
new := make([]string, 0, len(cur)+len(kv))
|
|||
|
|
|||
|
envs := make(map[string]string, len(cur))
|
|||
|
for _, ev := range cur {
|
|||
|
elem := strings.SplitN(ev, "=", 2)
|
|||
|
if len(elem) != 2 || elem[0] == "" {
|
|||
|
// pass the env var of unusual form untouched.
|
|||
|
// e.g. Windows may have env var names starting with "=".
|
|||
|
new = append(new, ev)
|
|||
|
continue
|
|||
|
}
|
|||
|
if goos == "windows" {
|
|||
|
elem[0] = strings.ToUpper(elem[0])
|
|||
|
}
|
|||
|
envs[elem[0]] = elem[1]
|
|||
|
}
|
|||
|
for _, ev := range kv {
|
|||
|
elem := strings.SplitN(ev, "=", 2)
|
|||
|
if len(elem) != 2 || elem[0] == "" {
|
|||
|
panic(fmt.Sprintf("malformed env var %q from input", ev))
|
|||
|
}
|
|||
|
if goos == "windows" {
|
|||
|
elem[0] = strings.ToUpper(elem[0])
|
|||
|
}
|
|||
|
envs[elem[0]] = elem[1]
|
|||
|
}
|
|||
|
for k, v := range envs {
|
|||
|
new = append(new, k+"="+v)
|
|||
|
}
|
|||
|
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 archNDK() string {
|
|||
|
if runtime.GOOS == "windows" && runtime.GOARCH == "386" {
|
|||
|
return "windows"
|
|||
|
} else {
|
|||
|
var arch string
|
|||
|
switch runtime.GOARCH {
|
|||
|
case "386":
|
|||
|
arch = "x86"
|
|||
|
case "amd64":
|
|||
|
arch = "x86_64"
|
|||
|
case "arm64":
|
|||
|
// Android NDK does not contain arm64 toolchains (until and
|
|||
|
// including NDK 23), use use x86_64 instead. See:
|
|||
|
// https://github.com/android/ndk/issues/1299
|
|||
|
if runtime.GOOS == "darwin" {
|
|||
|
arch = "x86_64"
|
|||
|
break
|
|||
|
}
|
|||
|
fallthrough
|
|||
|
default:
|
|||
|
panic("unsupported GOARCH: " + runtime.GOARCH)
|
|||
|
}
|
|||
|
return runtime.GOOS + "-" + arch
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
type ndkToolchain struct {
|
|||
|
arch string
|
|||
|
abi string
|
|||
|
minAPI int
|
|||
|
toolPrefix string
|
|||
|
clangPrefix string
|
|||
|
}
|
|||
|
|
|||
|
func (tc *ndkToolchain) ClangPrefix() string {
|
|||
|
if buildAndroidAPI < tc.minAPI {
|
|||
|
return fmt.Sprintf("%s%d", tc.clangPrefix, tc.minAPI)
|
|||
|
}
|
|||
|
return fmt.Sprintf("%s%d", tc.clangPrefix, buildAndroidAPI)
|
|||
|
}
|
|||
|
|
|||
|
func (tc *ndkToolchain) Path(ndkRoot, toolName string) string {
|
|||
|
cmdFromPref := func(pref string) string {
|
|||
|
return filepath.Join(ndkRoot, "toolchains", "llvm", "prebuilt", archNDK(), "bin", pref+"-"+toolName)
|
|||
|
}
|
|||
|
|
|||
|
var cmd string
|
|||
|
switch toolName {
|
|||
|
case "clang", "clang++":
|
|||
|
cmd = cmdFromPref(tc.ClangPrefix())
|
|||
|
default:
|
|||
|
cmd = cmdFromPref(tc.toolPrefix)
|
|||
|
// Starting from NDK 23, GNU binutils are fully migrated to LLVM binutils.
|
|||
|
// See https://android.googlesource.com/platform/ndk/+/master/docs/Roadmap.md#ndk-r23
|
|||
|
if _, err := os.Stat(cmd); errors.Is(err, fs.ErrNotExist) {
|
|||
|
cmd = cmdFromPref("llvm")
|
|||
|
}
|
|||
|
}
|
|||
|
return cmd
|
|||
|
}
|
|||
|
|
|||
|
type ndkConfig map[string]ndkToolchain // map: GOOS->androidConfig.
|
|||
|
|
|||
|
func (nc ndkConfig) Toolchain(arch string) ndkToolchain {
|
|||
|
tc, ok := nc[arch]
|
|||
|
if !ok {
|
|||
|
panic(`unsupported architecture: ` + arch)
|
|||
|
}
|
|||
|
return tc
|
|||
|
}
|
|||
|
|
|||
|
var ndk = ndkConfig{
|
|||
|
"arm": {
|
|||
|
arch: "arm",
|
|||
|
abi: "armeabi-v7a",
|
|||
|
minAPI: 16,
|
|||
|
toolPrefix: "arm-linux-androideabi",
|
|||
|
clangPrefix: "armv7a-linux-androideabi",
|
|||
|
},
|
|||
|
"arm64": {
|
|||
|
arch: "arm64",
|
|||
|
abi: "arm64-v8a",
|
|||
|
minAPI: 21,
|
|||
|
toolPrefix: "aarch64-linux-android",
|
|||
|
clangPrefix: "aarch64-linux-android",
|
|||
|
},
|
|||
|
|
|||
|
"386": {
|
|||
|
arch: "x86",
|
|||
|
abi: "x86",
|
|||
|
minAPI: 16,
|
|||
|
toolPrefix: "i686-linux-android",
|
|||
|
clangPrefix: "i686-linux-android",
|
|||
|
},
|
|||
|
"amd64": {
|
|||
|
arch: "x86_64",
|
|||
|
abi: "x86_64",
|
|||
|
minAPI: 21,
|
|||
|
toolPrefix: "x86_64-linux-android",
|
|||
|
clangPrefix: "x86_64-linux-android",
|
|||
|
},
|
|||
|
}
|
|||
|
|
|||
|
func xcodeAvailable() bool {
|
|||
|
err := exec.Command("xcrun", "xcodebuild", "-version").Run()
|
|||
|
return err == nil
|
|||
|
}
|