Skip to content

Commit

Permalink
u-root: add the new go builder the right way
Browse files Browse the repository at this point in the history
Previously, we added the gobusybox builder as a test.

Add it the way it should be added:
- make a new builder, gbb, and type GBBBBuilder that implements builder.Builder
  This new type uses the u-root/gobusybox package
- extend the string handling for the build option to include gbb
- add a test for the gbb builder

Remaining is the question of whether to pull gobusybox into u-root.
Is this separation the right thing in the long term? gobusybox and
u-root are very tightly connected.

Signed-off-by: Ronald G. Minnich <[email protected]>
  • Loading branch information
rminnichcodeu authored and rminnich committed Dec 10, 2021
1 parent b8c06a0 commit 73262e9
Show file tree
Hide file tree
Showing 4 changed files with 157 additions and 71 deletions.
3 changes: 1 addition & 2 deletions pkg/uroot/builder/bb_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,7 @@ import (
"github.com/u-root/u-root/pkg/uroot/initramfs"
)

// Disable this until we are done switching to modules.
func testBBBuild(t *testing.T) {
func TestBBBuild(t *testing.T) {
dir := t.TempDir()

opts := Opts{
Expand Down
111 changes: 111 additions & 0 deletions pkg/uroot/builder/gbb.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
// Copyright 2015-2021 the u-root Authors. All rights reserved
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package builder

import (
"errors"
"flag"
"fmt"
"os"
"path"
"path/filepath"

"github.com/u-root/gobusybox/src/pkg/bb"
"github.com/u-root/gobusybox/src/pkg/golang"
"github.com/u-root/u-root/pkg/cpio"
"github.com/u-root/u-root/pkg/uroot/initramfs"
)

// GBBBuilder is an implementation of Builder that compiles many Go commands
// into one busybox-style binary.
//
// GBBBuilder will also include symlinks for each command to the busybox binary.
//
// GBBBuilder does all this by rewriting the source files of the packages given
// to create one busybox-like binary containing all commands.
//
// The compiled binary uses argv[0] to decide which Go command to run.
//
// See bb/README.md for a detailed explanation of the implementation of busybox
// mode.
type GBBBuilder struct {
// ShellBang means generate #! files instead of symlinks.
// ShellBang are more portable and just as efficient.
ShellBang bool
}

// DefaultBinaryDir implements Builder.DefaultBinaryDir.
//
// The default initramfs binary dir is bbin for busybox binaries.
func (GBBBuilder) DefaultBinaryDir() string {
return "bbin"
}

// Build is an implementation of Builder.Build for a busybox-like initramfs.
func (b GBBBuilder) Build(af *initramfs.Files, opts Opts) error {
// Build the busybox binary.
if len(opts.TempDir) == 0 {
return fmt.Errorf("opts.TempDir is empty")
}
bbPath := filepath.Join(opts.TempDir, "bb")
env := golang.Default()
if env.CgoEnabled {
env.CgoEnabled = false
}

remove := false

if len(opts.BinaryDir) == 0 {
return fmt.Errorf("must specify binary directory")
}

bopts := &bb.Opts{
Env: env,
GenSrcDir: opts.TempDir,
CommandPaths: opts.Packages,
BinaryPath: bbPath,
GoBuildOpts: &golang.BuildOpts{},
}
bopts.GoBuildOpts.RegisterFlags(flag.CommandLine)

if err := bb.BuildBusybox(bopts); err != nil {
var errGopath *bb.ErrGopathBuild
var errGomod *bb.ErrModuleBuild
if errors.As(err, &errGopath) {
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`", opts.TempDir, errGopath.CmdDir, errGopath.GOPATH)
} else if errors.As(err, &errGomod) {
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", opts.TempDir, errGomod.CmdDir)
} else {
return fmt.Errorf("preserving bb generated source directory at %s due to error", opts.TempDir)
}
}
// Only remove temp dir if there was no error.
if remove {
os.RemoveAll(opts.TempDir)
}

if err := af.AddFile(bbPath, "/bbin/bb"); err != nil {
return err
}

// Add symlinks for included commands to initramfs.
for _, pkg := range opts.Packages {
if _, ok := skip[path.Base(pkg)]; ok {
continue
}

// Add a symlink /bbin/{cmd} -> /bbin/bb to our initramfs.
// Or add a #! file if b.ShellBang is set ...
if b.ShellBang {
b := path.Base(pkg)
if err := af.AddRecord(cpio.StaticFile(filepath.Join(opts.BinaryDir, b), "#!/bbin/bb #!"+b+"\n", 0o755)); err != nil {
return err
}
} else if err := af.AddRecord(cpio.Symlink(filepath.Join(opts.BinaryDir, path.Base(pkg)), "bb")); err != nil {
return err
}
}
return nil
}
41 changes: 41 additions & 0 deletions pkg/uroot/builder/gbb_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// Copyright 2021 the u-root Authors. All rights reserved
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package builder

import (
"testing"

"github.com/u-root/u-root/pkg/golang"
"github.com/u-root/u-root/pkg/uroot/initramfs"
)

func TestGBBBuild(t *testing.T) {
dir := t.TempDir()

opts := Opts{
Env: golang.Default(),
Packages: []string{
"../test/foo",
"../../../cmds/core/elvish",
},
TempDir: dir,
BinaryDir: "bbin",
}
af := initramfs.NewFiles()
var gbb GBBBuilder
if err := gbb.Build(af, opts); err != nil {
t.Fatalf("Build(%v, %v); %v != nil", af, opts, err)
}

mustContain := []string{
"bbin/elvish",
"bbin/foo",
}
for _, name := range mustContain {
if !af.Contains(name) {
t.Errorf("expected files to include %q; archive: %v", name, af)
}
}
}
73 changes: 4 additions & 69 deletions u-root.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,16 @@ package main

import (
"encoding/json"
"errors"
"flag"
"fmt"
"io/ioutil"
"log"
"os"
"path"
"path/filepath"
"runtime"
"sort"
"strings"
"time"

gbb "github.com/u-root/gobusybox/src/pkg/bb"
gbbgolang "github.com/u-root/gobusybox/src/pkg/golang"
"github.com/u-root/u-root/pkg/golang"
"github.com/u-root/u-root/pkg/shlex"
"github.com/u-root/u-root/pkg/uroot"
Expand Down Expand Up @@ -67,7 +62,7 @@ func init() {
sh = "elvish"
}

build = flag.String("build", "bb", "u-root build format (e.g. bb or binary).")
build = flag.String("build", "gbb", "u-root build format (e.g. bb or binary).")
format = flag.String("format", "cpio", "Archival format.")

tmpDir = flag.String("tmpdir", "", "Temporary directory to put binaries in.")
Expand All @@ -93,7 +88,6 @@ func init() {
tags = flag.String("tags", "", "Comma separated list of build tags")

// Flags for the gobusybox, which we hope to move to, since it works with modules.
usegobusybox = flag.Bool("gobusybox", os.Getenv("GO111MODULE") != "off", "Use the new gobusybox package to build u-root")
genDir = flag.String("gen-dir", "", "Directory to generate source in")

}
Expand Down Expand Up @@ -148,73 +142,11 @@ func generateLabel() string {
return fmt.Sprintf("%s-%s-%s-%s", *build, env.GOOS, env.GOARCH, strings.Join(baseCmds, "_"))
}

func gobusyboxMain() error {
bopts := &gbbgolang.BuildOpts{}
bopts.RegisterFlags(flag.CommandLine)

o, err := filepath.Abs(*outputPath)
if err != nil {
return err
}

env := gbbgolang.Default()
if env.CgoEnabled {
log.Printf("Disabling CGO for u-root...")
env.CgoEnabled = false
}
log.Printf("Build environment: %s", env)

tmpDir := *genDir
remove := false
if tmpDir == "" {
tdir, err := ioutil.TempDir("", "bb-")
if err != nil {
return fmt.Errorf("Could not create busybox source directory: %v", err)
}
tmpDir = tdir
remove = true
}

if len(flag.Args()) == 0 {
return fmt.Errorf("commands must be provided, as a path or glob, e.g., u-root cmds/core/* cmds/exp/rush")
}
opts := &gbb.Opts{
Env: env,
GenSrcDir: tmpDir,
CommandPaths: flag.Args(),
BinaryPath: o,
GoBuildOpts: bopts,
}
if err := gbb.BuildBusybox(opts); err != nil {
log.Print(err)
var errGopath *gbb.ErrGopathBuild
var errGomod *gbb.ErrModuleBuild
if errors.As(err, &errGopath) {
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`", tmpDir, errGopath.CmdDir, errGopath.GOPATH)
} else if errors.As(err, &errGomod) {
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", tmpDir, errGomod.CmdDir)
} else {
return fmt.Errorf("preserving bb generated source directory at %s due to error", tmpDir)
}
}
// Only remove temp dir if there was no error.
if remove {
os.RemoveAll(tmpDir)
}
return nil
}

func main() {
flag.Parse()

start := time.Now()

if *usegobusybox {
if err := gobusyboxMain(); err != nil {
log.Fatal(err)
}
return
}
// Main is in a separate functions so defers run on return.
if err := Main(); err != nil {
log.Fatal(err)
Expand Down Expand Up @@ -336,6 +268,9 @@ func Main() error {
switch *build {
case "bb":
b = builder.BBBuilder{ShellBang: *shellbang}
case "gbb":
log.Printf("NOTE: building with the new gobusybox; to get old behavior, use -build=bb")
b = builder.GBBBuilder{ShellBang: *shellbang}
case "binary":
b = builder.BinaryBuilder{}
case "source":
Expand Down

0 comments on commit 73262e9

Please sign in to comment.