github.com/SkycoinProject/gomobile@v0.0.0-20190312151609-d3739f865fa6/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 7.0. 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 gopath := goEnv("GOPATH"); gopath != "" { 211 cmd = strings.Replace(cmd, gopath, "$GOPATH", -1) 212 } 213 if env := os.Getenv("HOMEPATH"); env != "" { 214 cmd = strings.Replace(cmd, env, "$HOMEPATH", -1) 215 } 216 fmt.Fprint(xout, cmd) 217 } 218 219 // "Build flags", used by multiple commands. 220 var ( 221 buildA bool // -a 222 buildI bool // -i 223 buildN bool // -n 224 buildV bool // -v 225 buildX bool // -x 226 buildO string // -o 227 buildGcflags string // -gcflags 228 buildLdflags string // -ldflags 229 buildTarget string // -target 230 buildWork bool // -work 231 buildBundleID string // -bundleid 232 buildIOSVersion string // -iosversion 233 ) 234 235 func addBuildFlags(cmd *command) { 236 cmd.flag.StringVar(&buildO, "o", "", "") 237 cmd.flag.StringVar(&buildGcflags, "gcflags", "", "") 238 cmd.flag.StringVar(&buildLdflags, "ldflags", "", "") 239 cmd.flag.StringVar(&buildTarget, "target", "android", "") 240 cmd.flag.StringVar(&buildBundleID, "bundleid", "", "") 241 cmd.flag.StringVar(&buildIOSVersion, "iosversion", "7.0", "") 242 243 cmd.flag.BoolVar(&buildA, "a", false, "") 244 cmd.flag.BoolVar(&buildI, "i", false, "") 245 cmd.flag.Var((*stringsFlag)(&ctx.BuildTags), "tags", "") 246 } 247 248 func addBuildFlagsNVXWork(cmd *command) { 249 cmd.flag.BoolVar(&buildN, "n", false, "") 250 cmd.flag.BoolVar(&buildV, "v", false, "") 251 cmd.flag.BoolVar(&buildX, "x", false, "") 252 cmd.flag.BoolVar(&buildWork, "work", false, "") 253 } 254 255 type binInfo struct { 256 hasPkgApp bool 257 hasPkgAL bool 258 } 259 260 func init() { 261 addBuildFlags(cmdBuild) 262 addBuildFlagsNVXWork(cmdBuild) 263 264 addBuildFlags(cmdInstall) 265 addBuildFlagsNVXWork(cmdInstall) 266 267 addBuildFlagsNVXWork(cmdInit) 268 269 addBuildFlags(cmdBind) 270 addBuildFlagsNVXWork(cmdBind) 271 272 addBuildFlagsNVXWork(cmdClean) 273 } 274 275 func goBuild(src string, env []string, args ...string) error { 276 return goCmd("build", []string{src}, env, args...) 277 } 278 279 func goInstall(srcs []string, env []string, args ...string) error { 280 return goCmd("install", srcs, env, args...) 281 } 282 283 func goCmd(subcmd string, srcs []string, env []string, args ...string) error { 284 cmd := exec.Command( 285 "go", 286 subcmd, 287 ) 288 if len(ctx.BuildTags) > 0 { 289 cmd.Args = append(cmd.Args, "-tags", strings.Join(ctx.BuildTags, " ")) 290 } 291 if buildV { 292 cmd.Args = append(cmd.Args, "-v") 293 } 294 if subcmd != "install" && buildI { 295 cmd.Args = append(cmd.Args, "-i") 296 } 297 if buildX { 298 cmd.Args = append(cmd.Args, "-x") 299 } 300 if buildGcflags != "" { 301 cmd.Args = append(cmd.Args, "-gcflags", buildGcflags) 302 } 303 if buildLdflags != "" { 304 cmd.Args = append(cmd.Args, "-ldflags", buildLdflags) 305 } 306 if buildWork { 307 cmd.Args = append(cmd.Args, "-work") 308 } 309 cmd.Args = append(cmd.Args, args...) 310 cmd.Args = append(cmd.Args, srcs...) 311 cmd.Env = append([]string{}, env...) 312 return runCmd(cmd) 313 } 314 315 func parseBuildTarget(buildTarget string) (os string, archs []string, _ error) { 316 if buildTarget == "" { 317 return "", nil, fmt.Errorf(`invalid target ""`) 318 } 319 320 all := false 321 archNames := []string{} 322 for i, p := range strings.Split(buildTarget, ",") { 323 osarch := strings.SplitN(p, "/", 2) // len(osarch) > 0 324 if osarch[0] != "android" && osarch[0] != "ios" { 325 return "", nil, fmt.Errorf(`unsupported os`) 326 } 327 328 if i == 0 { 329 os = osarch[0] 330 } 331 332 if os != osarch[0] { 333 return "", nil, fmt.Errorf(`cannot target different OSes`) 334 } 335 336 if len(osarch) == 1 { 337 all = true 338 } else { 339 archNames = append(archNames, osarch[1]) 340 } 341 } 342 343 // verify all archs are supported one while deduping. 344 isSupported := func(arch string) bool { 345 for _, a := range allArchs { 346 if a == arch { 347 return true 348 } 349 } 350 return false 351 } 352 353 seen := map[string]bool{} 354 for _, arch := range archNames { 355 if _, ok := seen[arch]; ok { 356 continue 357 } 358 if !isSupported(arch) { 359 return "", nil, fmt.Errorf(`unsupported arch: %q`, arch) 360 } 361 362 seen[arch] = true 363 archs = append(archs, arch) 364 } 365 366 targetOS := os 367 if os == "ios" { 368 targetOS = "darwin" 369 } 370 if all { 371 return targetOS, allArchs, nil 372 } 373 return targetOS, archs, nil 374 }