github.com/SkycoinProject/gomobile@v0.0.0-20190312151609-d3739f865fa6/cmd/gomobile/env.go (about) 1 package main 2 3 import ( 4 "errors" 5 "fmt" 6 "io/ioutil" 7 "log" 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 cwd string 18 gomobilepath string // $GOPATH/pkg/gomobile 19 20 androidEnv map[string][]string // android arch -> []string 21 22 darwinEnv map[string][]string 23 24 androidArmNM string 25 darwinArmNM string 26 27 allArchs = []string{"arm", "arm64", "386", "amd64"} 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 // TODO(crawshaw): cwd only used by ctx.Import, which can take "." 79 cwd, err = os.Getwd() 80 if err != nil { 81 return err 82 } 83 84 // Setup the cross-compiler environments. 85 if ndkRoot, err := ndkRoot(); err == nil { 86 androidEnv = make(map[string][]string) 87 for arch, toolchain := range ndk { 88 clang := toolchain.Path(ndkRoot, "clang") 89 if !buildN { 90 _, err = os.Stat(clang) 91 if err != nil { 92 return fmt.Errorf("No compiler for %s was found in the NDK (tried %q). Make sure your NDK version is >= r19b. Use `sdkmanager --update` to update it.", arch, clang) 93 } 94 } 95 androidEnv[arch] = []string{ 96 "GOOS=android", 97 "GOARCH=" + arch, 98 "CC=" + clang, 99 "CXX=" + toolchain.Path(ndkRoot, "clang++"), 100 "CGO_ENABLED=1", 101 } 102 if arch == "arm" { 103 androidEnv[arch] = append(androidEnv[arch], "GOARM=7") 104 } 105 } 106 } 107 108 if !xcodeAvailable() { 109 return nil 110 } 111 112 darwinArmNM = "nm" 113 darwinEnv = make(map[string][]string) 114 for _, arch := range allArchs { 115 var env []string 116 var err error 117 var clang, cflags string 118 switch arch { 119 case "arm": 120 env = append(env, "GOARM=7") 121 fallthrough 122 case "arm64": 123 clang, cflags, err = envClang("iphoneos") 124 cflags += " -miphoneos-version-min=" + buildIOSVersion 125 case "386", "amd64": 126 clang, cflags, err = envClang("iphonesimulator") 127 cflags += " -mios-simulator-version-min=" + buildIOSVersion 128 default: 129 panic(fmt.Errorf("unknown GOARCH: %q", arch)) 130 } 131 if err != nil { 132 return err 133 } 134 env = append(env, 135 "GOOS=darwin", 136 "GOARCH="+arch, 137 "CC="+clang, 138 "CXX="+clang+"++", 139 "CGO_CFLAGS="+cflags+" -arch "+archClang(arch), 140 "CGO_CXXFLAGS="+cflags+" -arch "+archClang(arch), 141 "CGO_LDFLAGS="+cflags+" -arch "+archClang(arch), 142 "CGO_ENABLED=1", 143 ) 144 darwinEnv[arch] = env 145 } 146 147 return nil 148 } 149 150 func ndkRoot() (string, error) { 151 if buildN { 152 return "$NDK_PATH", nil 153 } 154 androidHome := os.Getenv("ANDROID_HOME") 155 if androidHome == "" { 156 return "", errors.New("The Android SDK was not found. Please set ANDROID_HOME to the root of the Android SDK.") 157 } 158 ndkRoot := filepath.Join(androidHome, "ndk-bundle") 159 _, err := os.Stat(ndkRoot) 160 if err != nil { 161 return "", fmt.Errorf("The NDK was not found in $ANDROID_HOME/ndk-bundle (%q). Install the NDK with `sdkmanager 'ndk-bundle'`", ndkRoot) 162 } 163 return ndkRoot, nil 164 } 165 166 func envClang(sdkName string) (clang, cflags string, err error) { 167 if buildN { 168 return sdkName + "-clang", "-isysroot=" + sdkName, nil 169 } 170 cmd := exec.Command("xcrun", "--sdk", sdkName, "--find", "clang") 171 out, err := cmd.CombinedOutput() 172 if err != nil { 173 return "", "", fmt.Errorf("xcrun --find: %v\n%s", err, out) 174 } 175 clang = strings.TrimSpace(string(out)) 176 177 cmd = exec.Command("xcrun", "--sdk", sdkName, "--show-sdk-path") 178 out, err = cmd.CombinedOutput() 179 if err != nil { 180 return "", "", fmt.Errorf("xcrun --show-sdk-path: %v\n%s", err, out) 181 } 182 sdk := strings.TrimSpace(string(out)) 183 return clang, "-isysroot " + sdk, nil 184 } 185 186 func archClang(goarch string) string { 187 switch goarch { 188 case "arm": 189 return "armv7" 190 case "arm64": 191 return "arm64" 192 case "386": 193 return "i386" 194 case "amd64": 195 return "x86_64" 196 default: 197 panic(fmt.Sprintf("unknown GOARCH: %q", goarch)) 198 } 199 } 200 201 // environ merges os.Environ and the given "key=value" pairs. 202 // If a key is in both os.Environ and kv, kv takes precedence. 203 func environ(kv []string) []string { 204 cur := os.Environ() 205 new := make([]string, 0, len(cur)+len(kv)) 206 207 envs := make(map[string]string, len(cur)) 208 for _, ev := range cur { 209 elem := strings.SplitN(ev, "=", 2) 210 if len(elem) != 2 || elem[0] == "" { 211 // pass the env var of unusual form untouched. 212 // e.g. Windows may have env var names starting with "=". 213 new = append(new, ev) 214 continue 215 } 216 if goos == "windows" { 217 elem[0] = strings.ToUpper(elem[0]) 218 } 219 envs[elem[0]] = elem[1] 220 } 221 for _, ev := range kv { 222 elem := strings.SplitN(ev, "=", 2) 223 if len(elem) != 2 || elem[0] == "" { 224 panic(fmt.Sprintf("malformed env var %q from input", ev)) 225 } 226 if goos == "windows" { 227 elem[0] = strings.ToUpper(elem[0]) 228 } 229 envs[elem[0]] = elem[1] 230 } 231 for k, v := range envs { 232 new = append(new, k+"="+v) 233 } 234 return new 235 } 236 237 func getenv(env []string, key string) string { 238 prefix := key + "=" 239 for _, kv := range env { 240 if strings.HasPrefix(kv, prefix) { 241 return kv[len(prefix):] 242 } 243 } 244 return "" 245 } 246 247 func archNDK() string { 248 if runtime.GOOS == "windows" && runtime.GOARCH == "386" { 249 return "windows" 250 } else { 251 var arch string 252 switch runtime.GOARCH { 253 case "386": 254 arch = "x86" 255 case "amd64": 256 arch = "x86_64" 257 default: 258 panic("unsupported GOARCH: " + runtime.GOARCH) 259 } 260 return runtime.GOOS + "-" + arch 261 } 262 } 263 264 type ndkToolchain struct { 265 arch string 266 abi string 267 toolPrefix string 268 clangPrefix string 269 } 270 271 func (tc *ndkToolchain) Path(ndkRoot, toolName string) string { 272 var pref string 273 switch toolName { 274 case "clang", "clang++": 275 if runtime.GOOS == "windows" { 276 return tc.createNDKr19bWorkaroundTool(ndkRoot, toolName) 277 } 278 pref = tc.clangPrefix 279 default: 280 pref = tc.toolPrefix 281 } 282 return filepath.Join(ndkRoot, "toolchains", "llvm", "prebuilt", archNDK(), "bin", pref+"-"+toolName) 283 } 284 285 // createNDKr19bWorkaroundTool creates a Windows wrapper script for clang or clang++. 286 // The scripts included in r19b are broken on Windows: https://github.com/android-ndk/ndk/issues/920. 287 // TODO: Remove this when r19c is out; the code inside is hacky and panicky. 288 func (tc *ndkToolchain) createNDKr19bWorkaroundTool(ndkRoot, toolName string) string { 289 toolCmd := filepath.Join(tmpdir, fmt.Sprintf("%s-%s.cmd", tc.arch, toolName)) 290 tool, err := os.Create(toolCmd) 291 if err != nil { 292 log.Fatal(err) 293 } 294 defer func() { 295 if err := tool.Close(); err != nil { 296 log.Fatal(err) 297 } 298 }() 299 tcBin := filepath.Join(ndkRoot, "toolchains", "llvm", "prebuilt", archNDK(), "bin") 300 // Adapted from the NDK cmd wrappers. 301 toolCmdContent := fmt.Sprintf(`@echo off 302 set _BIN_DIR=%s\ 303 %%_BIN_DIR%%%s.exe --target=%s -fno-addrsig %%*"`, tcBin, toolName, tc.clangPrefix) 304 if _, err = tool.Write([]byte(toolCmdContent)); err != nil { 305 log.Fatal(err) 306 } 307 return toolCmd 308 } 309 310 type ndkConfig map[string]ndkToolchain // map: GOOS->androidConfig. 311 312 func (nc ndkConfig) Toolchain(arch string) ndkToolchain { 313 tc, ok := nc[arch] 314 if !ok { 315 panic(`unsupported architecture: ` + arch) 316 } 317 return tc 318 } 319 320 var ndk = ndkConfig{ 321 "arm": { 322 arch: "arm", 323 abi: "armeabi-v7a", 324 toolPrefix: "arm-linux-androideabi", 325 clangPrefix: "armv7a-linux-androideabi16", 326 }, 327 "arm64": { 328 arch: "arm64", 329 abi: "arm64-v8a", 330 toolPrefix: "aarch64-linux-android", 331 clangPrefix: "aarch64-linux-android21", 332 }, 333 334 "386": { 335 arch: "x86", 336 abi: "x86", 337 toolPrefix: "i686-linux-android", 338 clangPrefix: "i686-linux-android16", 339 }, 340 "amd64": { 341 arch: "x86_64", 342 abi: "x86_64", 343 toolPrefix: "x86_64-linux-android", 344 clangPrefix: "x86_64-linux-android21", 345 }, 346 } 347 348 func xcodeAvailable() bool { 349 err := exec.Command("xcrun", "xcodebuild", "-version").Run() 350 return err == nil 351 }