Commit 803f0a295b

Marc Tiehuis <marctiehuis@gmail.com>
2018-04-12 12:23:58
Revise self-hosted command line interface
Commands are now separated more precisely from one another. Arguments are parsed mostly using a custom argument parser instead of manually. This should be on parity feature-wise with the previous main.zig but adds a few extra code-paths as well that were not yet implemented. Subcommands are much more prominent and consistent. The first argument is always a sub-command and then all following arguments refer to that command. Different commands display there own usage messages and options based on what they can do instead of a one-for-all usage message that was only applicable for the build commands previously. The `cc` command is added and is intended for driving a c compiler. See #490. This is currently a wrapper over the system cc and assumes that it exists, but it should suffice as a starting point.
1 parent 281c17f
Changed files (5)
src-self-hosted/arg.zig
@@ -0,0 +1,284 @@
+const std = @import("std");
+const debug = std.debug;
+const mem = std.mem;
+
+const Allocator = mem.Allocator;
+const ArrayList = std.ArrayList;
+const HashMap = std.HashMap;
+
+fn trimStart(slice: []const u8, ch: u8) []const u8 {
+    var i: usize = 0;
+    for (slice) |b| {
+        if (b != '-') break;
+        i += 1;
+    }
+
+    return slice[i..];
+}
+
+fn argInAllowedSet(maybe_set: ?[]const []const u8, arg: []const u8) bool {
+    if (maybe_set) |set| {
+        for (set) |possible| {
+            if (mem.eql(u8, arg, possible)) {
+                return true;
+            }
+        }
+        return false;
+    } else {
+        return true;
+    }
+}
+
+// Modifies the current argument index during iteration
+fn readFlagArguments(allocator: &Allocator, args: []const []const u8, required: usize,
+                     allowed_set: ?[]const []const u8, index: &usize) !FlagArg {
+
+    switch (required) {
+        0 => return FlagArg { .None = undefined },  // TODO: Required to force non-tag but value?
+        1 => {
+            if (*index + 1 >= args.len) {
+                return error.MissingFlagArguments;
+            }
+
+            *index += 1;
+            const arg = args[*index];
+
+            if (!argInAllowedSet(allowed_set, arg)) {
+                return error.ArgumentNotInAllowedSet;
+            }
+
+            return FlagArg { .Single = arg };
+        },
+        else => |needed| {
+            var extra = ArrayList([]const u8).init(allocator);
+            errdefer extra.deinit();
+
+            var j: usize = 0;
+            while (j < needed) : (j += 1) {
+                if (*index + 1 >= args.len) {
+                    return error.MissingFlagArguments;
+                }
+
+                *index += 1;
+                const arg = args[*index];
+
+                if (!argInAllowedSet(allowed_set, arg)) {
+                    return error.ArgumentNotInAllowedSet;
+                }
+
+                try extra.append(arg);
+            }
+
+            return FlagArg { .Many = extra };
+        },
+    }
+}
+
+const HashMapFlags = HashMap([]const u8, FlagArg, std.hash.Fnv1a_32.hash, mem.eql_slice_u8);
+
+// A store for querying found flags and positional arguments.
+pub const Args = struct {
+    flags: HashMapFlags,
+    positionals: ArrayList([]const u8),
+
+    pub fn parse(allocator: &Allocator, comptime spec: []const Flag, args: []const []const u8) !Args {
+        var parsed = Args {
+            .flags = HashMapFlags.init(allocator),
+            .positionals = ArrayList([]const u8).init(allocator),
+        };
+
+        var i: usize = 0;
+        next: while (i < args.len) : (i += 1) {
+            const arg = args[i];
+
+            if (arg.len != 0 and arg[0] == '-') {
+                // TODO: hashmap, although the linear scan is okay for small argument sets as is
+                for (spec) |flag| {
+                    if (mem.eql(u8, arg, flag.name)) {
+                        const flag_name_trimmed = trimStart(flag.name, '-');
+                        const flag_args = readFlagArguments(allocator, args, flag.required, flag.allowed_set, &i) catch |err| {
+                            switch (err) {
+                                error.ArgumentNotInAllowedSet => {
+                                    std.debug.warn("argument '{}' is invalid for flag '{}'\n", args[i], arg);
+                                    std.debug.warn("allowed options are ");
+                                    for (??flag.allowed_set) |possible| {
+                                        std.debug.warn("'{}' ", possible);
+                                    }
+                                    std.debug.warn("\n");
+                                },
+                                error.MissingFlagArguments => {
+                                    std.debug.warn("missing argument for flag: {}\n", arg);
+                                },
+                                else => {},
+                            }
+
+                            return err;
+                        };
+
+                        if (flag.mergable) {
+                            var prev =
+                                if (parsed.flags.get(flag_name_trimmed)) |entry|
+                                    entry.value.Many
+                                else
+                                    ArrayList([]const u8).init(allocator);
+
+                            // MergeN creation disallows 0 length flag entry (doesn't make sense)
+                            switch (flag_args) {
+                                FlagArg.None => unreachable,
+                                FlagArg.Single => |inner| try prev.append(inner),
+                                FlagArg.Many => |inner| try prev.appendSlice(inner.toSliceConst()),
+                            }
+
+                            _ = try parsed.flags.put(flag_name_trimmed, FlagArg { .Many = prev });
+                        } else {
+                            _ = try parsed.flags.put(flag_name_trimmed, flag_args);
+                        }
+
+                        continue :next;
+                    }
+                }
+
+                // TODO: Better errors with context, global error state and return is sufficient.
+                std.debug.warn("could not match flag: {}\n", arg);
+                return error.UnknownFlag;
+            } else {
+                try parsed.positionals.append(arg);
+            }
+        }
+
+        return parsed;
+    }
+
+    pub fn deinit(self: &Args) void {
+        self.flags.deinit();
+        self.positionals.deinit();
+    }
+
+    // e.g. --help
+    pub fn present(self: &Args, name: []const u8) bool {
+        return self.flags.contains(name);
+    }
+
+    // e.g. --name value
+    pub fn single(self: &Args, name: []const u8) ?[]const u8 {
+        if (self.flags.get(name)) |entry| {
+            switch (entry.value) {
+                FlagArg.Single => |inner| { return inner; },
+                else => @panic("attempted to retrieve flag with wrong type"),
+            }
+        } else {
+            return null;
+        }
+    }
+
+    // e.g. --names value1 value2 value3
+    pub fn many(self: &Args, name: []const u8) ?[]const []const u8 {
+        if (self.flags.get(name)) |entry| {
+            switch (entry.value) {
+                FlagArg.Many => |inner| { return inner.toSliceConst(); },
+                else => @panic("attempted to retrieve flag with wrong type"),
+            }
+        } else {
+            return null;
+        }
+    }
+};
+
+// Arguments for a flag. e.g. arg1, arg2 in `--command arg1 arg2`.
+const FlagArg = union(enum) {
+    None,
+    Single: []const u8,
+    Many: ArrayList([]const u8),
+};
+
+// Specification for how a flag should be parsed.
+pub const Flag = struct {
+    name: []const u8,
+    required: usize,
+    mergable: bool,
+    allowed_set: ?[]const []const u8,
+
+    pub fn Bool(comptime name: []const u8) Flag {
+        return ArgN(name, 0);
+    }
+
+    pub fn Arg1(comptime name: []const u8) Flag {
+        return ArgN(name, 1);
+    }
+
+    pub fn ArgN(comptime name: []const u8, comptime n: usize) Flag {
+        return Flag {
+            .name = name,
+            .required = n,
+            .mergable = false,
+            .allowed_set = null,
+        };
+    }
+
+    pub fn ArgMergeN(comptime name: []const u8, comptime n: usize) Flag {
+        if (n == 0) {
+            @compileError("n must be greater than 0");
+        }
+
+        return Flag {
+            .name = name,
+            .required = n,
+            .mergable = true,
+            .allowed_set = null,
+        };
+    }
+
+    pub fn Option(comptime name: []const u8, comptime set: []const []const u8) Flag {
+        return Flag {
+            .name = name,
+            .required = 1,
+            .mergable = false,
+            .allowed_set = set,
+        };
+    }
+};
+
+test "parse arguments" {
+    const spec1 = comptime []const Flag {
+        Flag.Bool("--help"),
+        Flag.Bool("--init"),
+        Flag.Arg1("--build-file"),
+        Flag.Option("--color", []const []const u8 { "on", "off", "auto" }),
+        Flag.ArgN("--pkg-begin", 2),
+        Flag.ArgMergeN("--object", 1),
+        Flag.ArgN("--library", 1),
+    };
+
+    const cliargs = []const []const u8 {
+        "build",
+        "--help",
+        "pos1",
+        "--build-file", "build.zig",
+        "--object", "obj1",
+        "--object", "obj2",
+        "--library", "lib1",
+        "--library", "lib2",
+        "--color", "on",
+        "pos2",
+    };
+
+    var args = try Args.parse(std.debug.global_allocator, spec1, cliargs);
+
+    debug.assert(args.present("help"));
+    debug.assert(!args.present("help2"));
+    debug.assert(!args.present("init"));
+
+    debug.assert(mem.eql(u8, ??args.single("build-file"), "build.zig"));
+    debug.assert(mem.eql(u8, ??args.single("color"), "on"));
+
+    const objects = ??args.many("object");
+    debug.assert(mem.eql(u8, objects[0], "obj1"));
+    debug.assert(mem.eql(u8, objects[1], "obj2"));
+
+    debug.assert(mem.eql(u8, ??args.single("library"), "lib2"));
+
+    const pos = args.positionals.toSliceConst();
+    debug.assert(mem.eql(u8, pos[0], "build"));
+    debug.assert(mem.eql(u8, pos[1], "pos1"));
+    debug.assert(mem.eql(u8, pos[2], "pos2"));
+}
src-self-hosted/introspect.zig
@@ -0,0 +1,71 @@
+// Introspection and determination of system libraries needed by zig.
+
+const std = @import("std");
+const mem = std.mem;
+const os = std.os;
+
+const warn = std.debug.warn;
+
+/// Caller must free result
+pub fn testZigInstallPrefix(allocator: &mem.Allocator, test_path: []const u8) ![]u8 {
+    const test_zig_dir = try os.path.join(allocator, test_path, "lib", "zig");
+    errdefer allocator.free(test_zig_dir);
+
+    const test_index_file = try os.path.join(allocator, test_zig_dir, "std", "index.zig");
+    defer allocator.free(test_index_file);
+
+    var file = try os.File.openRead(allocator, test_index_file);
+    file.close();
+
+    return test_zig_dir;
+}
+
+/// Caller must free result
+pub fn findZigLibDir(allocator: &mem.Allocator) ![]u8 {
+    const self_exe_path = try os.selfExeDirPath(allocator);
+    defer allocator.free(self_exe_path);
+
+    var cur_path: []const u8 = self_exe_path;
+    while (true) {
+        const test_dir = os.path.dirname(cur_path);
+
+        if (mem.eql(u8, test_dir, cur_path)) {
+            break;
+        }
+
+        return testZigInstallPrefix(allocator, test_dir) catch |err| {
+            cur_path = test_dir;
+            continue;
+        };
+    }
+
+    // TODO look in hard coded installation path from configuration
+    //if (ZIG_INSTALL_PREFIX != nullptr) {
+    //    if (test_zig_install_prefix(buf_create_from_str(ZIG_INSTALL_PREFIX), out_path)) {
+    //        return 0;
+    //    }
+    //}
+
+    return error.FileNotFound;
+}
+
+pub fn resolveZigLibDir(allocator: &mem.Allocator, zig_install_prefix_arg: ?[]const u8) ![]u8 {
+    if (zig_install_prefix_arg) |zig_install_prefix| {
+        return testZigInstallPrefix(allocator, zig_install_prefix) catch |err| {
+            warn("No Zig installation found at prefix {}: {}\n", zig_install_prefix_arg, @errorName(err));
+            return error.ZigInstallationNotFound;
+        };
+    } else {
+        return findZigLibDir(allocator) catch |err| {
+            warn(
+                \\Unable to find zig lib directory: {}.
+                \\Reinstall Zig or use --zig-install-prefix.
+                \\
+                ,
+                @errorName(err)
+            );
+
+            return error.ZigLibDirNotFound;
+        };
+    }
+}
src-self-hosted/main.zig
@@ -1,731 +1,720 @@
 const std = @import("std");
-const mem = std.mem;
-const io = std.io;
-const os = std.os;
-const heap = std.heap;
-const warn = std.debug.warn;
-const assert = std.debug.assert;
-const target = @import("target.zig");
-const Target = target.Target;
-const Module = @import("module.zig").Module;
-const ErrColor = Module.ErrColor;
-const Emit = Module.Emit;
 const builtin = @import("builtin");
-const ArrayList = std.ArrayList;
-const c = @import("c.zig");
 
-const default_zig_cache_name = "zig-cache";
+const os = std.os;
+const io = std.io;
+const mem = std.mem;
+const Allocator = mem.Allocator;
+const ArrayList = std.ArrayList;
+const Buffer = std.Buffer;
 
-const Cmd = enum {
-    None,
-    Build,
-    Test,
-    Version,
-    Zen,
-    TranslateC,
-    Targets,
+const arg = @import("arg.zig");
+const c = @import("c.zig");
+const introspect = @import("introspect.zig");
+const Args = arg.Args;
+const Flag = arg.Flag;
+const Module = @import("module.zig").Module;
+const Target = @import("target.zig").Target;
+
+var stderr: &io.OutStream(io.FileOutStream.Error) = undefined;
+var stdout: &io.OutStream(io.FileOutStream.Error) = undefined;
+
+const usage =
+    \\usage: zig [command] [options]
+    \\
+    \\Commands:
+    \\
+    \\  build                        Build project from build.zig
+    \\  build-exe   [source]         Create executable from source or object files
+    \\  build-lib   [source]         Create library from source or object files
+    \\  build-obj   [source]         Create object from source or assembly
+    \\  cc          [args]           Call the system c compiler and pass args through
+    \\  fmt         [source]         Parse file and render in canonical zig format
+    \\  run         [source]         Create executable and run immediately
+    \\  targets                      List available compilation targets
+    \\  test        [source]         Create and run a test build
+    \\  translate-c [source]         Convert c code to zig code
+    \\  version                      Print version number and exit
+    \\  zen                          Print zen of zig and exit
+    \\
+    \\
+    ;
+
+const Command = struct {
+    name: []const u8,
+    exec: fn(&Allocator, []const []const u8) error!void,
 };
 
-fn badArgs(comptime format: []const u8, args: ...) noreturn {
-    var stderr = io.getStdErr() catch std.os.exit(1);
-    var stderr_stream_adapter = io.FileOutStream.init(&stderr);
-    const stderr_stream = &stderr_stream_adapter.stream;
-    stderr_stream.print(format ++ "\n\n", args) catch std.os.exit(1);
-    printUsage(&stderr_stream_adapter.stream) catch std.os.exit(1);
-    std.os.exit(1);
-}
-
 pub fn main() !void {
-    const allocator = std.heap.c_allocator;
+    var allocator = std.heap.c_allocator;
+
+    var stdout_file = try std.io.getStdOut();
+    var stdout_out_stream = std.io.FileOutStream.init(&stdout_file);
+    stdout = &stdout_out_stream.stream;
+
+    var stderr_file = try std.io.getStdErr();
+    var stderr_out_stream = std.io.FileOutStream.init(&stderr_file);
+    stderr = &stderr_out_stream.stream;
 
     const args = try os.argsAlloc(allocator);
     defer os.argsFree(allocator, args);
 
-    if (args.len >= 2 and mem.eql(u8, args[1], "build")) {
-        return buildMain(allocator, args[2..]);
-    }
-
-    if (args.len >= 2 and mem.eql(u8, args[1], "fmt")) {
-        return fmtMain(allocator, args[2..]);
-    }
-
-    var cmd = Cmd.None;
-    var build_kind: Module.Kind = undefined;
-    var build_mode: builtin.Mode = builtin.Mode.Debug;
-    var color = ErrColor.Auto;
-    var emit_file_type = Emit.Binary;
-
-    var strip = false;
-    var is_static = false;
-    var verbose_tokenize = false;
-    var verbose_ast_tree = false;
-    var verbose_ast_fmt = false;
-    var verbose_link = false;
-    var verbose_ir = false;
-    var verbose_llvm_ir = false;
-    var verbose_cimport = false;
-    var mwindows = false;
-    var mconsole = false;
-    var rdynamic = false;
-    var each_lib_rpath = false;
-    var timing_info = false;
-
-    var in_file_arg: ?[]u8 = null;
-    var out_file: ?[]u8 = null;
-    var out_file_h: ?[]u8 = null;
-    var out_name_arg: ?[]u8 = null;
-    var libc_lib_dir_arg: ?[]u8 = null;
-    var libc_static_lib_dir_arg: ?[]u8 = null;
-    var libc_include_dir_arg: ?[]u8 = null;
-    var msvc_lib_dir_arg: ?[]u8 = null;
-    var kernel32_lib_dir_arg: ?[]u8 = null;
-    var zig_install_prefix: ?[]u8 = null;
-    var dynamic_linker_arg: ?[]u8 = null;
-    var cache_dir_arg: ?[]const u8 = null;
-    var target_arch: ?[]u8 = null;
-    var target_os: ?[]u8 = null;
-    var target_environ: ?[]u8 = null;
-    var mmacosx_version_min: ?[]u8 = null;
-    var mios_version_min: ?[]u8 = null;
-    var linker_script_arg: ?[]u8 = null;
-    var test_name_prefix_arg: ?[]u8 = null;
-
-    var test_filters = ArrayList([]const u8).init(allocator);
-    defer test_filters.deinit();
-
-    var lib_dirs = ArrayList([]const u8).init(allocator);
-    defer lib_dirs.deinit();
-
-    var clang_argv = ArrayList([]const u8).init(allocator);
-    defer clang_argv.deinit();
-
-    var llvm_argv = ArrayList([]const u8).init(allocator);
-    defer llvm_argv.deinit();
-
-    var link_libs = ArrayList([]const u8).init(allocator);
-    defer link_libs.deinit();
-
-    var frameworks = ArrayList([]const u8).init(allocator);
-    defer frameworks.deinit();
-
-    var objects = ArrayList([]const u8).init(allocator);
-    defer objects.deinit();
-
-    var asm_files = ArrayList([]const u8).init(allocator);
-    defer asm_files.deinit();
-
-    var rpath_list = ArrayList([]const u8).init(allocator);
-    defer rpath_list.deinit();
-
-    var ver_major: u32 = 0;
-    var ver_minor: u32 = 0;
-    var ver_patch: u32 = 0;
-
-    var arg_i: usize = 1;
-    while (arg_i < args.len) : (arg_i += 1) {
-        const arg = args[arg_i];
-
-        if (arg.len != 0 and arg[0] == '-') {
-            if (mem.eql(u8, arg, "--release-fast")) {
-                build_mode = builtin.Mode.ReleaseFast;
-            } else if (mem.eql(u8, arg, "--release-safe")) {
-                build_mode = builtin.Mode.ReleaseSafe;
-            } else if (mem.eql(u8, arg, "--strip")) {
-                strip = true;
-            } else if (mem.eql(u8, arg, "--static")) {
-                is_static = true;
-            } else if (mem.eql(u8, arg, "--verbose-tokenize")) {
-                verbose_tokenize = true;
-            } else if (mem.eql(u8, arg, "--verbose-ast-tree")) {
-                verbose_ast_tree = true;
-            } else if (mem.eql(u8, arg, "--verbose-ast-fmt")) {
-                verbose_ast_fmt = true;
-            } else if (mem.eql(u8, arg, "--verbose-link")) {
-                verbose_link = true;
-            } else if (mem.eql(u8, arg, "--verbose-ir")) {
-                verbose_ir = true;
-            } else if (mem.eql(u8, arg, "--verbose-llvm-ir")) {
-                verbose_llvm_ir = true;
-            } else if (mem.eql(u8, arg, "--verbose-cimport")) {
-                verbose_cimport = true;
-            } else if (mem.eql(u8, arg, "-mwindows")) {
-                mwindows = true;
-            } else if (mem.eql(u8, arg, "-mconsole")) {
-                mconsole = true;
-            } else if (mem.eql(u8, arg, "-rdynamic")) {
-                rdynamic = true;
-            } else if (mem.eql(u8, arg, "--each-lib-rpath")) {
-                each_lib_rpath = true;
-            } else if (mem.eql(u8, arg, "--enable-timing-info")) {
-                timing_info = true;
-            } else if (mem.eql(u8, arg, "--test-cmd-bin")) {
-                @panic("TODO --test-cmd-bin");
-            } else if (arg[1] == 'L' and arg.len > 2) {
-                // alias for --library-path
-                try lib_dirs.append(arg[1..]);
-            } else if (mem.eql(u8, arg, "--pkg-begin")) {
-                @panic("TODO --pkg-begin");
-            } else if (mem.eql(u8, arg, "--pkg-end")) {
-                @panic("TODO --pkg-end");
-            } else if (arg_i + 1 >= args.len) {
-                badArgs("expected another argument after {}", arg);
-            } else {
-                arg_i += 1;
-                if (mem.eql(u8, arg, "--output")) {
-                    out_file = args[arg_i];
-                } else if (mem.eql(u8, arg, "--output-h")) {
-                    out_file_h = args[arg_i];
-                } else if (mem.eql(u8, arg, "--color")) {
-                    if (mem.eql(u8, args[arg_i], "auto")) {
-                        color = ErrColor.Auto;
-                    } else if (mem.eql(u8, args[arg_i], "on")) {
-                        color = ErrColor.On;
-                    } else if (mem.eql(u8, args[arg_i], "off")) {
-                        color = ErrColor.Off;
-                    } else {
-                        badArgs("--color options are 'auto', 'on', or 'off'");
-                    }
-                } else if (mem.eql(u8, arg, "--emit")) {
-                    if (mem.eql(u8, args[arg_i], "asm")) {
-                        emit_file_type = Emit.Assembly;
-                    } else if (mem.eql(u8, args[arg_i], "bin")) {
-                        emit_file_type = Emit.Binary;
-                    } else if (mem.eql(u8, args[arg_i], "llvm-ir")) {
-                        emit_file_type = Emit.LlvmIr;
-                    } else {
-                        badArgs("--emit options are 'asm', 'bin', or 'llvm-ir'");
-                    }
-                } else if (mem.eql(u8, arg, "--name")) {
-                    out_name_arg = args[arg_i];
-                } else if (mem.eql(u8, arg, "--libc-lib-dir")) {
-                    libc_lib_dir_arg = args[arg_i];
-                } else if (mem.eql(u8, arg, "--libc-static-lib-dir")) {
-                    libc_static_lib_dir_arg = args[arg_i];
-                } else if (mem.eql(u8, arg, "--libc-include-dir")) {
-                    libc_include_dir_arg = args[arg_i];
-                } else if (mem.eql(u8, arg, "--msvc-lib-dir")) {
-                    msvc_lib_dir_arg = args[arg_i];
-                } else if (mem.eql(u8, arg, "--kernel32-lib-dir")) {
-                    kernel32_lib_dir_arg = args[arg_i];
-                } else if (mem.eql(u8, arg, "--zig-install-prefix")) {
-                    zig_install_prefix = args[arg_i];
-                } else if (mem.eql(u8, arg, "--dynamic-linker")) {
-                    dynamic_linker_arg = args[arg_i];
-                } else if (mem.eql(u8, arg, "-isystem")) {
-                    try clang_argv.append("-isystem");
-                    try clang_argv.append(args[arg_i]);
-                } else if (mem.eql(u8, arg, "-dirafter")) {
-                    try clang_argv.append("-dirafter");
-                    try clang_argv.append(args[arg_i]);
-                } else if (mem.eql(u8, arg, "-mllvm")) {
-                    try clang_argv.append("-mllvm");
-                    try clang_argv.append(args[arg_i]);
-
-                    try llvm_argv.append(args[arg_i]);
-                } else if (mem.eql(u8, arg, "--library-path") or mem.eql(u8, arg, "-L")) {
-                    try lib_dirs.append(args[arg_i]);
-                } else if (mem.eql(u8, arg, "--library")) {
-                    try link_libs.append(args[arg_i]);
-                } else if (mem.eql(u8, arg, "--object")) {
-                    try objects.append(args[arg_i]);
-                } else if (mem.eql(u8, arg, "--assembly")) {
-                    try asm_files.append(args[arg_i]);
-                } else if (mem.eql(u8, arg, "--cache-dir")) {
-                    cache_dir_arg = args[arg_i];
-                } else if (mem.eql(u8, arg, "--target-arch")) {
-                    target_arch = args[arg_i];
-                } else if (mem.eql(u8, arg, "--target-os")) {
-                    target_os = args[arg_i];
-                } else if (mem.eql(u8, arg, "--target-environ")) {
-                    target_environ = args[arg_i];
-                } else if (mem.eql(u8, arg, "-mmacosx-version-min")) {
-                    mmacosx_version_min = args[arg_i];
-                } else if (mem.eql(u8, arg, "-mios-version-min")) {
-                    mios_version_min = args[arg_i];
-                } else if (mem.eql(u8, arg, "-framework")) {
-                    try frameworks.append(args[arg_i]);
-                } else if (mem.eql(u8, arg, "--linker-script")) {
-                    linker_script_arg = args[arg_i];
-                } else if (mem.eql(u8, arg, "-rpath")) {
-                    try rpath_list.append(args[arg_i]);
-                } else if (mem.eql(u8, arg, "--test-filter")) {
-                    try test_filters.append(args[arg_i]);
-                } else if (mem.eql(u8, arg, "--test-name-prefix")) {
-                    test_name_prefix_arg = args[arg_i];
-                } else if (mem.eql(u8, arg, "--ver-major")) {
-                    ver_major = try std.fmt.parseUnsigned(u32, args[arg_i], 10);
-                } else if (mem.eql(u8, arg, "--ver-minor")) {
-                    ver_minor = try std.fmt.parseUnsigned(u32, args[arg_i], 10);
-                } else if (mem.eql(u8, arg, "--ver-patch")) {
-                    ver_patch = try std.fmt.parseUnsigned(u32, args[arg_i], 10);
-                } else if (mem.eql(u8, arg, "--test-cmd")) {
-                    @panic("TODO --test-cmd");
-                } else {
-                    badArgs("invalid argument: {}", arg);
-                }
-            }
-        } else if (cmd == Cmd.None) {
-            if (mem.eql(u8, arg, "build-obj")) {
-                cmd = Cmd.Build;
-                build_kind = Module.Kind.Obj;
-            } else if (mem.eql(u8, arg, "build-exe")) {
-                cmd = Cmd.Build;
-                build_kind = Module.Kind.Exe;
-            } else if (mem.eql(u8, arg, "build-lib")) {
-                cmd = Cmd.Build;
-                build_kind = Module.Kind.Lib;
-            } else if (mem.eql(u8, arg, "version")) {
-                cmd = Cmd.Version;
-            } else if (mem.eql(u8, arg, "zen")) {
-                cmd = Cmd.Zen;
-            } else if (mem.eql(u8, arg, "translate-c")) {
-                cmd = Cmd.TranslateC;
-            } else if (mem.eql(u8, arg, "test")) {
-                cmd = Cmd.Test;
-                build_kind = Module.Kind.Exe;
-            } else {
-                badArgs("unrecognized command: {}", arg);
-            }
-        } else switch (cmd) {
-            Cmd.Build, Cmd.TranslateC, Cmd.Test => {
-                if (in_file_arg == null) {
-                    in_file_arg = arg;
-                } else {
-                    badArgs("unexpected extra parameter: {}", arg);
-                }
-            },
-            Cmd.Version, Cmd.Zen, Cmd.Targets => {
-                badArgs("unexpected extra parameter: {}", arg);
-            },
-            Cmd.None => unreachable,
+    if (args.len <= 1) {
+        try stderr.write(usage);
+        os.exit(1);
+    }
+
+    const commands = []Command {
+        Command { .name = "build",       .exec = cmdBuild      },
+        Command { .name = "build-exe",   .exec = cmdBuildExe   },
+        Command { .name = "build-lib",   .exec = cmdBuildLib   },
+        Command { .name = "build-obj",   .exec = cmdBuildObj   },
+        Command { .name = "cc",          .exec = cmdCc         },
+        Command { .name = "fmt",         .exec = cmdFmt        },
+        Command { .name = "run",         .exec = cmdRun        },
+        Command { .name = "targets",     .exec = cmdTargets    },
+        Command { .name = "test",        .exec = cmdTest       },
+        Command { .name = "translate-c", .exec = cmdTranslateC },
+        Command { .name = "version",     .exec = cmdVersion    },
+        Command { .name = "zen",         .exec = cmdZen        },
+
+        // undocumented commands
+        Command { .name = "help",        .exec = cmdHelp       },
+        Command { .name = "internal",    .exec = cmdInternal   },
+    };
+
+    for (commands) |command| {
+        if (mem.eql(u8, command.name, args[1])) {
+            try command.exec(allocator, args[2..]);
+            return;
         }
     }
 
-    target.initializeAll();
-
-    // TODO
-//    ZigTarget alloc_target;
-//    ZigTarget *target;
-//    if (!target_arch && !target_os && !target_environ) {
-//        target = nullptr;
-//    } else {
-//        target = &alloc_target;
-//        get_unknown_target(target);
-//        if (target_arch) {
-//            if (parse_target_arch(target_arch, &target->arch)) {
-//                fprintf(stderr, "invalid --target-arch argument\n");
-//                return usage(arg0);
-//            }
-//        }
-//        if (target_os) {
-//            if (parse_target_os(target_os, &target->os)) {
-//                fprintf(stderr, "invalid --target-os argument\n");
-//                return usage(arg0);
-//            }
-//        }
-//        if (target_environ) {
-//            if (parse_target_environ(target_environ, &target->env_type)) {
-//                fprintf(stderr, "invalid --target-environ argument\n");
-//                return usage(arg0);
-//            }
-//        }
-//    }
-
-    switch (cmd) {
-        Cmd.None => badArgs("expected command"),
-        Cmd.Zen => return printZen(),
-        Cmd.Build, Cmd.Test, Cmd.TranslateC => {
-            if (cmd == Cmd.Build and in_file_arg == null and objects.len == 0 and asm_files.len == 0) {
-                badArgs("expected source file argument or at least one --object or --assembly argument");
-            } else if ((cmd == Cmd.TranslateC or cmd == Cmd.Test) and in_file_arg == null) {
-                badArgs("expected source file argument");
-            } else if (cmd == Cmd.Build and build_kind == Module.Kind.Obj and objects.len != 0) {
-                badArgs("When building an object file, --object arguments are invalid");
-            }
+    try stderr.print("unknown command: {}\n\n", args[1]);
+    try stderr.write(usage);
+}
 
-            const root_name = switch (cmd) {
-                Cmd.Build, Cmd.TranslateC => x: {
-                    if (out_name_arg) |out_name| {
-                        break :x out_name;
-                    } else if (in_file_arg) |in_file_path| {
-                        const basename = os.path.basename(in_file_path);
-                        var it = mem.split(basename, ".");
-                        break :x it.next() ?? badArgs("file name cannot be empty");
-                    } else {
-                        badArgs("--name [name] not provided and unable to infer");
-                    }
-                },
-                Cmd.Test => "test",
-                else => unreachable,
-            };
-
-            const zig_root_source_file = if (cmd == Cmd.TranslateC) null else in_file_arg;
-
-            const chosen_cache_dir = cache_dir_arg ?? default_zig_cache_name;
-            const full_cache_dir = try os.path.resolve(allocator, ".", chosen_cache_dir);
-            defer allocator.free(full_cache_dir);
-
-            const zig_lib_dir = try resolveZigLibDir(allocator, zig_install_prefix);
-            errdefer allocator.free(zig_lib_dir);
-
-            const module = try Module.create(allocator, root_name, zig_root_source_file,
-                Target.Native, build_kind, build_mode, zig_lib_dir, full_cache_dir);
-            defer module.destroy();
-
-            module.version_major = ver_major;
-            module.version_minor = ver_minor;
-            module.version_patch = ver_patch;
-
-            module.is_test = cmd == Cmd.Test;
-            if (linker_script_arg) |linker_script| {
-                module.linker_script = linker_script;
-            }
-            module.each_lib_rpath = each_lib_rpath;
-            module.clang_argv = clang_argv.toSliceConst();
-            module.llvm_argv = llvm_argv.toSliceConst();
-            module.strip = strip;
-            module.is_static = is_static;
-
-            if (libc_lib_dir_arg) |libc_lib_dir| {
-                module.libc_lib_dir = libc_lib_dir;
-            }
-            if (libc_static_lib_dir_arg) |libc_static_lib_dir| {
-                module.libc_static_lib_dir = libc_static_lib_dir;
-            }
-            if (libc_include_dir_arg) |libc_include_dir| {
-                module.libc_include_dir = libc_include_dir;
-            }
-            if (msvc_lib_dir_arg) |msvc_lib_dir| {
-                module.msvc_lib_dir = msvc_lib_dir;
-            }
-            if (kernel32_lib_dir_arg) |kernel32_lib_dir| {
-                module.kernel32_lib_dir = kernel32_lib_dir;
-            }
-            if (dynamic_linker_arg) |dynamic_linker| {
-                module.dynamic_linker = dynamic_linker;
-            }
-            module.verbose_tokenize = verbose_tokenize;
-            module.verbose_ast_tree = verbose_ast_tree;
-            module.verbose_ast_fmt = verbose_ast_fmt;
-            module.verbose_link = verbose_link;
-            module.verbose_ir = verbose_ir;
-            module.verbose_llvm_ir = verbose_llvm_ir;
-            module.verbose_cimport = verbose_cimport;
-
-            module.err_color = color;
-
-            module.lib_dirs = lib_dirs.toSliceConst();
-            module.darwin_frameworks = frameworks.toSliceConst();
-            module.rpath_list = rpath_list.toSliceConst();
-
-            for (link_libs.toSliceConst()) |name| {
-                _ = try module.addLinkLib(name, true);
-            }
+// cmd:build ///////////////////////////////////////////////////////////////////////////////////////
+
+const usage_build =
+    \\usage: zig build <options>
+    \\
+    \\General Options:
+    \\   --help                       Print this help and exit
+    \\   --init                       Generate a build.zig template
+    \\   --build-file [file]          Override path to build.zig
+    \\   --cache-dir [path]           Override path to cache directory
+    \\   --verbose                    Print commands before executing them
+    \\   --prefix [path]              Override default install prefix
+    \\   --zig-install-prefix [path]  Override directory where zig thinks it is installed
+    \\
+    \\Project-Specific Options:
+    \\
+    \\   Project-specific options become available when the build file is found.
+    \\
+    \\Advanced Options:
+    \\   --build-file [file]          Override path to build.zig
+    \\   --cache-dir [path]           Override path to cache directory
+    \\   --verbose-tokenize           Enable compiler debug output for tokenization
+    \\   --verbose-ast                Enable compiler debug output for parsing into an AST
+    \\   --verbose-link               Enable compiler debug output for linking
+    \\   --verbose-ir                 Enable compiler debug output for Zig IR
+    \\   --verbose-llvm-ir            Enable compiler debug output for LLVM IR
+    \\   --verbose-cimport            Enable compiler debug output for C imports
+    \\
+    \\
+    ;
+
+const args_build_spec = []Flag {
+    Flag.Bool("--help"),
+    Flag.Bool("--init"),
+    Flag.Arg1("--build-file"),
+    Flag.Arg1("--cache-dir"),
+    Flag.Bool("--verbose"),
+    Flag.Arg1("--prefix"),
+    Flag.Arg1("--zig-install-prefix"),
+
+    Flag.Arg1("--build-file"),
+    Flag.Arg1("--cache-dir"),
+    Flag.Bool("--verbose-tokenize"),
+    Flag.Bool("--verbose-ast"),
+    Flag.Bool("--verbose-link"),
+    Flag.Bool("--verbose-ir"),
+    Flag.Bool("--verbose-llvm-ir"),
+    Flag.Bool("--verbose-cimport"),
+};
 
-            module.windows_subsystem_windows = mwindows;
-            module.windows_subsystem_console = mconsole;
-            module.linker_rdynamic = rdynamic;
+const missing_build_file =
+    \\No 'build.zig' file found.
+    \\
+    \\Initialize a 'build.zig' template file with `zig build --init`,
+    \\or build an executable directly with `zig build-exe $FILENAME.zig`.
+    \\
+    \\See: `zig build --help` or `zig help` for more options.
+    \\
+    ;
+
+fn cmdBuild(allocator: &Allocator, args: []const []const u8) !void {
+    var flags = try Args.parse(allocator, args_build_spec, args);
+    defer flags.deinit();
+
+    if (flags.present("help")) {
+        try stderr.write(usage_build);
+        os.exit(0);
+    }
 
-            if (mmacosx_version_min != null and mios_version_min != null) {
-                badArgs("-mmacosx-version-min and -mios-version-min options not allowed together");
-            }
+    const zig_lib_dir = try introspect.resolveZigLibDir(allocator, flags.single("zig-install-prefix") ?? null);
+    defer allocator.free(zig_lib_dir);
 
-            if (mmacosx_version_min) |ver| {
-                module.darwin_version_min = Module.DarwinVersionMin { .MacOS = ver };
-            } else if (mios_version_min) |ver| {
-                module.darwin_version_min = Module.DarwinVersionMin { .Ios = ver };
-            }
+    const zig_std_dir = try os.path.join(allocator, zig_lib_dir, "std");
+    defer allocator.free(zig_std_dir);
+
+    const special_dir = try os.path.join(allocator, zig_std_dir, "special");
+    defer allocator.free(special_dir);
+
+    const build_runner_path = try os.path.join(allocator, special_dir, "build_runner.zig");
+    defer allocator.free(build_runner_path);
+
+    const build_file = flags.single("build-file") ?? "build.zig";
+    const build_file_abs = try os.path.resolve(allocator, ".", build_file);
+    defer allocator.free(build_file_abs);
+
+    const build_file_exists = os.File.exists(allocator, build_file_abs);
+
+    if (flags.present("init")) {
+        if (build_file_exists) {
+            try stderr.print("build.zig already exists\n");
+            os.exit(1);
+        }
+
+        // need a new scope for proper defer scope finalization on exit
+        {
+            const build_template_path = try os.path.join(allocator, special_dir, "build_file_template.zig");
+            defer allocator.free(build_template_path);
+
+            try os.copyFile(allocator, build_template_path, build_file_abs);
+            try stderr.print("wrote build.zig template\n");
+        }
+
+        os.exit(0);
+    }
+
+    if (!build_file_exists) {
+        try stderr.write(missing_build_file);
+        os.exit(1);
+    }
+
+    // TODO: Invoke build.zig entrypoint directly?
+    var zig_exe_path = try os.selfExePath(allocator);
+    defer allocator.free(zig_exe_path);
+
+    var build_args = ArrayList([]const u8).init(allocator);
+    defer build_args.deinit();
+
+    const build_file_basename = os.path.basename(build_file_abs);
+    const build_file_dirname = os.path.dirname(build_file_abs);
+
+    var full_cache_dir: []u8 = undefined;
+    if (flags.single("cache-dir")) |cache_dir| {
+        full_cache_dir = try os.path.resolve(allocator, ".", cache_dir, full_cache_dir);
+    } else {
+        full_cache_dir = try os.path.join(allocator, build_file_dirname, "zig-cache");
+    }
+    defer allocator.free(full_cache_dir);
 
-            module.test_filters = test_filters.toSliceConst();
-            module.test_name_prefix = test_name_prefix_arg;
-            module.out_h_path = out_file_h;
+    const path_to_build_exe = try os.path.join(allocator, full_cache_dir, "build");
+    defer allocator.free(path_to_build_exe);
 
-            // TODO
-            //add_package(g, cur_pkg, g->root_package);
+    try build_args.append(path_to_build_exe);
+    try build_args.append(zig_exe_path);
+    try build_args.append(build_file_dirname);
+    try build_args.append(full_cache_dir);
 
-            switch (cmd) {
-                Cmd.Build => {
-                    module.emit_file_type = emit_file_type;
+    if (flags.single("zig-install-prefix")) |zig_install_prefix| {
+        try build_args.append(zig_install_prefix);
+    }
 
-                    module.link_objects = objects.toSliceConst();
-                    module.assembly_files = asm_files.toSliceConst();
+    var proc = try os.ChildProcess.init(build_args.toSliceConst(), allocator);
+    defer proc.deinit();
 
-                    try module.build();
-                    try module.link(out_file);
-                },
-                Cmd.TranslateC => @panic("TODO translate-c"),
-                Cmd.Test => @panic("TODO test cmd"),
-                else => unreachable,
+    var term = try proc.spawnAndWait();
+    switch (term) {
+        os.ChildProcess.Term.Exited => |status| {
+            if (status != 0) {
+                try stderr.print("{} exited with status {}\n", build_args.at(0), status);
+                os.exit(1);
             }
         },
-        Cmd.Version => {
-            var stdout_file = try io.getStdErr();
-            try stdout_file.write(std.cstr.toSliceConst(c.ZIG_VERSION_STRING));
-            try stdout_file.write("\n");
+        os.ChildProcess.Term.Signal => |signal| {
+            try stderr.print("{} killed by signal {}\n", build_args.at(0), signal);
+            os.exit(1);
+        },
+        os.ChildProcess.Term.Stopped => |signal| {
+            try stderr.print("{} stopped by signal {}\n", build_args.at(0), signal);
+            os.exit(1);
+        },
+        os.ChildProcess.Term.Unknown => |status| {
+            try stderr.print("{} encountered unknown failure {}\n", build_args.at(0), status);
+            os.exit(1);
         },
-        Cmd.Targets => @panic("TODO zig targets"),
     }
 }
 
-fn printUsage(stream: var) !void {
-    try stream.write(
-        \\Usage: zig [command] [options]
-        \\
-        \\Commands:
-        \\  build                        build project from build.zig
-        \\  build-exe [source]           create executable from source or object files
-        \\  build-lib [source]           create library from source or object files
-        \\  build-obj [source]           create object from source or assembly
-        \\  fmt [file]                   parse file and render in canonical zig format
-        \\  translate-c [source]         convert c code to zig code
-        \\  targets                      list available compilation targets
-        \\  test [source]                create and run a test build
-        \\  version                      print version number and exit
-        \\  zen                          print zen of zig and exit
-        \\Compile Options:
-        \\  --assembly [source]          add assembly file to build
-        \\  --cache-dir [path]           override the cache directory
-        \\  --color [auto|off|on]        enable or disable colored error messages
-        \\  --emit [filetype]            emit a specific file format as compilation output
-        \\  --enable-timing-info         print timing diagnostics
-        \\  --libc-include-dir [path]    directory where libc stdlib.h resides
-        \\  --name [name]                override output name
-        \\  --output [file]              override destination path
-        \\  --output-h [file]            override generated header file path
-        \\  --pkg-begin [name] [path]    make package available to import and push current pkg
-        \\  --pkg-end                    pop current pkg
-        \\  --release-fast               build with optimizations on and safety off
-        \\  --release-safe               build with optimizations on and safety on
-        \\  --static                     output will be statically linked
-        \\  --strip                      exclude debug symbols
-        \\  --target-arch [name]         specify target architecture
-        \\  --target-environ [name]      specify target environment
-        \\  --target-os [name]           specify target operating system
-        \\  --verbose-tokenize           enable compiler debug info: tokenization
-        \\  --verbose-ast-tree           enable compiler debug info: parsing into an AST (treeview)
-        \\  --verbose-ast-fmt            enable compiler debug info: parsing into an AST (render source)
-        \\  --verbose-cimport            enable compiler debug info: C imports
-        \\  --verbose-ir                 enable compiler debug info: Zig IR
-        \\  --verbose-llvm-ir            enable compiler debug info: LLVM IR
-        \\  --verbose-link               enable compiler debug info: linking
-        \\  --zig-install-prefix [path]  override directory where zig thinks it is installed
-        \\  -dirafter [dir]              same as -isystem but do it last
-        \\  -isystem [dir]               add additional search path for other .h files
-        \\  -mllvm [arg]                 additional arguments to forward to LLVM's option processing
-        \\Link Options:
-        \\  --ar-path [path]             set the path to ar
-        \\  --dynamic-linker [path]      set the path to ld.so
-        \\  --each-lib-rpath             add rpath for each used dynamic library
-        \\  --libc-lib-dir [path]        directory where libc crt1.o resides
-        \\  --libc-static-lib-dir [path] directory where libc crtbegin.o resides
-        \\  --msvc-lib-dir [path]        (windows) directory where vcruntime.lib resides
-        \\  --kernel32-lib-dir [path]    (windows) directory where kernel32.lib resides
-        \\  --library [lib]              link against lib
-        \\  --library-path [dir]         add a directory to the library search path
-        \\  --linker-script [path]       use a custom linker script
-        \\  --object [obj]               add object file to build
-        \\  -L[dir]                      alias for --library-path
-        \\  -rdynamic                    add all symbols to the dynamic symbol table
-        \\  -rpath [path]                add directory to the runtime library search path
-        \\  -mconsole                    (windows) --subsystem console to the linker
-        \\  -mwindows                    (windows) --subsystem windows to the linker
-        \\  -framework [name]            (darwin) link against framework
-        \\  -mios-version-min [ver]      (darwin) set iOS deployment target
-        \\  -mmacosx-version-min [ver]   (darwin) set Mac OS X deployment target
-        \\  --ver-major [ver]            dynamic library semver major version
-        \\  --ver-minor [ver]            dynamic library semver minor version
-        \\  --ver-patch [ver]            dynamic library semver patch version
-        \\Test Options:
-        \\  --test-filter [text]         skip tests that do not match filter
-        \\  --test-name-prefix [text]    add prefix to all tests
-        \\  --test-cmd [arg]             specify test execution command one arg at a time
-        \\  --test-cmd-bin               appends test binary path to test cmd args
-        \\
-    );
-}
+// cmd:build-exe ///////////////////////////////////////////////////////////////////////////////////
+
+const usage_build_generic =
+    \\usage: zig build-exe <options> [file]
+    \\       zig build-lib <options> [file]
+    \\       zig build-obj <options> [file]
+    \\
+    \\General Options:
+    \\  --help                       Print this help and exit
+    \\  --color [auto|off|on]        Enable or disable colored error messages
+    \\
+    \\Compile Options:
+    \\  --assembly [source]          Add assembly file to build
+    \\  --cache-dir [path]           Override the cache directory
+    \\  --emit [filetype]            Emit a specific file format as compilation output
+    \\  --enable-timing-info         Print timing diagnostics
+    \\  --libc-include-dir [path]    Directory where libc stdlib.h resides
+    \\  --name [name]                Override output name
+    \\  --output [file]              Override destination path
+    \\  --output-h [file]            Override generated header file path
+    \\  --pkg-begin [name] [path]    Make package available to import and push current pkg
+    \\  --pkg-end                    Pop current pkg
+    \\  --release-fast               Build with optimizations on and safety off
+    \\  --release-safe               Build with optimizations on and safety on
+    \\  --static                     Output will be statically linked
+    \\  --strip                      Exclude debug symbols
+    \\  --target-arch [name]         Specify target architecture
+    \\  --target-environ [name]      Specify target environment
+    \\  --target-os [name]           Specify target operating system
+    \\  --verbose-tokenize           Turn on compiler debug output for tokenization
+    \\  --verbose-ast-tree           Turn on compiler debug output for parsing into an AST (tree view)
+    \\  --verbose-ast-fmt            Turn on compiler debug output for parsing into an AST (render source)
+    \\  --verbose-link               Turn on compiler debug output for linking
+    \\  --verbose-ir                 Turn on compiler debug output for Zig IR
+    \\  --verbose-llvm-ir            Turn on compiler debug output for LLVM IR
+    \\  --verbose-cimport            Turn on compiler debug output for C imports
+    \\  --zig-install-prefix [path]  Override directory where zig thinks it is installed
+    \\  -dirafter [dir]              Same as -isystem but do it last
+    \\  -isystem [dir]               Add additional search path for other .h files
+    \\  -mllvm [arg]                 Additional arguments to forward to LLVM's option processing
+    \\
+    \\Link Options:
+    \\  --ar-path [path]             Set the path to ar
+    \\  --dynamic-linker [path]      Set the path to ld.so
+    \\  --each-lib-rpath             Add rpath for each used dynamic library
+    \\  --libc-lib-dir [path]        Directory where libc crt1.o resides
+    \\  --libc-static-lib-dir [path] Directory where libc crtbegin.o resides
+    \\  --msvc-lib-dir [path]        (windows) directory where vcruntime.lib resides
+    \\  --kernel32-lib-dir [path]    (windows) directory where kernel32.lib resides
+    \\  --library [lib]              Link against lib
+    \\  --forbid-library [lib]       Make it an error to link against lib
+    \\  --library-path [dir]         Add a directory to the library search path
+    \\  --linker-script [path]       Use a custom linker script
+    \\  --object [obj]               Add object file to build
+    \\  -rdynamic                    Add all symbols to the dynamic symbol table
+    \\  -rpath [path]                Add directory to the runtime library search path
+    \\  -mconsole                    (windows) --subsystem console to the linker
+    \\  -mwindows                    (windows) --subsystem windows to the linker
+    \\  -framework [name]            (darwin) link against framework
+    \\  -mios-version-min [ver]      (darwin) set iOS deployment target
+    \\  -mmacosx-version-min [ver]   (darwin) set Mac OS X deployment target
+    \\  --ver-major [ver]            Dynamic library semver major version
+    \\  --ver-minor [ver]            Dynamic library semver minor version
+    \\  --ver-patch [ver]            Dynamic library semver patch version
+    \\
+    \\
+    ;
+
+const args_build_generic = []Flag {
+    Flag.Bool("--help"),
+    Flag.Option("--color", []const []const u8 { "auto", "off", "on" }),
+
+    Flag.ArgMergeN("--assembly", 1),
+    Flag.Arg1("--cache-dir"),
+    Flag.Option("--emit", []const []const u8 { "asm", "bin", "llvm-ir" }),
+    Flag.Bool("--enable-timing-info"),
+    Flag.Arg1("--libc-include-dir"),
+    Flag.Arg1("--name"),
+    Flag.Arg1("--output"),
+    Flag.Arg1("--output-h"),
+    // NOTE: Parsed manually after initial check
+    Flag.ArgN("--pkg-begin", 2),
+    Flag.Bool("--pkg-end"),
+    Flag.Bool("--release-fast"),
+    Flag.Bool("--release-safe"),
+    Flag.Bool("--static"),
+    Flag.Bool("--strip"),
+    Flag.Arg1("--target-arch"),
+    Flag.Arg1("--target-environ"),
+    Flag.Arg1("--target-os"),
+    Flag.Bool("--verbose-tokenize"),
+    Flag.Bool("--verbose-ast-tree"),
+    Flag.Bool("--verbose-ast-fmt"),
+    Flag.Bool("--verbose-link"),
+    Flag.Bool("--verbose-ir"),
+    Flag.Bool("--verbose-llvm-ir"),
+    Flag.Bool("--verbose-cimport"),
+    Flag.Arg1("--zig-install-prefix"),
+    Flag.Arg1("-dirafter"),
+    Flag.ArgMergeN("-isystem", 1),
+    Flag.Arg1("-mllvm"),
+
+    Flag.Arg1("--ar-path"),
+    Flag.Arg1("--dynamic-linker"),
+    Flag.Bool("--each-lib-rpath"),
+    Flag.Arg1("--libc-lib-dir"),
+    Flag.Arg1("--libc-static-lib-dir"),
+    Flag.Arg1("--msvc-lib-dir"),
+    Flag.Arg1("--kernel32-lib-dir"),
+    Flag.ArgMergeN("--library", 1),
+    Flag.ArgMergeN("--forbid-library", 1),
+    Flag.ArgMergeN("--library-path", 1),
+    Flag.Arg1("--linker-script"),
+    Flag.ArgMergeN("--object", 1),
+    // NOTE: Removed -L since it would need to be special-cased and we have an alias in library-path
+    Flag.Bool("-rdynamic"),
+    Flag.Arg1("-rpath"),
+    Flag.Bool("-mconsole"),
+    Flag.Bool("-mwindows"),
+    Flag.ArgMergeN("-framework", 1),
+    Flag.Arg1("-mios-version-min"),
+    Flag.Arg1("-mmacosx-version-min"),
+    Flag.Arg1("--ver-major"),
+    Flag.Arg1("--ver-minor"),
+    Flag.Arg1("--ver-patch"),
+};
 
-fn printZen() !void {
-    var stdout_file = try io.getStdErr();
-    try stdout_file.write(
-        \\
-        \\ * Communicate intent precisely.
-        \\ * Edge cases matter.
-        \\ * Favor reading code over writing code.
-        \\ * Only one obvious way to do things.
-        \\ * Runtime crashes are better than bugs.
-        \\ * Compile errors are better than runtime crashes.
-        \\ * Incremental improvements.
-        \\ * Avoid local maximums.
-        \\ * Reduce the amount one must remember.
-        \\ * Minimize energy spent on coding style.
-        \\ * Together we serve end users.
-        \\
-        \\
-    );
-}
+fn buildOutputType(allocator: &Allocator, args: []const []const u8, out_type: Module.Kind) !void {
+    var flags = try Args.parse(allocator, args_build_generic, args);
+    defer flags.deinit();
 
-fn buildMain(allocator: &mem.Allocator, argv: []const []const u8) !void {
-    var build_file: [] const u8 = "build.zig";
-    var cache_dir: ?[] const u8 = null;
-    var zig_install_prefix: ?[] const u8 = null;
-    var asked_for_help = false;
-    var asked_for_init = false;
+    if (flags.present("help")) {
+        try stderr.write(usage_build_generic);
+        os.exit(0);
+    }
 
-    var args = ArrayList([] const u8).init(allocator);
-    defer args.deinit();
+    var build_mode = builtin.Mode.Debug;
+    if (flags.present("release-fast")) {
+        build_mode = builtin.Mode.ReleaseFast;
+    } else if (flags.present("release-safe")) {
+        build_mode = builtin.Mode.ReleaseSafe;
+    }
 
-    var zig_exe_path = try os.selfExePath(allocator);
-    defer allocator.free(zig_exe_path);
+    var color = Module.ErrColor.Auto;
+    if (flags.single("color")) |color_flag| {
+        if (mem.eql(u8, color_flag, "auto")) {
+            color = Module.ErrColor.Auto;
+        } else if (mem.eql(u8, color_flag, "on")) {
+            color = Module.ErrColor.On;
+        } else if (mem.eql(u8, color_flag, "off")) {
+            color = Module.ErrColor.Off;
+        } else {
+            unreachable;
+        }
+    }
 
-    try args.append("");  // Placeholder for zig-cache/build
-    try args.append("");  // Placeholder for zig_exe_path
-    try args.append("");  // Placeholder for build_file_dirname
-    try args.append("");  // Placeholder for full_cache_dir
+    var emit_type = Module.Emit.Binary;
+    if (flags.single("emit")) |emit_flag| {
+        if (mem.eql(u8, emit_flag, "asm")) {
+            emit_type = Module.Emit.Assembly;
+        } else if (mem.eql(u8, emit_flag, "bin")) {
+            emit_type = Module.Emit.Binary;
+        } else if (mem.eql(u8, emit_flag, "llvm-ir")) {
+            emit_type = Module.Emit.LlvmIr;
+        } else {
+            unreachable;
+        }
+    }
+
+    var cur_pkg = try Module.CliPkg.init(allocator, "", "", null); // TODO: Need a path, name?
+    defer cur_pkg.deinit();
 
     var i: usize = 0;
-    while (i < argv.len) : (i += 1) {
-        var arg = argv[i];
-        if (mem.eql(u8, arg, "--help")) {
-            asked_for_help = true;
-            try args.append(argv[i]);
-        } else if (mem.eql(u8, arg, "--init")) {
-            asked_for_init = true;
-            try args.append(argv[i]);
-        } else if (i + 1 < argv.len and mem.eql(u8, arg, "--build-file")) {
-            build_file = argv[i + 1];
-            i += 1;
-        } else if (i + 1 < argv.len and mem.eql(u8, arg, "--cache-dir")) {
-            cache_dir = argv[i + 1];
+    while (i < args.len) : (i += 1) {
+        const arg_name = args[i];
+        if (mem.eql(u8, "--pkg-begin", arg_name)) {
+            // following two arguments guaranteed to exist due to arg parsing
             i += 1;
-        } else if (i + 1 < argv.len and mem.eql(u8, arg, "--zig-install-prefix")) {
-            try args.append(arg);
+            const new_pkg_name = args[i];
             i += 1;
-            zig_install_prefix = argv[i];
-            try args.append(argv[i]);
-        } else {
-            try args.append(arg);
+            const new_pkg_path = args[i];
+
+            var new_cur_pkg = try Module.CliPkg.init(allocator, new_pkg_name, new_pkg_path, cur_pkg);
+            try cur_pkg.children.append(new_cur_pkg);
+            cur_pkg = new_cur_pkg;
+        } else if (mem.eql(u8, "--pkg-end", arg_name)) {
+            if (cur_pkg.parent == null) {
+                try stderr.print("encountered --pkg-end with no matching --pkg-begin\n");
+                os.exit(1);
+            }
+            cur_pkg = ??cur_pkg.parent;
         }
     }
 
-    const zig_lib_dir = try resolveZigLibDir(allocator, zig_install_prefix);
-    defer allocator.free(zig_lib_dir);
+    if (cur_pkg.parent != null) {
+        try stderr.print("unmatched --pkg-begin\n");
+        os.exit(1);
+    }
 
-    const zig_std_dir = try os.path.join(allocator, zig_lib_dir, "std");
-    defer allocator.free(zig_std_dir);
+    var in_file: ?[]const u8 = undefined;
+    switch (flags.positionals.len) {
+        0 => {
+            try stderr.write("--name [name] not provided and unable to infer\n");
+            os.exit(1);
+        },
+        1 => {
+            in_file = flags.positionals.at(0);
+        },
+        else => {
+            try stderr.write("only one zig input file is accepted during build\n");
+            os.exit(1);
+        },
+    }
 
-    const special_dir = try os.path.join(allocator, zig_std_dir, "special");
-    defer allocator.free(special_dir);
+    const basename = os.path.basename(??in_file);
+    var it = mem.split(basename, ".");
+    const root_name = it.next() ?? {
+        try stderr.write("file name cannot be empty\n");
+        os.exit(1);
+    };
 
-    const build_runner_path = try os.path.join(allocator, special_dir, "build_runner.zig");
-    defer allocator.free(build_runner_path);
+    const asm_a= flags.many("assembly");
+    const obj_a = flags.many("object");
+    if (in_file == null and (obj_a == null or (??obj_a).len == 0) and (asm_a == null or (??asm_a).len == 0)) {
+        try stderr.write("Expected source file argument or at least one --object or --assembly argument\n");
+        os.exit(1);
+    }
 
-    // g = codegen_create(build_runner_path, ...)
-    // codegen_set_out_name(g, "build")
+    if (out_type == Module.Kind.Obj and (obj_a != null and (??obj_a).len != 0)) {
+        try stderr.write("When building an object file, --object arguments are invalid\n");
+        os.exit(1);
+    }
 
-    const build_file_abs = try os.path.resolve(allocator, ".", build_file);
-    defer allocator.free(build_file_abs);
+    const zig_root_source_file = in_file;
 
-    const build_file_basename = os.path.basename(build_file_abs);
-    const build_file_dirname = os.path.dirname(build_file_abs);
+    const full_cache_dir = os.path.resolve(allocator, ".", flags.single("cache-dir") ?? "zig-cache"[0..]) catch {
+        os.exit(1);
+    };
+    defer allocator.free(full_cache_dir);
 
-    var full_cache_dir: []u8 = undefined;
-    if (cache_dir == null) {
-        full_cache_dir = try os.path.join(allocator, build_file_dirname, "zig-cache");
-    } else {
-        full_cache_dir = try os.path.resolve(allocator, ".", ??cache_dir, full_cache_dir);
+    const zig_lib_dir = introspect.resolveZigLibDir(allocator, flags.single("zig-install-prefix") ?? null) catch {
+        os.exit(1);
+    };
+    defer allocator.free(zig_lib_dir);
+
+    var module =
+        try Module.create(
+            allocator,
+            root_name,
+            zig_root_source_file,
+            Target.Native,
+            out_type,
+            build_mode,
+            zig_lib_dir,
+            full_cache_dir
+        );
+    defer module.destroy();
+
+    module.version_major = try std.fmt.parseUnsigned(u32, flags.single("ver-major") ?? "0", 10);
+    module.version_minor = try std.fmt.parseUnsigned(u32, flags.single("ver-minor") ?? "0", 10);
+    module.version_patch = try std.fmt.parseUnsigned(u32, flags.single("ver-patch") ?? "0", 10);
+
+    module.is_test = false;
+
+    if (flags.single("linker-script")) |linker_script| {
+        module.linker_script = linker_script;
     }
-    defer allocator.free(full_cache_dir);
 
-    const path_to_build_exe = try os.path.join(allocator, full_cache_dir, "build");
-    defer allocator.free(path_to_build_exe);
-    // codegen_set_cache_dir(g, full_cache_dir)
+    module.each_lib_rpath = flags.present("each-lib-rpath");
 
-    args.items[0] = path_to_build_exe;
-    args.items[1] = zig_exe_path;
-    args.items[2] = build_file_dirname;
-    args.items[3] = full_cache_dir;
+    var clang_argv_buf = ArrayList([]const u8).init(allocator);
+    defer clang_argv_buf.deinit();
+    if (flags.many("mllvm")) |mllvm_flags| {
+        for (mllvm_flags) |mllvm| {
+            try clang_argv_buf.append("-mllvm");
+            try clang_argv_buf.append(mllvm);
+        }
 
-    var build_file_exists: bool = undefined;
-    if (os.File.openRead(allocator, build_file_abs)) |*file| {
-        file.close();
-        build_file_exists = true;
-    } else |_| {
-        build_file_exists = false;
+        module.llvm_argv = mllvm_flags;
+        module.clang_argv = clang_argv_buf.toSliceConst();
     }
 
-    if (!build_file_exists and asked_for_help) {
-        // TODO(bnoordhuis) Print help message from std/special/build_runner.zig
-        return;
+    module.strip = flags.present("strip");
+    module.is_static = flags.present("static");
+
+    if (flags.single("libc-lib-dir")) |libc_lib_dir| {
+        module.libc_lib_dir = libc_lib_dir;
+    }
+    if (flags.single("libc-static-lib-dir")) |libc_static_lib_dir| {
+        module.libc_static_lib_dir = libc_static_lib_dir;
+    }
+    if (flags.single("libc-include-dir")) |libc_include_dir| {
+        module.libc_include_dir = libc_include_dir;
+    }
+    if (flags.single("msvc-lib-dir")) |msvc_lib_dir| {
+        module.msvc_lib_dir = msvc_lib_dir;
+    }
+    if (flags.single("kernel32-lib-dir")) |kernel32_lib_dir| {
+        module.kernel32_lib_dir = kernel32_lib_dir;
+    }
+    if (flags.single("dynamic-linker")) |dynamic_linker| {
+        module.dynamic_linker = dynamic_linker;
     }
 
-    if (!build_file_exists and asked_for_init) {
-        const build_template_path = try os.path.join(allocator, special_dir, "build_file_template.zig");
-        defer allocator.free(build_template_path);
+    module.verbose_tokenize = flags.present("verbose-tokenize");
+    module.verbose_ast_tree = flags.present("verbose-ast-tree");
+    module.verbose_ast_fmt = flags.present("verbose-ast-fmt");
+    module.verbose_link = flags.present("verbose-link");
+    module.verbose_ir = flags.present("verbose-ir");
+    module.verbose_llvm_ir = flags.present("verbose-llvm-ir");
+    module.verbose_cimport = flags.present("verbose-cimport");
 
-        var srcfile = try os.File.openRead(allocator, build_template_path);
-        defer srcfile.close();
+    module.err_color = color;
 
-        var dstfile = try os.File.openWrite(allocator, build_file_abs);
-        defer dstfile.close();
+    if (flags.many("library-path")) |lib_dirs| {
+        module.lib_dirs = lib_dirs;
+    }
 
-        while (true) {
-            var buffer: [4096]u8 = undefined;
-            const n = try srcfile.read(buffer[0..]);
-            if (n == 0) break;
-            try dstfile.write(buffer[0..n]);
-        }
+    if (flags.many("framework")) |frameworks| {
+        module.darwin_frameworks = frameworks;
+    }
 
-        return;
+    if (flags.many("rpath")) |rpath_list| {
+        module.rpath_list = rpath_list;
     }
 
-    if (!build_file_exists) {
-        warn(
-            \\No 'build.zig' file found.
-            \\Initialize a 'build.zig' template file with `zig build --init`,
-            \\or build an executable directly with `zig build-exe $FILENAME.zig`.
-            \\See: `zig build --help` or `zig help` for more options.
-            \\
-        );
+    if (flags.single("output-h")) |output_h| {
+        module.out_h_path = output_h;
+    }
+
+    module.windows_subsystem_windows = flags.present("mwindows");
+    module.windows_subsystem_console = flags.present("mconsole");
+    module.linker_rdynamic = flags.present("rdynamic");
+
+    if (flags.single("mmacosx-version-min") != null and flags.single("mios-version-min") != null) {
+        try stderr.write("-mmacosx-version-min and -mios-version-min options not allowed together\n");
         os.exit(1);
     }
 
-    // codegen_build(g)
-    // codegen_link(g, path_to_build_exe)
-    // codegen_destroy(g)
+    if (flags.single("mmacosx-version-min")) |ver| {
+        module.darwin_version_min = Module.DarwinVersionMin { .MacOS = ver };
+    }
+    if (flags.single("mios-version-min")) |ver| {
+        module.darwin_version_min = Module.DarwinVersionMin { .Ios = ver };
+    }
+
+    module.emit_file_type = emit_type;
+    if (flags.many("object")) |objects| {
+        module.link_objects = objects;
+    }
+    if (flags.many("assembly")) |assembly_files| {
+        module.assembly_files = assembly_files;
+    }
+
+    try module.build();
+    try module.link(flags.single("out-file") ?? null);
 
-    var proc = try os.ChildProcess.init(args.toSliceConst(), allocator);
+    if (flags.present("print-timing-info")) {
+        // codegen_print_timing_info(g, stderr);
+    }
+
+    try stderr.print("building {}: {}\n", @tagName(out_type), in_file);
+}
+
+fn cmdBuildExe(allocator: &Allocator, args: []const []const u8) !void {
+    try buildOutputType(allocator, args, Module.Kind.Exe);
+}
+
+// cmd:build-lib ///////////////////////////////////////////////////////////////////////////////////
+
+fn cmdBuildLib(allocator: &Allocator, args: []const []const u8) !void {
+    try buildOutputType(allocator, args, Module.Kind.Lib);
+}
+
+// cmd:build-obj ///////////////////////////////////////////////////////////////////////////////////
+
+fn cmdBuildObj(allocator: &Allocator, args: []const []const u8) !void {
+    try buildOutputType(allocator, args, Module.Kind.Obj);
+}
+
+// cmd:cc //////////////////////////////////////////////////////////////////////////////////////////
+
+fn cmdCc(allocator: &Allocator, args: []const []const u8) !void {
+    // TODO: using libclang directly would be nice, but it may not expose argument parsing nicely
+    var command = ArrayList([]const u8).init(allocator);
+    defer command.deinit();
+
+    try command.append("cc");
+    try command.appendSlice(args);
+
+    var proc = try os.ChildProcess.init(command.toSliceConst(), allocator);
     defer proc.deinit();
 
     var term = try proc.spawnAndWait();
     switch (term) {
         os.ChildProcess.Term.Exited => |status| {
             if (status != 0) {
-                warn("{} exited with status {}\n", args.at(0), status);
+                try stderr.print("cc exited with status {}\n", status);
                 os.exit(1);
             }
         },
         os.ChildProcess.Term.Signal => |signal| {
-            warn("{} killed by signal {}\n", args.at(0), signal);
+            try stderr.print("cc killed by signal {}\n", signal);
             os.exit(1);
         },
         os.ChildProcess.Term.Stopped => |signal| {
-            warn("{} stopped by signal {}\n", args.at(0), signal);
+            try stderr.print("cc stopped by signal {}\n", signal);
             os.exit(1);
         },
         os.ChildProcess.Term.Unknown => |status| {
-            warn("{} encountered unknown failure {}\n", args.at(0), status);
+            try stderr.print("cc encountered unknown failure {}\n", status);
             os.exit(1);
         },
     }
 }
 
-fn fmtMain(allocator: &mem.Allocator, file_paths: []const []const u8) !void {
-    for (file_paths) |file_path| {
+// cmd:fmt /////////////////////////////////////////////////////////////////////////////////////////
+
+const usage_fmt =
+    \\usage: zig fmt [file]...
+    \\
+    \\   Formats the input files and modifies them in-place.
+    \\
+    \\Options:
+    \\   --help                 Print this help and exit
+    \\   --keep-backups         Retain backup entries for every file
+    \\
+    \\
+    ;
+
+const args_fmt_spec = []Flag {
+    Flag.Bool("--help"),
+    Flag.Bool("--keep-backups"),
+};
+
+fn cmdFmt(allocator: &Allocator, args: []const []const u8) !void {
+    var flags = try Args.parse(allocator, args_fmt_spec, args);
+    defer flags.deinit();
+
+    if (flags.present("help")) {
+        try stderr.write(usage_fmt);
+        os.exit(0);
+    }
+
+    if (flags.positionals.len == 0) {
+        try stderr.write("expected at least one source file argument\n");
+        os.exit(1);
+    }
+
+    for (flags.positionals.toSliceConst()) |file_path| {
         var file = try os.File.openRead(allocator, file_path);
         defer file.close();
 
         const source_code = io.readFileAlloc(allocator, file_path) catch |err| {
-            warn("unable to open '{}': {}", file_path, err);
+            try stderr.print("unable to open '{}': {}", file_path, err);
             continue;
         };
         defer allocator.free(source_code);
@@ -734,72 +723,423 @@ fn fmtMain(allocator: &mem.Allocator, file_paths: []const []const u8) !void {
         var parser = std.zig.Parser.init(&tokenizer, allocator, file_path);
         defer parser.deinit();
 
-        var tree = try parser.parse();
+        var tree = parser.parse() catch |err| {
+            try stderr.print("error parsing file '{}': {}\n", file_path, err);
+            continue;
+        };
         defer tree.deinit();
 
-        const baf = try io.BufferedAtomicFile.create(allocator, file_path);
-        defer baf.destroy();
+        var original_file_backup = try Buffer.init(allocator, file_path);
+        defer original_file_backup.deinit();
+        try original_file_backup.append(".backup");
+
+        try os.rename(allocator, file_path, original_file_backup.toSliceConst());
+
+        try stderr.print("{}\n", file_path);
 
-        try parser.renderSource(baf.stream(), tree.root_node);
-        try baf.finish();
+        // TODO: BufferedAtomicFile has some access problems.
+        var out_file = try os.File.openWrite(allocator, file_path);
+        defer out_file.close();
+
+        var out_file_stream = io.FileOutStream.init(&out_file);
+        try parser.renderSource(out_file_stream.stream, tree.root_node);
+
+        if (!flags.present("keep-backups")) {
+            try os.deleteFile(allocator, original_file_backup.toSliceConst());
+        }
     }
 }
 
-/// Caller must free result
-fn resolveZigLibDir(allocator: &mem.Allocator, zig_install_prefix_arg: ?[]const u8) ![]u8 {
-    if (zig_install_prefix_arg) |zig_install_prefix| {
-        return testZigInstallPrefix(allocator, zig_install_prefix) catch |err| {
-            warn("No Zig installation found at prefix {}: {}\n", zig_install_prefix_arg, @errorName(err));
-            return error.ZigInstallationNotFound;
-        };
-    } else {
-        return findZigLibDir(allocator) catch |err| {
-            warn("Unable to find zig lib directory: {}.\nReinstall Zig or use --zig-install-prefix.\n",
-                @errorName(err));
-            return error.ZigLibDirNotFound;
-        };
+// cmd:targets /////////////////////////////////////////////////////////////////////////////////////
+
+// TODO: comptime '@fields' for iteration here instead so we are always in sync.
+const Os = builtin.Os;
+pub const os_list = []const Os {
+    Os.freestanding,
+    Os.ananas,
+    Os.cloudabi,
+    Os.dragonfly,
+    Os.freebsd,
+    Os.fuchsia,
+    Os.ios,
+    Os.kfreebsd,
+    Os.linux,
+    Os.lv2,
+    Os.macosx,
+    Os.netbsd,
+    Os.openbsd,
+    Os.solaris,
+    Os.windows,
+    Os.haiku,
+    Os.minix,
+    Os.rtems,
+    Os.nacl,
+    Os.cnk,
+    Os.aix,
+    Os.cuda,
+    Os.nvcl,
+    Os.amdhsa,
+    Os.ps4,
+    Os.elfiamcu,
+    Os.tvos,
+    Os.watchos,
+    Os.mesa3d,
+    Os.contiki,
+    Os.zen,
+};
+
+const Arch = builtin.Arch;
+pub const arch_list = []const Arch {
+    Arch.armv8_2a,
+    Arch.armv8_1a,
+    Arch.armv8,
+    Arch.armv8r,
+    Arch.armv8m_baseline,
+    Arch.armv8m_mainline,
+    Arch.armv7,
+    Arch.armv7em,
+    Arch.armv7m,
+    Arch.armv7s,
+    Arch.armv7k,
+    Arch.armv7ve,
+    Arch.armv6,
+    Arch.armv6m,
+    Arch.armv6k,
+    Arch.armv6t2,
+    Arch.armv5,
+    Arch.armv5te,
+    Arch.armv4t,
+    Arch.aarch64,
+    Arch.aarch64_be,
+    Arch.avr,
+    Arch.bpfel,
+    Arch.bpfeb,
+    Arch.hexagon,
+    Arch.mips,
+    Arch.mipsel,
+    Arch.mips64,
+    Arch.mips64el,
+    Arch.msp430,
+    Arch.nios2,
+    Arch.powerpc,
+    Arch.powerpc64,
+    Arch.powerpc64le,
+    Arch.r600,
+    Arch.amdgcn,
+    Arch.riscv32,
+    Arch.riscv64,
+    Arch.sparc,
+    Arch.sparcv9,
+    Arch.sparcel,
+    Arch.s390x,
+    Arch.tce,
+    Arch.tcele,
+    Arch.thumb,
+    Arch.thumbeb,
+    Arch.i386,
+    Arch.x86_64,
+    Arch.xcore,
+    Arch.nvptx,
+    Arch.nvptx64,
+    Arch.le32,
+    Arch.le64,
+    Arch.amdil,
+    Arch.amdil64,
+    Arch.hsail,
+    Arch.hsail64,
+    Arch.spir,
+    Arch.spir64,
+    Arch.kalimbav3,
+    Arch.kalimbav4,
+    Arch.kalimbav5,
+    Arch.shave,
+    Arch.lanai,
+    Arch.wasm32,
+    Arch.wasm64,
+    Arch.renderscript32,
+    Arch.renderscript64,
+};
+
+const Environ = builtin.Environ;
+pub const environ_list = []const Environ {
+    Environ.unknown,
+    Environ.gnu,
+    Environ.gnuabi64,
+    Environ.gnueabi,
+    Environ.gnueabihf,
+    Environ.gnux32,
+    Environ.code16,
+    Environ.eabi,
+    Environ.eabihf,
+    Environ.android,
+    Environ.musl,
+    Environ.musleabi,
+    Environ.musleabihf,
+    Environ.msvc,
+    Environ.itanium,
+    Environ.cygnus,
+    Environ.amdopencl,
+    Environ.coreclr,
+    Environ.opencl,
+};
+
+fn cmdTargets(allocator: &Allocator, args: []const []const u8) !void {
+    try stdout.write("Architectures:\n");
+    for (arch_list) |arch_tag| {
+        const native_str = if (builtin.arch == arch_tag) " (native) " else "";
+        try stdout.print("  {}{}\n", @tagName(arch_tag), native_str);
     }
+    try stdout.write("\n");
+
+    try stdout.write("Operating Systems:\n");
+    for (os_list) |os_tag| {
+        const native_str = if (builtin.os == os_tag) " (native) " else "";
+        try stdout.print("  {}{}\n", @tagName(os_tag), native_str);
+    }
+    try stdout.write("\n");
+
+    try stdout.write("Environments:\n");
+    for (environ_list) |environ_tag| {
+        const native_str = if (builtin.environ == environ_tag) " (native) " else "";
+        try stdout.print("  {}{}\n", @tagName(environ_tag), native_str);
+    }
+}
+
+// cmd:version /////////////////////////////////////////////////////////////////////////////////////
+
+fn cmdVersion(allocator: &Allocator, args: []const []const u8) !void {
+    try stdout.print("{}\n", std.cstr.toSliceConst(c.ZIG_VERSION_STRING));
 }
 
-/// Caller must free result
-fn testZigInstallPrefix(allocator: &mem.Allocator, test_path: []const u8) ![]u8 {
-    const test_zig_dir = try os.path.join(allocator, test_path, "lib", "zig");
-    errdefer allocator.free(test_zig_dir);
+// cmd:test ////////////////////////////////////////////////////////////////////////////////////////
+
+const usage_test =
+    \\usage: zig test [file]...
+    \\
+    \\Options:
+    \\   --help                 Print this help and exit
+    \\
+    \\
+    ;
+
+const args_test_spec = []Flag {
+    Flag.Bool("--help"),
+};
 
-    const test_index_file = try os.path.join(allocator, test_zig_dir, "std", "index.zig");
-    defer allocator.free(test_index_file);
 
-    var file = try os.File.openRead(allocator, test_index_file);
-    file.close();
+fn cmdTest(allocator: &Allocator, args: []const []const u8) !void {
+    var flags = try Args.parse(allocator, args_build_spec, args);
+    defer flags.deinit();
 
-    return test_zig_dir;
+    if (flags.present("help")) {
+        try stderr.write(usage_test);
+        os.exit(0);
+    }
+
+    if (flags.positionals.len != 1) {
+        try stderr.write("expected exactly one zig source file\n");
+        os.exit(1);
+    }
+
+    // compile the test program into the cache and run
+
+    // NOTE: May be overlap with buildOutput, take the shared part out.
+    try stderr.print("testing file {}\n", flags.positionals.at(0));
 }
 
-/// Caller must free result
-fn findZigLibDir(allocator: &mem.Allocator) ![]u8 {
-    const self_exe_path = try os.selfExeDirPath(allocator);
-    defer allocator.free(self_exe_path);
+// cmd:run /////////////////////////////////////////////////////////////////////////////////////////
+
+// Run should be simple and not expose the full set of arguments provided by build-exe. If specific
+// build requirements are need, the user should `build-exe` then `run` manually.
+const usage_run =
+    \\usage: zig run [file] -- <runtime args>
+    \\
+    \\Options:
+    \\   --help                 Print this help and exit
+    \\
+    \\
+    ;
+
+const args_run_spec = []Flag {
+    Flag.Bool("--help"),
+};
 
-    var cur_path: []const u8 = self_exe_path;
-    while (true) {
-        const test_dir = os.path.dirname(cur_path);
 
-        if (mem.eql(u8, test_dir, cur_path)) {
+fn cmdRun(allocator: &Allocator, args: []const []const u8) !void {
+    var compile_args = args;
+    var runtime_args: []const []const u8 = []const []const u8 {};
+
+    for (args) |argv, i| {
+        if (mem.eql(u8, argv, "--")) {
+            compile_args = args[0..i];
+            runtime_args = args[i+1..];
             break;
         }
+    }
 
-        return testZigInstallPrefix(allocator, test_dir) catch |err| {
-            cur_path = test_dir;
-            continue;
-        };
+    var flags = try Args.parse(allocator, args_run_spec, compile_args);
+    defer flags.deinit();
+
+    if (flags.present("help")) {
+        try stderr.write(usage_run);
+        os.exit(0);
     }
 
-    // TODO look in hard coded installation path from configuration
-    //if (ZIG_INSTALL_PREFIX != nullptr) {
-    //    if (test_zig_install_prefix(buf_create_from_str(ZIG_INSTALL_PREFIX), out_path)) {
-    //        return 0;
-    //    }
-    //}
+    if (flags.positionals.len != 1) {
+        try stderr.write("expected exactly one zig source file\n");
+        os.exit(1);
+    }
+
+    try stderr.print("runtime args:\n");
+    for (runtime_args) |cargs| {
+        try stderr.print("{}\n", cargs);
+    }
+}
 
-    return error.FileNotFound;
+// cmd:translate-c /////////////////////////////////////////////////////////////////////////////////
+
+const usage_translate_c =
+    \\usage: zig translate-c [file]
+    \\
+    \\Options:
+    \\  --help                       Print this help and exit
+    \\  --enable-timing-info         Print timing diagnostics
+    \\  --output [path]              Output file to write generated zig file (default: stdout)
+    \\
+    \\
+    ;
+
+const args_translate_c_spec = []Flag {
+    Flag.Bool("--help"),
+    Flag.Bool("--enable-timing-info"),
+    Flag.Arg1("--libc-include-dir"),
+    Flag.Arg1("--output"),
+};
+
+fn cmdTranslateC(allocator: &Allocator, args: []const []const u8) !void {
+    var flags = try Args.parse(allocator, args_translate_c_spec, args);
+    defer flags.deinit();
+
+    if (flags.present("help")) {
+        try stderr.write(usage_translate_c);
+        os.exit(0);
+    }
+
+    if (flags.positionals.len != 1) {
+        try stderr.write("expected exactly one c source file\n");
+        os.exit(1);
+    }
+
+    // set up codegen
+
+    const zig_root_source_file = null;
+
+    // NOTE: translate-c shouldn't require setting up the full codegen instance as it does in
+    // the C++ compiler.
+
+    // codegen_create(g);
+    // codegen_set_out_name(g, null);
+    // codegen_translate_c(g, flags.positional.at(0))
+
+    var output_stream = stdout;
+    if (flags.single("output")) |output_file| {
+        var file = try os.File.openWrite(allocator, output_file);
+        defer file.close();
+
+        var file_stream = io.FileOutStream.init(&file);
+        // TODO: Not being set correctly, still stdout
+        output_stream = &file_stream.stream;
+    }
+
+    // ast_render(g, output_stream, g->root_import->root, 4);
+    try output_stream.write("pub const example = 10;\n");
+
+    if (flags.present("enable-timing-info")) {
+        // codegen_print_timing_info(g, stdout);
+        try stderr.write("printing timing info for translate-c\n");
+    }
+}
+
+// cmd:help ////////////////////////////////////////////////////////////////////////////////////////
+
+fn cmdHelp(allocator: &Allocator, args: []const []const u8) !void {
+    try stderr.write(usage);
+}
+
+// cmd:zen /////////////////////////////////////////////////////////////////////////////////////////
+
+const info_zen =
+    \\
+    \\ * Communicate intent precisely.
+    \\ * Edge cases matter.
+    \\ * Favor reading code over writing code.
+    \\ * Only one obvious way to do things.
+    \\ * Runtime crashes are better than bugs.
+    \\ * Compile errors are better than runtime crashes.
+    \\ * Incremental improvements.
+    \\ * Avoid local maximums.
+    \\ * Reduce the amount one must remember.
+    \\ * Minimize energy spent on coding style.
+    \\ * Together we serve end users.
+    \\
+    \\
+    ;
+
+fn cmdZen(allocator: &Allocator, args: []const []const u8) !void {
+    try stdout.write(info_zen);
+}
+
+// cmd:internal ////////////////////////////////////////////////////////////////////////////////////
+
+const usage_internal =
+    \\usage: zig internal [subcommand]
+    \\
+    \\Sub-Commands:
+    \\  build-info                   Print static compiler build-info
+    \\
+    \\
+    ;
+
+fn cmdInternal(allocator: &Allocator, args: []const []const u8) !void {
+    if (args.len == 0) {
+        try stderr.write(usage_internal);
+        os.exit(1);
+    }
+
+    const sub_commands = []Command {
+        Command { .name = "build-info", .exec = cmdInternalBuildInfo },
+    };
+
+    for (sub_commands) |sub_command| {
+        if (mem.eql(u8, sub_command.name, args[0])) {
+            try sub_command.exec(allocator, args[1..]);
+            return;
+        }
+    }
+
+    try stderr.print("unknown sub command: {}\n\n", args[0]);
+    try stderr.write(usage_internal);
+}
+
+fn cmdInternalBuildInfo(allocator: &Allocator, args: []const []const u8) !void {
+    try stdout.print(
+        \\ZIG_CMAKE_BINARY_DIR {}
+        \\ZIG_CXX_COMPILER     {}
+        \\ZIG_LLVM_CONFIG_EXE  {}
+        \\ZIG_LLD_INCLUDE_PATH {}
+        \\ZIG_LLD_LIBRARIES    {}
+        \\ZIG_STD_FILES        {}
+        \\ZIG_C_HEADER_FILES   {}
+        \\ZIG_DIA_GUIDS_LIB    {}
+        \\
+        ,
+        std.cstr.toSliceConst(c.ZIG_CMAKE_BINARY_DIR),
+        std.cstr.toSliceConst(c.ZIG_CXX_COMPILER),
+        std.cstr.toSliceConst(c.ZIG_LLVM_CONFIG_EXE),
+        std.cstr.toSliceConst(c.ZIG_LLD_INCLUDE_PATH),
+        std.cstr.toSliceConst(c.ZIG_LLD_LIBRARIES),
+        std.cstr.toSliceConst(c.ZIG_STD_FILES),
+        std.cstr.toSliceConst(c.ZIG_C_HEADER_FILES),
+        std.cstr.toSliceConst(c.ZIG_DIA_GUIDS_LIB),
+    );
 }
src-self-hosted/module.zig
@@ -109,6 +109,29 @@ pub const Module = struct {
         LlvmIr,
     };
 
+    pub const CliPkg = struct {
+        name: []const u8,
+        path: []const u8,
+        children: ArrayList(&CliPkg),
+        parent: ?&CliPkg,
+
+        pub fn init(allocator: &mem.Allocator, name: []const u8, path: []const u8, parent: ?&CliPkg) !&CliPkg {
+            var pkg = try allocator.create(CliPkg);
+            pkg.name = name;
+            pkg.path = path;
+            pkg.children = ArrayList(&CliPkg).init(allocator);
+            pkg.parent = parent;
+            return pkg;
+        }
+
+        pub fn deinit(self: &CliPkg) void {
+            for (self.children.toSliceConst()) |child| {
+                child.deinit();
+            }
+            self.children.deinit();
+        }
+    };
+
     pub fn create(allocator: &mem.Allocator, name: []const u8, root_src_path: ?[]const u8, target: &const Target,
         kind: Kind, build_mode: builtin.Mode, zig_lib_dir: []const u8, cache_dir: []const u8) !&Module
     {
std/os/file.zig
@@ -85,6 +85,14 @@ pub const File = struct {
         };
     }
 
+    pub fn exists(allocator: &mem.Allocator, path: []const u8) bool {
+        if (openRead(allocator, path)) |*file| {
+            file.close();
+            return true;
+        } else |_| {
+            return false;
+        }
+    }
 
     /// Upon success, the stream is in an uninitialized state. To continue using it,
     /// you must use the open() function.