github.com/kekek/gb@v0.4.5-0.20170222120241-d4ba64b0b297/build.go (about)

     1  package gb
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"path/filepath"
     7  	"runtime"
     8  	"strings"
     9  	"time"
    10  
    11  	"github.com/constabulary/gb/internal/debug"
    12  	"github.com/constabulary/gb/internal/fileutils"
    13  )
    14  
    15  // Build builds each of pkgs in succession. If pkg is a command, then the results of build include
    16  // linking the final binary into pkg.Context.Bindir().
    17  func Build(pkgs ...*Package) error {
    18  	build, err := BuildPackages(pkgs...)
    19  	if err != nil {
    20  		return err
    21  	}
    22  	return ExecuteConcurrent(build, runtime.NumCPU(), nil)
    23  }
    24  
    25  // BuildPackages produces a tree of *Actions that can be executed to build
    26  // a *Package.
    27  // BuildPackages walks the tree of *Packages and returns a corresponding
    28  // tree of *Actions representing the steps required to build *Package
    29  // and any of its dependencies
    30  func BuildPackages(pkgs ...*Package) (*Action, error) {
    31  	if len(pkgs) < 1 {
    32  		return nil, errors.New("no packages supplied")
    33  	}
    34  
    35  	targets := make(map[string]*Action) // maps package importpath to build action
    36  
    37  	names := func(pkgs []*Package) []string {
    38  		var names []string
    39  		for _, pkg := range pkgs {
    40  			names = append(names, pkg.ImportPath)
    41  		}
    42  		return names
    43  	}
    44  
    45  	// create top level build action to unify all packages
    46  	t0 := time.Now()
    47  	build := Action{
    48  		Name: fmt.Sprintf("build: %s", strings.Join(names(pkgs), ",")),
    49  		Run: func() error {
    50  			debug.Debugf("build duration: %v %v", time.Since(t0), pkgs[0].Statistics.String())
    51  			return nil
    52  		},
    53  	}
    54  
    55  	for _, pkg := range pkgs {
    56  		if len(pkg.GoFiles)+len(pkg.CgoFiles) == 0 {
    57  			debug.Debugf("skipping %v: no go files", pkg.ImportPath)
    58  			continue
    59  		}
    60  		a, err := BuildPackage(targets, pkg)
    61  		if err != nil {
    62  			return nil, err
    63  		}
    64  		if a == nil {
    65  			// nothing to do
    66  			continue
    67  		}
    68  		build.Deps = append(build.Deps, a)
    69  	}
    70  	return &build, nil
    71  }
    72  
    73  // BuildPackage returns an Action representing the steps required to
    74  // build this package.
    75  func BuildPackage(targets map[string]*Action, pkg *Package) (*Action, error) {
    76  
    77  	// if this action is already present in the map, return it
    78  	// rather than creating a new action.
    79  	if a, ok := targets[pkg.ImportPath]; ok {
    80  		return a, nil
    81  	}
    82  
    83  	// step 0. are we stale ?
    84  	// if this package is not stale, then by definition none of its
    85  	// dependencies are stale, so ignore this whole tree.
    86  	if pkg.NotStale {
    87  		return nil, nil
    88  	}
    89  
    90  	// step 1. build dependencies
    91  	deps, err := BuildDependencies(targets, pkg)
    92  	if err != nil {
    93  		return nil, err
    94  	}
    95  
    96  	// step 2. build this package
    97  	build, err := Compile(pkg, deps...)
    98  	if err != nil {
    99  		return nil, err
   100  	}
   101  
   102  	if build == nil {
   103  		panic("build action was nil") // shouldn't happen
   104  	}
   105  
   106  	// record the final action as the action that represents
   107  	// building this package.
   108  	targets[pkg.ImportPath] = build
   109  	return build, nil
   110  }
   111  
   112  // Compile returns an Action representing the steps required to compile this package.
   113  func Compile(pkg *Package, deps ...*Action) (*Action, error) {
   114  	var gofiles []string
   115  	gofiles = append(gofiles, pkg.GoFiles...)
   116  
   117  	// step 1. are there any .c files that we have to run cgo on ?
   118  	var ofiles []string // additional ofiles to pack
   119  	if len(pkg.CgoFiles) > 0 {
   120  		cgoACTION, cgoOFILES, cgoGOFILES, err := cgo(pkg)
   121  		if err != nil {
   122  			return nil, err
   123  		}
   124  
   125  		gofiles = append(gofiles, cgoGOFILES...)
   126  		ofiles = append(ofiles, cgoOFILES...)
   127  		deps = append(deps, cgoACTION)
   128  	}
   129  
   130  	if len(gofiles) == 0 {
   131  		return nil, fmt.Errorf("compile %q: no go files supplied", pkg.ImportPath)
   132  	}
   133  
   134  	// step 2. compile all the go files for this package, including pkg.CgoFiles
   135  	compile := Action{
   136  		Name: fmt.Sprintf("compile: %s", pkg.ImportPath),
   137  		Deps: deps,
   138  		Run:  func() error { return gc(pkg, gofiles) },
   139  	}
   140  
   141  	// step 3. are there any .s files to assemble.
   142  	var assemble []*Action
   143  	for _, sfile := range pkg.SFiles {
   144  		sfile := sfile
   145  		ofile := filepath.Join(pkg.Context.Workdir(), pkg.ImportPath, stripext(sfile)+".6")
   146  		assemble = append(assemble, &Action{
   147  			Name: fmt.Sprintf("asm: %s/%s", pkg.ImportPath, sfile),
   148  			Run: func() error {
   149  				t0 := time.Now()
   150  				err := pkg.tc.Asm(pkg, ofile, filepath.Join(pkg.Dir, sfile))
   151  				pkg.Record("asm", time.Since(t0))
   152  				return err
   153  			},
   154  			// asm depends on compile because compile will generate the local go_asm.h
   155  			Deps: []*Action{&compile},
   156  		})
   157  		ofiles = append(ofiles, ofile)
   158  	}
   159  
   160  	// step 4. add system object files.
   161  	for _, syso := range pkg.SysoFiles {
   162  		ofiles = append(ofiles, filepath.Join(pkg.Dir, syso))
   163  	}
   164  
   165  	build := &compile
   166  
   167  	// Do we need to pack ? Yes, replace build action with pack.
   168  	if len(ofiles) > 0 {
   169  		pack := Action{
   170  			Name: fmt.Sprintf("pack: %s", pkg.ImportPath),
   171  			Deps: []*Action{
   172  				&compile,
   173  			},
   174  			Run: func() error {
   175  				// collect .o files, ofiles always starts with the gc compiled object.
   176  				// TODO(dfc) objfile(pkg) should already be at the top of this set
   177  				ofiles = append(
   178  					[]string{pkg.objfile()},
   179  					ofiles...,
   180  				)
   181  
   182  				// pack
   183  				t0 := time.Now()
   184  				err := pkg.tc.Pack(pkg, ofiles...)
   185  				pkg.Record("pack", time.Since(t0))
   186  				return err
   187  			},
   188  		}
   189  		pack.Deps = append(pack.Deps, assemble...)
   190  		build = &pack
   191  	}
   192  
   193  	// should this package be cached
   194  	if pkg.Install && !pkg.TestScope {
   195  		build = &Action{
   196  			Name: fmt.Sprintf("install: %s", pkg.ImportPath),
   197  			Deps: []*Action{build},
   198  			Run:  func() error { return fileutils.Copyfile(pkg.installpath(), pkg.objfile()) },
   199  		}
   200  	}
   201  
   202  	// if this is a main package, add a link stage
   203  	if pkg.Main {
   204  		build = &Action{
   205  			Name: fmt.Sprintf("link: %s", pkg.ImportPath),
   206  			Deps: []*Action{build},
   207  			Run:  func() error { return pkg.link() },
   208  		}
   209  	}
   210  	if !pkg.TestScope {
   211  		// if this package is not compiled in test scope, then
   212  		// log the name of the package when complete.
   213  		build.Run = logInfoFn(build.Run, pkg.ImportPath)
   214  	}
   215  	return build, nil
   216  }
   217  
   218  func logInfoFn(fn func() error, s string) func() error {
   219  	return func() error {
   220  		err := fn()
   221  		fmt.Println(s)
   222  		return err
   223  	}
   224  }
   225  
   226  // BuildDependencies returns a slice of Actions representing the steps required
   227  // to build all dependant packages of this package.
   228  func BuildDependencies(targets map[string]*Action, pkg *Package) ([]*Action, error) {
   229  	var deps []*Action
   230  	pkgs := pkg.Imports
   231  
   232  	var extra []string
   233  	switch {
   234  	case pkg.Main:
   235  		// all binaries depend on runtime, even if they do not
   236  		// explicitly import it.
   237  		extra = append(extra, "runtime")
   238  		if pkg.race {
   239  			// race binaries have extra implicit depdendenceis.
   240  			extra = append(extra, "runtime/race")
   241  		}
   242  
   243  	case len(pkg.CgoFiles) > 0 && pkg.ImportPath != "runtime/cgo":
   244  		// anything that uses cgo has a dependency on runtime/cgo which is
   245  		// only visible after cgo file generation.
   246  		extra = append(extra, "runtime/cgo")
   247  	}
   248  	for _, i := range extra {
   249  		p, err := pkg.ResolvePackage(i)
   250  		if err != nil {
   251  			return nil, err
   252  		}
   253  		pkgs = append(pkgs, p)
   254  	}
   255  	for _, i := range pkgs {
   256  		a, err := BuildPackage(targets, i)
   257  		if err != nil {
   258  			return nil, err
   259  		}
   260  		if a == nil {
   261  			// no action required for this Package
   262  			continue
   263  		}
   264  		deps = append(deps, a)
   265  	}
   266  	return deps, nil
   267  }
   268  
   269  func gc(pkg *Package, gofiles []string) error {
   270  	t0 := time.Now()
   271  	err := pkg.tc.Gc(pkg, gofiles)
   272  	pkg.Record("gc", time.Since(t0))
   273  	return err
   274  }
   275  
   276  func (pkg *Package) link() error {
   277  	t0 := time.Now()
   278  	err := pkg.tc.Ld(pkg)
   279  	pkg.Record("link", time.Since(t0))
   280  	return err
   281  }