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