github.com/shranet/mobile@v0.0.0-20200814083559-5702cdcd481b/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/shranet/mobile/app"] { 152 //fmt.Errorf("Skip error") 153 //return nil, fmt.Errorf(`%s does not import "github.com/shranet/mobile/app"`, pkg.PkgPath) 154 } 155 156 return pkg, nil 157 } 158 159 var nmRE = regexp.MustCompile(`[0-9a-f]{8} t (?:.*/vendor/)?(golang.org/x.*/[^.]*)`) 160 161 func extractPkgs(nm string, path string) (map[string]bool, error) { 162 if buildN { 163 return map[string]bool{"github.com/shranet/mobile/app": true}, nil 164 } 165 r, w := io.Pipe() 166 cmd := exec.Command(nm, path) 167 cmd.Stdout = w 168 cmd.Stderr = os.Stderr 169 170 nmpkgs := make(map[string]bool) 171 errc := make(chan error, 1) 172 go func() { 173 s := bufio.NewScanner(r) 174 for s.Scan() { 175 if res := nmRE.FindStringSubmatch(s.Text()); res != nil { 176 nmpkgs[res[1]] = true 177 } 178 } 179 errc <- s.Err() 180 }() 181 182 err := cmd.Run() 183 w.Close() 184 if err != nil { 185 return nil, fmt.Errorf("%s %s: %v", nm, path, err) 186 } 187 if err := <-errc; err != nil { 188 return nil, fmt.Errorf("%s %s: %v", nm, path, err) 189 } 190 return nmpkgs, nil 191 } 192 193 var xout io.Writer = os.Stderr 194 195 func printcmd(format string, args ...interface{}) { 196 cmd := fmt.Sprintf(format+"\n", args...) 197 if tmpdir != "" { 198 cmd = strings.Replace(cmd, tmpdir, "$WORK", -1) 199 } 200 if androidHome := os.Getenv("ANDROID_HOME"); androidHome != "" { 201 cmd = strings.Replace(cmd, androidHome, "$ANDROID_HOME", -1) 202 } 203 if gomobilepath != "" { 204 cmd = strings.Replace(cmd, gomobilepath, "$GOMOBILE", -1) 205 } 206 if gopath := goEnv("GOPATH"); gopath != "" { 207 cmd = strings.Replace(cmd, gopath, "$GOPATH", -1) 208 } 209 if env := os.Getenv("HOMEPATH"); env != "" { 210 cmd = strings.Replace(cmd, env, "$HOMEPATH", -1) 211 } 212 fmt.Fprint(xout, cmd) 213 } 214 215 // "Build flags", used by multiple commands. 216 var ( 217 buildA bool // -a 218 buildI bool // -i 219 buildN bool // -n 220 buildV bool // -v 221 buildX bool // -x 222 buildO string // -o 223 buildGcflags string // -gcflags 224 buildLdflags string // -ldflags 225 buildTarget string // -target 226 buildTrimpath bool // -trimpath 227 buildWork bool // -work 228 buildBundleID string // -bundleid 229 buildIOSVersion string // -iosversion 230 buildAndroidAPI int // -androidapi 231 buildTags stringsFlag // -tags 232 ) 233 234 func addBuildFlags(cmd *command) { 235 cmd.flag.StringVar(&buildO, "o", "", "") 236 cmd.flag.StringVar(&buildGcflags, "gcflags", "", "") 237 cmd.flag.StringVar(&buildLdflags, "ldflags", "", "") 238 cmd.flag.StringVar(&buildTarget, "target", "android", "") 239 cmd.flag.StringVar(&buildBundleID, "bundleid", "", "") 240 cmd.flag.StringVar(&buildIOSVersion, "iosversion", "7.0", "") 241 cmd.flag.IntVar(&buildAndroidAPI, "androidapi", minAndroidAPI, "") 242 243 cmd.flag.BoolVar(&buildA, "a", false, "") 244 cmd.flag.BoolVar(&buildI, "i", false, "") 245 cmd.flag.BoolVar(&buildTrimpath, "trimpath", false, "") 246 cmd.flag.Var(&buildTags, "tags", "") 247 } 248 249 func addBuildFlagsNVXWork(cmd *command) { 250 cmd.flag.BoolVar(&buildN, "n", false, "") 251 cmd.flag.BoolVar(&buildV, "v", false, "") 252 cmd.flag.BoolVar(&buildX, "x", false, "") 253 cmd.flag.BoolVar(&buildWork, "work", false, "") 254 } 255 256 type binInfo struct { 257 hasPkgApp bool 258 hasPkgAL bool 259 } 260 261 func init() { 262 addBuildFlags(cmdBuild) 263 addBuildFlagsNVXWork(cmdBuild) 264 265 addBuildFlags(cmdInstall) 266 addBuildFlagsNVXWork(cmdInstall) 267 268 addBuildFlagsNVXWork(cmdInit) 269 270 addBuildFlags(cmdBind) 271 addBuildFlagsNVXWork(cmdBind) 272 273 addBuildFlagsNVXWork(cmdClean) 274 } 275 276 func goBuild(src string, env []string, args ...string) error { 277 return goCmd("build", []string{src}, env, args...) 278 } 279 280 func goBuildAt(at string, src string, env []string, args ...string) error { 281 return goCmdAt(at, "build", []string{src}, env, args...) 282 } 283 284 func goInstall(srcs []string, env []string, args ...string) error { 285 return goCmd("install", srcs, env, args...) 286 } 287 288 func goCmd(subcmd string, srcs []string, env []string, args ...string) error { 289 return goCmdAt("", subcmd, srcs, env, args...) 290 } 291 292 func goCmdAt(at string, subcmd string, srcs []string, env []string, args ...string) error { 293 cmd := exec.Command("go", subcmd) 294 tags := buildTags 295 targetOS, _, err := parseBuildTarget(buildTarget) 296 if err != nil { 297 return err 298 } 299 if targetOS == "darwin" { 300 tags = append(tags, "ios") 301 } 302 if len(tags) > 0 { 303 cmd.Args = append(cmd.Args, "-tags", strings.Join(tags, " ")) 304 } 305 if buildV { 306 cmd.Args = append(cmd.Args, "-v") 307 } 308 if subcmd != "install" && buildI { 309 cmd.Args = append(cmd.Args, "-i") 310 } 311 if buildX { 312 cmd.Args = append(cmd.Args, "-x") 313 } 314 if buildGcflags != "" { 315 cmd.Args = append(cmd.Args, "-gcflags", buildGcflags) 316 } 317 if buildLdflags != "" { 318 cmd.Args = append(cmd.Args, "-ldflags", buildLdflags) 319 } 320 if buildTrimpath { 321 cmd.Args = append(cmd.Args, "-trimpath") 322 } 323 if buildWork { 324 cmd.Args = append(cmd.Args, "-work") 325 } 326 cmd.Args = append(cmd.Args, args...) 327 cmd.Args = append(cmd.Args, srcs...) 328 cmd.Env = append([]string{}, env...) 329 cmd.Dir = at 330 return runCmd(cmd) 331 } 332 333 func parseBuildTarget(buildTarget string) (os string, archs []string, _ error) { 334 if buildTarget == "" { 335 return "", nil, fmt.Errorf(`invalid target ""`) 336 } 337 338 all := false 339 archNames := []string{} 340 for i, p := range strings.Split(buildTarget, ",") { 341 osarch := strings.SplitN(p, "/", 2) // len(osarch) > 0 342 if osarch[0] != "android" && osarch[0] != "ios" { 343 return "", nil, fmt.Errorf(`unsupported os`) 344 } 345 346 if i == 0 { 347 os = osarch[0] 348 } 349 350 if os != osarch[0] { 351 return "", nil, fmt.Errorf(`cannot target different OSes`) 352 } 353 354 if len(osarch) == 1 { 355 all = true 356 } else { 357 archNames = append(archNames, osarch[1]) 358 } 359 } 360 361 // verify all archs are supported one while deduping. 362 isSupported := func(os, arch string) bool { 363 for _, a := range allArchs(os) { 364 if a == arch { 365 return true 366 } 367 } 368 return false 369 } 370 371 targetOS := os 372 if os == "ios" { 373 targetOS = "darwin" 374 } 375 376 seen := map[string]bool{} 377 for _, arch := range archNames { 378 if _, ok := seen[arch]; ok { 379 continue 380 } 381 if !isSupported(os, arch) { 382 return "", nil, fmt.Errorf(`unsupported arch: %q`, arch) 383 } 384 385 seen[arch] = true 386 archs = append(archs, arch) 387 } 388 389 if all { 390 return targetOS, allArchs(os), nil 391 } 392 return targetOS, archs, nil 393 }