github.com/kekek/gb@v0.4.5-0.20170222120241-d4ba64b0b297/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 }