github.com/acrespo/mobile@v0.0.0-20190107162257-dc0771356504/cmd/gomobile/build.go (about) 1 // Copyright 2015 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 //go:generate go run gendex.go -o dex.go 6 7 package main 8 9 import ( 10 "bufio" 11 "fmt" 12 "go/build" 13 "io" 14 "os" 15 "os/exec" 16 "regexp" 17 "strings" 18 ) 19 20 var ctx = build.Default 21 var pkg *build.Package // TODO(crawshaw): remove global pkg variable 22 var tmpdir string 23 24 var cmdBuild = &command{ 25 run: runBuild, 26 Name: "build", 27 Usage: "[-target android|ios] [-o output] [-bundleid bundleID] [build flags] [package]", 28 Short: "compile android APK and iOS app", 29 Long: ` 30 Build compiles and encodes the app named by the import path. 31 32 The named package must define a main function. 33 34 The -target flag takes a target system name, either android (the 35 default) or ios. 36 37 For -target android, if an AndroidManifest.xml is defined in the 38 package directory, it is added to the APK output. Otherwise, a default 39 manifest is generated. By default, this builds a fat APK for all supported 40 instruction sets (arm, 386, amd64, arm64). A subset of instruction sets can 41 be selected by specifying target type with the architecture name. E.g. 42 -target=android/arm,android/386. 43 44 For -target ios, gomobile must be run on an OS X machine with Xcode 45 installed. 46 47 If the package directory contains an assets subdirectory, its contents 48 are copied into the output. 49 50 Flag -iosversion sets the minimal version of the iOS SDK to compile against. 51 The default version is 6.1. 52 53 The -bundleid flag is required for -target ios and sets the bundle ID to use 54 with the app. 55 56 The -o flag specifies the output file name. If not specified, the 57 output file name depends on the package built. 58 59 The -v flag provides verbose output, including the list of packages built. 60 61 The build flags -a, -i, -n, -x, -gcflags, -ldflags, -tags, and -work are 62 shared with the build command. For documentation, see 'go help build'. 63 `, 64 } 65 66 func runBuild(cmd *command) (err error) { 67 cleanup, err := buildEnvInit() 68 if err != nil { 69 return err 70 } 71 defer cleanup() 72 73 args := cmd.flag.Args() 74 75 targetOS, targetArchs, err := parseBuildTarget(buildTarget) 76 if err != nil { 77 return fmt.Errorf(`invalid -target=%q: %v`, buildTarget, err) 78 } 79 80 oldCtx := ctx 81 defer func() { 82 ctx = oldCtx 83 }() 84 ctx.GOARCH = targetArchs[0] 85 ctx.GOOS = targetOS 86 87 if ctx.GOOS == "darwin" { 88 ctx.BuildTags = append(ctx.BuildTags, "ios") 89 } 90 91 switch len(args) { 92 case 0: 93 pkg, err = ctx.ImportDir(cwd, build.ImportComment) 94 case 1: 95 pkg, err = ctx.Import(args[0], cwd, build.ImportComment) 96 default: 97 cmd.usage() 98 os.Exit(1) 99 } 100 if err != nil { 101 return err 102 } 103 104 if pkg.Name != "main" && buildO != "" { 105 return fmt.Errorf("cannot set -o when building non-main package") 106 } 107 108 var nmpkgs map[string]bool 109 switch targetOS { 110 case "android": 111 if pkg.Name != "main" { 112 for _, arch := range targetArchs { 113 env := androidEnv[arch] 114 if err := goBuild(pkg.ImportPath, env); err != nil { 115 return err 116 } 117 } 118 return nil 119 } 120 nmpkgs, err = goAndroidBuild(pkg, targetArchs) 121 if err != nil { 122 return err 123 } 124 case "darwin": 125 if !xcodeAvailable() { 126 return fmt.Errorf("-target=ios requires XCode") 127 } 128 if pkg.Name != "main" { 129 for _, arch := range targetArchs { 130 env := darwinEnv[arch] 131 if err := goBuild(pkg.ImportPath, env); err != nil { 132 return err 133 } 134 } 135 return nil 136 } 137 if buildBundleID == "" { 138 return fmt.Errorf("-target=ios requires -bundleid set") 139 } 140 nmpkgs, err = goIOSBuild(pkg, buildBundleID, targetArchs) 141 if err != nil { 142 return err 143 } 144 } 145 146 if !nmpkgs["golang.org/x/mobile/app"] { 147 return fmt.Errorf(`%s does not import "golang.org/x/mobile/app"`, pkg.ImportPath) 148 } 149 150 return nil 151 } 152 153 var nmRE = regexp.MustCompile(`[0-9a-f]{8} t (?:.*/vendor/)?(golang.org/x.*/[^.]*)`) 154 155 func extractPkgs(nm string, path string) (map[string]bool, error) { 156 if buildN { 157 return map[string]bool{"golang.org/x/mobile/app": true}, nil 158 } 159 r, w := io.Pipe() 160 cmd := exec.Command(nm, path) 161 cmd.Stdout = w 162 cmd.Stderr = os.Stderr 163 164 nmpkgs := make(map[string]bool) 165 errc := make(chan error, 1) 166 go func() { 167 s := bufio.NewScanner(r) 168 for s.Scan() { 169 if res := nmRE.FindStringSubmatch(s.Text()); res != nil { 170 nmpkgs[res[1]] = true 171 } 172 } 173 errc <- s.Err() 174 }() 175 176 err := cmd.Run() 177 w.Close() 178 if err != nil { 179 return nil, fmt.Errorf("%s %s: %v", nm, path, err) 180 } 181 if err := <-errc; err != nil { 182 return nil, fmt.Errorf("%s %s: %v", nm, path, err) 183 } 184 return nmpkgs, nil 185 } 186 187 func importsApp(pkg *build.Package) error { 188 // Building a program, make sure it is appropriate for mobile. 189 for _, path := range pkg.Imports { 190 if path == "golang.org/x/mobile/app" { 191 return nil 192 } 193 } 194 return fmt.Errorf(`%s does not import "golang.org/x/mobile/app"`, pkg.ImportPath) 195 } 196 197 var xout io.Writer = os.Stderr 198 199 func printcmd(format string, args ...interface{}) { 200 cmd := fmt.Sprintf(format+"\n", args...) 201 if tmpdir != "" { 202 cmd = strings.Replace(cmd, tmpdir, "$WORK", -1) 203 } 204 if androidHome := os.Getenv("ANDROID_HOME"); androidHome != "" { 205 cmd = strings.Replace(cmd, androidHome, "$ANDROID_HOME", -1) 206 } 207 if gomobilepath != "" { 208 cmd = strings.Replace(cmd, gomobilepath, "$GOMOBILE", -1) 209 } 210 if goroot := goEnv("GOROOT"); goroot != "" { 211 cmd = strings.Replace(cmd, goroot, "$GOROOT", -1) 212 } 213 if gopath := goEnv("GOPATH"); gopath != "" { 214 cmd = strings.Replace(cmd, gopath, "$GOPATH", -1) 215 } 216 if env := os.Getenv("HOME"); env != "" { 217 cmd = strings.Replace(cmd, env, "$HOME", -1) 218 } 219 if env := os.Getenv("HOMEPATH"); env != "" { 220 cmd = strings.Replace(cmd, env, "$HOMEPATH", -1) 221 } 222 fmt.Fprint(xout, cmd) 223 } 224 225 // "Build flags", used by multiple commands. 226 var ( 227 buildA bool // -a 228 buildI bool // -i 229 buildN bool // -n 230 buildV bool // -v 231 buildX bool // -x 232 buildO string // -o 233 buildGcflags string // -gcflags 234 buildLdflags string // -ldflags 235 buildTarget string // -target 236 buildWork bool // -work 237 buildBundleID string // -bundleid 238 buildIOSVersion string // -iosversion 239 ) 240 241 func addBuildFlags(cmd *command) { 242 cmd.flag.StringVar(&buildO, "o", "", "") 243 cmd.flag.StringVar(&buildGcflags, "gcflags", "", "") 244 cmd.flag.StringVar(&buildLdflags, "ldflags", "", "") 245 cmd.flag.StringVar(&buildTarget, "target", "android", "") 246 cmd.flag.StringVar(&buildBundleID, "bundleid", "", "") 247 cmd.flag.StringVar(&buildIOSVersion, "iosversion", "6.1", "") 248 249 cmd.flag.BoolVar(&buildA, "a", false, "") 250 cmd.flag.BoolVar(&buildI, "i", false, "") 251 cmd.flag.Var((*stringsFlag)(&ctx.BuildTags), "tags", "") 252 } 253 254 func addBuildFlagsNVXWork(cmd *command) { 255 cmd.flag.BoolVar(&buildN, "n", false, "") 256 cmd.flag.BoolVar(&buildV, "v", false, "") 257 cmd.flag.BoolVar(&buildX, "x", false, "") 258 cmd.flag.BoolVar(&buildWork, "work", false, "") 259 } 260 261 type binInfo struct { 262 hasPkgApp bool 263 hasPkgAL bool 264 } 265 266 func init() { 267 addBuildFlags(cmdBuild) 268 addBuildFlagsNVXWork(cmdBuild) 269 270 addBuildFlags(cmdInstall) 271 addBuildFlagsNVXWork(cmdInstall) 272 273 addBuildFlagsNVXWork(cmdInit) 274 275 addBuildFlags(cmdBind) 276 addBuildFlagsNVXWork(cmdBind) 277 278 addBuildFlagsNVXWork(cmdClean) 279 } 280 281 func goBuild(src string, env []string, args ...string) error { 282 return goCmd("build", []string{src}, env, args...) 283 } 284 285 func goInstall(srcs []string, env []string, args ...string) error { 286 return goCmd("install", srcs, env, args...) 287 } 288 289 func goCmd(subcmd string, srcs []string, env []string, args ...string) error { 290 cmd := exec.Command( 291 "go", 292 subcmd, 293 ) 294 if len(ctx.BuildTags) > 0 { 295 cmd.Args = append(cmd.Args, "-tags", strings.Join(ctx.BuildTags, " ")) 296 } 297 if buildV { 298 cmd.Args = append(cmd.Args, "-v") 299 } 300 if subcmd != "install" && buildI { 301 cmd.Args = append(cmd.Args, "-i") 302 } 303 if buildX { 304 cmd.Args = append(cmd.Args, "-x") 305 } 306 if buildGcflags != "" { 307 cmd.Args = append(cmd.Args, "-gcflags", buildGcflags) 308 } 309 if buildLdflags != "" { 310 cmd.Args = append(cmd.Args, "-ldflags", buildLdflags) 311 } 312 if buildWork { 313 cmd.Args = append(cmd.Args, "-work") 314 } 315 cmd.Args = append(cmd.Args, args...) 316 cmd.Args = append(cmd.Args, srcs...) 317 cmd.Env = append([]string{}, env...) 318 return runCmd(cmd) 319 } 320 321 func parseBuildTarget(buildTarget string) (os string, archs []string, _ error) { 322 if buildTarget == "" { 323 return "", nil, fmt.Errorf(`invalid target ""`) 324 } 325 326 all := false 327 archNames := []string{} 328 for i, p := range strings.Split(buildTarget, ",") { 329 osarch := strings.SplitN(p, "/", 2) // len(osarch) > 0 330 if osarch[0] != "android" && osarch[0] != "ios" { 331 return "", nil, fmt.Errorf(`unsupported os`) 332 } 333 334 if i == 0 { 335 os = osarch[0] 336 } 337 338 if os != osarch[0] { 339 return "", nil, fmt.Errorf(`cannot target different OSes`) 340 } 341 342 if len(osarch) == 1 { 343 all = true 344 } else { 345 archNames = append(archNames, osarch[1]) 346 } 347 } 348 349 // verify all archs are supported one while deduping. 350 isSupported := func(arch string) bool { 351 for _, a := range allArchs { 352 if a == arch { 353 return true 354 } 355 } 356 return false 357 } 358 359 seen := map[string]bool{} 360 for _, arch := range archNames { 361 if _, ok := seen[arch]; ok { 362 continue 363 } 364 if !isSupported(arch) { 365 return "", nil, fmt.Errorf(`unsupported arch: %q`, arch) 366 } 367 368 seen[arch] = true 369 archs = append(archs, arch) 370 } 371 372 targetOS := os 373 if os == "ios" { 374 targetOS = "darwin" 375 } 376 if all { 377 return targetOS, allArchs, nil 378 } 379 return targetOS, archs, nil 380 }