github.com/mvdan/u-root-coreutils@v0.0.0-20230122170626-c2eef2898555/pkg/uroot/builder/gbb.go (about)

     1  // Copyright 2015-2021 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 builder
     6  
     7  import (
     8  	"errors"
     9  	"fmt"
    10  	"path"
    11  	"path/filepath"
    12  
    13  	"github.com/u-root/gobusybox/src/pkg/bb"
    14  	"github.com/mvdan/u-root-coreutils/pkg/cpio"
    15  	"github.com/mvdan/u-root-coreutils/pkg/ulog"
    16  	"github.com/mvdan/u-root-coreutils/pkg/uroot/initramfs"
    17  )
    18  
    19  // Commands to skip building in bb mode.
    20  var skip = map[string]struct{}{
    21  	"bb": {},
    22  }
    23  
    24  // GBBBuilder is an implementation of Builder that compiles many Go commands
    25  // into one busybox-style binary.
    26  //
    27  // GBBBuilder will also include symlinks for each command to the busybox binary.
    28  //
    29  // GBBBuilder does all this by rewriting the source files of the packages given
    30  // to create one busybox-like binary containing all commands.
    31  //
    32  // The compiled binary uses argv[0] to decide which Go command to run.
    33  //
    34  // See bb/README.md for a detailed explanation of the implementation of busybox
    35  // mode.
    36  type GBBBuilder struct {
    37  	// ShellBang means generate #! files instead of symlinks.
    38  	// ShellBang are more portable and just as efficient.
    39  	ShellBang bool
    40  }
    41  
    42  // DefaultBinaryDir implements Builder.DefaultBinaryDir.
    43  //
    44  // The default initramfs binary dir is bbin for busybox binaries.
    45  func (GBBBuilder) DefaultBinaryDir() string {
    46  	return "bbin"
    47  }
    48  
    49  // Build is an implementation of Builder.Build for a busybox-like initramfs.
    50  func (b GBBBuilder) Build(l ulog.Logger, af *initramfs.Files, opts Opts) error {
    51  	// Build the busybox binary.
    52  	if len(opts.TempDir) == 0 {
    53  		return fmt.Errorf("opts.TempDir is empty")
    54  	}
    55  	bbPath := filepath.Join(opts.TempDir, "bb")
    56  
    57  	if len(opts.BinaryDir) == 0 {
    58  		return fmt.Errorf("must specify binary directory")
    59  	}
    60  
    61  	bopts := &bb.Opts{
    62  		Env:          opts.Env,
    63  		GenSrcDir:    opts.TempDir,
    64  		CommandPaths: opts.Packages,
    65  		BinaryPath:   bbPath,
    66  		GoBuildOpts:  opts.BuildOpts,
    67  	}
    68  
    69  	if err := bb.BuildBusybox(l, bopts); err != nil {
    70  		// Print the actual error. This may contain a suggestion for
    71  		// what to do, actually.
    72  		l.Printf("Gobusybox error: %v", err)
    73  
    74  		// Return some instructions for the user; this is printed last in the u-root tool.
    75  		//
    76  		// TODO: yeah, this isn't a good way to do error handling. The
    77  		// error should be the thing that's returned, I just wanted
    78  		// that to be printed first, and the instructions for what to
    79  		// do about it to be last.
    80  		var errGopath *bb.ErrGopathBuild
    81  		var errGomod *bb.ErrModuleBuild
    82  		if errors.As(err, &errGopath) {
    83  			return fmt.Errorf("preserving bb generated source directory at %s due to error. To reproduce build, `cd %s` and `GO111MODULE=off GOPATH=%s go build`: %v", opts.TempDir, errGopath.CmdDir, errGopath.GOPATH, err)
    84  		} else if errors.As(err, &errGomod) {
    85  			return fmt.Errorf("preserving bb generated source directory at %s due to error. To debug build, `cd %s` and use `go build` to build, or `go mod [why|tidy|graph]` to debug dependencies, or `go list -m all` to list all dependency versions:\n%v", opts.TempDir, errGomod.CmdDir, err)
    86  		} else {
    87  			return fmt.Errorf("preserving bb generated source directory at %s due to error:\n%v", opts.TempDir, err)
    88  		}
    89  	}
    90  
    91  	if err := af.AddFile(bbPath, "bbin/bb"); err != nil {
    92  		return err
    93  	}
    94  
    95  	// Add symlinks for included commands to initramfs.
    96  	for _, pkg := range opts.Packages {
    97  		if _, ok := skip[path.Base(pkg)]; ok {
    98  			continue
    99  		}
   100  
   101  		// Add a symlink /bbin/{cmd} -> /bbin/bb to our initramfs.
   102  		// Or add a #! file if b.ShellBang is set ...
   103  		if b.ShellBang {
   104  			b := path.Base(pkg)
   105  			if err := af.AddRecord(cpio.StaticFile(filepath.Join(opts.BinaryDir, b), "#!/bbin/bb #!"+b+"\n", 0o755)); err != nil {
   106  				return err
   107  			}
   108  		} else if err := af.AddRecord(cpio.Symlink(filepath.Join(opts.BinaryDir, path.Base(pkg)), "bb")); err != nil {
   109  			return err
   110  		}
   111  	}
   112  	return nil
   113  }