github.com/F4RD1N/gomobile@v1.0.1/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 "io" 13 "os" 14 "os/exec" 15 "regexp" 16 "strings" 17 18 "golang.org/x/tools/go/packages" 19 ) 20 21 var tmpdir string 22 23 var cmdBuild = &command{ 24 run: runBuild, 25 Name: "build", 26 Usage: "[-target android|ios] [-o output] [-bundleid bundleID] [build flags] [package]", 27 Short: "compile android APK and iOS app", 28 Long: ` 29 Build compiles and encodes the app named by the import path. 30 31 The named package must define a main function. 32 33 The -target flag takes a target system name, either android (the 34 default) or ios. 35 36 For -target android, if an AndroidManifest.xml is defined in the 37 package directory, it is added to the APK output. Otherwise, a default 38 manifest is generated. By default, this builds a fat APK for all supported 39 instruction sets (arm, 386, amd64, arm64). A subset of instruction sets can 40 be selected by specifying target type with the architecture name. E.g. 41 -target=android/arm,android/386. 42 43 For -target ios, gomobile must be run on an OS X machine with Xcode 44 installed. 45 46 If the package directory contains an assets subdirectory, its contents 47 are copied into the output. 48 49 Flag -iosversion sets the minimal version of the iOS SDK to compile against. 50 The default version is 7.0. 51 52 Flag -androidapi sets the Android API version to compile against. 53 The default and minimum is 15. 54 55 The -bundleid flag is required for -target ios and sets the bundle ID to use 56 with the app. 57 58 The -o flag specifies the output file name. If not specified, the 59 output file name depends on the package built. 60 61 The -v flag provides verbose output, including the list of packages built. 62 63 The build flags -a, -i, -n, -x, -gcflags, -ldflags, -tags, -trimpath, and -work are 64 shared with the build command. For documentation, see 'go help build'. 65 `, 66 } 67 68 func runBuild(cmd *command) (err error) { 69 _, err = runBuildImpl(cmd) 70 return 71 } 72 73 // runBuildImpl builds a package for mobiles based on the given commands. 74 // runBuildImpl returns a built package information and an error if exists. 75 func runBuildImpl(cmd *command) (*packages.Package, error) { 76 cleanup, err := buildEnvInit() 77 if err != nil { 78 return nil, err 79 } 80 defer cleanup() 81 82 args := cmd.flag.Args() 83 84 targetOS, targetArchs, err := parseBuildTarget(buildTarget) 85 if err != nil { 86 return nil, fmt.Errorf(`invalid -target=%q: %v`, buildTarget, err) 87 } 88 89 var buildPath string 90 switch len(args) { 91 case 0: 92 buildPath = "." 93 case 1: 94 buildPath = args[0] 95 default: 96 cmd.usage() 97 os.Exit(1) 98 } 99 pkgs, err := packages.Load(packagesConfig(targetOS), buildPath) 100 if err != nil { 101 return nil, err 102 } 103 // len(pkgs) can be more than 1 e.g., when the specified path includes `...`. 104 if len(pkgs) != 1 { 105 cmd.usage() 106 os.Exit(1) 107 } 108 109 pkg := pkgs[0] 110 111 if pkg.Name != "main" && buildO != "" { 112 return nil, fmt.Errorf("cannot set -o when building non-main package") 113 } 114 115 var nmpkgs map[string]bool 116 switch targetOS { 117 case "android": 118 if pkg.Name != "main" { 119 for _, arch := range targetArchs { 120 if err := goBuild(pkg.PkgPath, androidEnv[arch]); err != nil { 121 return nil, err 122 } 123 } 124 return pkg, nil 125 } 126 nmpkgs, err = goAndroidBuild(pkg, targetArchs) 127 if err != nil { 128 return nil, err 129 } 130 case "darwin": 131 if !xcodeAvailable() { 132 return nil, fmt.Errorf("-target=ios requires XCode") 133 } 134 if pkg.Name != "main" { 135 for _, arch := range targetArchs { 136 if err := goBuild(pkg.PkgPath, darwinEnv[arch]); err != nil { 137 return nil, err 138 } 139 } 140 return pkg, nil 141 } 142 if buildBundleID == "" { 143 return nil, fmt.Errorf("-target=ios requires -bundleid set") 144 } 145 nmpkgs, err = goIOSBuild(pkg, buildBundleID, targetArchs) 146 if err != nil { 147 return nil, err 148 } 149 } 150 151 if !nmpkgs["github.com/F4RD1N/gomobile/app"] { 152 return nil, fmt.Errorf(`%s does not import "github.com/F4RD1N/gomobile/app"`, pkg.PkgPath) 153 } 154 155 return pkg, nil 156 } 157 158 var nmRE = regexp.MustCompile(`[0-9a-f]{8} t (?:.*/vendor/)?(golang.org/x.*/[^.]*)`) 159 160 func extractPkgs(nm string, path string) (map[string]bool, error) { 161 if buildN { 162 return map[string]bool{"github.com/F4RD1N/gomobile/app": true}, nil 163 } 164 r, w := io.Pipe() 165 cmd := exec.Command(nm, path) 166 cmd.Stdout = w 167 cmd.Stderr = os.Stderr 168 169 nmpkgs := make(map[string]bool) 170 errc := make(chan error, 1) 171 go func() { 172 s := bufio.NewScanner(r) 173 for s.Scan() { 174 if res := nmRE.FindStringSubmatch(s.Text()); res != nil { 175 nmpkgs[res[1]] = true 176 } 177 } 178 errc <- s.Err() 179 }() 180 181 err := cmd.Run() 182 w.Close() 183 if err != nil { 184 return nil, fmt.Errorf("%s %s: %v", nm, path, err) 185 } 186 if err := <-errc; err != nil { 187 return nil, fmt.Errorf("%s %s: %v", nm, path, err) 188 } 189 return nmpkgs, nil 190 } 191 192 var xout io.Writer = os.Stderr 193 194 func printcmd(format string, args ...interface{}) { 195 cmd := fmt.Sprintf(format+"\n", args...) 196 if tmpdir != "" { 197 cmd = strings.Replace(cmd, tmpdir, "$WORK", -1) 198 } 199 if androidHome := os.Getenv("ANDROID_HOME"); androidHome != "" { 200 cmd = strings.Replace(cmd, androidHome, "$ANDROID_HOME", -1) 201 } 202 if gomobilepath != "" { 203 cmd = strings.Replace(cmd, gomobilepath, "$GOMOBILE", -1) 204 } 205 if gopath := goEnv("GOPATH"); gopath != "" { 206 cmd = strings.Replace(cmd, gopath, "$GOPATH", -1) 207 } 208 if env := os.Getenv("HOMEPATH"); env != "" { 209 cmd = strings.Replace(cmd, env, "$HOMEPATH", -1) 210 } 211 fmt.Fprint(xout, cmd) 212 } 213 214 // "Build flags", used by multiple commands. 215 var ( 216 buildA bool // -a 217 buildI bool // -i 218 buildN bool // -n 219 buildV bool // -v 220 buildX bool // -x 221 buildO string // -o 222 buildGcflags string // -gcflags 223 buildLdflags string // -ldflags 224 buildTarget string // -target 225 buildTrimpath bool // -trimpath 226 buildWork bool // -work 227 buildBundleID string // -bundleid 228 buildIOSVersion string // -iosversion 229 buildAndroidAPI int // -androidapi 230 buildTags stringsFlag // -tags 231 ) 232 233 func addBuildFlags(cmd *command) { 234 cmd.flag.StringVar(&buildO, "o", "", "") 235 cmd.flag.StringVar(&buildGcflags, "gcflags", "", "") 236 cmd.flag.StringVar(&buildLdflags, "ldflags", "", "") 237 cmd.flag.StringVar(&buildTarget, "target", "android", "") 238 cmd.flag.StringVar(&buildBundleID, "bundleid", "", "") 239 cmd.flag.StringVar(&buildIOSVersion, "iosversion", "7.0", "") 240 cmd.flag.IntVar(&buildAndroidAPI, "androidapi", minAndroidAPI, "") 241 242 cmd.flag.BoolVar(&buildA, "a", false, "") 243 cmd.flag.BoolVar(&buildI, "i", false, "") 244 cmd.flag.BoolVar(&buildTrimpath, "trimpath", false, "") 245 cmd.flag.Var(&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 goBuildAt(at string, src string, env []string, args ...string) error { 280 return goCmdAt(at, "build", []string{src}, env, args...) 281 } 282 283 func goInstall(srcs []string, env []string, args ...string) error { 284 return goCmd("install", srcs, env, args...) 285 } 286 287 func goCmd(subcmd string, srcs []string, env []string, args ...string) error { 288 return goCmdAt("", subcmd, srcs, env, args...) 289 } 290 291 func goCmdAt(at string, subcmd string, srcs []string, env []string, args ...string) error { 292 cmd := exec.Command("go", subcmd) 293 tags := buildTags 294 targetOS, _, err := parseBuildTarget(buildTarget) 295 if err != nil { 296 return err 297 } 298 if targetOS == "darwin" { 299 tags = append(tags, "ios") 300 } 301 if len(tags) > 0 { 302 cmd.Args = append(cmd.Args, "-tags", strings.Join(tags, " ")) 303 } 304 if buildV { 305 cmd.Args = append(cmd.Args, "-v") 306 } 307 if subcmd != "install" && buildI { 308 cmd.Args = append(cmd.Args, "-i") 309 } 310 if buildX { 311 cmd.Args = append(cmd.Args, "-x") 312 } 313 if buildGcflags != "" { 314 cmd.Args = append(cmd.Args, "-gcflags", buildGcflags) 315 } 316 if buildLdflags != "" { 317 cmd.Args = append(cmd.Args, "-ldflags", buildLdflags) 318 } 319 if buildTrimpath { 320 cmd.Args = append(cmd.Args, "-trimpath") 321 } 322 if buildWork { 323 cmd.Args = append(cmd.Args, "-work") 324 } 325 cmd.Args = append(cmd.Args, args...) 326 cmd.Args = append(cmd.Args, srcs...) 327 cmd.Env = append([]string{}, env...) 328 cmd.Dir = at 329 return runCmd(cmd) 330 } 331 332 func goModTidyAt(at string, env []string) error { 333 cmd := exec.Command("go", "mod", "tidy") 334 if buildV { 335 cmd.Args = append(cmd.Args, "-v") 336 } 337 cmd.Env = append([]string{}, env...) 338 cmd.Dir = at 339 return runCmd(cmd) 340 } 341 342 func parseBuildTarget(buildTarget string) (os string, archs []string, _ error) { 343 if buildTarget == "" { 344 return "", nil, fmt.Errorf(`invalid target ""`) 345 } 346 347 all := false 348 archNames := []string{} 349 for i, p := range strings.Split(buildTarget, ",") { 350 osarch := strings.SplitN(p, "/", 2) // len(osarch) > 0 351 if osarch[0] != "android" && osarch[0] != "ios" { 352 return "", nil, fmt.Errorf(`unsupported os`) 353 } 354 355 if i == 0 { 356 os = osarch[0] 357 } 358 359 if os != osarch[0] { 360 return "", nil, fmt.Errorf(`cannot target different OSes`) 361 } 362 363 if len(osarch) == 1 { 364 all = true 365 } else { 366 archNames = append(archNames, osarch[1]) 367 } 368 } 369 370 // verify all archs are supported one while deduping. 371 isSupported := func(os, arch string) bool { 372 for _, a := range allArchs(os) { 373 if a == arch { 374 return true 375 } 376 } 377 return false 378 } 379 380 targetOS := os 381 if os == "ios" { 382 targetOS = "darwin" 383 } 384 385 seen := map[string]bool{} 386 for _, arch := range archNames { 387 if _, ok := seen[arch]; ok { 388 continue 389 } 390 if !isSupported(os, arch) { 391 return "", nil, fmt.Errorf(`unsupported arch: %q`, arch) 392 } 393 394 seen[arch] = true 395 archs = append(archs, arch) 396 } 397 398 if all { 399 return targetOS, allArchs(os), nil 400 } 401 return targetOS, archs, nil 402 }