github.com/acrespo/mobile@v0.0.0-20190107162257-dc0771356504/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  	cwd          string
    17  	gomobilepath string // $GOPATH/pkg/gomobile
    18  
    19  	androidEnv map[string][]string // android arch -> []string
    20  
    21  	darwinEnv map[string][]string
    22  
    23  	androidArmNM string
    24  	darwinArmNM  string
    25  
    26  	allArchs = []string{"arm", "arm64", "386", "amd64"}
    27  )
    28  
    29  func buildEnvInit() (cleanup func(), err error) {
    30  	// Find gomobilepath.
    31  	gopath := goEnv("GOPATH")
    32  	for _, p := range filepath.SplitList(gopath) {
    33  		gomobilepath = filepath.Join(p, "pkg", "gomobile")
    34  		if _, err := os.Stat(gomobilepath); buildN || err == nil {
    35  			break
    36  		}
    37  	}
    38  
    39  	if buildX {
    40  		fmt.Fprintln(xout, "GOMOBILE="+gomobilepath)
    41  	}
    42  
    43  	// Check the toolchain is in a good state.
    44  	// Pick a temporary directory for assembling an apk/app.
    45  	if gomobilepath == "" {
    46  		return nil, errors.New("toolchain not installed, run `gomobile init`")
    47  	}
    48  
    49  	if err := envInit(); err != nil {
    50  		return nil, err
    51  	}
    52  
    53  	cleanupFn := func() {
    54  		if buildWork {
    55  			fmt.Printf("WORK=%s\n", tmpdir)
    56  			return
    57  		}
    58  		removeAll(tmpdir)
    59  	}
    60  	if buildN {
    61  		tmpdir = "$WORK"
    62  		cleanupFn = func() {}
    63  	} else {
    64  		tmpdir, err = ioutil.TempDir("", "gomobile-work-")
    65  		if err != nil {
    66  			return nil, err
    67  		}
    68  	}
    69  	if buildX {
    70  		fmt.Fprintln(xout, "WORK="+tmpdir)
    71  	}
    72  
    73  	return cleanupFn, nil
    74  }
    75  
    76  func envInit() (err error) {
    77  	// TODO(crawshaw): cwd only used by ctx.Import, which can take "."
    78  	cwd, err = os.Getwd()
    79  	if err != nil {
    80  		return err
    81  	}
    82  
    83  	// Setup the cross-compiler environments.
    84  	if hasNDK() {
    85  		androidEnv = make(map[string][]string)
    86  		for arch, toolchain := range ndk {
    87  			androidEnv[arch] = []string{
    88  				"GOOS=android",
    89  				"GOARCH=" + arch,
    90  				"CC=" + toolchain.Path("clang"),
    91  				"CXX=" + toolchain.Path("clang++"),
    92  				"CGO_ENABLED=1",
    93  			}
    94  			if arch == "arm" {
    95  				androidEnv[arch] = append(androidEnv[arch], "GOARM=7")
    96  			}
    97  		}
    98  	}
    99  
   100  	if !xcodeAvailable() {
   101  		return nil
   102  	}
   103  
   104  	darwinArmNM = "nm"
   105  	darwinEnv = make(map[string][]string)
   106  	for _, arch := range allArchs {
   107  		var env []string
   108  		var err error
   109  		var clang, cflags string
   110  		switch arch {
   111  		case "arm":
   112  			env = append(env, "GOARM=7")
   113  			fallthrough
   114  		case "arm64":
   115  			clang, cflags, err = envClang("iphoneos")
   116  			cflags += " -miphoneos-version-min=" + buildIOSVersion
   117  		case "386", "amd64":
   118  			clang, cflags, err = envClang("iphonesimulator")
   119  			cflags += " -mios-simulator-version-min=" + buildIOSVersion
   120  		default:
   121  			panic(fmt.Errorf("unknown GOARCH: %q", arch))
   122  		}
   123  		if err != nil {
   124  			return err
   125  		}
   126  		env = append(env,
   127  			"GOOS=darwin",
   128  			"GOARCH="+arch,
   129  			"CC="+clang,
   130  			"CXX="+clang+"++",
   131  			"CGO_CFLAGS="+cflags+" -arch "+archClang(arch),
   132  			"CGO_CXXFLAGS="+cflags+" -arch "+archClang(arch),
   133  			"CGO_LDFLAGS="+cflags+" -arch "+archClang(arch),
   134  			"CGO_ENABLED=1",
   135  		)
   136  		darwinEnv[arch] = env
   137  	}
   138  
   139  	return nil
   140  }
   141  
   142  func hasNDK() bool {
   143  	if buildN {
   144  		return true
   145  	}
   146  	tcPath := filepath.Join(gomobilepath, "ndk-toolchains")
   147  	_, err := os.Stat(tcPath)
   148  	return err == nil
   149  }
   150  
   151  func envClang(sdkName string) (clang, cflags string, err error) {
   152  	if buildN {
   153  		return "clang-" + sdkName, "-isysroot=" + sdkName, nil
   154  	}
   155  	cmd := exec.Command("xcrun", "--sdk", sdkName, "--find", "clang")
   156  	out, err := cmd.CombinedOutput()
   157  	if err != nil {
   158  		return "", "", fmt.Errorf("xcrun --find: %v\n%s", err, out)
   159  	}
   160  	clang = strings.TrimSpace(string(out))
   161  
   162  	cmd = exec.Command("xcrun", "--sdk", sdkName, "--show-sdk-path")
   163  	out, err = cmd.CombinedOutput()
   164  	if err != nil {
   165  		return "", "", fmt.Errorf("xcrun --show-sdk-path: %v\n%s", err, out)
   166  	}
   167  	sdk := strings.TrimSpace(string(out))
   168  	return clang, "-isysroot " + sdk, nil
   169  }
   170  
   171  func archClang(goarch string) string {
   172  	switch goarch {
   173  	case "arm":
   174  		return "armv7"
   175  	case "arm64":
   176  		return "arm64"
   177  	case "386":
   178  		return "i386"
   179  	case "amd64":
   180  		return "x86_64"
   181  	default:
   182  		panic(fmt.Sprintf("unknown GOARCH: %q", goarch))
   183  	}
   184  }
   185  
   186  // environ merges os.Environ and the given "key=value" pairs.
   187  // If a key is in both os.Environ and kv, kv takes precedence.
   188  func environ(kv []string) []string {
   189  	cur := os.Environ()
   190  	new := make([]string, 0, len(cur)+len(kv))
   191  
   192  	envs := make(map[string]string, len(cur))
   193  	for _, ev := range cur {
   194  		elem := strings.SplitN(ev, "=", 2)
   195  		if len(elem) != 2 || elem[0] == "" {
   196  			// pass the env var of unusual form untouched.
   197  			// e.g. Windows may have env var names starting with "=".
   198  			new = append(new, ev)
   199  			continue
   200  		}
   201  		if goos == "windows" {
   202  			elem[0] = strings.ToUpper(elem[0])
   203  		}
   204  		envs[elem[0]] = elem[1]
   205  	}
   206  	for _, ev := range kv {
   207  		elem := strings.SplitN(ev, "=", 2)
   208  		if len(elem) != 2 || elem[0] == "" {
   209  			panic(fmt.Sprintf("malformed env var %q from input", ev))
   210  		}
   211  		if goos == "windows" {
   212  			elem[0] = strings.ToUpper(elem[0])
   213  		}
   214  		envs[elem[0]] = elem[1]
   215  	}
   216  	for k, v := range envs {
   217  		new = append(new, k+"="+v)
   218  	}
   219  	return new
   220  }
   221  
   222  func getenv(env []string, key string) string {
   223  	prefix := key + "="
   224  	for _, kv := range env {
   225  		if strings.HasPrefix(kv, prefix) {
   226  			return kv[len(prefix):]
   227  		}
   228  	}
   229  	return ""
   230  }
   231  
   232  func archNDK() string {
   233  	if runtime.GOOS == "windows" && runtime.GOARCH == "386" {
   234  		return "windows"
   235  	} else {
   236  		var arch string
   237  		switch runtime.GOARCH {
   238  		case "386":
   239  			arch = "x86"
   240  		case "amd64":
   241  			arch = "x86_64"
   242  		default:
   243  			panic("unsupported GOARCH: " + runtime.GOARCH)
   244  		}
   245  		return runtime.GOOS + "-" + arch
   246  	}
   247  }
   248  
   249  type ndkToolchain struct {
   250  	arch       string
   251  	abi        string
   252  	platform   string
   253  	gcc        string
   254  	toolPrefix string
   255  }
   256  
   257  func (tc *ndkToolchain) Path(toolName string) string {
   258  	return filepath.Join(gomobilepath, "ndk-toolchains", tc.arch, "bin", tc.toolPrefix+"-"+toolName)
   259  }
   260  
   261  type ndkConfig map[string]ndkToolchain // map: GOOS->androidConfig.
   262  
   263  func (nc ndkConfig) Toolchain(arch string) ndkToolchain {
   264  	tc, ok := nc[arch]
   265  	if !ok {
   266  		panic(`unsupported architecture: ` + arch)
   267  	}
   268  	return tc
   269  }
   270  
   271  var ndk = ndkConfig{
   272  	"arm": {
   273  		arch:       "arm",
   274  		abi:        "armeabi-v7a",
   275  		platform:   "android-16",
   276  		gcc:        "arm-linux-androideabi-4.9",
   277  		toolPrefix: "arm-linux-androideabi",
   278  	},
   279  	"arm64": {
   280  		arch:       "arm64",
   281  		abi:        "arm64-v8a",
   282  		platform:   "android-21",
   283  		gcc:        "aarch64-linux-android-4.9",
   284  		toolPrefix: "aarch64-linux-android",
   285  	},
   286  
   287  	"386": {
   288  		arch:       "x86",
   289  		abi:        "x86",
   290  		platform:   "android-16",
   291  		gcc:        "x86-4.9",
   292  		toolPrefix: "i686-linux-android",
   293  	},
   294  	"amd64": {
   295  		arch:       "x86_64",
   296  		abi:        "x86_64",
   297  		platform:   "android-21",
   298  		gcc:        "x86_64-4.9",
   299  		toolPrefix: "x86_64-linux-android",
   300  	},
   301  }
   302  
   303  func xcodeAvailable() bool {
   304  	err := exec.Command("xcrun", "xcodebuild", "-version").Run()
   305  	return err == nil
   306  }