| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448 |
- // Copyright 2015 The Go Authors. All rights reserved.
- // Use of this source code is governed by a BSD-style
- // license that can be found in the LICENSE file.
- //go:generate go run gendex.go -o dex.go
- package main
- import (
- "bufio"
- "errors"
- "fmt"
- "io"
- "os"
- "os/exec"
- "regexp"
- "strconv"
- "strings"
- "golang.org/x/mobile/internal/sdkpath"
- "golang.org/x/tools/go/packages"
- )
- var tmpdir string
- var cmdBuild = &command{
- run: runBuild,
- Name: "build",
- Usage: "[-target android|" + strings.Join(applePlatforms, "|") + "] [-o output] [-bundleid bundleID] [build flags] [package]",
- Short: "compile android APK and iOS app",
- Long: `
- Build compiles and encodes the app named by the import path.
- The named package must define a main function.
- The -target flag takes either android (the default), or one or more
- comma-delimited Apple platforms (` + strings.Join(applePlatforms, ", ") + `).
- For -target android, if an AndroidManifest.xml is defined in the
- package directory, it is added to the APK output. Otherwise, a default
- manifest is generated. By default, this builds a fat APK for all supported
- instruction sets (arm, 386, amd64, arm64). A subset of instruction sets can
- be selected by specifying target type with the architecture name. E.g.
- -target=android/arm,android/386.
- For Apple -target platforms, gomobile must be run on an OS X machine with
- Xcode installed.
- By default, -target ios will generate an XCFramework for both ios
- and iossimulator. Multiple Apple targets can be specified, creating a "fat"
- XCFramework with each slice. To generate a fat XCFramework that supports
- iOS, macOS, and macCatalyst for all supportec architectures (amd64 and arm64),
- specify -target ios,macos,maccatalyst. A subset of instruction sets can be
- selectged by specifying the platform with an architecture name. E.g.
- -target=ios/arm64,maccatalyst/arm64.
- If the package directory contains an assets subdirectory, its contents
- are copied into the output.
- Flag -iosversion sets the minimal version of the iOS SDK to compile against.
- The default version is 13.0.
- Flag -androidapi sets the Android API version to compile against.
- The default and minimum is 16.
- The -bundleid flag is required for -target ios and sets the bundle ID to use
- with the app.
- The -o flag specifies the output file name. If not specified, the
- output file name depends on the package built.
- The -v flag provides verbose output, including the list of packages built.
- The build flags -a, -i, -n, -x, -gcflags, -ldflags, -tags, -trimpath, and -work are
- shared with the build command. For documentation, see 'go help build'.
- `,
- }
- func runBuild(cmd *command) (err error) {
- _, err = runBuildImpl(cmd)
- return
- }
- // runBuildImpl builds a package for mobiles based on the given commands.
- // runBuildImpl returns a built package information and an error if exists.
- func runBuildImpl(cmd *command) (*packages.Package, error) {
- cleanup, err := buildEnvInit()
- if err != nil {
- return nil, err
- }
- defer cleanup()
- args := cmd.flag.Args()
- targets, err := parseBuildTarget(buildTarget)
- if err != nil {
- return nil, fmt.Errorf(`invalid -target=%q: %v`, buildTarget, err)
- }
- var buildPath string
- switch len(args) {
- case 0:
- buildPath = "."
- case 1:
- buildPath = args[0]
- default:
- cmd.usage()
- os.Exit(1)
- }
- // TODO(ydnar): this should work, unless build tags affect loading a single package.
- // Should we try to import packages with different build tags per platform?
- pkgs, err := packages.Load(packagesConfig(targets[0]), buildPath)
- if err != nil {
- return nil, err
- }
- // len(pkgs) can be more than 1 e.g., when the specified path includes `...`.
- if len(pkgs) != 1 {
- cmd.usage()
- os.Exit(1)
- }
- pkg := pkgs[0]
- if pkg.Name != "main" && buildO != "" {
- return nil, fmt.Errorf("cannot set -o when building non-main package")
- }
- var nmpkgs map[string]bool
- switch {
- case isAndroidPlatform(targets[0].platform):
- if pkg.Name != "main" {
- for _, t := range targets {
- if err := goBuild(pkg.PkgPath, androidEnv[t.arch]); err != nil {
- return nil, err
- }
- }
- return pkg, nil
- }
- nmpkgs, err = goAndroidBuild(pkg, targets)
- if err != nil {
- return nil, err
- }
- case isApplePlatform(targets[0].platform):
- if !xcodeAvailable() {
- return nil, fmt.Errorf("-target=%s requires XCode", buildTarget)
- }
- if pkg.Name != "main" {
- for _, t := range targets {
- // Catalyst support requires iOS 13+
- v, _ := strconv.ParseFloat(buildIOSVersion, 64)
- if t.platform == "maccatalyst" && v < 13.0 {
- return nil, errors.New("catalyst requires -iosversion=13 or higher")
- }
- if err := goBuild(pkg.PkgPath, appleEnv[t.String()]); err != nil {
- return nil, err
- }
- }
- return pkg, nil
- }
- if buildBundleID == "" {
- return nil, fmt.Errorf("-target=%s requires -bundleid set", buildTarget)
- }
- nmpkgs, err = goAppleBuild(pkg, buildBundleID, targets)
- if err != nil {
- return nil, err
- }
- }
- if !nmpkgs["golang.org/x/mobile/app"] {
- return nil, fmt.Errorf(`%s does not import "golang.org/x/mobile/app"`, pkg.PkgPath)
- }
- return pkg, nil
- }
- var nmRE = regexp.MustCompile(`[0-9a-f]{8} t _?(?:.*/vendor/)?(golang.org/x.*/[^.]*)`)
- func extractPkgs(nm string, path string) (map[string]bool, error) {
- if buildN {
- return map[string]bool{"golang.org/x/mobile/app": true}, nil
- }
- r, w := io.Pipe()
- cmd := exec.Command(nm, path)
- cmd.Stdout = w
- cmd.Stderr = os.Stderr
- nmpkgs := make(map[string]bool)
- errc := make(chan error, 1)
- go func() {
- s := bufio.NewScanner(r)
- for s.Scan() {
- if res := nmRE.FindStringSubmatch(s.Text()); res != nil {
- nmpkgs[res[1]] = true
- }
- }
- errc <- s.Err()
- }()
- err := cmd.Run()
- w.Close()
- if err != nil {
- return nil, fmt.Errorf("%s %s: %v", nm, path, err)
- }
- if err := <-errc; err != nil {
- return nil, fmt.Errorf("%s %s: %v", nm, path, err)
- }
- return nmpkgs, nil
- }
- var xout io.Writer = os.Stderr
- func printcmd(format string, args ...interface{}) {
- cmd := fmt.Sprintf(format+"\n", args...)
- if tmpdir != "" {
- cmd = strings.Replace(cmd, tmpdir, "$WORK", -1)
- }
- if androidHome, err := sdkpath.AndroidHome(); err == nil {
- cmd = strings.Replace(cmd, androidHome, "$ANDROID_HOME", -1)
- }
- if gomobilepath != "" {
- cmd = strings.Replace(cmd, gomobilepath, "$GOMOBILE", -1)
- }
- if gopath := goEnv("GOPATH"); gopath != "" {
- cmd = strings.Replace(cmd, gopath, "$GOPATH", -1)
- }
- if env := os.Getenv("HOMEPATH"); env != "" {
- cmd = strings.Replace(cmd, env, "$HOMEPATH", -1)
- }
- fmt.Fprint(xout, cmd)
- }
- // "Build flags", used by multiple commands.
- var (
- buildA bool // -a
- buildI bool // -i
- buildN bool // -n
- buildV bool // -v
- buildX bool // -x
- buildO string // -o
- buildGcflags string // -gcflags
- buildLdflags string // -ldflags
- buildTarget string // -target
- buildTrimpath bool // -trimpath
- buildWork bool // -work
- buildBundleID string // -bundleid
- buildIOSVersion string // -iosversion
- buildAndroidAPI int // -androidapi
- buildTags stringsFlag // -tags
- )
- func addBuildFlags(cmd *command) {
- cmd.flag.StringVar(&buildO, "o", "", "")
- cmd.flag.StringVar(&buildGcflags, "gcflags", "", "")
- cmd.flag.StringVar(&buildLdflags, "ldflags", "", "")
- cmd.flag.StringVar(&buildTarget, "target", "android", "")
- cmd.flag.StringVar(&buildBundleID, "bundleid", "", "")
- cmd.flag.StringVar(&buildIOSVersion, "iosversion", "13.0", "")
- cmd.flag.IntVar(&buildAndroidAPI, "androidapi", minAndroidAPI, "")
- cmd.flag.BoolVar(&buildA, "a", false, "")
- cmd.flag.BoolVar(&buildI, "i", false, "")
- cmd.flag.BoolVar(&buildTrimpath, "trimpath", false, "")
- cmd.flag.Var(&buildTags, "tags", "")
- }
- func addBuildFlagsNVXWork(cmd *command) {
- cmd.flag.BoolVar(&buildN, "n", false, "")
- cmd.flag.BoolVar(&buildV, "v", false, "")
- cmd.flag.BoolVar(&buildX, "x", false, "")
- cmd.flag.BoolVar(&buildWork, "work", false, "")
- }
- func init() {
- addBuildFlags(cmdBuild)
- addBuildFlagsNVXWork(cmdBuild)
- addBuildFlags(cmdInstall)
- addBuildFlagsNVXWork(cmdInstall)
- addBuildFlagsNVXWork(cmdInit)
- addBuildFlags(cmdBind)
- addBuildFlagsNVXWork(cmdBind)
- addBuildFlagsNVXWork(cmdClean)
- }
- func goBuild(src string, env []string, args ...string) error {
- return goCmd("build", []string{src}, env, args...)
- }
- func goBuildAt(at string, src string, env []string, args ...string) error {
- return goCmdAt(at, "build", []string{src}, env, args...)
- }
- func goInstall(srcs []string, env []string, args ...string) error {
- return goCmd("install", srcs, env, args...)
- }
- func goCmd(subcmd string, srcs []string, env []string, args ...string) error {
- return goCmdAt("", subcmd, srcs, env, args...)
- }
- func goCmdAt(at string, subcmd string, srcs []string, env []string, args ...string) error {
- cmd := exec.Command("go", subcmd)
- tags := buildTags
- if len(tags) > 0 {
- cmd.Args = append(cmd.Args, "-tags", strings.Join(tags, ","))
- }
- if buildV {
- cmd.Args = append(cmd.Args, "-v")
- }
- if subcmd != "install" && buildI {
- cmd.Args = append(cmd.Args, "-i")
- }
- if buildX {
- cmd.Args = append(cmd.Args, "-x")
- }
- if buildGcflags != "" {
- cmd.Args = append(cmd.Args, "-gcflags", buildGcflags)
- }
- if buildLdflags != "" {
- cmd.Args = append(cmd.Args, "-ldflags", buildLdflags)
- }
- if buildTrimpath {
- cmd.Args = append(cmd.Args, "-trimpath")
- }
- if buildWork {
- cmd.Args = append(cmd.Args, "-work")
- }
- cmd.Args = append(cmd.Args, args...)
- cmd.Args = append(cmd.Args, srcs...)
- // Specify GOMODCACHE explicitly. The default cache path is GOPATH[0]/pkg/mod,
- // but the path varies when GOPATH is specified at env, which results in cold cache.
- if gmc, err := goModCachePath(); err == nil {
- env = append([]string{"GOMODCACHE=" + gmc}, env...)
- } else {
- env = append([]string{}, env...)
- }
- cmd.Env = env
- cmd.Dir = at
- return runCmd(cmd)
- }
- func goModTidyAt(at string, env []string) error {
- cmd := exec.Command("go", "mod", "tidy")
- if buildV {
- cmd.Args = append(cmd.Args, "-v")
- }
- // Specify GOMODCACHE explicitly. The default cache path is GOPATH[0]/pkg/mod,
- // but the path varies when GOPATH is specified at env, which results in cold cache.
- if gmc, err := goModCachePath(); err == nil {
- env = append([]string{"GOMODCACHE=" + gmc}, env...)
- } else {
- env = append([]string{}, env...)
- }
- cmd.Env = env
- cmd.Dir = at
- return runCmd(cmd)
- }
- // parseBuildTarget parses buildTarget into 1 or more platforms and architectures.
- // Returns an error if buildTarget contains invalid input.
- // Example valid target strings:
- //
- // android
- // android/arm64,android/386,android/amd64
- // ios,iossimulator,maccatalyst
- // macos/amd64
- func parseBuildTarget(buildTarget string) ([]targetInfo, error) {
- if buildTarget == "" {
- return nil, fmt.Errorf(`invalid target ""`)
- }
- targets := []targetInfo{}
- targetsAdded := make(map[targetInfo]bool)
- addTarget := func(platform, arch string) {
- t := targetInfo{platform, arch}
- if targetsAdded[t] {
- return
- }
- targets = append(targets, t)
- targetsAdded[t] = true
- }
- addPlatform := func(platform string) {
- for _, arch := range platformArchs(platform) {
- addTarget(platform, arch)
- }
- }
- var isAndroid, isApple bool
- for _, target := range strings.Split(buildTarget, ",") {
- tuple := strings.SplitN(target, "/", 2)
- platform := tuple[0]
- hasArch := len(tuple) == 2
- if isAndroidPlatform(platform) {
- isAndroid = true
- } else if isApplePlatform(platform) {
- isApple = true
- } else {
- return nil, fmt.Errorf("unsupported platform: %q", platform)
- }
- if isAndroid && isApple {
- return nil, fmt.Errorf(`cannot mix android and Apple platforms`)
- }
- if hasArch {
- arch := tuple[1]
- if !isSupportedArch(platform, arch) {
- return nil, fmt.Errorf(`unsupported platform/arch: %q`, target)
- }
- addTarget(platform, arch)
- } else {
- addPlatform(platform)
- }
- }
- // Special case to build iossimulator if -target=ios
- if buildTarget == "ios" {
- addPlatform("iossimulator")
- }
- return targets, nil
- }
- type targetInfo struct {
- platform string
- arch string
- }
- func (t targetInfo) String() string {
- return t.platform + "/" + t.arch
- }
- func goModCachePath() (string, error) {
- out, err := exec.Command("go", "env", "GOMODCACHE").Output()
- if err != nil {
- return "", err
- }
- return strings.TrimSpace(string(out)), nil
- }
|