gopkg.in/hugelgupf/u-root.v9@v9.0.0-20180831063832-3f6f1057f09b/pkg/uroot/builder/bb/bb.go (about) 1 // Copyright 2015-2017 the u-root Authors. All rights reserved 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package bb 6 7 import ( 8 "bytes" 9 "fmt" 10 "go/ast" 11 "go/build" 12 "go/format" 13 "go/importer" 14 "go/parser" 15 "go/token" 16 "go/types" 17 "io/ioutil" 18 "os" 19 "path" 20 "path/filepath" 21 "sort" 22 "strconv" 23 "strings" 24 25 "golang.org/x/tools/go/ast/astutil" 26 "golang.org/x/tools/imports" 27 28 "github.com/u-root/u-root/pkg/golang" 29 ) 30 31 // Commands to skip building in bb mode. 32 var skip = map[string]struct{}{ 33 "bb": {}, 34 } 35 36 // BuildBusybox builds a busybox of the given Go packages. 37 // 38 // pkgs is a list of Go import paths. If nil is returned, binaryPath will hold 39 // the busybox-style binary. 40 func BuildBusybox(env golang.Environ, pkgs []string, binaryPath string) error { 41 urootPkg, err := env.Package("github.com/u-root/u-root") 42 if err != nil { 43 return err 44 } 45 46 bbDir := filepath.Join(urootPkg.Dir, "bb") 47 // Blow bb away before trying to re-create it. 48 if err := os.RemoveAll(bbDir); err != nil { 49 return err 50 } 51 if err := os.MkdirAll(bbDir, 0755); err != nil { 52 return err 53 } 54 55 var bbPackages []string 56 // Move and rewrite package files. 57 importer := importer.For("source", nil) 58 for _, pkg := range pkgs { 59 if _, ok := skip[path.Base(pkg)]; ok { 60 continue 61 } 62 63 pkgDir := filepath.Join(bbDir, "cmds", path.Base(pkg)) 64 // TODO: use bbDir to derive import path below or vice versa. 65 if err := RewritePackage(env, pkg, pkgDir, "github.com/u-root/u-root/pkg/bb", importer); err != nil { 66 return err 67 } 68 69 bbPackages = append(bbPackages, fmt.Sprintf("github.com/u-root/u-root/bb/cmds/%s", path.Base(pkg))) 70 } 71 72 bb, err := NewPackageFromEnv(env, "github.com/u-root/u-root/pkg/bb/cmd", importer) 73 if err != nil { 74 return err 75 } 76 if bb == nil { 77 return fmt.Errorf("bb cmd template missing") 78 } 79 if len(bb.ast.Files) != 1 { 80 return fmt.Errorf("bb cmd template is supposed to only have one file") 81 } 82 // Create bb main.go. 83 if err := CreateBBMainSource(bb.fset, bb.ast, bbPackages, bbDir); err != nil { 84 return err 85 } 86 87 // Compile bb. 88 return env.Build("github.com/u-root/u-root/bb", binaryPath, golang.BuildOpts{}) 89 } 90 91 // CreateBBMainSource creates a bb Go command that imports all given pkgs. 92 // 93 // p must be the bb template. 94 // 95 // - For each pkg in pkgs, add 96 // import _ "pkg" 97 // to astp's first file. 98 // - Write source file out to destDir. 99 func CreateBBMainSource(fset *token.FileSet, astp *ast.Package, pkgs []string, destDir string) error { 100 for _, pkg := range pkgs { 101 for _, sourceFile := range astp.Files { 102 // Add side-effect import to bb binary so init registers itself. 103 // 104 // import _ "pkg" 105 astutil.AddNamedImport(fset, sourceFile, "_", pkg) 106 break 107 } 108 } 109 110 // Write bb main binary out. 111 for filePath, sourceFile := range astp.Files { 112 path := filepath.Join(destDir, filepath.Base(filePath)) 113 if err := writeFile(path, fset, sourceFile); err != nil { 114 return err 115 } 116 break 117 } 118 return nil 119 } 120 121 // Package is a Go package. 122 // 123 // It holds AST, type, file, and Go package information about a Go package. 124 type Package struct { 125 name string 126 127 fset *token.FileSet 128 ast *ast.Package 129 typeInfo types.Info 130 types *types.Package 131 sortedFiles []*ast.File 132 133 // initCount keeps track of what the next init's index should be. 134 initCount uint 135 136 // init is the cmd.Init function that calls all other InitXs in the 137 // right order. 138 init *ast.FuncDecl 139 140 // initAssigns is a map of assignment expression -> InitN function call 141 // statement. 142 // 143 // That InitN should contain the assignment statement for the 144 // appropriate assignment expression. 145 // 146 // types.Info.InitOrder keeps track of Initializations by Lhs name and 147 // Rhs ast.Expr. We reparent the Rhs in assignment statements in InitN 148 // functions, so we use the Rhs as an easy key here. 149 // types.Info.InitOrder + initAssigns can then easily be used to derive 150 // the order of Stmts in the "real" init. 151 // 152 // The key Expr must also be the AssignStmt.Rhs[0]. 153 initAssigns map[ast.Expr]ast.Stmt 154 } 155 156 // NewPackageFromEnv finds the package identified by importPath, and gathers 157 // AST, type, and token information. 158 func NewPackageFromEnv(env golang.Environ, importPath string, importer types.Importer) (*Package, error) { 159 p, err := env.Package(importPath) 160 if err != nil { 161 return nil, err 162 } 163 return NewPackage(filepath.Base(p.Dir), p, importer) 164 } 165 166 // ParseAST parses p's package files into an AST. 167 func ParseAST(p *build.Package) (*token.FileSet, *ast.Package, error) { 168 name := filepath.Base(p.Dir) 169 if !p.IsCommand() { 170 return nil, nil, fmt.Errorf("package %q is not a command and cannot be included in bb", name) 171 } 172 173 fset := token.NewFileSet() 174 pars, err := parser.ParseDir(fset, p.Dir, func(fi os.FileInfo) bool { 175 // Only parse Go files that match build tags of this package. 176 for _, name := range p.GoFiles { 177 if name == fi.Name() { 178 return true 179 } 180 } 181 return false 182 }, parser.ParseComments) 183 if err != nil { 184 return nil, nil, fmt.Errorf("failed to parse AST for pkg %q: %v", name, err) 185 } 186 187 astp, ok := pars[p.Name] 188 if !ok { 189 return nil, nil, fmt.Errorf("parsed files, but could not find package %q in ast: %v", p.Name, pars) 190 } 191 return fset, astp, nil 192 } 193 194 // NewPackage gathers AST, type, and token information about package p, using 195 // the given importer to resolve dependencies. 196 func NewPackage(name string, p *build.Package, importer types.Importer) (*Package, error) { 197 fset, astp, err := ParseAST(p) 198 if err != nil { 199 return nil, err 200 } 201 202 pp := &Package{ 203 name: name, 204 fset: fset, 205 ast: astp, 206 typeInfo: types.Info{ 207 Types: make(map[ast.Expr]types.TypeAndValue), 208 }, 209 initAssigns: make(map[ast.Expr]ast.Stmt), 210 } 211 212 // This Init will hold calls to all other InitXs. 213 pp.init = &ast.FuncDecl{ 214 Name: ast.NewIdent("Init"), 215 Type: &ast.FuncType{ 216 Params: &ast.FieldList{}, 217 Results: nil, 218 }, 219 Body: &ast.BlockStmt{}, 220 } 221 222 // The order of types.Info.InitOrder depends on this list of files 223 // always being passed to conf.Check in the same order. 224 filenames := make([]string, 0, len(pp.ast.Files)) 225 for name := range pp.ast.Files { 226 filenames = append(filenames, name) 227 } 228 sort.Strings(filenames) 229 230 pp.sortedFiles = make([]*ast.File, 0, len(pp.ast.Files)) 231 for _, name := range filenames { 232 pp.sortedFiles = append(pp.sortedFiles, pp.ast.Files[name]) 233 } 234 // Type-check the package before we continue. We need types to rewrite 235 // some statements. 236 conf := types.Config{ 237 Importer: importer, 238 239 // We only need global declarations' types. 240 IgnoreFuncBodies: true, 241 } 242 tpkg, err := conf.Check(p.ImportPath, pp.fset, pp.sortedFiles, &pp.typeInfo) 243 if err != nil { 244 return nil, fmt.Errorf("type checking failed: %#v: %v", importer, err) 245 } 246 pp.types = tpkg 247 return pp, nil 248 } 249 250 func (p *Package) nextInit(addToCallList bool) *ast.Ident { 251 i := ast.NewIdent(fmt.Sprintf("Init%d", p.initCount)) 252 if addToCallList { 253 p.init.Body.List = append(p.init.Body.List, &ast.ExprStmt{X: &ast.CallExpr{Fun: i}}) 254 } 255 p.initCount++ 256 return i 257 } 258 259 // TODO: 260 // - write an init name generator, in case InitN is already taken. 261 // - also rewrite all non-Go-stdlib dependencies. 262 func (p *Package) rewriteFile(f *ast.File) bool { 263 hasMain := false 264 265 // Change the package name declaration from main to the command's name. 266 f.Name = ast.NewIdent(p.name) 267 268 // Map of fully qualified package name -> imported alias in the file. 269 importAliases := make(map[string]string) 270 for _, impt := range f.Imports { 271 if impt.Name != nil { 272 importPath, err := strconv.Unquote(impt.Path.Value) 273 if err != nil { 274 panic(err) 275 } 276 importAliases[importPath] = impt.Name.Name 277 } 278 } 279 280 // When the types.TypeString function translates package names, it uses 281 // this function to map fully qualified package paths to a local alias, 282 // if it exists. 283 qualifier := func(pkg *types.Package) string { 284 name, ok := importAliases[pkg.Path()] 285 if ok { 286 return name 287 } 288 // When referring to self, don't use any package name. 289 if pkg == p.types { 290 return "" 291 } 292 return pkg.Name() 293 } 294 295 for _, decl := range f.Decls { 296 switch d := decl.(type) { 297 case *ast.GenDecl: 298 // We only care about vars. 299 if d.Tok != token.VAR { 300 break 301 } 302 for _, spec := range d.Specs { 303 s := spec.(*ast.ValueSpec) 304 if s.Values == nil { 305 continue 306 } 307 308 // For each assignment, create a new init 309 // function, and place it in the same file. 310 for i, name := range s.Names { 311 varInit := &ast.FuncDecl{ 312 Name: p.nextInit(false), 313 Type: &ast.FuncType{ 314 Params: &ast.FieldList{}, 315 Results: nil, 316 }, 317 Body: &ast.BlockStmt{ 318 List: []ast.Stmt{ 319 &ast.AssignStmt{ 320 Lhs: []ast.Expr{name}, 321 Tok: token.ASSIGN, 322 Rhs: []ast.Expr{s.Values[i]}, 323 }, 324 }, 325 }, 326 } 327 // Add a call to the new init func to 328 // this map, so they can be added to 329 // Init0() in the correct init order 330 // later. 331 p.initAssigns[s.Values[i]] = &ast.ExprStmt{X: &ast.CallExpr{Fun: varInit.Name}} 332 f.Decls = append(f.Decls, varInit) 333 } 334 335 // Add the type of the expression to the global 336 // declaration instead. 337 if s.Type == nil { 338 typ := p.typeInfo.Types[s.Values[0]] 339 s.Type = ast.NewIdent(types.TypeString(typ.Type, qualifier)) 340 } 341 s.Values = nil 342 } 343 344 case *ast.FuncDecl: 345 if d.Recv == nil && d.Name.Name == "main" { 346 d.Name.Name = "Main" 347 hasMain = true 348 } 349 if d.Recv == nil && d.Name.Name == "init" { 350 d.Name = p.nextInit(true) 351 } 352 } 353 } 354 355 // Now we change any import names attached to package declarations. We 356 // just upcase it for now; it makes it easy to look in bbsh for things 357 // we changed, e.g. grep -r bbsh Import is useful. 358 for _, cg := range f.Comments { 359 for _, c := range cg.List { 360 if strings.HasPrefix(c.Text, "// import") { 361 c.Text = "// Import" + c.Text[9:] 362 } 363 } 364 } 365 return hasMain 366 } 367 368 // RewritePackage rewrites pkgPath to be bb-mode compatible, where destDir is 369 // the file system destination of the written files and bbImportPath is the Go 370 // import path of the bb package to register with. 371 func RewritePackage(env golang.Environ, pkgPath, destDir, bbImportPath string, importer types.Importer) error { 372 p, err := NewPackageFromEnv(env, pkgPath, importer) 373 if err != nil { 374 return err 375 } 376 if p == nil { 377 return nil 378 } 379 return p.Rewrite(destDir, bbImportPath) 380 } 381 382 // Rewrite rewrites p into destDir as a bb package using bbImportPath for the 383 // bb implementation. 384 func (p *Package) Rewrite(destDir, bbImportPath string) error { 385 if err := os.MkdirAll(destDir, 0755); err != nil { 386 return err 387 } 388 389 // This init holds all variable initializations. 390 // 391 // func Init0() {} 392 varInit := &ast.FuncDecl{ 393 Name: p.nextInit(true), 394 Type: &ast.FuncType{ 395 Params: &ast.FieldList{}, 396 Results: nil, 397 }, 398 Body: &ast.BlockStmt{}, 399 } 400 401 var mainFile *ast.File 402 for _, sourceFile := range p.sortedFiles { 403 if hasMainFile := p.rewriteFile(sourceFile); hasMainFile { 404 mainFile = sourceFile 405 } 406 } 407 if mainFile == nil { 408 return os.RemoveAll(destDir) 409 } 410 411 // Add variable initializations to Init0 in the right order. 412 for _, initStmt := range p.typeInfo.InitOrder { 413 a, ok := p.initAssigns[initStmt.Rhs] 414 if !ok { 415 return fmt.Errorf("couldn't find init assignment %s", initStmt) 416 } 417 varInit.Body.List = append(varInit.Body.List, a) 418 } 419 420 // import bb "bbImportPath" 421 astutil.AddNamedImport(p.fset, mainFile, "bb", bbImportPath) 422 423 // func init() { 424 // bb.Register(p.name, Init, Main) 425 // } 426 bbRegisterInit := &ast.FuncDecl{ 427 Name: ast.NewIdent("init"), 428 Type: &ast.FuncType{}, 429 Body: &ast.BlockStmt{ 430 List: []ast.Stmt{ 431 &ast.ExprStmt{X: &ast.CallExpr{ 432 Fun: ast.NewIdent("bb.Register"), 433 Args: []ast.Expr{ 434 // name= 435 &ast.BasicLit{ 436 Kind: token.STRING, 437 Value: strconv.Quote(p.name), 438 }, 439 // init= 440 ast.NewIdent("Init"), 441 // main= 442 ast.NewIdent("Main"), 443 }, 444 }}, 445 }, 446 }, 447 } 448 449 // We could add these statements to any of the package files. We choose 450 // the one that contains Main() to guarantee reproducibility of the 451 // same bbsh binary. 452 mainFile.Decls = append(mainFile.Decls, varInit, p.init, bbRegisterInit) 453 454 // Write all files out. 455 for filePath, sourceFile := range p.ast.Files { 456 path := filepath.Join(destDir, filepath.Base(filePath)) 457 if err := writeFile(path, p.fset, sourceFile); err != nil { 458 return err 459 } 460 } 461 return nil 462 } 463 464 func writeFile(path string, fset *token.FileSet, f *ast.File) error { 465 var buf bytes.Buffer 466 if err := format.Node(&buf, fset, f); err != nil { 467 return fmt.Errorf("error formatting Go file %q: %v", path, err) 468 } 469 return writeGoFile(path, buf.Bytes()) 470 } 471 472 func writeGoFile(path string, code []byte) error { 473 // Format the file. Do not fix up imports, as we only moved code around 474 // within files. 475 opts := imports.Options{ 476 Comments: true, 477 TabIndent: true, 478 TabWidth: 8, 479 FormatOnly: true, 480 } 481 code, err := imports.Process("commandline", code, &opts) 482 if err != nil { 483 return fmt.Errorf("bad parse while processing imports %q: %v", path, err) 484 } 485 486 if err := ioutil.WriteFile(path, code, 0644); err != nil { 487 return fmt.Errorf("error writing Go file to %q: %v", path, err) 488 } 489 return nil 490 }