github.com/gsquire/gb@v0.4.4-0.20161112235727-3982dc872064/cmd/gb/build.go (about)

     1  package main
     2  
     3  import (
     4  	"flag"
     5  	"os"
     6  	"path/filepath"
     7  	"runtime"
     8  
     9  	"github.com/constabulary/gb"
    10  	"github.com/constabulary/gb/cmd"
    11  	"github.com/constabulary/gb/internal/importer"
    12  	"github.com/pkg/errors"
    13  )
    14  
    15  func init() {
    16  	registerCommand(BuildCmd)
    17  }
    18  
    19  var (
    20  	// build flags
    21  
    22  	// should we build all packages in this project.
    23  	// defaults to true when build is invoked from the project root.
    24  	A bool
    25  
    26  	// should we perform a release build +release tag ?
    27  	// defaults to false, +debug.
    28  	R bool
    29  
    30  	// force rebuild of packages
    31  	F bool
    32  
    33  	// skip caching of packages
    34  	FF bool
    35  
    36  	// enable race runtime
    37  	race bool
    38  
    39  	ldflags, gcflags []string
    40  
    41  	P int // number of executors to run in parallel
    42  
    43  	dotfile string // path to dot output file
    44  
    45  	buildtags []string
    46  )
    47  
    48  func addBuildFlags(fs *flag.FlagSet) {
    49  	// TODO(dfc) this should accept a *gb.Context
    50  	fs.BoolVar(&A, "a", false, "build all packages in this project")
    51  	fs.BoolVar(&R, "r", false, "perform a release build")
    52  	fs.BoolVar(&F, "f", false, "rebuild up-to-date packages")
    53  	fs.BoolVar(&FF, "F", false, "do not cache built packages")
    54  	fs.BoolVar(&race, "race", false, "enable race detector")
    55  	fs.IntVar(&P, "P", runtime.NumCPU(), "number of parallel jobs")
    56  	fs.Var((*stringsFlag)(&ldflags), "ldflags", "flags passed to the linker")
    57  	fs.Var((*stringsFlag)(&gcflags), "gcflags", "flags passed to the compiler")
    58  	fs.StringVar(&dotfile, "dotfile", "", "path to dot output file")
    59  	fs.Var((*stringsFlag)(&buildtags), "tags", "")
    60  }
    61  
    62  var BuildCmd = &cmd.Command{
    63  	Name:      "build",
    64  	Short:     "build a package",
    65  	UsageLine: "build [build flags] [packages]",
    66  	Long: `
    67  Build compiles the packages named by the import paths, along with their
    68  dependencies.
    69  
    70  Flags:
    71  
    72  	-f
    73  		ignore cached packages if present, new packages built will overwrite
    74  		any cached packages. This effectively disables incremental
    75  		compilation.
    76  	-F
    77  		do not cache packages, cached packages will still be used for
    78  		incremental compilation. -f -F is advised to disable the package
    79  		caching system.
    80  	-P
    81  		The number of build jobs to run in parallel, including test execution.
    82  		By default this is the number of CPUs visible to gb.
    83  	-R
    84  		sets the base of the project root search path from the current working
    85  		directory to the value supplied. Effectively gb changes working
    86  		directory to this path before searching for the project root.
    87  	-dotfile
    88  		if provided, gb will output a dot formatted file of the build steps to
    89  		be performed.
    90  	-ldflags 'flag list'
    91  		arguments to pass on each linker invocation.
    92  	-gcflags 'arg list'
    93  		arguments to pass on each compile invocation.
    94          -race
    95                  enable data race detection.
    96                  Supported only on linux/amd64, freebsd/amd64, darwin/amd64 and windows/amd64.
    97  	-tags 'tag list'
    98  		additional build tags.
    99  
   100  The list flags accept a space-separated list of strings. To embed spaces in an
   101  element in the list, surround it with either single or double quotes.
   102  
   103  For more about where packages and binaries are installed, run 'gb help project'.
   104  `,
   105  	Run: func(ctx *gb.Context, args []string) error {
   106  		// TODO(dfc) run should take a *gb.Context not a *gb.Project
   107  		ctx.Force = F
   108  		ctx.Install = !FF
   109  
   110  		pkgs, err := resolveRootPackages(ctx, args...)
   111  		if err != nil {
   112  			return err
   113  		}
   114  
   115  		build, err := gb.BuildPackages(pkgs...)
   116  		if err != nil {
   117  			return err
   118  		}
   119  
   120  		if dotfile != "" {
   121  			f, err := os.Create(dotfile)
   122  			if err != nil {
   123  				return err
   124  			}
   125  			defer f.Close()
   126  			printActions(f, build)
   127  		}
   128  
   129  		startSigHandlers()
   130  		return gb.ExecuteConcurrent(build, P, interrupted)
   131  	},
   132  	AddFlags: addBuildFlags,
   133  }
   134  
   135  // Resolver resolves packages.
   136  type Resolver interface {
   137  	ResolvePackage(path string) (*gb.Package, error)
   138  }
   139  
   140  // resolveRootPackages resolves import paths into packages.
   141  // Only packages which exist inside $PROJECT/src are elegable to be
   142  // roots to build or test. Other import paths are discarded.
   143  func resolveRootPackages(r Resolver, paths ...string) ([]*gb.Package, error) {
   144  	var pkgs []*gb.Package
   145  	for _, path := range paths {
   146  		pkg, err := r.ResolvePackage(path)
   147  		if _, nogo := errors.Cause(err).(*importer.NoGoError); nogo {
   148  			// if the package is empty, to no Go files are in scope
   149  			// ignore it.
   150  			// TODO(dfc) ResolvePackage should return an empty *Package
   151  			// and build/test should ignore them.
   152  			continue
   153  		}
   154  		if err != nil {
   155  			return pkgs, errors.Wrapf(err, "failed to resolve import path %q", path)
   156  		}
   157  		if pkg.SrcRoot == filepath.Join(runtime.GOROOT(), "src") {
   158  			// skip package roots that are not part of this project.
   159  			// TODO(dfc) should gb return an error here?
   160  			continue
   161  		}
   162  		pkgs = append(pkgs, pkg)
   163  	}
   164  	return pkgs, nil
   165  }