Add script to construct (minimal) SDK from a Fuchsia build

This adds a script that combines portions of a Fuchsia build into a
minimally useful SDK. To start, the script assumes a Linux host and
x86-64 target, although this will be easy to extend. The scripts
assumes it is run from a working Fuchsia checkout that has at least the
sysroot built and produces a single tarball that may contain:

- toolchain (clang)
- toolchain libraries (compiler-rt)
- sysroot
- kernel image
- debug symbols for kernel image
- qemu binary
- tools from magneta (mkbootfs, netcp, netruncmd, etc)

Change-Id: I0c08dcaba95c253fc2233f63a8649790d50820ab
diff --git a/.gitignore b/.gitignore
index 0d20b64..8c4722d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,2 @@
 *.pyc
+fuchsia-sdk.tgz
diff --git a/makesdk.go b/makesdk.go
new file mode 100755
index 0000000..a0aa56f
--- /dev/null
+++ b/makesdk.go
@@ -0,0 +1,190 @@
+///bin/true ; exec /usr/bin/env go run "$0" "$@"
+package main
+
+import (
+	"flag"
+	"fmt"
+	"io/ioutil"
+	"log"
+	"os"
+	"os/exec"
+	"path/filepath"
+)
+
+var output = flag.String("output", "fuchsia-sdk.tgz", "Output name")
+var toolchain = flag.Bool("toolchain", false, "Include toolchain")
+var toolchainLibs = flag.Bool("toolchain-lib", true, "Include toolchain libraries in SDK. Typically used when --toolchain is false")
+var sysroot = flag.Bool("sysroot", true, "Include sysroot")
+var kernelImg = flag.Bool("kernel-img", true, "Include kernel image")
+var kernelDebugObjs = flag.Bool("kernel-dbg", true, "Include kernel objects with debug symbols")
+var qemu = flag.Bool("qemu", true, "Include QEMU binary")
+var tools = flag.Bool("tools", true, "Include additional tools")
+var verbose = flag.Bool("v", false, "Verbose output")
+var dryRun = flag.Bool("n", false, "Dry run - print what would happen but don't actually do it")
+
+type compType int
+
+const (
+	dir compType = iota
+	file
+	custom
+)
+
+type component struct {
+	flag      *bool  // Flag controlling whether this component should be included
+	srcPrefix string // Source path prefix relative to the fuchsia root
+	dstPrefix string // Destination path prefix relative to the SDK root
+	t         compType
+	f         func(src, dst string) // When t is 'custom', function to run to copy
+}
+
+var components = []component{
+	{
+		toolchain,
+		"buildtools/toolchain",
+		"toolchain",
+		dir,
+		nil,
+	},
+	{
+		toolchainLibs,
+		"buildtools/toolchain/clang+llvm-x86_64-linux/lib/clang/5.0.0/lib/fuchsia",
+		"toolchain_libs/clang/5.0.0/lib/fuchsia",
+		dir,
+		nil,
+	},
+	{
+		sysroot,
+		"out/sysroot",
+		"sysroot",
+		dir,
+		nil,
+	},
+	{
+		kernelImg,
+		"out/build-magenta/build-magenta-pc-x86-64/magenta.bin",
+		"kernel/magenta.bin",
+		file,
+		nil,
+	},
+	{
+		kernelDebugObjs,
+		"out/build-magenta/build-magenta-pc-x86-64",
+		"kernel/debug",
+		custom,
+		copyKernelDebugObjs,
+	},
+	{
+		qemu,
+		"buildtools/qemu",
+		"qemu",
+		dir,
+		nil,
+	},
+	{
+		tools,
+		"out/build-magenta/tools",
+		"tools",
+		dir,
+		nil,
+	},
+}
+
+func copyKernelDebugObjs(src, dstPrefix string) {
+	// The kernel debug information lives in many .elf files in the out directory
+	filepath.Walk(src, func(path string, info os.FileInfo, err error) error {
+		if !info.IsDir() && filepath.Ext(path) == ".elf" {
+			dst := filepath.Join(dstPrefix, path[len(src):])
+			mkdir(filepath.Dir(dst))
+			cp(path, dst)
+		}
+		return nil
+	})
+	cp(filepath.Join(src, "ids.txt"), filepath.Join(dstPrefix, "ids.txt"))
+}
+
+func mkdir(dir string) {
+	if *verbose || *dryRun {
+		fmt.Println("Making directory", dir)
+	}
+	if *dryRun {
+		return
+	}
+	_, err := exec.Command("mkdir", "-p", dir).Output()
+	if err != nil {
+		log.Fatal("could not create directory", dir)
+	}
+}
+
+func cp(args ...string) {
+	if *verbose || *dryRun {
+		fmt.Println("Copying", args)
+	}
+	if *dryRun {
+		return
+	}
+	out, err := exec.Command("cp", args...).CombinedOutput()
+	if err != nil {
+		log.Fatal("cp failed with output", string(out), "error", err)
+	}
+}
+
+func copyFile(src, dst string) {
+	mkdir(filepath.Dir(dst))
+	cp(src, dst)
+}
+
+func copyDir(src, dst string) {
+	mkdir(filepath.Dir(dst))
+	cp("-r", src, dst)
+}
+
+func tar(src, dst string) {
+	if *verbose || *dryRun {
+		fmt.Println("Archiving", src, "to", dst)
+	}
+	if *dryRun {
+		return
+	}
+	out, err := exec.Command("tar", "cvzf", dst, "-C", src, ".").Output()
+	if err != nil {
+		log.Fatal("tar failed with output", string(out), "error", err)
+	}
+}
+
+func main() {
+	flag.Usage = func() {
+		fmt.Fprintf(os.Stderr, `Usage: ./makesdk.go [flags] /path/to/fuchsia/root
+
+This script creates a Fuchsia SDK containing the specified features and places it into a tarball.
+`)
+		flag.PrintDefaults()
+	}
+	flag.Parse()
+	fuchsiaRoot := flag.Arg(0)
+	if _, err := os.Stat(fuchsiaRoot); os.IsNotExist(err) {
+		flag.Usage()
+		log.Fatalf("Fuchsia root not found at \"%v\"\n", fuchsiaRoot)
+	}
+	tmpSdk, err := ioutil.TempDir("", "fuchsia-sdk")
+	if err != nil {
+		log.Fatal("Could not create temporary directory: ", err)
+	}
+	defer os.RemoveAll(tmpSdk)
+
+	for _, c := range components {
+		if *c.flag {
+			src := filepath.Join(fuchsiaRoot, c.srcPrefix)
+			dst := filepath.Join(tmpSdk, c.dstPrefix)
+			switch c.t {
+			case dir:
+				copyDir(src, dst)
+			case file:
+				copyFile(src, dst)
+			case custom:
+				c.f(src, dst)
+			}
+		}
+	}
+	tar(tmpSdk, *output)
+}