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 }