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 }