Commit e76ce2c1d0

Andrew Kelley <andrew@ziglang.org>
2019-02-25 17:37:54
first class support for compiling C code
New CLI parameter: --c-source [options] [file] It even works with `--cache on` when there are transitive dependencies. Instead of `builder.addCExecutable`, use `builder.addExecutable` and pass `null` for the root source file. Then use `builder.addCSourceFile`, which takes the path to the C code, and a list of C compiler args. Be sure to linkSystemLibrary("c") if you want libc headers to be available. Merge TestStep into LibExeObjStep. That was long overdue.
1 parent e5d4862
Changed files (8)
example
mix_o_files
shared_library
src
std
test
stage1
c_abi
example/mix_o_files/build.zig
@@ -3,10 +3,10 @@ const Builder = @import("std").build.Builder;
 pub fn build(b: *Builder) void {
     const obj = b.addObject("base64", "base64.zig");
 
-    const exe = b.addCExecutable("test");
-    exe.addCompileFlags([][]const u8{"-std=c99"});
-    exe.addSourceFile("test.c");
+    const exe = b.addExecutable("test", null);
+    exe.addCSourceFile("test.c",[][]const u8{"-std=c99"});
     exe.addObject(obj);
+    exe.linkSystemLibrary("c");
 
     b.default_step.dependOn(&exe.step);
 
example/shared_library/build.zig
@@ -3,10 +3,10 @@ const Builder = @import("std").build.Builder;
 pub fn build(b: *Builder) void {
     const lib = b.addSharedLibrary("mathtest", "mathtest.zig", b.version(1, 0, 0));
 
-    const exe = b.addCExecutable("test");
-    exe.addCompileFlags([][]const u8{"-std=c99"});
-    exe.addSourceFile("test.c");
+    const exe = b.addExecutable("test", null);
+    exe.addCSourceFile("test.c", [][]const u8{"-std=c99"});
     exe.linkLibrary(lib);
+    exe.linkSystemLibrary("c");
 
     b.default_step.dependOn(&exe.step);
 
src/all_types.hpp
@@ -1611,6 +1611,11 @@ enum ValgrindSupport {
     ValgrindSupportEnabled,
 };
 
+struct CFile {
+    ZigList<const char *> args;
+    const char *source_path;
+};
+
 // When adding fields, check if they should be added to the hash computation in build_with_cache
 struct CodeGen {
     //////////////////////////// Runtime State
@@ -1788,6 +1793,7 @@ struct CodeGen {
     bool verbose_ir;
     bool verbose_llvm_ir;
     bool verbose_cimport;
+    bool verbose_cc;
     bool error_during_imports;
     bool generate_error_name_table;
     bool enable_cache;
@@ -1805,6 +1811,7 @@ struct CodeGen {
     ZigList<Buf *> forbidden_libs;
     ZigList<Buf *> link_objects;
     ZigList<Buf *> assembly_files;
+    ZigList<CFile *> c_source_files;
     ZigList<const char *> lib_dirs;
 
     ZigLibCInstallation *libc;
src/codegen.cpp
@@ -7885,8 +7885,8 @@ static void detect_libc(CodeGen *g) {
                 fprintf(stderr, "Unable to save %s: %s\n", buf_ptr(native_libc_tmp), strerror(errno));
                 exit(1);
             }
-            if (rename(buf_ptr(native_libc_tmp), buf_ptr(native_libc_txt)) == -1) {
-                fprintf(stderr, "Unable to create %s: %s\n", buf_ptr(native_libc_txt), strerror(errno));
+            if ((err = os_rename(native_libc_tmp, native_libc_txt))) {
+                fprintf(stderr, "Unable to create %s: %s\n", buf_ptr(native_libc_txt), err_str(err));
                 exit(1);
             }
         }
@@ -8123,6 +8123,162 @@ static void gen_global_asm(CodeGen *g) {
     }
 }
 
+static void gen_c_object(CodeGen *g, Buf *self_exe_path, CFile *c_file) {
+    Error err;
+
+    Buf *c_source_file = buf_create_from_str(c_file->source_path);
+    Buf *c_source_basename = buf_alloc();
+    os_path_split(c_source_file, nullptr, c_source_basename);
+    Buf *out_obj_name = buf_sprintf("%s%s", buf_ptr(c_source_basename), target_o_file_ext(g->zig_target));
+    Buf *out_obj_path = buf_alloc();
+    os_path_join(&g->cache_dir, out_obj_name, out_obj_path);
+    Buf *out_dep_name = buf_sprintf("%s.d", buf_ptr(c_source_file));
+    Buf *out_dep_path = buf_alloc();
+    os_path_join(&g->cache_dir, out_dep_name, out_dep_path);
+
+    Termination term;
+    ZigList<const char *> args = {};
+    args.append("cc");
+
+    if (g->enable_cache) {
+        args.append("-MD");
+        args.append("-MF");
+        args.append(buf_ptr(out_dep_path));
+    }
+
+    args.append("-isystem");
+    args.append(buf_ptr(g->zig_c_headers_dir));
+
+    if (g->libc != nullptr) {
+        args.append("-isystem");
+        args.append(buf_ptr(&g->libc->include_dir));
+    }
+
+    if (g->zig_target->is_native) {
+        args.append("-march=native");
+    } else {
+        args.append("-target");
+        args.append(buf_ptr(&g->triple_str));
+    }
+
+    if (!g->strip_debug_symbols) {
+        args.append("-g");
+    }
+    switch (g->build_mode) {
+        case BuildModeDebug:
+            if (g->libc_link_lib != nullptr) {
+                args.append("-fstack-protector-strong");
+                args.append("--param");
+                args.append("ssp-buffer-size=4");
+            } else {
+                args.append("-fno-stack-protector");
+            }
+            break;
+        case BuildModeSafeRelease:
+            args.append("-O2");
+            if (g->libc_link_lib != nullptr) {
+                args.append("-D_FORTIFY_SOURCE=2");
+                args.append("-fstack-protector-strong");
+                args.append("--param");
+                args.append("ssp-buffer-size=4");
+            } else {
+                args.append("-fno-stack-protector");
+            }
+            break;
+        case BuildModeFastRelease:
+            args.append("-O2");
+            args.append("-fno-stack-protector");
+            break;
+        case BuildModeSmallRelease:
+            args.append("-Os");
+            args.append("-fno-stack-protector");
+            break;
+    }
+
+    args.append("-o");
+    args.append(buf_ptr(out_obj_path));
+
+    args.append("-c");
+    args.append(buf_ptr(c_source_file));
+
+    if (!g->disable_pic) {
+        args.append("-fPIC");
+    }
+
+    for (size_t arg_i = 0; arg_i < g->clang_argv_len; arg_i += 1) {
+        args.append(g->clang_argv[arg_i]);
+    }
+
+    for (size_t arg_i = 0; arg_i < c_file->args.length; arg_i += 1) {
+        args.append(c_file->args.at(arg_i));
+    }
+
+    if (g->verbose_cc) {
+        for (size_t arg_i = 0; arg_i < args.length; arg_i += 1) {
+            fprintf(stderr, "%s ", args.at(arg_i));
+        }
+        fprintf(stderr, "\n");
+    }
+
+    os_spawn_process(buf_ptr(self_exe_path), args, &term);
+    if (term.how != TerminationIdClean || term.code != 0) {
+        fprintf(stderr, "`zig cc` failed\n");
+        exit(1);
+    }
+
+    g->link_objects.append(out_obj_path);
+
+    // add the files depended on to the cache system
+    if (g->enable_cache) {
+        Buf *contents = buf_alloc();
+        if ((err = os_fetch_file_path(out_dep_path, contents, false))) {
+            fprintf(stderr, "unable to read .d file: %s\n", err_str(err));
+            exit(1);
+        }
+        if ((err = cache_add_file(&g->cache_hash, c_source_file))) {
+            fprintf(stderr, "unable to add %s to cache: %s\n", buf_ptr(c_source_file), err_str(err));
+            exit(1);
+        }
+        SplitIterator it = memSplit(buf_to_slice(contents), str("\n"));
+        // skip first line
+        SplitIterator_next(&it);
+        for (;;) {
+            Optional<Slice<uint8_t>> opt_line = SplitIterator_next(&it);
+            if (!opt_line.is_some)
+                break;
+            if (opt_line.value.len == 0)
+                continue;
+            SplitIterator line_it = memSplit(opt_line.value, str(" \t"));
+            Slice<uint8_t> filename;
+            if (!SplitIterator_next(&line_it).unwrap(&filename))
+                continue;
+            Buf *filename_buf = buf_create_from_slice(filename);
+            if ((err = cache_add_file(&g->cache_hash, filename_buf))) {
+                fprintf(stderr, "unable to add %s to cache: %s\n", buf_ptr(c_source_file), err_str(err));
+                exit(1);
+            }
+        }
+    }
+}
+
+static void gen_c_objects(CodeGen *g) {
+    Error err;
+
+    if (g->c_source_files.length == 0)
+        return;
+
+    Buf *self_exe_path = buf_alloc();
+    if ((err = os_self_exe_path(self_exe_path))) {
+        fprintf(stderr, "Unable to get self exe path: %s\n", err_str(err));
+        exit(1);
+    }
+
+    for (size_t c_file_i = 0; c_file_i < g->c_source_files.length; c_file_i += 1) {
+        CFile *c_file = g->c_source_files.at(c_file_i);
+        gen_c_object(g, self_exe_path, c_file);
+    }
+}
+
 void codegen_add_object(CodeGen *g, Buf *object_path) {
     g->link_objects.append(object_path);
 }
@@ -8637,6 +8793,13 @@ static Error check_cache(CodeGen *g, Buf *manifest_dir, Buf *digest) {
     cache_list_of_buf(ch, g->forbidden_libs.items, g->forbidden_libs.length);
     cache_list_of_file(ch, g->link_objects.items, g->link_objects.length);
     cache_list_of_file(ch, g->assembly_files.items, g->assembly_files.length);
+    for (size_t c_file_i = 0; c_file_i < g->c_source_files.length; c_file_i += 1) {
+        CFile *c_file = g->c_source_files.at(c_file_i);
+        cache_file(ch, buf_create_from_str(c_file->source_path));
+        for (size_t opt_i = 0; opt_i < c_file->args.length; opt_i += 1) {
+            cache_buf(ch, buf_create_from_str(c_file->args.at(opt_i)));
+        }
+    }
     cache_int(ch, g->emit_file_type);
     cache_int(ch, g->build_mode);
     cache_int(ch, g->out_type);
@@ -8788,6 +8951,7 @@ void codegen_build_and_link(CodeGen *g) {
 
         gen_global_asm(g);
         gen_root_source(g);
+        gen_c_objects(g);
 
         if (g->enable_cache) {
             if ((err = cache_final(&g->cache_hash, &digest))) {
@@ -8805,16 +8969,25 @@ void codegen_build_and_link(CodeGen *g) {
         resolve_out_paths(g);
 
         codegen_add_time_event(g, "Code Generation");
-        do_code_gen(g);
-        codegen_add_time_event(g, "LLVM Emit Output");
-        zig_llvm_emit_output(g);
+        if (g->out_type == OutTypeObj && g->c_source_files.length == 1) {
+            assert(g->link_objects.length == 1);
+            if ((err = os_rename(g->link_objects.pop(), &g->o_file_output_path))) {
+                fprintf(stderr, "unable to move object to '%s': %s\n",
+                        buf_ptr(&g->o_file_output_path), err_str(err));
+                exit(1);
+            }
+        } else {
+            do_code_gen(g);
+            codegen_add_time_event(g, "LLVM Emit Output");
+            zig_llvm_emit_output(g);
 
-        if (g->out_h_path != nullptr) {
-            codegen_add_time_event(g, "Generate .h");
-            gen_h_file(g);
-        }
-        if (g->out_type != OutTypeObj && g->emit_file_type == EmitFileTypeBinary) {
-            codegen_link(g);
+            if (g->out_h_path != nullptr) {
+                codegen_add_time_event(g, "Generate .h");
+                gen_h_file(g);
+            }
+            if (g->out_type != OutTypeObj && g->emit_file_type == EmitFileTypeBinary) {
+                codegen_link(g);
+            }
         }
     }
 
src/main.cpp
@@ -18,7 +18,7 @@
 #include <stdio.h>
 
 static int print_error_usage(const char *arg0) {
-    fprintf(stderr, "See `%s help` for detailed usage information\n", arg0);
+    fprintf(stderr, "See `%s --help` for detailed usage information\n", arg0);
     return EXIT_FAILURE;
 }
 
@@ -34,7 +34,6 @@ static int print_full_usage(const char *arg0, FILE *file, int return_code) {
         "  builtin                      show the source code of that @import(\"builtin\")\n"
         "  cc                           C compiler\n"
         "  fmt                          parse files and render in canonical zig format\n"
-        "  help                         show this usage information\n"
         "  id                           print the base64-encoded compiler id\n"
         "  init-exe                     initialize a `zig build` application in the cwd\n"
         "  init-lib                     initialize a `zig build` library in the cwd\n"
@@ -48,6 +47,7 @@ static int print_full_usage(const char *arg0, FILE *file, int return_code) {
         "\n"
         "Compile Options:\n"
         "  --assembly [source]          add assembly file to build\n"
+        "  --c-source [options] [file]  compile C source code\n"
         "  --cache-dir [path]           override the cache directory\n"
         "  --cache [auto|off|on]        build in global cache, print out paths to stdout\n"
         "  --color [auto|off|on]        enable or disable colored error messages\n"
@@ -77,6 +77,7 @@ static int print_full_usage(const char *arg0, FILE *file, int return_code) {
         "  --verbose-ir                 enable compiler debug output for Zig IR\n"
         "  --verbose-llvm-ir            enable compiler debug output for LLVM IR\n"
         "  --verbose-cimport            enable compiler debug output for C imports\n"
+        "  --verbose-cc                 enable compiler debug output for C compilation\n"
         "  -dirafter [dir]              same as -isystem but do it last\n"
         "  -isystem [dir]               add additional search path for other .h files\n"
         "  -mllvm [arg]                 forward an arg to LLVM's option processing\n"
@@ -181,7 +182,6 @@ enum Cmd {
     CmdNone,
     CmdBuild,
     CmdBuiltin,
-    CmdHelp,
     CmdRun,
     CmdTargets,
     CmdTest,
@@ -385,6 +385,7 @@ int main(int argc, char **argv) {
     bool verbose_ir = false;
     bool verbose_llvm_ir = false;
     bool verbose_cimport = false;
+    bool verbose_cc = false;
     ErrColor color = ErrColorAuto;
     CacheOpt enable_cache = CacheOptAuto;
     const char *libc_txt = nullptr;
@@ -404,6 +405,7 @@ int main(int argc, char **argv) {
     ZigList<const char *> rpath_list = {0};
     bool each_lib_rpath = false;
     ZigList<const char *> objects = {0};
+    ZigList<CFile *> c_source_files = {0};
     ZigList<const char *> asm_files = {0};
     const char *test_filter = nullptr;
     const char *test_name_prefix = nullptr;
@@ -512,6 +514,7 @@ int main(int argc, char **argv) {
                         "  --verbose-ir           Enable compiler debug output for Zig IR\n"
                         "  --verbose-llvm-ir      Enable compiler debug output for LLVM IR\n"
                         "  --verbose-cimport      Enable compiler debug output for C imports\n"
+                        "  --verbose-cc           Enable compiler debug output for C compilation\n"
                         "\n"
                 , zig_exe_path);
                 return EXIT_SUCCESS;
@@ -521,7 +524,7 @@ int main(int argc, char **argv) {
                     "No 'build.zig' file found.\n"
                     "Initialize a 'build.zig' template file with `zig init-lib` or `zig init-exe`,\n"
                     "or build an executable directly with `zig build-exe $FILENAME.zig`.\n"
-                    "See: `zig build --help` or `zig help` for more options.\n"
+                    "See: `zig build --help` or `zig --help` for more options.\n"
                    );
             return EXIT_FAILURE;
         }
@@ -587,9 +590,9 @@ int main(int argc, char **argv) {
                 build_mode = BuildModeSmallRelease;
             } else if (strcmp(arg, "--help") == 0) {
                 if (cmd == CmdLibC) {
-                    return print_libc_usage(arg0, stderr, EXIT_FAILURE);
+                    return print_libc_usage(arg0, stdout, EXIT_SUCCESS);
                 } else {
-                    return print_full_usage(arg0, stderr, EXIT_FAILURE);
+                    return print_full_usage(arg0, stdout, EXIT_SUCCESS);
                 }
             } else if (strcmp(arg, "--strip") == 0) {
                 strip = true;
@@ -607,6 +610,8 @@ int main(int argc, char **argv) {
                 verbose_llvm_ir = true;
             } else if (strcmp(arg, "--verbose-cimport") == 0) {
                 verbose_cimport = true;
+            } else if (strcmp(arg, "--verbose-cc") == 0) {
+                verbose_cc = true;
             } else if (strcmp(arg, "-rdynamic") == 0) {
                 rdynamic = true;
             } else if (strcmp(arg, "--each-lib-rpath") == 0) {
@@ -714,6 +719,19 @@ int main(int argc, char **argv) {
                     forbidden_link_libs.append(argv[i]);
                 } else if (strcmp(arg, "--object") == 0) {
                     objects.append(argv[i]);
+                } else if (strcmp(arg, "--c-source") == 0) {
+                    CFile *c_file = allocate<CFile>(1);
+                    for (;;) {
+                        if (argv[i][0] == '-') {
+                            c_file->args.append(argv[i]);
+                            i += 1;
+                            continue;
+                        } else {
+                            c_file->source_path = argv[i];
+                            c_source_files.append(c_file);
+                            break;
+                        }
+                    }
                 } else if (strcmp(arg, "--assembly") == 0) {
                     asm_files.append(argv[i]);
                 } else if (strcmp(arg, "--cache-dir") == 0) {
@@ -792,8 +810,6 @@ int main(int argc, char **argv) {
             } else if (strcmp(arg, "build-lib") == 0) {
                 cmd = CmdBuild;
                 out_type = OutTypeLib;
-            } else if (strcmp(arg, "help") == 0) {
-                cmd = CmdHelp;
             } else if (strcmp(arg, "run") == 0) {
                 cmd = CmdRun;
                 out_type = OutTypeExe;
@@ -835,7 +851,6 @@ int main(int argc, char **argv) {
                     }
                     break;
                 case CmdBuiltin:
-                case CmdHelp:
                 case CmdVersion:
                 case CmdZen:
                 case CmdTargets:
@@ -910,15 +925,43 @@ int main(int argc, char **argv) {
     case CmdTranslateC:
     case CmdTest:
         {
-            if (cmd == CmdBuild && !in_file && objects.length == 0 && asm_files.length == 0) {
-                fprintf(stderr, "Expected source file argument or at least one --object or --assembly argument.\n");
+            if (cmd == CmdBuild && !in_file && objects.length == 0 && asm_files.length == 0 &&
+                    c_source_files.length == 0)
+            {
+                fprintf(stderr,
+                    "Expected at least one of these things:\n"
+                    " * Zig root source file argument\n"
+                    " * --object argument\n"
+                    " * --assembly argument\n"
+                    " * --c-source argument\n");
                 return print_error_usage(arg0);
             } else if ((cmd == CmdTranslateC || cmd == CmdTest || cmd == CmdRun) && !in_file) {
                 fprintf(stderr, "Expected source file argument.\n");
                 return print_error_usage(arg0);
-            } else if (cmd == CmdBuild && out_type == OutTypeObj && objects.length != 0) {
-                fprintf(stderr, "When building an object file, --object arguments are invalid.\n");
-                return print_error_usage(arg0);
+            } else if (cmd == CmdBuild && out_type == OutTypeObj) {
+                if (objects.length != 0) {
+                    fprintf(stderr,
+                        "When building an object file, --object arguments are invalid.\n"
+                        "Consider building a static library instead.\n");
+                    return print_error_usage(arg0);
+                }
+                size_t zig_root_src_count = in_file ? 1 : 0;
+                if (zig_root_src_count + c_source_files.length > 1) {
+                    fprintf(stderr,
+                        "When building an object file, only one of these allowed:\n"
+                        " * Zig root source file argument\n"
+                        " * --c-source argument\n"
+                        "Consider building a static library instead.\n");
+                    return print_error_usage(arg0);
+                }
+                if (c_source_files.length != 0 && asm_files.length != 0) {
+                    fprintf(stderr,
+                        "When building an object file, only one of these allowed:\n"
+                        " * --assembly argument\n"
+                        " * --c-source argument\n"
+                        "Consider building a static library instead.\n");
+                    return print_error_usage(arg0);
+                }
             }
 
             assert(cmd != CmdBuild || out_type != OutTypeUnknown);
@@ -945,6 +988,13 @@ int main(int argc, char **argv) {
                 }
             }
 
+            if (need_name && buf_out_name == nullptr && c_source_files.length == 1) {
+                Buf basename = BUF_INIT;
+                os_path_split(buf_create_from_str(c_source_files.at(0)->source_path), nullptr, &basename);
+                buf_out_name = buf_alloc();
+                os_path_extname(&basename, buf_out_name, nullptr);
+            }
+
             if (need_name && buf_out_name == nullptr) {
                 fprintf(stderr, "--name [name] not provided and unable to infer\n\n");
                 return print_error_usage(arg0);
@@ -996,6 +1046,7 @@ int main(int argc, char **argv) {
             g->verbose_ir = verbose_ir;
             g->verbose_llvm_ir = verbose_llvm_ir;
             g->verbose_cimport = verbose_cimport;
+            g->verbose_cc = verbose_cc;
             codegen_set_errmsg_color(g, color);
             g->system_linker_hack = system_linker_hack;
 
@@ -1048,6 +1099,7 @@ int main(int argc, char **argv) {
             add_package(g, cur_pkg, g->root_package);
 
             if (cmd == CmdBuild || cmd == CmdRun || cmd == CmdTest) {
+                g->c_source_files = c_source_files;
                 for (size_t i = 0; i < objects.length; i += 1) {
                     codegen_add_object(g, buf_create_from_str(objects.at(i)));
                 }
@@ -1147,8 +1199,6 @@ int main(int argc, char **argv) {
                 zig_unreachable();
             }
         }
-    case CmdHelp:
-        return print_full_usage(arg0, stdout, EXIT_SUCCESS);
     case CmdVersion:
         printf("%s\n", ZIG_VERSION_STRING);
         return EXIT_SUCCESS;
@@ -1158,7 +1208,6 @@ int main(int argc, char **argv) {
     case CmdTargets:
         return print_target_list(stdout);
     case CmdNone:
-        fprintf(stderr, "Zig programming language\n");
-        return print_error_usage(arg0);
+        return print_full_usage(arg0, stderr, EXIT_FAILURE);
     }
 }
std/buf_set.zig
@@ -32,6 +32,10 @@ pub const BufSet = struct {
         }
     }
 
+    pub fn exists(self: BufSet, key: []const u8) bool {
+        return self.hash_map.get(key) != null;
+    }
+
     pub fn delete(self: *BufSet, key: []const u8) void {
         const entry = self.hash_map.remove(key) orelse return;
         self.free(entry.key);
std/build.zig
@@ -31,6 +31,7 @@ pub const Builder = struct {
     verbose_tokenize: bool,
     verbose_ast: bool,
     verbose_link: bool,
+    verbose_cc: bool,
     verbose_ir: bool,
     verbose_llvm_ir: bool,
     verbose_cimport: bool,
@@ -99,6 +100,7 @@ pub const Builder = struct {
             .verbose_tokenize = false,
             .verbose_ast = false,
             .verbose_link = false,
+            .verbose_cc = false,
             .verbose_ir = false,
             .verbose_llvm_ir = false,
             .verbose_cimport = false,
@@ -157,7 +159,7 @@ pub const Builder = struct {
         return LibExeObjStep.createExecutable(self, name, root_src, true);
     }
 
-    pub fn addObject(self: *Builder, name: []const u8, root_src: []const u8) *LibExeObjStep {
+    pub fn addObject(self: *Builder, name: []const u8, root_src: ?[]const u8) *LibExeObjStep {
         return LibExeObjStep.createObject(self, name, root_src);
     }
 
@@ -169,10 +171,8 @@ pub const Builder = struct {
         return LibExeObjStep.createStaticLibrary(self, name, root_src);
     }
 
-    pub fn addTest(self: *Builder, root_src: []const u8) *TestStep {
-        const test_step = self.allocator.create(TestStep) catch unreachable;
-        test_step.* = TestStep.init(self, root_src);
-        return test_step;
+    pub fn addTest(self: *Builder, root_src: []const u8) *LibExeObjStep {
+        return LibExeObjStep.createTest(self, "test", root_src);
     }
 
     pub fn addAssemble(self: *Builder, name: []const u8, src: []const u8) *LibExeObjStep {
@@ -181,22 +181,6 @@ pub const Builder = struct {
         return obj_step;
     }
 
-    pub fn addCStaticLibrary(self: *Builder, name: []const u8) *LibExeObjStep {
-        return LibExeObjStep.createCStaticLibrary(self, name);
-    }
-
-    pub fn addCSharedLibrary(self: *Builder, name: []const u8, ver: Version) *LibExeObjStep {
-        return LibExeObjStep.createCSharedLibrary(self, name, ver);
-    }
-
-    pub fn addCExecutable(self: *Builder, name: []const u8) *LibExeObjStep {
-        return LibExeObjStep.createCExecutable(self, name);
-    }
-
-    pub fn addCObject(self: *Builder, name: []const u8, src: []const u8) *LibExeObjStep {
-        return LibExeObjStep.createCObject(self, name, src);
-    }
-
     /// ::argv is copied.
     pub fn addCommand(self: *Builder, cwd: ?[]const u8, env_map: *const BufMap, argv: []const []const u8) *CommandStep {
         return CommandStep.create(self, cwd, env_map, argv);
@@ -663,14 +647,6 @@ pub const Builder = struct {
         return fmt_lib.allocPrint(self.allocator, format, args) catch unreachable;
     }
 
-    fn getCCExe(self: *Builder) []const u8 {
-        if (builtin.environ == builtin.Environ.msvc) {
-            return "cl.exe";
-        } else {
-            return os.getEnvVarOwned(self.allocator, "CC") catch |err| if (err == error.EnvironmentVariableNotFound) ([]const u8)("cc") else debug.panic("Unable to get environment variable: {}", err);
-        }
-    }
-
     pub fn findProgram(self: *Builder, names: []const []const u8, paths: []const []const u8) ![]const u8 {
         // TODO report error for ambiguous situations
         const exe_extension = (Target{ .Native = {} }).exeFileExt();
@@ -825,6 +801,11 @@ const Pkg = struct {
     path: []const u8,
 };
 
+const CSourceFile = struct {
+    source_path: []const u8,
+    args: []const []const u8,
+};
+
 pub const LibExeObjStep = struct {
     step: Step,
     builder: *Builder,
@@ -844,16 +825,17 @@ pub const LibExeObjStep = struct {
     strip: bool,
     full_path_libs: ArrayList([]const u8),
     need_flat_namespace_hack: bool,
-    is_zig: bool,
-    cflags: ArrayList([]const u8),
     include_dirs: ArrayList([]const u8),
     lib_paths: ArrayList([]const u8),
-    disable_libc: bool,
     frameworks: BufSet,
     verbose_link: bool,
+    verbose_cc: bool,
     c_std: Builder.CStd,
+    override_std_dir: ?[]const u8,
+    exec_cmd_args: ?[]const ?[]const u8,
+    name_prefix: []const u8,
+    filter: ?[]const u8,
 
-    // zig only stuff
     root_src: ?[]const u8,
     output_h_path: ?[]const u8,
     out_h_filename: []const u8,
@@ -862,14 +844,14 @@ pub const LibExeObjStep = struct {
     build_options_contents: std.Buffer,
     system_linker_hack: bool,
 
-    // C only stuff
-    source_files: ArrayList([]const u8),
+    c_source_files: ArrayList(*CSourceFile),
     object_src: []const u8,
 
     const Kind = enum {
         Exe,
         Lib,
         Obj,
+        Test,
     };
 
     pub fn createSharedLibrary(builder: *Builder, name: []const u8, root_src: ?[]const u8, ver: Version) *LibExeObjStep {
@@ -878,46 +860,27 @@ pub const LibExeObjStep = struct {
         return self;
     }
 
-    pub fn createCSharedLibrary(builder: *Builder, name: []const u8, version: Version) *LibExeObjStep {
-        const self = builder.allocator.create(LibExeObjStep) catch unreachable;
-        self.* = initC(builder, name, Kind.Lib, version, false);
-        return self;
-    }
-
     pub fn createStaticLibrary(builder: *Builder, name: []const u8, root_src: ?[]const u8) *LibExeObjStep {
         const self = builder.allocator.create(LibExeObjStep) catch unreachable;
         self.* = initExtraArgs(builder, name, root_src, Kind.Lib, true, builder.version(0, 0, 0));
         return self;
     }
 
-    pub fn createCStaticLibrary(builder: *Builder, name: []const u8) *LibExeObjStep {
-        const self = builder.allocator.create(LibExeObjStep) catch unreachable;
-        self.* = initC(builder, name, Kind.Lib, builder.version(0, 0, 0), true);
-        return self;
-    }
-
-    pub fn createObject(builder: *Builder, name: []const u8, root_src: []const u8) *LibExeObjStep {
+    pub fn createObject(builder: *Builder, name: []const u8, root_src: ?[]const u8) *LibExeObjStep {
         const self = builder.allocator.create(LibExeObjStep) catch unreachable;
         self.* = initExtraArgs(builder, name, root_src, Kind.Obj, false, builder.version(0, 0, 0));
         return self;
     }
 
-    pub fn createCObject(builder: *Builder, name: []const u8, src: []const u8) *LibExeObjStep {
-        const self = builder.allocator.create(LibExeObjStep) catch unreachable;
-        self.* = initC(builder, name, Kind.Obj, builder.version(0, 0, 0), false);
-        self.object_src = src;
-        return self;
-    }
-
     pub fn createExecutable(builder: *Builder, name: []const u8, root_src: ?[]const u8, static: bool) *LibExeObjStep {
         const self = builder.allocator.create(LibExeObjStep) catch unreachable;
         self.* = initExtraArgs(builder, name, root_src, Kind.Exe, static, builder.version(0, 0, 0));
         return self;
     }
 
-    pub fn createCExecutable(builder: *Builder, name: []const u8) *LibExeObjStep {
+    pub fn createTest(builder: *Builder, name: []const u8, root_src: []const u8) *LibExeObjStep {
         const self = builder.allocator.create(LibExeObjStep) catch unreachable;
-        self.* = initC(builder, name, Kind.Exe, builder.version(0, 0, 0), false);
+        self.* = initExtraArgs(builder, name, root_src, Kind.Test, false, builder.version(0, 0, 0));
         return self;
     }
 
@@ -926,6 +889,7 @@ pub const LibExeObjStep = struct {
             .strip = false,
             .builder = builder,
             .verbose_link = false,
+            .verbose_cc = false,
             .build_mode = builtin.Mode.Debug,
             .static = static,
             .kind = kind,
@@ -946,61 +910,19 @@ pub const LibExeObjStep = struct {
             .object_files = ArrayList([]const u8).init(builder.allocator),
             .assembly_files = ArrayList([]const u8).init(builder.allocator),
             .packages = ArrayList(Pkg).init(builder.allocator),
-            .is_zig = true,
             .full_path_libs = ArrayList([]const u8).init(builder.allocator),
             .need_flat_namespace_hack = false,
-            .cflags = ArrayList([]const u8).init(builder.allocator),
-            .source_files = undefined,
+            .c_source_files = ArrayList(*CSourceFile).init(builder.allocator),
             .include_dirs = ArrayList([]const u8).init(builder.allocator),
             .lib_paths = ArrayList([]const u8).init(builder.allocator),
             .object_src = undefined,
-            .disable_libc = true,
             .build_options_contents = std.Buffer.initSize(builder.allocator, 0) catch unreachable,
             .c_std = Builder.CStd.C99,
             .system_linker_hack = false,
-        };
-        self.computeOutFileNames();
-        return self;
-    }
-
-    fn initC(builder: *Builder, name: []const u8, kind: Kind, version: Version, static: bool) LibExeObjStep {
-        var self = LibExeObjStep{
-            .builder = builder,
-            .name = name,
-            .kind = kind,
-            .version = version,
-            .static = static,
-            .target = Target.Native,
-            .cflags = ArrayList([]const u8).init(builder.allocator),
-            .source_files = ArrayList([]const u8).init(builder.allocator),
-            .object_files = ArrayList([]const u8).init(builder.allocator),
-            .step = Step.init(name, builder.allocator, make),
-            .link_libs = BufSet.init(builder.allocator),
-            .frameworks = BufSet.init(builder.allocator),
-            .full_path_libs = ArrayList([]const u8).init(builder.allocator),
-            .include_dirs = ArrayList([]const u8).init(builder.allocator),
-            .lib_paths = ArrayList([]const u8).init(builder.allocator),
-            .output_path = null,
-            .out_filename = undefined,
-            .major_only_filename = undefined,
-            .name_only_filename = undefined,
-            .object_src = undefined,
-            .build_mode = builtin.Mode.Debug,
-            .strip = false,
-            .need_flat_namespace_hack = false,
-            .disable_libc = false,
-            .is_zig = false,
-            .linker_script = null,
-            .c_std = Builder.CStd.C99,
-            .system_linker_hack = false,
-
-            .root_src = undefined,
-            .verbose_link = false,
-            .output_h_path = undefined,
-            .out_h_filename = undefined,
-            .assembly_files = undefined,
-            .packages = undefined,
-            .build_options_contents = undefined,
+            .override_std_dir = null,
+            .exec_cmd_args = null,
+            .name_prefix = "",
+            .filter = null,
         };
         self.computeOutFileNames();
         return self;
@@ -1014,6 +936,9 @@ pub const LibExeObjStep = struct {
             Kind.Exe => {
                 self.out_filename = self.builder.fmt("{}{}", self.name, self.target.exeFileExt());
             },
+            Kind.Test => {
+                self.out_filename = self.builder.fmt("test{}", self.target.exeFileExt());
+            },
             Kind.Lib => {
                 if (self.static) {
                     self.out_filename = self.builder.fmt("lib{}.a", self.name);
@@ -1081,24 +1006,44 @@ pub const LibExeObjStep = struct {
     }
 
     pub fn linkSystemLibrary(self: *LibExeObjStep, name: []const u8) void {
-        assert(self.kind != Kind.Obj);
         self.link_libs.put(name) catch unreachable;
     }
 
-    pub fn addSourceFile(self: *LibExeObjStep, file: []const u8) void {
-        assert(self.kind != Kind.Obj);
-        assert(!self.is_zig);
-        self.source_files.append(file) catch unreachable;
+    pub fn setNamePrefix(self: *LibExeObjStep, text: []const u8) void {
+        assert(self.kind == Kind.Test);
+        self.name_prefix = text;
+    }
+
+    pub fn setFilter(self: *LibExeObjStep, text: ?[]const u8) void {
+        assert(self.kind == Kind.Test);
+        self.filter = text;
+    }
+
+    pub fn addCSourceFile(self: *LibExeObjStep, file: []const u8, args: []const []const u8) void {
+        const c_source_file = self.builder.allocator.create(CSourceFile) catch unreachable;
+        c_source_file.* = CSourceFile{
+            .source_path = file,
+            .args = args,
+        };
+        self.c_source_files.append(c_source_file) catch unreachable;
     }
 
     pub fn setVerboseLink(self: *LibExeObjStep, value: bool) void {
         self.verbose_link = value;
     }
 
+    pub fn setVerboseCC(self: *LibExeObjStep, value: bool) void {
+        self.verbose_cc = value;
+    }
+
     pub fn setBuildMode(self: *LibExeObjStep, mode: builtin.Mode) void {
         self.build_mode = mode;
     }
 
+    pub fn overrideStdDir(self: *LibExeObjStep, dir_path: []const u8) void {
+        self.override_std_dir = dir_path;
+    }
+
     pub fn setOutputPath(self: *LibExeObjStep, file_path: []const u8) void {
         self.output_path = file_path;
 
@@ -1149,17 +1094,15 @@ pub const LibExeObjStep = struct {
 
         self.object_files.append(obj.getOutputPath()) catch unreachable;
 
-        // TODO make this lazy instead of stateful
-        if (!obj.disable_libc) {
-            self.disable_libc = false;
-        }
-
         // TODO should be some kind of isolated directory that only has this header in it
         self.include_dirs.append(self.builder.cache_root) catch unreachable;
+
+        if (obj.link_libs.exists("c")) {
+            self.link_libs.put("c") catch unreachable;
+        }
     }
 
     pub fn addBuildOption(self: *LibExeObjStep, comptime T: type, name: []const u8, value: T) void {
-        assert(self.is_zig);
         const out = &std.io.BufferOutStream.init(&self.build_options_contents).stream;
         out.print("pub const {} = {};\n", name, value) catch unreachable;
     }
@@ -1173,23 +1116,15 @@ pub const LibExeObjStep = struct {
     }
 
     pub fn addPackagePath(self: *LibExeObjStep, name: []const u8, pkg_index_path: []const u8) void {
-        assert(self.is_zig);
-
         self.packages.append(Pkg{
             .name = name,
             .path = pkg_index_path,
         }) catch unreachable;
     }
 
-    pub fn addCompileFlags(self: *LibExeObjStep, flags: []const []const u8) void {
-        for (flags) |flag| {
-            self.cflags.append(flag) catch unreachable;
-        }
-    }
-
-    pub fn setNoStdLib(self: *LibExeObjStep, disable: bool) void {
-        assert(!self.is_zig);
-        self.disable_libc = disable;
+    pub fn setExecCmd(self: *LibExeObjStep, args: []const ?[]const u8) void {
+        assert(self.kind == Kind.Test);
+        self.exec_cmd_args = args;
     }
 
     pub fn enableSystemLinkerHack(self: *LibExeObjStep) void {
@@ -1198,15 +1133,11 @@ pub const LibExeObjStep = struct {
 
     fn make(step: *Step) !void {
         const self = @fieldParentPtr(LibExeObjStep, "step", step);
-        return if (self.is_zig) self.makeZig() else self.makeC();
-    }
-
-    fn makeZig(self: *LibExeObjStep) !void {
         const builder = self.builder;
 
-        assert(self.is_zig);
-
-        if (self.root_src == null and self.object_files.len == 0 and self.assembly_files.len == 0) {
+        if (self.root_src == null and self.object_files.len == 0 and
+            self.assembly_files.len == 0 and self.c_source_files.len == 0)
+        {
             warn("{}: linker needs 1 or more objects to link\n", self.step.name);
             return error.NeedAnObject;
         }
@@ -1220,6 +1151,7 @@ pub const LibExeObjStep = struct {
             Kind.Lib => "build-lib",
             Kind.Exe => "build-exe",
             Kind.Obj => "build-obj",
+            Kind.Test => "test",
         };
         zig_args.append(cmd) catch unreachable;
 
@@ -1227,6 +1159,14 @@ pub const LibExeObjStep = struct {
             zig_args.append(builder.pathFromRoot(root_src)) catch unreachable;
         }
 
+        for (self.c_source_files.toSliceConst()) |c_source_file| {
+            try zig_args.append("--c-source");
+            for (c_source_file.args) |arg| {
+                try zig_args.append(arg);
+            }
+            try zig_args.append(self.builder.pathFromRoot(c_source_file.source_path));
+        }
+
         if (self.build_options_contents.len() > 0) {
             const build_options_file = try os.path.join(
                 builder.allocator,
@@ -1239,6 +1179,16 @@ pub const LibExeObjStep = struct {
             try zig_args.append("--pkg-end");
         }
 
+        if (self.filter) |filter| {
+            try zig_args.append("--test-filter");
+            try zig_args.append(filter);
+        }
+
+        if (self.name_prefix.len != 0) {
+            try zig_args.append("--test-name-prefix");
+            try zig_args.append(self.name_prefix);
+        }
+
         for (self.object_files.toSliceConst()) |object_file| {
             zig_args.append("--object") catch unreachable;
             zig_args.append(builder.pathFromRoot(object_file)) catch unreachable;
@@ -1255,6 +1205,7 @@ pub const LibExeObjStep = struct {
         if (builder.verbose_ir) zig_args.append("--verbose-ir") catch unreachable;
         if (builder.verbose_llvm_ir) zig_args.append("--verbose-llvm-ir") catch unreachable;
         if (builder.verbose_link or self.verbose_link) zig_args.append("--verbose-link") catch unreachable;
+        if (builder.verbose_cc or self.verbose_cc) zig_args.append("--verbose-cc") catch unreachable;
 
         if (self.strip) {
             zig_args.append("--strip") catch unreachable;
@@ -1293,7 +1244,7 @@ pub const LibExeObjStep = struct {
             zig_args.append("--ver-patch") catch unreachable;
             zig_args.append(builder.fmt("{}", self.version.patch)) catch unreachable;
         }
-        if (self.kind == Kind.Exe and self.static) {
+        if ((self.kind == Kind.Exe or self.kind == Kind.Test) and self.static) {
             zig_args.append("--static") catch unreachable;
         }
 
@@ -1325,11 +1276,16 @@ pub const LibExeObjStep = struct {
             }
         }
 
-        if (!self.disable_libc) {
-            zig_args.append("--library") catch unreachable;
-            zig_args.append("c") catch unreachable;
+        if (self.exec_cmd_args) |exec_cmd_args| {
+            for (exec_cmd_args) |cmd_arg| {
+                if (cmd_arg) |arg| {
+                    try zig_args.append("--test-cmd");
+                    try zig_args.append(arg);
+                } else {
+                    try zig_args.append("--test-cmd-bin");
+                }
+            }
         }
-
         for (self.packages.toSliceConst()) |pkg| {
             zig_args.append("--pkg-begin") catch unreachable;
             zig_args.append(pkg.name) catch unreachable;
@@ -1363,8 +1319,14 @@ pub const LibExeObjStep = struct {
         }
 
         for (self.full_path_libs.toSliceConst()) |full_path_lib| {
-            zig_args.append("--library") catch unreachable;
-            zig_args.append(builder.pathFromRoot(full_path_lib)) catch unreachable;
+            try zig_args.append("--library");
+            try zig_args.append(builder.pathFromRoot(full_path_lib));
+
+            const full_path_lib_abs = builder.pathFromRoot(full_path_lib);
+            if (os.path.dirname(full_path_lib_abs)) |dirname| {
+                try zig_args.append("-rpath");
+                try zig_args.append(dirname);
+            }
         }
 
         if (self.target.isDarwin()) {
@@ -1379,558 +1341,16 @@ pub const LibExeObjStep = struct {
             try zig_args.append("--system-linker-hack");
         }
 
-        try builder.spawnChild(zig_args.toSliceConst());
-
-        if (self.kind == Kind.Lib and !self.static and self.target.wantSharedLibSymLinks()) {
-            try doAtomicSymLinks(builder.allocator, output_path, self.major_only_filename, self.name_only_filename);
-        }
-    }
-
-    fn appendCompileFlags(self: *LibExeObjStep, args: *ArrayList([]const u8)) void {
-        if (!self.strip) {
-            args.append("-g") catch unreachable;
-        }
-        switch (self.build_mode) {
-            builtin.Mode.Debug => {
-                if (self.disable_libc) {
-                    args.append("-fno-stack-protector") catch unreachable;
-                } else {
-                    args.append("-fstack-protector-strong") catch unreachable;
-                    args.append("--param") catch unreachable;
-                    args.append("ssp-buffer-size=4") catch unreachable;
-                }
-            },
-            builtin.Mode.ReleaseSafe => {
-                args.append("-O2") catch unreachable;
-                if (self.disable_libc) {
-                    args.append("-fno-stack-protector") catch unreachable;
-                } else {
-                    args.append("-D_FORTIFY_SOURCE=2") catch unreachable;
-                    args.append("-fstack-protector-strong") catch unreachable;
-                    args.append("--param") catch unreachable;
-                    args.append("ssp-buffer-size=4") catch unreachable;
-                }
-            },
-            builtin.Mode.ReleaseFast, builtin.Mode.ReleaseSmall => {
-                args.append("-O2") catch unreachable;
-                args.append("-fno-stack-protector") catch unreachable;
-            },
-        }
-
-        for (self.include_dirs.toSliceConst()) |dir| {
-            args.append("-I") catch unreachable;
-            args.append(self.builder.pathFromRoot(dir)) catch unreachable;
-        }
-
-        for (self.cflags.toSliceConst()) |cflag| {
-            args.append(cflag) catch unreachable;
-        }
-
-        if (self.disable_libc) {
-            args.append("-nostdlib") catch unreachable;
-        }
-    }
-
-    fn makeC(self: *LibExeObjStep) !void {
-        const builder = self.builder;
-
-        const cc = builder.getCCExe();
-
-        assert(!self.is_zig);
-
-        var cc_args = ArrayList([]const u8).init(builder.allocator);
-        defer cc_args.deinit();
-
-        cc_args.append(cc) catch unreachable;
-
-        const is_darwin = self.target.isDarwin();
-
-        const c_std_arg = switch (self.c_std) {
-            Builder.CStd.C89 => "-std=c89",
-            Builder.CStd.C99 => "-std=c99",
-            Builder.CStd.C11 => "-std=c11",
-        };
-        try cc_args.append(c_std_arg);
-
-        switch (self.kind) {
-            Kind.Obj => {
-                cc_args.append("-c") catch unreachable;
-                cc_args.append(builder.pathFromRoot(self.object_src)) catch unreachable;
-
-                const output_path = builder.pathFromRoot(self.getOutputPath());
-                cc_args.append("-o") catch unreachable;
-                cc_args.append(output_path) catch unreachable;
-
-                self.appendCompileFlags(&cc_args);
-
-                try builder.spawnChild(cc_args.toSliceConst());
-            },
-            Kind.Lib => {
-                for (self.source_files.toSliceConst()) |source_file| {
-                    cc_args.resize(0) catch unreachable;
-                    cc_args.append(cc) catch unreachable;
-
-                    if (!self.static) {
-                        cc_args.append("-fPIC") catch unreachable;
-                    }
-
-                    const abs_source_file = builder.pathFromRoot(source_file);
-                    cc_args.append("-c") catch unreachable;
-                    cc_args.append(abs_source_file) catch unreachable;
-
-                    const cache_o_src = os.path.join(
-                        builder.allocator,
-                        [][]const u8{ builder.cache_root, source_file },
-                    ) catch unreachable;
-                    if (os.path.dirname(cache_o_src)) |cache_o_dir| {
-                        try builder.makePath(cache_o_dir);
-                    }
-                    const cache_o_file = builder.fmt("{}{}", cache_o_src, self.target.oFileExt());
-                    cc_args.append("-o") catch unreachable;
-                    cc_args.append(builder.pathFromRoot(cache_o_file)) catch unreachable;
-
-                    self.appendCompileFlags(&cc_args);
-
-                    try builder.spawnChild(cc_args.toSliceConst());
-
-                    self.object_files.append(cache_o_file) catch unreachable;
-                }
-
-                if (self.static) {
-                    // ar
-                    cc_args.resize(0) catch unreachable;
-                    cc_args.append("ar") catch unreachable;
-
-                    cc_args.append("qc") catch unreachable;
-
-                    const output_path = builder.pathFromRoot(self.getOutputPath());
-                    cc_args.append(output_path) catch unreachable;
-
-                    for (self.object_files.toSliceConst()) |object_file| {
-                        cc_args.append(builder.pathFromRoot(object_file)) catch unreachable;
-                    }
-
-                    try builder.spawnChild(cc_args.toSliceConst());
-
-                    // ranlib
-                    cc_args.resize(0) catch unreachable;
-                    cc_args.append("ranlib") catch unreachable;
-                    cc_args.append(output_path) catch unreachable;
-
-                    try builder.spawnChild(cc_args.toSliceConst());
-                } else {
-                    cc_args.resize(0) catch unreachable;
-                    cc_args.append(cc) catch unreachable;
-
-                    if (is_darwin) {
-                        cc_args.append("-dynamiclib") catch unreachable;
-
-                        cc_args.append("-Wl,-headerpad_max_install_names") catch unreachable;
-
-                        cc_args.append("-compatibility_version") catch unreachable;
-                        cc_args.append(builder.fmt("{}.0.0", self.version.major)) catch unreachable;
-
-                        cc_args.append("-current_version") catch unreachable;
-                        cc_args.append(builder.fmt("{}.{}.{}", self.version.major, self.version.minor, self.version.patch)) catch unreachable;
-
-                        const install_name = builder.pathFromRoot(os.path.join(
-                            builder.allocator,
-                            [][]const u8{ builder.cache_root, self.major_only_filename },
-                        ) catch unreachable);
-                        cc_args.append("-install_name") catch unreachable;
-                        cc_args.append(install_name) catch unreachable;
-                    } else {
-                        cc_args.append("-fPIC") catch unreachable;
-                        cc_args.append("-shared") catch unreachable;
-
-                        const soname_arg = builder.fmt("-Wl,-soname,lib{}.so.{d}", self.name, self.version.major);
-                        defer builder.allocator.free(soname_arg);
-                        cc_args.append(soname_arg) catch unreachable;
-                    }
-
-                    const output_path = builder.pathFromRoot(self.getOutputPath());
-                    cc_args.append("-o") catch unreachable;
-                    cc_args.append(output_path) catch unreachable;
-
-                    for (self.object_files.toSliceConst()) |object_file| {
-                        cc_args.append(builder.pathFromRoot(object_file)) catch unreachable;
-                    }
-
-                    if (!is_darwin) {
-                        const rpath_arg = builder.fmt("-Wl,-rpath,{}", try os.path.realAlloc(
-                            builder.allocator,
-                            builder.pathFromRoot(builder.cache_root),
-                        ));
-                        defer builder.allocator.free(rpath_arg);
-                        try cc_args.append(rpath_arg);
-
-                        try cc_args.append("-rdynamic");
-                    }
-
-                    for (self.full_path_libs.toSliceConst()) |full_path_lib| {
-                        cc_args.append(builder.pathFromRoot(full_path_lib)) catch unreachable;
-                    }
-
-                    {
-                        var it = self.link_libs.iterator();
-                        while (it.next()) |entry| {
-                            cc_args.append(builder.fmt("-l{}", entry.key)) catch unreachable;
-                        }
-                    }
-
-                    if (is_darwin and !self.static) {
-                        var it = self.frameworks.iterator();
-                        while (it.next()) |entry| {
-                            cc_args.append("-framework") catch unreachable;
-                            cc_args.append(entry.key) catch unreachable;
-                        }
-                    }
-
-                    try builder.spawnChild(cc_args.toSliceConst());
-
-                    if (self.target.wantSharedLibSymLinks()) {
-                        try doAtomicSymLinks(builder.allocator, output_path, self.major_only_filename, self.name_only_filename);
-                    }
-                }
-            },
-            Kind.Exe => {
-                for (self.source_files.toSliceConst()) |source_file| {
-                    cc_args.resize(0) catch unreachable;
-                    cc_args.append(cc) catch unreachable;
-
-                    const abs_source_file = builder.pathFromRoot(source_file);
-                    cc_args.append("-c") catch unreachable;
-                    cc_args.append(abs_source_file) catch unreachable;
-
-                    const cache_o_src = os.path.join(
-                        builder.allocator,
-                        [][]const u8{ builder.cache_root, source_file },
-                    ) catch unreachable;
-                    if (os.path.dirname(cache_o_src)) |cache_o_dir| {
-                        try builder.makePath(cache_o_dir);
-                    }
-                    const cache_o_file = builder.fmt("{}{}", cache_o_src, self.target.oFileExt());
-                    cc_args.append("-o") catch unreachable;
-                    cc_args.append(builder.pathFromRoot(cache_o_file)) catch unreachable;
-
-                    for (self.cflags.toSliceConst()) |cflag| {
-                        cc_args.append(cflag) catch unreachable;
-                    }
-
-                    for (self.include_dirs.toSliceConst()) |dir| {
-                        cc_args.append("-I") catch unreachable;
-                        cc_args.append(builder.pathFromRoot(dir)) catch unreachable;
-                    }
-
-                    try builder.spawnChild(cc_args.toSliceConst());
-
-                    self.object_files.append(cache_o_file) catch unreachable;
-                }
-
-                cc_args.resize(0) catch unreachable;
-                cc_args.append(cc) catch unreachable;
-
-                for (self.object_files.toSliceConst()) |object_file| {
-                    cc_args.append(builder.pathFromRoot(object_file)) catch unreachable;
-                }
-
-                const output_path = builder.pathFromRoot(self.getOutputPath());
-                cc_args.append("-o") catch unreachable;
-                cc_args.append(output_path) catch unreachable;
-
-                const rpath_arg = builder.fmt("-Wl,-rpath,{}", try os.path.realAlloc(
-                    builder.allocator,
-                    builder.pathFromRoot(builder.cache_root),
-                ));
-                defer builder.allocator.free(rpath_arg);
-                try cc_args.append(rpath_arg);
-
-                try cc_args.append("-rdynamic");
-
-                {
-                    var it = self.link_libs.iterator();
-                    while (it.next()) |entry| {
-                        cc_args.append(builder.fmt("-l{}", entry.key)) catch unreachable;
-                    }
-                }
-
-                if (is_darwin) {
-                    if (self.need_flat_namespace_hack) {
-                        cc_args.append("-Wl,-flat_namespace") catch unreachable;
-                    }
-                    cc_args.append("-Wl,-search_paths_first") catch unreachable;
-                }
-
-                for (self.full_path_libs.toSliceConst()) |full_path_lib| {
-                    cc_args.append(builder.pathFromRoot(full_path_lib)) catch unreachable;
-                }
-
-                if (is_darwin) {
-                    var it = self.frameworks.iterator();
-                    while (it.next()) |entry| {
-                        cc_args.append("-framework") catch unreachable;
-                        cc_args.append(entry.key) catch unreachable;
-                    }
-                }
-
-                try builder.spawnChild(cc_args.toSliceConst());
-            },
-        }
-    }
-};
-
-pub const TestStep = struct {
-    step: Step,
-    builder: *Builder,
-    root_src: []const u8,
-    build_mode: builtin.Mode,
-    verbose: bool,
-    link_libs: BufSet,
-    name_prefix: []const u8,
-    filter: ?[]const u8,
-    target: Target,
-    exec_cmd_args: ?[]const ?[]const u8,
-    include_dirs: ArrayList([]const u8),
-    lib_paths: ArrayList([]const u8),
-    packages: ArrayList(Pkg),
-    object_files: ArrayList([]const u8),
-    output_path: ?[]const u8,
-    system_linker_hack: bool,
-    override_std_dir: ?[]const u8,
-
-    pub fn init(builder: *Builder, root_src: []const u8) TestStep {
-        const step_name = builder.fmt("test {}", root_src);
-        return TestStep{
-            .step = Step.init(step_name, builder.allocator, make),
-            .builder = builder,
-            .root_src = root_src,
-            .build_mode = builtin.Mode.Debug,
-            .verbose = false,
-            .name_prefix = "",
-            .filter = null,
-            .link_libs = BufSet.init(builder.allocator),
-            .target = Target{ .Native = {} },
-            .exec_cmd_args = null,
-            .include_dirs = ArrayList([]const u8).init(builder.allocator),
-            .lib_paths = ArrayList([]const u8).init(builder.allocator),
-            .packages = ArrayList(Pkg).init(builder.allocator),
-            .object_files = ArrayList([]const u8).init(builder.allocator),
-            .output_path = null,
-            .system_linker_hack = false,
-            .override_std_dir = null,
-        };
-    }
-
-    pub fn addLibPath(self: *TestStep, path: []const u8) void {
-        self.lib_paths.append(path) catch unreachable;
-    }
-
-    pub fn addPackagePath(self: *TestStep, name: []const u8, pkg_index_path: []const u8) void {
-        self.packages.append(Pkg{
-            .name = name,
-            .path = pkg_index_path,
-        }) catch unreachable;
-    }
-
-    pub fn setVerbose(self: *TestStep, value: bool) void {
-        self.verbose = value;
-    }
-
-    pub fn addIncludeDir(self: *TestStep, path: []const u8) void {
-        self.include_dirs.append(path) catch unreachable;
-    }
-
-    pub fn setBuildMode(self: *TestStep, mode: builtin.Mode) void {
-        self.build_mode = mode;
-    }
-
-    pub fn overrideStdDir(self: *TestStep, dir_path: []const u8) void {
-        self.override_std_dir = dir_path;
-    }
-
-    pub fn setOutputPath(self: *TestStep, file_path: []const u8) void {
-        self.output_path = file_path;
-
-        // catch a common mistake
-        if (mem.eql(u8, self.builder.pathFromRoot(file_path), self.builder.pathFromRoot("."))) {
-            debug.panic("setOutputPath wants a file path, not a directory\n");
-        }
-    }
-
-    pub fn getOutputPath(self: *TestStep) []const u8 {
-        if (self.output_path) |output_path| {
-            return output_path;
-        } else {
-            const basename = self.builder.fmt("test{}", self.target.exeFileExt());
-            return os.path.join(
-                self.builder.allocator,
-                [][]const u8{ self.builder.cache_root, basename },
-            ) catch unreachable;
-        }
-    }
-
-    pub fn linkSystemLibrary(self: *TestStep, name: []const u8) void {
-        self.link_libs.put(name) catch unreachable;
-    }
-
-    pub fn setNamePrefix(self: *TestStep, text: []const u8) void {
-        self.name_prefix = text;
-    }
-
-    pub fn setFilter(self: *TestStep, text: ?[]const u8) void {
-        self.filter = text;
-    }
-
-    pub fn addObject(self: *TestStep, obj: *LibExeObjStep) void {
-        assert(obj.kind == LibExeObjStep.Kind.Obj);
-
-        self.step.dependOn(&obj.step);
-
-        self.object_files.append(obj.getOutputPath()) catch unreachable;
-
-        // TODO should be some kind of isolated directory that only has this header in it
-        self.include_dirs.append(self.builder.cache_root) catch unreachable;
-    }
-
-    pub fn addObjectFile(self: *TestStep, path: []const u8) void {
-        self.object_files.append(path) catch unreachable;
-    }
-
-    pub fn setTarget(self: *TestStep, target_arch: builtin.Arch, target_os: builtin.Os, target_environ: builtin.Environ) void {
-        self.target = Target{
-            .Cross = CrossTarget{
-                .arch = target_arch,
-                .os = target_os,
-                .environ = target_environ,
-            },
-        };
-    }
-
-    pub fn setExecCmd(self: *TestStep, args: []const ?[]const u8) void {
-        self.exec_cmd_args = args;
-    }
-
-    pub fn enableSystemLinkerHack(self: *TestStep) void {
-        self.system_linker_hack = true;
-    }
-
-    fn make(step: *Step) !void {
-        const self = @fieldParentPtr(TestStep, "step", step);
-        const builder = self.builder;
-
-        var zig_args = ArrayList([]const u8).init(builder.allocator);
-        defer zig_args.deinit();
-
-        try zig_args.append(builder.zig_exe);
-
-        try zig_args.append("test");
-        try zig_args.append(builder.pathFromRoot(self.root_src));
-
-        if (self.verbose) {
-            try zig_args.append("--verbose");
-        }
-
-        switch (self.build_mode) {
-            builtin.Mode.Debug => {},
-            builtin.Mode.ReleaseSafe => try zig_args.append("--release-safe"),
-            builtin.Mode.ReleaseFast => try zig_args.append("--release-fast"),
-            builtin.Mode.ReleaseSmall => try zig_args.append("--release-small"),
-        }
-
-        const output_path = builder.pathFromRoot(self.getOutputPath());
-        try zig_args.append("--output");
-        try zig_args.append(output_path);
-
-        switch (self.target) {
-            Target.Native => {},
-            Target.Cross => |cross_target| {
-                try zig_args.append("--target-arch");
-                try zig_args.append(@tagName(cross_target.arch));
-
-                try zig_args.append("--target-os");
-                try zig_args.append(@tagName(cross_target.os));
-
-                try zig_args.append("--target-environ");
-                try zig_args.append(@tagName(cross_target.environ));
-            },
-        }
-
-        if (self.filter) |filter| {
-            try zig_args.append("--test-filter");
-            try zig_args.append(filter);
-        }
-
-        if (self.name_prefix.len != 0) {
-            try zig_args.append("--test-name-prefix");
-            try zig_args.append(self.name_prefix);
-        }
-
-        for (self.object_files.toSliceConst()) |object_file| {
-            try zig_args.append("--object");
-            try zig_args.append(builder.pathFromRoot(object_file));
-        }
-
-        {
-            var it = self.link_libs.iterator();
-            while (true) {
-                const entry = it.next() orelse break;
-                try zig_args.append("--library");
-                try zig_args.append(entry.key);
-            }
-        }
-
-        if (self.exec_cmd_args) |exec_cmd_args| {
-            for (exec_cmd_args) |cmd_arg| {
-                if (cmd_arg) |arg| {
-                    try zig_args.append("--test-cmd");
-                    try zig_args.append(arg);
-                } else {
-                    try zig_args.append("--test-cmd-bin");
-                }
-            }
-        }
-
-        for (self.include_dirs.toSliceConst()) |include_path| {
-            try zig_args.append("-isystem");
-            try zig_args.append(builder.pathFromRoot(include_path));
-        }
-
-        for (builder.include_paths.toSliceConst()) |include_path| {
-            try zig_args.append("-isystem");
-            try zig_args.append(builder.pathFromRoot(include_path));
-        }
-
-        for (builder.rpaths.toSliceConst()) |rpath| {
-            try zig_args.append("-rpath");
-            try zig_args.append(rpath);
-        }
-
-        for (self.lib_paths.toSliceConst()) |lib_path| {
-            try zig_args.append("--library-path");
-            try zig_args.append(lib_path);
-        }
-
-        for (builder.lib_paths.toSliceConst()) |lib_path| {
-            try zig_args.append("--library-path");
-            try zig_args.append(lib_path);
-        }
-
-        for (self.packages.toSliceConst()) |pkg| {
-            zig_args.append("--pkg-begin") catch unreachable;
-            zig_args.append(pkg.name) catch unreachable;
-            zig_args.append(builder.pathFromRoot(pkg.path)) catch unreachable;
-            zig_args.append("--pkg-end") catch unreachable;
-        }
-
-        if (self.system_linker_hack) {
-            try zig_args.append("--system-linker-hack");
-        }
         if (self.override_std_dir) |dir| {
             try zig_args.append("--override-std-dir");
             try zig_args.append(builder.pathFromRoot(dir));
         }
 
         try builder.spawnChild(zig_args.toSliceConst());
+
+        if (self.kind == Kind.Lib and !self.static and self.target.wantSharedLibSymLinks()) {
+            try doAtomicSymLinks(builder.allocator, output_path, self.major_only_filename, self.name_only_filename);
+        }
     }
 };
 
@@ -1976,6 +1396,7 @@ const InstallArtifactStep = struct {
     pub fn create(builder: *Builder, artifact: *LibExeObjStep) *Self {
         const dest_dir = switch (artifact.kind) {
             LibExeObjStep.Kind.Obj => unreachable,
+            LibExeObjStep.Kind.Test => unreachable,
             LibExeObjStep.Kind.Exe => builder.exe_dir,
             LibExeObjStep.Kind.Lib => builder.lib_dir,
         };
@@ -2012,6 +1433,7 @@ const InstallArtifactStep = struct {
             builtin.Os.windows => {},
             else => switch (self.artifact.kind) {
                 LibExeObjStep.Kind.Obj => unreachable,
+                LibExeObjStep.Kind.Test => unreachable,
                 LibExeObjStep.Kind.Exe => u32(0o755),
                 LibExeObjStep.Kind.Lib => if (self.artifact.static) u32(0o666) else u32(0o755),
             },
test/stage1/c_abi/build.zig
@@ -3,9 +3,10 @@ const Builder = @import("std").build.Builder;
 pub fn build(b: *Builder) void {
     const rel_opts = b.standardReleaseOptions();
 
-    const c_obj = b.addCObject("cfuncs", "cfuncs.c");
+    const c_obj = b.addObject("cfuncs", null);
+    c_obj.addCSourceFile("cfuncs.c", [][]const u8{"-std=c99"});
     c_obj.setBuildMode(rel_opts);
-    c_obj.setNoStdLib(true);
+    c_obj.linkSystemLibrary("c");
 
     const main = b.addTest("main.zig");
     main.setBuildMode(rel_opts);