Commit 6fd8d455bc

Andrew Kelley <andrew@ziglang.org>
2019-02-23 15:35:56
better libc detection (#1996)
* better libc detection This introduces a new command `zig libc` which prints the various paths of libc files. It outputs them to stdout in a simple text file format that it is capable of parsing. You can use `zig libc libc.txt` to validate a file. These arguments are gone: --libc-lib-dir [path] directory where libc crt1.o resides --libc-static-lib-dir [path] directory where libc crtbegin.o resides --msvc-lib-dir [path] (windows) directory where vcruntime.lib resides --kernel32-lib-dir [path] (windows) directory where kernel32.lib resides Instead we have this argument: --libc [file] Provide a file which specifies libc paths This is used to pass a libc text file (which can be generated with `zig libc`). So it is easier to manage multiple cross compilation environments. `--cache on` now works when linking against libc. `ZigTarget` now has a bool field `is_native` Better error messaging when you try to link against libc or use `@cImport` but the various paths cannot be found. It should also be faster. * save native_libc.txt in zig-cache This avoids having to detect libc at runtime on every invocation.
1 parent 52bb718
src/all_types.hpp
@@ -18,6 +18,7 @@
 #include "bigfloat.hpp"
 #include "target.hpp"
 #include "tokenizer.hpp"
+#include "libc_installation.hpp"
 
 struct AstNode;
 struct ImportTableEntry;
@@ -1743,6 +1744,9 @@ struct CodeGen {
     Buf *wanted_output_file_path;
     Buf cache_dir;
 
+    Buf *zig_c_headers_dir; // Cannot be overridden; derived from zig_lib_dir.
+    Buf *zig_std_special_dir; // Cannot be overridden; derived from zig_lib_dir.
+
     IrInstruction *invalid_instruction;
     IrInstruction *unreach_instruction;
 
@@ -1791,6 +1795,8 @@ struct CodeGen {
     bool system_linker_hack;
 
     //////////////////////////// Participates in Input Parameter Cache Hash
+    /////// Note: there is a separate cache hash for builtin.zig, when adding fields,
+    ///////       consider if they need to go into both.
     ZigList<LinkLib *> link_libs_list;
     // add -framework [name] args to linker
     ZigList<Buf *> darwin_frameworks;
@@ -1801,6 +1807,8 @@ struct CodeGen {
     ZigList<Buf *> assembly_files;
     ZigList<const char *> lib_dirs;
 
+    ZigLibCInstallation *libc;
+
     size_t version_major;
     size_t version_minor;
     size_t version_patch;
@@ -1809,14 +1817,13 @@ struct CodeGen {
     EmitFileType emit_file_type;
     BuildMode build_mode;
     OutType out_type;
-    ZigTarget zig_target;
+    const ZigTarget *zig_target;
     TargetSubsystem subsystem;
     ValgrindSupport valgrind_support;
     bool is_static;
     bool strip_debug_symbols;
     bool is_test_build;
     bool is_single_threaded;
-    bool is_native_target;
     bool linker_rdynamic;
     bool each_lib_rpath;
     bool disable_pic;
@@ -1827,26 +1834,14 @@ struct CodeGen {
     Buf *test_filter;
     Buf *test_name_prefix;
     PackageTableEntry *root_package;
+    Buf *zig_lib_dir;
+    Buf *zig_std_dir;
 
     const char **llvm_argv;
     size_t llvm_argv_len;
 
     const char **clang_argv;
     size_t clang_argv_len;
-
-    //////////////////////////// Unsorted
-
-    Buf *libc_lib_dir;
-    Buf *libc_static_lib_dir;
-    Buf *libc_include_dir;
-    Buf *msvc_lib_dir;
-    Buf *kernel32_lib_dir;
-    Buf *zig_lib_dir;
-    Buf *zig_std_dir;
-    Buf *zig_c_headers_dir;
-    Buf *zig_std_special_dir;
-    Buf *dynamic_linker;
-    ZigWindowsSDK *win_sdk;
 };
 
 enum VarLinkage {
src/analyze.cpp
@@ -1102,10 +1102,10 @@ bool want_first_arg_sret(CodeGen *g, FnTypeId *fn_type_id) {
     if (type_is_c_abi_int(g, fn_type_id->return_type)) {
         return false;
     }
-    if (g->zig_target.arch.arch == ZigLLVM_x86_64) {
+    if (g->zig_target->arch.arch == ZigLLVM_x86_64) {
         X64CABIClass abi_class = type_c_abi_x86_64_class(g, fn_type_id->return_type);
         return abi_class == X64CABIClass_MEMORY;
-    } else if (target_is_arm(&g->zig_target)) {
+    } else if (target_is_arm(g->zig_target)) {
         return type_size(g, fn_type_id->return_type) > 16;
     }
     zig_panic("TODO implement C ABI for this architecture. See https://github.com/ziglang/zig/issues/1481");
@@ -3304,16 +3304,16 @@ void add_fn_export(CodeGen *g, ZigFn *fn_table_entry, Buf *symbol_name, GlobalLi
             g->have_c_main = true;
             g->subsystem = TargetSubsystemConsole;
         } else if (buf_eql_str(symbol_name, "WinMain") &&
-            g->zig_target.os == OsWindows)
+            g->zig_target->os == OsWindows)
         {
             g->have_winmain = true;
             g->subsystem = TargetSubsystemWindows;
         } else if (buf_eql_str(symbol_name, "WinMainCRTStartup") &&
-            g->zig_target.os == OsWindows)
+            g->zig_target->os == OsWindows)
         {
             g->have_winmain_crt_startup = true;
         } else if (buf_eql_str(symbol_name, "DllMainCRTStartup") &&
-            g->zig_target.os == OsWindows)
+            g->zig_target->os == OsWindows)
         {
             g->have_dllmain_crt_startup = true;
         }
@@ -4651,186 +4651,6 @@ bool handle_is_ptr(ZigType *type_entry) {
     zig_unreachable();
 }
 
-static ZigWindowsSDK *get_windows_sdk(CodeGen *g) {
-    if (g->win_sdk == nullptr) {
-        if (zig_find_windows_sdk(&g->win_sdk)) {
-            fprintf(stderr, "unable to determine windows sdk path\n");
-            exit(1);
-        }
-    }
-    assert(g->win_sdk != nullptr);
-    return g->win_sdk;
-}
-
-
-static Buf *get_linux_libc_lib_path(const char *o_file) {
-    const char *cc_exe = getenv("CC");
-    cc_exe = (cc_exe == nullptr) ? "cc" : cc_exe;
-    ZigList<const char *> args = {};
-    args.append(buf_ptr(buf_sprintf("-print-file-name=%s", o_file)));
-    Termination term;
-    Buf *out_stderr = buf_alloc();
-    Buf *out_stdout = buf_alloc();
-    Error err;
-    if ((err = os_exec_process(cc_exe, args, &term, out_stderr, out_stdout))) {
-        zig_panic("unable to determine libc lib path: executing C compiler: %s", err_str(err));
-    }
-    if (term.how != TerminationIdClean || term.code != 0) {
-        zig_panic("unable to determine libc lib path: executing C compiler command failed");
-    }
-    if (buf_ends_with_str(out_stdout, "\n")) {
-        buf_resize(out_stdout, buf_len(out_stdout) - 1);
-    }
-    if (buf_len(out_stdout) == 0 || buf_eql_str(out_stdout, o_file)) {
-        zig_panic("unable to determine libc lib path: C compiler could not find %s", o_file);
-    }
-    Buf *result = buf_alloc();
-    os_path_dirname(out_stdout, result);
-    return result;
-}
-
-static Buf *get_posix_libc_include_path(void) {
-    const char *cc_exe = getenv("CC");
-    cc_exe = (cc_exe == nullptr) ? "cc" : cc_exe;
-    ZigList<const char *> args = {};
-    args.append("-E");
-    args.append("-Wp,-v");
-    args.append("-xc");
-    args.append("/dev/null");
-    Termination term;
-    Buf *out_stderr = buf_alloc();
-    Buf *out_stdout = buf_alloc();
-    Error err;
-    if ((err = os_exec_process(cc_exe, args, &term, out_stderr, out_stdout))) {
-        zig_panic("unable to determine libc include path: executing C compiler: %s", err_str(err));
-    }
-    if (term.how != TerminationIdClean || term.code != 0) {
-        zig_panic("unable to determine libc include path: executing C compiler command failed");
-    }
-    char *prev_newline = buf_ptr(out_stderr);
-    ZigList<const char *> search_paths = {};
-    for (;;) {
-        char *newline = strchr(prev_newline, '\n');
-        if (newline == nullptr) {
-            break;
-        }
-        *newline = 0;
-        if (prev_newline[0] == ' ') {
-            search_paths.append(prev_newline);
-        }
-        prev_newline = newline + 1;
-    }
-    if (search_paths.length == 0) {
-        zig_panic("unable to determine libc include path: even C compiler does not know where libc headers are");
-    }
-    for (size_t i = 0; i < search_paths.length; i += 1) {
-        // search in reverse order
-        const char *search_path = search_paths.items[search_paths.length - i - 1];
-        // cut off spaces
-        while (*search_path == ' ') {
-            search_path += 1;
-        }
-        Buf *stdlib_path = buf_sprintf("%s/stdlib.h", search_path);
-        bool exists;
-        if ((err = os_file_exists(stdlib_path, &exists))) {
-            exists = false;
-        }
-        if (exists) {
-            return buf_create_from_str(search_path);
-        }
-    }
-    zig_panic("unable to determine libc include path: stdlib.h not found in C compiler search paths");
-}
-
-void find_libc_include_path(CodeGen *g) {
-    if (g->libc_include_dir == nullptr) {
-        if (!g->is_native_target) {
-            return;
-        }
-
-        if (g->zig_target.os == OsWindows) {
-            ZigWindowsSDK *sdk = get_windows_sdk(g);
-            g->libc_include_dir = buf_alloc();
-            if (os_get_win32_ucrt_include_path(sdk, g->libc_include_dir)) {
-                fprintf(stderr, "Unable to determine libc include path. --libc-include-dir");
-                exit(1);
-            }
-        } else if (g->zig_target.os == OsLinux ||
-            g->zig_target.os == OsMacOSX ||
-            g->zig_target.os == OsFreeBSD ||
-	    g->zig_target.os == OsNetBSD)
-        {
-            g->libc_include_dir = get_posix_libc_include_path();
-        } else {
-            fprintf(stderr, "Unable to determine libc include path.\n"
-                    "TODO: implement finding libc at runtime for other operating systems.\n"
-                    "in the meantime, you can use as a workaround: --libc-include-dir\n");
-            exit(1);
-        }
-    }
-    assert(buf_len(g->libc_include_dir) != 0);
-}
-
-void find_libc_lib_path(CodeGen *g) {
-    // later we can handle this better by reporting an error via the normal mechanism
-    if (g->libc_lib_dir == nullptr ||
-        (g->zig_target.os == OsWindows && (g->msvc_lib_dir == nullptr || g->kernel32_lib_dir == nullptr)))
-    {
-        if (g->zig_target.os == OsWindows) {
-            ZigWindowsSDK *sdk = get_windows_sdk(g);
-
-            if (g->msvc_lib_dir == nullptr) {
-                if (sdk->msvc_lib_dir_ptr == nullptr) {
-                    fprintf(stderr, "Unable to determine vcruntime path. --msvc-lib-dir");
-                    exit(1);
-                }
-                g->msvc_lib_dir = buf_create_from_mem(sdk->msvc_lib_dir_ptr, sdk->msvc_lib_dir_len);
-            }
-
-            if (g->libc_lib_dir == nullptr) {
-                Buf* ucrt_lib_path = buf_alloc();
-                if (os_get_win32_ucrt_lib_path(sdk, ucrt_lib_path, g->zig_target.arch.arch)) {
-                    fprintf(stderr, "Unable to determine ucrt path. --libc-lib-dir");
-                    exit(1);
-                }
-                g->libc_lib_dir = ucrt_lib_path;
-            }
-
-            if (g->kernel32_lib_dir == nullptr) {
-                Buf* kern_lib_path = buf_alloc();
-                if (os_get_win32_kern32_path(sdk, kern_lib_path, g->zig_target.arch.arch)) {
-                    fprintf(stderr, "Unable to determine kernel32 path. --kernel32-lib-dir");
-                    exit(1);
-                }
-                g->kernel32_lib_dir = kern_lib_path;
-            }
-
-        } else if (g->zig_target.os == OsLinux) {
-            g->libc_lib_dir = get_linux_libc_lib_path("crt1.o");
-        } else if ((g->zig_target.os == OsFreeBSD) || (g->zig_target.os == OsNetBSD)) {
-            g->libc_lib_dir = buf_create_from_str("/usr/lib");
-        } else {
-            zig_panic("Unable to determine libc lib path.");
-        }
-    } else {
-        assert(buf_len(g->libc_lib_dir) != 0);
-    }
-
-    if (g->libc_static_lib_dir == nullptr) {
-        if ((g->zig_target.os == OsWindows) && (g->msvc_lib_dir != NULL)) {
-            return;
-        } else if (g->zig_target.os == OsLinux) {
-            g->libc_static_lib_dir = get_linux_libc_lib_path("crtbegin.o");
-        } else if ((g->zig_target.os == OsFreeBSD) || (g->zig_target.os == OsNetBSD)) {
-            g->libc_static_lib_dir = buf_create_from_str("/usr/lib");
-        } else {
-            zig_panic("Unable to determine libc static lib path.");
-        }
-    } else {
-        assert(buf_len(g->libc_static_lib_dir) != 0);
-    }
-}
-
 static uint32_t hash_ptr(void *ptr) {
     return (uint32_t)(((uintptr_t)ptr) % UINT32_MAX);
 }
@@ -6736,14 +6556,6 @@ LinkLib *add_link_lib(CodeGen *g, Buf *name) {
     if (is_libc && g->libc_link_lib != nullptr)
         return g->libc_link_lib;
 
-    if (g->enable_cache && is_libc && g->zig_target.os != OsMacOSX &&
-        g->zig_target.os != OsIOS && g->zig_target.os != OsFreeBSD &&
-	g->zig_target.os != OsNetBSD) {
-        fprintf(stderr, "TODO linking against libc is currently incompatible with `--cache on`.\n"
-        "Zig is not yet capable of determining whether the libc installation has changed on subsequent builds.\n");
-        exit(1);
-    }
-
     for (size_t i = 0; i < g->link_libs_list.length; i += 1) {
         LinkLib *existing_lib = g->link_libs_list.at(i);
         if (buf_eql_buf(existing_lib->name, name)) {
src/analyze.hpp
@@ -40,8 +40,6 @@ ZigType *get_promise_type(CodeGen *g, ZigType *result_type);
 ZigType *get_promise_frame_type(CodeGen *g, ZigType *return_type);
 ZigType *get_test_fn_type(CodeGen *g);
 bool handle_is_ptr(ZigType *type_entry);
-void find_libc_include_path(CodeGen *g);
-void find_libc_lib_path(CodeGen *g);
 
 bool type_has_bits(ZigType *type_entry);
 bool type_allowed_in_extern(CodeGen *g, ZigType *type_entry);
src/codegen.cpp
@@ -29,9 +29,9 @@ static void init_darwin_native(CodeGen *g) {
 
     // Allow conflicts among OSX and iOS, but choose the default platform.
     if (osx_target && ios_target) {
-        if (g->zig_target.arch.arch == ZigLLVM_arm ||
-            g->zig_target.arch.arch == ZigLLVM_aarch64 ||
-            g->zig_target.arch.arch == ZigLLVM_thumb)
+        if (g->zig_target->arch.arch == ZigLLVM_arm ||
+            g->zig_target->arch.arch == ZigLLVM_aarch64 ||
+            g->zig_target->arch.arch == ZigLLVM_thumb)
         {
             osx_target = nullptr;
         } else {
@@ -43,7 +43,7 @@ static void init_darwin_native(CodeGen *g) {
         g->mmacosx_version_min = buf_create_from_str(osx_target);
     } else if (ios_target) {
         g->mios_version_min = buf_create_from_str(ios_target);
-    } else if (g->zig_target.os != OsIOS) {
+    } else if (g->zig_target->os != OsIOS) {
         g->mmacosx_version_min = buf_create_from_str("10.10");
     }
 }
@@ -88,13 +88,15 @@ static const char *symbols_that_llvm_depends_on[] = {
 };
 
 CodeGen *codegen_create(Buf *root_src_path, const ZigTarget *target, OutType out_type, BuildMode build_mode,
-    Buf *zig_lib_dir, Buf *override_std_dir)
+    Buf *zig_lib_dir, Buf *override_std_dir, ZigLibCInstallation *libc)
 {
     CodeGen *g = allocate<CodeGen>(1);
 
     codegen_add_time_event(g, "Initialize");
 
+    g->libc = libc;
     g->zig_lib_dir = zig_lib_dir;
+    g->zig_target = target;
 
     if (override_std_dir == nullptr) {
         g->zig_std_dir = buf_alloc();
@@ -149,44 +151,19 @@ CodeGen *codegen_create(Buf *root_src_path, const ZigTarget *target, OutType out
     g->zig_std_special_dir = buf_alloc();
     os_path_join(g->zig_std_dir, buf_sprintf("special"), g->zig_std_special_dir);
 
-    if (target) {
-        // cross compiling, so we can't rely on all the configured stuff since
-        // that's for native compilation
-        g->zig_target = *target;
-        resolve_target_object_format(&g->zig_target);
-        g->dynamic_linker = nullptr;
-        g->libc_lib_dir = nullptr;
-        g->libc_static_lib_dir = nullptr;
-        g->libc_include_dir = nullptr;
-        g->msvc_lib_dir = nullptr;
-        g->kernel32_lib_dir = nullptr;
+    assert(target != nullptr);
+    if (!target->is_native) {
         g->each_lib_rpath = false;
     } else {
-        // native compilation, we can rely on the configuration stuff
-        g->is_native_target = true;
-        get_native_target(&g->zig_target);
-        g->dynamic_linker = nullptr; // find it at runtime
-        g->libc_lib_dir = nullptr; // find it at runtime
-        g->libc_static_lib_dir = nullptr; // find it at runtime
-        g->libc_include_dir = nullptr; // find it at runtime
-        g->msvc_lib_dir = nullptr; // find it at runtime
-        g->kernel32_lib_dir = nullptr; // find it at runtime
         g->each_lib_rpath = true;
 
-        if (g->zig_target.os == OsMacOSX ||
-            g->zig_target.os == OsIOS)
-        {
+        if (target_is_darwin(g->zig_target)) {
             init_darwin_native(g);
         }
 
     }
 
-    // On Darwin/MacOS/iOS, we always link libSystem which contains libc.
-    if (g->zig_target.os == OsMacOSX ||
-        g->zig_target.os == OsIOS ||
-	g->zig_target.os == OsFreeBSD ||
-	g->zig_target.os == OsNetBSD)
-    {
+    if (target_requires_libc(g->zig_target)) {
         g->libc_link_lib = create_link_lib(buf_create_from_str("c"));
         g->link_libs_list.append(g->libc_link_lib);
     }
@@ -254,30 +231,6 @@ void codegen_set_out_name(CodeGen *g, Buf *out_name) {
     g->root_out_name = out_name;
 }
 
-void codegen_set_libc_lib_dir(CodeGen *g, Buf *libc_lib_dir) {
-    g->libc_lib_dir = libc_lib_dir;
-}
-
-void codegen_set_libc_static_lib_dir(CodeGen *g, Buf *libc_static_lib_dir) {
-    g->libc_static_lib_dir = libc_static_lib_dir;
-}
-
-void codegen_set_libc_include_dir(CodeGen *g, Buf *libc_include_dir) {
-    g->libc_include_dir = libc_include_dir;
-}
-
-void codegen_set_msvc_lib_dir(CodeGen *g, Buf *msvc_lib_dir) {
-    g->msvc_lib_dir = msvc_lib_dir;
-}
-
-void codegen_set_kernel32_lib_dir(CodeGen *g, Buf *kernel32_lib_dir) {
-    g->kernel32_lib_dir = kernel32_lib_dir;
-}
-
-void codegen_set_dynamic_linker(CodeGen *g, Buf *dynamic_linker) {
-    g->dynamic_linker = dynamic_linker;
-}
-
 void codegen_add_lib_dir(CodeGen *g, const char *dir) {
     g->lib_dirs.append(dir);
 }
@@ -390,11 +343,11 @@ static LLVMCallConv get_llvm_cc(CodeGen *g, CallingConvention cc) {
         case CallingConventionC: return LLVMCCallConv;
         case CallingConventionCold:
             // cold calling convention only works on x86.
-            if (g->zig_target.arch.arch == ZigLLVM_x86 ||
-                g->zig_target.arch.arch == ZigLLVM_x86_64)
+            if (g->zig_target->arch.arch == ZigLLVM_x86 ||
+                g->zig_target->arch.arch == ZigLLVM_x86_64)
             {
                 // cold calling convention is not supported on windows
-                if (g->zig_target.os == OsWindows) {
+                if (g->zig_target->os == OsWindows) {
                     return LLVMCCallConv;
                 } else {
                     return LLVMColdCallConv;
@@ -407,7 +360,7 @@ static LLVMCallConv get_llvm_cc(CodeGen *g, CallingConvention cc) {
             zig_unreachable();
         case CallingConventionStdcall:
             // stdcall calling convention only works on x86.
-            if (g->zig_target.arch.arch == ZigLLVM_x86) {
+            if (g->zig_target->arch.arch == ZigLLVM_x86) {
                 return LLVMX86StdcallCallConv;
             } else {
                 return LLVMCCallConv;
@@ -419,7 +372,7 @@ static LLVMCallConv get_llvm_cc(CodeGen *g, CallingConvention cc) {
 }
 
 static void add_uwtable_attr(CodeGen *g, LLVMValueRef fn_val) {
-    if (g->zig_target.os == OsWindows) {
+    if (g->zig_target->os == OsWindows) {
         addLLVMFnAttr(fn_val, "uwtable");
     }
 }
@@ -455,13 +408,13 @@ static uint32_t get_err_ret_trace_arg_index(CodeGen *g, ZigFn *fn_table_entry) {
 }
 
 static void maybe_export_dll(CodeGen *g, LLVMValueRef global_value, GlobalLinkageId linkage) {
-    if (linkage != GlobalLinkageIdInternal && g->zig_target.os == OsWindows) {
+    if (linkage != GlobalLinkageIdInternal && g->zig_target->os == OsWindows) {
         LLVMSetDLLStorageClass(global_value, LLVMDLLExportStorageClass);
     }
 }
 
 static void maybe_import_dll(CodeGen *g, LLVMValueRef global_value, GlobalLinkageId linkage) {
-    if (linkage != GlobalLinkageIdInternal && g->zig_target.os == OsWindows) {
+    if (linkage != GlobalLinkageIdInternal && g->zig_target->os == OsWindows) {
         // TODO come up with a good explanation/understanding for why we never do
         // DLLImportStorageClass. Empirically it only causes problems. But let's have
         // this documented and then clean up the code accordingly.
@@ -506,7 +459,7 @@ static LLVMValueRef fn_llvm_value(CodeGen *g, ZigFn *fn_table_entry) {
     bool external_linkage = linkage != GlobalLinkageIdInternal;
     CallingConvention cc = fn_table_entry->type_entry->data.fn.fn_type_id.cc;
     if (cc == CallingConventionStdcall && external_linkage &&
-        g->zig_target.arch.arch == ZigLLVM_x86)
+        g->zig_target->arch.arch == ZigLLVM_x86)
     {
         // prevent llvm name mangling
         symbol_name = buf_sprintf("\x01_%s", buf_ptr(symbol_name));
@@ -2138,7 +2091,7 @@ static bool iter_function_params_c_abi(CodeGen *g, ZigType *fn_type, FnWalk *fn_
         return true;
     }
 
-    if (g->zig_target.arch.arch == ZigLLVM_x86_64) {
+    if (g->zig_target->arch.arch == ZigLLVM_x86_64) {
         X64CABIClass abi_class = type_c_abi_x86_64_class(g, ty);
         size_t ty_size = type_size(g, ty);
         if (abi_class == X64CABIClass_MEMORY) {
@@ -3381,15 +3334,15 @@ static bool value_is_all_undef(ConstExprValue *const_val) {
 static LLVMValueRef gen_valgrind_client_request(CodeGen *g, LLVMValueRef default_value, LLVMValueRef request,
         LLVMValueRef a1, LLVMValueRef a2, LLVMValueRef a3, LLVMValueRef a4, LLVMValueRef a5)
 {
-    if (!target_has_valgrind_support(&g->zig_target)) {
+    if (!target_has_valgrind_support(g->zig_target)) {
         return default_value;
     }
     LLVMTypeRef usize_type_ref = g->builtin_types.entry_usize->type_ref;
     bool asm_has_side_effects = true;
     bool asm_is_alignstack = false;
-    if (g->zig_target.arch.arch == ZigLLVM_x86_64) {
-        if (g->zig_target.os == OsLinux || target_is_darwin(&g->zig_target) || g->zig_target.os == OsSolaris ||
-            (g->zig_target.os == OsWindows && g->zig_target.env_type != ZigLLVM_MSVC))
+    if (g->zig_target->arch.arch == ZigLLVM_x86_64) {
+        if (g->zig_target->os == OsLinux || target_is_darwin(g->zig_target) || g->zig_target->os == OsSolaris ||
+            (g->zig_target->os == OsWindows && g->zig_target->env_type != ZigLLVM_MSVC))
         {
             if (g->cur_fn->valgrind_client_request_array == nullptr) {
                 LLVMBasicBlockRef prev_block = LLVMGetInsertBlock(g->builder);
@@ -3436,7 +3389,7 @@ static LLVMValueRef gen_valgrind_client_request(CodeGen *g, LLVMValueRef default
 }
 
 static bool want_valgrind_support(CodeGen *g) {
-    if (!target_has_valgrind_support(&g->zig_target))
+    if (!target_has_valgrind_support(g->zig_target))
         return false;
     switch (g->valgrind_support) {
         case ValgrindSupportDisabled:
@@ -3629,7 +3582,7 @@ static void gen_set_stack_pointer(CodeGen *g, LLVMValueRef aligned_end_addr) {
     LLVMValueRef write_register_fn_val = get_write_register_fn_val(g);
 
     if (g->sp_md_node == nullptr) {
-        Buf *sp_reg_name = buf_create_from_str(arch_stack_pointer_register_name(&g->zig_target.arch));
+        Buf *sp_reg_name = buf_create_from_str(arch_stack_pointer_register_name(&g->zig_target->arch));
         LLVMValueRef str_node = LLVMMDString(buf_ptr(sp_reg_name), buf_len(sp_reg_name) + 1);
         g->sp_md_node = LLVMMDNode(&str_node, 1);
     }
@@ -7042,7 +6995,7 @@ static void define_builtin_types(CodeGen *g) {
 
     for (size_t i = 0; i < array_length(c_int_type_infos); i += 1) {
         const CIntTypeInfo *info = &c_int_type_infos[i];
-        uint32_t size_in_bits = target_c_type_size_in_bits(&g->zig_target, info->id);
+        uint32_t size_in_bits = target_c_type_size_in_bits(g->zig_target, info->id);
         bool is_signed = info->is_signed;
 
         ZigType *entry = new_type_table_entry(ZigTypeIdInt);
@@ -7309,6 +7262,9 @@ static const char *build_mode_to_str(BuildMode build_mode) {
 Buf *codegen_generate_builtin_source(CodeGen *g) {
     Buf *contents = buf_alloc();
 
+    // NOTE: when editing this file, you may need to make modifications to the
+    // cache input parameters in define_builtin_compile_vars
+
     // Modifications to this struct must be coordinated with code that does anything with
     // g->stack_trace_type. There are hard-coded references to the field indexes.
     buf_append_str(contents,
@@ -7328,7 +7284,7 @@ Buf *codegen_generate_builtin_source(CodeGen *g) {
             const char *name = get_target_os_name(os_type);
             buf_appendf(contents, "    %s,\n", name);
 
-            if (os_type == g->zig_target.os) {
+            if (os_type == g->zig_target->os) {
                 g->target_os_index = i;
                 cur_os = name;
             }
@@ -7350,8 +7306,8 @@ Buf *codegen_generate_builtin_source(CodeGen *g) {
 
             buf_appendf(contents, "    %s,\n", buf_ptr(arch_name));
 
-            if (arch_type->arch == g->zig_target.arch.arch &&
-                arch_type->sub_arch == g->zig_target.arch.sub_arch)
+            if (arch_type->arch == g->zig_target->arch.arch &&
+                arch_type->sub_arch == g->zig_target->arch.sub_arch)
             {
                 g->target_arch_index = i;
                 cur_arch = buf_ptr(arch_name);
@@ -7370,7 +7326,7 @@ Buf *codegen_generate_builtin_source(CodeGen *g) {
             const char *name = ZigLLVMGetEnvironmentTypeName(environ_type);
             buf_appendf(contents, "    %s,\n", name);
 
-            if (environ_type == g->zig_target.env_type) {
+            if (environ_type == g->zig_target->env_type) {
                 g->target_environ_index = i;
                 cur_environ = name;
             }
@@ -7388,7 +7344,8 @@ Buf *codegen_generate_builtin_source(CodeGen *g) {
             const char *name = get_target_oformat_name(oformat);
             buf_appendf(contents, "    %s,\n", name);
 
-            if (oformat == g->zig_target.oformat) {
+            ZigLLVM_ObjectFormatType target_oformat = target_object_format(g->zig_target);
+            if (oformat == target_oformat) {
                 g->target_oformat_index = i;
                 cur_obj_fmt = name;
             }
@@ -7707,14 +7664,15 @@ static Error define_builtin_compile_vars(CodeGen *g) {
     cache_int(&cache_hash, g->build_mode);
     cache_bool(&cache_hash, g->is_test_build);
     cache_bool(&cache_hash, g->is_single_threaded);
-    cache_int(&cache_hash, g->zig_target.arch.arch);
-    cache_int(&cache_hash, g->zig_target.arch.sub_arch);
-    cache_int(&cache_hash, g->zig_target.vendor);
-    cache_int(&cache_hash, g->zig_target.os);
-    cache_int(&cache_hash, g->zig_target.env_type);
-    cache_int(&cache_hash, g->zig_target.oformat);
+    cache_int(&cache_hash, g->zig_target->is_native);
+    cache_int(&cache_hash, g->zig_target->arch.arch);
+    cache_int(&cache_hash, g->zig_target->arch.sub_arch);
+    cache_int(&cache_hash, g->zig_target->vendor);
+    cache_int(&cache_hash, g->zig_target->os);
+    cache_int(&cache_hash, g->zig_target->env_type);
     cache_bool(&cache_hash, g->have_err_ret_tracing);
     cache_bool(&cache_hash, g->libc_link_lib != nullptr);
+    cache_bool(&cache_hash, g->valgrind_support);
 
     Buf digest = BUF_INIT;
     buf_resize(&digest, 0);
@@ -7783,11 +7741,11 @@ static void init(CodeGen *g) {
     assert(g->root_out_name);
     g->module = LLVMModuleCreateWithName(buf_ptr(g->root_out_name));
 
-    get_target_triple(&g->triple_str, &g->zig_target);
+    get_target_triple(&g->triple_str, g->zig_target);
 
     LLVMSetTarget(g->module, buf_ptr(&g->triple_str));
 
-    if (g->zig_target.oformat == ZigLLVM_COFF) {
+    if (target_object_format(g->zig_target) == ZigLLVM_COFF) {
         ZigLLVMAddModuleCodeViewFlag(g->module);
     } else {
         ZigLLVMAddModuleDebugInfoFlag(g->module);
@@ -7815,11 +7773,11 @@ static void init(CodeGen *g) {
 
     const char *target_specific_cpu_args;
     const char *target_specific_features;
-    if (g->is_native_target) {
+    if (g->zig_target->is_native) {
         // LLVM creates invalid binaries on Windows sometimes.
         // See https://github.com/ziglang/zig/issues/508
         // As a workaround we do not use target native features on Windows.
-        if (g->zig_target.os == OsWindows || g->zig_target.os == OsUefi) {
+        if (g->zig_target->os == OsWindows || g->zig_target->os == OsUefi) {
             target_specific_cpu_args = "";
             target_specific_features = "";
         } else {
@@ -7892,9 +7850,59 @@ static void init(CodeGen *g) {
     }
 }
 
-void codegen_translate_c(CodeGen *g, Buf *full_path) {
-    find_libc_include_path(g);
+static void detect_libc(CodeGen *g) {
+    Error err;
+
+    if (g->libc != nullptr || g->libc_link_lib == nullptr)
+        return;
 
+    if (g->zig_target->is_native) {
+        g->libc = allocate<ZigLibCInstallation>(1);
+
+        // Look for zig-cache/native_libc.txt
+        Buf *native_libc_txt = buf_alloc();
+        os_path_join(&g->cache_dir, buf_create_from_str("native_libc.txt"), native_libc_txt);
+        if ((err = zig_libc_parse(g->libc, native_libc_txt, g->zig_target, false))) {
+            if ((err = zig_libc_find_native(g->libc, true))) {
+                fprintf(stderr,
+                    "Unable to link against libc: Unable to find libc installation: %s\n"
+                    "See `zig libc --help` for more details.\n", err_str(err));
+                exit(1);
+            }
+            if ((err = os_make_path(&g->cache_dir))) {
+                fprintf(stderr, "Unable to create %s directory: %s\n",
+                    buf_ptr(&g->cache_dir), err_str(err));
+                exit(1);
+            }
+            Buf *native_libc_tmp = buf_sprintf("%s.tmp", buf_ptr(native_libc_txt));
+            FILE *file = fopen(buf_ptr(native_libc_tmp), "wb");
+            if (file == nullptr) {
+                fprintf(stderr, "Unable to open %s: %s\n", buf_ptr(native_libc_tmp), strerror(errno));
+                exit(1);
+            }
+            zig_libc_render(g->libc, file);
+            if (fclose(file) != 0) {
+                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));
+                exit(1);
+            }
+        }
+    } else if ((g->out_type == OutTypeExe || (g->out_type == OutTypeLib && !g->is_static)) &&
+        !target_is_darwin(g->zig_target))
+    {
+        // Currently darwin is the only platform that we can link libc on when not compiling natively,
+        // without a cross compiling libc kit.
+        fprintf(stderr,
+            "Cannot link against libc for non-native OS '%s' without providing a libc installation file.\n"
+            "See `zig libc --help` for more details.\n", get_target_os_name(g->zig_target->os));
+        exit(1);
+    }
+}
+
+void codegen_translate_c(CodeGen *g, Buf *full_path) {
     Buf *src_basename = buf_alloc();
     Buf *src_dirname = buf_alloc();
     os_path_split(full_path, src_dirname, src_basename);
@@ -7905,6 +7913,8 @@ void codegen_translate_c(CodeGen *g, Buf *full_path) {
     g->root_import = import;
     import->decls_scope = create_decls_scope(g, nullptr, nullptr, nullptr, import);
 
+    detect_libc(g);
+
     init(g);
 
     import->di_file = ZigLLVMCreateFile(g->dbuilder, buf_ptr(src_basename), buf_ptr(src_dirname));
@@ -8064,14 +8074,14 @@ static void gen_root_source(CodeGen *g) {
     }
     report_errors_and_maybe_exit(g);
 
-    if (!g->is_test_build && g->zig_target.os != OsFreestanding &&
-        g->zig_target.os != OsUefi &&
+    if (!g->is_test_build && g->zig_target->os != OsFreestanding &&
+        g->zig_target->os != OsUefi &&
         !g->have_c_main && !g->have_winmain && !g->have_winmain_crt_startup &&
         ((g->have_pub_main && g->out_type == OutTypeObj) || g->out_type == OutTypeExe))
     {
         g->bootstrap_import = add_special_code(g, create_bootstrap_pkg(g, g->root_package), "bootstrap.zig");
     }
-    if (g->zig_target.os == OsWindows && !g->have_dllmain_crt_startup &&
+    if (g->zig_target->os == OsWindows && !g->have_dllmain_crt_startup &&
             g->out_type == OutTypeLib && !g->is_static)
     {
         g->bootstrap_import = add_special_code(g, create_bootstrap_pkg(g, g->root_package), "bootstrap_lib.zig");
@@ -8619,6 +8629,8 @@ static Error check_cache(CodeGen *g, Buf *manifest_dir, Buf *digest) {
     }
     cache_buf(ch, compiler_id);
     cache_buf(ch, g->root_out_name);
+    cache_buf(ch, g->zig_lib_dir);
+    cache_buf(ch, g->zig_std_dir);
     cache_list_of_link_lib(ch, g->link_libs_list.items, g->link_libs_list.length);
     cache_list_of_buf(ch, g->darwin_frameworks.items, g->darwin_frameworks.length);
     cache_list_of_buf(ch, g->rpath_list.items, g->rpath_list.length);
@@ -8628,18 +8640,17 @@ static Error check_cache(CodeGen *g, Buf *manifest_dir, Buf *digest) {
     cache_int(ch, g->emit_file_type);
     cache_int(ch, g->build_mode);
     cache_int(ch, g->out_type);
-    cache_int(ch, g->zig_target.arch.arch);
-    cache_int(ch, g->zig_target.arch.sub_arch);
-    cache_int(ch, g->zig_target.vendor);
-    cache_int(ch, g->zig_target.os);
-    cache_int(ch, g->zig_target.env_type);
-    cache_int(ch, g->zig_target.oformat);
+    cache_bool(ch, g->zig_target->is_native);
+    cache_int(ch, g->zig_target->arch.arch);
+    cache_int(ch, g->zig_target->arch.sub_arch);
+    cache_int(ch, g->zig_target->vendor);
+    cache_int(ch, g->zig_target->os);
+    cache_int(ch, g->zig_target->env_type);
     cache_int(ch, g->subsystem);
     cache_bool(ch, g->is_static);
     cache_bool(ch, g->strip_debug_symbols);
     cache_bool(ch, g->is_test_build);
     cache_bool(ch, g->is_single_threaded);
-    cache_bool(ch, g->is_native_target);
     cache_bool(ch, g->linker_rdynamic);
     cache_bool(ch, g->each_lib_rpath);
     cache_bool(ch, g->disable_pic);
@@ -8654,6 +8665,14 @@ static Error check_cache(CodeGen *g, Buf *manifest_dir, Buf *digest) {
     cache_list_of_str(ch, g->llvm_argv, g->llvm_argv_len);
     cache_list_of_str(ch, g->clang_argv, g->clang_argv_len);
     cache_list_of_str(ch, g->lib_dirs.items, g->lib_dirs.length);
+    if (g->libc) {
+        cache_buf(ch, &g->libc->include_dir);
+        cache_buf(ch, &g->libc->lib_dir);
+        cache_buf(ch, &g->libc->static_lib_dir);
+        cache_buf(ch, &g->libc->msvc_lib_dir);
+        cache_buf(ch, &g->libc->kernel32_lib_dir);
+        cache_buf(ch, &g->libc->dynamic_linker_path);
+    }
 
     buf_resize(digest, 0);
     if ((err = cache_hit(ch, digest)))
@@ -8668,19 +8687,19 @@ static void resolve_out_paths(CodeGen *g) {
     switch (g->emit_file_type) {
         case EmitFileTypeBinary:
         {
-            const char *o_ext = target_o_file_ext(&g->zig_target);
+            const char *o_ext = target_o_file_ext(g->zig_target);
             buf_append_str(o_basename, o_ext);
             break;
         }
         case EmitFileTypeAssembly:
         {
-            const char *asm_ext = target_asm_file_ext(&g->zig_target);
+            const char *asm_ext = target_asm_file_ext(g->zig_target);
             buf_append_str(o_basename, asm_ext);
             break;
         }
         case EmitFileTypeLLVMIr:
         {
-            const char *llvm_ir_ext = target_llvm_ir_file_ext(&g->zig_target);
+            const char *llvm_ir_ext = target_llvm_ir_file_ext(g->zig_target);
             buf_append_str(o_basename, llvm_ir_ext);
             break;
         }
@@ -8706,7 +8725,7 @@ static void resolve_out_paths(CodeGen *g) {
 
             Buf basename = BUF_INIT;
             buf_init_from_buf(&basename, g->root_out_name);
-            buf_append_str(&basename, target_exe_file_ext(&g->zig_target));
+            buf_append_str(&basename, target_exe_file_ext(g->zig_target));
             if (g->enable_cache || g->is_test_build) {
                 os_path_join(&g->artifact_dir, &basename, &g->output_file_path);
             } else {
@@ -8719,7 +8738,7 @@ static void resolve_out_paths(CodeGen *g) {
         } else {
             Buf basename = BUF_INIT;
             buf_init_from_buf(&basename, g->root_out_name);
-            buf_append_str(&basename, target_lib_file_ext(&g->zig_target, g->is_static,
+            buf_append_str(&basename, target_lib_file_ext(g->zig_target, g->is_static,
                         g->version_major, g->version_minor, g->version_patch));
             if (g->enable_cache) {
                 os_path_join(&g->artifact_dir, &basename, &g->output_file_path);
@@ -8732,11 +8751,12 @@ static void resolve_out_paths(CodeGen *g) {
     }
 }
 
-
 void codegen_build_and_link(CodeGen *g) {
     Error err;
     assert(g->out_type != OutTypeUnknown);
 
+    detect_libc(g);
+
     Buf *stage1_dir = get_stage1_cache_path();
     Buf *artifact_dir = buf_alloc();
     Buf digest = BUF_INIT;
src/codegen.hpp
@@ -11,11 +11,12 @@
 #include "parser.hpp"
 #include "errmsg.hpp"
 #include "target.hpp"
+#include "libc_installation.hpp"
 
 #include <stdio.h>
 
 CodeGen *codegen_create(Buf *root_src_path, const ZigTarget *target, OutType out_type, BuildMode build_mode,
-    Buf *zig_lib_dir, Buf *override_std_dir);
+    Buf *zig_lib_dir, Buf *override_std_dir, ZigLibCInstallation *libc);
 
 void codegen_set_clang_argv(CodeGen *codegen, const char **args, size_t len);
 void codegen_set_llvm_argv(CodeGen *codegen, const char **args, size_t len);
@@ -27,12 +28,6 @@ void codegen_set_is_static(CodeGen *codegen, bool is_static);
 void codegen_set_strip(CodeGen *codegen, bool strip);
 void codegen_set_errmsg_color(CodeGen *codegen, ErrColor err_color);
 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);
-void codegen_set_libc_include_dir(CodeGen *codegen, Buf *libc_include_dir);
-void codegen_set_msvc_lib_dir(CodeGen *g, Buf *msvc_lib_dir);
-void codegen_set_kernel32_lib_dir(CodeGen *codegen, Buf *kernel32_lib_dir);
-void codegen_set_dynamic_linker(CodeGen *g, Buf *dynamic_linker);
 void codegen_add_lib_dir(CodeGen *codegen, const char *dir);
 void codegen_add_forbidden_lib(CodeGen *codegen, Buf *lib);
 LinkLib *codegen_add_link_lib(CodeGen *codegen, Buf *lib);
src/error.cpp
@@ -34,6 +34,8 @@ const char *err_str(Error err) {
         case ErrorPipeBusy: return "pipe busy";
         case ErrorPrimitiveTypeNotFound: return "primitive type not found";
         case ErrorCacheUnavailable: return "cache unavailable";
+        case ErrorPathTooLong: return "path too long";
+        case ErrorCCompilerCannotFindFile: return "C compiler cannot find file";
     }
     return "(invalid error)";
 }
src/error.hpp
@@ -36,6 +36,8 @@ enum Error {
     ErrorPipeBusy,
     ErrorPrimitiveTypeNotFound,
     ErrorCacheUnavailable,
+    ErrorPathTooLong,
+    ErrorCCompilerCannotFindFile,
 };
 
 const char *err_str(Error err);
src/ir.cpp
@@ -18690,8 +18690,6 @@ static IrInstruction *ir_analyze_instruction_c_import(IrAnalyze *ira, IrInstruct
     if (type_is_invalid(cimport_result->type))
         return ira->codegen->invalid_instruction;
 
-    find_libc_include_path(ira->codegen);
-
     ImportTableEntry *child_import = allocate<ImportTableEntry>(1);
     child_import->decls_scope = create_decls_scope(ira->codegen, node, nullptr, nullptr, child_import);
     child_import->c_import_node = node;
src/libc_installation.cpp
@@ -0,0 +1,408 @@
+/*
+ * Copyright (c) 2019 Andrew Kelley
+ *
+ * This file is part of zig, which is MIT licensed.
+ * See http://opensource.org/licenses/MIT
+ */
+
+#include "libc_installation.hpp"
+#include "os.hpp"
+#include "windows_sdk.h"
+#include "target.hpp"
+
+static const size_t zig_libc_keys_len = 6;
+
+static const char *zig_libc_keys[] = {
+    "include_dir",
+    "lib_dir",
+    "static_lib_dir",
+    "msvc_lib_dir",
+    "kernel32_lib_dir",
+    "dynamic_linker_path",
+};
+
+static bool zig_libc_match_key(Slice<uint8_t> name, Slice<uint8_t> value, bool *found_keys,
+        size_t index, Buf *field_ptr)
+{
+    if (!memEql(name, str(zig_libc_keys[index]))) return false;
+    buf_init_from_mem(field_ptr, (const char*)value.ptr, value.len);
+    found_keys[index] = true;
+    return true;
+}
+
+static void zig_libc_init_empty(ZigLibCInstallation *libc) {
+    *libc = {};
+    buf_init_from_str(&libc->include_dir, "");
+    buf_init_from_str(&libc->lib_dir, "");
+    buf_init_from_str(&libc->static_lib_dir, "");
+    buf_init_from_str(&libc->msvc_lib_dir, "");
+    buf_init_from_str(&libc->kernel32_lib_dir, "");
+    buf_init_from_str(&libc->dynamic_linker_path, "");
+}
+
+Error zig_libc_parse(ZigLibCInstallation *libc, Buf *libc_file, const ZigTarget *target, bool verbose) {
+    Error err;
+    zig_libc_init_empty(libc);
+
+    bool found_keys[6] = {}; // zig_libc_keys_len
+
+    Buf *contents = buf_alloc();
+    if ((err = os_fetch_file_path(libc_file, contents, false))) {
+        if (err != ErrorFileNotFound && verbose) {
+            fprintf(stderr, "Unable to read '%s': %s\n", buf_ptr(libc_file), err_str(err));
+        }
+        return err;
+    }
+
+    SplitIterator it = memSplit(buf_to_slice(contents), str("\n"));
+    for (;;) {
+        Optional<Slice<uint8_t>> opt_line = SplitIterator_next(&it);
+        if (!opt_line.is_some)
+            break;
+
+        if (opt_line.value.len == 0 || opt_line.value.ptr[0] == '#')
+            continue;
+
+        SplitIterator line_it = memSplit(opt_line.value, str("="));
+        Slice<uint8_t> name;
+        if (!SplitIterator_next(&line_it).unwrap(&name)) {
+            if (verbose) {
+                fprintf(stderr, "missing equal sign after field name\n");
+            }
+            return ErrorSemanticAnalyzeFail;
+        }
+        Slice<uint8_t> value = SplitIterator_rest(&line_it);
+        bool match = false;
+        match = match || zig_libc_match_key(name, value, found_keys, 0, &libc->include_dir);
+        match = match || zig_libc_match_key(name, value, found_keys, 1, &libc->lib_dir);
+        match = match || zig_libc_match_key(name, value, found_keys, 2, &libc->static_lib_dir);
+        match = match || zig_libc_match_key(name, value, found_keys, 3, &libc->msvc_lib_dir);
+        match = match || zig_libc_match_key(name, value, found_keys, 4, &libc->kernel32_lib_dir);
+        match = match || zig_libc_match_key(name, value, found_keys, 5, &libc->dynamic_linker_path);
+    }
+
+    for (size_t i = 0; i < zig_libc_keys_len; i += 1) {
+        if (!found_keys[i]) {
+            if (verbose) {
+                fprintf(stderr, "missing field: %s\n", zig_libc_keys[i]);
+            }
+            return ErrorSemanticAnalyzeFail;
+        }
+    }
+
+    if (buf_len(&libc->include_dir) == 0) {
+        if (verbose) {
+            fprintf(stderr, "include_dir may not be empty\n");
+        }
+        return ErrorSemanticAnalyzeFail;
+    }
+
+    if (buf_len(&libc->lib_dir) == 0) {
+        if (!target_is_darwin(target)) {
+            if (verbose) {
+                fprintf(stderr, "lib_dir may not be empty for %s\n", get_target_os_name(target->os));
+            }
+            return ErrorSemanticAnalyzeFail;
+        }
+    }
+
+    if (buf_len(&libc->static_lib_dir) == 0) {
+        if (!target_is_darwin(target) && target->os != OsWindows) {
+            if (verbose) {
+                fprintf(stderr, "static_lib_dir may not be empty for %s\n", get_target_os_name(target->os));
+            }
+            return ErrorSemanticAnalyzeFail;
+        }
+    }
+
+    if (buf_len(&libc->msvc_lib_dir) == 0) {
+        if (target->os == OsWindows) {
+            if (verbose) {
+                fprintf(stderr, "msvc_lib_dir may not be empty for %s\n", get_target_os_name(target->os));
+            }
+            return ErrorSemanticAnalyzeFail;
+        }
+    }
+
+    if (buf_len(&libc->kernel32_lib_dir) == 0) {
+        if (target->os == OsWindows) {
+            if (verbose) {
+                fprintf(stderr, "kernel32_lib_dir may not be empty for %s\n", get_target_os_name(target->os));
+            }
+            return ErrorSemanticAnalyzeFail;
+        }
+    }
+
+    if (buf_len(&libc->dynamic_linker_path) == 0) {
+        if (target->os == OsLinux) {
+            if (verbose) {
+                fprintf(stderr, "dynamic_linker_path may not be empty for %s\n", get_target_os_name(target->os));
+            }
+            return ErrorSemanticAnalyzeFail;
+        }
+    }
+
+    return ErrorNone;
+}
+
+#if defined(ZIG_OS_WINDOWS)
+static Error zig_libc_find_native_include_dir_windows(ZigLibCInstallation *self, ZigWindowsSDK *sdk, bool verbose) {
+    Error err;
+    if ((err = os_get_win32_ucrt_include_path(sdk, &self->include_dir))) {
+        if (verbose) {
+            fprintf(stderr, "Unable to determine libc include path: %s\n", err_str(err));
+        }
+        return err;
+    }
+    return ErrorNone;
+}
+static Error zig_libc_find_lib_dir_windows(ZigLibCInstallation *self, ZigWindowsSDK *sdk, ZigTarget *target,
+        bool verbose)
+{
+    Error err;
+    if ((err = os_get_win32_ucrt_lib_path(sdk, &self->lib_dir, target->arch.arch))) {
+        if (verbose) {
+            fprintf(stderr, "Unable to determine ucrt path: %s\n", err_str(err));
+        }
+        return err;
+    }
+    return ErrorNone;
+}
+static Error zig_libc_find_kernel32_lib_dir(ZigLibCInstallation *self, ZigWindowsSDK *sdk, ZigTarget *target,
+        bool verbose)
+{
+    Error err;
+    if ((err = os_get_win32_kern32_path(sdk, &self->kernel32_lib_dir, target->arch.arch))) {
+        if (verbose) {
+            fprintf(stderr, "Unable to determine kernel32 path: %s\n", err_str(err));
+        }
+        return err;
+    }
+    return ErrorNone;
+}
+static Error zig_libc_find_native_msvc_lib_dir(ZigLibCInstallation *self, ZigWindowsSDK *sdk, bool verbose) {
+    if (sdk->msvc_lib_dir_ptr == nullptr) {
+        if (verbose) {
+            fprintf(stderr, "Unable to determine vcruntime path\n");
+        }
+        return ErrorFileNotFound;
+    }
+    buf_init_from_mem(&self->msvc_lib_dir, sdk->msvc_lib_dir_ptr, sdk->msvc_lib_dir_len);
+    return ErrorNone;
+}
+#else
+static Error zig_libc_find_native_include_dir_posix(ZigLibCInstallation *self, bool verbose) {
+    const char *cc_exe = getenv("CC");
+    cc_exe = (cc_exe == nullptr) ? "cc" : cc_exe;
+    ZigList<const char *> args = {};
+    args.append("-E");
+    args.append("-Wp,-v");
+    args.append("-xc");
+    args.append("/dev/null");
+    Termination term;
+    Buf *out_stderr = buf_alloc();
+    Buf *out_stdout = buf_alloc();
+    Error err;
+    if ((err = os_exec_process(cc_exe, args, &term, out_stderr, out_stdout))) {
+        if (verbose) {
+            fprintf(stderr, "unable to determine libc include path: executing '%s': %s\n", cc_exe, err_str(err));
+        }
+        return err;
+    }
+    if (term.how != TerminationIdClean || term.code != 0) {
+        if (verbose) {
+            fprintf(stderr, "unable to determine libc include path: executing '%s' failed\n", cc_exe);
+        }
+        return ErrorCCompileErrors;
+    }
+    char *prev_newline = buf_ptr(out_stderr);
+    ZigList<const char *> search_paths = {};
+    for (;;) {
+        char *newline = strchr(prev_newline, '\n');
+        if (newline == nullptr) {
+            break;
+        }
+        *newline = 0;
+        if (prev_newline[0] == ' ') {
+            search_paths.append(prev_newline);
+        }
+        prev_newline = newline + 1;
+    }
+    if (search_paths.length == 0) {
+        if (verbose) {
+            fprintf(stderr, "unable to determine libc include path: '%s' cannot find libc headers\n", cc_exe);
+        }
+        return ErrorCCompileErrors;
+    }
+    for (size_t i = 0; i < search_paths.length; i += 1) {
+        // search in reverse order
+        const char *search_path = search_paths.items[search_paths.length - i - 1];
+        // cut off spaces
+        while (*search_path == ' ') {
+            search_path += 1;
+        }
+        Buf *stdlib_path = buf_sprintf("%s/stdlib.h", search_path);
+        bool exists;
+        if ((err = os_file_exists(stdlib_path, &exists))) {
+            exists = false;
+        }
+        if (exists) {
+            buf_init_from_str(&self->include_dir, search_path);
+            return ErrorNone;
+        }
+    }
+    if (verbose) {
+        fprintf(stderr, "unable to determine libc include path: stdlib.h not found in '%s' search paths\n", cc_exe);
+    }
+    return ErrorFileNotFound;
+}
+#if !defined(ZIG_OS_DARWIN)
+static Error zig_libc_cc_print_file_name(const char *o_file, Buf *out, bool want_dirname, bool verbose) {
+    const char *cc_exe = getenv("CC");
+    cc_exe = (cc_exe == nullptr) ? "cc" : cc_exe;
+    ZigList<const char *> args = {};
+    args.append(buf_ptr(buf_sprintf("-print-file-name=%s", o_file)));
+    Termination term;
+    Buf *out_stderr = buf_alloc();
+    Buf *out_stdout = buf_alloc();
+    Error err;
+    if ((err = os_exec_process(cc_exe, args, &term, out_stderr, out_stdout))) {
+        if (verbose) {
+            fprintf(stderr, "unable to determine libc include path: executing '%s': %s\n", cc_exe, err_str(err));
+        }
+        return err;
+    }
+    if (term.how != TerminationIdClean || term.code != 0) {
+        if (verbose) {
+            fprintf(stderr, "unable to determine libc include path: executing '%s' failed\n", cc_exe);
+        }
+        return ErrorCCompileErrors;
+    }
+    if (buf_ends_with_str(out_stdout, "\n")) {
+        buf_resize(out_stdout, buf_len(out_stdout) - 1);
+    }
+    if (buf_len(out_stdout) == 0 || buf_eql_str(out_stdout, o_file)) {
+        return ErrorCCompilerCannotFindFile;
+    }
+    if (want_dirname) {
+        os_path_dirname(out_stdout, out);
+    } else {
+        buf_init_from_buf(out, out_stdout);
+    }
+    return ErrorNone;
+}
+static Error zig_libc_find_native_lib_dir_posix(ZigLibCInstallation *self, bool verbose) {
+    return zig_libc_cc_print_file_name("crt1.o", &self->lib_dir, true, verbose);
+}
+
+static Error zig_libc_find_native_static_lib_dir_posix(ZigLibCInstallation *self, bool verbose) {
+    return zig_libc_cc_print_file_name("crtbegin.o", &self->static_lib_dir, true, verbose);
+}
+#endif
+
+static Error zig_libc_find_native_dynamic_linker_posix(ZigLibCInstallation *self, bool verbose) {
+#if defined(ZIG_OS_LINUX)
+    Error err;
+    static const char *dyn_tests[] = {
+        "ld-linux-x86-64.so.2",
+        "ld-musl-x86_64.so.1",
+    };
+    for (size_t i = 0; i < array_length(dyn_tests); i += 1) {
+        const char *lib_name = dyn_tests[i];
+        if ((err = zig_libc_cc_print_file_name(lib_name, &self->dynamic_linker_path, false, true))) {
+            if (err != ErrorCCompilerCannotFindFile)
+                return err;
+            continue;
+        }
+        return ErrorNone;
+    }
+#endif
+    ZigTarget native_target;
+    get_native_target(&native_target);
+    Buf *dynamic_linker_path = target_dynamic_linker(&native_target);
+    buf_init_from_buf(&self->dynamic_linker_path, dynamic_linker_path);
+    return ErrorNone;
+}
+#endif
+
+void zig_libc_render(ZigLibCInstallation *self, FILE *file) {
+    fprintf(file,
+        "# The directory that contains `stdlib.h`.\n"
+        "# On Linux, can be found with: `cc -E -Wp,-v -xc /dev/null`\n"
+        "include_dir=%s\n"
+        "\n"
+        "# The directory that contains `crt1.o`.\n"
+        "# On Linux, can be found with `cc -print-file-name=crt1.o`.\n"
+        "# Not needed when targeting MacOS.\n"
+        "lib_dir=%s\n"
+        "\n"
+        "# The directory that contains `crtbegin.o`.\n"
+        "# On Linux, can be found with `cc -print-file-name=crtbegin.o`.\n"
+        "# Not needed when targeting MacOS or Windows.\n"
+        "static_lib_dir=%s\n"
+        "\n"
+        "# The directory that contains `vcruntime.lib`.\n"
+        "# Only needed when targeting Windows.\n"
+        "msvc_lib_dir=%s\n"
+        "\n"
+        "# The directory that contains `kernel32.lib`.\n"
+        "# Only needed when targeting Windows.\n"
+        "kernel32_lib_dir=%s\n"
+        "\n"
+        "# The full path to the dynamic linker, on the target system.\n"
+        "# Only needed when targeting Linux.\n"
+        "dynamic_linker_path=%s\n"
+        "\n"
+    ,
+        buf_ptr(&self->include_dir),
+        buf_ptr(&self->lib_dir),
+        buf_ptr(&self->static_lib_dir),
+        buf_ptr(&self->msvc_lib_dir),
+        buf_ptr(&self->kernel32_lib_dir),
+        buf_ptr(&self->dynamic_linker_path)
+    );
+}
+
+Error zig_libc_find_native(ZigLibCInstallation *self, bool verbose) {
+    Error err;
+    zig_libc_init_empty(self);
+#if defined(ZIG_OS_WINDOWS)
+    ZigTarget native_target;
+    get_native_target(&native_target);
+    ZigWindowsSDK *sdk;
+    switch (zig_find_windows_sdk(&sdk)) {
+        case ZigFindWindowsSdkErrorNone:
+            if ((err = zig_libc_find_native_msvc_lib_dir(self, sdk, verbose)))
+                return err;
+            if ((err = zig_libc_find_kernel32_lib_dir(self, sdk, &native_target, verbose)))
+                return err;
+            if ((err = zig_libc_find_native_include_dir_windows(self, sdk, verbose)))
+                return err;
+            if ((err = zig_libc_find_lib_dir_windows(self, sdk, &native_target, verbose)))
+                return err;
+            return ErrorNone;
+        case ZigFindWindowsSdkErrorOutOfMemory:
+            return ErrorNoMem;
+        case ZigFindWindowsSdkErrorNotFound:
+            return ErrorFileNotFound;
+        case ZigFindWindowsSdkErrorPathTooLong:
+            return ErrorPathTooLong;
+    }
+    zig_unreachable();
+#else
+    if ((err = zig_libc_find_native_include_dir_posix(self, verbose)))
+        return err;
+#if defined(ZIG_OS_FREEBSD) || defined(ZIG_OS_NETBSD)
+    buf_init_from_str(&self->lib_dir, "/usr/lib");
+    buf_init_from_str(&self->static_lib_dir, "/usr/lib");
+#elif !defined(ZIG_OS_DARWIN)
+    if ((err = zig_libc_find_native_lib_dir_posix(self, verbose)))
+        return err;
+    if ((err = zig_libc_find_native_static_lib_dir_posix(self, verbose)))
+        return err;
+#endif
+    if ((err = zig_libc_find_native_dynamic_linker_posix(self, verbose)))
+        return err;
+    return ErrorNone;
+#endif
+}
src/libc_installation.hpp
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2019 Andrew Kelley
+ *
+ * This file is part of zig, which is MIT licensed.
+ * See http://opensource.org/licenses/MIT
+ */
+
+#ifndef ZIG_LIBC_INSTALLATION_HPP
+#define ZIG_LIBC_INSTALLATION_HPP
+
+#include <stdio.h>
+
+#include "buffer.hpp"
+#include "error.hpp"
+#include "target.hpp"
+
+// Must be synchronized with zig_libc_keys
+struct ZigLibCInstallation {
+    Buf include_dir;
+    Buf lib_dir;
+    Buf static_lib_dir;
+    Buf msvc_lib_dir;
+    Buf kernel32_lib_dir;
+    Buf dynamic_linker_path;
+};
+
+Error ATTRIBUTE_MUST_USE zig_libc_parse(ZigLibCInstallation *libc, Buf *libc_file,
+        const ZigTarget *target, bool verbose);
+void zig_libc_render(ZigLibCInstallation *self, FILE *file);
+
+Error ATTRIBUTE_MUST_USE zig_libc_find_native(ZigLibCInstallation *self, bool verbose);
+
+#endif
src/link.cpp
@@ -18,31 +18,32 @@ struct LinkJob {
 };
 
 static const char *get_libc_file(CodeGen *g, const char *file) {
+    assert(g->libc != nullptr);
     Buf *out_buf = buf_alloc();
-    os_path_join(g->libc_lib_dir, buf_create_from_str(file), out_buf);
+    os_path_join(&g->libc->lib_dir, buf_create_from_str(file), out_buf);
     return buf_ptr(out_buf);
 }
 
 static const char *get_libc_static_file(CodeGen *g, const char *file) {
+    assert(g->libc != nullptr);
     Buf *out_buf = buf_alloc();
-    os_path_join(g->libc_static_lib_dir, buf_create_from_str(file), out_buf);
+    os_path_join(&g->libc->static_lib_dir, buf_create_from_str(file), out_buf);
     return buf_ptr(out_buf);
 }
 
 static Buf *build_a_raw(CodeGen *parent_gen, const char *aname, Buf *full_path) {
-    ZigTarget *child_target = parent_gen->is_native_target ? nullptr : &parent_gen->zig_target;
-
     // The Mach-O LLD code is not well maintained, and trips an assertion
     // when we link compiler_rt and builtin as libraries rather than objects.
     // Here we workaround this by having compiler_rt and builtin be objects.
     // TODO write our own linker. https://github.com/ziglang/zig/issues/1535
     OutType child_out_type = OutTypeLib;
-    if (parent_gen->zig_target.os == OsMacOSX) {
+    if (parent_gen->zig_target->os == OsMacOSX) {
         child_out_type = OutTypeObj;
     }
 
-    CodeGen *child_gen = codegen_create(full_path, child_target, child_out_type,
-        parent_gen->build_mode, parent_gen->zig_lib_dir, parent_gen->zig_std_dir);
+    CodeGen *child_gen = codegen_create(full_path, parent_gen->zig_target, child_out_type,
+        parent_gen->build_mode, parent_gen->zig_lib_dir, parent_gen->zig_std_dir,
+        parent_gen->libc);
 
     child_gen->out_h_path = nullptr;
     child_gen->verbose_tokenize = parent_gen->verbose_tokenize;
@@ -171,62 +172,11 @@ static void add_rpath(LinkJob *lj, Buf *rpath) {
     lj->rpath_table.put(rpath, true);
 }
 
-static Buf *try_dynamic_linker_path(const char *ld_name) {
-    const char *cc_exe = getenv("CC");
-    cc_exe = (cc_exe == nullptr) ? "cc" : cc_exe;
-    ZigList<const char *> args = {};
-    args.append(buf_ptr(buf_sprintf("-print-file-name=%s", ld_name)));
-    Termination term;
-    Buf *out_stderr = buf_alloc();
-    Buf *out_stdout = buf_alloc();
-    int err;
-    if ((err = os_exec_process(cc_exe, args, &term, out_stderr, out_stdout))) {
-        return nullptr;
-    }
-    if (term.how != TerminationIdClean || term.code != 0) {
-        return nullptr;
-    }
-    if (buf_ends_with_str(out_stdout, "\n")) {
-        buf_resize(out_stdout, buf_len(out_stdout) - 1);
-    }
-    if (buf_len(out_stdout) == 0 || buf_eql_str(out_stdout, ld_name)) {
-        return nullptr;
-    }
-    return out_stdout;
-}
-
-static Buf *get_dynamic_linker_path(CodeGen *g) {
-    if (g->zig_target.os == OsFreeBSD) {
-        return buf_create_from_str("/libexec/ld-elf.so.1");
-    }
-    if (g->zig_target.os == OsNetBSD) {
-        return buf_create_from_str("/libexec/ld.elf_so");
-    }
-    if (g->is_native_target && g->zig_target.arch.arch == ZigLLVM_x86_64) {
-        static const char *ld_names[] = {
-            "ld-linux-x86-64.so.2",
-            "ld-musl-x86_64.so.1",
-        };
-        for (size_t i = 0; i < array_length(ld_names); i += 1) {
-            const char *ld_name = ld_names[i];
-            Buf *result = try_dynamic_linker_path(ld_name);
-            if (result != nullptr) {
-                return result;
-            }
-        }
-    }
-    return target_dynamic_linker(&g->zig_target);
-}
-
 static void construct_linker_job_elf(LinkJob *lj) {
     CodeGen *g = lj->codegen;
 
     lj->args.append("-error-limit=0");
 
-    if (g->libc_link_lib != nullptr) {
-        find_libc_lib_path(g);
-    }
-
     if (g->linker_script) {
         lj->args.append("-T");
         lj->args.append(g->linker_script);
@@ -235,14 +185,14 @@ static void construct_linker_job_elf(LinkJob *lj) {
     lj->args.append("--gc-sections");
 
     lj->args.append("-m");
-    lj->args.append(getLDMOption(&g->zig_target));
+    lj->args.append(getLDMOption(g->zig_target));
 
     bool is_lib = g->out_type == OutTypeLib;
     bool shared = !g->is_static && is_lib;
     Buf *soname = nullptr;
     if (g->is_static) {
-        if (g->zig_target.arch.arch == ZigLLVM_arm || g->zig_target.arch.arch == ZigLLVM_armeb ||
-            g->zig_target.arch.arch == ZigLLVM_thumb || g->zig_target.arch.arch == ZigLLVM_thumbeb)
+        if (g->zig_target->arch.arch == ZigLLVM_arm || g->zig_target->arch.arch == ZigLLVM_armeb ||
+            g->zig_target->arch.arch == ZigLLVM_thumb || g->zig_target->arch.arch == ZigLLVM_thumbeb)
         {
             lj->args.append("-Bstatic");
         } else {
@@ -264,13 +214,13 @@ static void construct_linker_job_elf(LinkJob *lj) {
     if (lj->link_in_crt) {
         const char *crt1o;
         const char *crtbegino;
-        if (g->zig_target.os == OsNetBSD) {
-	    crt1o = "crt0.o";
-	    crtbegino = "crtbegin.o";
-	} else if (g->is_static) {
+        if (g->zig_target->os == OsNetBSD) {
+            crt1o = "crt0.o";
+            crtbegino = "crtbegin.o";
+        } else if (g->is_static) {
             crt1o = "crt1.o";
             crtbegino = "crtbeginT.o";
-	} else {
+        } else {
             crt1o = "Scrt1.o";
             crtbegino = "crtbegin.o";
         }
@@ -311,23 +261,19 @@ static void construct_linker_job_elf(LinkJob *lj) {
     }
 
     if (g->libc_link_lib != nullptr) {
+        assert(g->libc != nullptr);
         lj->args.append("-L");
-        lj->args.append(buf_ptr(g->libc_lib_dir));
+        lj->args.append(buf_ptr(&g->libc->lib_dir));
 
         lj->args.append("-L");
-        lj->args.append(buf_ptr(g->libc_static_lib_dir));
-    }
+        lj->args.append(buf_ptr(&g->libc->static_lib_dir));
 
-    if (!g->is_static) {
-        if (g->dynamic_linker != nullptr) {
-            assert(buf_len(g->dynamic_linker) != 0);
-            lj->args.append("-dynamic-linker");
-            lj->args.append(buf_ptr(g->dynamic_linker));
-        } else {
-            Buf *resolved_dynamic_linker = get_dynamic_linker_path(g);
+        if (!g->is_static) {
+            assert(buf_len(&g->libc->dynamic_linker_path) != 0);
             lj->args.append("-dynamic-linker");
-            lj->args.append(buf_ptr(resolved_dynamic_linker));
+            lj->args.append(buf_ptr(&g->libc->dynamic_linker_path));
         }
+
     }
 
     if (shared) {
@@ -397,11 +343,11 @@ static void construct_linker_job_elf(LinkJob *lj) {
         lj->args.append(get_libc_file(g, "crtn.o"));
     }
 
-    if (!g->is_native_target) {
+    if (!g->zig_target->is_native) {
         lj->args.append("--allow-shlib-undefined");
     }
 
-    if (g->zig_target.os == OsZen) {
+    if (g->zig_target->os == OsZen) {
         lj->args.append("-e");
         lj->args.append("_start");
 
@@ -429,11 +375,11 @@ static void construct_linker_job_wasm(LinkJob *lj) {
 //}
 
 static void coff_append_machine_arg(CodeGen *g, ZigList<const char *> *list) {
-    if (g->zig_target.arch.arch == ZigLLVM_x86) {
+    if (g->zig_target->arch.arch == ZigLLVM_x86) {
         list->append("-MACHINE:X86");
-    } else if (g->zig_target.arch.arch == ZigLLVM_x86_64) {
+    } else if (g->zig_target->arch.arch == ZigLLVM_x86_64) {
         list->append("-MACHINE:X64");
-    } else if (g->zig_target.arch.arch == ZigLLVM_arm) {
+    } else if (g->zig_target->arch.arch == ZigLLVM_arm) {
         list->append("-MACHINE:ARM");
     }
 }
@@ -592,10 +538,6 @@ static void construct_linker_job_coff(LinkJob *lj) {
 
     lj->args.append("/ERRORLIMIT:0");
 
-    if (g->libc_link_lib != nullptr) {
-        find_libc_lib_path(g);
-    }
-
     lj->args.append("/NOLOGO");
 
     if (!g->strip_debug_symbols) {
@@ -650,13 +592,11 @@ static void construct_linker_job_coff(LinkJob *lj) {
     lj->args.append(buf_ptr(buf_sprintf("-OUT:%s", buf_ptr(&g->output_file_path))));
 
     if (g->libc_link_lib != nullptr) {
-        lj->args.append(buf_ptr(buf_sprintf("-LIBPATH:%s", buf_ptr(g->msvc_lib_dir))));
-        lj->args.append(buf_ptr(buf_sprintf("-LIBPATH:%s", buf_ptr(g->kernel32_lib_dir))));
+        assert(g->libc != nullptr);
 
-        lj->args.append(buf_ptr(buf_sprintf("-LIBPATH:%s", buf_ptr(g->libc_lib_dir))));
-        if (g->libc_static_lib_dir != nullptr) {
-            lj->args.append(buf_ptr(buf_sprintf("-LIBPATH:%s", buf_ptr(g->libc_static_lib_dir))));
-        }
+        lj->args.append(buf_ptr(buf_sprintf("-LIBPATH:%s", buf_ptr(&g->libc->msvc_lib_dir))));
+        lj->args.append(buf_ptr(buf_sprintf("-LIBPATH:%s", buf_ptr(&g->libc->kernel32_lib_dir))));
+        lj->args.append(buf_ptr(buf_sprintf("-LIBPATH:%s", buf_ptr(&g->libc->lib_dir))));
     }
 
     if (is_library && !g->is_static) {
@@ -691,7 +631,7 @@ static void construct_linker_job_coff(LinkJob *lj) {
             continue;
         }
         if (link_lib->provided_explicitly) {
-            if (lj->codegen->zig_target.env_type == ZigLLVM_GNU) {
+            if (lj->codegen->zig_target->env_type == ZigLLVM_GNU) {
                 Buf *arg = buf_sprintf("-l%s", buf_ptr(link_lib->name));
                 lj->args.append(buf_ptr(arg));
             }
@@ -721,7 +661,8 @@ static void construct_linker_job_coff(LinkJob *lj) {
             gen_lib_args.append(buf_ptr(buf_sprintf("-DEF:%s", buf_ptr(def_path))));
             gen_lib_args.append(buf_ptr(buf_sprintf("-OUT:%s", buf_ptr(generated_lib_path))));
             Buf diag = BUF_INIT;
-            if (!zig_lld_link(g->zig_target.oformat, gen_lib_args.items, gen_lib_args.length, &diag)) {
+            ZigLLVM_ObjectFormatType target_ofmt = target_object_format(g->zig_target);
+            if (!zig_lld_link(target_ofmt, gen_lib_args.items, gen_lib_args.length, &diag)) {
                 fprintf(stderr, "%s\n", buf_ptr(&diag));
                 exit(1);
             }
@@ -790,7 +731,7 @@ static void get_darwin_platform(LinkJob *lj, DarwinPlatform *platform) {
         platform->kind = MacOS;
     } else if (g->mios_version_min) {
         platform->kind = IPhoneOS;
-    } else if (g->zig_target.os == OsMacOSX) {
+    } else if (g->zig_target->os == OsMacOSX) {
         platform->kind = MacOS;
         g->mmacosx_version_min = buf_create_from_str("10.10");
     } else {
@@ -817,8 +758,8 @@ static void get_darwin_platform(LinkJob *lj, DarwinPlatform *platform) {
     }
 
     if (platform->kind == IPhoneOS &&
-        (g->zig_target.arch.arch == ZigLLVM_x86 ||
-         g->zig_target.arch.arch == ZigLLVM_x86_64))
+        (g->zig_target->arch.arch == ZigLLVM_x86 ||
+         g->zig_target->arch.arch == ZigLLVM_x86_64))
     {
         platform->kind = IPhoneOSSimulator;
     }
@@ -882,7 +823,7 @@ static void construct_linker_job_macho(LinkJob *lj) {
     }
 
     lj->args.append("-arch");
-    lj->args.append(get_darwin_arch_string(&g->zig_target));
+    lj->args.append(get_darwin_arch_string(g->zig_target));
 
     DarwinPlatform platform;
     get_darwin_platform(lj, &platform);
@@ -939,7 +880,7 @@ static void construct_linker_job_macho(LinkJob *lj) {
                 }
                 break;
             case IPhoneOS:
-                if (g->zig_target.arch.arch == ZigLLVM_aarch64) {
+                if (g->zig_target->arch.arch == ZigLLVM_aarch64) {
                     // iOS does not need any crt1 files for arm64
                 } else if (darwin_version_lt(&platform, 3, 1)) {
                     lj->args.append("-lcrt1.o");
@@ -969,7 +910,7 @@ static void construct_linker_job_macho(LinkJob *lj) {
         lj->args.append(buf_ptr(compiler_rt_o_path));
     }
 
-    if (g->is_native_target) {
+    if (g->zig_target->is_native) {
         for (size_t lib_i = 0; lib_i < g->link_libs_list.length; lib_i += 1) {
             LinkLib *link_lib = g->link_libs_list.at(lib_i);
             if (buf_eql_str(link_lib->name, "c")) {
@@ -1010,7 +951,7 @@ static void construct_linker_job_macho(LinkJob *lj) {
 }
 
 static void construct_linker_job(LinkJob *lj) {
-    switch (lj->codegen->zig_target.oformat) {
+    switch (target_object_format(lj->codegen->zig_target)) {
         case ZigLLVM_UnknownObjectFormat:
             zig_unreachable();
 
@@ -1050,7 +991,7 @@ void codegen_link(CodeGen *g) {
         for (size_t i = 0; i < g->link_objects.length; i += 1) {
             file_names.append((const char *)buf_ptr(g->link_objects.at(i)));
         }
-        ZigLLVM_OSType os_type = get_llvm_os_type(g->zig_target.os);
+        ZigLLVM_OSType os_type = get_llvm_os_type(g->zig_target->os);
         codegen_add_time_event(g, "LLVM Link");
         if (ZigLLVMWriteArchive(buf_ptr(&g->output_file_path), file_names.items, file_names.length, os_type)) {
             fprintf(stderr, "Unable to write archive '%s'\n", buf_ptr(&g->output_file_path));
@@ -1075,7 +1016,7 @@ void codegen_link(CodeGen *g) {
     Buf diag = BUF_INIT;
 
     codegen_add_time_event(g, "LLVM Link");
-    if (g->system_linker_hack && g->zig_target.os == OsMacOSX) {
+    if (g->system_linker_hack && g->zig_target->os == OsMacOSX) {
         Termination term;
         ZigList<const char *> args = {};
         for (size_t i = 1; i < lj.args.length; i += 1) {
@@ -1085,7 +1026,7 @@ void codegen_link(CodeGen *g) {
         if (term.how != TerminationIdClean || term.code != 0) {
             exit(1);
         }
-    } else if (!zig_lld_link(g->zig_target.oformat, lj.args.items, lj.args.length, &diag)) {
+    } else if (!zig_lld_link(target_object_format(g->zig_target), lj.args.items, lj.args.length, &diag)) {
         fprintf(stderr, "%s\n", buf_ptr(&diag));
         exit(1);
     }
src/main.cpp
@@ -13,6 +13,7 @@
 #include "error.hpp"
 #include "os.hpp"
 #include "target.hpp"
+#include "libc_installation.hpp"
 
 #include <stdio.h>
 
@@ -36,6 +37,7 @@ static int print_full_usage(const char *arg0, FILE *file, int return_code) {
         "  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"
+        "  libc [paths_file]            Display native libc paths file or validate one\n"
         "  run [source]                 create executable and run immediately\n"
         "  translate-c [source]         convert c code to zig code\n"
         "  targets                      list available compilation targets\n"
@@ -53,7 +55,7 @@ static int print_full_usage(const char *arg0, FILE *file, int return_code) {
         "  --enable-valgrind            include valgrind client requests release builds\n"
         "  --emit [asm|bin|llvm-ir]     emit a specific file format as compilation output\n"
         "  -ftime-report                print timing diagnostics\n"
-        "  --libc-include-dir [path]    directory where libc stdlib.h resides\n"
+        "  --libc [file]                Provide a file which specifies libc paths\n"
         "  --name [name]                override output name\n"
         "  --output [file]              override destination path\n"
         "  --output-h [file]            generate header file\n"
@@ -82,10 +84,6 @@ static int print_full_usage(const char *arg0, FILE *file, int return_code) {
         "Link Options:\n"
         "  --dynamic-linker [path]      set the path to ld.so\n"
         "  --each-lib-rpath             add rpath for each used dynamic library\n"
-        "  --libc-lib-dir [path]        directory where libc crt1.o resides\n"
-        "  --libc-static-lib-dir [path] directory where libc crtbegin.o resides\n"
-        "  --msvc-lib-dir [path]        (windows) directory where vcruntime.lib resides\n"
-        "  --kernel32-lib-dir [path]    (windows) directory where kernel32.lib resides\n"
         "  --library [lib]              link against lib\n"
         "  --forbid-library [lib]       make it an error to link against lib\n"
         "  --library-path [dir]         add a directory to the library search path\n"
@@ -111,6 +109,26 @@ static int print_full_usage(const char *arg0, FILE *file, int return_code) {
     return return_code;
 }
 
+static int print_libc_usage(const char *arg0, FILE *file, int return_code) {
+    fprintf(file,
+        "Usage: %s libc\n"
+        "\n"
+        "Detect the native libc installation and print the resulting paths to stdout.\n"
+        "You can save this into a file and then edit the paths to create a cross\n"
+        "compilation libc kit. Then you can pass `--libc [file]` for Zig to use it.\n"
+        "\n"
+        "When compiling natively and no `--libc` argument provided, Zig automatically\n"
+        "creates zig-cache/native_libc.txt so that it does not have to detect libc\n"
+        "on every invocation. You can remove this file to have Zig re-detect the\n"
+        "native libc.\n"
+        "\n\n"
+        "Usage: %s libc [file]\n"
+        "\n"
+        "Parse a libc installation text file and validate it.\n"
+    , arg0, arg0);
+    return return_code;
+}
+
 static const char *ZIG_ZEN = "\n"
 " * Communicate intent precisely.\n"
 " * Edge cases matter.\n"
@@ -169,6 +187,7 @@ enum Cmd {
     CmdTranslateC,
     CmdVersion,
     CmdZen,
+    CmdLibC,
 };
 
 static const char *default_zig_cache_name = "zig-cache";
@@ -359,12 +378,7 @@ int main(int argc, char **argv) {
     bool verbose_cimport = false;
     ErrColor color = ErrColorAuto;
     CacheOpt enable_cache = CacheOptAuto;
-    const char *libc_lib_dir = nullptr;
-    const char *libc_static_lib_dir = nullptr;
-    const char *libc_include_dir = nullptr;
-    const char *msvc_lib_dir = nullptr;
-    const char *kernel32_lib_dir = nullptr;
-    const char *dynamic_linker = nullptr;
+    const char *libc_txt = nullptr;
     ZigList<const char *> clang_argv = {0};
     ZigList<const char *> llvm_argv = {0};
     ZigList<const char *> lib_dirs = {0};
@@ -434,8 +448,10 @@ int main(int argc, char **argv) {
         Buf *build_runner_path = buf_alloc();
         os_path_join(get_zig_special_dir(), buf_create_from_str("build_runner.zig"), build_runner_path);
 
-        CodeGen *g = codegen_create(build_runner_path, nullptr, OutTypeExe, BuildModeDebug, get_zig_lib_dir(),
-                override_std_dir);
+        ZigTarget target;
+        get_native_target(&target);
+        CodeGen *g = codegen_create(build_runner_path, &target, OutTypeExe, BuildModeDebug, get_zig_lib_dir(),
+                override_std_dir, nullptr);
         g->valgrind_support = valgrind_support;
         g->enable_time_report = timing_info;
         buf_init_from_str(&g->cache_dir, cache_dir ? cache_dir : default_zig_cache_name);
@@ -520,10 +536,12 @@ int main(int argc, char **argv) {
         return (term.how == TerminationIdClean) ? term.code : -1;
     } else if (argc >= 2 && strcmp(argv[1], "fmt") == 0) {
         init_all_targets();
+        ZigTarget target;
+        get_native_target(&target);
         Buf *fmt_runner_path = buf_alloc();
         os_path_join(get_zig_special_dir(), buf_create_from_str("fmt_runner.zig"), fmt_runner_path);
-        CodeGen *g = codegen_create(fmt_runner_path, nullptr, OutTypeExe, BuildModeDebug, get_zig_lib_dir(),
-                nullptr);
+        CodeGen *g = codegen_create(fmt_runner_path, &target, OutTypeExe, BuildModeDebug, get_zig_lib_dir(),
+                nullptr, nullptr);
         g->valgrind_support = valgrind_support;
         g->is_single_threaded = true;
         codegen_set_out_name(g, buf_create_from_str("fmt"));
@@ -557,7 +575,11 @@ int main(int argc, char **argv) {
             } else if (strcmp(arg, "--release-small") == 0) {
                 build_mode = BuildModeSmallRelease;
             } else if (strcmp(arg, "--help") == 0) {
-                return print_full_usage(arg0, stderr, EXIT_FAILURE);
+                if (cmd == CmdLibC) {
+                    return print_libc_usage(arg0, stderr, EXIT_FAILURE);
+                } else {
+                    return print_full_usage(arg0, stderr, EXIT_FAILURE);
+                }
             } else if (strcmp(arg, "--strip") == 0) {
                 strip = true;
             } else if (strcmp(arg, "--static") == 0) {
@@ -658,18 +680,8 @@ int main(int argc, char **argv) {
                     }
                 } else if (strcmp(arg, "--name") == 0) {
                     out_name = argv[i];
-                } else if (strcmp(arg, "--libc-lib-dir") == 0) {
-                    libc_lib_dir = argv[i];
-                } else if (strcmp(arg, "--libc-static-lib-dir") == 0) {
-                    libc_static_lib_dir = argv[i];
-                } else if (strcmp(arg, "--libc-include-dir") == 0) {
-                    libc_include_dir = argv[i];
-                } else if (strcmp(arg, "--msvc-lib-dir") == 0) {
-                    msvc_lib_dir = argv[i];
-                } else if (strcmp(arg, "--kernel32-lib-dir") == 0) {
-                    kernel32_lib_dir = argv[i];
-                } else if (strcmp(arg, "--dynamic-linker") == 0) {
-                    dynamic_linker = argv[i];
+                } else if (strcmp(arg, "--libc") == 0) {
+                    libc_txt = argv[i];
                 } else if (strcmp(arg, "-isystem") == 0) {
                     clang_argv.append("-isystem");
                     clang_argv.append(argv[i]);
@@ -778,6 +790,8 @@ int main(int argc, char **argv) {
                 cmd = CmdVersion;
             } else if (strcmp(arg, "zen") == 0) {
                 cmd = CmdZen;
+            } else if (strcmp(arg, "libc") == 0) {
+                cmd = CmdLibC;
             } else if (strcmp(arg, "translate-c") == 0) {
                 cmd = CmdTranslateC;
             } else if (strcmp(arg, "test") == 0) {
@@ -797,6 +811,7 @@ int main(int argc, char **argv) {
                 case CmdRun:
                 case CmdTranslateC:
                 case CmdTest:
+                case CmdLibC:
                     if (!in_file) {
                         in_file = arg;
                         if (cmd == CmdRun) {
@@ -828,27 +843,25 @@ int main(int argc, char **argv) {
 
     init_all_targets();
 
-    ZigTarget alloc_target;
-    ZigTarget *target;
+    ZigTarget target;
     if (!target_arch && !target_os && !target_environ) {
-        target = nullptr;
+        get_native_target(&target);
     } else {
-        target = &alloc_target;
-        get_unknown_target(target);
+        get_unknown_target(&target);
         if (target_arch) {
-            if (parse_target_arch(target_arch, &target->arch)) {
+            if (parse_target_arch(target_arch, &target.arch)) {
                 fprintf(stderr, "invalid --target-arch argument\n");
                 return print_error_usage(arg0);
             }
         }
         if (target_os) {
-            if (parse_target_os(target_os, &target->os)) {
+            if (parse_target_os(target_os, &target.os)) {
                 fprintf(stderr, "invalid --target-os argument\n");
                 return print_error_usage(arg0);
             }
         }
         if (target_environ) {
-            if (parse_target_environ(target_environ, &target->env_type)) {
+            if (parse_target_environ(target_environ, &target.env_type)) {
                 fprintf(stderr, "invalid --target-environ argument\n");
                 return print_error_usage(arg0);
             }
@@ -856,8 +869,22 @@ int main(int argc, char **argv) {
     }
 
     switch (cmd) {
+    case CmdLibC: {
+        if (in_file) {
+            ZigLibCInstallation libc;
+            if ((err = zig_libc_parse(&libc, buf_create_from_str(in_file), &target, true)))
+                return EXIT_FAILURE;
+            return EXIT_SUCCESS;
+        }
+        ZigLibCInstallation libc;
+        if ((err = zig_libc_find_native(&libc, true)))
+            return EXIT_FAILURE;
+        zig_libc_render(&libc, stdout);
+        return EXIT_SUCCESS;
+    }
     case CmdBuiltin: {
-        CodeGen *g = codegen_create(nullptr, target, out_type, build_mode, get_zig_lib_dir(), override_std_dir);
+        CodeGen *g = codegen_create(nullptr, &target, out_type, build_mode, get_zig_lib_dir(), override_std_dir,
+                nullptr);
         g->valgrind_support = valgrind_support;
         g->is_single_threaded = is_single_threaded;
         Buf *builtin_source = codegen_generate_builtin_source(g);
@@ -917,8 +944,16 @@ int main(int argc, char **argv) {
             if (cmd == CmdRun && buf_out_name == nullptr) {
                 buf_out_name = buf_create_from_str("run");
             }
-            CodeGen *g = codegen_create(zig_root_source_file, target, out_type, build_mode, get_zig_lib_dir(),
-                    override_std_dir);
+            ZigLibCInstallation *libc = nullptr;
+            if (libc_txt != nullptr) {
+                libc = allocate<ZigLibCInstallation>(1);
+                if ((err = zig_libc_parse(libc, buf_create_from_str(libc_txt), &target, true))) {
+                    fprintf(stderr, "Unable to parse --libc text file: %s\n", err_str(err));
+                    return EXIT_FAILURE;
+                }
+            }
+            CodeGen *g = codegen_create(zig_root_source_file, &target, out_type, build_mode, get_zig_lib_dir(),
+                    override_std_dir, libc);
             g->valgrind_support = valgrind_support;
             g->subsystem = subsystem;
 
@@ -944,18 +979,6 @@ int main(int argc, char **argv) {
             codegen_set_llvm_argv(g, llvm_argv.items, llvm_argv.length);
             codegen_set_strip(g, strip);
             codegen_set_is_static(g, is_static);
-            if (libc_lib_dir)
-                codegen_set_libc_lib_dir(g, buf_create_from_str(libc_lib_dir));
-            if (libc_static_lib_dir)
-                codegen_set_libc_static_lib_dir(g, buf_create_from_str(libc_static_lib_dir));
-            if (libc_include_dir)
-                codegen_set_libc_include_dir(g, buf_create_from_str(libc_include_dir));
-            if (msvc_lib_dir)
-                codegen_set_msvc_lib_dir(g, buf_create_from_str(msvc_lib_dir));
-            if (kernel32_lib_dir)
-                codegen_set_kernel32_lib_dir(g, buf_create_from_str(kernel32_lib_dir));
-            if (dynamic_linker)
-                codegen_set_dynamic_linker(g, buf_create_from_str(dynamic_linker));
             g->verbose_tokenize = verbose_tokenize;
             g->verbose_ast = verbose_ast;
             g->verbose_link = verbose_link;
@@ -1086,7 +1109,7 @@ int main(int argc, char **argv) {
                     }
                 }
 
-                if (!target_can_exec(&native, target)) {
+                if (!target_can_exec(&native, &target)) {
                     fprintf(stderr, "Created %s but skipping execution because it is non-native.\n",
                             buf_ptr(test_exe_path));
                     return 0;
src/os.cpp
@@ -1550,7 +1550,7 @@ void os_stderr_set_color(TermColor color) {
 #endif
 }
 
-int os_get_win32_ucrt_lib_path(ZigWindowsSDK *sdk, Buf* output_buf, ZigLLVM_ArchType platform_type) {
+Error os_get_win32_ucrt_lib_path(ZigWindowsSDK *sdk, Buf* output_buf, ZigLLVM_ArchType platform_type) {
 #if defined(ZIG_OS_WINDOWS)
     buf_resize(output_buf, 0);
     buf_appendf(output_buf, "%s\\Lib\\%s\\ucrt\\", sdk->path10_ptr, sdk->version10_ptr);
@@ -1571,7 +1571,7 @@ int os_get_win32_ucrt_lib_path(ZigWindowsSDK *sdk, Buf* output_buf, ZigLLVM_Arch
     buf_init_from_buf(tmp_buf, output_buf);
     buf_append_str(tmp_buf, "ucrt.lib");
     if (GetFileAttributesA(buf_ptr(tmp_buf)) != INVALID_FILE_ATTRIBUTES) {
-        return 0;
+        return ErrorNone;
     }
     else {
         buf_resize(output_buf, 0);
@@ -1582,12 +1582,12 @@ int os_get_win32_ucrt_lib_path(ZigWindowsSDK *sdk, Buf* output_buf, ZigLLVM_Arch
 #endif
 }
 
-int os_get_win32_ucrt_include_path(ZigWindowsSDK *sdk, Buf* output_buf) {
+Error os_get_win32_ucrt_include_path(ZigWindowsSDK *sdk, Buf* output_buf) {
 #if defined(ZIG_OS_WINDOWS)
     buf_resize(output_buf, 0);
     buf_appendf(output_buf, "%s\\Include\\%s\\ucrt", sdk->path10_ptr, sdk->version10_ptr);
     if (GetFileAttributesA(buf_ptr(output_buf)) != INVALID_FILE_ATTRIBUTES) {
-        return 0;
+        return ErrorNone;
     }
     else {
         buf_resize(output_buf, 0);
@@ -1598,7 +1598,7 @@ int os_get_win32_ucrt_include_path(ZigWindowsSDK *sdk, Buf* output_buf) {
 #endif
 }
 
-int os_get_win32_kern32_path(ZigWindowsSDK *sdk, Buf* output_buf, ZigLLVM_ArchType platform_type) {
+Error os_get_win32_kern32_path(ZigWindowsSDK *sdk, Buf* output_buf, ZigLLVM_ArchType platform_type) {
 #if defined(ZIG_OS_WINDOWS)
     {
         buf_resize(output_buf, 0);
@@ -1620,7 +1620,7 @@ int os_get_win32_kern32_path(ZigWindowsSDK *sdk, Buf* output_buf, ZigLLVM_ArchTy
         buf_init_from_buf(tmp_buf, output_buf);
         buf_append_str(tmp_buf, "kernel32.lib");
         if (GetFileAttributesA(buf_ptr(tmp_buf)) != INVALID_FILE_ATTRIBUTES) {
-            return 0;
+            return ErrorNone;
         }
     }
     {
@@ -1643,7 +1643,7 @@ int os_get_win32_kern32_path(ZigWindowsSDK *sdk, Buf* output_buf, ZigLLVM_ArchTy
         buf_init_from_buf(tmp_buf, output_buf);
         buf_append_str(tmp_buf, "kernel32.lib");
         if (GetFileAttributesA(buf_ptr(tmp_buf)) != INVALID_FILE_ATTRIBUTES) {
-            return 0;
+            return ErrorNone;
         }
     }
     return ErrorFileNotFound;
src/os.hpp
@@ -135,9 +135,9 @@ Error ATTRIBUTE_MUST_USE os_self_exe_path(Buf *out_path);
 
 Error ATTRIBUTE_MUST_USE os_get_app_data_dir(Buf *out_path, const char *appname);
 
-int os_get_win32_ucrt_include_path(ZigWindowsSDK *sdk, Buf *output_buf);
-int os_get_win32_ucrt_lib_path(ZigWindowsSDK *sdk, Buf *output_buf, ZigLLVM_ArchType platform_type);
-int os_get_win32_kern32_path(ZigWindowsSDK *sdk, Buf *output_buf, ZigLLVM_ArchType platform_type);
+Error ATTRIBUTE_MUST_USE os_get_win32_ucrt_include_path(ZigWindowsSDK *sdk, Buf *output_buf);
+Error ATTRIBUTE_MUST_USE os_get_win32_ucrt_lib_path(ZigWindowsSDK *sdk, Buf *output_buf, ZigLLVM_ArchType platform_type);
+Error ATTRIBUTE_MUST_USE os_get_win32_kern32_path(ZigWindowsSDK *sdk, Buf *output_buf, ZigLLVM_ArchType platform_type);
 
 Error ATTRIBUTE_MUST_USE os_self_exe_shared_libs(ZigList<Buf *> &paths);
 
src/target.cpp
@@ -214,7 +214,7 @@ size_t target_oformat_count(void) {
     return array_length(oformat_list);
 }
 
-const ZigLLVM_ObjectFormatType get_target_oformat(size_t index) {
+ZigLLVM_ObjectFormatType get_target_oformat(size_t index) {
     return oformat_list[index];
 }
 
@@ -443,14 +443,16 @@ ZigLLVM_EnvironmentType get_target_environ(size_t index) {
 
 void get_native_target(ZigTarget *target) {
     ZigLLVM_OSType os_type;
+    ZigLLVM_ObjectFormatType oformat; // ignored; based on arch/os
     ZigLLVMGetNativeTarget(
             &target->arch.arch,
             &target->arch.sub_arch,
             &target->vendor,
             &os_type,
             &target->env_type,
-            &target->oformat);
+            &oformat);
     target->os = get_zig_os_type(os_type);
+    target->is_native = true;
 }
 
 void get_unknown_target(ZigTarget *target) {
@@ -459,7 +461,7 @@ void get_unknown_target(ZigTarget *target) {
     target->vendor = ZigLLVM_UnknownVendor;
     target->os = OsFreestanding;
     target->env_type = ZigLLVM_UnknownEnvironment;
-    target->oformat = ZigLLVM_UnknownObjectFormat;
+    target->is_native = false;
 }
 
 static void get_arch_name_raw(char *out_str, ZigLLVM_ArchType arch, ZigLLVM_SubArchType sub_arch) {
@@ -554,85 +556,18 @@ bool target_is_darwin(const ZigTarget *target) {
     }
 }
 
-void resolve_target_object_format(ZigTarget *target) {
-    if (target->oformat != ZigLLVM_UnknownObjectFormat) {
-        return;
+ZigLLVM_ObjectFormatType target_object_format(const ZigTarget *target) {
+    if (target->os == OsUefi || target->os == OsWindows) {
+        return ZigLLVM_COFF;
+    } else if (target_is_darwin(target)) {
+        return ZigLLVM_MachO;
     }
-
-    switch (target->arch.arch) {
-        case ZigLLVM_UnknownArch:
-        case ZigLLVM_aarch64:
-        case ZigLLVM_arm:
-        case ZigLLVM_thumb:
-        case ZigLLVM_x86:
-        case ZigLLVM_x86_64:
-            if (target_is_darwin(target)) {
-                target->oformat = ZigLLVM_MachO;
-            } else if (target->os == OsWindows) {
-                target->oformat = ZigLLVM_COFF;
-            } else {
-                target->oformat = ZigLLVM_ELF;
-            }
-            return;
-
-        case ZigLLVM_aarch64_be:
-        case ZigLLVM_amdgcn:
-        case ZigLLVM_amdil:
-        case ZigLLVM_amdil64:
-        case ZigLLVM_armeb:
-        case ZigLLVM_arc:
-        case ZigLLVM_avr:
-        case ZigLLVM_bpfeb:
-        case ZigLLVM_bpfel:
-        case ZigLLVM_hexagon:
-        case ZigLLVM_lanai:
-        case ZigLLVM_hsail:
-        case ZigLLVM_hsail64:
-        case ZigLLVM_kalimba:
-        case ZigLLVM_le32:
-        case ZigLLVM_le64:
-        case ZigLLVM_mips:
-        case ZigLLVM_mips64:
-        case ZigLLVM_mips64el:
-        case ZigLLVM_mipsel:
-        case ZigLLVM_msp430:
-        case ZigLLVM_nios2:
-        case ZigLLVM_nvptx:
-        case ZigLLVM_nvptx64:
-        case ZigLLVM_ppc64le:
-        case ZigLLVM_r600:
-        case ZigLLVM_renderscript32:
-        case ZigLLVM_renderscript64:
-        case ZigLLVM_riscv32:
-        case ZigLLVM_riscv64:
-        case ZigLLVM_shave:
-        case ZigLLVM_sparc:
-        case ZigLLVM_sparcel:
-        case ZigLLVM_sparcv9:
-        case ZigLLVM_spir:
-        case ZigLLVM_spir64:
-        case ZigLLVM_systemz:
-        case ZigLLVM_tce:
-        case ZigLLVM_tcele:
-        case ZigLLVM_thumbeb:
-        case ZigLLVM_xcore:
-            target->oformat= ZigLLVM_ELF;
-            return;
-
-        case ZigLLVM_wasm32:
-        case ZigLLVM_wasm64:
-            target->oformat = ZigLLVM_Wasm;
-            return;
-
-        case ZigLLVM_ppc:
-        case ZigLLVM_ppc64:
-            if (target_is_darwin(target)) {
-                target->oformat = ZigLLVM_MachO;
-            } else {
-                target->oformat= ZigLLVM_ELF;
-            }
-            return;
+    if (target->arch.arch == ZigLLVM_wasm32 ||
+        target->arch.arch == ZigLLVM_wasm64)
+    {
+        return ZigLLVM_Wasm;
     }
+    return ZigLLVM_ELF;
 }
 
 // See lib/Support/Triple.cpp in LLVM for the source of this data.
@@ -812,7 +747,7 @@ bool target_allows_addr_zero(const ZigTarget *target) {
     return target->os == OsFreestanding;
 }
 
-const char *target_o_file_ext(ZigTarget *target) {
+const char *target_o_file_ext(const ZigTarget *target) {
     if (target->env_type == ZigLLVM_MSVC || target->os == OsWindows || target->os == OsUefi) {
         return ".obj";
     } else {
@@ -820,15 +755,15 @@ const char *target_o_file_ext(ZigTarget *target) {
     }
 }
 
-const char *target_asm_file_ext(ZigTarget *target) {
+const char *target_asm_file_ext(const ZigTarget *target) {
     return ".s";
 }
 
-const char *target_llvm_ir_file_ext(ZigTarget *target) {
+const char *target_llvm_ir_file_ext(const ZigTarget *target) {
     return ".ll";
 }
 
-const char *target_exe_file_ext(ZigTarget *target) {
+const char *target_exe_file_ext(const ZigTarget *target) {
     if (target->os == OsWindows) {
         return ".exe";
     } else if (target->os == OsUefi) {
@@ -838,7 +773,9 @@ const char *target_exe_file_ext(ZigTarget *target) {
     }
 }
 
-const char *target_lib_file_ext(ZigTarget *target, bool is_static, size_t version_major, size_t version_minor, size_t version_patch) {
+const char *target_lib_file_ext(const ZigTarget *target, bool is_static,
+        size_t version_major, size_t version_minor, size_t version_patch)
+{
     if (target->os == OsWindows || target->os == OsUefi) {
         if (is_static) {
             return ".lib";
@@ -860,7 +797,7 @@ enum FloatAbi {
     FloatAbiSoftFp,
 };
 
-static FloatAbi get_float_abi(ZigTarget *target) {
+static FloatAbi get_float_abi(const ZigTarget *target) {
     const ZigLLVM_EnvironmentType env = target->env_type;
     if (env == ZigLLVM_GNUEABIHF ||
         env == ZigLLVM_EABIHF ||
@@ -876,7 +813,14 @@ static bool is_64_bit(ZigLLVM_ArchType arch) {
     return get_arch_pointer_bit_width(arch) == 64;
 }
 
-Buf *target_dynamic_linker(ZigTarget *target) {
+Buf *target_dynamic_linker(const ZigTarget *target) {
+    if (target->os == OsFreeBSD) {
+        return buf_create_from_str("/libexec/ld-elf.so.1");
+    }
+    if (target->os == OsNetBSD) {
+        return buf_create_from_str("/libexec/ld.elf_so");
+    }
+
     const ZigLLVM_ArchType arch = target->arch.arch;
     const ZigLLVM_EnvironmentType env = target->env_type;
 
@@ -1098,3 +1042,10 @@ bool target_has_valgrind_support(const ZigTarget *target) {
     }
     zig_unreachable();
 }
+
+bool target_requires_libc(const ZigTarget *target) {
+    // On Darwin, we always link libSystem which contains libc.
+    // Similarly on FreeBSD and NetBSD we always link system libc
+    // since this is the stable syscall interface.
+    return (target_is_darwin(target) || target->os == OsFreeBSD || target->os == OsNetBSD);
+}
src/target.hpp
@@ -71,7 +71,7 @@ struct ZigTarget {
     ZigLLVM_VendorType vendor;
     Os os;
     ZigLLVM_EnvironmentType env_type;
-    ZigLLVM_ObjectFormatType oformat;
+    bool is_native;
 };
 
 enum CIntType {
@@ -105,8 +105,9 @@ ZigLLVM_EnvironmentType get_target_environ(size_t index);
 
 
 size_t target_oformat_count(void);
-const ZigLLVM_ObjectFormatType get_target_oformat(size_t index);
+ZigLLVM_ObjectFormatType get_target_oformat(size_t index);
 const char *get_target_oformat_name(ZigLLVM_ObjectFormatType oformat);
+ZigLLVM_ObjectFormatType target_object_format(const ZigTarget *target);
 
 void get_native_target(ZigTarget *target);
 void get_unknown_target(ZigTarget *target);
@@ -123,13 +124,14 @@ void resolve_target_object_format(ZigTarget *target);
 
 uint32_t target_c_type_size_in_bits(const ZigTarget *target, CIntType id);
 
-const char *target_o_file_ext(ZigTarget *target);
-const char *target_asm_file_ext(ZigTarget *target);
-const char *target_llvm_ir_file_ext(ZigTarget *target);
-const char *target_exe_file_ext(ZigTarget *target);
-const char *target_lib_file_ext(ZigTarget *target, bool is_static, size_t version_major, size_t version_minor, size_t version_patch);
+const char *target_o_file_ext(const ZigTarget *target);
+const char *target_asm_file_ext(const ZigTarget *target);
+const char *target_llvm_ir_file_ext(const ZigTarget *target);
+const char *target_exe_file_ext(const ZigTarget *target);
+const char *target_lib_file_ext(const ZigTarget *target, bool is_static,
+        size_t version_major, size_t version_minor, size_t version_patch);
 
-Buf *target_dynamic_linker(ZigTarget *target);
+Buf *target_dynamic_linker(const ZigTarget *target);
 
 bool target_can_exec(const ZigTarget *host_target, const ZigTarget *guest_target);
 ZigLLVM_OSType get_llvm_os_type(Os os_type);
@@ -138,5 +140,6 @@ bool target_is_arm(const ZigTarget *target);
 bool target_allows_addr_zero(const ZigTarget *target);
 bool target_has_valgrind_support(const ZigTarget *target);
 bool target_is_darwin(const ZigTarget *target);
+bool target_requires_libc(const ZigTarget *target);
 
 #endif
src/translate_c.cpp
@@ -4776,7 +4776,7 @@ Error parse_h_file(ImportTableEntry *import, ZigList<ErrorMsg *> *errors, const
     clang_argv.append("-x");
     clang_argv.append("c");
 
-    if (c->codegen->is_native_target) {
+    if (c->codegen->zig_target->is_native) {
         char *ZIG_PARSEC_CFLAGS = getenv("ZIG_NATIVE_PARSEC_CFLAGS");
         if (ZIG_PARSEC_CFLAGS) {
             Buf tmp_buf = BUF_INIT;
@@ -4798,9 +4798,9 @@ Error parse_h_file(ImportTableEntry *import, ZigList<ErrorMsg *> *errors, const
     clang_argv.append("-isystem");
     clang_argv.append(buf_ptr(codegen->zig_c_headers_dir));
 
-    if (codegen->libc_include_dir != nullptr) {
+    if (codegen->libc != nullptr) {
         clang_argv.append("-isystem");
-        clang_argv.append(buf_ptr(codegen->libc_include_dir));
+        clang_argv.append(buf_ptr(&codegen->libc->include_dir));
     }
 
     // windows c runtime requires -D_DEBUG if using debug libraries
@@ -4820,7 +4820,7 @@ Error parse_h_file(ImportTableEntry *import, ZigList<ErrorMsg *> *errors, const
     clang_argv.append("-Xclang");
     clang_argv.append("-detailed-preprocessing-record");
 
-    if (!c->codegen->is_native_target) {
+    if (!c->codegen->zig_target->is_native) {
         clang_argv.append("-target");
         clang_argv.append(buf_ptr(&c->codegen->triple_str));
     }
CMakeLists.txt
@@ -414,6 +414,7 @@ set(ZIG_SOURCES
     "${CMAKE_SOURCE_DIR}/src/error.cpp"
     "${CMAKE_SOURCE_DIR}/src/ir.cpp"
     "${CMAKE_SOURCE_DIR}/src/ir_print.cpp"
+    "${CMAKE_SOURCE_DIR}/src/libc_installation.cpp"
     "${CMAKE_SOURCE_DIR}/src/link.cpp"
     "${CMAKE_SOURCE_DIR}/src/main.cpp"
     "${CMAKE_SOURCE_DIR}/src/os.cpp"