github.com/mem/u-root@v2.0.1-0.20181004165302-9b18b4636a33+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  	"log"
    10  	"path"
    11  	"path/filepath"
    12  
    13  	"github.com/u-root/u-root/pkg/cpio"
    14  	"github.com/u-root/u-root/pkg/golang"
    15  	"github.com/u-root/u-root/pkg/uroot/initramfs"
    16  )
    17  
    18  // SourceBuilder includes full source for Go commands in the initramfs.
    19  //
    20  // SourceBuilder is an implementation of Builder.
    21  //
    22  // It also includes the Go toolchain in the initramfs, and a tool called
    23  // installcommand that can compile the other commands using symlinks.
    24  //
    25  // E.g. if "ls" is an included command, "ls" will be a symlink to
    26  // "installcommand" in the initramfs, which uses argv[0] to figure out which
    27  // command to compile.
    28  type SourceBuilder struct{}
    29  
    30  // DefaultBinaryDir implements Builder.DefaultBinaryDir.
    31  //
    32  // The initramfs default binary dir is buildbin.
    33  func (SourceBuilder) DefaultBinaryDir() string {
    34  	return "buildbin"
    35  }
    36  
    37  // Build is an implementation of Builder.Build.
    38  func (SourceBuilder) Build(af *initramfs.Files, opts Opts) error {
    39  	// TODO: this is a failure to collect the correct dependencies.
    40  	if err := af.AddFile(filepath.Join(opts.Env.GOROOT, "pkg/include"), "go/pkg/include"); err != nil {
    41  		return err
    42  	}
    43  
    44  	var installcommand string
    45  	log.Printf("Collecting package files and dependencies...")
    46  	deps := make(map[string]struct{})
    47  	for _, pkg := range opts.Packages {
    48  		name := path.Base(pkg)
    49  		if name == "installcommand" {
    50  			installcommand = pkg
    51  		}
    52  
    53  		// Add high-level packages' src files to archive.
    54  		p := goListPkg(opts, pkg, af)
    55  		if p == nil {
    56  			continue
    57  		}
    58  		for _, d := range p.Deps {
    59  			deps[d] = struct{}{}
    60  		}
    61  
    62  		if name != "installcommand" {
    63  			// Add a symlink to installcommand. This means source mode can
    64  			// work with any init.
    65  			if err := af.AddRecord(cpio.Symlink(
    66  				path.Join(opts.BinaryDir, name),
    67  				path.Join("/", opts.BinaryDir, "installcommand"))); err != nil {
    68  				return err
    69  			}
    70  		}
    71  	}
    72  	if len(installcommand) == 0 {
    73  		return fmt.Errorf("must include a version of installcommand in source mode")
    74  	}
    75  
    76  	// Add src files of dependencies to archive.
    77  	for dep := range deps {
    78  		goListPkg(opts, dep, af)
    79  	}
    80  
    81  	// Add Go toolchain.
    82  	log.Printf("Building go toolchain...")
    83  	if err := buildToolchain(opts); err != nil {
    84  		return err
    85  	}
    86  	if err := opts.Env.Build(installcommand, filepath.Join(opts.TempDir, opts.BinaryDir, "installcommand"), golang.BuildOpts{}); err != nil {
    87  		return err
    88  	}
    89  
    90  	// Add Go toolchain and installcommand to archive.
    91  	return af.AddFile(opts.TempDir, "")
    92  }
    93  
    94  // buildToolchain builds the needed Go toolchain binaries: go, compile, link,
    95  // asm.
    96  func buildToolchain(opts Opts) error {
    97  	goBin := filepath.Join(opts.TempDir, "go/bin/go")
    98  	tcbo := golang.BuildOpts{
    99  		ExtraArgs: []string{"-tags", "cmd_go_bootstrap"},
   100  	}
   101  	if err := opts.Env.Build("cmd/go", goBin, tcbo); err != nil {
   102  		return err
   103  	}
   104  
   105  	toolDir := filepath.Join(opts.TempDir, fmt.Sprintf("go/pkg/tool/%v_%v", opts.Env.GOOS, opts.Env.GOARCH))
   106  	for _, pkg := range []string{"compile", "link", "asm"} {
   107  		c := filepath.Join(toolDir, pkg)
   108  		if err := opts.Env.Build(fmt.Sprintf("cmd/%s", pkg), c, golang.BuildOpts{}); err != nil {
   109  			return err
   110  		}
   111  	}
   112  	return nil
   113  }
   114  
   115  func goListPkg(opts Opts, importPath string, out *initramfs.Files) *golang.ListPackage {
   116  	p, err := opts.Env.Deps(importPath)
   117  	if err != nil {
   118  		log.Printf("Can't list Go dependencies for %v; ignoring.", importPath)
   119  		return nil
   120  	}
   121  
   122  	// Add Go files in this package to archive.
   123  	for _, file := range append(append(p.GoFiles, p.SFiles...), p.HFiles...) {
   124  		relPath := filepath.Join("src", p.ImportPath, file)
   125  		srcFile := filepath.Join(p.Root, relPath)
   126  		if p.Goroot {
   127  			out.AddFile(srcFile, filepath.Join("go", relPath))
   128  		} else {
   129  			out.AddFile(srcFile, relPath)
   130  		}
   131  	}
   132  	return p
   133  }