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