github.com/u-root/u-root@v7.0.1-0.20200915234505-ad7babab0a8e+incompatible/u-root.go (about)

     1  // Copyright 2015-2018 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 main
     6  
     7  import (
     8  	"encoding/json"
     9  	"flag"
    10  	"fmt"
    11  	"io/ioutil"
    12  	"log"
    13  	"os"
    14  	"path"
    15  	"runtime"
    16  	"sort"
    17  	"strings"
    18  	"time"
    19  
    20  	"github.com/u-root/u-root/pkg/golang"
    21  	"github.com/u-root/u-root/pkg/shlex"
    22  	"github.com/u-root/u-root/pkg/uroot"
    23  	"github.com/u-root/u-root/pkg/uroot/builder"
    24  	"github.com/u-root/u-root/pkg/uroot/initramfs"
    25  )
    26  
    27  // multiFlag is used for flags that support multiple invocations, e.g. -files
    28  type multiFlag []string
    29  
    30  func (m *multiFlag) String() string {
    31  	return fmt.Sprint(*m)
    32  }
    33  
    34  func (m *multiFlag) Set(value string) error {
    35  	*m = append(*m, value)
    36  	return nil
    37  }
    38  
    39  // Flags for u-root builder.
    40  var (
    41  	build, format, tmpDir, base, outputPath *string
    42  	uinitCmd, initCmd                       *string
    43  	defaultShell                            *string
    44  	useExistingInit                         *bool
    45  	fourbins                                *bool
    46  	noCommands                              *bool
    47  	extraFiles                              multiFlag
    48  	noStrip                                 *bool
    49  	statsOutputPath                         *string
    50  	statsLabel                              *string
    51  )
    52  
    53  func init() {
    54  	var sh string
    55  	switch golang.Default().GOOS {
    56  	case "plan9":
    57  		sh = ""
    58  	default:
    59  		sh = "elvish"
    60  	}
    61  
    62  	fourbins = flag.Bool("fourbins", false, "build installcommand on boot, no ahead of time, so we have only four binares")
    63  	build = flag.String("build", "bb", "u-root build format (e.g. bb or source).")
    64  	format = flag.String("format", "cpio", "Archival format.")
    65  
    66  	tmpDir = flag.String("tmpdir", "", "Temporary directory to put binaries in.")
    67  
    68  	base = flag.String("base", "", "Base archive to add files to. By default, this is a couple of directories like /bin, /etc, etc. u-root has a default internally supplied set of files; use base=/dev/null if you don't want any base files.")
    69  	useExistingInit = flag.Bool("useinit", false, "Use existing init from base archive (only if --base was specified).")
    70  	outputPath = flag.String("o", "", "Path to output initramfs file.")
    71  
    72  	initCmd = flag.String("initcmd", "init", "Symlink target for /init. Can be an absolute path or a u-root command name. Use initcmd=\"\" if you don't want the symlink.")
    73  	uinitCmd = flag.String("uinitcmd", "", "Symlink target and arguments for /bin/uinit. Can be an absolute path or a u-root command name. Use uinitcmd=\"\" if you don't want the symlink. E.g. -uinitcmd=\"echo foobar\"")
    74  	defaultShell = flag.String("defaultsh", sh, "Default shell. Can be an absolute path or a u-root command name. Use defaultsh=\"\" if you don't want the symlink.")
    75  
    76  	noCommands = flag.Bool("nocmd", false, "Build no Go commands; initramfs only")
    77  
    78  	flag.Var(&extraFiles, "files", "Additional files, directories, and binaries (with their ldd dependencies) to add to archive. Can be speficified multiple times.")
    79  
    80  	noStrip = flag.Bool("no-strip", false, "Build unstripped binaries")
    81  
    82  	statsOutputPath = flag.String("stats-output-path", "", "Write build stats to this file (JSON)")
    83  
    84  	statsLabel = flag.String("stats-label", "", "Use this statsLabel when writing stats")
    85  }
    86  
    87  type buildStats struct {
    88  	Label      string  `json:"label,omitempty"`
    89  	Time       int64   `json:"time"`
    90  	Duration   float64 `json:"duration"`
    91  	OutputSize int64   `json:"output_size"`
    92  }
    93  
    94  func writeBuildStats(stats buildStats, path string) error {
    95  	var allStats []buildStats
    96  	if data, err := ioutil.ReadFile(*statsOutputPath); err == nil {
    97  		json.Unmarshal(data, &allStats)
    98  	}
    99  	found := false
   100  	for i, s := range allStats {
   101  		if s.Label == stats.Label {
   102  			allStats[i] = stats
   103  			found = true
   104  			break
   105  		}
   106  	}
   107  	if !found {
   108  		allStats = append(allStats, stats)
   109  		sort.Slice(allStats, func(i, j int) bool {
   110  			return strings.Compare(allStats[i].Label, allStats[j].Label) == -1
   111  		})
   112  	}
   113  	data, err := json.MarshalIndent(allStats, "", "  ")
   114  	if err != nil {
   115  		return err
   116  	}
   117  	if err := ioutil.WriteFile(*statsOutputPath, data, 0644); err != nil {
   118  		return err
   119  	}
   120  	return nil
   121  }
   122  
   123  func generateLabel() string {
   124  	var baseCmds []string
   125  	env := golang.Default()
   126  	if len(flag.Args()) > 0 {
   127  		// Use the last component of the name to keep the label short
   128  		for _, e := range flag.Args() {
   129  			baseCmds = append(baseCmds, path.Base(e))
   130  		}
   131  	} else {
   132  		baseCmds = []string{"core"}
   133  	}
   134  	return fmt.Sprintf("%s-%s-%s-%s", *build, env.GOOS, env.GOARCH, strings.Join(baseCmds, "_"))
   135  }
   136  
   137  func main() {
   138  	flag.Parse()
   139  
   140  	start := time.Now()
   141  
   142  	// Main is in a separate functions so defers run on return.
   143  	if err := Main(); err != nil {
   144  		log.Fatal(err)
   145  	}
   146  
   147  	elapsed := time.Now().Sub(start)
   148  
   149  	stats := buildStats{
   150  		Label:    *statsLabel,
   151  		Time:     start.Unix(),
   152  		Duration: float64(elapsed.Milliseconds()) / 1000,
   153  	}
   154  	if stats.Label == "" {
   155  		stats.Label = generateLabel()
   156  	}
   157  	if stat, err := os.Stat(*outputPath); err == nil && stat.ModTime().After(start) {
   158  		log.Printf("Successfully built %q (size %d).", *outputPath, stat.Size())
   159  		stats.OutputSize = stat.Size()
   160  		if *statsOutputPath != "" {
   161  			if err := writeBuildStats(stats, *statsOutputPath); err == nil {
   162  				log.Printf("Wrote stats to %q (label %q)", *statsOutputPath, stats.Label)
   163  			} else {
   164  				log.Printf("Failed to write stats to %s: %v", *statsOutputPath, err)
   165  			}
   166  		}
   167  	}
   168  }
   169  
   170  var recommendedVersions = []string{
   171  	"go1.13",
   172  	"go1.14",
   173  }
   174  
   175  func isRecommendedVersion(v string) bool {
   176  	for _, r := range recommendedVersions {
   177  		if strings.HasPrefix(v, r) {
   178  			return true
   179  		}
   180  	}
   181  	return false
   182  }
   183  
   184  // Main is a separate function so defers are run on return, which they wouldn't
   185  // on exit.
   186  func Main() error {
   187  	env := golang.Default()
   188  	if *fourbins && env.GOROOT == "" {
   189  		log.Fatalf("You have to set GOROOT for fourbins to work")
   190  	}
   191  	if env.CgoEnabled {
   192  		log.Printf("Disabling CGO for u-root...")
   193  		env.CgoEnabled = false
   194  	}
   195  	log.Printf("Build environment: %s", env)
   196  	if env.GOOS != "linux" {
   197  		log.Printf("GOOS is not linux. Did you mean to set GOOS=linux?")
   198  	}
   199  
   200  	v, err := env.Version()
   201  	if err != nil {
   202  		log.Printf("Could not get environment's Go version, using runtime's version: %v", err)
   203  		v = runtime.Version()
   204  	}
   205  	if !isRecommendedVersion(v) {
   206  		log.Printf(`WARNING: You are not using one of the recommended Go versions (have = %s, recommended = %v).
   207  			Some packages may not compile.
   208  			Go to https://golang.org/doc/install to find out how to install a newer version of Go,
   209  			or use https://godoc.org/golang.org/dl/%s to install an additional version of Go.`,
   210  			v, recommendedVersions, recommendedVersions[0])
   211  	}
   212  
   213  	archiver, err := initramfs.GetArchiver(*format)
   214  	if err != nil {
   215  		return err
   216  	}
   217  
   218  	logger := log.New(os.Stderr, "", log.LstdFlags)
   219  	// Open the target initramfs file.
   220  	if *outputPath == "" {
   221  		if len(env.GOOS) == 0 && len(env.GOARCH) == 0 {
   222  			return fmt.Errorf("passed no path, GOOS, and GOARCH to CPIOArchiver.OpenWriter")
   223  		}
   224  		*outputPath = fmt.Sprintf("/tmp/initramfs.%s_%s.cpio", env.GOOS, env.GOARCH)
   225  	}
   226  	w, err := archiver.OpenWriter(logger, *outputPath)
   227  	if err != nil {
   228  		return err
   229  	}
   230  
   231  	var baseFile initramfs.Reader
   232  	if *base != "" {
   233  		bf, err := os.Open(*base)
   234  		if err != nil {
   235  			return err
   236  		}
   237  		defer bf.Close()
   238  		baseFile = archiver.Reader(bf)
   239  	} else {
   240  		baseFile = uroot.DefaultRamfs().Reader()
   241  	}
   242  
   243  	tempDir := *tmpDir
   244  	if tempDir == "" {
   245  		var err error
   246  		tempDir, err = ioutil.TempDir("", "u-root")
   247  		if err != nil {
   248  			return err
   249  		}
   250  		defer os.RemoveAll(tempDir)
   251  	} else if _, err := os.Stat(tempDir); os.IsNotExist(err) {
   252  		if err := os.MkdirAll(tempDir, 0755); err != nil {
   253  			return fmt.Errorf("temporary directory %q did not exist; tried to mkdir but failed: %v", tempDir, err)
   254  		}
   255  	}
   256  
   257  	var (
   258  		c           []uroot.Commands
   259  		initCommand = *initCmd
   260  	)
   261  	if !*noCommands {
   262  		var b builder.Builder
   263  		switch *build {
   264  		case "bb":
   265  			b = builder.BBBuilder{}
   266  		case "binary":
   267  			b = builder.BinaryBuilder{}
   268  		case "source":
   269  			b = builder.SourceBuilder{
   270  				FourBins: *fourbins,
   271  			}
   272  		default:
   273  			return fmt.Errorf("could not find builder %q", *build)
   274  		}
   275  
   276  		// Resolve globs into package imports.
   277  		//
   278  		// Currently allowed formats:
   279  		//   Go package imports; e.g. github.com/u-root/u-root/cmds/ls (must be in $GOPATH)
   280  		//   Paths to Go package directories; e.g. $GOPATH/src/github.com/u-root/u-root/cmds/*
   281  		var pkgs []string
   282  		for _, a := range flag.Args() {
   283  			p, ok := templates[a]
   284  			if !ok {
   285  				pkgs = append(pkgs, a)
   286  				continue
   287  			}
   288  			pkgs = append(pkgs, p...)
   289  		}
   290  		if len(pkgs) == 0 {
   291  			pkgs = []string{"github.com/u-root/u-root/cmds/core/*"}
   292  		}
   293  
   294  		if *fourbins && *build == "source" {
   295  			initCommand = "/go/bin/go"
   296  		}
   297  
   298  		// The command-line tool only allows specifying one build mode
   299  		// right now.
   300  		c = append(c, uroot.Commands{
   301  			Builder:  b,
   302  			Packages: pkgs,
   303  		})
   304  	}
   305  
   306  	opts := uroot.Opts{
   307  		Env:             env,
   308  		Commands:        c,
   309  		TempDir:         tempDir,
   310  		ExtraFiles:      extraFiles,
   311  		OutputFile:      w,
   312  		BaseArchive:     baseFile,
   313  		UseExistingInit: *useExistingInit,
   314  		InitCmd:         initCommand,
   315  		DefaultShell:    *defaultShell,
   316  		NoStrip:         *noStrip,
   317  	}
   318  	uinitArgs := shlex.Argv(*uinitCmd)
   319  	if len(uinitArgs) > 0 {
   320  		opts.UinitCmd = uinitArgs[0]
   321  	}
   322  	if len(uinitArgs) > 1 {
   323  		opts.UinitArgs = uinitArgs[1:]
   324  	}
   325  	return uroot.CreateInitramfs(logger, opts)
   326  }