From 8b4491fc937a9f8adaf000aa3482f37108f37a38 Mon Sep 17 00:00:00 2001
From: Chris Koch <chrisko@google.com>
Date: Sat, 24 Feb 2024 01:12:45 +0000
Subject: [PATCH 1/3] Rename mkuimage -> uimage make

Signed-off-by: Chris Koch <chrisko@google.com>
---
 .github/workflows/go.yml                      |  2 +-
 cmd/mkuimage/main.go                          | 99 -------------------
 cmd/{mkuimage => uimage}/.gitignore           |  1 +
 cmd/uimage/main.go                            | 62 ++++++++++++
 cmd/{mkuimage => uimage}/main_test.go         | 82 ++++++++-------
 .../testdata/test-config.yaml                 |  0
 go.mod                                        |  2 +-
 go.sum                                        |  2 +
 uimage/builder/binary_test.go                 |  4 +-
 uimage/builder/gbb_test.go                    | 18 ++--
 uimage/mkuimage/cmd.go                        | 10 +-
 11 files changed, 127 insertions(+), 155 deletions(-)
 delete mode 100644 cmd/mkuimage/main.go
 rename cmd/{mkuimage => uimage}/.gitignore (68%)
 create mode 100644 cmd/uimage/main.go
 rename cmd/{mkuimage => uimage}/main_test.go (82%)
 rename cmd/{mkuimage => uimage}/testdata/test-config.yaml (100%)

diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml
index 8304f63..bee02d9 100644
--- a/.github/workflows/go.yml
+++ b/.github/workflows/go.yml
@@ -53,7 +53,7 @@ jobs:
         run: go test -v -covermode atomic -coverpkg ./... -coverprofile cover.out ./...
 
       - name: Convert GOCOVERDIR coverage data
-        run: go tool covdata textfmt -i=cmd/mkuimage/cover -o cmdcover.out
+        run: go tool covdata textfmt -i=cmd/uimage/cover -o cmdcover.out
 
       - uses: codecov/codecov-action@v4-beta
         env:
diff --git a/cmd/mkuimage/main.go b/cmd/mkuimage/main.go
deleted file mode 100644
index 58947f5..0000000
--- a/cmd/mkuimage/main.go
+++ /dev/null
@@ -1,99 +0,0 @@
-// Copyright 2015-2018 the u-root Authors. All rights reserved
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-// Command mkuimage builds CPIO archives with the given files and Go commands.
-package main
-
-import (
-	"errors"
-	"flag"
-	"fmt"
-	"log"
-	"log/slog"
-	"os"
-
-	"github.com/dustin/go-humanize"
-	"github.com/u-root/gobusybox/src/pkg/golang"
-	"github.com/u-root/mkuimage/uimage"
-	"github.com/u-root/mkuimage/uimage/mkuimage"
-	"github.com/u-root/uio/llog"
-)
-
-var (
-	errEmptyFilesArg = errors.New("empty argument to -files")
-)
-
-// checkArgs checks for common mistakes that cause confusion.
-//  1. -files as the last argument
-//  2. -files followed by any switch, indicating a shell expansion problem
-//     This is usually caused by Makfiles structured as follows
-//     u-root -files `which ethtool` -files `which bash`
-//     if ethtool is not installed, the expansion yields
-//     u-root -files -files `which bash`
-//     and the rather confusing error message
-//     16:14:51 Skipping /usr/bin/bash because it is not a directory
-//     which, in practice, nobody understands
-func checkArgs(args ...string) error {
-	if len(args) == 0 {
-		return nil
-	}
-
-	if args[len(args)-1] == "-files" {
-		return fmt.Errorf("last argument is -files:%w", errEmptyFilesArg)
-	}
-
-	// We know the last arg is not -files; scan the arguments for -files
-	// followed by a switch.
-	for i := 0; i < len(args)-1; i++ {
-		if args[i] == "-files" && args[i+1][0] == '-' {
-			return fmt.Errorf("-files argument %d is followed by a switch: %w", i, errEmptyFilesArg)
-		}
-	}
-
-	return nil
-}
-
-func main() {
-	log.SetFlags(log.Ltime)
-	if err := checkArgs(os.Args...); err != nil {
-		log.Fatal(err)
-	}
-
-	env := golang.Default(golang.DisableCGO())
-	f := &mkuimage.Flags{
-		Commands:      mkuimage.CommandFlags{Builder: "bb"},
-		ArchiveFormat: "cpio",
-		OutputFile:    defaultFile(env),
-	}
-	f.RegisterFlags(flag.CommandLine)
-
-	l := llog.Default()
-	l.RegisterVerboseFlag(flag.CommandLine, "v", slog.LevelDebug)
-
-	tf := &mkuimage.TemplateFlags{}
-	tf.RegisterFlags(flag.CommandLine)
-	flag.Parse()
-
-	// Set defaults.
-	m := []uimage.Modifier{
-		uimage.WithReplaceEnv(env),
-		uimage.WithBaseArchive(uimage.DefaultRamfs()),
-		uimage.WithCPIOOutput(defaultFile(env)),
-	}
-	if err := mkuimage.CreateUimage(l, m, tf, f, flag.Args()); err != nil {
-		l.Errorf("mkuimage error: %v", err)
-		os.Exit(1)
-	}
-
-	if stat, err := os.Stat(f.OutputFile); err == nil && f.ArchiveFormat == "cpio" {
-		l.Infof("Successfully built %q (size %d bytes -- %s).", f.OutputFile, stat.Size(), humanize.IBytes(uint64(stat.Size())))
-	}
-}
-
-func defaultFile(env *golang.Environ) string {
-	if len(env.GOOS) == 0 || len(env.GOARCH) == 0 {
-		return "/tmp/initramfs.cpio"
-	}
-	return fmt.Sprintf("/tmp/initramfs.%s_%s.cpio", env.GOOS, env.GOARCH)
-}
diff --git a/cmd/mkuimage/.gitignore b/cmd/uimage/.gitignore
similarity index 68%
rename from cmd/mkuimage/.gitignore
rename to cmd/uimage/.gitignore
index 56fc4cf..41e6ef7 100644
--- a/cmd/mkuimage/.gitignore
+++ b/cmd/uimage/.gitignore
@@ -1,2 +1,3 @@
 mkuimage
+uimage
 cover
diff --git a/cmd/uimage/main.go b/cmd/uimage/main.go
new file mode 100644
index 0000000..54831f7
--- /dev/null
+++ b/cmd/uimage/main.go
@@ -0,0 +1,62 @@
+// Copyright 2015-2024 the u-root Authors. All rights reserved
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Command uimage builds CPIO archives with the given files and Go commands.
+package main
+
+import (
+	"fmt"
+	"log"
+	"log/slog"
+	"os"
+
+	"github.com/u-root/gobusybox/src/pkg/golang"
+	"github.com/u-root/mkuimage/uimage"
+	"github.com/u-root/mkuimage/uimage/mkuimage"
+	"github.com/u-root/uio/cli"
+	"github.com/u-root/uio/llog"
+)
+
+func main() {
+	log.SetFlags(log.Ltime)
+	l := llog.Default()
+
+	env := golang.Default(golang.DisableCGO())
+	f := &mkuimage.Flags{
+		Commands:      mkuimage.CommandFlags{Builder: "bb"},
+		ArchiveFormat: "cpio",
+		OutputFile:    defaultFile(env),
+	}
+	tf := &mkuimage.TemplateFlags{}
+
+	makeCmd := cli.Command{
+		Name:  "make",
+		Short: "create uimage from specified flags",
+		Run: func(args []string) {
+			// Set defaults.
+			m := []uimage.Modifier{
+				uimage.WithReplaceEnv(env),
+				uimage.WithBaseArchive(uimage.DefaultRamfs()),
+				uimage.WithCPIOOutput(defaultFile(env)),
+			}
+			if err := mkuimage.CreateUimage(l, m, tf, f, args); err != nil {
+				l.Errorf("mkuimage error: %v", err)
+				os.Exit(1)
+			}
+		},
+	}
+	l.RegisterVerboseFlag(makeCmd.Flags(), "v", slog.LevelDebug)
+	f.RegisterFlags(makeCmd.Flags())
+	tf.RegisterFlags(makeCmd.Flags())
+
+	app := cli.App{makeCmd}
+	app.Run(os.Args)
+}
+
+func defaultFile(env *golang.Environ) string {
+	if len(env.GOOS) == 0 || len(env.GOARCH) == 0 {
+		return "/tmp/initramfs.cpio"
+	}
+	return fmt.Sprintf("/tmp/initramfs.%s_%s.cpio", env.GOOS, env.GOARCH)
+}
diff --git a/cmd/mkuimage/main_test.go b/cmd/uimage/main_test.go
similarity index 82%
rename from cmd/mkuimage/main_test.go
rename to cmd/uimage/main_test.go
index 7aca4bc..f3020c1 100644
--- a/cmd/mkuimage/main_test.go
+++ b/cmd/uimage/main_test.go
@@ -93,10 +93,10 @@ func TestUrootCmdline(t *testing.T) {
 		wantOutput func(*testing.T, string)
 	}
 
-	noCmdTests := []testCase{
+	tests := []testCase{
 		{
 			name: "include one extra file",
-			args: []string{"-nocmd", "-files=/bin/bash"},
+			args: []string{"make", "-nocmd", "-files=/bin/bash"},
 			env:  []string{"GO111MODULE=off"},
 			validators: []itest.ArchiveValidator{
 				itest.HasFile{Path: "bin/bash"},
@@ -104,7 +104,7 @@ func TestUrootCmdline(t *testing.T) {
 		},
 		{
 			name: "fix usage of an absolute path",
-			args: []string{"-nocmd", fmt.Sprintf("-files=%s:/bin", sampledir)},
+			args: []string{"make", "-nocmd", fmt.Sprintf("-files=%s:/bin", sampledir)},
 			env:  []string{"GO111MODULE=off"},
 			validators: []itest.ArchiveValidator{
 				itest.HasFile{Path: "/bin/foo"},
@@ -113,7 +113,7 @@ func TestUrootCmdline(t *testing.T) {
 		},
 		{
 			name: "include multiple extra files",
-			args: []string{"-nocmd", "-files=/bin/bash", "-files=/bin/ls", fmt.Sprintf("-files=%s", samplef.Name())},
+			args: []string{"make", "-nocmd", "-files=/bin/bash", "-files=/bin/ls", fmt.Sprintf("-files=%s", samplef.Name())},
 			env:  []string{"GO111MODULE=off"},
 			validators: []itest.ArchiveValidator{
 				itest.HasFile{Path: "bin/bash"},
@@ -123,7 +123,7 @@ func TestUrootCmdline(t *testing.T) {
 		},
 		{
 			name: "include one extra file with rename",
-			args: []string{"-nocmd", "-files=/bin/bash:bin/bush"},
+			args: []string{"make", "-nocmd", "-files=/bin/bash:bin/bush"},
 			env:  []string{"GO111MODULE=off"},
 			validators: []itest.ArchiveValidator{
 				itest.HasFile{Path: "bin/bush"},
@@ -131,19 +131,16 @@ func TestUrootCmdline(t *testing.T) {
 		},
 		{
 			name: "supplied file can be uinit",
-			args: []string{"-nocmd", "-files=/bin/bash:bin/bash", "-uinitcmd=/bin/bash"},
+			args: []string{"make", "-nocmd", "-files=/bin/bash:bin/bash", "-uinitcmd=/bin/bash"},
 			env:  []string{"GO111MODULE=off"},
 			validators: []itest.ArchiveValidator{
 				itest.HasFile{Path: "bin/bash"},
 				itest.HasRecord{R: cpio.Symlink("bin/uinit", "bash")},
 			},
 		},
-	}
-
-	bareTests := []testCase{
 		{
 			name: "uinitcmd",
-			args: []string{"-uinitcmd=echo foobar fuzz", "-defaultsh=", "github.com/u-root/u-root/cmds/core/init", "github.com/u-root/u-root/cmds/core/echo"},
+			args: []string{"make", "-uinitcmd=echo foobar fuzz", "-defaultsh=", "github.com/u-root/u-root/cmds/core/init", "github.com/u-root/u-root/cmds/core/echo"},
 			validators: []itest.ArchiveValidator{
 				itest.HasRecord{R: cpio.Symlink("bin/uinit", "../bbin/echo")},
 				itest.HasContent{
@@ -155,6 +152,7 @@ func TestUrootCmdline(t *testing.T) {
 		{
 			name: "binary build",
 			args: []string{
+				"make",
 				"-build=binary",
 				"-defaultsh=",
 				"github.com/u-root/u-root/cmds/core/init",
@@ -169,6 +167,7 @@ func TestUrootCmdline(t *testing.T) {
 		{
 			name: "hosted mode",
 			args: []string{
+				"make",
 				"-base=/dev/null",
 				"-defaultsh=",
 				"-initcmd=",
@@ -180,6 +179,7 @@ func TestUrootCmdline(t *testing.T) {
 			name: "AMD64 build",
 			env:  []string{"GOARCH=amd64"},
 			args: []string{
+				"make",
 				"-defaultsh=echo",
 				"github.com/u-root/u-root/cmds/core/echo",
 				"github.com/u-root/u-root/cmds/core/init",
@@ -189,6 +189,7 @@ func TestUrootCmdline(t *testing.T) {
 			name: "AMD64 build with temp dir",
 			env:  []string{"GOARCH=amd64"},
 			args: []string{
+				"make",
 				"--keep-tmp-dir",
 				"--defaultsh=echo",
 				"github.com/u-root/u-root/cmds/core/echo",
@@ -201,6 +202,7 @@ func TestUrootCmdline(t *testing.T) {
 			name: "ARM7 build",
 			env:  []string{"GOARCH=arm", "GOARM=7"},
 			args: []string{
+				"make",
 				"-defaultsh=",
 				"github.com/u-root/u-root/cmds/core/init",
 				"github.com/u-root/u-root/cmds/core/echo",
@@ -210,6 +212,7 @@ func TestUrootCmdline(t *testing.T) {
 			name: "ARM64 build",
 			env:  []string{"GOARCH=arm64"},
 			args: []string{
+				"make",
 				"-defaultsh=",
 				"github.com/u-root/u-root/cmds/core/init",
 				"github.com/u-root/u-root/cmds/core/echo",
@@ -219,6 +222,7 @@ func TestUrootCmdline(t *testing.T) {
 			name: "RISCV 64bit build",
 			env:  []string{"GOARCH=riscv64"},
 			args: []string{
+				"make",
 				"-defaultsh=",
 				"github.com/u-root/u-root/cmds/core/init",
 				"github.com/u-root/u-root/cmds/core/echo",
@@ -227,6 +231,7 @@ func TestUrootCmdline(t *testing.T) {
 		{
 			name: "build invalid",
 			args: []string{
+				"make",
 				"-build=source",
 				"github.com/u-root/u-root/cmds/core/init",
 				"github.com/u-root/u-root/cmds/core/echo",
@@ -237,6 +242,7 @@ func TestUrootCmdline(t *testing.T) {
 			name: "arch invalid preserves temp dir",
 			env:  []string{"GOARCH=doesnotexist"},
 			args: []string{
+				"make",
 				"--defaultsh=echo",
 				"github.com/u-root/u-root/cmds/core/echo",
 				"github.com/u-root/u-root/cmds/core/init",
@@ -247,6 +253,7 @@ func TestUrootCmdline(t *testing.T) {
 		{
 			name: "specify temp dir",
 			args: []string{
+				"make",
 				"--tmp-dir=" + tempDir,
 				"github.com/u-root/u-root/cmds/core/echo",
 				"github.com/u-root/u-root/cmds/core/init",
@@ -256,7 +263,12 @@ func TestUrootCmdline(t *testing.T) {
 		},
 		{
 			name: "template config",
-			args: []string{"-config-file=./testdata/test-config.yaml", "-v", "-config=coreconf"},
+			args: []string{
+				"make",
+				"-config-file=./testdata/test-config.yaml",
+				"-v",
+				"-config=coreconf",
+			},
 			validators: []itest.ArchiveValidator{
 				itest.HasRecord{R: cpio.CharDev("dev/tty", 0o666, 5, 0)},
 				itest.HasFile{Path: "bbin/bb"},
@@ -275,7 +287,12 @@ func TestUrootCmdline(t *testing.T) {
 		},
 		{
 			name: "template command",
-			args: []string{"-config-file=./testdata/test-config.yaml", "-v", "core"},
+			args: []string{
+				"make",
+				"-config-file=./testdata/test-config.yaml",
+				"-v",
+				"core",
+			},
 			validators: []itest.ArchiveValidator{
 				itest.HasRecord{R: cpio.CharDev("dev/tty", 0o666, 5, 0)},
 				itest.HasFile{Path: "bbin/bb"},
@@ -286,27 +303,27 @@ func TestUrootCmdline(t *testing.T) {
 		},
 		{
 			name:     "template config not found",
-			args:     []string{"-config-file=./testdata/test-config.yaml", "-v", "-config=foobar"},
+			args:     []string{"make", "-config-file=./testdata/test-config.yaml", "-v", "-config=foobar"},
 			exitCode: 1,
 		},
 		{
 			name:     "builder not found",
-			args:     []string{"-v", "build=source"},
+			args:     []string{"make", "-v", "build=source"},
 			exitCode: 1,
 		},
 		{
 			name:     "template file not found",
-			args:     []string{"-v", "-config-file=./testdata/doesnotexist"},
+			args:     []string{"make", "-v", "-config-file=./testdata/doesnotexist"},
 			exitCode: 1,
 		},
 		{
 			name:     "config not found with no default template",
-			args:     []string{"-v", "-config=foo"},
+			args:     []string{"make", "-v", "-config=foo"},
 			exitCode: 1,
 		},
 	}
 
-	for _, tt := range append(noCmdTests, bareTests...) {
+	for _, tt := range tests {
 		tt := tt
 		t.Run(tt.name, func(t *testing.T) {
 			var g errgroup.Group
@@ -334,11 +351,11 @@ func TestUrootCmdline(t *testing.T) {
 			var exitErr *exec.ExitError
 			if errors.As(err, &exitErr) {
 				if ec := exitErr.Sys().(syscall.WaitStatus).ExitStatus(); ec != tt.exitCode {
-					t.Errorf("mkuimage exit code = %d, want %d", ec, tt.exitCode)
+					t.Errorf("uimage exit code = %d, want %d", ec, tt.exitCode)
 				}
 				return
 			} else if err != nil {
-				t.Errorf("mkuimage failed: %v", err)
+				t.Errorf("uimage failed: %v", err)
 				return
 			}
 
@@ -370,8 +387,10 @@ func buildIt(t *testing.T, execPath string, args, env []string, gocoverdir strin
 
 	// Use the u-root command outside of the $GOPATH tree to make sure it
 	// still works.
-	args = append([]string{"-o", initramfs.Name()}, args...)
-	t.Logf("Commandline: %v mkuimage %v", strings.Join(env, " "), strings.Join(args, " "))
+	if len(args) > 0 {
+		args = append(append([]string{args[0]}, "-o", initramfs.Name()), args[1:]...)
+	}
+	t.Logf("Commandline: %v uimage %v", strings.Join(env, " "), strings.Join(args, " "))
 
 	c := exec.Command(execPath, args...)
 	c.Env = append(os.Environ(), env...)
@@ -388,24 +407,3 @@ func buildIt(t *testing.T, execPath string, args, env []string, gocoverdir strin
 	}
 	return initramfs, string(out), h1.Sum(nil), nil
 }
-
-func TestCheckArgs(t *testing.T) {
-	for _, tt := range []struct {
-		name string
-		args []string
-		err  error
-	}{
-		{"-files is only arg", []string{"-files"}, errEmptyFilesArg},
-		{"-files followed by -files", []string{"-files", "-files"}, errEmptyFilesArg},
-		{"-files followed by any other switch", []string{"-files", "-abc"}, errEmptyFilesArg},
-		{"no args", []string{}, nil},
-		{"u-root alone", []string{"u-root"}, nil},
-		{"u-root with -files and other args", []string{"u-root", "-files", "/bin/bash", "core"}, nil},
-	} {
-		t.Run(tt.name, func(t *testing.T) {
-			if err := checkArgs(tt.args...); !errors.Is(err, tt.err) {
-				t.Errorf("%q: got %v, want %v", tt.args, err, tt.err)
-			}
-		})
-	}
-}
diff --git a/cmd/mkuimage/testdata/test-config.yaml b/cmd/uimage/testdata/test-config.yaml
similarity index 100%
rename from cmd/mkuimage/testdata/test-config.yaml
rename to cmd/uimage/testdata/test-config.yaml
diff --git a/go.mod b/go.mod
index 084726a..f457b51 100644
--- a/go.mod
+++ b/go.mod
@@ -8,7 +8,7 @@ require (
 	github.com/hugelgupf/go-shlex v0.0.0-20200702092117-c80c9d0918fa
 	github.com/u-root/gobusybox/src v0.0.0-20240218001334-a32c1883bffa
 	github.com/u-root/u-root v0.12.0
-	github.com/u-root/uio v0.0.0-20240209044354-b3d14b93376a
+	github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701
 	golang.org/x/sync v0.6.0
 	golang.org/x/sys v0.16.0
 	golang.org/x/tools v0.17.0
diff --git a/go.sum b/go.sum
index 9eab4a4..e5279cf 100644
--- a/go.sum
+++ b/go.sum
@@ -46,6 +46,8 @@ github.com/u-root/u-root v0.12.0 h1:K0AuBFriwr0w/PGS3HawiAw89e3+MU7ks80GpghAsNs=
 github.com/u-root/u-root v0.12.0/go.mod h1:FYjTOh4IkIZHhjsd17lb8nYW6udgXdJhG1c0r6u0arI=
 github.com/u-root/uio v0.0.0-20240209044354-b3d14b93376a h1:BH1SOPEvehD2kVrndDnGJiUF0TrBpNs+iyYocu6h0og=
 github.com/u-root/uio v0.0.0-20240209044354-b3d14b93376a/go.mod h1:P3a5rG4X7tI17Nn3aOIAYr5HbIMukwXG0urG0WuL8OA=
+github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 h1:pyC9PaHYZFgEKFdlp3G8RaCKgVpHZnecvArXvPXcFkM=
+github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701/go.mod h1:P3a5rG4X7tI17Nn3aOIAYr5HbIMukwXG0urG0WuL8OA=
 github.com/ulikunitz/xz v0.5.11 h1:kpFauv27b6ynzBNT/Xy+1k+fK4WswhN/6PN5WhFAGw8=
 github.com/ulikunitz/xz v0.5.11/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
 github.com/vishvananda/netlink v1.2.1-beta.2 h1:Llsql0lnQEbHj0I1OuKyp8otXp0r3q0mPkuhwHfStVs=
diff --git a/uimage/builder/binary_test.go b/uimage/builder/binary_test.go
index 2697684..5f8d933 100644
--- a/uimage/builder/binary_test.go
+++ b/uimage/builder/binary_test.go
@@ -17,7 +17,7 @@ func TestBinaryBuild(t *testing.T) {
 	opts := Opts{
 		Env: golang.Default(golang.DisableCGO()),
 		Packages: []string{
-			"../../cmd/mkuimage",
+			"../../cmd/uimage",
 			"github.com/u-root/u-root/cmds/core/init",
 			"cmd/test2json",
 		},
@@ -30,7 +30,7 @@ func TestBinaryBuild(t *testing.T) {
 	}
 
 	mustContain := []string{
-		"bin/mkuimage",
+		"bin/uimage",
 		"bin/test2json",
 		"bin/init",
 	}
diff --git a/uimage/builder/gbb_test.go b/uimage/builder/gbb_test.go
index b7bee6a..f4fdfa4 100644
--- a/uimage/builder/gbb_test.go
+++ b/uimage/builder/gbb_test.go
@@ -21,7 +21,7 @@ func TestGBBBuild(t *testing.T) {
 	opts := Opts{
 		Env: golang.Default(golang.DisableCGO()),
 		Packages: []string{
-			"../../cmd/mkuimage",
+			"../../cmd/uimage",
 		},
 		TempDir: dir,
 	}
@@ -32,7 +32,7 @@ func TestGBBBuild(t *testing.T) {
 	}
 
 	mustContain := []string{
-		"bbin/mkuimage",
+		"bbin/uimage",
 		"bbin/bb",
 	}
 	for _, name := range mustContain {
@@ -53,7 +53,7 @@ func TestGBBBuildError(t *testing.T) {
 			opts: Opts{
 				Env: golang.Default(golang.DisableCGO()),
 				Packages: []string{
-					"../../cmd/mkuimage",
+					"../../cmd/uimage",
 				},
 				BinaryDir: "bbin",
 			},
@@ -63,7 +63,7 @@ func TestGBBBuildError(t *testing.T) {
 			opts: Opts{
 				TempDir: t.TempDir(),
 				Packages: []string{
-					"../../cmd/mkuimage",
+					"../../cmd/uimage",
 				},
 				BinaryDir: "bbin",
 			},
@@ -74,7 +74,7 @@ func TestGBBBuildError(t *testing.T) {
 				Env:     golang.Default(golang.DisableCGO()),
 				TempDir: t.TempDir(),
 				Packages: []string{
-					"../../cmd/mkuimage",
+					"../../cmd/uimage",
 				},
 				BinaryDir: "bbin",
 			},
@@ -88,12 +88,12 @@ func TestGBBBuildError(t *testing.T) {
 				Env:     golang.Default(golang.DisableCGO()),
 				TempDir: t.TempDir(),
 				Packages: []string{
-					"../../cmd/mkuimage",
+					"../../cmd/uimage",
 				},
 				BinaryDir: "bbin",
 			},
 			files: []cpio.Record{
-				cpio.StaticFile("bbin/mkuimage", "", 0o777),
+				cpio.StaticFile("bbin/uimage", "", 0o777),
 			},
 			want: os.ErrExist,
 		},
@@ -102,12 +102,12 @@ func TestGBBBuildError(t *testing.T) {
 				Env:     golang.Default(golang.DisableCGO()),
 				TempDir: t.TempDir(),
 				Packages: []string{
-					"../../cmd/mkuimage",
+					"../../cmd/uimage",
 				},
 				BinaryDir: "bbin",
 			},
 			files: []cpio.Record{
-				cpio.StaticFile("bbin/mkuimage", "", 0o777),
+				cpio.StaticFile("bbin/uimage", "", 0o777),
 			},
 			gbb:  GBBBuilder{ShellBang: true},
 			want: os.ErrExist,
diff --git a/uimage/mkuimage/cmd.go b/uimage/mkuimage/cmd.go
index 1f4dc88..421e7a8 100644
--- a/uimage/mkuimage/cmd.go
+++ b/uimage/mkuimage/cmd.go
@@ -11,6 +11,7 @@ import (
 	"runtime"
 	"strings"
 
+	"github.com/dustin/go-humanize"
 	"github.com/u-root/mkuimage/uimage"
 	"github.com/u-root/mkuimage/uimage/builder"
 	"github.com/u-root/mkuimage/uimage/templates"
@@ -113,5 +114,12 @@ func CreateUimage(l *llog.Logger, base []uimage.Modifier, tf *TemplateFlags, f *
 		l.Errorf("Preserving temp dir due to busybox build error")
 		keepTempDir = true
 	}
-	return err
+	if err != nil {
+		return err
+	}
+
+	if stat, err := os.Stat(f.OutputFile); err == nil && f.ArchiveFormat == "cpio" {
+		l.Infof("Successfully built %q (size %d bytes -- %s).", f.OutputFile, stat.Size(), humanize.IBytes(uint64(stat.Size())))
+	}
+	return nil
 }

From 440ed193394b696d2b31a1011a43b8efced81734 Mon Sep 17 00:00:00 2001
From: Chris Koch <chrisko@google.com>
Date: Sat, 24 Feb 2024 01:15:12 +0000
Subject: [PATCH 2/3] Update README for mkuimage->uimage make

Signed-off-by: Chris Koch <chrisko@google.com>
---
 README.md | 38 +++++++++++++++++++-------------------
 1 file changed, 19 insertions(+), 19 deletions(-)

diff --git a/README.md b/README.md
index 35933c4..ce0db9c 100644
--- a/README.md
+++ b/README.md
@@ -27,27 +27,27 @@ $ go1.21.5 version
 # Now use go1.21.5 in place of go
 ```
 
-Download and install mkuimage either via git:
+Download and install uimage either via git:
 
 ```shell
 git clone https://github.com/u-root/mkuimage
-cd mkuimage/cmd/mkuimage
+cd mkuimage/cmd/uimage
 go install
 ```
 
 Or install directly with go:
 
 ```shell
-go install github.com/u-root/mkuimage/cmd/mkuimage@latest
+go install github.com/u-root/mkuimage/cmd/uimage@latest
 ```
 
 > [!NOTE]
-> The `mkuimage` command will end up in `$GOPATH/bin/mkuimage`, so you may
+> The `uimage` command will end up in `$GOPATH/bin/uimage`, so you may
 > need to add `$GOPATH/bin` to your `$PATH`.
 
 ## Examples
 
-Here are some examples of using the `mkuimage` command to build an initramfs.
+Here are some examples of using the `uimage` command to build an initramfs.
 
 ```shell
 git clone https://github.com/u-root/u-root
@@ -57,7 +57,7 @@ git clone https://github.com/u-root/cpu
 Build gobusybox binaries of these two commands and add to initramfs:
 
 ```shell
-$ mkuimage ./u-root/cmds/core/{init,gosh}
+$ uimage make ./u-root/cmds/core/{init,gosh}
 
 $ cpio -ivt < /tmp/initramfs.linux_amd64.cpio
 ...
@@ -70,7 +70,7 @@ lrwxrwxrwx   0 root     root            2 Jan  1  1970 bbin/init -> bb
 Add symlinks for shell and init:
 
 ```shell
-$ mkuimage -initcmd=init -defaultsh=gosh ./u-root/cmds/core/{init,gosh}
+$ uimage make -initcmd=init -defaultsh=gosh ./u-root/cmds/core/{init,gosh}
 
 $ cpio -ivt < /tmp/initramfs.linux_amd64.cpio
 ...
@@ -84,7 +84,7 @@ lrwxrwxrwx   0 root     root            9 Jan  1  1970 init -> bbin/init
 Build everything from core without ls and losetup:
 
 ```shell
-$ mkuimage ./u-root/cmds/core/* -./u-root/cmds/core/{ls,losetup}
+$ uimage make ./u-root/cmds/core/* -./u-root/cmds/core/{ls,losetup}
 ```
 
 Build an initramfs with init, gosh and cpud in a gobusybox binary:
@@ -99,7 +99,7 @@ Build an initramfs with init, gosh and cpud in a gobusybox binary:
 > To properly resolve these dependencies, head down to the [multi-module uimages section](#multi-module-uimages).
 
 ```shell
-$ mkuimage ./u-root/cmds/core/{init,gosh} ./cpu/cmds/cpud
+$ uimage make ./u-root/cmds/core/{init,gosh} ./cpu/cmds/cpud
 ...
 01:24:15 INFO GBB_STRICT is not set.
 01:24:15 INFO [WARNING] github.com/u-root/cpu/cmds/cpud depends on github.com/u-root/u-root @ version v0.11.1-0.20230913033713-004977728a9d
@@ -115,12 +115,12 @@ lrwxrwxrwx   0 root     root            2 Jan  1  1970 bbin/init -> bb
 ...
 ```
 
-`GBB_PATH` is a place that mkuimage will look for commands. Each colon-separated
+`GBB_PATH` is a place that uimage will look for commands. Each colon-separated
 `GBB_PATH` element is concatenated with patterns from the command-line and
 checked for existence. For example:
 
 ```shell
-GBB_PATH=$(pwd)/u-root:$(pwd)/cpu mkuimage \
+GBB_PATH=$(pwd)/u-root:$(pwd)/cpu uimage make \
     cmds/core/{init,gosh} \
     cmds/cpud
 
@@ -137,7 +137,7 @@ If you add binaries with `-files` are listed, their ldd dependencies will be
 included as well.
 
 ```shell
-$ mkuimage -files /bin/bash
+$ uimage make -files /bin/bash
 
 $ cpio -ivt < /tmp/initramfs.linux_amd64.cpio
 ...
@@ -156,7 +156,7 @@ lrwxrwxrwx   0 root     root           42 Jan  1  1970 lib64/ld-linux-x86-64.so.
 You can determine placement with colons:
 
 ```shell
-$ mkuimage -files "/bin/bash:sbin/sh"
+$ uimage make -files "/bin/bash:sbin/sh"
 
 $ cpio -ivt < /tmp/initramfs.linux_amd64.cpio
 ...
@@ -168,7 +168,7 @@ For example on Debian, if you want to add two kernel modules for testing,
 executing your currently booted kernel:
 
 ```shell
-$ mkuimage -files "$HOME/hello.ko:etc/hello.ko" -files "$HOME/hello2.ko:etc/hello2.ko" ./u-root/cmds/core/*
+$ uimage make -files "$HOME/hello.ko:etc/hello.ko" -files "$HOME/hello2.ko:etc/hello2.ko" ./u-root/cmds/core/*
 $ qemu-system-x86_64 -kernel /boot/vmlinuz-$(uname -r) -initrd /tmp/initramfs.linux_amd64.cpio
 ```
 
@@ -177,7 +177,7 @@ $ qemu-system-x86_64 -kernel /boot/vmlinuz-$(uname -r) -initrd /tmp/initramfs.li
 To cross compile for an ARM, on Linux:
 
 ```shell
-GOARCH=arm mkuimage ./u-root/cmds/core/*
+GOARCH=arm uimage make ./u-root/cmds/core/*
 ```
 
 If you are on OSX, and wish to build for Linux on AMD64:
@@ -213,7 +213,7 @@ has native uimage support.
 
 ## Multi-module uimages
 
-Rather than having mkuimage decide how to resolve dependencies across
+Rather than having uimage decide how to resolve dependencies across
 multi-module repositories, you may also create a go.mod with all commands you
 intend to use in them.
 
@@ -247,7 +247,7 @@ mod tidy` to add these dependencies to `go.mod`:
 ```sh
 go mod tidy
 
-mkuimage \
+uimage make \
   github.com/u-root/u-root/cmds/core/ip \
   github.com/u-root/u-root/cmds/core/init \
   github.com/hugelgupf/p9/cmd/p9ufs
@@ -255,12 +255,12 @@ mkuimage \
 
 ## Build Modes
 
-mkuimage can create an initramfs in two different modes, specified by `-build`:
+uimage can create an initramfs in two different modes, specified by `-build`:
 
 *   `bb` mode: One busybox-like binary comprising all the Go tools you ask to
     include.
     See [the gobusybox README for how it works](https://github.com/u-root/gobusybox).
-    In this mode, mkuimage copies and rewrites the source of the tools you asked
+    In this mode, uimage copies and rewrites the source of the tools you asked
     to include to be able to compile everything into one busybox-like binary.
 
 *   `binary` mode: each specified binary is compiled separately and all binaries

From 1d7d9b00b3120d483ab814edb9fb3f78ff59c866 Mon Sep 17 00:00:00 2001
From: Chris Koch <chrisko@google.com>
Date: Sat, 24 Feb 2024 01:40:26 +0000
Subject: [PATCH 3/3] list and listconfigs commands

Signed-off-by: Chris Koch <chrisko@google.com>
---
 cmd/uimage/main.go | 75 +++++++++++++++++++++++++++++++++++++++++++++-
 go.mod             |  1 +
 go.sum             |  2 --
 3 files changed, 75 insertions(+), 3 deletions(-)

diff --git a/cmd/uimage/main.go b/cmd/uimage/main.go
index 54831f7..f61531e 100644
--- a/cmd/uimage/main.go
+++ b/cmd/uimage/main.go
@@ -10,12 +10,15 @@ import (
 	"log"
 	"log/slog"
 	"os"
+	"slices"
 
+	"github.com/u-root/gobusybox/src/pkg/bb/findpkg"
 	"github.com/u-root/gobusybox/src/pkg/golang"
 	"github.com/u-root/mkuimage/uimage"
 	"github.com/u-root/mkuimage/uimage/mkuimage"
 	"github.com/u-root/uio/cli"
 	"github.com/u-root/uio/llog"
+	"golang.org/x/exp/maps"
 )
 
 func main() {
@@ -50,7 +53,77 @@ func main() {
 	f.RegisterFlags(makeCmd.Flags())
 	tf.RegisterFlags(makeCmd.Flags())
 
-	app := cli.App{makeCmd}
+	listconfigsCmd := cli.Command{
+		Name:  "listconfigs",
+		Short: "list template configs",
+		Run: func(args []string) {
+			tpl, err := tf.Get()
+			if err != nil {
+				l.Errorf("Failed to get template: %w", err)
+				os.Exit(1)
+			}
+			configs := maps.Keys(tpl.Configs)
+			slices.Sort(configs)
+			for _, name := range configs {
+				fmt.Println(name)
+			}
+		},
+	}
+	l.RegisterVerboseFlag(listconfigsCmd.Flags(), "v", slog.LevelDebug)
+	tf.RegisterFlags(listconfigsCmd.Flags())
+
+	listCmd := cli.Command{
+		Name:  "list",
+		Short: "list commands from template (no args: lists all cmds in template)",
+		Run: func(args []string) {
+			tpl, err := tf.Get()
+			if err != nil {
+				l.Errorf("Failed to get template: %w", err)
+				os.Exit(1)
+			}
+			var cmds []string
+			if tf.Config == "" && len(args) == 0 {
+				for _, conf := range tpl.Configs {
+					for _, c := range conf.Commands {
+						cmds = append(cmds, tpl.CommandsFor(c.Commands...)...)
+					}
+				}
+				for _, c := range tpl.Commands {
+					cmds = append(cmds, c...)
+				}
+			}
+			if tf.Config != "" {
+				if _, ok := tpl.Configs[tf.Config]; !ok {
+					l.Errorf("Config %s not found", tf.Config)
+					os.Exit(1)
+				}
+				for _, c := range tpl.Configs[tf.Config].Commands {
+					cmds = append(cmds, tpl.CommandsFor(c.Commands...)...)
+				}
+			}
+			cmds = append(cmds, tpl.CommandsFor(args...)...)
+
+			lookupEnv := findpkg.DefaultEnv()
+			paths, err := findpkg.ResolveGlobs(l.AtLevel(slog.LevelInfo), env, lookupEnv, cmds)
+			if err != nil {
+				l.Errorf("Failed to resolve commands: %v", err)
+				os.Exit(1)
+			}
+			uniquePaths := map[string]struct{}{}
+			for _, p := range paths {
+				uniquePaths[p] = struct{}{}
+			}
+			ps := maps.Keys(uniquePaths)
+			slices.Sort(ps)
+			for _, p := range ps {
+				fmt.Println(p)
+			}
+		},
+	}
+	l.RegisterVerboseFlag(listCmd.Flags(), "v", slog.LevelDebug)
+	tf.RegisterFlags(listCmd.Flags())
+
+	app := cli.App{makeCmd, listconfigsCmd, listCmd}
 	app.Run(os.Args)
 }
 
diff --git a/go.mod b/go.mod
index f457b51..89e0fa7 100644
--- a/go.mod
+++ b/go.mod
@@ -9,6 +9,7 @@ require (
 	github.com/u-root/gobusybox/src v0.0.0-20240218001334-a32c1883bffa
 	github.com/u-root/u-root v0.12.0
 	github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701
+	golang.org/x/exp v0.0.0-20231219180239-dc181d75b848
 	golang.org/x/sync v0.6.0
 	golang.org/x/sys v0.16.0
 	golang.org/x/tools v0.17.0
diff --git a/go.sum b/go.sum
index e5279cf..be86361 100644
--- a/go.sum
+++ b/go.sum
@@ -44,8 +44,6 @@ github.com/u-root/gobusybox/src v0.0.0-20240218001334-a32c1883bffa h1:NNmn/fsvgA
 github.com/u-root/gobusybox/src v0.0.0-20240218001334-a32c1883bffa/go.mod h1:vN1IwhlCo7gTDTJDUs6WCKM4/C2uiq5w0XvZCqLtb5s=
 github.com/u-root/u-root v0.12.0 h1:K0AuBFriwr0w/PGS3HawiAw89e3+MU7ks80GpghAsNs=
 github.com/u-root/u-root v0.12.0/go.mod h1:FYjTOh4IkIZHhjsd17lb8nYW6udgXdJhG1c0r6u0arI=
-github.com/u-root/uio v0.0.0-20240209044354-b3d14b93376a h1:BH1SOPEvehD2kVrndDnGJiUF0TrBpNs+iyYocu6h0og=
-github.com/u-root/uio v0.0.0-20240209044354-b3d14b93376a/go.mod h1:P3a5rG4X7tI17Nn3aOIAYr5HbIMukwXG0urG0WuL8OA=
 github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 h1:pyC9PaHYZFgEKFdlp3G8RaCKgVpHZnecvArXvPXcFkM=
 github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701/go.mod h1:P3a5rG4X7tI17Nn3aOIAYr5HbIMukwXG0urG0WuL8OA=
 github.com/ulikunitz/xz v0.5.11 h1:kpFauv27b6ynzBNT/Xy+1k+fK4WswhN/6PN5WhFAGw8=