github.com/cyrilou242/gomobile-java@v0.0.0-20220215185836-09daef25a210/cmd/gomobile/env.go (about) 1 package main 2 3 import ( 4 "errors" 5 "fmt" 6 "io/fs" 7 "io/ioutil" 8 "os" 9 "os/exec" 10 "path/filepath" 11 "runtime" 12 "strings" 13 ) 14 15 // General mobile build environment. Initialized by envInit. 16 var ( 17 gomobilepath string // $GOPATH/pkg/gomobile 18 19 androidEnv map[string][]string // android arch -> []string 20 21 appleEnv map[string][]string 22 23 androidArmNM 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 } 193 } 194 195 if !xcodeAvailable() { 196 return nil 197 } 198 199 appleNM = "nm" 200 appleEnv = make(map[string][]string) 201 for _, platform := range applePlatforms { 202 for _, arch := range platformArchs(platform) { 203 var env []string 204 var goos, sdk, clang, cflags string 205 var err error 206 switch platform { 207 case "ios": 208 goos = "ios" 209 sdk = "iphoneos" 210 clang, cflags, err = envClang(sdk) 211 cflags += " -miphoneos-version-min=" + buildIOSVersion 212 cflags += " -fembed-bitcode" 213 case "iossimulator": 214 goos = "ios" 215 sdk = "iphonesimulator" 216 clang, cflags, err = envClang(sdk) 217 cflags += " -mios-simulator-version-min=" + buildIOSVersion 218 cflags += " -fembed-bitcode" 219 case "maccatalyst": 220 // Mac Catalyst is a subset of iOS APIs made available on macOS 221 // designed to ease porting apps developed for iPad to macOS. 222 // See https://developer.apple.com/mac-catalyst/. 223 // Because of this, when building a Go package targeting maccatalyst, 224 // GOOS=darwin (not ios). To bridge the gap and enable maccatalyst 225 // packages to be compiled, we also specify the "ios" build tag. 226 // To help discriminate between darwin, ios, macos, and maccatalyst 227 // targets, there is also a "maccatalyst" tag. 228 // Some additional context on this can be found here: 229 // https://stackoverflow.com/questions/12132933/preprocessor-macro-for-os-x-targets/49560690#49560690 230 goos = "darwin" 231 sdk = "macosx" 232 clang, cflags, err = envClang(sdk) 233 // TODO(ydnar): the following 3 lines MAY be needed to compile 234 // packages or apps for maccatalyst. Commenting them out now in case 235 // it turns out they are necessary. Currently none of the example 236 // apps will build for macos or maccatalyst because they have a 237 // GLKit dependency, which is deprecated on all Apple platforms, and 238 // broken on maccatalyst (GLKView isn’t available). 239 // sysroot := strings.SplitN(cflags, " ", 2)[1] 240 // cflags += " -isystem " + sysroot + "/System/iOSSupport/usr/include" 241 // cflags += " -iframework " + sysroot + "/System/iOSSupport/System/Library/Frameworks" 242 switch arch { 243 case "amd64": 244 cflags += " -target x86_64-apple-ios" + buildIOSVersion + "-macabi" 245 case "arm64": 246 cflags += " -target arm64-apple-ios" + buildIOSVersion + "-macabi" 247 cflags += " -fembed-bitcode" 248 } 249 case "macos": 250 goos = "darwin" 251 sdk = "macosx" // Note: the SDK is called "macosx", not "macos" 252 clang, cflags, err = envClang(sdk) 253 if arch == "arm64" { 254 cflags += " -fembed-bitcode" 255 } 256 default: 257 panic(fmt.Errorf("unknown Apple target: %s/%s", platform, arch)) 258 } 259 260 if err != nil { 261 return err 262 } 263 264 env = append(env, 265 "GOOS="+goos, 266 "GOARCH="+arch, 267 "GOFLAGS="+"-tags="+strings.Join(platformTags(platform), ","), 268 "CC="+clang, 269 "CXX="+clang+"++", 270 "CGO_CFLAGS="+cflags+" -arch "+archClang(arch), 271 "CGO_CXXFLAGS="+cflags+" -arch "+archClang(arch), 272 "CGO_LDFLAGS="+cflags+" -arch "+archClang(arch), 273 "CGO_ENABLED=1", 274 "DARWIN_SDK="+sdk, 275 ) 276 appleEnv[platform+"/"+arch] = env 277 } 278 } 279 280 return nil 281 } 282 283 func ndkRoot() (string, error) { 284 if buildN { 285 return "$NDK_PATH", nil 286 } 287 288 androidHome := os.Getenv("ANDROID_HOME") 289 if androidHome != "" { 290 ndkRoot := filepath.Join(androidHome, "ndk-bundle") 291 _, err := os.Stat(ndkRoot) 292 if err == nil { 293 return ndkRoot, nil 294 } 295 } 296 297 ndkRoot := os.Getenv("ANDROID_NDK_HOME") 298 if ndkRoot != "" { 299 _, err := os.Stat(ndkRoot) 300 if err == nil { 301 return ndkRoot, nil 302 } 303 } 304 305 return "", fmt.Errorf("no Android NDK found in $ANDROID_HOME/ndk-bundle nor in $ANDROID_NDK_HOME") 306 } 307 308 func envClang(sdkName string) (clang, cflags string, err error) { 309 if buildN { 310 return sdkName + "-clang", "-isysroot " + sdkName, nil 311 } 312 cmd := exec.Command("xcrun", "--sdk", sdkName, "--find", "clang") 313 out, err := cmd.CombinedOutput() 314 if err != nil { 315 return "", "", fmt.Errorf("xcrun --find: %v\n%s", err, out) 316 } 317 clang = strings.TrimSpace(string(out)) 318 319 cmd = exec.Command("xcrun", "--sdk", sdkName, "--show-sdk-path") 320 out, err = cmd.CombinedOutput() 321 if err != nil { 322 return "", "", fmt.Errorf("xcrun --show-sdk-path: %v\n%s", err, out) 323 } 324 sdk := strings.TrimSpace(string(out)) 325 return clang, "-isysroot " + sdk, nil 326 } 327 328 func archClang(goarch string) string { 329 switch goarch { 330 case "arm": 331 return "armv7" 332 case "arm64": 333 return "arm64" 334 case "386": 335 return "i386" 336 case "amd64": 337 return "x86_64" 338 default: 339 panic(fmt.Sprintf("unknown GOARCH: %q", goarch)) 340 } 341 } 342 343 // environ merges os.Environ and the given "key=value" pairs. 344 // If a key is in both os.Environ and kv, kv takes precedence. 345 func environ(kv []string) []string { 346 cur := os.Environ() 347 new := make([]string, 0, len(cur)+len(kv)) 348 349 envs := make(map[string]string, len(cur)) 350 for _, ev := range cur { 351 elem := strings.SplitN(ev, "=", 2) 352 if len(elem) != 2 || elem[0] == "" { 353 // pass the env var of unusual form untouched. 354 // e.g. Windows may have env var names starting with "=". 355 new = append(new, ev) 356 continue 357 } 358 if goos == "windows" { 359 elem[0] = strings.ToUpper(elem[0]) 360 } 361 envs[elem[0]] = elem[1] 362 } 363 for _, ev := range kv { 364 elem := strings.SplitN(ev, "=", 2) 365 if len(elem) != 2 || elem[0] == "" { 366 panic(fmt.Sprintf("malformed env var %q from input", ev)) 367 } 368 if goos == "windows" { 369 elem[0] = strings.ToUpper(elem[0]) 370 } 371 envs[elem[0]] = elem[1] 372 } 373 for k, v := range envs { 374 new = append(new, k+"="+v) 375 } 376 return new 377 } 378 379 func getenv(env []string, key string) string { 380 prefix := key + "=" 381 for _, kv := range env { 382 if strings.HasPrefix(kv, prefix) { 383 return kv[len(prefix):] 384 } 385 } 386 return "" 387 } 388 389 func archNDK() string { 390 if runtime.GOOS == "windows" && runtime.GOARCH == "386" { 391 return "windows" 392 } else { 393 var arch string 394 switch runtime.GOARCH { 395 case "386": 396 arch = "x86" 397 case "amd64": 398 arch = "x86_64" 399 case "arm64": 400 // Android NDK does not contain arm64 toolchains (until and 401 // including NDK 23), use use x86_64 instead. See: 402 // https://github.com/android/ndk/issues/1299 403 if runtime.GOOS == "darwin" { 404 arch = "x86_64" 405 break 406 } 407 fallthrough 408 default: 409 panic("unsupported GOARCH: " + runtime.GOARCH) 410 } 411 return runtime.GOOS + "-" + arch 412 } 413 } 414 415 type ndkToolchain struct { 416 arch string 417 abi string 418 minAPI int 419 toolPrefix string 420 clangPrefix string 421 } 422 423 func (tc *ndkToolchain) ClangPrefix() string { 424 if buildAndroidAPI < tc.minAPI { 425 return fmt.Sprintf("%s%d", tc.clangPrefix, tc.minAPI) 426 } 427 return fmt.Sprintf("%s%d", tc.clangPrefix, buildAndroidAPI) 428 } 429 430 func (tc *ndkToolchain) Path(ndkRoot, toolName string) string { 431 cmdFromPref := func(pref string) string { 432 return filepath.Join(ndkRoot, "toolchains", "llvm", "prebuilt", archNDK(), "bin", pref+"-"+toolName) 433 } 434 435 var cmd string 436 switch toolName { 437 case "clang", "clang++": 438 cmd = cmdFromPref(tc.ClangPrefix()) 439 default: 440 cmd = cmdFromPref(tc.toolPrefix) 441 // Starting from NDK 23, GNU binutils are fully migrated to LLVM binutils. 442 // See https://android.googlesource.com/platform/ndk/+/master/docs/Roadmap.md#ndk-r23 443 if _, err := os.Stat(cmd); errors.Is(err, fs.ErrNotExist) { 444 cmd = cmdFromPref("llvm") 445 } 446 } 447 return cmd 448 } 449 450 type ndkConfig map[string]ndkToolchain // map: GOOS->androidConfig. 451 452 func (nc ndkConfig) Toolchain(arch string) ndkToolchain { 453 tc, ok := nc[arch] 454 if !ok { 455 panic(`unsupported architecture: ` + arch) 456 } 457 return tc 458 } 459 460 var ndk = ndkConfig{ 461 "arm": { 462 arch: "arm", 463 abi: "armeabi-v7a", 464 minAPI: 16, 465 toolPrefix: "arm-linux-androideabi", 466 clangPrefix: "armv7a-linux-androideabi", 467 }, 468 "arm64": { 469 arch: "arm64", 470 abi: "arm64-v8a", 471 minAPI: 21, 472 toolPrefix: "aarch64-linux-android", 473 clangPrefix: "aarch64-linux-android", 474 }, 475 476 "386": { 477 arch: "x86", 478 abi: "x86", 479 minAPI: 16, 480 toolPrefix: "i686-linux-android", 481 clangPrefix: "i686-linux-android", 482 }, 483 "amd64": { 484 arch: "x86_64", 485 abi: "x86_64", 486 minAPI: 21, 487 toolPrefix: "x86_64-linux-android", 488 clangPrefix: "x86_64-linux-android", 489 }, 490 } 491 492 func xcodeAvailable() bool { 493 err := exec.Command("xcrun", "xcodebuild", "-version").Run() 494 return err == nil 495 }