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.