github.com/oweisse/u-root@v0.0.0-20181109060735-d005ad25fef1/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/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(
   110  				path.Join(opts.BinaryDir, name),
   111  				path.Join("/", opts.BinaryDir, "installcommand"))); err != nil {
   112  				return err
   113  			}
   114  		}
   115  	}
   116  	if len(installcommand) == 0 {
   117  		return fmt.Errorf("must include a version of installcommand in source mode")
   118  	}
   119  
   120  	// Add src files of dependencies to archive.
   121  	for dep := range deps {
   122  		goListPkg(opts, dep, af)
   123  	}
   124  
   125  	// If we are doing "four bins" mode, or maybe I should call it Go of
   126  	// Four, then we need to drop a file into the Go command source
   127  	// directory before we build, and we need to remove it after.  And we
   128  	// need to verify that we're not supplanting something.
   129  	if sb.FourBins {
   130  		goCmd := filepath.Join(opts.Env.GOROOT, "src/cmd/go")
   131  		if _, err := os.Stat(goCmd); err != nil {
   132  			return fmt.Errorf("stat(%q): %v", goCmd, err)
   133  		}
   134  
   135  		z := filepath.Join(goCmd, goCommandFile)
   136  		if _, err := os.Stat(z); err == nil {
   137  			return fmt.Errorf("%q exists, and we will not overwrite it", z)
   138  		}
   139  
   140  		if err := ioutil.WriteFile(z, addInitToGoCommand, 0444); err != nil {
   141  			return err
   142  		}
   143  		defer os.Remove(z)
   144  	}
   145  
   146  	// Add Go toolchain.
   147  	log.Printf("Building go toolchain...")
   148  	if err := buildToolchain(opts); err != nil {
   149  		return err
   150  	}
   151  	if !sb.FourBins {
   152  		if err := opts.Env.Build(installcommand, filepath.Join(opts.TempDir, opts.BinaryDir, "installcommand"), golang.BuildOpts{}); err != nil {
   153  			return err
   154  		}
   155  	}
   156  
   157  	// Add Go toolchain and installcommand to archive.
   158  	return af.AddFile(opts.TempDir, "")
   159  }
   160  
   161  // buildToolchain builds the needed Go toolchain binaries: go, compile, link,
   162  // asm.
   163  func buildToolchain(opts Opts) error {
   164  	goBin := filepath.Join(opts.TempDir, "go/bin/go")
   165  	tcbo := golang.BuildOpts{
   166  		ExtraArgs: []string{"-tags", "cmd_go_bootstrap"},
   167  	}
   168  	if err := opts.Env.Build("cmd/go", goBin, tcbo); err != nil {
   169  		return err
   170  	}
   171  
   172  	toolDir := filepath.Join(opts.TempDir, fmt.Sprintf("go/pkg/tool/%v_%v", opts.Env.GOOS, opts.Env.GOARCH))
   173  	for _, pkg := range []string{"compile", "link", "asm"} {
   174  		c := filepath.Join(toolDir, pkg)
   175  		if err := opts.Env.Build(fmt.Sprintf("cmd/%s", pkg), c, golang.BuildOpts{}); err != nil {
   176  			return err
   177  		}
   178  	}
   179  	return nil
   180  }
   181  
   182  func goListPkg(opts Opts, importPath string, out *initramfs.Files) *golang.ListPackage {
   183  	p, err := opts.Env.Deps(importPath)
   184  	if err != nil {
   185  		log.Printf("Can't list Go dependencies for %v; ignoring.", importPath)
   186  		return nil
   187  	}
   188  
   189  	// Add Go files in this package to archive.
   190  	for _, file := range append(append(p.GoFiles, p.SFiles...), p.HFiles...) {
   191  		relPath := filepath.Join("src", p.ImportPath, file)
   192  		srcFile := filepath.Join(p.Root, relPath)
   193  		if p.Goroot {
   194  			out.AddFile(srcFile, filepath.Join("go", relPath))
   195  		} else {
   196  			out.AddFile(srcFile, relPath)
   197  		}
   198  	}
   199  	return p
   200  }