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 }