github.com/gsquire/gb@v0.4.4-0.20161112235727-3982dc872064/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.Stale { 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.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, pkg.Dir, 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{objfile(pkg)}, 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(installpath(pkg), objfile(pkg)) }, 199 } 200 } 201 202 // if this is a main package, add a link stage 203 if pkg.isMain() { 204 build = &Action{ 205 Name: fmt.Sprintf("link: %s", pkg.ImportPath), 206 Deps: []*Action{build}, 207 Run: func() error { return link(pkg) }, 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.isMain(): 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 includes := pkg.IncludePaths() 272 importpath := pkg.ImportPath 273 if pkg.TestScope && pkg.ExtraIncludes != "" { 274 // TODO(dfc) gross 275 includes = append([]string{pkg.ExtraIncludes}, includes...) 276 } 277 for i := range gofiles { 278 if filepath.IsAbs(gofiles[i]) { 279 // terrible hack for cgo files which come with an absolute path 280 continue 281 } 282 fullpath := filepath.Join(pkg.Dir, gofiles[i]) 283 path, err := filepath.Rel(pkg.Dir, fullpath) 284 if err == nil { 285 gofiles[i] = path 286 } else { 287 gofiles[i] = fullpath 288 } 289 } 290 err := pkg.tc.Gc(pkg, includes, importpath, pkg.Dir, objfile(pkg), gofiles) 291 pkg.Record("gc", time.Since(t0)) 292 return err 293 } 294 295 func link(pkg *Package) error { 296 t0 := time.Now() 297 target := pkg.Binfile() 298 if err := mkdir(filepath.Dir(target)); err != nil { 299 return err 300 } 301 302 includes := pkg.IncludePaths() 303 if pkg.TestScope && pkg.ExtraIncludes != "" { 304 // TODO(dfc) gross 305 includes = append([]string{pkg.ExtraIncludes}, includes...) 306 } 307 err := pkg.tc.Ld(pkg, includes, target, objfile(pkg)) 308 pkg.Record("link", time.Since(t0)) 309 return err 310 } 311 312 // Workdir returns the working directory for a package. 313 func Workdir(pkg *Package) string { 314 if pkg.TestScope { 315 ip := strings.TrimSuffix(filepath.FromSlash(pkg.ImportPath), "_test") 316 return filepath.Join(pkg.Workdir(), ip, "_test", filepath.Dir(filepath.FromSlash(pkg.ImportPath))) 317 } 318 return filepath.Join(pkg.Workdir(), filepath.Dir(filepath.FromSlash(pkg.ImportPath))) 319 } 320 321 // objfile returns the name of the object file for this package 322 func objfile(pkg *Package) string { 323 return filepath.Join(Workdir(pkg), objname(pkg)) 324 } 325 326 func objname(pkg *Package) string { 327 if pkg.isMain() { 328 return filepath.Join(filepath.Base(filepath.FromSlash(pkg.ImportPath)), "main.a") 329 } 330 return filepath.Base(filepath.FromSlash(pkg.ImportPath)) + ".a" 331 } 332 333 func pkgname(pkg *Package) string { 334 switch { 335 case pkg.TestScope: 336 return filepath.Base(filepath.FromSlash(pkg.ImportPath)) 337 case pkg.Name == "main": 338 return filepath.Base(filepath.FromSlash(pkg.ImportPath)) 339 default: 340 return pkg.Name 341 } 342 } 343 344 func binname(pkg *Package) string { 345 switch { 346 case pkg.TestScope: 347 return pkg.Name + ".test" 348 case pkg.Name == "main": 349 return filepath.Base(filepath.FromSlash(pkg.ImportPath)) 350 default: 351 panic("binname called with non main package: " + pkg.ImportPath) 352 } 353 }