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 }