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 }