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