Commit 363d9038c9

Andrew Kelley <superjoe30@gmail.com>
2017-05-01 00:56:24
zig build: organize build artifacts
closes #328
1 parent 38a04a2
example/mix_o_files/build.zig
@@ -12,7 +12,7 @@ pub fn build(b: &Builder) {
 
     b.default_step.dependOn(&exe.step);
 
-    const run_cmd = b.addCommand(b.out_dir, b.env_map, "./test", [][]const u8{});
+    const run_cmd = b.addCommand(b.cache_root, b.env_map, "./test", [][]const u8{});
     run_cmd.step.dependOn(&exe.step);
 
     const test_step = b.step("test", "Test the program");
example/shared_library/build.zig
@@ -12,7 +12,7 @@ pub fn build(b: &Builder) {
 
     b.default_step.dependOn(&exe.step);
 
-    const run_cmd = b.addCommand(b.out_dir, b.env_map, "./test", [][]const u8{});
+    const run_cmd = b.addCommand(b.cache_root, b.env_map, "./test", [][]const u8{});
     run_cmd.step.dependOn(&exe.step);
 
     const test_step = b.step("test", "Test the program");
src/all_types.hpp
@@ -1488,6 +1488,7 @@ struct CodeGen {
     ZigList<TimeEvent> timing_events;
 
     Buf *cache_dir;
+    Buf *out_h_path;
 };
 
 enum VarLinkage {
src/codegen.cpp
@@ -55,11 +55,12 @@ PackageTableEntry *new_package(const char *root_src_dir, const char *root_src_pa
     return entry;
 }
 
-CodeGen *codegen_create(Buf *root_src_path, const ZigTarget *target) {
+CodeGen *codegen_create(Buf *root_src_path, const ZigTarget *target, OutType out_type) {
     CodeGen *g = allocate<CodeGen>(1);
 
     codegen_add_time_event(g, "Initialize");
 
+    g->out_type = out_type;
     g->import_table.init(32);
     g->builtin_fn_table.init(32);
     g->primitive_type_table.init(32);
@@ -74,7 +75,7 @@ CodeGen *codegen_create(Buf *root_src_path, const ZigTarget *target) {
     g->external_prototypes.init(8);
     g->is_release_build = false;
     g->is_test_build = false;
-    g->want_h_file = true;
+    g->want_h_file = (out_type == OutTypeObj || out_type == OutTypeLib);
 
     buf_resize(&g->global_asm, 0);
 
@@ -145,6 +146,10 @@ CodeGen *codegen_create(Buf *root_src_path, const ZigTarget *target) {
     return g;
 }
 
+void codegen_set_output_h_path(CodeGen *g, Buf *h_path) {
+    g->out_h_path = h_path;
+}
+
 void codegen_set_clang_argv(CodeGen *g, const char **args, size_t len) {
     g->clang_argv = args;
     g->clang_argv_len = len;
@@ -196,10 +201,6 @@ void codegen_set_strip(CodeGen *g, bool strip) {
     g->strip_debug_symbols = strip;
 }
 
-void codegen_set_out_type(CodeGen *g, OutType out_type) {
-    g->out_type = out_type;
-}
-
 void codegen_set_out_name(CodeGen *g, Buf *out_name) {
     g->root_out_name = out_name;
 }
@@ -4808,15 +4809,6 @@ static void gen_global_asm(CodeGen *g) {
     }
 }
 
-void codegen_build(CodeGen *g) {
-    assert(g->out_type != OutTypeUnknown);
-    init(g);
-
-    gen_global_asm(g);
-    gen_root_source(g);
-    do_code_gen(g);
-}
-
 void codegen_add_object(CodeGen *g, Buf *object_path) {
     g->link_objects.append(object_path);
 }
@@ -4946,13 +4938,21 @@ static void get_c_type(CodeGen *g, TypeTableEntry *type_entry, Buf *out_buf) {
     }
 }
 
-void codegen_generate_h_file(CodeGen *g) {
+static void gen_h_file(CodeGen *g) {
+    if (!g->want_h_file)
+        return;
+
+    codegen_add_time_event(g, "Generate .h");
+
     assert(!g->is_test_build);
 
-    Buf *h_file_out_path = buf_sprintf("%s.h", buf_ptr(g->root_out_name));
-    FILE *out_h = fopen(buf_ptr(h_file_out_path), "wb");
+    if (!g->out_h_path) {
+        g->out_h_path = buf_sprintf("%s.h", buf_ptr(g->root_out_name));
+    }
+
+    FILE *out_h = fopen(buf_ptr(g->out_h_path), "wb");
     if (!out_h)
-        zig_panic("unable to open %s: %s", buf_ptr(h_file_out_path), strerror(errno));
+        zig_panic("unable to open %s: %s", buf_ptr(g->out_h_path), strerror(errno));
 
     Buf *export_macro = buf_sprintf("%s_EXPORT", buf_ptr(g->root_out_name));
     buf_upcase(export_macro);
@@ -5056,3 +5056,13 @@ void codegen_print_timing_report(CodeGen *g, FILE *f) {
 void codegen_add_time_event(CodeGen *g, const char *name) {
     g->timing_events.append({os_get_time(), name});
 }
+
+void codegen_build(CodeGen *g) {
+    assert(g->out_type != OutTypeUnknown);
+    init(g);
+
+    gen_global_asm(g);
+    gen_root_source(g);
+    do_code_gen(g);
+    gen_h_file(g);
+}
src/codegen.hpp
@@ -14,7 +14,7 @@
 
 #include <stdio.h>
 
-CodeGen *codegen_create(Buf *root_src_path, const ZigTarget *target);
+CodeGen *codegen_create(Buf *root_src_path, const ZigTarget *target, OutType out_type);
 
 void codegen_set_clang_argv(CodeGen *codegen, const char **args, size_t len);
 void codegen_set_is_release(CodeGen *codegen, bool is_release);
@@ -25,7 +25,6 @@ void codegen_set_is_static(CodeGen *codegen, bool is_static);
 void codegen_set_strip(CodeGen *codegen, bool strip);
 void codegen_set_verbose(CodeGen *codegen, bool verbose);
 void codegen_set_errmsg_color(CodeGen *codegen, ErrColor err_color);
-void codegen_set_out_type(CodeGen *codegen, OutType out_type);
 void codegen_set_out_name(CodeGen *codegen, Buf *out_name);
 void codegen_set_libc_lib_dir(CodeGen *codegen, Buf *libc_lib_dir);
 void codegen_set_libc_static_lib_dir(CodeGen *g, Buf *libc_static_lib_dir);
@@ -48,6 +47,7 @@ void codegen_set_test_filter(CodeGen *g, Buf *filter);
 void codegen_set_test_name_prefix(CodeGen *g, Buf *prefix);
 void codegen_set_lib_version(CodeGen *g, size_t major, size_t minor, size_t patch);
 void codegen_set_cache_dir(CodeGen *g, Buf *cache_dir);
+void codegen_set_output_h_path(CodeGen *g, Buf *h_path);
 void codegen_add_time_event(CodeGen *g, const char *name);
 void codegen_print_timing_report(CodeGen *g, FILE *f);
 void codegen_build(CodeGen *g);
@@ -59,6 +59,5 @@ void codegen_add_object(CodeGen *g, Buf *object_path);
 void codegen_parseh(CodeGen *g, Buf *path);
 void codegen_render_ast(CodeGen *g, FILE *f, int indent_size);
 
-void codegen_generate_h_file(CodeGen *g);
 
 #endif
src/link.cpp
@@ -37,7 +37,7 @@ static Buf *build_o(CodeGen *parent_gen, const char *oname) {
     os_path_join(parent_gen->zig_std_special_dir, source_basename, full_path);
 
     ZigTarget *child_target = parent_gen->is_native_target ? nullptr : &parent_gen->zig_target;
-    CodeGen *child_gen = codegen_create(full_path, child_target);
+    CodeGen *child_gen = codegen_create(full_path, child_target, OutTypeObj);
     child_gen->link_libc = parent_gen->link_libc;
 
     child_gen->link_libs.resize(parent_gen->link_libs.length);
@@ -55,7 +55,6 @@ static Buf *build_o(CodeGen *parent_gen, const char *oname) {
     codegen_set_strip(child_gen, parent_gen->strip_debug_symbols);
     codegen_set_is_static(child_gen, parent_gen->is_static);
 
-    codegen_set_out_type(child_gen, OutTypeObj);
     codegen_set_out_name(child_gen, buf_create_from_str(oname));
 
     codegen_set_verbose(child_gen, parent_gen->verbose);
@@ -186,9 +185,10 @@ static void construct_linker_job_elf(LinkJob *lj) {
     } else if (shared) {
         lj->args.append("-shared");
 
-        buf_resize(&lj->out_file, 0);
-        buf_appendf(&lj->out_file, "lib%s.so.%zu.%zu.%zu",
-                buf_ptr(g->root_out_name), g->version_major, g->version_minor, g->version_patch);
+        if (buf_len(&lj->out_file) == 0) {
+            buf_appendf(&lj->out_file, "lib%s.so.%zu.%zu.%zu",
+                    buf_ptr(g->root_out_name), g->version_major, g->version_minor, g->version_patch);
+        }
         soname = buf_sprintf("lib%s.so.%zu", buf_ptr(g->root_out_name), g->version_major);
     }
 
@@ -752,9 +752,6 @@ void codegen_link(CodeGen *g, const char *out_file) {
     }
 
     if (g->out_type == OutTypeObj) {
-        if (g->want_h_file) {
-            codegen_generate_h_file(g);
-        }
         if (override_out_file) {
             assert(g->link_objects.length == 1);
             Buf *o_file_path = g->link_objects.at(0);
@@ -798,13 +795,6 @@ void codegen_link(CodeGen *g, const char *out_file) {
         fprintf(stderr, "%s\n", buf_ptr(&diag));
         exit(1);
     }
-    codegen_add_time_event(g, "Generate .h");
-
-    if (g->out_type == OutTypeLib ||
-        g->out_type == OutTypeObj)
-    {
-        codegen_generate_h_file(g);
-    }
 
     codegen_add_time_event(g, "Done");
 
src/main.cpp
@@ -35,6 +35,7 @@ static int usage(const char *arg0) {
         "  --libc-include-dir [path]    directory where libc stdlib.h resides\n"
         "  --name [name]                override output name\n"
         "  --output [file]              override destination path\n"
+        "  --output-h [file]            override generated header file path\n"
         "  --release                    build with optimizations on and debug protection off\n"
         "  --static                     output will be statically linked\n"
         "  --strip                      exclude debug symbols\n"
@@ -118,6 +119,8 @@ enum Cmd {
     CmdTargets,
 };
 
+static const char *default_zig_cache_name = "zig-cache";
+
 int main(int argc, char **argv) {
     os_init();
 
@@ -125,6 +128,7 @@ int main(int argc, char **argv) {
     Cmd cmd = CmdInvalid;
     const char *in_file = nullptr;
     const char *out_file = nullptr;
+    const char *out_file_h = nullptr;
     bool is_release_build = false;
     bool strip = false;
     bool is_static = false;
@@ -163,7 +167,7 @@ int main(int argc, char **argv) {
     size_t ver_minor = 0;
     size_t ver_patch = 0;
     bool timing_info = false;
-    const char *cache_dir = "zig-cache";
+    const char *cache_dir = nullptr;
 
     if (argc >= 2 && strcmp(argv[1], "build") == 0) {
         const char *zig_exe_path = arg0;
@@ -200,9 +204,8 @@ int main(int argc, char **argv) {
             }
         }
 
-        CodeGen *g = codegen_create(build_runner_path, nullptr);
+        CodeGen *g = codegen_create(build_runner_path, nullptr, OutTypeExe);
         codegen_set_out_name(g, buf_create_from_str("build"));
-        codegen_set_out_type(g, OutTypeExe);
         codegen_set_verbose(g, verbose);
 
         Buf build_file_abs = BUF_INIT;
@@ -212,7 +215,12 @@ int main(int argc, char **argv) {
         os_path_split(&build_file_abs, &build_file_dirname, &build_file_basename);
 
         Buf *full_cache_dir = buf_alloc();
-        os_path_resolve(buf_create_from_str("."), buf_create_from_str(cache_dir), full_cache_dir);
+        if (cache_dir == nullptr) {
+            os_path_join(&build_file_dirname, buf_create_from_str(default_zig_cache_name), full_cache_dir);
+        } else {
+            os_path_resolve(buf_create_from_str("."), buf_create_from_str(cache_dir), full_cache_dir);
+        }
+
         Buf *path_to_build_exe = buf_alloc();
         os_path_join(full_cache_dir, buf_create_from_str("build"), path_to_build_exe);
         codegen_set_cache_dir(g, full_cache_dir);
@@ -309,6 +317,8 @@ int main(int argc, char **argv) {
                     return usage(arg0);
                 } else if (strcmp(arg, "--output") == 0) {
                     out_file = argv[i];
+                } else if (strcmp(arg, "--output-h") == 0) {
+                    out_file_h = argv[i];
                 } else if (strcmp(arg, "--color") == 0) {
                     if (strcmp(argv[i], "auto") == 0) {
                         color = ErrColorAuto;
@@ -396,6 +406,7 @@ int main(int argc, char **argv) {
                 cmd = CmdParseH;
             } else if (strcmp(arg, "test") == 0) {
                 cmd = CmdTest;
+                out_type = OutTypeExe;
             } else if (strcmp(arg, "targets") == 0) {
                 cmd = CmdTargets;
             } else {
@@ -495,9 +506,11 @@ int main(int argc, char **argv) {
             Buf *zig_root_source_file = (cmd == CmdParseH) ? nullptr : in_file_buf;
 
             Buf *full_cache_dir = buf_alloc();
-            os_path_resolve(buf_create_from_str("."), buf_create_from_str(cache_dir), full_cache_dir);
+            os_path_resolve(buf_create_from_str("."),
+                    buf_create_from_str((cache_dir == nullptr) ? default_zig_cache_name : cache_dir),
+                    full_cache_dir);
 
-            CodeGen *g = codegen_create(zig_root_source_file, target);
+            CodeGen *g = codegen_create(zig_root_source_file, target, out_type);
             codegen_set_out_name(g, buf_out_name);
             codegen_set_lib_version(g, ver_major, ver_minor, ver_patch);
             codegen_set_is_release(g, is_release_build);
@@ -510,11 +523,6 @@ int main(int argc, char **argv) {
             codegen_set_clang_argv(g, clang_argv.items, clang_argv.length);
             codegen_set_strip(g, strip);
             codegen_set_is_static(g, is_static);
-            if (out_type != OutTypeUnknown) {
-                codegen_set_out_type(g, out_type);
-            } else if (cmd == CmdTest) {
-                codegen_set_out_type(g, OutTypeExe);
-            }
             if (libc_lib_dir)
                 codegen_set_libc_lib_dir(g, buf_create_from_str(libc_lib_dir));
             if (libc_static_lib_dir)
@@ -568,6 +576,9 @@ int main(int argc, char **argv) {
                 codegen_set_test_name_prefix(g, buf_create_from_str(test_name_prefix));
             }
 
+            if (out_file_h)
+                codegen_set_output_h_path(g, buf_create_from_str(out_file_h));
+
             if (cmd == CmdBuild) {
                 for (size_t i = 0; i < objects.length; i += 1) {
                     codegen_add_object(g, buf_create_from_str(objects.at(i)));
std/os/child_process.zig
@@ -30,12 +30,13 @@ pub const ChildProcess = struct {
         Close,
     };
 
-    pub fn spawn(exe_path: []const u8, args: []const []const u8, env_map: &const BufMap,
+    pub fn spawn(exe_path: []const u8, args: []const []const u8,
+        cwd: ?[]const u8, env_map: &const BufMap,
         stdin: StdIo, stdout: StdIo, stderr: StdIo, allocator: &Allocator) -> %ChildProcess
     {
         switch (@compileVar("os")) {
             Os.linux, Os.macosx, Os.ios, Os.darwin => {
-                return spawnPosix(exe_path, args, env_map, stdin, stdout, stderr, allocator);
+                return spawnPosix(exe_path, args, cwd, env_map, stdin, stdout, stderr, allocator);
             },
             else => @compileError("Unsupported OS"),
         }
@@ -98,7 +99,8 @@ pub const ChildProcess = struct {
         };
     }
 
-    fn spawnPosix(exe_path: []const u8, args: []const []const u8, env_map: &const BufMap,
+    fn spawnPosix(exe_path: []const u8, args: []const []const u8,
+        maybe_cwd: ?[]const u8, env_map: &const BufMap,
         stdin: StdIo, stdout: StdIo, stderr: StdIo, allocator: &Allocator) -> %ChildProcess
     {
         // TODO issue #295
@@ -155,6 +157,11 @@ pub const ChildProcess = struct {
             setUpChildIo(stderr, stderr_pipe[1], posix.STDERR_FILENO, dev_null_fd) %%
                 |err| forkChildErrReport(err_pipe[1], err);
 
+            test (maybe_cwd) |cwd| {
+                os.changeCurDir(allocator, cwd) %%
+                    |err| forkChildErrReport(err_pipe[1], err);
+            }
+
             os.posixExecve(exe_path, args, env_map, allocator) %%
                 |err| forkChildErrReport(err_pipe[1], err);
         }
std/os/index.zig
@@ -780,3 +780,60 @@ pub const Dir = struct {
         };
     }
 };
+
+pub fn changeCurDir(allocator: &Allocator, dir_path: []const u8) -> %void {
+    const path_buf = %return allocator.alloc(u8, dir_path.len + 1);
+    defer allocator.free(path_buf);
+
+    mem.copy(u8, path_buf, dir_path);
+    path_buf[dir_path.len] = 0;
+
+    const err = posix.getErrno(posix.chdir(path_buf.ptr));
+    if (err > 0) {
+        return switch (err) {
+            errno.EACCES => error.AccessDenied,
+            errno.EFAULT => unreachable,
+            errno.EIO => error.FileSystem,
+            errno.ELOOP => error.SymLinkLoop,
+            errno.ENAMETOOLONG => error.NameTooLong,
+            errno.ENOENT => error.FileNotFound,
+            errno.ENOMEM => error.SystemResources,
+            errno.ENOTDIR => error.NotDir,
+            else => error.Unexpected,
+        };
+    }
+}
+
+/// Read value of a symbolic link.
+pub fn readLink(allocator: &Allocator, pathname: []const u8) -> %[]u8 {
+    const path_buf = %return allocator.alloc(u8, pathname.len + 1);
+    defer allocator.free(path_buf);
+
+    mem.copy(u8, path_buf, pathname);
+    path_buf[pathname.len] = 0;
+
+    var result_buf = %return allocator.alloc(u8, 1024);
+    %defer allocator.free(result_buf);
+    while (true) {
+        const ret_val = posix.readlink(path_buf.ptr, result_buf.ptr, result_buf.len);
+        const err = posix.getErrno(ret_val);
+        if (err > 0) {
+            return switch (err) {
+                errno.EACCES => error.AccessDenied,
+                errno.EFAULT, errno.EINVAL => unreachable,
+                errno.EIO => error.FileSystem,
+                errno.ELOOP => error.SymLinkLoop,
+                errno.ENAMETOOLONG => error.NameTooLong,
+                errno.ENOENT => error.FileNotFound,
+                errno.ENOMEM => error.SystemResources,
+                errno.ENOTDIR => error.NotDir,
+                else => error.Unexpected,
+            };
+        }
+        if (ret_val == result_buf.len) {
+            result_buf = %return allocator.realloc(u8, result_buf, result_buf.len * 2);
+            continue;
+        }
+        return result_buf[0...ret_val];
+    }
+}
std/os/linux.zig
@@ -335,6 +335,10 @@ pub fn dup2(old: i32, new: i32) -> usize {
     arch.syscall2(arch.SYS_dup2, usize(old), usize(new))
 }
 
+pub fn chdir(path: &const u8) -> usize {
+    arch.syscall1(arch.SYS_chdir, usize(path))
+}
+
 pub fn execve(path: &const u8, argv: &const ?&const u8, envp: &const ?&const u8) -> usize {
     arch.syscall3(arch.SYS_execve, usize(path), usize(argv), usize(envp))
 }
@@ -356,6 +360,10 @@ pub fn isatty(fd: i32) -> bool {
     return arch.syscall3(arch.SYS_ioctl, usize(fd), TIOCGWINSZ, usize(&wsz)) == 0;
 }
 
+pub fn readlink(noalias path: &const u8, noalias buf_ptr: &u8, buf_len: usize) -> usize {
+    arch.syscall3(arch.SYS_readlink, usize(path), usize(buf_ptr), buf_len)
+}
+
 pub fn mkdir(path: &const u8, mode: usize) -> usize {
     arch.syscall2(arch.SYS_mkdir, usize(path), mode)
 }
std/os/path.zig
@@ -1,9 +1,11 @@
 const debug = @import("../debug.zig");
 const assert = debug.assert;
 const mem = @import("../mem.zig");
+const fmt = @import("../fmt.zig");
 const Allocator = mem.Allocator;
 const os = @import("index.zig");
 const math = @import("../math.zig");
+const posix = os.posix;
 
 pub const sep = switch (@compileVar("os")) {
     Os.windows => '\\',
@@ -185,6 +187,45 @@ fn testDirname(input: []const u8, expected_output: []const u8) {
     assert(mem.eql(u8, dirname(input), expected_output));
 }
 
+pub fn basename(path: []const u8) -> []const u8 {
+    if (path.len == 0)
+        return []u8{};
+
+    var end_index: usize = path.len - 1;
+    while (path[end_index] == '/') {
+        if (end_index == 0)
+            return []u8{};
+        end_index -= 1;
+    }
+    var start_index: usize = end_index;
+    end_index += 1;
+    while (path[start_index] != '/') {
+        if (start_index == 0)
+            return path[0...end_index];
+        start_index -= 1;
+    }
+
+    return path[start_index + 1...end_index];
+}
+
+test "os.path.basename" {
+    testBasename("", "");
+    testBasename("/", "");
+    testBasename("/dir/basename.ext", "basename.ext");
+    testBasename("/basename.ext", "basename.ext");
+    testBasename("basename.ext", "basename.ext");
+    testBasename("basename.ext/", "basename.ext");
+    testBasename("basename.ext//", "basename.ext");
+    testBasename("/aaa/bbb", "bbb");
+    testBasename("/aaa/", "aaa");
+    testBasename("/aaa/b", "b");
+    testBasename("/a/b", "b");
+    testBasename("//a", "a");
+}
+fn testBasename(input: []const u8, expected_output: []const u8) {
+    assert(mem.eql(u8, basename(input), expected_output));
+}
+
 /// Returns the relative path from ::from to ::to. If ::from and ::to each
 /// resolve to the same path (after calling ::resolve on each), a zero-length
 /// string is returned.
@@ -252,3 +293,17 @@ fn testRelative(from: []const u8, to: []const u8, expected_output: []const u8) {
     const result = %%relative(&debug.global_allocator, from, to);
     assert(mem.eql(u8, result, expected_output));
 }
+
+/// Return the canonicalized absolute pathname.
+/// Expands all symbolic links and resolves references to `.`, `..`, and
+/// extra `/` characters in ::pathname.
+/// Caller must deallocate result.
+pub fn real(allocator: &Allocator, pathname: []const u8) -> %[]u8 {
+    const fd = %return os.posixOpen(pathname, posix.O_PATH|posix.O_NONBLOCK|posix.O_CLOEXEC, 0, allocator);
+    defer os.posixClose(fd);
+
+    var buf: ["/proc/self/fd/-2147483648".len]u8 = undefined;
+    const proc_path = fmt.bufPrint(buf[0...], "/proc/self/fd/{}", fd);
+
+    return os.readLink(allocator, proc_path);
+}
std/build.zig
@@ -37,7 +37,6 @@ pub const Builder = struct {
     top_level_steps: List(&TopLevelStep),
     prefix: []const u8,
     lib_dir: []const u8,
-    out_dir: []u8, // TODO get rid of this
     installed_files: List([]const u8),
     build_root: []const u8,
     cache_root: []const u8,
@@ -97,7 +96,6 @@ pub const Builder = struct {
             .env_map = %%os.getEnvMap(allocator),
             .prefix = undefined,
             .lib_dir = undefined,
-            .out_dir = %%os.getCwd(allocator),
             .installed_files = List([]const u8).init(allocator),
             .uninstall_tls = TopLevelStep {
                 .step = Step.init("uninstall", allocator, makeUninstall),
@@ -111,7 +109,6 @@ pub const Builder = struct {
     }
 
     pub fn deinit(self: &Builder) {
-        self.allocator.free(self.out_dir);
         self.lib_paths.deinit();
         self.include_paths.deinit();
         self.rpaths.deinit();
@@ -172,12 +169,10 @@ pub const Builder = struct {
         return exe;
     }
 
-    pub fn addCommand(self: &Builder, cwd: []const u8, env_map: &const BufMap,
+    pub fn addCommand(self: &Builder, cwd: ?[]const u8, env_map: &const BufMap,
         path: []const u8, args: []const []const u8) -> &CommandStep
     {
-        const cmd = %%self.allocator.create(CommandStep);
-        *cmd = CommandStep.init(self, cwd, env_map, path, args);
-        return cmd;
+        return CommandStep.create(self, cwd, env_map, path, args);
     }
 
     pub fn addWriteFile(self: &Builder, file_path: []const u8, data: []const u8) -> &WriteFileStep {
@@ -489,21 +484,22 @@ pub const Builder = struct {
     }
 
     fn spawnChild(self: &Builder, exe_path: []const u8, args: []const []const u8) -> %void {
-        return self.spawnChildEnvMap(&self.env_map, exe_path, args);
+        return self.spawnChildEnvMap(null, &self.env_map, exe_path, args);
     }
 
-    fn spawnChildEnvMap(self: &Builder, env_map: &const BufMap, exe_path: []const u8,
-        args: []const []const u8) -> %void
+    fn spawnChildEnvMap(self: &Builder, cwd: ?[]const u8, env_map: &const BufMap,
+        exe_path: []const u8, args: []const []const u8) -> %void
     {
         if (self.verbose) {
-            %%io.stderr.printf("{}", exe_path);
+            test (cwd) |yes_cwd| %%io.stderr.print("cd {}; ", yes_cwd);
+            %%io.stderr.print("{}", exe_path);
             for (args) |arg| {
-                %%io.stderr.printf(" {}", arg);
+                %%io.stderr.print(" {}", arg);
             }
             %%io.stderr.printf("\n");
         }
 
-        var child = os.ChildProcess.spawn(exe_path, args, env_map,
+        var child = os.ChildProcess.spawn(exe_path, args, cwd, env_map,
             StdIo.Ignore, StdIo.Inherit, StdIo.Inherit, self.allocator) %% |err|
         {
             %%io.stderr.printf("Unable to spawn {}: {}\n", exe_path, @errorName(err));
@@ -511,6 +507,7 @@ pub const Builder = struct {
         };
 
         const term = child.wait() %% |err| {
+            test (cwd) |yes_cwd| %%io.stderr.printf("cwd: {}\n", yes_cwd);
             %%io.stderr.printf("Unable to spawn {}: {}\n", exe_path, @errorName(err));
             return err;
         };
@@ -560,11 +557,12 @@ pub const Builder = struct {
 
     fn copyFile(self: &Builder, source_path: []const u8, dest_path: []const u8) {
         const dirname = os.path.dirname(dest_path);
+        const abs_source_path = self.pathFromRoot(source_path);
         os.makePath(self.allocator, dirname) %% |err| {
             debug.panic("Unable to create path {}: {}", dirname, @errorName(err));
         };
-        os.copyFile(self.allocator, source_path, dest_path) %% |err| {
-            debug.panic("Unable to copy {} to {}: {}", source_path, dest_path, @errorName(err));
+        os.copyFile(self.allocator, abs_source_path, dest_path) %% |err| {
+            debug.panic("Unable to copy {} to {}: {}", abs_source_path, dest_path, @errorName(err));
         };
     }
 
@@ -628,8 +626,10 @@ pub const LibExeObjStep = struct {
     release: bool,
     static: bool,
     output_path: ?[]const u8,
+    output_h_path: ?[]const u8,
     kind: Kind,
     version: Version,
+    out_h_filename: []const u8,
     out_filename: []const u8,
     out_filename_major_only: []const u8,
     out_filename_name_only: []const u8,
@@ -684,8 +684,10 @@ pub const LibExeObjStep = struct {
             .link_libs = BufSet.init(builder.allocator),
             .step = Step.init(name, builder.allocator, make),
             .output_path = null,
+            .output_h_path = null,
             .version = *ver,
             .out_filename = undefined,
+            .out_h_filename = builder.fmt("{}.h", name),
             .out_filename_major_only = undefined,
             .out_filename_name_only = undefined,
             .object_files = List([]const u8).init(builder.allocator),
@@ -747,6 +749,27 @@ pub const LibExeObjStep = struct {
         self.output_path = value;
     }
 
+    pub fn getOutputPath(self: &LibExeObjStep) -> []const u8 {
+        test (self.output_path) |output_path| {
+            output_path
+        } else {
+            const wanted_path = %%os.path.join(self.builder.allocator, self.builder.cache_root, self.out_filename);
+            %%os.path.relative(self.builder.allocator, self.builder.build_root, wanted_path)
+        }
+    }
+
+    pub fn setOutputHPath(self: &LibExeObjStep, value: []const u8) {
+        self.output_h_path = value;
+    }
+
+    pub fn getOutputHPath(self: &LibExeObjStep) -> []const u8 {
+        test (self.output_h_path) |output_h_path| {
+            output_h_path
+        } else {
+            %%os.path.join(self.builder.allocator, self.builder.cache_root, self.out_h_filename)
+        }
+    }
+
     pub fn addAssemblyFile(self: &LibExeObjStep, path: []const u8) {
         %%self.assembly_files.append(path);
     }
@@ -763,14 +786,7 @@ pub const LibExeObjStep = struct {
 
         self.step.dependOn(&obj.step);
 
-        const path_to_obj = test (obj.output_path) |explicit_out_path| {
-            explicit_out_path
-        } else {
-            // TODO make it so we always know where this will be
-            %%os.path.join(self.builder.allocator, self.builder.cache_root,
-                self.builder.fmt("{}{}", obj.name, obj.target.oFileExt()))
-        };
-        %%self.object_files.append(path_to_obj);
+        %%self.object_files.append(obj.getOutputPath());
     }
 
     fn make(step: &Step) -> %void {
@@ -814,10 +830,16 @@ pub const LibExeObjStep = struct {
             %%zig_args.append("--release");
         }
 
-        test (self.output_path) |output_path| {
-            %%zig_args.append("--output");
-            %%zig_args.append(builder.pathFromRoot(output_path));
-        }
+        %%zig_args.append("--cache-dir");
+        %%zig_args.append(builder.cache_root);
+
+        const output_path = builder.pathFromRoot(self.getOutputPath());
+        %%zig_args.append("--output");
+        %%zig_args.append(output_path);
+
+        const output_h_path = self.getOutputHPath();
+        %%zig_args.append("--output-h");
+        %%zig_args.append(builder.pathFromRoot(output_h_path));
 
         %%zig_args.append("--name");
         %%zig_args.append(self.name);
@@ -879,10 +901,8 @@ pub const LibExeObjStep = struct {
         %return builder.spawnChild(builder.zig_exe, zig_args.toSliceConst());
 
         if (self.kind == Kind.Lib and !self.static) {
-            // sym link for libfoo.so.1 to libfoo.so.1.2.3
-            %%os.atomicSymLink(builder.allocator, self.out_filename, self.out_filename_major_only);
-            // sym link for libfoo.so to libfoo.so.1
-            %%os.atomicSymLink(builder.allocator, self.out_filename_major_only, self.out_filename_name_only);
+            %return doAtomicSymLinks(builder.allocator, output_path, self.out_filename_major_only,
+                self.out_filename_name_only);
         }
     }
 };
@@ -987,10 +1007,12 @@ pub const TestStep = struct {
     }
 };
 
+// TODO merge with CExecutable
 pub const CLibrary = struct {
     step: Step,
     name: []const u8,
     out_filename: []const u8,
+    output_path: ?[]const u8,
     static: bool,
     version: Version,
     cflags: List([]const u8),
@@ -1024,6 +1046,7 @@ pub const CLibrary = struct {
             .step = Step.init(name, builder.allocator, make),
             .link_libs = BufSet.init(builder.allocator),
             .include_dirs = List([]const u8).init(builder.allocator),
+            .output_path = null,
             .out_filename = undefined,
             .major_only_filename = undefined,
             .name_only_filename = undefined,
@@ -1043,6 +1066,19 @@ pub const CLibrary = struct {
         }
     }
 
+    pub fn setOutputPath(self: &CLibrary, value: []const u8) {
+        self.output_path = value;
+    }
+
+    pub fn getOutputPath(self: &CLibrary) -> []const u8 {
+        test (self.output_path) |output_path| {
+            output_path
+        } else {
+            const wanted_path = %%os.path.join(self.builder.allocator, self.builder.cache_root, self.out_filename);
+            %%os.path.relative(self.builder.allocator, self.builder.build_root, wanted_path)
+        }
+    }
+
     pub fn linkSystemLibrary(self: &CLibrary, name: []const u8) {
         %%self.link_libs.put(name);
     }
@@ -1131,8 +1167,9 @@ pub const CLibrary = struct {
             defer builder.allocator.free(soname_arg);
             %%cc_args.append(soname_arg);
 
+            const output_path = builder.pathFromRoot(self.getOutputPath());
             %%cc_args.append("-o");
-            %%cc_args.append(self.out_filename);
+            %%cc_args.append(output_path);
 
             for (self.object_files.toSliceConst()) |object_file| {
                 %%cc_args.append(builder.pathFromRoot(object_file));
@@ -1140,10 +1177,8 @@ pub const CLibrary = struct {
 
             %return builder.spawnChild(cc, cc_args.toSliceConst());
 
-            // sym link for libfoo.so.1 to libfoo.so.1.2.3
-            %%os.atomicSymLink(builder.allocator, self.out_filename, self.major_only_filename);
-            // sym link for libfoo.so to libfoo.so.1
-            %%os.atomicSymLink(builder.allocator, self.major_only_filename, self.name_only_filename);
+            %return doAtomicSymLinks(builder.allocator, output_path, self.major_only_filename,
+                self.name_only_filename);
         }
     }
 
@@ -1169,9 +1204,11 @@ pub const CExecutable = struct {
     link_libs: BufSet,
     target: Target,
     include_dirs: List([]const u8),
+    output_path: ?[]const u8,
+    out_filename: []const u8,
 
     pub fn init(builder: &Builder, name: []const u8) -> CExecutable {
-        CExecutable {
+        var self = CExecutable {
             .builder = builder,
             .name = name,
             .target = Target.Native,
@@ -1182,6 +1219,27 @@ pub const CExecutable = struct {
             .step = Step.init(name, builder.allocator, make),
             .link_libs = BufSet.init(builder.allocator),
             .include_dirs = List([]const u8).init(builder.allocator),
+            .output_path = null,
+            .out_filename = undefined,
+        };
+        self.computeOutFileName();
+        return self;
+    }
+
+    fn computeOutFileName(self: &CExecutable) {
+        self.out_filename = self.builder.fmt("{}{}", self.name, self.target.exeFileExt());
+    }
+
+    pub fn setOutputPath(self: &CExecutable, value: []const u8) {
+        self.output_path = value;
+    }
+
+    pub fn getOutputPath(self: &CExecutable) -> []const u8 {
+        test (self.output_path) |output_path| {
+            self.builder.pathFromRoot(output_path)
+        } else {
+            const wanted_path = %%os.path.join(self.builder.allocator, self.builder.cache_root, self.out_filename);
+            %%os.path.relative(self.builder.allocator, self.builder.build_root, wanted_path)
         }
     }
 
@@ -1191,15 +1249,15 @@ pub const CExecutable = struct {
 
     pub fn linkCLibrary(self: &CExecutable, clib: &CLibrary) {
         self.step.dependOn(&clib.step);
-        %%self.full_path_libs.append(clib.out_filename);
+        %%self.full_path_libs.append(clib.getOutputPath());
     }
 
     pub fn linkLibrary(self: &CExecutable, lib: &LibExeObjStep) {
         assert(lib.kind == LibExeObjStep.Kind.Lib);
         self.step.dependOn(&lib.step);
-        %%self.full_path_libs.append(lib.out_filename);
+        %%self.full_path_libs.append(lib.getOutputPath());
         // TODO should be some kind of isolated directory that only has this header in it
-        %%self.include_dirs.append(self.builder.out_dir);
+        %%self.include_dirs.append(self.builder.cache_root);
     }
 
     pub fn addObject(self: &CExecutable, obj: &LibExeObjStep) {
@@ -1207,12 +1265,10 @@ pub const CExecutable = struct {
 
         self.step.dependOn(&obj.step);
 
-        // TODO make it so we always know where this will be
-        %%self.object_files.append(%%os.path.join(self.builder.allocator, self.builder.cache_root,
-            self.builder.fmt("{}{}", obj.name, obj.target.oFileExt())));
+        %%self.object_files.append(obj.getOutputPath());
 
         // TODO should be some kind of isolated directory that only has this header in it
-        %%self.include_dirs.append(self.builder.out_dir);
+        %%self.include_dirs.append(self.builder.cache_root);
     }
 
     pub fn addSourceFile(self: &CExecutable, file: []const u8) {
@@ -1256,7 +1312,6 @@ pub const CExecutable = struct {
             %%cc_args.append("-c");
             %%cc_args.append(builder.pathFromRoot(source_file));
 
-            // TODO don't dump the .o file in the same place as the source file
             const rel_src_path = %%os.path.relative(builder.allocator, builder.build_root, source_file);
             const cache_o_src = %%os.path.join(builder.allocator, builder.cache_root, rel_src_path);
             const cache_o_dir = os.path.dirname(cache_o_src);
@@ -1276,26 +1331,28 @@ pub const CExecutable = struct {
 
             %return builder.spawnChild(cc, cc_args.toSliceConst());
 
-            %%self.object_files.append(cache_o_file);
+            %%self.object_files.append(%%os.path.relative(builder.allocator, builder.build_root, cache_o_file));
         }
 
         %%cc_args.resize(0);
 
         for (self.object_files.toSliceConst()) |object_file| {
-            %%cc_args.append(object_file);
+            %%cc_args.append(builder.pathFromRoot(object_file));
         }
 
+        const output_path = builder.pathFromRoot(self.getOutputPath());
         %%cc_args.append("-o");
-        %%cc_args.append(self.name);
+        %%cc_args.append(output_path);
 
-        const rpath_arg = builder.fmt("-Wl,-rpath,{}", builder.out_dir);
+        const rpath_arg = builder.fmt("-Wl,-rpath,{}",
+            %%os.path.real(builder.allocator, builder.cache_root));
         defer builder.allocator.free(rpath_arg);
         %%cc_args.append(rpath_arg);
 
         %%cc_args.append("-rdynamic");
 
         for (self.full_path_libs.toSliceConst()) |full_path_lib| {
-            %%cc_args.append(full_path_lib);
+            %%cc_args.append(builder.pathFromRoot(full_path_lib));
         }
 
         %return builder.spawnChild(cc, cc_args.toSliceConst());
@@ -1317,27 +1374,29 @@ pub const CommandStep = struct {
     builder: &Builder,
     exe_path: []const u8,
     args: []const []const u8,
-    cwd: []const u8,
+    cwd: ?[]const u8,
     env_map: &const BufMap,
 
-    pub fn init(builder: &Builder, cwd: []const u8, env_map: &const BufMap,
-        exe_path: []const u8, args: []const []const u8) -> CommandStep
+    pub fn create(builder: &Builder, cwd: ?[]const u8, env_map: &const BufMap,
+        exe_path: []const u8, args: []const []const u8) -> &CommandStep
     {
-        CommandStep {
+        const self = %%builder.allocator.create(CommandStep);
+        *self = CommandStep {
             .builder = builder,
             .step = Step.init(exe_path, builder.allocator, make),
             .exe_path = exe_path,
             .args = args,
             .cwd = cwd,
             .env_map = env_map,
-        }
+        };
+        return self;
     }
 
     fn make(step: &Step) -> %void {
         const self = @fieldParentPtr(CommandStep, "step", step);
 
-        // TODO set cwd
-        return self.builder.spawnChildEnvMap(self.env_map, self.exe_path, self.args);
+        const cwd = test (self.cwd) |cwd| self.builder.pathFromRoot(cwd) else null;
+        return self.builder.spawnChildEnvMap(cwd, self.env_map, self.exe_path, self.args);
     }
 };
 
@@ -1367,12 +1426,10 @@ pub const InstallCLibraryStep = struct {
         const self = @fieldParentPtr(InstallCLibraryStep, "step", step);
         const builder = self.builder;
 
-        self.builder.copyFile(self.lib.out_filename, self.dest_file);
+        self.builder.copyFile(self.lib.getOutputPath(), self.dest_file);
         if (!self.lib.static) {
-            const dest_major_only = %%os.path.join(builder.allocator, builder.lib_dir, self.lib.major_only_filename);
-            const dest_name_only = %%os.path.join(builder.allocator, builder.lib_dir, self.lib.name_only_filename);
-            %%os.atomicSymLink(self.builder.allocator, self.lib.out_filename, dest_major_only);
-            %%os.atomicSymLink(self.builder.allocator, self.lib.major_only_filename, dest_name_only);
+            %return doAtomicSymLinks(self.builder.allocator, self.dest_file, self.lib.major_only_filename,
+                self.lib.name_only_filename);
         }
     }
 };
@@ -1506,3 +1563,22 @@ pub const Step = struct {
 
     fn makeNoOp(self: &Step) -> %void {}
 };
+
+fn doAtomicSymLinks(allocator: &Allocator, output_path: []const u8, filename_major_only: []const u8,
+    filename_name_only: []const u8) -> %void
+{
+    const out_dir = os.path.dirname(output_path);
+    const out_basename = os.path.basename(output_path);
+    // sym link for libfoo.so.1 to libfoo.so.1.2.3
+    const major_only_path = %%os.path.join(allocator, out_dir, filename_major_only);
+    os.atomicSymLink(allocator, out_basename, major_only_path) %% |err| {
+        %%io.stderr.printf("Unable to symlink {} -> {}\n", major_only_path, out_basename);
+        return err;
+    };
+    // sym link for libfoo.so to libfoo.so.1
+    const name_only_path = %%os.path.join(allocator, out_dir, filename_name_only);
+    os.atomicSymLink(allocator, filename_major_only, name_only_path) %% |err| {
+        %%io.stderr.printf("Unable to symlink {} -> {}\n", name_only_path, filename_major_only);
+        return err;
+    };
+}
std/fmt.zig
@@ -345,17 +345,17 @@ fn bufPrintWrite(context: &BufPrintContext, bytes: []const u8) -> bool {
     return true;
 }
 
-pub fn bufPrint(buf: []u8, comptime fmt: []const u8, args: ...) {
+pub fn bufPrint(buf: []u8, comptime fmt: []const u8, args: ...) -> []u8 {
     var context = BufPrintContext { .remaining = buf, };
     _ = format(&context, bufPrintWrite, fmt, args);
+    return buf[0...buf.len - context.remaining.len];
 }
 
 pub fn allocPrint(allocator: &mem.Allocator, comptime fmt: []const u8, args: ...) -> %[]u8 {
     var size: usize = 0;
     _ = format(&size, countSize, fmt, args);
     const buf = %return allocator.alloc(u8, size);
-    bufPrint(buf, fmt, args);
-    return buf;
+    return bufPrint(buf, fmt, args);
 }
 
 fn countSize(size: &usize, bytes: []const u8) -> bool {
test/tests.zig
@@ -189,7 +189,7 @@ pub const CompareOutputContext = struct {
 
             %%io.stderr.printf("Test {}/{} {}...", self.test_index+1, self.context.test_index, self.name);
 
-            var child = os.ChildProcess.spawn(full_exe_path, [][]u8{}, &b.env_map,
+            var child = os.ChildProcess.spawn(full_exe_path, [][]u8{}, null, &b.env_map,
                 StdIo.Ignore, StdIo.Pipe, StdIo.Pipe, b.allocator) %% |err|
             {
                 debug.panic("Unable to spawn {}: {}\n", full_exe_path, @errorName(err));
@@ -264,7 +264,7 @@ pub const CompareOutputContext = struct {
 
             %%io.stderr.printf("Test {}/{} {}...", self.test_index+1, self.context.test_index, self.name);
 
-            var child = os.ChildProcess.spawn(full_exe_path, [][]u8{}, &b.env_map,
+            var child = os.ChildProcess.spawn(full_exe_path, [][]u8{}, null, &b.env_map,
                 StdIo.Ignore, StdIo.Pipe, StdIo.Pipe, b.allocator) %% |err|
             {
                 debug.panic("Unable to spawn {}: {}\n", full_exe_path, @errorName(err));
@@ -344,8 +344,8 @@ pub const CompareOutputContext = struct {
     pub fn addCase(self: &CompareOutputContext, case: &const TestCase) {
         const b = self.b;
 
-        const root_src = %%os.path.join(b.allocator, "test_artifacts", case.sources.items[0].filename);
-        const exe_path = %%os.path.join(b.allocator, "test_artifacts", "test");
+        const root_src = %%os.path.join(b.allocator, b.cache_root, case.sources.items[0].filename);
+        const exe_path = %%os.path.join(b.allocator, b.cache_root, "test");
 
         switch (case.special) {
             Special.Asm => {
@@ -360,7 +360,7 @@ pub const CompareOutputContext = struct {
                 exe.setOutputPath(exe_path);
 
                 for (case.sources.toSliceConst()) |src_file| {
-                    const expanded_src_path = %%os.path.join(b.allocator, "test_artifacts", src_file.filename);
+                    const expanded_src_path = %%os.path.join(b.allocator, b.cache_root, src_file.filename);
                     const write_src = b.addWriteFile(expanded_src_path, src_file.source);
                     exe.step.dependOn(&write_src.step);
                 }
@@ -388,7 +388,7 @@ pub const CompareOutputContext = struct {
                     }
 
                     for (case.sources.toSliceConst()) |src_file| {
-                        const expanded_src_path = %%os.path.join(b.allocator, "test_artifacts", src_file.filename);
+                        const expanded_src_path = %%os.path.join(b.allocator, b.cache_root, src_file.filename);
                         const write_src = b.addWriteFile(expanded_src_path, src_file.source);
                         exe.step.dependOn(&write_src.step);
                     }
@@ -401,7 +401,7 @@ pub const CompareOutputContext = struct {
                 }
             },
             Special.DebugSafety => {
-                const obj_path = %%os.path.join(b.allocator, "test_artifacts", "test.o");
+                const obj_path = %%os.path.join(b.allocator, b.cache_root, "test.o");
                 const annotated_case_name = %%fmt.allocPrint(self.b.allocator, "debug-safety {}", case.name);
                 test (self.test_filter) |filter| {
                     if (mem.indexOf(u8, annotated_case_name, filter) == null)
@@ -415,7 +415,7 @@ pub const CompareOutputContext = struct {
                 }
 
                 for (case.sources.toSliceConst()) |src_file| {
-                    const expanded_src_path = %%os.path.join(b.allocator, "test_artifacts", src_file.filename);
+                    const expanded_src_path = %%os.path.join(b.allocator, b.cache_root, src_file.filename);
                     const write_src = b.addWriteFile(expanded_src_path, src_file.source);
                     exe.step.dependOn(&write_src.step);
                 }
@@ -488,8 +488,8 @@ pub const CompileErrorContext = struct {
             const self = @fieldParentPtr(CompileCmpOutputStep, "step", step);
             const b = self.context.b;
 
-            const root_src = %%os.path.join(b.allocator, "test_artifacts", self.case.sources.items[0].filename);
-            const obj_path = %%os.path.join(b.allocator, "test_artifacts", "test.o");
+            const root_src = %%os.path.join(b.allocator, b.cache_root, self.case.sources.items[0].filename);
+            const obj_path = %%os.path.join(b.allocator, b.cache_root, "test.o");
 
             var zig_args = List([]const u8).init(b.allocator);
             %%zig_args.append(if (self.case.is_exe) "build_exe" else "build_obj");
@@ -511,7 +511,7 @@ pub const CompileErrorContext = struct {
                 printInvocation(b.zig_exe, zig_args.toSliceConst());
             }
 
-            var child = os.ChildProcess.spawn(b.zig_exe, zig_args.toSliceConst(), &b.env_map,
+            var child = os.ChildProcess.spawn(b.zig_exe, zig_args.toSliceConst(), null, &b.env_map,
                 StdIo.Ignore, StdIo.Pipe, StdIo.Pipe, b.allocator) %% |err|
             {
                 debug.panic("Unable to spawn {}: {}\n", b.zig_exe, @errorName(err));
@@ -632,7 +632,7 @@ pub const CompileErrorContext = struct {
             self.step.dependOn(&compile_and_cmp_errors.step);
 
             for (case.sources.toSliceConst()) |src_file| {
-                const expanded_src_path = %%os.path.join(b.allocator, "test_artifacts", src_file.filename);
+                const expanded_src_path = %%os.path.join(b.allocator, b.cache_root, src_file.filename);
                 const write_src = b.addWriteFile(expanded_src_path, src_file.source);
                 compile_and_cmp_errors.step.dependOn(&write_src.step);
             }
@@ -675,7 +675,7 @@ pub const BuildExamplesContext = struct {
             %%zig_args.append("--verbose");
         }
 
-        const run_cmd = b.addCommand(b.cache_root, b.env_map, b.zig_exe, zig_args.toSliceConst());
+        const run_cmd = b.addCommand(null, b.env_map, b.zig_exe, zig_args.toSliceConst());
 
         const log_step = b.addLog("PASS {}\n", annotated_case_name);
         log_step.step.dependOn(&run_cmd.step);
@@ -762,7 +762,7 @@ pub const ParseHContext = struct {
             const self = @fieldParentPtr(ParseHCmpOutputStep, "step", step);
             const b = self.context.b;
 
-            const root_src = %%os.path.join(b.allocator, "test_artifacts", self.case.sources.items[0].filename);
+            const root_src = %%os.path.join(b.allocator, b.cache_root, self.case.sources.items[0].filename);
 
             var zig_args = List([]const u8).init(b.allocator);
             %%zig_args.append("parseh");
@@ -774,7 +774,7 @@ pub const ParseHContext = struct {
                 printInvocation(b.zig_exe, zig_args.toSliceConst());
             }
 
-            var child = os.ChildProcess.spawn(b.zig_exe, zig_args.toSliceConst(), &b.env_map,
+            var child = os.ChildProcess.spawn(b.zig_exe, zig_args.toSliceConst(), null, &b.env_map,
                 StdIo.Ignore, StdIo.Pipe, StdIo.Pipe, b.allocator) %% |err|
             {
                 debug.panic("Unable to spawn {}: {}\n", b.zig_exe, @errorName(err));
@@ -886,7 +886,7 @@ pub const ParseHContext = struct {
         self.step.dependOn(&parseh_and_cmp.step);
 
         for (case.sources.toSliceConst()) |src_file| {
-            const expanded_src_path = %%os.path.join(b.allocator, "test_artifacts", src_file.filename);
+            const expanded_src_path = %%os.path.join(b.allocator, b.cache_root, src_file.filename);
             const write_src = b.addWriteFile(expanded_src_path, src_file.source);
             parseh_and_cmp.step.dependOn(&write_src.step);
         }
.gitignore
@@ -1,6 +1,6 @@
+zig-cache/
 build/
 build-release/
 /.cproject
 /.project
 /.settings/
-/test_artifacts/
build.zig
@@ -5,19 +5,16 @@ pub fn build(b: &Builder) {
     const test_filter = b.option([]const u8, "test-filter", "Skip tests that do not match filter");
     const test_step = b.step("test", "Run all the tests");
 
-    const cleanup = b.addRemoveDirTree("test_artifacts");
-    test_step.dependOn(&cleanup.step);
-
-    cleanup.step.dependOn(tests.addPkgTests(b, test_filter,
+    test_step.dependOn(tests.addPkgTests(b, test_filter,
         "test/behavior.zig", "behavior", "Run the behavior tests"));
 
-    cleanup.step.dependOn(tests.addPkgTests(b, test_filter,
+    test_step.dependOn(tests.addPkgTests(b, test_filter,
         "std/index.zig", "std", "Run the standard library tests"));
 
-    cleanup.step.dependOn(tests.addCompareOutputTests(b, test_filter));
-    cleanup.step.dependOn(tests.addBuildExampleTests(b, test_filter));
-    cleanup.step.dependOn(tests.addCompileErrorTests(b, test_filter));
-    cleanup.step.dependOn(tests.addAssembleAndLinkTests(b, test_filter));
-    cleanup.step.dependOn(tests.addDebugSafetyTests(b, test_filter));
-    cleanup.step.dependOn(tests.addParseHTests(b, test_filter));
+    test_step.dependOn(tests.addCompareOutputTests(b, test_filter));
+    test_step.dependOn(tests.addBuildExampleTests(b, test_filter));
+    test_step.dependOn(tests.addCompileErrorTests(b, test_filter));
+    test_step.dependOn(tests.addAssembleAndLinkTests(b, test_filter));
+    test_step.dependOn(tests.addDebugSafetyTests(b, test_filter));
+    test_step.dependOn(tests.addParseHTests(b, test_filter));
 }