github.com/u-root/u-root@v7.0.1-0.20200915234505-ad7babab0a8e+incompatible/pkg/uroot/builder/source.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 builder
     6  
     7  import (
     8  	"fmt"
     9  	"io/ioutil"
    10  	"log"
    11  	"os"
    12  	"path"
    13  	"path/filepath"
    14  
    15  	"github.com/u-root/u-root/pkg/cpio"
    16  	"github.com/u-root/u-root/pkg/golang"
    17  	"github.com/u-root/u-root/pkg/uroot/initramfs"
    18  )
    19  
    20  var (
    21  	goCommandFile      = "zzzzinit.go"
    22  	addInitToGoCommand = []byte(`// Copyright 2011 The Go Authors. All rights reserved.
    23  // Use of this source code is governed by a BSD-style
    24  // license that can be found in the LICENSE file.
    25  
    26  package main
    27  
    28  import (
    29  	"log"
    30  	"os"
    31  	"os/exec"
    32  	"syscall"
    33  
    34  )
    35  
    36  func init() {
    37  	if os.Args[0] != "/init" {
    38  		return
    39  	}
    40  
    41  	c := exec.Command("/go/bin/go", "build", "-o", "/buildbin/installcommand", "github.com/u-root/u-root/cmds/core/installcommand")
    42  	c.Env = append(c.Env,  []string{"GOROOT=/go", "GOPATH=/",}...)
    43  	o, err := c.CombinedOutput()
    44  	if err != nil {
    45  		log.Printf("building installcommand: %s, %v", string(o), err)
    46  		return
    47  	}
    48  	if err := syscall.Exec("/buildbin/init", []string{"init"}, []string{}); err != nil {
    49  		log.Printf("Exec of /buildbin/init failed. %v", err)
    50  	}
    51  }
    52  `)
    53  )
    54  
    55  // SourceBuilder includes full source for Go commands in the initramfs.
    56  //
    57  // SourceBuilder is an implementation of Builder.
    58  //
    59  // It also includes the Go toolchain in the initramfs, and a tool called
    60  // installcommand that can compile the other commands using symlinks.
    61  //
    62  // E.g. if "ls" is an included command, "ls" will be a symlink to
    63  // "installcommand" in the initramfs, which uses argv[0] to figure out which
    64  // command to compile.
    65  type SourceBuilder struct {
    66  	// FourBins, if true, will cause us to not build
    67  	// an installcommand. This only makes sense if you are using the
    68  	// fourbins command in the u-root command, but that's your call.
    69  	// In operation, the default behavior is the one most people will want,
    70  	// i.e. the installcommand will be built.
    71  	FourBins bool
    72  }
    73  
    74  // DefaultBinaryDir implements Builder.DefaultBinaryDir.
    75  //
    76  // The initramfs default binary dir is buildbin.
    77  func (SourceBuilder) DefaultBinaryDir() string {
    78  	return "buildbin"
    79  }
    80  
    81  // Build is an implementation of Builder.Build.
    82  func (sb SourceBuilder) Build(af *initramfs.Files, opts Opts) error {
    83  	// TODO: this is a failure to collect the correct dependencies.
    84  	if err := af.AddFile(filepath.Join(opts.Env.GOROOT, "pkg/include"), "go/pkg/include"); err != nil {
    85  		return err
    86  	}
    87  
    88  	var installcommand string
    89  	log.Printf("Collecting package files and dependencies...")
    90  	deps := make(map[string]struct{})
    91  	for _, pkg := range opts.Packages {
    92  		name := path.Base(pkg)
    93  		if name == "installcommand" {
    94  			installcommand = pkg
    95  		}
    96  
    97  		// Add high-level packages' src files to archive.
    98  		p := goListPkg(opts, pkg, af)
    99  		if p == nil {
   100  			continue
   101  		}
   102  		for _, d := range p.Deps {
   103  			deps[d] = struct{}{}
   104  		}
   105  
   106  		if name != "installcommand" {
   107  			// Add a symlink to installcommand. This means source mode can
   108  			// work with any init.
   109  			if err := af.AddRecord(cpio.Symlink(path.Join(opts.BinaryDir, name), "installcommand")); err != nil {
   110  				return err
   111  			}
   112  		}
   113  	}
   114  	if len(installcommand) == 0 {
   115  		return fmt.Errorf("must include a version of installcommand in source mode")
   116  	}
   117  
   118  	// Add src files of dependencies to archive.
   119  	for dep := range deps {
   120  		goListPkg(opts, dep, af)
   121  	}
   122  
   123  	// If we are doing "four bins" mode, or maybe I should call it Go of
   124  	// Four, then we need to drop a file into the Go command source
   125  	// directory before we build, and we need to remove it after.  And we
   126  	// need to verify that we're not supplanting something.
   127  	if sb.FourBins {
   128  		goCmd := filepath.Join(opts.Env.GOROOT, "src/cmd/go")
   129  		if _, err := os.Stat(goCmd); err != nil {
   130  			return fmt.Errorf("stat(%q): %v", goCmd, err)
   131  		}
   132  
   133  		z := filepath.Join(goCmd, goCommandFile)
   134  		if _, err := os.Stat(z); err == nil {
   135  			return fmt.Errorf("%q exists, and we will not overwrite it", z)
   136  		}
   137  
   138  		if err := ioutil.WriteFile(z, addInitToGoCommand, 0444); err != nil {
   139  			return err
   140  		}
   141  		defer os.Remove(z)
   142  	}
   143  
   144  	// Add Go toolchain.
   145  	log.Printf("Building go toolchain...")
   146  	if err := buildToolchain(opts); err != nil {
   147  		return err
   148  	}
   149  	if !sb.FourBins {
   150  		if err := opts.Env.Build(installcommand, filepath.Join(opts.TempDir, opts.BinaryDir, "installcommand"), golang.BuildOpts{}); err != nil {
   151  			return err
   152  		}
   153  	}
   154  
   155  	// Add Go toolchain and installcommand to archive.
   156  	return af.AddFile(opts.TempDir, "")
   157  }
   158  
   159  // buildToolchain builds the needed Go toolchain binaries: go, compile, link,
   160  // asm.
   161  func buildToolchain(opts Opts) error {
   162  	goBin := filepath.Join(opts.TempDir, "go/bin/go")
   163  	tcbo := golang.BuildOpts{
   164  		ExtraArgs: []string{"-tags", "cmd_go_bootstrap"},
   165  	}
   166  	if err := opts.Env.Build("cmd/go", goBin, tcbo); err != nil {
   167  		return err
   168  	}
   169  
   170  	toolDir := filepath.Join(opts.TempDir, fmt.Sprintf("go/pkg/tool/%v_%v", opts.Env.GOOS, opts.Env.GOARCH))
   171  	for _, pkg := range []string{"compile", "link", "asm"} {
   172  		c := filepath.Join(toolDir, pkg)
   173  		if err := opts.Env.Build(fmt.Sprintf("cmd/%s", pkg), c, golang.BuildOpts{}); err != nil {
   174  			return err
   175  		}
   176  	}
   177  	return nil
   178  }
   179  
   180  func goListPkg(opts Opts, importPath string, out *initramfs.Files) *golang.ListPackage {
   181  	p, err := opts.Env.Deps(importPath)
   182  	if err != nil {
   183  		log.Printf("Can't list Go dependencies for %v; ignoring.", importPath)
   184  		return nil
   185  	}
   186  
   187  	// Add Go files in this package to archive.
   188  	for _, file := range append(append(p.GoFiles, p.SFiles...), p.HFiles...) {
   189  		relPath := filepath.Join("src", p.ImportPath, file)
   190  		srcFile := filepath.Join(p.Root, relPath)
   191  		if p.Goroot {
   192  			out.AddFile(srcFile, filepath.Join("go", relPath))
   193  		} else {
   194  			out.AddFile(srcFile, relPath)
   195  		}
   196  	}
   197  	return p
   198  }