github.com/u-root/u-root@v7.0.1-0.20200915234505-ad7babab0a8e+incompatible/pkg/bb/README.md (about) 1 ## Go Busybox 2 3 `bb.go` in this package implements a Go source-to-source transformation on pure 4 Go code (no cgo). 5 6 This AST transformation does the following: 7 8 - Takes a Go command's source files and rewrites them into Go package files 9 without global side effects. 10 - Writes a `main.go` file with a `main()` that calls into the appropriate Go 11 command package based on `argv[0]`. 12 13 This allows you to take two Go commands, such as Go implementations of `sl` and 14 `cowsay` and compile them into one binary. 15 16 Which command is invoked is determined by `argv[0]` or `argv[1]` if `argv[0]` is 17 not recognized. Let's say `bb` is the compiled binary; the following are 18 equivalent invocations of `sl` and `cowsay`: 19 20 ```sh 21 # Make a symlink sl -> bb 22 ln -s bb sl 23 ./sl -l 24 25 # Make a symlink cowsay -> bb 26 ln -s bb cowsay 27 ./cowsay Haha 28 ``` 29 30 ```sh 31 ./bb sl -l 32 ./bb cowsay Haha 33 ``` 34 35 ### AST Transformation 36 37 Principally, the AST transformation moves all global side-effects into callable 38 package functions. E.g. `main` becomes `Main`, each `init` becomes `InitN`, and 39 global variable assignments are moved into their own `InitN`. 40 41 Then, these `Main` and `Init` functions can be registered with a global map of 42 commands by name and used when called upon. 43 44 Let's say a command `github.com/org/repo/cmds/sl` contains the following 45 `main.go`: 46 47 ```go 48 package main 49 50 import ( 51 "flag" 52 "log" 53 ) 54 55 var name = flag.String("name", "", "Gimme name") 56 57 func init() { 58 log.Printf("init %s", *name) 59 } 60 61 func main() { 62 log.Printf("train") 63 } 64 ``` 65 66 This would be rewritten to be: 67 68 ```go 69 package sl // based on the directory name or bazel-rule go_binary name 70 71 import ( 72 "flag" 73 "log" 74 75 // This package holds the global map of commands. 76 "github.com/u-root/u-root/pkg/bb" 77 ) 78 79 // Type has to be inferred through type checking. 80 var name *string 81 82 func Init0() { 83 log.Printf("init %s", *name) 84 } 85 86 func Init1() { 87 name = flag.String("name", "", "Gimme name") 88 } 89 90 func Init() { 91 // Order is determined by go/types.Info.InitOrder. 92 Init1() 93 Init0() 94 } 95 96 func Main() { 97 log.Printf("train") 98 } 99 100 func init() { 101 // Register `sl` as a command. 102 bb.Register("sl", Init, Main) 103 } 104 ``` 105 106 #### Shortcomings 107 108 - If there is already a function `Main` or `InitN` for some `N`, there may be 109 a compilation error. 110 - Any packages imported by commands may still have global side-effects 111 affecting other commands. Done properly, we would have to rewrite all 112 non-standard-library packages as well as commands. This has not been 113 necessary to implement so far. It would likely be necessary if two different 114 imported packages register the same flag unconditionally globally. 115 116 ## Generated main 117 118 The main file can be generated based on any template Go files, but the default 119 looks something like the following: 120 121 ```go 122 import ( 123 "os" 124 125 "github.com/u-root/u-root/pkg/bb" 126 127 // Side-effect import registers command with bb. 128 _ "github.com/org/repo/cmds/generated/sl" 129 ) 130 131 func main() { 132 bb.Run(os.Argv[0]) 133 } 134 ``` 135 136 The default template will use `argv[1]` if `argv[0]` is not in the map.