gopkg.in/constabulary/gb.v0@v0.4.4/package.go (about)

     1  package gb
     2  
     3  import (
     4  	"fmt"
     5  	"go/build"
     6  	"os"
     7  	"path/filepath"
     8  	"runtime"
     9  	"strings"
    10  	"time"
    11  
    12  	"github.com/constabulary/gb/internal/debug"
    13  	"github.com/pkg/errors"
    14  )
    15  
    16  // Package represents a resolved package from the Project with respect to the Context.
    17  type Package struct {
    18  	*Context
    19  	*build.Package
    20  	TestScope bool
    21  	NotStale  bool // this package _and_ all its dependencies are not stale
    22  	Main      bool // is this a command
    23  	Imports   []*Package
    24  }
    25  
    26  // newPackage creates a resolved Package without setting pkg.Stale.
    27  func (ctx *Context) newPackage(p *build.Package) (*Package, error) {
    28  	pkg := &Package{
    29  		Context: ctx,
    30  		Package: p,
    31  	}
    32  	for _, i := range p.Imports {
    33  		dep, ok := ctx.pkgs[i]
    34  		if !ok {
    35  			return nil, errors.Errorf("newPackage(%q): could not locate dependant package %q ", p.Name, i)
    36  		}
    37  		pkg.Imports = append(pkg.Imports, dep)
    38  	}
    39  	return pkg, nil
    40  }
    41  
    42  func (p *Package) String() string {
    43  	return fmt.Sprintf("%s {Name:%s, Dir:%s}", p.ImportPath, p.Name, p.Dir)
    44  }
    45  
    46  func (p *Package) includePaths() []string {
    47  	includes := p.Context.includePaths()
    48  	switch {
    49  	case p.TestScope && p.Main:
    50  		ip := filepath.Dir(filepath.FromSlash(p.ImportPath))
    51  		return append([]string{filepath.Join(p.Context.Workdir(), ip, "_test")}, includes...)
    52  	case p.TestScope:
    53  		ip := strings.TrimSuffix(filepath.FromSlash(p.ImportPath), "_test")
    54  		return append([]string{filepath.Join(p.Context.Workdir(), ip, "_test")}, includes...)
    55  	default:
    56  		return includes
    57  	}
    58  }
    59  
    60  // Complete indicates if this is a pure Go package
    61  func (p *Package) Complete() bool {
    62  	// If we're giving the compiler the entire package (no C etc files), tell it that,
    63  	// so that it can give good error messages about forward declarations.
    64  	// Exceptions: a few standard packages have forward declarations for
    65  	// pieces supplied behind-the-scenes by package runtime.
    66  	extFiles := len(p.CgoFiles) + len(p.CFiles) + len(p.CXXFiles) + len(p.MFiles) + len(p.SFiles) + len(p.SysoFiles) + len(p.SwigFiles) + len(p.SwigCXXFiles)
    67  	if p.Goroot {
    68  		switch p.ImportPath {
    69  		case "bytes", "net", "os", "runtime/pprof", "sync", "time":
    70  			extFiles++
    71  		}
    72  	}
    73  	return extFiles == 0
    74  }
    75  
    76  // Binfile returns the destination of the compiled target of this command.
    77  func (pkg *Package) Binfile() string {
    78  	target := filepath.Join(pkg.bindir(), pkg.binname())
    79  
    80  	// if this is a cross compile or GOOS/GOARCH are both defined or there are build tags, add ctxString.
    81  	if pkg.isCrossCompile() || (os.Getenv("GOOS") != "" && os.Getenv("GOARCH") != "") {
    82  		target += "-" + pkg.ctxString()
    83  	} else if len(pkg.buildtags) > 0 {
    84  		target += "-" + strings.Join(pkg.buildtags, "-")
    85  	}
    86  
    87  	if pkg.gotargetos == "windows" {
    88  		target += ".exe"
    89  	}
    90  	return target
    91  }
    92  
    93  func (pkg *Package) bindir() string {
    94  	switch {
    95  	case pkg.TestScope:
    96  		return filepath.Join(pkg.Context.Workdir(), filepath.FromSlash(pkg.ImportPath), "_test")
    97  	default:
    98  		return pkg.Context.bindir()
    99  	}
   100  }
   101  
   102  func (pkg *Package) Workdir() string {
   103  	path := filepath.FromSlash(pkg.ImportPath)
   104  	dir := filepath.Dir(path)
   105  	switch {
   106  	case pkg.TestScope:
   107  		ip := strings.TrimSuffix(path, "_test")
   108  		return filepath.Join(pkg.Context.Workdir(), ip, "_test", dir)
   109  	default:
   110  		return filepath.Join(pkg.Context.Workdir(), dir)
   111  	}
   112  }
   113  
   114  // objfile returns the name of the object file for this package
   115  func (pkg *Package) objfile() string {
   116  	return filepath.Join(pkg.Workdir(), pkg.objname())
   117  }
   118  
   119  func (pkg *Package) objname() string {
   120  	return pkg.pkgname() + ".a"
   121  }
   122  
   123  func (pkg *Package) pkgname() string {
   124  	// TODO(dfc) use pkg path instead?
   125  	return filepath.Base(filepath.FromSlash(pkg.ImportPath))
   126  }
   127  
   128  func (pkg *Package) binname() string {
   129  	if !pkg.Main {
   130  		panic("binname called with non main package: " + pkg.ImportPath)
   131  	}
   132  	// TODO(dfc) use pkg path instead?
   133  	return filepath.Base(filepath.FromSlash(pkg.ImportPath))
   134  }
   135  
   136  // installpath returns the distination to cache this package's compiled .a file.
   137  // pkgpath and installpath differ in that the former returns the location where you will find
   138  // a previously cached .a file, the latter returns the location where an installed file
   139  // will be placed.
   140  //
   141  // The difference is subtle. pkgpath must deal with the possibility that the file is from the
   142  // standard library and is previously compiled. installpath will always return a path for the
   143  // project's pkg/ directory in the case that the stdlib is out of date, or not compiled for
   144  // a specific architecture.
   145  func (pkg *Package) installpath() string {
   146  	if pkg.TestScope {
   147  		panic("installpath called with test scope")
   148  	}
   149  	return filepath.Join(pkg.Pkgdir(), filepath.FromSlash(pkg.ImportPath)+".a")
   150  }
   151  
   152  // pkgpath returns the destination for object cached for this Package.
   153  func (pkg *Package) pkgpath() string {
   154  	importpath := filepath.FromSlash(pkg.ImportPath) + ".a"
   155  	switch {
   156  	case pkg.isCrossCompile():
   157  		return filepath.Join(pkg.Pkgdir(), importpath)
   158  	case pkg.Goroot && pkg.race:
   159  		// race enabled standard lib
   160  		return filepath.Join(runtime.GOROOT(), "pkg", pkg.gotargetos+"_"+pkg.gotargetarch+"_race", importpath)
   161  	case pkg.Goroot:
   162  		// standard lib
   163  		return filepath.Join(runtime.GOROOT(), "pkg", pkg.gotargetos+"_"+pkg.gotargetarch, importpath)
   164  	default:
   165  		return filepath.Join(pkg.Pkgdir(), importpath)
   166  	}
   167  }
   168  
   169  // isStale returns true if the source pkg is considered to be stale with
   170  // respect to its installed version.
   171  func (pkg *Package) isStale() bool {
   172  	switch pkg.ImportPath {
   173  	case "C", "unsafe":
   174  		// synthetic packages are never stale
   175  		return false
   176  	}
   177  
   178  	if !pkg.Goroot && pkg.Force {
   179  		return true
   180  	}
   181  
   182  	// tests are always stale, they are never installed
   183  	if pkg.TestScope {
   184  		return true
   185  	}
   186  
   187  	// Package is stale if completely unbuilt.
   188  	var built time.Time
   189  	if fi, err := os.Stat(pkg.pkgpath()); err == nil {
   190  		built = fi.ModTime()
   191  	}
   192  
   193  	if built.IsZero() {
   194  		debug.Debugf("%s is missing", pkg.pkgpath())
   195  		return true
   196  	}
   197  
   198  	olderThan := func(file string) bool {
   199  		fi, err := os.Stat(file)
   200  		return err != nil || fi.ModTime().After(built)
   201  	}
   202  
   203  	newerThan := func(file string) bool {
   204  		fi, err := os.Stat(file)
   205  		return err != nil || fi.ModTime().Before(built)
   206  	}
   207  
   208  	// As a courtesy to developers installing new versions of the compiler
   209  	// frequently, define that packages are stale if they are
   210  	// older than the compiler, and commands if they are older than
   211  	// the linker.  This heuristic will not work if the binaries are
   212  	// back-dated, as some binary distributions may do, but it does handle
   213  	// a very common case.
   214  	if !pkg.Goroot {
   215  		if olderThan(pkg.tc.compiler()) {
   216  			debug.Debugf("%s is older than %s", pkg.pkgpath(), pkg.tc.compiler())
   217  			return true
   218  		}
   219  		if pkg.Main && olderThan(pkg.tc.linker()) {
   220  			debug.Debugf("%s is older than %s", pkg.pkgpath(), pkg.tc.compiler())
   221  			return true
   222  		}
   223  	}
   224  
   225  	if pkg.Goroot && !pkg.isCrossCompile() {
   226  		// if this is a standard lib package, and we are not cross compiling
   227  		// then assume the package is up to date. This also works around
   228  		// golang/go#13769.
   229  		return false
   230  	}
   231  
   232  	// Package is stale if a dependency is newer.
   233  	for _, p := range pkg.Imports {
   234  		if p.ImportPath == "C" || p.ImportPath == "unsafe" {
   235  			continue // ignore stale imports of synthetic packages
   236  		}
   237  		if olderThan(p.pkgpath()) {
   238  			debug.Debugf("%s is older than %s", pkg.pkgpath(), p.pkgpath())
   239  			return true
   240  		}
   241  	}
   242  
   243  	// if the main package is up to date but _newer_ than the binary (which
   244  	// could have been removed), then consider it stale.
   245  	if pkg.Main && newerThan(pkg.Binfile()) {
   246  		debug.Debugf("%s is newer than %s", pkg.pkgpath(), pkg.Binfile())
   247  		return true
   248  	}
   249  
   250  	srcs := stringList(pkg.GoFiles, pkg.CFiles, pkg.CXXFiles, pkg.MFiles, pkg.HFiles, pkg.SFiles, pkg.CgoFiles, pkg.SysoFiles, pkg.SwigFiles, pkg.SwigCXXFiles)
   251  
   252  	for _, src := range srcs {
   253  		if olderThan(filepath.Join(pkg.Dir, src)) {
   254  			debug.Debugf("%s is older than %s", pkg.pkgpath(), filepath.Join(pkg.Dir, src))
   255  			return true
   256  		}
   257  	}
   258  
   259  	return false
   260  }
   261  
   262  func stringList(args ...[]string) []string {
   263  	var l []string
   264  	for _, arg := range args {
   265  		l = append(l, arg...)
   266  	}
   267  	return l
   268  }