github.com/aca02djr/gb@v0.4.1/build.go (about)

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