Commit 4616af0ca4

Andrew Kelley <andrew@ziglang.org>
2020-02-25 07:52:27
introduce operating system version ranges as part of the target
* re-introduce `std.build.Target` which is distinct from `std.Target`. `std.build.Target` wraps `std.Target` so that it can be annotated as "the native target" or an explicitly specified target. * `std.Target.Os` is moved to `std.Target.Os.Tag`. The former is now a struct which has the tag as well as version range information. * `std.elf` gains some more ELF header constants. * `std.Target.parse` gains the ability to parse operating system version ranges as well as glibc version. * Added `std.Target.isGnuLibC()`. * self-hosted dynamic linker detection and glibc version detection. This also adds the improved logic using `/usr/bin/env` rather than invoking the system C compiler to find the dynamic linker when zig is statically linked. Related: #2084 Note: this `/usr/bin/env` code is work-in-progress. * `-target-glibc` CLI option is removed in favor of the new `-target` syntax. Example: `-target x86_64-linux-gnu.2.27` closes #1907
1 parent fba39ff
lib/std/build/run.zig
@@ -82,7 +82,7 @@ pub const RunStep = struct {
 
         var key: []const u8 = undefined;
         var prev_path: ?[]const u8 = undefined;
-        if (builtin.os == .windows) {
+        if (builtin.os.tag == .windows) {
             key = "Path";
             prev_path = env_map.get(key);
             if (prev_path == null) {
lib/std/c/linux.zig
@@ -94,7 +94,7 @@ pub const pthread_cond_t = extern struct {
     size: [__SIZEOF_PTHREAD_COND_T]u8 align(@alignOf(usize)) = [_]u8{0} ** __SIZEOF_PTHREAD_COND_T,
 };
 const __SIZEOF_PTHREAD_COND_T = 48;
-const __SIZEOF_PTHREAD_MUTEX_T = if (builtin.os == .fuchsia) 40 else switch (builtin.abi) {
+const __SIZEOF_PTHREAD_MUTEX_T = if (builtin.os.tag == .fuchsia) 40 else switch (builtin.abi) {
     .musl, .musleabi, .musleabihf => if (@sizeOf(usize) == 8) 40 else 24,
     .gnu, .gnuabin32, .gnuabi64, .gnueabi, .gnueabihf, .gnux32 => switch (builtin.arch) {
         .aarch64 => 48,
lib/std/event/channel.zig
@@ -273,7 +273,7 @@ test "std.event.Channel" {
     if (builtin.single_threaded) return error.SkipZigTest;
 
     // https://github.com/ziglang/zig/issues/3251
-    if (builtin.os == .freebsd) return error.SkipZigTest;
+    if (builtin.os.tag == .freebsd) return error.SkipZigTest;
 
     var channel: Channel(i32) = undefined;
     channel.init(&[0]i32{});
lib/std/event/future.zig
@@ -86,7 +86,7 @@ test "std.event.Future" {
     // https://github.com/ziglang/zig/issues/1908
     if (builtin.single_threaded) return error.SkipZigTest;
     // https://github.com/ziglang/zig/issues/3251
-    if (builtin.os == .freebsd) return error.SkipZigTest;
+    if (builtin.os.tag == .freebsd) return error.SkipZigTest;
     // TODO provide a way to run tests in evented I/O mode
     if (!std.io.is_async) return error.SkipZigTest;
 
lib/std/event/lock.zig
@@ -123,7 +123,7 @@ test "std.event.Lock" {
     if (builtin.single_threaded) return error.SkipZigTest;
 
     // TODO https://github.com/ziglang/zig/issues/3251
-    if (builtin.os == .freebsd) return error.SkipZigTest;
+    if (builtin.os.tag == .freebsd) return error.SkipZigTest;
 
     var lock = Lock.init();
     defer lock.deinit();
lib/std/event/loop.zig
@@ -34,7 +34,7 @@ pub const Loop = struct {
         handle: anyframe,
         overlapped: Overlapped,
 
-        pub const overlapped_init = switch (builtin.os) {
+        pub const overlapped_init = switch (builtin.os.tag) {
             .windows => windows.OVERLAPPED{
                 .Internal = 0,
                 .InternalHigh = 0,
@@ -52,7 +52,7 @@ pub const Loop = struct {
             EventFd,
         };
 
-        pub const EventFd = switch (builtin.os) {
+        pub const EventFd = switch (builtin.os.tag) {
             .macosx, .freebsd, .netbsd, .dragonfly => KEventFd,
             .linux => struct {
                 base: ResumeNode,
@@ -71,7 +71,7 @@ pub const Loop = struct {
             kevent: os.Kevent,
         };
 
-        pub const Basic = switch (builtin.os) {
+        pub const Basic = switch (builtin.os.tag) {
             .macosx, .freebsd, .netbsd, .dragonfly => KEventBasic,
             .linux => struct {
                 base: ResumeNode,
@@ -173,7 +173,7 @@ pub const Loop = struct {
     const wakeup_bytes = [_]u8{0x1} ** 8;
 
     fn initOsData(self: *Loop, extra_thread_count: usize) InitOsDataError!void {
-        switch (builtin.os) {
+        switch (builtin.os.tag) {
             .linux => {
                 self.os_data.fs_queue = std.atomic.Queue(Request).init();
                 self.os_data.fs_queue_item = 0;
@@ -404,7 +404,7 @@ pub const Loop = struct {
     }
 
     fn deinitOsData(self: *Loop) void {
-        switch (builtin.os) {
+        switch (builtin.os.tag) {
             .linux => {
                 noasync os.close(self.os_data.final_eventfd);
                 while (self.available_eventfd_resume_nodes.pop()) |node| noasync os.close(node.data.eventfd);
@@ -568,7 +568,7 @@ pub const Loop = struct {
             };
             const eventfd_node = &resume_stack_node.data;
             eventfd_node.base.handle = next_tick_node.data;
-            switch (builtin.os) {
+            switch (builtin.os.tag) {
                 .macosx, .freebsd, .netbsd, .dragonfly => {
                     const kevent_array = @as(*const [1]os.Kevent, &eventfd_node.kevent);
                     const empty_kevs = &[0]os.Kevent{};
@@ -628,7 +628,7 @@ pub const Loop = struct {
 
         self.workerRun();
 
-        switch (builtin.os) {
+        switch (builtin.os.tag) {
             .linux,
             .macosx,
             .freebsd,
@@ -678,7 +678,7 @@ pub const Loop = struct {
         const prev = @atomicRmw(usize, &self.pending_event_count, AtomicRmwOp.Sub, 1, AtomicOrder.SeqCst);
         if (prev == 1) {
             // cause all the threads to stop
-            switch (builtin.os) {
+            switch (builtin.os.tag) {
                 .linux => {
                     self.posixFsRequest(&self.os_data.fs_end_request);
                     // writing 8 bytes to an eventfd cannot fail
@@ -902,7 +902,7 @@ pub const Loop = struct {
                 self.finishOneEvent();
             }
 
-            switch (builtin.os) {
+            switch (builtin.os.tag) {
                 .linux => {
                     // only process 1 event so we don't steal from other threads
                     var events: [1]os.linux.epoll_event = undefined;
@@ -989,7 +989,7 @@ pub const Loop = struct {
     fn posixFsRequest(self: *Loop, request_node: *Request.Node) void {
         self.beginOneEvent(); // finished in posixFsRun after processing the msg
         self.os_data.fs_queue.put(request_node);
-        switch (builtin.os) {
+        switch (builtin.os.tag) {
             .macosx, .freebsd, .netbsd, .dragonfly => {
                 const fs_kevs = @as(*const [1]os.Kevent, &self.os_data.fs_kevent_wake);
                 const empty_kevs = &[0]os.Kevent{};
@@ -1018,7 +1018,7 @@ pub const Loop = struct {
     // https://github.com/ziglang/zig/issues/3157
     fn posixFsRun(self: *Loop) void {
         while (true) {
-            if (builtin.os == .linux) {
+            if (builtin.os.tag == .linux) {
                 @atomicStore(i32, &self.os_data.fs_queue_item, 0, .SeqCst);
             }
             while (self.os_data.fs_queue.get()) |node| {
@@ -1053,7 +1053,7 @@ pub const Loop = struct {
                 }
                 self.finishOneEvent();
             }
-            switch (builtin.os) {
+            switch (builtin.os.tag) {
                 .linux => {
                     const rc = os.linux.futex_wait(&self.os_data.fs_queue_item, os.linux.FUTEX_WAIT, 0, null);
                     switch (os.linux.getErrno(rc)) {
@@ -1071,7 +1071,7 @@ pub const Loop = struct {
         }
     }
 
-    const OsData = switch (builtin.os) {
+    const OsData = switch (builtin.os.tag) {
         .linux => LinuxOsData,
         .macosx, .freebsd, .netbsd, .dragonfly => KEventData,
         .windows => struct {
lib/std/fs/file.zig
@@ -29,7 +29,7 @@ pub const File = struct {
 
     pub const Mode = os.mode_t;
 
-    pub const default_mode = switch (builtin.os) {
+    pub const default_mode = switch (builtin.os.tag) {
         .windows => 0,
         else => 0o666,
     };
@@ -83,7 +83,7 @@ pub const File = struct {
 
     /// Test whether ANSI escape codes will be treated as such.
     pub fn supportsAnsiEscapeCodes(self: File) bool {
-        if (builtin.os == .windows) {
+        if (builtin.os.tag == .windows) {
             return os.isCygwinPty(self.handle);
         }
         if (self.isTty()) {
@@ -128,7 +128,7 @@ pub const File = struct {
 
     /// TODO: integrate with async I/O
     pub fn getEndPos(self: File) GetPosError!u64 {
-        if (builtin.os == .windows) {
+        if (builtin.os.tag == .windows) {
             return windows.GetFileSizeEx(self.handle);
         }
         return (try self.stat()).size;
@@ -138,7 +138,7 @@ pub const File = struct {
 
     /// TODO: integrate with async I/O
     pub fn mode(self: File) ModeError!Mode {
-        if (builtin.os == .windows) {
+        if (builtin.os.tag == .windows) {
             return {};
         }
         return (try self.stat()).mode;
@@ -162,7 +162,7 @@ pub const File = struct {
 
     /// TODO: integrate with async I/O
     pub fn stat(self: File) StatError!Stat {
-        if (builtin.os == .windows) {
+        if (builtin.os.tag == .windows) {
             var io_status_block: windows.IO_STATUS_BLOCK = undefined;
             var info: windows.FILE_ALL_INFORMATION = undefined;
             const rc = windows.ntdll.NtQueryInformationFile(self.handle, &io_status_block, &info, @sizeOf(windows.FILE_ALL_INFORMATION), .FileAllInformation);
@@ -209,7 +209,7 @@ pub const File = struct {
         /// last modification timestamp in nanoseconds
         mtime: i64,
     ) UpdateTimesError!void {
-        if (builtin.os == .windows) {
+        if (builtin.os.tag == .windows) {
             const atime_ft = windows.nanoSecondsToFileTime(atime);
             const mtime_ft = windows.nanoSecondsToFileTime(mtime);
             return windows.SetFileTime(self.handle, null, &atime_ft, &mtime_ft);
lib/std/fs/get_app_data_dir.zig
@@ -13,7 +13,7 @@ pub const GetAppDataDirError = error{
 /// Caller owns returned memory.
 /// TODO determine if we can remove the allocator requirement
 pub fn getAppDataDir(allocator: *mem.Allocator, appname: []const u8) GetAppDataDirError![]u8 {
-    switch (builtin.os) {
+    switch (builtin.os.tag) {
         .windows => {
             var dir_path_ptr: [*:0]u16 = undefined;
             switch (os.windows.shell32.SHGetKnownFolderPath(
lib/std/fs/path.zig
@@ -13,18 +13,18 @@ const process = std.process;
 
 pub const sep_windows = '\\';
 pub const sep_posix = '/';
-pub const sep = if (builtin.os == .windows) sep_windows else sep_posix;
+pub const sep = if (builtin.os.tag == .windows) sep_windows else sep_posix;
 
 pub const sep_str_windows = "\\";
 pub const sep_str_posix = "/";
-pub const sep_str = if (builtin.os == .windows) sep_str_windows else sep_str_posix;
+pub const sep_str = if (builtin.os.tag == .windows) sep_str_windows else sep_str_posix;
 
 pub const delimiter_windows = ';';
 pub const delimiter_posix = ':';
-pub const delimiter = if (builtin.os == .windows) delimiter_windows else delimiter_posix;
+pub const delimiter = if (builtin.os.tag == .windows) delimiter_windows else delimiter_posix;
 
 pub fn isSep(byte: u8) bool {
-    if (builtin.os == .windows) {
+    if (builtin.os.tag == .windows) {
         return byte == '/' or byte == '\\';
     } else {
         return byte == '/';
@@ -74,7 +74,7 @@ fn joinSep(allocator: *Allocator, separator: u8, paths: []const []const u8) ![]u
     return buf;
 }
 
-pub const join = if (builtin.os == .windows) joinWindows else joinPosix;
+pub const join = if (builtin.os.tag == .windows) joinWindows else joinPosix;
 
 /// Naively combines a series of paths with the native path seperator.
 /// Allocates memory for the result, which must be freed by the caller.
@@ -129,7 +129,7 @@ test "join" {
 }
 
 pub fn isAbsoluteC(path_c: [*:0]const u8) bool {
-    if (builtin.os == .windows) {
+    if (builtin.os.tag == .windows) {
         return isAbsoluteWindowsC(path_c);
     } else {
         return isAbsolutePosixC(path_c);
@@ -137,7 +137,7 @@ pub fn isAbsoluteC(path_c: [*:0]const u8) bool {
 }
 
 pub fn isAbsolute(path: []const u8) bool {
-    if (builtin.os == .windows) {
+    if (builtin.os.tag == .windows) {
         return isAbsoluteWindows(path);
     } else {
         return isAbsolutePosix(path);
@@ -318,7 +318,7 @@ test "windowsParsePath" {
 }
 
 pub fn diskDesignator(path: []const u8) []const u8 {
-    if (builtin.os == .windows) {
+    if (builtin.os.tag == .windows) {
         return diskDesignatorWindows(path);
     } else {
         return "";
@@ -383,7 +383,7 @@ fn asciiEqlIgnoreCase(s1: []const u8, s2: []const u8) bool {
 
 /// On Windows, this calls `resolveWindows` and on POSIX it calls `resolvePosix`.
 pub fn resolve(allocator: *Allocator, paths: []const []const u8) ![]u8 {
-    if (builtin.os == .windows) {
+    if (builtin.os.tag == .windows) {
         return resolveWindows(allocator, paths);
     } else {
         return resolvePosix(allocator, paths);
@@ -400,7 +400,7 @@ pub fn resolve(allocator: *Allocator, paths: []const []const u8) ![]u8 {
 /// Without performing actual syscalls, resolving `..` could be incorrect.
 pub fn resolveWindows(allocator: *Allocator, paths: []const []const u8) ![]u8 {
     if (paths.len == 0) {
-        assert(builtin.os == .windows); // resolveWindows called on non windows can't use getCwd
+        assert(builtin.os.tag == .windows); // resolveWindows called on non windows can't use getCwd
         return process.getCwdAlloc(allocator);
     }
 
@@ -495,7 +495,7 @@ pub fn resolveWindows(allocator: *Allocator, paths: []const []const u8) ![]u8 {
                 result_disk_designator = result[0..result_index];
             },
             WindowsPath.Kind.None => {
-                assert(builtin.os == .windows); // resolveWindows called on non windows can't use getCwd
+                assert(builtin.os.tag == .windows); // resolveWindows called on non windows can't use getCwd
                 const cwd = try process.getCwdAlloc(allocator);
                 defer allocator.free(cwd);
                 const parsed_cwd = windowsParsePath(cwd);
@@ -510,7 +510,7 @@ pub fn resolveWindows(allocator: *Allocator, paths: []const []const u8) ![]u8 {
             },
         }
     } else {
-        assert(builtin.os == .windows); // resolveWindows called on non windows can't use getCwd
+        assert(builtin.os.tag == .windows); // resolveWindows called on non windows can't use getCwd
         // TODO call get cwd for the result_disk_designator instead of the global one
         const cwd = try process.getCwdAlloc(allocator);
         defer allocator.free(cwd);
@@ -581,7 +581,7 @@ pub fn resolveWindows(allocator: *Allocator, paths: []const []const u8) ![]u8 {
 /// Without performing actual syscalls, resolving `..` could be incorrect.
 pub fn resolvePosix(allocator: *Allocator, paths: []const []const u8) ![]u8 {
     if (paths.len == 0) {
-        assert(builtin.os != .windows); // resolvePosix called on windows can't use getCwd
+        assert(builtin.os.tag != .windows); // resolvePosix called on windows can't use getCwd
         return process.getCwdAlloc(allocator);
     }
 
@@ -603,7 +603,7 @@ pub fn resolvePosix(allocator: *Allocator, paths: []const []const u8) ![]u8 {
     if (have_abs) {
         result = try allocator.alloc(u8, max_size);
     } else {
-        assert(builtin.os != .windows); // resolvePosix called on windows can't use getCwd
+        assert(builtin.os.tag != .windows); // resolvePosix called on windows can't use getCwd
         const cwd = try process.getCwdAlloc(allocator);
         defer allocator.free(cwd);
         result = try allocator.alloc(u8, max_size + cwd.len + 1);
@@ -645,7 +645,7 @@ pub fn resolvePosix(allocator: *Allocator, paths: []const []const u8) ![]u8 {
 test "resolve" {
     const cwd = try process.getCwdAlloc(testing.allocator);
     defer testing.allocator.free(cwd);
-    if (builtin.os == .windows) {
+    if (builtin.os.tag == .windows) {
         if (windowsParsePath(cwd).kind == WindowsPath.Kind.Drive) {
             cwd[0] = asciiUpper(cwd[0]);
         }
@@ -661,7 +661,7 @@ test "resolveWindows" {
         // TODO https://github.com/ziglang/zig/issues/3288
         return error.SkipZigTest;
     }
-    if (builtin.os == .windows) {
+    if (builtin.os.tag == .windows) {
         const cwd = try process.getCwdAlloc(testing.allocator);
         defer testing.allocator.free(cwd);
         const parsed_cwd = windowsParsePath(cwd);
@@ -732,7 +732,7 @@ fn testResolvePosix(paths: []const []const u8, expected: []const u8) !void {
 /// If the path is a file in the current directory (no directory component)
 /// then returns null
 pub fn dirname(path: []const u8) ?[]const u8 {
-    if (builtin.os == .windows) {
+    if (builtin.os.tag == .windows) {
         return dirnameWindows(path);
     } else {
         return dirnamePosix(path);
@@ -864,7 +864,7 @@ fn testDirnameWindows(input: []const u8, expected_output: ?[]const u8) void {
 }
 
 pub fn basename(path: []const u8) []const u8 {
-    if (builtin.os == .windows) {
+    if (builtin.os.tag == .windows) {
         return basenameWindows(path);
     } else {
         return basenamePosix(path);
@@ -980,7 +980,7 @@ fn testBasenameWindows(input: []const u8, expected_output: []const u8) void {
 /// string is returned.
 /// On Windows this canonicalizes the drive to a capital letter and paths to `\\`.
 pub fn relative(allocator: *Allocator, from: []const u8, to: []const u8) ![]u8 {
-    if (builtin.os == .windows) {
+    if (builtin.os.tag == .windows) {
         return relativeWindows(allocator, from, to);
     } else {
         return relativePosix(allocator, from, to);
lib/std/fs/watch.zig
@@ -42,7 +42,7 @@ pub fn Watch(comptime V: type) type {
         os_data: OsData,
         allocator: *Allocator,
 
-        const OsData = switch (builtin.os) {
+        const OsData = switch (builtin.os.tag) {
             // TODO https://github.com/ziglang/zig/issues/3778
             .macosx, .freebsd, .netbsd, .dragonfly => KqOsData,
             .linux => LinuxOsData,
@@ -121,7 +121,7 @@ pub fn Watch(comptime V: type) type {
             const self = try allocator.create(Self);
             errdefer allocator.destroy(self);
 
-            switch (builtin.os) {
+            switch (builtin.os.tag) {
                 .linux => {
                     const inotify_fd = try os.inotify_init1(os.linux.IN_NONBLOCK | os.linux.IN_CLOEXEC);
                     errdefer os.close(inotify_fd);
@@ -172,7 +172,7 @@ pub fn Watch(comptime V: type) type {
 
         /// All addFile calls and removeFile calls must have completed.
         pub fn deinit(self: *Self) void {
-            switch (builtin.os) {
+            switch (builtin.os.tag) {
                 .macosx, .freebsd, .netbsd, .dragonfly => {
                     // TODO we need to cancel the frames before destroying the lock
                     self.os_data.table_lock.deinit();
@@ -223,7 +223,7 @@ pub fn Watch(comptime V: type) type {
         }
 
         pub fn addFile(self: *Self, file_path: []const u8, value: V) !?V {
-            switch (builtin.os) {
+            switch (builtin.os.tag) {
                 .macosx, .freebsd, .netbsd, .dragonfly => return addFileKEvent(self, file_path, value),
                 .linux => return addFileLinux(self, file_path, value),
                 .windows => return addFileWindows(self, file_path, value),
lib/std/net/test.zig
@@ -63,7 +63,7 @@ test "parse and render IPv4 addresses" {
 }
 
 test "resolve DNS" {
-    if (std.builtin.os == .windows) {
+    if (std.builtin.os.tag == .windows) {
         // DNS resolution not implemented on Windows yet.
         return error.SkipZigTest;
     }
@@ -81,7 +81,7 @@ test "resolve DNS" {
 test "listen on a port, send bytes, receive bytes" {
     if (!std.io.is_async) return error.SkipZigTest;
 
-    if (std.builtin.os != .linux) {
+    if (std.builtin.os.tag != .linux) {
         // TODO build abstractions for other operating systems
         return error.SkipZigTest;
     }
lib/std/os/bits.zig
@@ -3,10 +3,10 @@
 //! Root source files can define `os.bits` and these will additionally be added
 //! to the namespace.
 
-const builtin = @import("builtin");
+const std = @import("std");
 const root = @import("root");
 
-pub usingnamespace switch (builtin.os) {
+pub usingnamespace switch (std.Target.current.os.tag) {
     .macosx, .ios, .tvos, .watchos => @import("bits/darwin.zig"),
     .dragonfly => @import("bits/dragonfly.zig"),
     .freebsd => @import("bits/freebsd.zig"),
lib/std/os/linux.zig
@@ -1070,7 +1070,7 @@ pub fn tcsetattr(fd: fd_t, optional_action: TCSA, termios_p: *const termios) usi
 }
 
 test "" {
-    if (builtin.os == .linux) {
+    if (builtin.os.tag == .linux) {
         _ = @import("linux/test.zig");
     }
 }
lib/std/os/test.zig
@@ -53,7 +53,7 @@ test "std.Thread.getCurrentId" {
     thread.wait();
     if (Thread.use_pthreads) {
         expect(thread_current_id == thread_id);
-    } else if (builtin.os == .windows) {
+    } else if (builtin.os.tag == .windows) {
         expect(Thread.getCurrentId() != thread_current_id);
     } else {
         // If the thread completes very quickly, then thread_id can be 0. See the
@@ -151,7 +151,7 @@ test "realpath" {
 }
 
 test "sigaltstack" {
-    if (builtin.os == .windows or builtin.os == .wasi) return error.SkipZigTest;
+    if (builtin.os.tag == .windows or builtin.os.tag == .wasi) return error.SkipZigTest;
 
     var st: os.stack_t = undefined;
     try os.sigaltstack(null, &st);
@@ -204,7 +204,7 @@ fn iter_fn(info: *dl_phdr_info, size: usize, counter: *usize) IterFnError!void {
 }
 
 test "dl_iterate_phdr" {
-    if (builtin.os == .windows or builtin.os == .wasi or builtin.os == .macosx)
+    if (builtin.os.tag == .windows or builtin.os.tag == .wasi or builtin.os.tag == .macosx)
         return error.SkipZigTest;
 
     var counter: usize = 0;
@@ -213,7 +213,7 @@ test "dl_iterate_phdr" {
 }
 
 test "gethostname" {
-    if (builtin.os == .windows)
+    if (builtin.os.tag == .windows)
         return error.SkipZigTest;
 
     var buf: [os.HOST_NAME_MAX]u8 = undefined;
@@ -222,7 +222,7 @@ test "gethostname" {
 }
 
 test "pipe" {
-    if (builtin.os == .windows)
+    if (builtin.os.tag == .windows)
         return error.SkipZigTest;
 
     var fds = try os.pipe();
@@ -241,7 +241,7 @@ test "argsAlloc" {
 
 test "memfd_create" {
     // memfd_create is linux specific.
-    if (builtin.os != .linux) return error.SkipZigTest;
+    if (builtin.os.tag != .linux) return error.SkipZigTest;
     const fd = std.os.memfd_create("test", 0) catch |err| switch (err) {
         // Related: https://github.com/ziglang/zig/issues/4019
         error.SystemOutdated => return error.SkipZigTest,
@@ -258,7 +258,7 @@ test "memfd_create" {
 }
 
 test "mmap" {
-    if (builtin.os == .windows)
+    if (builtin.os.tag == .windows)
         return error.SkipZigTest;
 
     // Simple mmap() call with non page-aligned size
@@ -353,7 +353,7 @@ test "mmap" {
 }
 
 test "getenv" {
-    if (builtin.os == .windows) {
+    if (builtin.os.tag == .windows) {
         expect(os.getenvW(&[_:0]u16{ 'B', 'O', 'G', 'U', 'S', 0x11, 0x22, 0x33, 0x44, 0x55 }) == null);
     } else {
         expect(os.getenvZ("BOGUSDOESNOTEXISTENVVAR") == null);
lib/std/special/compiler_rt/extendXfYf2_test.zig
@@ -90,7 +90,7 @@ test "extendhfsf2" {
     test__extendhfsf2(0x7f00, 0x7fe00000); // sNaN
     // On x86 the NaN becomes quiet because the return is pushed on the x87
     // stack due to ABI requirements
-    if (builtin.arch != .i386 and builtin.os == .windows)
+    if (builtin.arch != .i386 and builtin.os.tag == .windows)
         test__extendhfsf2(0x7c01, 0x7f802000); // sNaN
 
     test__extendhfsf2(0, 0); // 0
lib/std/special/c.zig
@@ -17,7 +17,7 @@ const is_msvc = switch (builtin.abi) {
     .msvc => true,
     else => false,
 };
-const is_freestanding = switch (builtin.os) {
+const is_freestanding = switch (builtin.os.tag) {
     .freestanding => true,
     else => false,
 };
@@ -81,7 +81,7 @@ pub fn panic(msg: []const u8, error_return_trace: ?*builtin.StackTrace) noreturn
         @setCold(true);
         std.debug.panic("{}", .{msg});
     }
-    if (builtin.os != .freestanding and builtin.os != .other) {
+    if (builtin.os.tag != .freestanding and builtin.os.tag != .other) {
         std.os.abort();
     }
     while (true) {}
@@ -178,11 +178,11 @@ test "test_bcmp" {
 comptime {
     if (builtin.mode != builtin.Mode.ReleaseFast and
         builtin.mode != builtin.Mode.ReleaseSmall and
-        builtin.os != builtin.Os.windows)
+        builtin.os.tag != .windows)
     {
         @export(__stack_chk_fail, .{ .name = "__stack_chk_fail" });
     }
-    if (builtin.os == builtin.Os.linux) {
+    if (builtin.os.tag == .linux) {
         @export(clone, .{ .name = "clone" });
     }
 }
lib/std/special/compiler_rt.zig
@@ -1,11 +1,12 @@
-const builtin = @import("builtin");
+const std = @import("std");
+const builtin = std.builtin;
 const is_test = builtin.is_test;
 
 const is_gnu = switch (builtin.abi) {
     .gnu, .gnuabin32, .gnuabi64, .gnueabi, .gnueabihf, .gnux32 => true,
     else => false,
 };
-const is_mingw = builtin.os == .windows and is_gnu;
+const is_mingw = builtin.os.tag == .windows and is_gnu;
 
 comptime {
     const linkage = if (is_test) builtin.GlobalLinkage.Internal else builtin.GlobalLinkage.Weak;
@@ -180,7 +181,7 @@ comptime {
         @export(@import("compiler_rt/arm.zig").__aeabi_memclr, .{ .name = "__aeabi_memclr4", .linkage = linkage });
         @export(@import("compiler_rt/arm.zig").__aeabi_memclr, .{ .name = "__aeabi_memclr8", .linkage = linkage });
 
-        if (builtin.os == .linux) {
+        if (builtin.os.tag == .linux) {
             @export(@import("compiler_rt/arm.zig").__aeabi_read_tp, .{ .name = "__aeabi_read_tp", .linkage = linkage });
         }
 
@@ -250,7 +251,7 @@ comptime {
         @export(@import("compiler_rt/aullrem.zig")._aullrem, .{ .name = "\x01__aullrem", .linkage = strong_linkage });
     }
 
-    if (builtin.os == .windows) {
+    if (builtin.os.tag == .windows) {
         // Default stack-probe functions emitted by LLVM
         if (is_mingw) {
             @export(@import("compiler_rt/stack_probe.zig")._chkstk, .{ .name = "_alloca", .linkage = strong_linkage });
@@ -288,7 +289,7 @@ comptime {
             else => {},
         }
     } else {
-        if (builtin.glibc_version != null) {
+        if (std.Target.current.isGnuLibC()) {
             @export(__stack_chk_guard, .{ .name = "__stack_chk_guard", .linkage = linkage });
         }
         @export(@import("compiler_rt/divti3.zig").__divti3, .{ .name = "__divti3", .linkage = linkage });
@@ -307,7 +308,7 @@ comptime {
 pub fn panic(msg: []const u8, error_return_trace: ?*builtin.StackTrace) noreturn {
     @setCold(true);
     if (is_test) {
-        @import("std").debug.panic("{}", .{msg});
+        std.debug.panic("{}", .{msg});
     } else {
         unreachable;
     }
lib/std/zig/system.zig
@@ -1,11 +1,14 @@
 const std = @import("../std.zig");
+const elf = std.elf;
 const mem = std.mem;
+const fs = std.fs;
 const Allocator = std.mem.Allocator;
 const ArrayList = std.ArrayList;
 const assert = std.debug.assert;
 const process = std.process;
+const Target = std.Target;
 
-const is_windows = std.Target.current.isWindows();
+const is_windows = Target.current.os.tag == .windows;
 
 pub const NativePaths = struct {
     include_dirs: ArrayList([:0]u8),
@@ -77,7 +80,7 @@ pub const NativePaths = struct {
         }
 
         if (!is_windows) {
-            const triple = try std.Target.current.linuxTriple(allocator);
+            const triple = try Target.current.linuxTriple(allocator);
 
             // TODO: $ ld --verbose | grep SEARCH_DIR
             // the output contains some paths that end with lib64, maybe include them too?
@@ -161,3 +164,326 @@ pub const NativePaths = struct {
         try array.append(item);
     }
 };
+
+pub const NativeTargetInfo = struct {
+    target: Target,
+    dynamic_linker: ?[:0]u8,
+
+    pub const DetectError = error{
+        OutOfMemory,
+        FileSystem,
+        SystemResources,
+        SymLinkLoop,
+        ProcessFdQuotaExceeded,
+        SystemFdQuotaExceeded,
+        DeviceBusy,
+    };
+
+    /// Detects the native CPU model & features, operating system & version, and C ABI & dynamic linker.
+    /// On Linux, this is additionally responsible for detecting the native glibc version when applicable.
+    pub fn detect(allocator: *Allocator) DetectError!NativeTargetInfo {
+        const arch = Target.current.cpu.arch;
+        const os_tag = Target.current.os.tag;
+
+        // TODO Detect native CPU model & features. Until that is implemented we hard code baseline.
+        const cpu = Target.Cpu.baseline(arch);
+
+        // TODO Detect native operating system version. Until that is implemented we use the minimum version
+        // of the default range.
+        const os = Target.Os.defaultVersionRange(os_tag);
+
+        return detectAbiAndDynamicLinker(allocator, cpu, os);
+    }
+
+    /// Must be the same `Allocator` passed to `detect`.
+    pub fn deinit(self: *NativeTargetInfo, allocator: *Allocator) void {
+        if (self.dynamic_linker) |dl| allocator.free(dl);
+        self.* = undefined;
+    }
+
+    /// First we attempt to use the executable's own binary. If it is dynamically
+    /// linked, then it should answer both the C ABI question and the dynamic linker question.
+    /// If it is statically linked, then we try /usr/bin/env. If that does not provide the answer, then
+    /// we fall back to the defaults.
+    fn detectAbiAndDynamicLinker(
+        allocator: *Allocator,
+        cpu: Target.Cpu,
+        os: Target.Os,
+    ) DetectError!NativeTargetInfo {
+        if (!comptime Target.current.hasDynamicLinker()) {
+            return defaultAbiAndDynamicLinker(allocator, cpu, os);
+        }
+        // The current target's ABI cannot be relied on for this. For example, we may build the zig
+        // compiler for target riscv64-linux-musl and provide a tarball for users to download.
+        // A user could then run that zig compiler on riscv64-linux-gnu. This use case is well-defined
+        // and supported by Zig. But that means that we must detect the system ABI here rather than
+        // relying on `Target.current`.
+        const LdInfo = struct {
+            ld_path: []u8,
+            abi: Target.Abi,
+        };
+        var ld_info_list = std.ArrayList(LdInfo).init(allocator);
+        defer {
+            for (ld_info_list.toSlice()) |ld_info| allocator.free(ld_info.ld_path);
+            ld_info_list.deinit();
+        }
+
+        const all_abis = comptime blk: {
+            assert(@enumToInt(Target.Abi.none) == 0);
+            const fields = std.meta.fields(Target.Abi)[1..];
+            var array: [fields.len]Target.Abi = undefined;
+            inline for (fields) |field, i| {
+                array[i] = @field(Target.Abi, field.name);
+            }
+            break :blk array;
+        };
+        for (all_abis) |abi| {
+            // This may be a nonsensical parameter. We detect this with error.UnknownDynamicLinkerPath and
+            // skip adding it to `ld_info_list`.
+            const target: Target = .{
+                .cpu = cpu,
+                .os = os,
+                .abi = abi,
+            };
+            const standard_ld_path = target.getStandardDynamicLinkerPath(allocator) catch |err| switch (err) {
+                error.OutOfMemory => return error.OutOfMemory,
+                error.UnknownDynamicLinkerPath, error.TargetHasNoDynamicLinker => continue,
+            };
+            errdefer allocator.free(standard_ld_path);
+            try ld_info_list.append(.{
+                .ld_path = standard_ld_path,
+                .abi = abi,
+            });
+        }
+
+        // Best case scenario: the executable is dynamically linked, and we can iterate
+        // over our own shared objects and find a dynamic linker.
+        self_exe: {
+            const lib_paths = try std.process.getSelfExeSharedLibPaths(allocator);
+            defer allocator.free(lib_paths);
+
+            var found_ld_info: LdInfo = undefined;
+            var found_ld_path: [:0]const u8 = undefined;
+
+            // Look for dynamic linker.
+            // This is O(N^M) but typical case here is N=2 and M=10.
+            find_ld: for (lib_paths) |lib_path| {
+                for (ld_info_list.toSlice()) |ld_info| {
+                    const standard_ld_basename = fs.path.basename(ld_info.ld_path);
+                    if (std.mem.endsWith(u8, lib_path, standard_ld_basename)) {
+                        found_ld_info = ld_info;
+                        found_ld_path = lib_path;
+                        break :find_ld;
+                    }
+                }
+            } else break :self_exe;
+
+            // Look for glibc version.
+            var os_adjusted = os;
+            if (Target.current.os.tag == .linux and found_ld_info.abi.isGnu()) {
+                for (lib_paths) |lib_path| {
+                    if (std.mem.endsWith(u8, lib_path, glibc_so_basename)) {
+                        os_adjusted.version_range.linux.glibc = glibcVerFromSO(lib_path) catch |err| switch (err) {
+                            error.UnrecognizedGnuLibCFileName => continue,
+                            error.InvalidGnuLibCVersion => continue,
+                            error.GnuLibCVersionUnavailable => continue,
+                            else => |e| return e,
+                        };
+                        break;
+                    }
+                }
+            }
+
+            return NativeTargetInfo{
+                .target = .{
+                    .cpu = cpu,
+                    .os = os_adjusted,
+                    .abi = found_ld_info.abi,
+                },
+                .dynamic_linker = try mem.dupeZ(allocator, u8, found_ld_path),
+            };
+        }
+
+        // If Zig is statically linked, such as via distributed binary static builds, the above
+        // trick won't work. The next thing we fall back to is the same thing, but for /usr/bin/env.
+        // Since that path is hard-coded into the shebang line of many portable scripts, it's a
+        // reasonably reliable path to check for.
+        return abiAndDynamicLinkerFromUsrBinEnv(allocator, cpu, os) catch |err| switch (err) {
+            error.OutOfMemory => return error.OutOfMemory,
+            error.FileSystem => return error.FileSystem,
+            error.SystemResources => return error.SystemResources,
+            error.SymLinkLoop => return error.SymLinkLoop,
+            error.ProcessFdQuotaExceeded => return error.ProcessFdQuotaExceeded,
+            error.SystemFdQuotaExceeded => return error.SystemFdQuotaExceeded,
+            error.DeviceBusy => return error.DeviceBusy,
+
+            error.UnableToReadElfFile,
+            error.ElfNotADynamicExecutable,
+            error.InvalidElfProgramHeaders,
+            error.InvalidElfClass,
+            error.InvalidElfVersion,
+            error.InvalidElfEndian,
+            error.InvalidElfFile,
+            error.InvalidElfMagic,
+            error.UsrBinEnvNotAvailable,
+            error.Unexpected,
+            // Finally, we fall back on the standard path.
+            => defaultAbiAndDynamicLinker(allocator, cpu, os),
+        };
+    }
+
+    const glibc_so_basename = "libc.so.6";
+
+    fn glibcVerFromSO(so_path: [:0]const u8) !std.builtin.Version {
+        var link_buf: [std.os.PATH_MAX]u8 = undefined;
+        const link_name = std.os.readlinkC(so_path.ptr, &link_buf) catch |err| switch (err) {
+            error.AccessDenied => return error.GnuLibCVersionUnavailable,
+            error.FileSystem => return error.FileSystem,
+            error.SymLinkLoop => return error.SymLinkLoop,
+            error.NameTooLong => unreachable,
+            error.FileNotFound => return error.GnuLibCVersionUnavailable,
+            error.SystemResources => return error.SystemResources,
+            error.NotDir => return error.GnuLibCVersionUnavailable,
+            error.Unexpected => return error.GnuLibCVersionUnavailable,
+        };
+        // example: "libc-2.3.4.so"
+        // example: "libc-2.27.so"
+        const prefix = "libc-";
+        const suffix = ".so";
+        if (!mem.startsWith(u8, link_name, prefix) or !mem.endsWith(u8, link_name, suffix)) {
+            return error.UnrecognizedGnuLibCFileName;
+        }
+        // chop off "libc-" and ".so"
+        const link_name_chopped = link_name[prefix.len .. link_name.len - suffix.len];
+        return std.builtin.Version.parse(link_name_chopped) catch |err| switch (err) {
+            error.Overflow => return error.InvalidGnuLibCVersion,
+            error.InvalidCharacter => return error.InvalidGnuLibCVersion,
+            error.InvalidVersion => return error.InvalidGnuLibCVersion,
+        };
+    }
+
+    fn abiAndDynamicLinkerFromUsrBinEnv(
+        allocator: *Allocator,
+        cpu: Target.Cpu,
+        os: Target.Os,
+    ) !NativeTargetInfo {
+        const env_file = std.fs.openFileAbsoluteC("/usr/bin/env", .{}) catch |err| switch (err) {
+            error.NoSpaceLeft => unreachable,
+            error.NameTooLong => unreachable,
+            error.PathAlreadyExists => unreachable,
+            error.SharingViolation => unreachable,
+            error.InvalidUtf8 => unreachable,
+            error.BadPathName => unreachable,
+            error.PipeBusy => unreachable,
+
+            error.IsDir => return error.UsrBinEnvNotAvailable,
+            error.NotDir => return error.UsrBinEnvNotAvailable,
+            error.AccessDenied => return error.UsrBinEnvNotAvailable,
+            error.NoDevice => return error.UsrBinEnvNotAvailable,
+            error.FileNotFound => return error.UsrBinEnvNotAvailable,
+            error.FileTooBig => return error.UsrBinEnvNotAvailable,
+
+            else => |e| return e,
+        };
+        var hdr_buf: [@sizeOf(elf.Elf64_Ehdr)]u8 align(@alignOf(elf.Elf64_Ehdr)) = undefined;
+        const hdr_bytes_len = try wrapRead(env_file.pread(&hdr_buf, 0));
+        if (hdr_bytes_len < @sizeOf(elf.Elf32_Ehdr)) return error.InvalidElfFile;
+        const hdr32 = @ptrCast(*elf.Elf32_Ehdr, &hdr_buf);
+        const hdr64 = @ptrCast(*elf.Elf64_Ehdr, &hdr_buf);
+        if (!mem.eql(u8, hdr32.e_ident[0..4], "\x7fELF")) return error.InvalidElfMagic;
+        const elf_endian: std.builtin.Endian = switch (hdr32.e_ident[elf.EI_DATA]) {
+            elf.ELFDATA2LSB => .Little,
+            elf.ELFDATA2MSB => .Big,
+            else => return error.InvalidElfEndian,
+        };
+        const need_bswap = elf_endian != std.builtin.endian;
+        if (hdr32.e_ident[elf.EI_VERSION] != 1) return error.InvalidElfVersion;
+
+        const is_64 = switch (hdr32.e_ident[elf.EI_CLASS]) {
+            elf.ELFCLASS32 => false,
+            elf.ELFCLASS64 => true,
+            else => return error.InvalidElfClass,
+        };
+        var phoff = elfInt(is_64, need_bswap, hdr32.e_phoff, hdr64.e_phoff);
+        const phentsize = elfInt(is_64, need_bswap, hdr32.e_phentsize, hdr64.e_phentsize);
+        const phnum = elfInt(is_64, need_bswap, hdr32.e_phnum, hdr64.e_phnum);
+        const shstrndx = elfInt(is_64, need_bswap, hdr32.e_shstrndx, hdr64.e_shstrndx);
+
+        const ph_total_size = std.math.mul(u32, phentsize, phnum) catch |err| switch (err) {
+            error.Overflow => return error.InvalidElfProgramHeaders,
+        };
+        var ph_buf: [16 * @sizeOf(elf.Elf64_Phdr)]u8 align(@alignOf(elf.Elf64_Phdr)) = undefined;
+        var ph_i: u16 = 0;
+        while (ph_i < phnum) {
+            // Reserve some bytes so that we can deref the 64-bit struct fields even when the ELF file is 32-bits.
+            const reserve = @sizeOf(elf.Elf64_Phdr) - @sizeOf(elf.Elf32_Phdr);
+            const read_byte_len = try wrapRead(env_file.pread(ph_buf[0 .. ph_buf.len - reserve], phoff));
+            if (read_byte_len < phentsize) return error.ElfNotADynamicExecutable;
+            var buf_i: usize = 0;
+            while (buf_i < read_byte_len and ph_i < phnum) : ({
+                ph_i += 1;
+                phoff += phentsize;
+                buf_i += phentsize;
+            }) {
+                const ph32 = @ptrCast(*elf.Elf32_Phdr, @alignCast(@alignOf(elf.Elf32_Phdr), &ph_buf[buf_i]));
+                const ph64 = @ptrCast(*elf.Elf64_Phdr, @alignCast(@alignOf(elf.Elf64_Phdr), &ph_buf[buf_i]));
+                const p_type = elfInt(is_64, need_bswap, ph32.p_type, ph64.p_type);
+                switch (p_type) {
+                    elf.PT_INTERP => {
+                        std.debug.warn("found PT_INTERP\n", .{});
+                    },
+                    elf.PT_DYNAMIC => {
+                        std.debug.warn("found PT_DYNAMIC\n", .{});
+                    },
+                    else => continue,
+                }
+            }
+        }
+
+        return error.OutOfMemory; // TODO
+    }
+
+    fn wrapRead(res: std.os.ReadError!usize) !usize {
+        return res catch |err| switch (err) {
+            error.OperationAborted => unreachable, // Windows-only
+            error.WouldBlock => unreachable, // Did not request blocking mode
+            error.SystemResources => return error.SystemResources,
+            error.IsDir => return error.UnableToReadElfFile,
+            error.BrokenPipe => return error.UnableToReadElfFile,
+            error.ConnectionResetByPeer => return error.UnableToReadElfFile,
+            error.Unexpected => return error.Unexpected,
+            error.InputOutput => return error.FileSystem,
+        };
+    }
+
+    fn defaultAbiAndDynamicLinker(allocator: *Allocator, cpu: Target.Cpu, os: Target.Os) !NativeTargetInfo {
+        const target: Target = .{
+            .cpu = cpu,
+            .os = os,
+            .abi = Target.Abi.default(cpu.arch, os),
+        };
+        return @as(NativeTargetInfo, .{
+            .target = target,
+            .dynamic_linker = target.getStandardDynamicLinkerPath(allocator) catch |err| switch (err) {
+                error.OutOfMemory => return error.OutOfMemory,
+                error.UnknownDynamicLinkerPath, error.TargetHasNoDynamicLinker => null,
+            },
+        });
+    }
+};
+
+fn elfInt(is_64: bool, need_bswap: bool, int_32: var, int_64: var) @TypeOf(int_64) {
+    if (is_64) {
+        if (need_bswap) {
+            return @byteSwap(@TypeOf(int_64), int_64);
+        } else {
+            return int_64;
+        }
+    } else {
+        if (need_bswap) {
+            return @byteSwap(@TypeOf(int_32), int_32);
+        } else {
+            return int_32;
+        }
+    }
+}
lib/std/build.zig
@@ -971,9 +971,9 @@ pub const Builder = struct {
 };
 
 test "builder.findProgram compiles" {
-    // TODO: uncomment and fix the leak
-    // const builder = try Builder.create(std.testing.allocator, "zig", "zig-cache", "zig-cache");
-    const builder = try Builder.create(std.heap.page_allocator, "zig", "zig-cache", "zig-cache");
+    var buf: [1000]u8 = undefined;
+    var fba = std.heap.FixedBufferAllocator.init(&buf);
+    const builder = try Builder.create(&fba.allocator, "zig", "zig-cache", "zig-cache");
     defer builder.destroy();
     _ = builder.findProgram(&[_][]const u8{}, &[_][]const u8{}) catch null;
 }
@@ -981,11 +981,37 @@ test "builder.findProgram compiles" {
 /// Deprecated. Use `builtin.Version`.
 pub const Version = builtin.Version;
 
-/// Deprecated. Use `std.Target.Cross`.
-pub const CrossTarget = std.Target.Cross;
-
 /// Deprecated. Use `std.Target`.
-pub const Target = std.Target;
+pub const CrossTarget = std.Target;
+
+/// Wraps `std.Target` so that it can be annotated as "the native target" or an explicitly specified target.
+pub const Target = union(enum) {
+    Native,
+    Cross: std.Target,
+
+    pub fn getTarget(self: Target) std.Target {
+        return switch (self) {
+            .Native => std.Target.current,
+            .Cross => |t| t,
+        };
+    }
+
+    pub fn getOs(self: Target) std.Target.Os.Tag {
+        return self.getTarget().os.tag;
+    }
+
+    pub fn getCpu(self: Target) std.Target.Cpu {
+        return self.getTarget().cpu;
+    }
+
+    pub fn getAbi(self: Target) std.Target.Abi {
+        return self.getTarget().abi;
+    }
+
+    pub fn getArch(self: Target) std.Target.Cpu.Arch {
+        return self.getCpu().arch;
+    }
+};
 
 pub const Pkg = struct {
     name: []const u8,
lib/std/builtin.zig
@@ -411,7 +411,7 @@ pub const Version = struct {
         }
     };
 
-    pub fn order(lhs: Version, rhs: version) std.math.Order {
+    pub fn order(lhs: Version, rhs: Version) std.math.Order {
         if (lhs.major < rhs.major) return .lt;
         if (lhs.major > rhs.major) return .gt;
         if (lhs.minor < rhs.minor) return .lt;
@@ -504,7 +504,7 @@ pub fn default_panic(msg: []const u8, error_return_trace: ?*StackTrace) noreturn
         root.os.panic(msg, error_return_trace);
         unreachable;
     }
-    switch (os) {
+    switch (os.tag) {
         .freestanding => {
             while (true) {
                 @breakpoint();
lib/std/c.zig
@@ -1,5 +1,5 @@
-const builtin = @import("builtin");
 const std = @import("std");
+const builtin = std.builtin;
 const page_size = std.mem.page_size;
 
 pub const tokenizer = @import("c/tokenizer.zig");
@@ -10,7 +10,7 @@ pub const ast = @import("c/ast.zig");
 
 pub usingnamespace @import("os/bits.zig");
 
-pub usingnamespace switch (builtin.os) {
+pub usingnamespace switch (std.Target.current.os.tag) {
     .linux => @import("c/linux.zig"),
     .windows => @import("c/windows.zig"),
     .macosx, .ios, .tvos, .watchos => @import("c/darwin.zig"),
@@ -46,17 +46,16 @@ pub fn versionCheck(glibc_version: builtin.Version) type {
     return struct {
         pub const ok = blk: {
             if (!builtin.link_libc) break :blk false;
-            switch (builtin.abi) {
-                .musl, .musleabi, .musleabihf => break :blk true,
-                .gnu, .gnuabin32, .gnuabi64, .gnueabi, .gnueabihf, .gnux32 => {
-                    const ver = builtin.glibc_version orelse break :blk false;
-                    if (ver.major < glibc_version.major) break :blk false;
-                    if (ver.major > glibc_version.major) break :blk true;
-                    if (ver.minor < glibc_version.minor) break :blk false;
-                    if (ver.minor > glibc_version.minor) break :blk true;
-                    break :blk ver.patch >= glibc_version.patch;
-                },
-                else => break :blk false,
+            if (std.Target.current.abi.isMusl()) break :blk true;
+            if (std.Target.current.isGnuLibC()) {
+                const ver = std.Target.current.os.version_range.linux.glibc;
+                const order = ver.order(glibc_version);
+                break :blk switch (order) {
+                    .gt, .eq => true,
+                    .lt => false,
+                };
+            } else {
+                break :blk false;
             }
         };
     };
lib/std/child_process.zig
@@ -17,9 +17,9 @@ const TailQueue = std.TailQueue;
 const maxInt = std.math.maxInt;
 
 pub const ChildProcess = struct {
-    pid: if (builtin.os == .windows) void else i32,
-    handle: if (builtin.os == .windows) windows.HANDLE else void,
-    thread_handle: if (builtin.os == .windows) windows.HANDLE else void,
+    pid: if (builtin.os.tag == .windows) void else i32,
+    handle: if (builtin.os.tag == .windows) windows.HANDLE else void,
+    thread_handle: if (builtin.os.tag == .windows) windows.HANDLE else void,
 
     allocator: *mem.Allocator,
 
@@ -39,15 +39,15 @@ pub const ChildProcess = struct {
     stderr_behavior: StdIo,
 
     /// Set to change the user id when spawning the child process.
-    uid: if (builtin.os == .windows) void else ?u32,
+    uid: if (builtin.os.tag == .windows) void else ?u32,
 
     /// Set to change the group id when spawning the child process.
-    gid: if (builtin.os == .windows) void else ?u32,
+    gid: if (builtin.os.tag == .windows) void else ?u32,
 
     /// Set to change the current working directory when spawning the child process.
     cwd: ?[]const u8,
 
-    err_pipe: if (builtin.os == .windows) void else [2]os.fd_t,
+    err_pipe: if (builtin.os.tag == .windows) void else [2]os.fd_t,
 
     expand_arg0: Arg0Expand,
 
@@ -96,8 +96,8 @@ pub const ChildProcess = struct {
             .term = null,
             .env_map = null,
             .cwd = null,
-            .uid = if (builtin.os == .windows) {} else null,
-            .gid = if (builtin.os == .windows) {} else null,
+            .uid = if (builtin.os.tag == .windows) {} else null,
+            .gid = if (builtin.os.tag == .windows) {} else null,
             .stdin = null,
             .stdout = null,
             .stderr = null,
@@ -118,7 +118,7 @@ pub const ChildProcess = struct {
 
     /// On success must call `kill` or `wait`.
     pub fn spawn(self: *ChildProcess) SpawnError!void {
-        if (builtin.os == .windows) {
+        if (builtin.os.tag == .windows) {
             return self.spawnWindows();
         } else {
             return self.spawnPosix();
@@ -132,7 +132,7 @@ pub const ChildProcess = struct {
 
     /// Forcibly terminates child process and then cleans up all resources.
     pub fn kill(self: *ChildProcess) !Term {
-        if (builtin.os == .windows) {
+        if (builtin.os.tag == .windows) {
             return self.killWindows(1);
         } else {
             return self.killPosix();
@@ -162,7 +162,7 @@ pub const ChildProcess = struct {
 
     /// Blocks until child process terminates and then cleans up all resources.
     pub fn wait(self: *ChildProcess) !Term {
-        if (builtin.os == .windows) {
+        if (builtin.os.tag == .windows) {
             return self.waitWindows();
         } else {
             return self.waitPosix();
@@ -307,7 +307,7 @@ pub const ChildProcess = struct {
     fn cleanupAfterWait(self: *ChildProcess, status: u32) !Term {
         defer destroyPipe(self.err_pipe);
 
-        if (builtin.os == .linux) {
+        if (builtin.os.tag == .linux) {
             var fd = [1]std.os.pollfd{std.os.pollfd{
                 .fd = self.err_pipe[0],
                 .events = std.os.POLLIN,
@@ -402,7 +402,7 @@ pub const ChildProcess = struct {
         // This pipe is used to communicate errors between the time of fork
         // and execve from the child process to the parent process.
         const err_pipe = blk: {
-            if (builtin.os == .linux) {
+            if (builtin.os.tag == .linux) {
                 const fd = try os.eventfd(0, 0);
                 // There's no distinction between the readable and the writeable
                 // end with eventfd
lib/std/cstr.zig
@@ -4,8 +4,8 @@ const debug = std.debug;
 const mem = std.mem;
 const testing = std.testing;
 
-pub const line_sep = switch (builtin.os) {
-    builtin.Os.windows => "\r\n",
+pub const line_sep = switch (builtin.os.tag) {
+    .windows => "\r\n",
     else => "\n",
 };
 
lib/std/debug.zig
@@ -1,4 +1,5 @@
 const std = @import("std.zig");
+const builtin = std.builtin;
 const math = std.math;
 const mem = std.mem;
 const io = std.io;
@@ -11,7 +12,6 @@ const macho = std.macho;
 const coff = std.coff;
 const pdb = std.pdb;
 const ArrayList = std.ArrayList;
-const builtin = @import("builtin");
 const root = @import("root");
 const maxInt = std.math.maxInt;
 const File = std.fs.File;
@@ -101,7 +101,7 @@ pub fn detectTTYConfig() TTY.Config {
     } else |_| {
         if (stderr_file.supportsAnsiEscapeCodes()) {
             return .escape_codes;
-        } else if (builtin.os == .windows and stderr_file.isTty()) {
+        } else if (builtin.os.tag == .windows and stderr_file.isTty()) {
             return .windows_api;
         } else {
             return .no_color;
@@ -155,7 +155,7 @@ pub fn dumpStackTraceFromBase(bp: usize, ip: usize) void {
 /// chopping off the irrelevant frames and shifting so that the returned addresses pointer
 /// equals the passed in addresses pointer.
 pub fn captureStackTrace(first_address: ?usize, stack_trace: *builtin.StackTrace) void {
-    if (builtin.os == .windows) {
+    if (builtin.os.tag == .windows) {
         const addrs = stack_trace.instruction_addresses;
         const u32_addrs_len = @intCast(u32, addrs.len);
         const first_addr = first_address orelse {
@@ -231,7 +231,7 @@ pub fn assert(ok: bool) void {
 pub fn panic(comptime format: []const u8, args: var) noreturn {
     @setCold(true);
     // TODO: remove conditional once wasi / LLVM defines __builtin_return_address
-    const first_trace_addr = if (builtin.os == .wasi) null else @returnAddress();
+    const first_trace_addr = if (builtin.os.tag == .wasi) null else @returnAddress();
     panicExtra(null, first_trace_addr, format, args);
 }
 
@@ -361,7 +361,7 @@ pub fn writeCurrentStackTrace(
     tty_config: TTY.Config,
     start_addr: ?usize,
 ) !void {
-    if (builtin.os == .windows) {
+    if (builtin.os.tag == .windows) {
         return writeCurrentStackTraceWindows(out_stream, debug_info, tty_config, start_addr);
     }
     var it = StackIterator.init(start_addr, null);
@@ -418,7 +418,7 @@ pub const TTY = struct {
                     .Dim => noasync out_stream.write(DIM) catch return,
                     .Reset => noasync out_stream.write(RESET) catch return,
                 },
-                .windows_api => if (builtin.os == .windows) {
+                .windows_api => if (builtin.os.tag == .windows) {
                     const S = struct {
                         var attrs: windows.WORD = undefined;
                         var init_attrs = false;
@@ -617,7 +617,7 @@ pub fn openSelfDebugInfo(allocator: *mem.Allocator) anyerror!DebugInfo {
     if (@hasDecl(root, "os") and @hasDecl(root.os, "debug") and @hasDecl(root.os.debug, "openSelfDebugInfo")) {
         return noasync root.os.debug.openSelfDebugInfo(allocator);
     }
-    switch (builtin.os) {
+    switch (builtin.os.tag) {
         .linux,
         .freebsd,
         .macosx,
@@ -1019,7 +1019,7 @@ pub const DebugInfo = struct {
     pub fn getModuleForAddress(self: *DebugInfo, address: usize) !*ModuleDebugInfo {
         if (comptime std.Target.current.isDarwin())
             return self.lookupModuleDyld(address)
-        else if (builtin.os == .windows)
+        else if (builtin.os.tag == .windows)
             return self.lookupModuleWin32(address)
         else
             return self.lookupModuleDl(address);
@@ -1242,7 +1242,7 @@ const SymbolInfo = struct {
     }
 };
 
-pub const ModuleDebugInfo = switch (builtin.os) {
+pub const ModuleDebugInfo = switch (builtin.os.tag) {
     .macosx, .ios, .watchos, .tvos => struct {
         base_address: usize,
         mapped_memory: []const u8,
@@ -1602,7 +1602,7 @@ fn getDebugInfoAllocator() *mem.Allocator {
 }
 
 /// Whether or not the current target can print useful debug information when a segfault occurs.
-pub const have_segfault_handling_support = builtin.os == .linux or builtin.os == .windows;
+pub const have_segfault_handling_support = builtin.os.tag == .linux or builtin.os.tag == .windows;
 pub const enable_segfault_handler: bool = if (@hasDecl(root, "enable_segfault_handler"))
     root.enable_segfault_handler
 else
@@ -1621,7 +1621,7 @@ pub fn attachSegfaultHandler() void {
     if (!have_segfault_handling_support) {
         @compileError("segfault handler not supported for this target");
     }
-    if (builtin.os == .windows) {
+    if (builtin.os.tag == .windows) {
         windows_segfault_handle = windows.kernel32.AddVectoredExceptionHandler(0, handleSegfaultWindows);
         return;
     }
@@ -1637,7 +1637,7 @@ pub fn attachSegfaultHandler() void {
 }
 
 fn resetSegfaultHandler() void {
-    if (builtin.os == .windows) {
+    if (builtin.os.tag == .windows) {
         if (windows_segfault_handle) |handle| {
             assert(windows.kernel32.RemoveVectoredExceptionHandler(handle) != 0);
             windows_segfault_handle = null;
lib/std/dynamic_library.zig
@@ -11,7 +11,7 @@ const system = std.os.system;
 const maxInt = std.math.maxInt;
 const max = std.math.max;
 
-pub const DynLib = switch (builtin.os) {
+pub const DynLib = switch (builtin.os.tag) {
     .linux => if (builtin.link_libc) DlDynlib else ElfDynLib,
     .windows => WindowsDynLib,
     .macosx, .tvos, .watchos, .ios, .freebsd => DlDynlib,
@@ -390,7 +390,7 @@ pub const DlDynlib = struct {
 };
 
 test "dynamic_library" {
-    const libname = switch (builtin.os) {
+    const libname = switch (builtin.os.tag) {
         .linux, .freebsd => "invalid_so.so",
         .windows => "invalid_dll.dll",
         .macosx, .tvos, .watchos, .ios => "invalid_dylib.dylib",
lib/std/elf.zig
@@ -349,16 +349,6 @@ pub const Elf = struct {
     program_headers: []ProgramHeader,
     allocator: *mem.Allocator,
 
-    /// Call close when done.
-    pub fn openPath(allocator: *mem.Allocator, path: []const u8) !Elf {
-        @compileError("TODO implement");
-    }
-
-    /// Call close when done.
-    pub fn openFile(allocator: *mem.Allocator, file: File) !Elf {
-        @compileError("TODO implement");
-    }
-
     pub fn openStream(
         allocator: *mem.Allocator,
         seekable_stream: *io.SeekableStream(anyerror, anyerror),
@@ -554,6 +544,21 @@ pub const Elf = struct {
 };
 
 pub const EI_NIDENT = 16;
+
+pub const EI_CLASS = 4;
+pub const ELFCLASSNONE = 0;
+pub const ELFCLASS32 = 1;
+pub const ELFCLASS64 = 2;
+pub const ELFCLASSNUM = 3;
+
+pub const EI_DATA = 5;
+pub const ELFDATANONE = 0;
+pub const ELFDATA2LSB = 1;
+pub const ELFDATA2MSB = 2;
+pub const ELFDATANUM = 3;
+
+pub const EI_VERSION = 6;
+
 pub const Elf32_Half = u16;
 pub const Elf64_Half = u16;
 pub const Elf32_Word = u32;
lib/std/fs.zig
@@ -29,7 +29,7 @@ pub const Watch = @import("fs/watch.zig").Watch;
 /// All file system operations which return a path are guaranteed to
 /// fit into a UTF-8 encoded array of this length.
 /// The byte count includes room for a null sentinel byte.
-pub const MAX_PATH_BYTES = switch (builtin.os) {
+pub const MAX_PATH_BYTES = switch (builtin.os.tag) {
     .linux, .macosx, .ios, .freebsd, .netbsd, .dragonfly => os.PATH_MAX,
     // Each UTF-16LE character may be expanded to 3 UTF-8 bytes.
     // If it would require 4 UTF-8 bytes, then there would be a surrogate
@@ -47,7 +47,7 @@ pub const base64_encoder = base64.Base64Encoder.init(
 
 /// Whether or not async file system syscalls need a dedicated thread because the operating
 /// system does not support non-blocking I/O on the file system.
-pub const need_async_thread = std.io.is_async and switch (builtin.os) {
+pub const need_async_thread = std.io.is_async and switch (builtin.os.tag) {
     .windows, .other => false,
     else => true,
 };
@@ -270,7 +270,7 @@ pub const AtomicFile = struct {
         assert(!self.finished);
         self.file.close();
         self.finished = true;
-        if (builtin.os == .windows) {
+        if (builtin.os.tag == .windows) {
             const dest_path_w = try os.windows.sliceToPrefixedFileW(self.dest_path);
             const tmp_path_w = try os.windows.cStrToPrefixedFileW(@ptrCast([*:0]u8, &self.tmp_path_buf));
             return os.renameW(&tmp_path_w, &dest_path_w);
@@ -394,7 +394,7 @@ pub const Dir = struct {
 
     const IteratorError = error{AccessDenied} || os.UnexpectedError;
 
-    pub const Iterator = switch (builtin.os) {
+    pub const Iterator = switch (builtin.os.tag) {
         .macosx, .ios, .freebsd, .netbsd, .dragonfly => struct {
             dir: Dir,
             seek: i64,
@@ -409,7 +409,7 @@ pub const Dir = struct {
             /// Memory such as file names referenced in this returned entry becomes invalid
             /// with subsequent calls to `next`, as well as when this `Dir` is deinitialized.
             pub fn next(self: *Self) Error!?Entry {
-                switch (builtin.os) {
+                switch (builtin.os.tag) {
                     .macosx, .ios => return self.nextDarwin(),
                     .freebsd, .netbsd, .dragonfly => return self.nextBsd(),
                     else => @compileError("unimplemented"),
@@ -644,7 +644,7 @@ pub const Dir = struct {
     };
 
     pub fn iterate(self: Dir) Iterator {
-        switch (builtin.os) {
+        switch (builtin.os.tag) {
             .macosx, .ios, .freebsd, .netbsd, .dragonfly => return Iterator{
                 .dir = self,
                 .seek = 0,
@@ -710,7 +710,7 @@ pub const Dir = struct {
     /// Asserts that the path parameter has no null bytes.
     pub fn openFile(self: Dir, sub_path: []const u8, flags: File.OpenFlags) File.OpenError!File {
         if (std.debug.runtime_safety) for (sub_path) |byte| assert(byte != 0);
-        if (builtin.os == .windows) {
+        if (builtin.os.tag == .windows) {
             const path_w = try os.windows.sliceToPrefixedFileW(sub_path);
             return self.openFileW(&path_w, flags);
         }
@@ -720,7 +720,7 @@ pub const Dir = struct {
 
     /// Same as `openFile` but the path parameter is null-terminated.
     pub fn openFileC(self: Dir, sub_path: [*:0]const u8, flags: File.OpenFlags) File.OpenError!File {
-        if (builtin.os == .windows) {
+        if (builtin.os.tag == .windows) {
             const path_w = try os.windows.cStrToPrefixedFileW(sub_path);
             return self.openFileW(&path_w, flags);
         }
@@ -760,7 +760,7 @@ pub const Dir = struct {
     /// Asserts that the path parameter has no null bytes.
     pub fn createFile(self: Dir, sub_path: []const u8, flags: File.CreateFlags) File.OpenError!File {
         if (std.debug.runtime_safety) for (sub_path) |byte| assert(byte != 0);
-        if (builtin.os == .windows) {
+        if (builtin.os.tag == .windows) {
             const path_w = try os.windows.sliceToPrefixedFileW(sub_path);
             return self.createFileW(&path_w, flags);
         }
@@ -770,7 +770,7 @@ pub const Dir = struct {
 
     /// Same as `createFile` but the path parameter is null-terminated.
     pub fn createFileC(self: Dir, sub_path_c: [*:0]const u8, flags: File.CreateFlags) File.OpenError!File {
-        if (builtin.os == .windows) {
+        if (builtin.os.tag == .windows) {
             const path_w = try os.windows.cStrToPrefixedFileW(sub_path_c);
             return self.createFileW(&path_w, flags);
         }
@@ -901,7 +901,7 @@ pub const Dir = struct {
     /// Asserts that the path parameter has no null bytes.
     pub fn openDirTraverse(self: Dir, sub_path: []const u8) OpenError!Dir {
         if (std.debug.runtime_safety) for (sub_path) |byte| assert(byte != 0);
-        if (builtin.os == .windows) {
+        if (builtin.os.tag == .windows) {
             const sub_path_w = try os.windows.sliceToPrefixedFileW(sub_path);
             return self.openDirTraverseW(&sub_path_w);
         }
@@ -919,7 +919,7 @@ pub const Dir = struct {
     /// Asserts that the path parameter has no null bytes.
     pub fn openDirList(self: Dir, sub_path: []const u8) OpenError!Dir {
         if (std.debug.runtime_safety) for (sub_path) |byte| assert(byte != 0);
-        if (builtin.os == .windows) {
+        if (builtin.os.tag == .windows) {
             const sub_path_w = try os.windows.sliceToPrefixedFileW(sub_path);
             return self.openDirListW(&sub_path_w);
         }
@@ -930,7 +930,7 @@ pub const Dir = struct {
 
     /// Same as `openDirTraverse` except the parameter is null-terminated.
     pub fn openDirTraverseC(self: Dir, sub_path_c: [*:0]const u8) OpenError!Dir {
-        if (builtin.os == .windows) {
+        if (builtin.os.tag == .windows) {
             const sub_path_w = try os.windows.cStrToPrefixedFileW(sub_path_c);
             return self.openDirTraverseW(&sub_path_w);
         } else {
@@ -941,7 +941,7 @@ pub const Dir = struct {
 
     /// Same as `openDirList` except the parameter is null-terminated.
     pub fn openDirListC(self: Dir, sub_path_c: [*:0]const u8) OpenError!Dir {
-        if (builtin.os == .windows) {
+        if (builtin.os.tag == .windows) {
             const sub_path_w = try os.windows.cStrToPrefixedFileW(sub_path_c);
             return self.openDirListW(&sub_path_w);
         } else {
@@ -1083,7 +1083,7 @@ pub const Dir = struct {
     /// Asserts that the path parameter has no null bytes.
     pub fn deleteDir(self: Dir, sub_path: []const u8) DeleteDirError!void {
         if (std.debug.runtime_safety) for (sub_path) |byte| assert(byte != 0);
-        if (builtin.os == .windows) {
+        if (builtin.os.tag == .windows) {
             const sub_path_w = try os.windows.sliceToPrefixedFileW(sub_path);
             return self.deleteDirW(&sub_path_w);
         }
@@ -1340,7 +1340,7 @@ pub const Dir = struct {
     /// For example, instead of testing if a file exists and then opening it, just
     /// open it and handle the error for file not found.
     pub fn access(self: Dir, sub_path: []const u8, flags: File.OpenFlags) AccessError!void {
-        if (builtin.os == .windows) {
+        if (builtin.os.tag == .windows) {
             const sub_path_w = try os.windows.sliceToPrefixedFileW(sub_path);
             return self.accessW(&sub_path_w, flags);
         }
@@ -1350,7 +1350,7 @@ pub const Dir = struct {
 
     /// Same as `access` except the path parameter is null-terminated.
     pub fn accessZ(self: Dir, sub_path: [*:0]const u8, flags: File.OpenFlags) AccessError!void {
-        if (builtin.os == .windows) {
+        if (builtin.os.tag == .windows) {
             const sub_path_w = try os.windows.cStrToPrefixedFileW(sub_path);
             return self.accessW(&sub_path_w, flags);
         }
@@ -1381,7 +1381,7 @@ pub const Dir = struct {
 /// Closing the returned `Dir` is checked illegal behavior. Iterating over the result is illegal behavior.
 /// On POSIX targets, this function is comptime-callable.
 pub fn cwd() Dir {
-    if (builtin.os == .windows) {
+    if (builtin.os.tag == .windows) {
         return Dir{ .fd = os.windows.peb().ProcessParameters.CurrentDirectory.Handle };
     } else {
         return Dir{ .fd = os.AT_FDCWD };
@@ -1560,10 +1560,10 @@ pub fn readLinkC(pathname_c: [*]const u8, buffer: *[MAX_PATH_BYTES]u8) ![]u8 {
 pub const OpenSelfExeError = os.OpenError || os.windows.CreateFileError || SelfExePathError;
 
 pub fn openSelfExe() OpenSelfExeError!File {
-    if (builtin.os == .linux) {
+    if (builtin.os.tag == .linux) {
         return openFileAbsoluteC("/proc/self/exe", .{});
     }
-    if (builtin.os == .windows) {
+    if (builtin.os.tag == .windows) {
         const wide_slice = selfExePathW();
         const prefixed_path_w = try os.windows.wToPrefixedFileW(wide_slice);
         return cwd().openReadW(&prefixed_path_w);
@@ -1575,7 +1575,7 @@ pub fn openSelfExe() OpenSelfExeError!File {
 }
 
 test "openSelfExe" {
-    switch (builtin.os) {
+    switch (builtin.os.tag) {
         .linux, .macosx, .ios, .windows, .freebsd, .dragonfly => (try openSelfExe()).close(),
         else => return error.SkipZigTest, // Unsupported OS.
     }
@@ -1600,7 +1600,7 @@ pub fn selfExePath(out_buffer: *[MAX_PATH_BYTES]u8) SelfExePathError![]u8 {
         if (rc != 0) return error.NameTooLong;
         return mem.toSlice(u8, @ptrCast([*:0]u8, out_buffer));
     }
-    switch (builtin.os) {
+    switch (builtin.os.tag) {
         .linux => return os.readlinkC("/proc/self/exe", out_buffer),
         .freebsd, .dragonfly => {
             var mib = [4]c_int{ os.CTL_KERN, os.KERN_PROC, os.KERN_PROC_PATHNAME, -1 };
@@ -1642,7 +1642,7 @@ pub fn selfExeDirPathAlloc(allocator: *Allocator) ![]u8 {
 /// Get the directory path that contains the current executable.
 /// Returned value is a slice of out_buffer.
 pub fn selfExeDirPath(out_buffer: *[MAX_PATH_BYTES]u8) SelfExePathError![]const u8 {
-    if (builtin.os == .linux) {
+    if (builtin.os.tag == .linux) {
         // If the currently executing binary has been deleted,
         // the file path looks something like `/a/b/c/exe (deleted)`
         // This path cannot be opened, but it's valid for determining the directory
lib/std/heap.zig
@@ -36,7 +36,7 @@ fn cShrink(self: *Allocator, old_mem: []u8, old_align: u29, new_size: usize, new
 /// Thread-safe and lock-free.
 pub const page_allocator = if (std.Target.current.isWasm())
     &wasm_page_allocator_state
-else if (std.Target.current.getOs() == .freestanding)
+else if (std.Target.current.os.tag == .freestanding)
     root.os.heap.page_allocator
 else
     &page_allocator_state;
@@ -57,7 +57,7 @@ const PageAllocator = struct {
     fn alloc(allocator: *Allocator, n: usize, alignment: u29) error{OutOfMemory}![]u8 {
         if (n == 0) return &[0]u8{};
 
-        if (builtin.os == .windows) {
+        if (builtin.os.tag == .windows) {
             const w = os.windows;
 
             // Although officially it's at least aligned to page boundary,
@@ -143,7 +143,7 @@ const PageAllocator = struct {
 
     fn shrink(allocator: *Allocator, old_mem_unaligned: []u8, old_align: u29, new_size: usize, new_align: u29) []u8 {
         const old_mem = @alignCast(mem.page_size, old_mem_unaligned);
-        if (builtin.os == .windows) {
+        if (builtin.os.tag == .windows) {
             const w = os.windows;
             if (new_size == 0) {
                 // From the docs:
@@ -183,7 +183,7 @@ const PageAllocator = struct {
 
     fn realloc(allocator: *Allocator, old_mem_unaligned: []u8, old_align: u29, new_size: usize, new_align: u29) ![]u8 {
         const old_mem = @alignCast(mem.page_size, old_mem_unaligned);
-        if (builtin.os == .windows) {
+        if (builtin.os.tag == .windows) {
             if (old_mem.len == 0) {
                 return alloc(allocator, new_size, new_align);
             }
@@ -412,7 +412,7 @@ const WasmPageAllocator = struct {
     }
 };
 
-pub const HeapAllocator = switch (builtin.os) {
+pub const HeapAllocator = switch (builtin.os.tag) {
     .windows => struct {
         allocator: Allocator,
         heap_handle: ?HeapHandle,
@@ -855,7 +855,7 @@ test "PageAllocator" {
         try testAllocatorAlignedShrink(allocator);
     }
 
-    if (builtin.os == .windows) {
+    if (builtin.os.tag == .windows) {
         // Trying really large alignment. As mentionned in the implementation,
         // VirtualAlloc returns 64K aligned addresses. We want to make sure
         // PageAllocator works beyond that, as it's not tested by
@@ -868,7 +868,7 @@ test "PageAllocator" {
 }
 
 test "HeapAllocator" {
-    if (builtin.os == .windows) {
+    if (builtin.os.tag == .windows) {
         var heap_allocator = HeapAllocator.init();
         defer heap_allocator.deinit();
 
lib/std/io.zig
@@ -35,7 +35,7 @@ else
 pub const is_async = mode != .blocking;
 
 fn getStdOutHandle() os.fd_t {
-    if (builtin.os == .windows) {
+    if (builtin.os.tag == .windows) {
         return os.windows.peb().ProcessParameters.hStdOutput;
     }
 
@@ -54,7 +54,7 @@ pub fn getStdOut() File {
 }
 
 fn getStdErrHandle() os.fd_t {
-    if (builtin.os == .windows) {
+    if (builtin.os.tag == .windows) {
         return os.windows.peb().ProcessParameters.hStdError;
     }
 
@@ -74,7 +74,7 @@ pub fn getStdErr() File {
 }
 
 fn getStdInHandle() os.fd_t {
-    if (builtin.os == .windows) {
+    if (builtin.os.tag == .windows) {
         return os.windows.peb().ProcessParameters.hStdInput;
     }
 
lib/std/mutex.zig
@@ -73,7 +73,7 @@ pub const Mutex = if (builtin.single_threaded)
             return self.tryAcquire() orelse @panic("deadlock detected");
         }
     }
-else if (builtin.os == .windows)
+else if (builtin.os.tag == .windows)
 // https://locklessinc.com/articles/keyed_events/
     extern union {
         locked: u8,
@@ -161,7 +161,7 @@ else if (builtin.os == .windows)
             }
         };
     }
-else if (builtin.link_libc or builtin.os == .linux)
+else if (builtin.link_libc or builtin.os.tag == .linux)
 // stack-based version of https://github.com/Amanieu/parking_lot/blob/master/core/src/word_lock.rs
     struct {
         state: usize,
lib/std/net.zig
@@ -501,7 +501,7 @@ pub fn getAddressList(allocator: *mem.Allocator, name: []const u8, port: u16) !*
 
         return result;
     }
-    if (builtin.os == .linux) {
+    if (builtin.os.tag == .linux) {
         const flags = std.c.AI_NUMERICSERV;
         const family = os.AF_UNSPEC;
         var lookup_addrs = std.ArrayList(LookupAddr).init(allocator);
lib/std/os.zig
@@ -56,7 +56,7 @@ pub const system = if (@hasDecl(root, "os") and root.os != @This())
     root.os.system
 else if (builtin.link_libc)
     std.c
-else switch (builtin.os) {
+else switch (builtin.os.tag) {
     .macosx, .ios, .watchos, .tvos => darwin,
     .freebsd => freebsd,
     .linux => linux,
@@ -93,10 +93,10 @@ pub const errno = system.getErrno;
 /// must call `fsync` before `close`.
 /// Note: The Zig standard library does not support POSIX thread cancellation.
 pub fn close(fd: fd_t) void {
-    if (builtin.os == .windows) {
+    if (builtin.os.tag == .windows) {
         return windows.CloseHandle(fd);
     }
-    if (builtin.os == .wasi) {
+    if (builtin.os.tag == .wasi) {
         _ = wasi.fd_close(fd);
     }
     if (comptime std.Target.current.isDarwin()) {
@@ -121,12 +121,12 @@ pub const GetRandomError = OpenError;
 /// appropriate OS-specific library call. Otherwise it uses the zig standard
 /// library implementation.
 pub fn getrandom(buffer: []u8) GetRandomError!void {
-    if (builtin.os == .windows) {
+    if (builtin.os.tag == .windows) {
         return windows.RtlGenRandom(buffer);
     }
-    if (builtin.os == .linux or builtin.os == .freebsd) {
+    if (builtin.os.tag == .linux or builtin.os.tag == .freebsd) {
         var buf = buffer;
-        const use_c = builtin.os != .linux or
+        const use_c = builtin.os.tag != .linux or
             std.c.versionCheck(builtin.Version{ .major = 2, .minor = 25, .patch = 0 }).ok;
 
         while (buf.len != 0) {
@@ -153,7 +153,7 @@ pub fn getrandom(buffer: []u8) GetRandomError!void {
         }
         return;
     }
-    if (builtin.os == .wasi) {
+    if (builtin.os.tag == .wasi) {
         switch (wasi.random_get(buffer.ptr, buffer.len)) {
             0 => return,
             else => |err| return unexpectedErrno(err),
@@ -188,13 +188,13 @@ pub fn abort() noreturn {
     // MSVCRT abort() sometimes opens a popup window which is undesirable, so
     // even when linking libc on Windows we use our own abort implementation.
     // See https://github.com/ziglang/zig/issues/2071 for more details.
-    if (builtin.os == .windows) {
+    if (builtin.os.tag == .windows) {
         if (builtin.mode == .Debug) {
             @breakpoint();
         }
         windows.kernel32.ExitProcess(3);
     }
-    if (!builtin.link_libc and builtin.os == .linux) {
+    if (!builtin.link_libc and builtin.os.tag == .linux) {
         raise(SIGABRT) catch {};
 
         // TODO the rest of the implementation of abort() from musl libc here
@@ -202,10 +202,10 @@ pub fn abort() noreturn {
         raise(SIGKILL) catch {};
         exit(127);
     }
-    if (builtin.os == .uefi) {
+    if (builtin.os.tag == .uefi) {
         exit(0); // TODO choose appropriate exit code
     }
-    if (builtin.os == .wasi) {
+    if (builtin.os.tag == .wasi) {
         @breakpoint();
         exit(1);
     }
@@ -223,7 +223,7 @@ pub fn raise(sig: u8) RaiseError!void {
         }
     }
 
-    if (builtin.os == .linux) {
+    if (builtin.os.tag == .linux) {
         var set: linux.sigset_t = undefined;
         // block application signals
         _ = linux.sigprocmask(SIG_BLOCK, &linux.app_mask, &set);
@@ -260,16 +260,16 @@ pub fn exit(status: u8) noreturn {
     if (builtin.link_libc) {
         system.exit(status);
     }
-    if (builtin.os == .windows) {
+    if (builtin.os.tag == .windows) {
         windows.kernel32.ExitProcess(status);
     }
-    if (builtin.os == .wasi) {
+    if (builtin.os.tag == .wasi) {
         wasi.proc_exit(status);
     }
-    if (builtin.os == .linux and !builtin.single_threaded) {
+    if (builtin.os.tag == .linux and !builtin.single_threaded) {
         linux.exit_group(status);
     }
-    if (builtin.os == .uefi) {
+    if (builtin.os.tag == .uefi) {
         // exit() is only avaliable if exitBootServices() has not been called yet.
         // This call to exit should not fail, so we don't care about its return value.
         if (uefi.system_table.boot_services) |bs| {
@@ -299,11 +299,11 @@ pub const ReadError = error{
 /// If the application has a global event loop enabled, EAGAIN is handled
 /// via the event loop. Otherwise EAGAIN results in error.WouldBlock.
 pub fn read(fd: fd_t, buf: []u8) ReadError!usize {
-    if (builtin.os == .windows) {
+    if (builtin.os.tag == .windows) {
         return windows.ReadFile(fd, buf, null);
     }
 
-    if (builtin.os == .wasi and !builtin.link_libc) {
+    if (builtin.os.tag == .wasi and !builtin.link_libc) {
         const iovs = [1]iovec{iovec{
             .iov_base = buf.ptr,
             .iov_len = buf.len,
@@ -352,7 +352,7 @@ pub fn read(fd: fd_t, buf: []u8) ReadError!usize {
 /// * Windows
 /// On these systems, the read races with concurrent writes to the same file descriptor.
 pub fn readv(fd: fd_t, iov: []const iovec) ReadError!usize {
-    if (builtin.os == .windows) {
+    if (builtin.os.tag == .windows) {
         // TODO batch these into parallel requests
         var off: usize = 0;
         var iov_i: usize = 0;
@@ -406,7 +406,7 @@ pub fn readv(fd: fd_t, iov: []const iovec) ReadError!usize {
 /// On Windows, if the application has a global event loop enabled, I/O Completion Ports are
 /// used to perform the I/O. `error.WouldBlock` is not possible on Windows.
 pub fn pread(fd: fd_t, buf: []u8, offset: u64) ReadError!usize {
-    if (builtin.os == .windows) {
+    if (builtin.os.tag == .windows) {
         return windows.ReadFile(fd, buf, offset);
     }
 
@@ -493,7 +493,7 @@ pub fn preadv(fd: fd_t, iov: []const iovec, offset: u64) ReadError!usize {
         }
     }
 
-    if (builtin.os == .windows) {
+    if (builtin.os.tag == .windows) {
         // TODO batch these into parallel requests
         var off: usize = 0;
         var iov_i: usize = 0;
@@ -557,11 +557,11 @@ pub const WriteError = error{
 /// If the application has a global event loop enabled, EAGAIN is handled
 /// via the event loop. Otherwise EAGAIN results in error.WouldBlock.
 pub fn write(fd: fd_t, bytes: []const u8) WriteError!void {
-    if (builtin.os == .windows) {
+    if (builtin.os.tag == .windows) {
         return windows.WriteFile(fd, bytes, null);
     }
 
-    if (builtin.os == .wasi and !builtin.link_libc) {
+    if (builtin.os.tag == .wasi and !builtin.link_libc) {
         const ciovs = [1]iovec_const{iovec_const{
             .iov_base = bytes.ptr,
             .iov_len = bytes.len,
@@ -1129,7 +1129,7 @@ pub fn getenv(key: []const u8) ?[]const u8 {
         }
         return null;
     }
-    if (builtin.os == .windows) {
+    if (builtin.os.tag == .windows) {
         @compileError("std.os.getenv is unavailable for Windows because environment string is in WTF-16 format. See std.process.getEnvVarOwned for cross-platform API or std.os.getenvW for Windows-specific API.");
     }
     // TODO see https://github.com/ziglang/zig/issues/4524
@@ -1158,7 +1158,7 @@ pub fn getenvZ(key: [*:0]const u8) ?[]const u8 {
         const value = system.getenv(key) orelse return null;
         return mem.toSliceConst(u8, value);
     }
-    if (builtin.os == .windows) {
+    if (builtin.os.tag == .windows) {
         @compileError("std.os.getenvZ is unavailable for Windows because environment string is in WTF-16 format. See std.process.getEnvVarOwned for cross-platform API or std.os.getenvW for Windows-specific API.");
     }
     return getenv(mem.toSliceConst(u8, key));
@@ -1167,7 +1167,7 @@ pub fn getenvZ(key: [*:0]const u8) ?[]const u8 {
 /// Windows-only. Get an environment variable with a null-terminated, WTF-16 encoded name.
 /// See also `getenv`.
 pub fn getenvW(key: [*:0]const u16) ?[:0]const u16 {
-    if (builtin.os != .windows) {
+    if (builtin.os.tag != .windows) {
         @compileError("std.os.getenvW is a Windows-only API");
     }
     const key_slice = mem.toSliceConst(u16, key);
@@ -1199,7 +1199,7 @@ pub const GetCwdError = error{
 
 /// The result is a slice of out_buffer, indexed from 0.
 pub fn getcwd(out_buffer: []u8) GetCwdError![]u8 {
-    if (builtin.os == .windows) {
+    if (builtin.os.tag == .windows) {
         return windows.GetCurrentDirectory(out_buffer);
     }
 
@@ -1240,7 +1240,7 @@ pub const SymLinkError = error{
 /// If `sym_link_path` exists, it will not be overwritten.
 /// See also `symlinkC` and `symlinkW`.
 pub fn symlink(target_path: []const u8, sym_link_path: []const u8) SymLinkError!void {
-    if (builtin.os == .windows) {
+    if (builtin.os.tag == .windows) {
         const target_path_w = try windows.sliceToPrefixedFileW(target_path);
         const sym_link_path_w = try windows.sliceToPrefixedFileW(sym_link_path);
         return windows.CreateSymbolicLinkW(&sym_link_path_w, &target_path_w, 0);
@@ -1254,7 +1254,7 @@ pub fn symlink(target_path: []const u8, sym_link_path: []const u8) SymLinkError!
 /// This is the same as `symlink` except the parameters are null-terminated pointers.
 /// See also `symlink`.
 pub fn symlinkC(target_path: [*:0]const u8, sym_link_path: [*:0]const u8) SymLinkError!void {
-    if (builtin.os == .windows) {
+    if (builtin.os.tag == .windows) {
         const target_path_w = try windows.cStrToPrefixedFileW(target_path);
         const sym_link_path_w = try windows.cStrToPrefixedFileW(sym_link_path);
         return windows.CreateSymbolicLinkW(&sym_link_path_w, &target_path_w, 0);
@@ -1329,7 +1329,7 @@ pub const UnlinkError = error{
 /// Delete a name and possibly the file it refers to.
 /// See also `unlinkC`.
 pub fn unlink(file_path: []const u8) UnlinkError!void {
-    if (builtin.os == .windows) {
+    if (builtin.os.tag == .windows) {
         const file_path_w = try windows.sliceToPrefixedFileW(file_path);
         return windows.DeleteFileW(&file_path_w);
     } else {
@@ -1340,7 +1340,7 @@ pub fn unlink(file_path: []const u8) UnlinkError!void {
 
 /// Same as `unlink` except the parameter is a null terminated UTF8-encoded string.
 pub fn unlinkC(file_path: [*:0]const u8) UnlinkError!void {
-    if (builtin.os == .windows) {
+    if (builtin.os.tag == .windows) {
         const file_path_w = try windows.cStrToPrefixedFileW(file_path);
         return windows.DeleteFileW(&file_path_w);
     }
@@ -1372,7 +1372,7 @@ pub const UnlinkatError = UnlinkError || error{
 /// Asserts that the path parameter has no null bytes.
 pub fn unlinkat(dirfd: fd_t, file_path: []const u8, flags: u32) UnlinkatError!void {
     if (std.debug.runtime_safety) for (file_path) |byte| assert(byte != 0);
-    if (builtin.os == .windows) {
+    if (builtin.os.tag == .windows) {
         const file_path_w = try windows.sliceToPrefixedFileW(file_path);
         return unlinkatW(dirfd, &file_path_w, flags);
     }
@@ -1382,7 +1382,7 @@ pub fn unlinkat(dirfd: fd_t, file_path: []const u8, flags: u32) UnlinkatError!vo
 
 /// Same as `unlinkat` but `file_path` is a null-terminated string.
 pub fn unlinkatC(dirfd: fd_t, file_path_c: [*:0]const u8, flags: u32) UnlinkatError!void {
-    if (builtin.os == .windows) {
+    if (builtin.os.tag == .windows) {
         const file_path_w = try windows.cStrToPrefixedFileW(file_path_c);
         return unlinkatW(dirfd, &file_path_w, flags);
     }
@@ -1493,7 +1493,7 @@ const RenameError = error{
 
 /// Change the name or location of a file.
 pub fn rename(old_path: []const u8, new_path: []const u8) RenameError!void {
-    if (builtin.os == .windows) {
+    if (builtin.os.tag == .windows) {
         const old_path_w = try windows.sliceToPrefixedFileW(old_path);
         const new_path_w = try windows.sliceToPrefixedFileW(new_path);
         return renameW(&old_path_w, &new_path_w);
@@ -1506,7 +1506,7 @@ pub fn rename(old_path: []const u8, new_path: []const u8) RenameError!void {
 
 /// Same as `rename` except the parameters are null-terminated byte arrays.
 pub fn renameC(old_path: [*:0]const u8, new_path: [*:0]const u8) RenameError!void {
-    if (builtin.os == .windows) {
+    if (builtin.os.tag == .windows) {
         const old_path_w = try windows.cStrToPrefixedFileW(old_path);
         const new_path_w = try windows.cStrToPrefixedFileW(new_path);
         return renameW(&old_path_w, &new_path_w);
@@ -1561,7 +1561,7 @@ pub const MakeDirError = error{
 /// Create a directory.
 /// `mode` is ignored on Windows.
 pub fn mkdir(dir_path: []const u8, mode: u32) MakeDirError!void {
-    if (builtin.os == .windows) {
+    if (builtin.os.tag == .windows) {
         const dir_path_w = try windows.sliceToPrefixedFileW(dir_path);
         return windows.CreateDirectoryW(&dir_path_w, null);
     } else {
@@ -1572,7 +1572,7 @@ pub fn mkdir(dir_path: []const u8, mode: u32) MakeDirError!void {
 
 /// Same as `mkdir` but the parameter is a null-terminated UTF8-encoded string.
 pub fn mkdirC(dir_path: [*:0]const u8, mode: u32) MakeDirError!void {
-    if (builtin.os == .windows) {
+    if (builtin.os.tag == .windows) {
         const dir_path_w = try windows.cStrToPrefixedFileW(dir_path);
         return windows.CreateDirectoryW(&dir_path_w, null);
     }
@@ -1611,7 +1611,7 @@ pub const DeleteDirError = error{
 
 /// Deletes an empty directory.
 pub fn rmdir(dir_path: []const u8) DeleteDirError!void {
-    if (builtin.os == .windows) {
+    if (builtin.os.tag == .windows) {
         const dir_path_w = try windows.sliceToPrefixedFileW(dir_path);
         return windows.RemoveDirectoryW(&dir_path_w);
     } else {
@@ -1622,7 +1622,7 @@ pub fn rmdir(dir_path: []const u8) DeleteDirError!void {
 
 /// Same as `rmdir` except the parameter is null-terminated.
 pub fn rmdirC(dir_path: [*:0]const u8) DeleteDirError!void {
-    if (builtin.os == .windows) {
+    if (builtin.os.tag == .windows) {
         const dir_path_w = try windows.cStrToPrefixedFileW(dir_path);
         return windows.RemoveDirectoryW(&dir_path_w);
     }
@@ -1658,7 +1658,7 @@ pub const ChangeCurDirError = error{
 /// Changes the current working directory of the calling process.
 /// `dir_path` is recommended to be a UTF-8 encoded string.
 pub fn chdir(dir_path: []const u8) ChangeCurDirError!void {
-    if (builtin.os == .windows) {
+    if (builtin.os.tag == .windows) {
         const dir_path_w = try windows.sliceToPrefixedFileW(dir_path);
         @compileError("TODO implement chdir for Windows");
     } else {
@@ -1669,7 +1669,7 @@ pub fn chdir(dir_path: []const u8) ChangeCurDirError!void {
 
 /// Same as `chdir` except the parameter is null-terminated.
 pub fn chdirC(dir_path: [*:0]const u8) ChangeCurDirError!void {
-    if (builtin.os == .windows) {
+    if (builtin.os.tag == .windows) {
         const dir_path_w = try windows.cStrToPrefixedFileW(dir_path);
         @compileError("TODO implement chdir for Windows");
     }
@@ -1700,7 +1700,7 @@ pub const ReadLinkError = error{
 /// Read value of a symbolic link.
 /// The return value is a slice of `out_buffer` from index 0.
 pub fn readlink(file_path: []const u8, out_buffer: []u8) ReadLinkError![]u8 {
-    if (builtin.os == .windows) {
+    if (builtin.os.tag == .windows) {
         const file_path_w = try windows.sliceToPrefixedFileW(file_path);
         @compileError("TODO implement readlink for Windows");
     } else {
@@ -1711,7 +1711,7 @@ pub fn readlink(file_path: []const u8, out_buffer: []u8) ReadLinkError![]u8 {
 
 /// Same as `readlink` except `file_path` is null-terminated.
 pub fn readlinkC(file_path: [*:0]const u8, out_buffer: []u8) ReadLinkError![]u8 {
-    if (builtin.os == .windows) {
+    if (builtin.os.tag == .windows) {
         const file_path_w = try windows.cStrToPrefixedFileW(file_path);
         @compileError("TODO implement readlink for Windows");
     }
@@ -1732,7 +1732,7 @@ pub fn readlinkC(file_path: [*:0]const u8, out_buffer: []u8) ReadLinkError![]u8
 }
 
 pub fn readlinkatC(dirfd: fd_t, file_path: [*:0]const u8, out_buffer: []u8) ReadLinkError![]u8 {
-    if (builtin.os == .windows) {
+    if (builtin.os.tag == .windows) {
         const file_path_w = try windows.cStrToPrefixedFileW(file_path);
         @compileError("TODO implement readlink for Windows");
     }
@@ -1800,7 +1800,7 @@ pub fn setregid(rgid: u32, egid: u32) SetIdError!void {
 
 /// Test whether a file descriptor refers to a terminal.
 pub fn isatty(handle: fd_t) bool {
-    if (builtin.os == .windows) {
+    if (builtin.os.tag == .windows) {
         if (isCygwinPty(handle))
             return true;
 
@@ -1810,7 +1810,7 @@ pub fn isatty(handle: fd_t) bool {
     if (builtin.link_libc) {
         return system.isatty(handle) != 0;
     }
-    if (builtin.os == .wasi) {
+    if (builtin.os.tag == .wasi) {
         var statbuf: fdstat_t = undefined;
         const err = system.fd_fdstat_get(handle, &statbuf);
         if (err != 0) {
@@ -1828,7 +1828,7 @@ pub fn isatty(handle: fd_t) bool {
 
         return true;
     }
-    if (builtin.os == .linux) {
+    if (builtin.os.tag == .linux) {
         var wsz: linux.winsize = undefined;
         return linux.syscall3(linux.SYS_ioctl, @bitCast(usize, @as(isize, handle)), linux.TIOCGWINSZ, @ptrToInt(&wsz)) == 0;
     }
@@ -1836,7 +1836,7 @@ pub fn isatty(handle: fd_t) bool {
 }
 
 pub fn isCygwinPty(handle: fd_t) bool {
-    if (builtin.os != .windows) return false;
+    if (builtin.os.tag != .windows) return false;
 
     const size = @sizeOf(windows.FILE_NAME_INFO);
     var name_info_bytes align(@alignOf(windows.FILE_NAME_INFO)) = [_]u8{0} ** (size + windows.MAX_PATH);
@@ -2589,7 +2589,7 @@ pub const AccessError = error{
 /// check user's permissions for a file
 /// TODO currently this assumes `mode` is `F_OK` on Windows.
 pub fn access(path: []const u8, mode: u32) AccessError!void {
-    if (builtin.os == .windows) {
+    if (builtin.os.tag == .windows) {
         const path_w = try windows.sliceToPrefixedFileW(path);
         _ = try windows.GetFileAttributesW(&path_w);
         return;
@@ -2603,7 +2603,7 @@ pub const accessC = accessZ;
 
 /// Same as `access` except `path` is null-terminated.
 pub fn accessZ(path: [*:0]const u8, mode: u32) AccessError!void {
-    if (builtin.os == .windows) {
+    if (builtin.os.tag == .windows) {
         const path_w = try windows.cStrToPrefixedFileW(path);
         _ = try windows.GetFileAttributesW(&path_w);
         return;
@@ -2644,7 +2644,7 @@ pub fn accessW(path: [*:0]const u16, mode: u32) windows.GetFileAttributesError!v
 /// Check user's permissions for a file, based on an open directory handle.
 /// TODO currently this ignores `mode` and `flags` on Windows.
 pub fn faccessat(dirfd: fd_t, path: []const u8, mode: u32, flags: u32) AccessError!void {
-    if (builtin.os == .windows) {
+    if (builtin.os.tag == .windows) {
         const path_w = try windows.sliceToPrefixedFileW(path);
         return faccessatW(dirfd, &path_w, mode, flags);
     }
@@ -2654,7 +2654,7 @@ pub fn faccessat(dirfd: fd_t, path: []const u8, mode: u32, flags: u32) AccessErr
 
 /// Same as `faccessat` except the path parameter is null-terminated.
 pub fn faccessatZ(dirfd: fd_t, path: [*:0]const u8, mode: u32, flags: u32) AccessError!void {
-    if (builtin.os == .windows) {
+    if (builtin.os.tag == .windows) {
         const path_w = try windows.cStrToPrefixedFileW(path);
         return faccessatW(dirfd, &path_w, mode, flags);
     }
@@ -2811,7 +2811,7 @@ pub const SeekError = error{Unseekable} || UnexpectedError;
 
 /// Repositions read/write file offset relative to the beginning.
 pub fn lseek_SET(fd: fd_t, offset: u64) SeekError!void {
-    if (builtin.os == .linux and !builtin.link_libc and @sizeOf(usize) == 4) {
+    if (builtin.os.tag == .linux and !builtin.link_libc and @sizeOf(usize) == 4) {
         var result: u64 = undefined;
         switch (errno(system.llseek(fd, offset, &result, SEEK_SET))) {
             0 => return,
@@ -2823,7 +2823,7 @@ pub fn lseek_SET(fd: fd_t, offset: u64) SeekError!void {
             else => |err| return unexpectedErrno(err),
         }
     }
-    if (builtin.os == .windows) {
+    if (builtin.os.tag == .windows) {
         return windows.SetFilePointerEx_BEGIN(fd, offset);
     }
     const ipos = @bitCast(i64, offset); // the OS treats this as unsigned
@@ -2840,7 +2840,7 @@ pub fn lseek_SET(fd: fd_t, offset: u64) SeekError!void {
 
 /// Repositions read/write file offset relative to the current offset.
 pub fn lseek_CUR(fd: fd_t, offset: i64) SeekError!void {
-    if (builtin.os == .linux and !builtin.link_libc and @sizeOf(usize) == 4) {
+    if (builtin.os.tag == .linux and !builtin.link_libc and @sizeOf(usize) == 4) {
         var result: u64 = undefined;
         switch (errno(system.llseek(fd, @bitCast(u64, offset), &result, SEEK_CUR))) {
             0 => return,
@@ -2852,7 +2852,7 @@ pub fn lseek_CUR(fd: fd_t, offset: i64) SeekError!void {
             else => |err| return unexpectedErrno(err),
         }
     }
-    if (builtin.os == .windows) {
+    if (builtin.os.tag == .windows) {
         return windows.SetFilePointerEx_CURRENT(fd, offset);
     }
     switch (errno(system.lseek(fd, offset, SEEK_CUR))) {
@@ -2868,7 +2868,7 @@ pub fn lseek_CUR(fd: fd_t, offset: i64) SeekError!void {
 
 /// Repositions read/write file offset relative to the end.
 pub fn lseek_END(fd: fd_t, offset: i64) SeekError!void {
-    if (builtin.os == .linux and !builtin.link_libc and @sizeOf(usize) == 4) {
+    if (builtin.os.tag == .linux and !builtin.link_libc and @sizeOf(usize) == 4) {
         var result: u64 = undefined;
         switch (errno(system.llseek(fd, @bitCast(u64, offset), &result, SEEK_END))) {
             0 => return,
@@ -2880,7 +2880,7 @@ pub fn lseek_END(fd: fd_t, offset: i64) SeekError!void {
             else => |err| return unexpectedErrno(err),
         }
     }
-    if (builtin.os == .windows) {
+    if (builtin.os.tag == .windows) {
         return windows.SetFilePointerEx_END(fd, offset);
     }
     switch (errno(system.lseek(fd, offset, SEEK_END))) {
@@ -2896,7 +2896,7 @@ pub fn lseek_END(fd: fd_t, offset: i64) SeekError!void {
 
 /// Returns the read/write file offset relative to the beginning.
 pub fn lseek_CUR_get(fd: fd_t) SeekError!u64 {
-    if (builtin.os == .linux and !builtin.link_libc and @sizeOf(usize) == 4) {
+    if (builtin.os.tag == .linux and !builtin.link_libc and @sizeOf(usize) == 4) {
         var result: u64 = undefined;
         switch (errno(system.llseek(fd, 0, &result, SEEK_CUR))) {
             0 => return result,
@@ -2908,7 +2908,7 @@ pub fn lseek_CUR_get(fd: fd_t) SeekError!u64 {
             else => |err| return unexpectedErrno(err),
         }
     }
-    if (builtin.os == .windows) {
+    if (builtin.os.tag == .windows) {
         return windows.SetFilePointerEx_CURRENT_get(fd);
     }
     const rc = system.lseek(fd, 0, SEEK_CUR);
@@ -2957,7 +2957,7 @@ pub const RealPathError = error{
 /// The return value is a slice of `out_buffer`, but not necessarily from the beginning.
 /// See also `realpathC` and `realpathW`.
 pub fn realpath(pathname: []const u8, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 {
-    if (builtin.os == .windows) {
+    if (builtin.os.tag == .windows) {
         const pathname_w = try windows.sliceToPrefixedFileW(pathname);
         return realpathW(&pathname_w, out_buffer);
     }
@@ -2967,11 +2967,11 @@ pub fn realpath(pathname: []const u8, out_buffer: *[MAX_PATH_BYTES]u8) RealPathE
 
 /// Same as `realpath` except `pathname` is null-terminated.
 pub fn realpathC(pathname: [*:0]const u8, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 {
-    if (builtin.os == .windows) {
+    if (builtin.os.tag == .windows) {
         const pathname_w = try windows.cStrToPrefixedFileW(pathname);
         return realpathW(&pathname_w, out_buffer);
     }
-    if (builtin.os == .linux and !builtin.link_libc) {
+    if (builtin.os.tag == .linux and !builtin.link_libc) {
         const fd = try openC(pathname, linux.O_PATH | linux.O_NONBLOCK | linux.O_CLOEXEC, 0);
         defer close(fd);
 
@@ -3121,7 +3121,7 @@ pub fn dl_iterate_phdr(
 pub const ClockGetTimeError = error{UnsupportedClock} || UnexpectedError;
 
 pub fn clock_gettime(clk_id: i32, tp: *timespec) ClockGetTimeError!void {
-    if (comptime std.Target.current.getOs() == .wasi) {
+    if (std.Target.current.os.tag == .wasi) {
         var ts: timestamp_t = undefined;
         switch (system.clock_time_get(@bitCast(u32, clk_id), 1, &ts)) {
             0 => {
@@ -3144,7 +3144,7 @@ pub fn clock_gettime(clk_id: i32, tp: *timespec) ClockGetTimeError!void {
 }
 
 pub fn clock_getres(clk_id: i32, res: *timespec) ClockGetTimeError!void {
-    if (comptime std.Target.current.getOs() == .wasi) {
+    if (std.Target.current.os.tag == .wasi) {
         var ts: timestamp_t = undefined;
         switch (system.clock_res_get(@bitCast(u32, clk_id), &ts)) {
             0 => res.* = .{
@@ -3222,7 +3222,7 @@ pub const SigaltstackError = error{
 } || UnexpectedError;
 
 pub fn sigaltstack(ss: ?*stack_t, old_ss: ?*stack_t) SigaltstackError!void {
-    if (builtin.os == .windows or builtin.os == .uefi or builtin.os == .wasi)
+    if (builtin.os.tag == .windows or builtin.os.tag == .uefi or builtin.os.tag == .wasi)
         @compileError("std.os.sigaltstack not available for this target");
 
     switch (errno(system.sigaltstack(ss, old_ss))) {
@@ -3294,7 +3294,7 @@ pub fn gethostname(name_buffer: *[HOST_NAME_MAX]u8) GetHostNameError![]u8 {
             else => |err| return unexpectedErrno(err),
         }
     }
-    if (builtin.os == .linux) {
+    if (builtin.os.tag == .linux) {
         var uts: utsname = undefined;
         switch (errno(system.uname(&uts))) {
             0 => {
@@ -3611,7 +3611,7 @@ pub const SchedYieldError = error{
 };
 
 pub fn sched_yield() SchedYieldError!void {
-    if (builtin.os == .windows) {
+    if (builtin.os.tag == .windows) {
         // The return value has to do with how many other threads there are; it is not
         // an error condition on Windows.
         _ = windows.kernel32.SwitchToThread();
lib/std/packed_int_array.zig
@@ -593,7 +593,7 @@ test "PackedInt(Array/Slice)Endian" {
 // after this one is not mapped and will cause a segfault if we
 // don't account for the bounds.
 test "PackedIntArray at end of available memory" {
-    switch (builtin.os) {
+    switch (builtin.os.tag) {
         .linux, .macosx, .ios, .freebsd, .netbsd, .windows => {},
         else => return,
     }
@@ -612,7 +612,7 @@ test "PackedIntArray at end of available memory" {
 }
 
 test "PackedIntSlice at end of available memory" {
-    switch (builtin.os) {
+    switch (builtin.os.tag) {
         .linux, .macosx, .ios, .freebsd, .netbsd, .windows => {},
         else => return,
     }
lib/std/process.zig
@@ -36,7 +36,7 @@ pub fn getEnvMap(allocator: *Allocator) !BufMap {
     var result = BufMap.init(allocator);
     errdefer result.deinit();
 
-    if (builtin.os == .windows) {
+    if (builtin.os.tag == .windows) {
         const ptr = os.windows.peb().ProcessParameters.Environment;
 
         var i: usize = 0;
@@ -61,7 +61,7 @@ pub fn getEnvMap(allocator: *Allocator) !BufMap {
             try result.setMove(key, value);
         }
         return result;
-    } else if (builtin.os == .wasi) {
+    } else if (builtin.os.tag == .wasi) {
         var environ_count: usize = undefined;
         var environ_buf_size: usize = undefined;
 
@@ -137,7 +137,7 @@ pub const GetEnvVarOwnedError = error{
 
 /// Caller must free returned memory.
 pub fn getEnvVarOwned(allocator: *mem.Allocator, key: []const u8) GetEnvVarOwnedError![]u8 {
-    if (builtin.os == .windows) {
+    if (builtin.os.tag == .windows) {
         const result_w = blk: {
             const key_w = try std.unicode.utf8ToUtf16LeWithNull(allocator, key);
             defer allocator.free(key_w);
@@ -338,12 +338,12 @@ pub const ArgIteratorWindows = struct {
 };
 
 pub const ArgIterator = struct {
-    const InnerType = if (builtin.os == .windows) ArgIteratorWindows else ArgIteratorPosix;
+    const InnerType = if (builtin.os.tag == .windows) ArgIteratorWindows else ArgIteratorPosix;
 
     inner: InnerType,
 
     pub fn init() ArgIterator {
-        if (builtin.os == .wasi) {
+        if (builtin.os.tag == .wasi) {
             // TODO: Figure out a compatible interface accomodating WASI
             @compileError("ArgIterator is not yet supported in WASI. Use argsAlloc and argsFree instead.");
         }
@@ -355,7 +355,7 @@ pub const ArgIterator = struct {
 
     /// You must free the returned memory when done.
     pub fn next(self: *ArgIterator, allocator: *Allocator) ?(NextError![]u8) {
-        if (builtin.os == .windows) {
+        if (builtin.os.tag == .windows) {
             return self.inner.next(allocator);
         } else {
             return mem.dupe(allocator, u8, self.inner.next() orelse return null);
@@ -380,7 +380,7 @@ pub fn args() ArgIterator {
 
 /// Caller must call argsFree on result.
 pub fn argsAlloc(allocator: *mem.Allocator) ![][]u8 {
-    if (builtin.os == .wasi) {
+    if (builtin.os.tag == .wasi) {
         var count: usize = undefined;
         var buf_size: usize = undefined;
 
@@ -445,7 +445,7 @@ pub fn argsAlloc(allocator: *mem.Allocator) ![][]u8 {
 }
 
 pub fn argsFree(allocator: *mem.Allocator, args_alloc: []const []u8) void {
-    if (builtin.os == .wasi) {
+    if (builtin.os.tag == .wasi) {
         const last_item = args_alloc[args_alloc.len - 1];
         const last_byte_addr = @ptrToInt(last_item.ptr) + last_item.len + 1; // null terminated
         const first_item_ptr = args_alloc[0].ptr;
@@ -498,7 +498,7 @@ pub const UserInfo = struct {
 
 /// POSIX function which gets a uid from username.
 pub fn getUserInfo(name: []const u8) !UserInfo {
-    return switch (builtin.os) {
+    return switch (builtin.os.tag) {
         .linux, .macosx, .watchos, .tvos, .ios, .freebsd, .netbsd => posixGetUserInfo(name),
         else => @compileError("Unsupported OS"),
     };
@@ -591,7 +591,7 @@ pub fn posixGetUserInfo(name: []const u8) !UserInfo {
 }
 
 pub fn getBaseAddress() usize {
-    switch (builtin.os) {
+    switch (builtin.os.tag) {
         .linux => {
             const base = os.system.getauxval(std.elf.AT_BASE);
             if (base != 0) {
@@ -615,7 +615,7 @@ pub fn getSelfExeSharedLibPaths(allocator: *Allocator) error{OutOfMemory}![][:0]
         .Dynamic => {},
     }
     const List = std.ArrayList([:0]u8);
-    switch (builtin.os) {
+    switch (builtin.os.tag) {
         .linux,
         .freebsd,
         .netbsd,
lib/std/reset_event.zig
@@ -16,7 +16,7 @@ pub const ResetEvent = struct {
 
     pub const OsEvent = if (builtin.single_threaded)
         DebugEvent
-    else if (builtin.link_libc and builtin.os != .windows and builtin.os != .linux)
+    else if (builtin.link_libc and builtin.os.tag != .windows and builtin.os.tag != .linux)
         PosixEvent
     else
         AtomicEvent;
@@ -106,7 +106,7 @@ const PosixEvent = struct {
     fn deinit(self: *PosixEvent) void {
         // on dragonfly, *destroy() functions can return EINVAL
         // for statically initialized pthread structures
-        const err = if (builtin.os == .dragonfly) os.EINVAL else 0;
+        const err = if (builtin.os.tag == .dragonfly) os.EINVAL else 0;
 
         const retm = c.pthread_mutex_destroy(&self.mutex);
         assert(retm == 0 or retm == err);
@@ -215,7 +215,7 @@ const AtomicEvent = struct {
         }
     }
 
-    pub const Futex = switch (builtin.os) {
+    pub const Futex = switch (builtin.os.tag) {
         .windows => WindowsFutex,
         .linux => LinuxFutex,
         else => SpinFutex,
lib/std/spinlock.zig
@@ -46,7 +46,7 @@ pub const SpinLock = struct {
         // and yielding for 380-410 iterations was found to be
         // a nice sweet spot. Posix systems on the other hand,
         // especially linux, perform better by yielding the thread.
-        switch (builtin.os) {
+        switch (builtin.os.tag) {
             .windows => loopHint(400),
             else => std.os.sched_yield() catch loopHint(1),
         }
lib/std/start.zig
@@ -12,7 +12,7 @@ const start_sym_name = if (builtin.arch.isMIPS()) "__start" else "_start";
 
 comptime {
     if (builtin.output_mode == .Lib and builtin.link_mode == .Dynamic) {
-        if (builtin.os == .windows and !@hasDecl(root, "_DllMainCRTStartup")) {
+        if (builtin.os.tag == .windows and !@hasDecl(root, "_DllMainCRTStartup")) {
             @export(_DllMainCRTStartup, .{ .name = "_DllMainCRTStartup" });
         }
     } else if (builtin.output_mode == .Exe or @hasDecl(root, "main")) {
@@ -20,17 +20,17 @@ comptime {
             if (@typeInfo(@TypeOf(root.main)).Fn.calling_convention != .C) {
                 @export(main, .{ .name = "main", .linkage = .Weak });
             }
-        } else if (builtin.os == .windows) {
+        } else if (builtin.os.tag == .windows) {
             if (!@hasDecl(root, "WinMain") and !@hasDecl(root, "WinMainCRTStartup") and
                 !@hasDecl(root, "wWinMain") and !@hasDecl(root, "wWinMainCRTStartup"))
             {
                 @export(WinMainCRTStartup, .{ .name = "WinMainCRTStartup" });
             }
-        } else if (builtin.os == .uefi) {
+        } else if (builtin.os.tag == .uefi) {
             if (!@hasDecl(root, "EfiMain")) @export(EfiMain, .{ .name = "EfiMain" });
-        } else if (builtin.arch.isWasm() and builtin.os == .freestanding) {
+        } else if (builtin.arch.isWasm() and builtin.os.tag == .freestanding) {
             if (!@hasDecl(root, start_sym_name)) @export(wasm_freestanding_start, .{ .name = start_sym_name });
-        } else if (builtin.os != .other and builtin.os != .freestanding) {
+        } else if (builtin.os.tag != .other and builtin.os.tag != .freestanding) {
             if (!@hasDecl(root, start_sym_name)) @export(_start, .{ .name = start_sym_name });
         }
     }
@@ -78,7 +78,7 @@ fn EfiMain(handle: uefi.Handle, system_table: *uefi.tables.SystemTable) callconv
 }
 
 fn _start() callconv(.Naked) noreturn {
-    if (builtin.os == builtin.Os.wasi) {
+    if (builtin.os.tag == .wasi) {
         // This is marked inline because for some reason LLVM in release mode fails to inline it,
         // and we want fewer call frames in stack traces.
         std.os.wasi.proc_exit(@call(.{ .modifier = .always_inline }, callMain, .{}));
@@ -133,7 +133,7 @@ fn WinMainCRTStartup() callconv(.Stdcall) noreturn {
 
 // TODO https://github.com/ziglang/zig/issues/265
 fn posixCallMainAndExit() noreturn {
-    if (builtin.os == builtin.Os.freebsd) {
+    if (builtin.os.tag == .freebsd) {
         @setAlignStack(16);
     }
     const argc = starting_stack_ptr[0];
@@ -144,7 +144,7 @@ fn posixCallMainAndExit() noreturn {
     while (envp_optional[envp_count]) |_| : (envp_count += 1) {}
     const envp = @ptrCast([*][*:0]u8, envp_optional)[0..envp_count];
 
-    if (builtin.os == .linux) {
+    if (builtin.os.tag == .linux) {
         // Find the beginning of the auxiliary vector
         const auxv = @ptrCast([*]std.elf.Auxv, @alignCast(@alignOf(usize), envp.ptr + envp_count + 1));
         std.os.linux.elf_aux_maybe = auxv;
lib/std/target.zig
@@ -11,143 +11,48 @@ pub const Target = struct {
     os: Os,
     abi: Abi,
 
-    /// The version ranges here represent the minimum OS version to be supported
-    /// and the maximum OS version to be supported. The default values represent
-    /// the range that the Zig Standard Library bases its abstractions on.
-    ///
-    /// The minimum version of the range is the main setting to tweak for a target.
-    /// Usually, the maximum target OS version will remain the default, which is
-    /// the latest released version of the OS.
-    ///
-    /// To test at compile time if the target is guaranteed to support a given OS feature,
-    /// one should check that the minimum version of the range is greater than or equal to
-    /// the version the feature was introduced in.
-    ///
-    /// To test at compile time if the target certainly will not support a given OS feature,
-    /// one should check that the maximum version of the range is less than the version the
-    /// feature was introduced in.
-    ///
-    /// If neither of these cases apply, a runtime check should be used to determine if the
-    /// target supports a given OS feature.
-    ///
-    /// Binaries built with a given maximum version will continue to function on newer operating system
-    /// versions. However, such a binary may not take full advantage of the newer operating system APIs.
-    pub const Os = union(enum) {
-        freestanding,
-        ananas,
-        cloudabi,
-        dragonfly,
-        freebsd: Version.Range,
-        fuchsia,
-        ios,
-        kfreebsd,
-        linux: LinuxVersionRange,
-        lv2,
-        macosx: Version.Range,
-        netbsd: Version.Range,
-        openbsd: Version.Range,
-        solaris,
-        windows: WindowsVersion.Range,
-        haiku,
-        minix,
-        rtems,
-        nacl,
-        cnk,
-        aix,
-        cuda,
-        nvcl,
-        amdhsa,
-        ps4,
-        elfiamcu,
-        tvos,
-        watchos,
-        mesa3d,
-        contiki,
-        amdpal,
-        hermit,
-        hurd,
-        wasi,
-        emscripten,
-        uefi,
-        other,
-
-        /// See the documentation for `Os` for an explanation of the default version range.
-        pub fn defaultVersionRange(tag: @TagType(Os)) Os {
-            switch (tag) {
-                .freestanding => return .freestanding,
-                .ananas => return .ananas,
-                .cloudabi => return .cloudabi,
-                .dragonfly => return .dragonfly,
-                .freebsd => return .{
-                    .freebsd = Version.Range{
-                        .min = .{ .major = 12, .minor = 0 },
-                        .max = .{ .major = 12, .minor = 1 },
-                    },
-                },
-                .fuchsia => return .fuchsia,
-                .ios => return .ios,
-                .kfreebsd => return .kfreebsd,
-                .linux => return .{
-                    .linux = .{
-                        .range = .{
-                            .min = .{ .major = 3, .minor = 16 },
-                            .max = .{ .major = 5, .minor = 5, .patch = 5 },
-                        },
-                        .glibc = .{ .major = 2, .minor = 17 },
-                    },
-                },
-                .lv2 => return .lv2,
-                .macosx => return .{
-                    .min = .{ .major = 10, .minor = 13 },
-                    .max = .{ .major = 10, .minor = 15, .patch = 3 },
-                },
-                .netbsd => return .{
-                    .min = .{ .major = 8, .minor = 0 },
-                    .max = .{ .major = 9, .minor = 0 },
-                },
-                .openbsd => return .{
-                    .min = .{ .major = 6, .minor = 6 },
-                    .max = .{ .major = 6, .minor = 6 },
-                },
-                solaris => return .solaris,
-                windows => return .{
-                    .windows = .{
-                        .min = .win8_1,
-                        .max = .win10_19h1,
-                    },
-                },
-                haiku => return .haiku,
-                minix => return .minix,
-                rtems => return .rtems,
-                nacl => return .nacl,
-                cnk => return .cnk,
-                aix => return .aix,
-                cuda => return .cuda,
-                nvcl => return .nvcl,
-                amdhsa => return .amdhsa,
-                ps4 => return .ps4,
-                elfiamcu => return .elfiamcu,
-                tvos => return .tvos,
-                watchos => return .watchos,
-                mesa3d => return .mesa3d,
-                contiki => return .contiki,
-                amdpal => return .amdpal,
-                hermit => return .hermit,
-                hurd => return .hurd,
-                wasi => return .wasi,
-                emscripten => return .emscripten,
-                uefi => return .uefi,
-                other => return .other,
-            }
-        }
-
-        pub const LinuxVersionRange = struct {
-            range: Version.Range,
-            glibc: Version,
-
-            pub fn includesVersion(self: LinuxVersionRange, ver: Version) bool {
-                return self.range.includesVersion(ver);
-            }
+    pub const Os = struct {
+        tag: Tag,
+        version_range: VersionRange,
+
+        pub const Tag = enum {
+            freestanding,
+            ananas,
+            cloudabi,
+            dragonfly,
+            freebsd,
+            fuchsia,
+            ios,
+            kfreebsd,
+            linux,
+            lv2,
+            macosx,
+            netbsd,
+            openbsd,
+            solaris,
+            windows,
+            haiku,
+            minix,
+            rtems,
+            nacl,
+            cnk,
+            aix,
+            cuda,
+            nvcl,
+            amdhsa,
+            ps4,
+            elfiamcu,
+            tvos,
+            watchos,
+            mesa3d,
+            contiki,
+            amdpal,
+            hermit,
+            hurd,
+            wasi,
+            emscripten,
+            uefi,
+            other,
         };
 
         /// Based on NTDDI version constants from
@@ -178,29 +83,137 @@ pub const Target = struct {
                     return @enumToInt(ver) >= @enumToInt(self.min) and @enumToInt(ver) <= @enumToInt(self.max);
                 }
             };
+        };
 
-            pub fn nameToTag(name: []const u8) ?WindowsVersion {
-                const info = @typeInfo(WindowsVersion);
-                inline for (info.Enum.fields) |field| {
-                    if (mem.eql(u8, name, field.name)) {
-                        return @field(WindowsVersion, field.name);
-                    }
+        pub const LinuxVersionRange = struct {
+            range: Version.Range,
+            glibc: Version,
+
+            pub fn includesVersion(self: LinuxVersionRange, ver: Version) bool {
+                return self.range.includesVersion(ver);
+            }
+        };
+
+        /// The version ranges here represent the minimum OS version to be supported
+        /// and the maximum OS version to be supported. The default values represent
+        /// the range that the Zig Standard Library bases its abstractions on.
+        ///
+        /// The minimum version of the range is the main setting to tweak for a target.
+        /// Usually, the maximum target OS version will remain the default, which is
+        /// the latest released version of the OS.
+        ///
+        /// To test at compile time if the target is guaranteed to support a given OS feature,
+        /// one should check that the minimum version of the range is greater than or equal to
+        /// the version the feature was introduced in.
+        ///
+        /// To test at compile time if the target certainly will not support a given OS feature,
+        /// one should check that the maximum version of the range is less than the version the
+        /// feature was introduced in.
+        ///
+        /// If neither of these cases apply, a runtime check should be used to determine if the
+        /// target supports a given OS feature.
+        ///
+        /// Binaries built with a given maximum version will continue to function on newer operating system
+        /// versions. However, such a binary may not take full advantage of the newer operating system APIs.
+        pub const VersionRange = union {
+            none: void,
+            semver: Version.Range,
+            linux: LinuxVersionRange,
+            windows: WindowsVersion.Range,
+
+            /// The default `VersionRange` represents the range that the Zig Standard Library
+            /// bases its abstractions on.
+            pub fn default(tag: Tag) VersionRange {
+                switch (tag) {
+                    .freestanding,
+                    .ananas,
+                    .cloudabi,
+                    .dragonfly,
+                    .fuchsia,
+                    .ios,
+                    .kfreebsd,
+                    .lv2,
+                    .solaris,
+                    .haiku,
+                    .minix,
+                    .rtems,
+                    .nacl,
+                    .cnk,
+                    .aix,
+                    .cuda,
+                    .nvcl,
+                    .amdhsa,
+                    .ps4,
+                    .elfiamcu,
+                    .tvos,
+                    .watchos,
+                    .mesa3d,
+                    .contiki,
+                    .amdpal,
+                    .hermit,
+                    .hurd,
+                    .wasi,
+                    .emscripten,
+                    .uefi,
+                    .other,
+                    => return .{ .none = {} },
+
+                    .freebsd => return .{
+                        .semver = Version.Range{
+                            .min = .{ .major = 12, .minor = 0 },
+                            .max = .{ .major = 12, .minor = 1 },
+                        },
+                    },
+                    .macosx => return .{
+                        .semver = .{
+                            .min = .{ .major = 10, .minor = 13 },
+                            .max = .{ .major = 10, .minor = 15, .patch = 3 },
+                        },
+                    },
+                    .netbsd => return .{
+                        .semver = .{
+                            .min = .{ .major = 8, .minor = 0 },
+                            .max = .{ .major = 9, .minor = 0 },
+                        },
+                    },
+                    .openbsd => return .{
+                        .semver = .{
+                            .min = .{ .major = 6, .minor = 6 },
+                            .max = .{ .major = 6, .minor = 6 },
+                        },
+                    },
+
+                    .linux => return .{
+                        .linux = .{
+                            .range = .{
+                                .min = .{ .major = 3, .minor = 16 },
+                                .max = .{ .major = 5, .minor = 5, .patch = 5 },
+                            },
+                            .glibc = .{ .major = 2, .minor = 17 },
+                        },
+                    },
+
+                    .windows => return .{
+                        .windows = .{
+                            .min = .win8_1,
+                            .max = .win10_19h1,
+                        },
+                    },
                 }
-                return null;
             }
         };
 
         pub fn parse(text: []const u8) !Os {
             var it = mem.separate(text, ".");
             const os_name = it.next().?;
-            const tag = nameToTag(os_name) orelse return error.UnknownOperatingSystem;
+            const tag = std.meta.stringToEnum(Tag, os_name) orelse return error.UnknownOperatingSystem;
             const version_text = it.rest();
             const S = struct {
                 fn parseNone(s: []const u8) !void {
                     if (s.len != 0) return error.InvalidOperatingSystemVersion;
                 }
-                fn parseSemVer(s: []const u8, default: Version.Range) !Version.Range {
-                    if (s.len == 0) return default;
+                fn parseSemVer(s: []const u8, d_range: Version.Range) !Version.Range {
+                    if (s.len == 0) return d_range;
                     var range_it = mem.separate(s, "...");
 
                     const min_text = range_it.next().?;
@@ -212,7 +225,7 @@ pub const Target = struct {
 
                     const max_text = range_it.next() orelse return Version.Range{
                         .min = min_ver,
-                        .max = default.max,
+                        .max = d_range.max,
                     };
                     const max_ver = Version.parse(max_text) catch |err| switch (err) {
                         error.Overflow => return error.InvalidOperatingSystemVersion,
@@ -222,79 +235,93 @@ pub const Target = struct {
 
                     return Version.Range{ .min = min_ver, .max = max_ver };
                 }
-                fn parseWindows(s: []const u8, default: WindowsVersion.Range) !WindowsVersion.Range {
-                    if (s.len == 0) return default;
+                fn parseWindows(s: []const u8, d_range: WindowsVersion.Range) !WindowsVersion.Range {
+                    if (s.len == 0) return d_range;
                     var range_it = mem.separate(s, "...");
 
                     const min_text = range_it.next().?;
-                    const min_ver = WindowsVersion.nameToTag(min_text) orelse
+                    const min_ver = std.meta.stringToEnum(WindowsVersion, min_text) orelse
                         return error.InvalidOperatingSystemVersion;
 
                     const max_text = range_it.next() orelse return WindowsVersion.Range{
                         .min = min_ver,
-                        .max = default.max,
+                        .max = d_range.max,
                     };
-                    const max_ver = WindowsVersion.nameToTag(max_text) orelse
+                    const max_ver = std.meta.stringToEnum(WindowsVersion, max_text) orelse
                         return error.InvalidOperatingSystemVersion;
 
                     return WindowsVersion.Range{ .min = min_ver, .max = max_ver };
                 }
             };
-            const default = defaultVersionRange(tag);
+            const d_range = VersionRange.default(tag);
             switch (tag) {
-                .freestanding => return Os{ .freestanding = try S.parseNone(version_text) },
-                .ananas => return Os{ .ananas = try S.parseNone(version_text) },
-                .cloudabi => return Os{ .cloudabi = try S.parseNone(version_text) },
-                .dragonfly => return Os{ .dragonfly = try S.parseNone(version_text) },
-                .freebsd => return Os{ .freebsd = try S.parseSemVer(version_text, default.freebsd) },
-                .fuchsia => return Os{ .fuchsia = try S.parseNone(version_text) },
-                .ios => return Os{ .ios = try S.parseNone(version_text) },
-                .kfreebsd => return Os{ .kfreebsd = try S.parseNone(version_text) },
+                .freestanding,
+                .ananas,
+                .cloudabi,
+                .dragonfly,
+                .fuchsia,
+                .ios,
+                .kfreebsd,
+                .lv2,
+                .solaris,
+                .haiku,
+                .minix,
+                .rtems,
+                .nacl,
+                .cnk,
+                .aix,
+                .cuda,
+                .nvcl,
+                .amdhsa,
+                .ps4,
+                .elfiamcu,
+                .tvos,
+                .watchos,
+                .mesa3d,
+                .contiki,
+                .amdpal,
+                .hermit,
+                .hurd,
+                .wasi,
+                .emscripten,
+                .uefi,
+                .other,
+                => return Os{
+                    .tag = tag,
+                    .version_range = .{ .none = try S.parseNone(version_text) },
+                },
+
+                .freebsd,
+                .macosx,
+                .netbsd,
+                .openbsd,
+                => return Os{
+                    .tag = tag,
+                    .version_range = .{ .semver = try S.parseSemVer(version_text, d_range.semver) },
+                },
+
                 .linux => return Os{
-                    .linux = .{
-                        .range = try S.parseSemVer(version_text, default.linux.range),
-                        .glibc = default.linux.glibc,
+                    .tag = tag,
+                    .version_range = .{
+                        .linux = .{
+                            .range = try S.parseSemVer(version_text, d_range.linux.range),
+                            .glibc = d_range.linux.glibc,
+                        },
                     },
                 },
-                .lv2 => return Os{ .lv2 = try S.parseNone(version_text) },
-                .macosx => return Os{ .macosx = try S.parseSemVer(version_text, default.macosx) },
-                .netbsd => return Os{ .netbsd = try S.parseSemVer(version_text, default.netbsd) },
-                .openbsd => return Os{ .openbsd = try S.parseSemVer(version_text, default.openbsd) },
-                .solaris => return Os{ .solaris = try S.parseNone(version_text) },
-                .windows => return Os{ .windows = try S.parseWindows(version_text, default.windows) },
-                .haiku => return Os{ .haiku = try S.parseNone(version_text) },
-                .minix => return Os{ .minix = try S.parseNone(version_text) },
-                .rtems => return Os{ .rtems = try S.parseNone(version_text) },
-                .nacl => return Os{ .nacl = try S.parseNone(version_text) },
-                .cnk => return Os{ .cnk = try S.parseNone(version_text) },
-                .aix => return Os{ .aix = try S.parseNone(version_text) },
-                .cuda => return Os{ .cuda = try S.parseNone(version_text) },
-                .nvcl => return Os{ .nvcl = try S.parseNone(version_text) },
-                .amdhsa => return Os{ .amdhsa = try S.parseNone(version_text) },
-                .ps4 => return Os{ .ps4 = try S.parseNone(version_text) },
-                .elfiamcu => return Os{ .elfiamcu = try S.parseNone(version_text) },
-                .tvos => return Os{ .tvos = try S.parseNone(version_text) },
-                .watchos => return Os{ .watchos = try S.parseNone(version_text) },
-                .mesa3d => return Os{ .mesa3d = try S.parseNone(version_text) },
-                .contiki => return Os{ .contiki = try S.parseNone(version_text) },
-                .amdpal => return Os{ .amdpal = try S.parseNone(version_text) },
-                .hermit => return Os{ .hermit = try S.parseNone(version_text) },
-                .hurd => return Os{ .hurd = try S.parseNone(version_text) },
-                .wasi => return Os{ .wasi = try S.parseNone(version_text) },
-                .emscripten => return Os{ .emscripten = try S.parseNone(version_text) },
-                .uefi => return Os{ .uefi = try S.parseNone(version_text) },
-                .other => return Os{ .other = try S.parseNone(version_text) },
+
+                .windows => return Os{
+                    .tag = tag,
+                    .version_range = .{ .windows = try S.parseWindows(version_text, d_range.windows) },
+                },
             }
         }
 
-        pub fn nameToTag(name: []const u8) ?@TagType(Os) {
-            const info = @typeInfo(Os);
-            inline for (info.Union.fields) |field| {
-                if (mem.eql(u8, name, field.name)) {
-                    return @field(Os, field.name);
-                }
-            }
-            return null;
+        pub fn defaultVersionRange(tag: Tag) Os {
+            return .{
+                .tag = tag,
+                .version_range = VersionRange.default(tag),
+            };
         }
     };
 
@@ -339,11 +366,10 @@ pub const Target = struct {
         macabi,
 
         pub fn default(arch: Cpu.Arch, target_os: Os) Abi {
-            switch (arch) {
-                .wasm32, .wasm64 => return .musl,
-                else => {},
+            if (arch.isWasm()) {
+                return .musl;
             }
-            switch (target_os) {
+            switch (target_os.tag) {
                 .freestanding,
                 .ananas,
                 .cloudabi,
@@ -388,40 +414,19 @@ pub const Target = struct {
             }
         }
 
-        pub fn nameToTag(text: []const u8) ?Abi {
-            const info = @typeInfo(Abi);
-            inline for (info.Enum.fields) |field| {
-                if (mem.eql(u8, text, field.name)) {
-                    return @field(Abi, field.name);
-                }
-            }
-            return null;
-        }
-
-        pub fn parse(text: []const u8, os: *Os) !Abi {
-            var it = mem.separate(text, ".");
-            const tag = nameToTag(it.next().?) orelse return error.UnknownApplicationBinaryInterface;
-            const version_text = it.rest();
-            if (version_text.len != 0) {
-                if (@as(@TagType(Os), os.*) == .linux and tag.isGnu()) {
-                    os.linux.glibc = Version.parse(version_text) catch |err| switch (err) {
-                        error.Overflow => return error.InvalidGlibcVersion,
-                        error.InvalidCharacter => return error.InvalidGlibcVersion,
-                        error.InvalidVersion => return error.InvalidGlibcVersion,
-                    };
-                } else {
-                    return error.InvalidAbiVersion;
-                }
-            }
-            return tag;
-        }
-
         pub fn isGnu(abi: Abi) bool {
             return switch (abi) {
                 .gnu, .gnuabin32, .gnuabi64, .gnueabi, .gnueabihf, .gnux32 => true,
                 else => false,
             };
         }
+
+        pub fn isMusl(abi: Abi) bool {
+            return switch (abi) {
+                .musl, .musleabi, .musleabihf => true,
+                else => false,
+            };
+        }
     };
 
     pub const ObjectFormat = enum {
@@ -909,15 +914,15 @@ pub const Target = struct {
     /// TODO add OS version ranges and glibc version
     pub fn zigTriple(self: Target, allocator: *mem.Allocator) ![]u8 {
         return std.fmt.allocPrint(allocator, "{}-{}-{}", .{
-            @tagName(self.getArch()),
-            @tagName(self.os),
+            @tagName(self.cpu.arch),
+            @tagName(self.os.tag),
             @tagName(self.abi),
         });
     }
 
     /// Returned slice must be freed by the caller.
     pub fn vcpkgTriplet(allocator: *mem.Allocator, target: Target, linkage: std.build.VcpkgLinkage) ![]const u8 {
-        const arch = switch (target.getArch()) {
+        const arch = switch (target.cpu.arch) {
             .i386 => "x86",
             .x86_64 => "x64",
 
@@ -957,16 +962,16 @@ pub const Target = struct {
 
     pub fn zigTripleNoSubArch(self: Target, allocator: *mem.Allocator) ![]u8 {
         return std.fmt.allocPrint(allocator, "{}-{}-{}", .{
-            @tagName(self.getArch()),
-            @tagName(self.os),
+            @tagName(self.cpu.arch),
+            @tagName(self.os.tag),
             @tagName(self.abi),
         });
     }
 
     pub fn linuxTriple(self: Target, allocator: *mem.Allocator) ![]u8 {
         return std.fmt.allocPrint(allocator, "{}-{}-{}", .{
-            @tagName(self.getArch()),
-            @tagName(self.os),
+            @tagName(self.cpu.arch),
+            @tagName(self.os.tag),
             @tagName(self.abi),
         });
     }
@@ -1017,11 +1022,28 @@ pub const Target = struct {
         diags.arch = arch;
 
         const os_name = it.next() orelse return error.MissingOperatingSystem;
-        var os = try Os.parse(os_name); // var because Abi.parse can update linux.glibc version
+        var os = try Os.parse(os_name);
         diags.os = os;
 
-        const abi_name = it.next();
-        const abi = if (abi_name) |n| try Abi.parse(n, &os) else Abi.default(arch, os);
+        const opt_abi_text = it.next();
+        const abi = if (opt_abi_text) |abi_text| blk: {
+            var abi_it = mem.separate(abi_text, ".");
+            const abi = std.meta.stringToEnum(Abi, abi_it.next().?) orelse
+                return error.UnknownApplicationBinaryInterface;
+            const abi_ver_text = abi_it.rest();
+            if (abi_ver_text.len != 0) {
+                if (os.tag == .linux and abi.isGnu()) {
+                    os.version_range.linux.glibc = Version.parse(abi_ver_text) catch |err| switch (err) {
+                        error.Overflow => return error.InvalidAbiVersion,
+                        error.InvalidCharacter => return error.InvalidAbiVersion,
+                        error.InvalidVersion => return error.InvalidAbiVersion,
+                    };
+                } else {
+                    return error.InvalidAbiVersion;
+                }
+            }
+            break :blk abi;
+        } else Abi.default(arch, os);
         diags.abi = abi;
 
         if (it.next() != null) return error.UnexpectedExtraField;
@@ -1130,25 +1152,6 @@ pub const Target = struct {
         }
     }
 
-    /// Deprecated; access the `os` field directly.
-    pub fn getOs(self: Target) @TagType(Os) {
-        return self.os;
-    }
-
-    /// Deprecated; access the `cpu` field directly.
-    pub fn getCpu(self: Target) Cpu {
-        return self.cpu;
-    }
-
-    /// Deprecated; access the `abi` field directly.
-    pub fn getAbi(self: Target) Abi {
-        return self.abi;
-    }
-
-    pub fn getArch(self: Target) Cpu.Arch {
-        return self.cpu.arch;
-    }
-
     pub fn getObjectFormat(self: Target) ObjectFormat {
         if (self.isWindows() or self.isUefi()) {
             return .coff;
@@ -1170,28 +1173,25 @@ pub const Target = struct {
     }
 
     pub fn isMusl(self: Target) bool {
-        return switch (self.abi) {
-            .musl, .musleabi, .musleabihf => true,
-            else => false,
-        };
+        return self.abi.isMusl();
     }
 
     pub fn isDarwin(self: Target) bool {
-        return switch (self.os) {
+        return switch (self.os.tag) {
             .ios, .macosx, .watchos, .tvos => true,
             else => false,
         };
     }
 
     pub fn isWindows(self: Target) bool {
-        return switch (self.os) {
+        return switch (self.os.tag) {
             .windows => true,
             else => false,
         };
     }
 
     pub fn isLinux(self: Target) bool {
-        return switch (self.os) {
+        return switch (self.os.tag) {
             .linux => true,
             else => false,
         };
@@ -1205,40 +1205,41 @@ pub const Target = struct {
     }
 
     pub fn isDragonFlyBSD(self: Target) bool {
-        return switch (self.os) {
+        return switch (self.os.tag) {
             .dragonfly => true,
             else => false,
         };
     }
 
     pub fn isUefi(self: Target) bool {
-        return switch (self.os) {
+        return switch (self.os.tag) {
             .uefi => true,
             else => false,
         };
     }
 
     pub fn isWasm(self: Target) bool {
-        return switch (self.getArch()) {
-            .wasm32, .wasm64 => true,
-            else => false,
-        };
+        return self.cpu.arch.isWasm();
     }
 
     pub fn isFreeBSD(self: Target) bool {
-        return switch (self.os) {
+        return switch (self.os.tag) {
             .freebsd => true,
             else => false,
         };
     }
 
     pub fn isNetBSD(self: Target) bool {
-        return switch (self.os) {
+        return switch (self.os.tag) {
             .netbsd => true,
             else => false,
         };
     }
 
+    pub fn isGnuLibC(self: Target) bool {
+        return self.os.tag == .linux and self.abi.isGnu();
+    }
+
     pub fn wantSharedLibSymLinks(self: Target) bool {
         return !self.isWindows();
     }
@@ -1248,7 +1249,7 @@ pub const Target = struct {
     }
 
     pub fn getArchPtrBitWidth(self: Target) u32 {
-        switch (self.getArch()) {
+        switch (self.cpu.arch) {
             .avr,
             .msp430,
             => return 16,
@@ -1323,8 +1324,8 @@ pub const Target = struct {
         if (@as(@TagType(Target), self) == .Native) return .native;
 
         // If the target OS matches the host OS, we can use QEMU to emulate a foreign architecture.
-        if (self.os == builtin.os) {
-            return switch (self.getArch()) {
+        if (self.os.tag == builtin.os.tag) {
+            return switch (self.cpu.arch) {
                 .aarch64 => Executor{ .qemu = "qemu-aarch64" },
                 .aarch64_be => Executor{ .qemu = "qemu-aarch64_be" },
                 .arm => Executor{ .qemu = "qemu-arm" },
@@ -1381,13 +1382,10 @@ pub const Target = struct {
     }
 
     pub fn hasDynamicLinker(self: Target) bool {
-        switch (self.getArch()) {
-            .wasm32,
-            .wasm64,
-            => return false,
-            else => {},
+        if (self.cpu.arch.isWasm()) {
+            return false;
         }
-        switch (self.os) {
+        switch (self.os.tag) {
             .freestanding,
             .ios,
             .tvos,
@@ -1424,7 +1422,7 @@ pub const Target = struct {
             defer result.deinit();
 
             var is_arm = false;
-            switch (self.getArch()) {
+            switch (self.cpu.arch) {
                 .arm, .thumb => {
                     try result.append("arm");
                     is_arm = true;
@@ -1442,11 +1440,11 @@ pub const Target = struct {
             return result.toOwnedSlice();
         }
 
-        switch (self.os) {
+        switch (self.os.tag) {
             .freebsd => return mem.dupeZ(a, u8, "/libexec/ld-elf.so.1"),
             .netbsd => return mem.dupeZ(a, u8, "/libexec/ld.elf_so"),
             .dragonfly => return mem.dupeZ(a, u8, "/libexec/ld-elf.so.2"),
-            .linux => switch (self.getArch()) {
+            .linux => switch (self.cpu.arch) {
                 .i386,
                 .sparc,
                 .sparcel,
@@ -1539,7 +1537,7 @@ test "Target.parse" {
             .cpu_features = "x86_64-sse-sse2-avx-cx8",
         });
 
-        std.testing.expect(target.os == .linux);
+        std.testing.expect(target.os.tag == .linux);
         std.testing.expect(target.abi == .gnu);
         std.testing.expect(target.cpu.arch == .x86_64);
         std.testing.expect(!Target.x86.featureSetHas(target.cpu.features, .sse));
@@ -1554,10 +1552,29 @@ test "Target.parse" {
             .cpu_features = "generic+v8a",
         });
 
-        std.testing.expect(target.os == .linux);
+        std.testing.expect(target.os.tag == .linux);
         std.testing.expect(target.abi == .musleabihf);
         std.testing.expect(target.cpu.arch == .arm);
         std.testing.expect(target.cpu.model == &Target.arm.cpu.generic);
         std.testing.expect(Target.arm.featureSetHas(target.cpu.features, .v8a));
     }
+    {
+        const target = try Target.parse(.{
+            .arch_os_abi = "aarch64-linux.3.10...4.4.1-gnu.2.27",
+            .cpu_features = "generic+v8a",
+        });
+
+        std.testing.expect(target.cpu.arch == .aarch64);
+        std.testing.expect(target.os.tag == .linux);
+        std.testing.expect(target.os.version_range.linux.min.major == 3);
+        std.testing.expect(target.os.version_range.linux.min.minor == 10);
+        std.testing.expect(target.os.version_range.linux.min.patch == 0);
+        std.testing.expect(target.os.version_range.linux.max.major == 4);
+        std.testing.expect(target.os.version_range.linux.max.minor == 4);
+        std.testing.expect(target.os.version_range.linux.max.patch == 1);
+        std.testing.expect(target.os.version_range.linux.glibc.major == 2);
+        std.testing.expect(target.os.version_range.linux.glibc.minor == 27);
+        std.testing.expect(target.os.version_range.linux.glibc.patch == 0);
+        std.testing.expect(target.abi == .gnu);
+    }
 }
lib/std/thread.zig
@@ -9,14 +9,14 @@ const assert = std.debug.assert;
 pub const Thread = struct {
     data: Data,
 
-    pub const use_pthreads = builtin.os != .windows and builtin.link_libc;
+    pub const use_pthreads = builtin.os.tag != .windows and builtin.link_libc;
 
     /// Represents a kernel thread handle.
     /// May be an integer or a pointer depending on the platform.
     /// On Linux and POSIX, this is the same as Id.
     pub const Handle = if (use_pthreads)
         c.pthread_t
-    else switch (builtin.os) {
+    else switch (builtin.os.tag) {
         .linux => i32,
         .windows => windows.HANDLE,
         else => void,
@@ -25,7 +25,7 @@ pub const Thread = struct {
     /// Represents a unique ID per thread.
     /// May be an integer or pointer depending on the platform.
     /// On Linux and POSIX, this is the same as Handle.
-    pub const Id = switch (builtin.os) {
+    pub const Id = switch (builtin.os.tag) {
         .windows => windows.DWORD,
         else => Handle,
     };
@@ -35,7 +35,7 @@ pub const Thread = struct {
             handle: Thread.Handle,
             memory: []align(mem.page_size) u8,
         }
-    else switch (builtin.os) {
+    else switch (builtin.os.tag) {
         .linux => struct {
             handle: Thread.Handle,
             memory: []align(mem.page_size) u8,
@@ -55,7 +55,7 @@ pub const Thread = struct {
         if (use_pthreads) {
             return c.pthread_self();
         } else
-            return switch (builtin.os) {
+            return switch (builtin.os.tag) {
             .linux => os.linux.gettid(),
             .windows => windows.kernel32.GetCurrentThreadId(),
             else => @compileError("Unsupported OS"),
@@ -83,7 +83,7 @@ pub const Thread = struct {
                 else => unreachable,
             }
             os.munmap(self.data.memory);
-        } else switch (builtin.os) {
+        } else switch (builtin.os.tag) {
             .linux => {
                 while (true) {
                     const pid_value = @atomicLoad(i32, &self.data.handle, .SeqCst);
@@ -150,7 +150,7 @@ pub const Thread = struct {
         const Context = @TypeOf(context);
         comptime assert(@typeInfo(@TypeOf(startFn)).Fn.args[0].arg_type.? == Context);
 
-        if (builtin.os == builtin.Os.windows) {
+        if (builtin.os.tag == .windows) {
             const WinThread = struct {
                 const OuterContext = struct {
                     thread: Thread,
@@ -309,7 +309,7 @@ pub const Thread = struct {
                 os.EINVAL => unreachable,
                 else => return os.unexpectedErrno(@intCast(usize, err)),
             }
-        } else if (builtin.os == .linux) {
+        } else if (builtin.os.tag == .linux) {
             var flags: u32 = os.CLONE_VM | os.CLONE_FS | os.CLONE_FILES | os.CLONE_SIGHAND |
                 os.CLONE_THREAD | os.CLONE_SYSVSEM | os.CLONE_PARENT_SETTID | os.CLONE_CHILD_CLEARTID |
                 os.CLONE_DETACHED;
@@ -369,11 +369,11 @@ pub const Thread = struct {
     };
 
     pub fn cpuCount() CpuCountError!usize {
-        if (builtin.os == .linux) {
+        if (builtin.os.tag == .linux) {
             const cpu_set = try os.sched_getaffinity(0);
             return @as(usize, os.CPU_COUNT(cpu_set)); // TODO should not need this usize cast
         }
-        if (builtin.os == .windows) {
+        if (builtin.os.tag == .windows) {
             var system_info: windows.SYSTEM_INFO = undefined;
             windows.kernel32.GetSystemInfo(&system_info);
             return @intCast(usize, system_info.dwNumberOfProcessors);
lib/std/time.zig
@@ -1,5 +1,5 @@
-const builtin = @import("builtin");
 const std = @import("std.zig");
+const builtin = std.builtin;
 const assert = std.debug.assert;
 const testing = std.testing;
 const os = std.os;
@@ -7,10 +7,12 @@ const math = std.math;
 
 pub const epoch = @import("time/epoch.zig");
 
+const is_windows = std.Target.current.os.tag == .windows;
+
 /// Spurious wakeups are possible and no precision of timing is guaranteed.
 /// TODO integrate with evented I/O
 pub fn sleep(nanoseconds: u64) void {
-    if (builtin.os == .windows) {
+    if (is_windows) {
         const ns_per_ms = ns_per_s / ms_per_s;
         const big_ms_from_ns = nanoseconds / ns_per_ms;
         const ms = math.cast(os.windows.DWORD, big_ms_from_ns) catch math.maxInt(os.windows.DWORD);
@@ -31,7 +33,7 @@ pub fn timestamp() u64 {
 /// Get the posix timestamp, UTC, in milliseconds
 /// TODO audit this function. is it possible to return an error?
 pub fn milliTimestamp() u64 {
-    if (builtin.os == .windows) {
+    if (is_windows) {
         //FileTime has a granularity of 100 nanoseconds
         //  and uses the NTFS/Windows epoch
         var ft: os.windows.FILETIME = undefined;
@@ -42,7 +44,7 @@ pub fn milliTimestamp() u64 {
         const ft64 = (@as(u64, ft.dwHighDateTime) << 32) | ft.dwLowDateTime;
         return @divFloor(ft64, hns_per_ms) - -epoch_adj;
     }
-    if (builtin.os == .wasi and !builtin.link_libc) {
+    if (builtin.os.tag == .wasi and !builtin.link_libc) {
         var ns: os.wasi.timestamp_t = undefined;
 
         // TODO: Verify that precision is ignored
@@ -102,7 +104,7 @@ pub const Timer = struct {
     ///if we used resolution's value when performing the
     ///  performance counter calc on windows/darwin, it would
     ///  be less precise
-    frequency: switch (builtin.os) {
+    frequency: switch (builtin.os.tag) {
         .windows => u64,
         .macosx, .ios, .tvos, .watchos => os.darwin.mach_timebase_info_data,
         else => void,
@@ -127,7 +129,7 @@ pub const Timer = struct {
     pub fn start() Error!Timer {
         var self: Timer = undefined;
 
-        if (builtin.os == .windows) {
+        if (is_windows) {
             self.frequency = os.windows.QueryPerformanceFrequency();
             self.resolution = @divFloor(ns_per_s, self.frequency);
             self.start_time = os.windows.QueryPerformanceCounter();
@@ -172,7 +174,7 @@ pub const Timer = struct {
     }
 
     fn clockNative() u64 {
-        if (builtin.os == .windows) {
+        if (is_windows) {
             return os.windows.QueryPerformanceCounter();
         }
         if (comptime std.Target.current.isDarwin()) {
@@ -184,7 +186,7 @@ pub const Timer = struct {
     }
 
     fn nativeDurationToNanos(self: Timer, duration: u64) u64 {
-        if (builtin.os == .windows) {
+        if (is_windows) {
             return @divFloor(duration * ns_per_s, self.frequency);
         }
         if (comptime std.Target.current.isDarwin()) {
src/codegen.cpp
@@ -4483,7 +4483,7 @@ static LLVMValueRef ir_render_union_field_ptr(CodeGen *g, IrExecutableGen *execu
 
     if (!type_has_bits(field->type_entry)) {
         ZigType *tag_type = union_type->data.unionation.tag_type;
-        if (!instruction->initializing || !type_has_bits(tag_type))
+        if (!instruction->initializing || tag_type == nullptr || !type_has_bits(tag_type))
             return nullptr;
 
         // The field has no bits but we still have to change the discriminant
@@ -8543,25 +8543,24 @@ Buf *codegen_generate_builtin_source(CodeGen *g) {
     buf_appendf(contents, "pub const link_mode = LinkMode.%s;\n", link_type);
     buf_appendf(contents, "pub const is_test = %s;\n", bool_to_str(g->is_test_build));
     buf_appendf(contents, "pub const single_threaded = %s;\n", bool_to_str(g->is_single_threaded));
-    buf_appendf(contents, "pub const os = Os.%s;\n", cur_os);
+    buf_append_str(contents, "/// Deprecated: use `std.Target.cpu.arch`\n");
     buf_appendf(contents, "pub const arch = Arch.%s;\n", cur_arch);
     buf_appendf(contents, "pub const abi = Abi.%s;\n", cur_abi);
     {
         buf_append_str(contents, "pub const cpu: Cpu = ");
-        if (g->zig_target->builtin_str != nullptr) {
-            buf_append_str(contents, g->zig_target->builtin_str);
+        if (g->zig_target->cpu_builtin_str != nullptr) {
+            buf_append_str(contents, g->zig_target->cpu_builtin_str);
         } else {
-            buf_append_str(contents, "Target.Cpu.baseline(arch);\n");
+            buf_appendf(contents, "Target.Cpu.baseline(.%s);\n", cur_arch);
         }
     }
-    if (g->libc_link_lib != nullptr && g->zig_target->glibc_version != nullptr) {
-        buf_appendf(contents,
-            "pub const glibc_version: ?Version = Version{.major = %d, .minor = %d, .patch = %d};\n",
-                g->zig_target->glibc_version->major,
-                g->zig_target->glibc_version->minor,
-                g->zig_target->glibc_version->patch);
-    } else {
-        buf_appendf(contents, "pub const glibc_version: ?Version = null;\n");
+    {
+        buf_append_str(contents, "pub const os = ");
+        if (g->zig_target->os_builtin_str != nullptr) {
+            buf_append_str(contents, g->zig_target->os_builtin_str);
+        } else {
+            buf_appendf(contents, "Target.Os.defaultVersionRange(.%s);\n", cur_os);
+        }
     }
     buf_appendf(contents, "pub const object_format = ObjectFormat.%s;\n", cur_obj_fmt);
     buf_appendf(contents, "pub const mode = %s;\n", build_mode_to_str(g->build_mode));
@@ -8867,8 +8866,6 @@ static void init(CodeGen *g) {
 }
 
 static void detect_dynamic_linker(CodeGen *g) {
-    Error err;
-
     if (g->dynamic_linker_path != nullptr)
         return;
     if (!g->have_dynamic_link)
@@ -8876,16 +8873,9 @@ static void detect_dynamic_linker(CodeGen *g) {
     if (g->out_type == OutTypeObj || (g->out_type == OutTypeLib && !g->is_dynamic))
         return;
 
-    char *dynamic_linker_ptr;
-    size_t dynamic_linker_len;
-    if ((err = stage2_detect_dynamic_linker(g->zig_target, &dynamic_linker_ptr, &dynamic_linker_len))) {
-        if (err == ErrorTargetHasNoDynamicLinker) return;
-        fprintf(stderr, "Unable to detect dynamic linker: %s\n", err_str(err));
-        exit(1);
+    if (g->zig_target->dynamic_linker != nullptr) {
+        g->dynamic_linker_path = buf_create_from_str(g->zig_target->dynamic_linker);
     }
-    g->dynamic_linker_path = buf_create_from_mem(dynamic_linker_ptr, dynamic_linker_len);
-    // Skips heap::c_allocator because the memory is allocated by stage2 library.
-    free(dynamic_linker_ptr);
 }
 
 static void detect_libc(CodeGen *g) {
src/error.cpp
@@ -81,6 +81,8 @@ const char *err_str(Error err) {
         case ErrorWindowsSdkNotFound: return "Windows SDK not found";
         case ErrorUnknownDynamicLinkerPath: return "unknown dynamic linker path";
         case ErrorTargetHasNoDynamicLinker: return "target has no dynamic linker";
+        case ErrorInvalidAbiVersion: return "invalid C ABI version";
+        case ErrorInvalidOperatingSystemVersion: return "invalid operating system version";
     }
     return "(invalid error)";
 }
src/main.cpp
@@ -89,8 +89,7 @@ static int print_full_usage(const char *arg0, FILE *file, int return_code) {
         "  --single-threaded            source may assume it is only used single-threaded\n"
         "  -dynamic                     create a shared library (.so; .dll; .dylib)\n"
         "  --strip                      exclude debug symbols\n"
-        "  -target [name]               <arch><sub>-<os>-<abi> see the targets command\n"
-        "  -target-glibc [version]      target a specific glibc version (default: 2.17)\n"
+        "  -target [name]               <arch>-<os>-<abi> see the targets command\n"
         "  --verbose-tokenize           enable compiler debug output for tokenization\n"
         "  --verbose-ast                enable compiler debug output for AST parsing\n"
         "  --verbose-link               enable compiler debug output for linking\n"
@@ -419,7 +418,6 @@ static int main0(int argc, char **argv) {
     const char *mios_version_min = nullptr;
     const char *linker_script = nullptr;
     Buf *version_script = nullptr;
-    const char *target_glibc = nullptr;
     ZigList<const char *> rpath_list = {0};
     bool each_lib_rpath = false;
     ZigList<const char *> objects = {0};
@@ -853,8 +851,6 @@ static int main0(int argc, char **argv) {
                     linker_script = argv[i];
                 } else if (strcmp(arg, "--version-script") == 0) {
                     version_script = buf_create_from_str(argv[i]); 
-                } else if (strcmp(arg, "-target-glibc") == 0) {
-                    target_glibc = argv[i];
                 } else if (strcmp(arg, "-rpath") == 0) {
                     rpath_list.append(argv[i]);
                 } else if (strcmp(arg, "--test-filter") == 0) {
@@ -982,29 +978,6 @@ static int main0(int argc, char **argv) {
                 "See `%s targets` to display valid targets.\n", err_str(err), arg0);
         return print_error_usage(arg0);
     }
-    if (target_is_glibc(&target)) {
-        target.glibc_version = heap::c_allocator.create<ZigGLibCVersion>();
-
-        if (target_glibc != nullptr) {
-            if ((err = target_parse_glibc_version(target.glibc_version, target_glibc))) {
-                fprintf(stderr, "invalid glibc version '%s': %s\n", target_glibc, err_str(err));
-                return print_error_usage(arg0);
-            }
-        } else {
-            target_init_default_glibc_version(&target);
-#if defined(ZIG_OS_LINUX)
-            if (target.is_native) {
-                // TODO self-host glibc version detection, and then this logic can go away
-                if ((err = glibc_detect_native_version(target.glibc_version))) {
-                    // Fall back to the default version.
-                }
-            }
-#endif
-        }
-    } else if (target_glibc != nullptr) {
-        fprintf(stderr, "'%s' is not a glibc-compatible target", target_string);
-        return print_error_usage(arg0);
-    }
 
     Buf zig_triple_buf = BUF_INIT;
     target_triple_zig(&zig_triple_buf, &target);
src/stage2.cpp
@@ -100,13 +100,11 @@ Error stage2_target_parse(struct ZigTarget *target, const char *zig_triple, cons
         if (mcpu == nullptr) {
             target->llvm_cpu_name = ZigLLVMGetHostCPUName();
             target->llvm_cpu_features = ZigLLVMGetNativeFeatures();
-            target->builtin_str = "Target.Cpu.baseline(arch);\n";
             target->cache_hash = "native\n\n";
         } else if (strcmp(mcpu, "baseline") == 0) {
             target->is_native = false;
             target->llvm_cpu_name = "";
             target->llvm_cpu_features = "";
-            target->builtin_str = "Target.Cpu.baseline(arch);\n";
             target->cache_hash = "baseline\n\n";
         } else {
             const char *msg = "stage0 can't handle CPU/features in the target";
@@ -148,7 +146,6 @@ Error stage2_target_parse(struct ZigTarget *target, const char *zig_triple, cons
             const char *msg = "stage0 can't handle CPU/features in the target";
             stage2_panic(msg, strlen(msg));
         }
-        target->builtin_str = "Target.Cpu.baseline(arch);\n";
         target->cache_hash = "\n\n";
     }
 
@@ -186,11 +183,6 @@ enum Error stage2_libc_find_native(struct Stage2LibCInstallation *libc) {
     stage2_panic(msg, strlen(msg));
 }
 
-enum Error stage2_detect_dynamic_linker(const struct ZigTarget *target, char **out_ptr, size_t *out_len) {
-    const char *msg = "stage0 called stage2_detect_dynamic_linker";
-    stage2_panic(msg, strlen(msg));
-}
-
 enum Error stage2_detect_native_paths(struct Stage2NativePaths *native_paths) {
     native_paths->include_dirs_ptr = nullptr;
     native_paths->include_dirs_len = 0;
src/stage2.h
@@ -103,6 +103,8 @@ enum Error {
     ErrorWindowsSdkNotFound,
     ErrorUnknownDynamicLinkerPath,
     ErrorTargetHasNoDynamicLinker,
+    ErrorInvalidAbiVersion,
+    ErrorInvalidOperatingSystemVersion,
 };
 
 // ABI warning
@@ -290,14 +292,12 @@ struct ZigTarget {
 
     const char *llvm_cpu_name;
     const char *llvm_cpu_features;
-    const char *builtin_str;
+    const char *cpu_builtin_str;
     const char *cache_hash;
+    const char *os_builtin_str;
+    const char *dynamic_linker;
 };
 
-// ABI warning
-ZIG_EXTERN_C enum Error stage2_detect_dynamic_linker(const struct ZigTarget *target,
-        char **out_ptr, size_t *out_len);
-
 // ABI warning
 ZIG_EXTERN_C enum Error stage2_target_parse(struct ZigTarget *target, const char *zig_triple, const char *mcpu);
 
src-self-hosted/c_int.zig
@@ -70,7 +70,7 @@ pub const CInt = struct {
 
     pub fn sizeInBits(cint: CInt, self: Target) u32 {
         const arch = self.getArch();
-        switch (self.getOs()) {
+        switch (self.os.tag) {
             .freestanding, .other => switch (self.getArch()) {
                 .msp430 => switch (cint.id) {
                     .Short,
src-self-hosted/clang.zig
@@ -1050,7 +1050,7 @@ pub const struct_ZigClangExprEvalResult = extern struct {
 
 pub const struct_ZigClangAPValue = extern struct {
     Kind: ZigClangAPValueKind,
-    Data: if (builtin.os == .windows and builtin.abi == .msvc) [52]u8 else [68]u8,
+    Data: if (builtin.os.tag == .windows and builtin.abi == .msvc) [52]u8 else [68]u8,
 };
 pub extern fn ZigClangVarDecl_getTypeSourceInfo_getType(self: *const struct_ZigClangVarDecl) struct_ZigClangQualType;
 
src-self-hosted/introspect.zig
@@ -1,4 +1,4 @@
-// Introspection and determination of system libraries needed by zig.
+//! Introspection and determination of system libraries needed by zig.
 
 const std = @import("std");
 const mem = std.mem;
@@ -6,14 +6,6 @@ const fs = std.fs;
 
 const warn = std.debug.warn;
 
-pub fn detectDynamicLinker(allocator: *mem.Allocator, target: std.Target) ![:0]u8 {
-    if (target == .Native) {
-        return @import("libc_installation.zig").detectNativeDynamicLinker(allocator);
-    } else {
-        return target.getStandardDynamicLinkerPath(allocator);
-    }
-}
-
 /// Caller must free result
 pub fn testZigInstallPrefix(allocator: *mem.Allocator, test_path: []const u8) ![]u8 {
     const test_zig_dir = try fs.path.join(allocator, &[_][]const u8{ test_path, "lib", "zig" });
src-self-hosted/libc_installation.zig
@@ -99,27 +99,27 @@ pub const LibCInstallation = struct {
             return error.ParseError;
         }
         if (self.crt_dir == null and !is_darwin) {
-            try stderr.print("crt_dir may not be empty for {}\n", .{@tagName(Target.current.getOs())});
+            try stderr.print("crt_dir may not be empty for {}\n", .{@tagName(Target.current.os.tag)});
             return error.ParseError;
         }
         if (self.static_crt_dir == null and is_windows and is_gnu) {
             try stderr.print("static_crt_dir may not be empty for {}-{}\n", .{
-                @tagName(Target.current.getOs()),
-                @tagName(Target.current.getAbi()),
+                @tagName(Target.current.os.tag),
+                @tagName(Target.current.abi),
             });
             return error.ParseError;
         }
         if (self.msvc_lib_dir == null and is_windows and !is_gnu) {
             try stderr.print("msvc_lib_dir may not be empty for {}-{}\n", .{
-                @tagName(Target.current.getOs()),
-                @tagName(Target.current.getAbi()),
+                @tagName(Target.current.os.tag),
+                @tagName(Target.current.abi),
             });
             return error.ParseError;
         }
         if (self.kernel32_lib_dir == null and is_windows and !is_gnu) {
             try stderr.print("kernel32_lib_dir may not be empty for {}-{}\n", .{
-                @tagName(Target.current.getOs()),
-                @tagName(Target.current.getAbi()),
+                @tagName(Target.current.os.tag),
+                @tagName(Target.current.abi),
             });
             return error.ParseError;
         }
@@ -616,104 +616,6 @@ fn printVerboseInvocation(
     }
 }
 
-/// Caller owns returned memory.
-pub fn detectNativeDynamicLinker(allocator: *Allocator) error{
-    OutOfMemory,
-    TargetHasNoDynamicLinker,
-    UnknownDynamicLinkerPath,
-}![:0]u8 {
-    if (!comptime Target.current.hasDynamicLinker()) {
-        return error.TargetHasNoDynamicLinker;
-    }
-
-    // The current target's ABI cannot be relied on for this. For example, we may build the zig
-    // compiler for target riscv64-linux-musl and provide a tarball for users to download.
-    // A user could then run that zig compiler on riscv64-linux-gnu. This use case is well-defined
-    // and supported by Zig. But that means that we must detect the system ABI here rather than
-    // relying on `std.Target.current`.
-
-    const LdInfo = struct {
-        ld_path: []u8,
-        abi: Target.Abi,
-    };
-    var ld_info_list = std.ArrayList(LdInfo).init(allocator);
-    defer {
-        for (ld_info_list.toSlice()) |ld_info| allocator.free(ld_info.ld_path);
-        ld_info_list.deinit();
-    }
-
-    const all_abis = comptime blk: {
-        const fields = std.meta.fields(Target.Abi);
-        var array: [fields.len]Target.Abi = undefined;
-        inline for (fields) |field, i| {
-            array[i] = @field(Target.Abi, field.name);
-        }
-        break :blk array;
-    };
-    for (all_abis) |abi| {
-        // This may be a nonsensical parameter. We detect this with error.UnknownDynamicLinkerPath and
-        // skip adding it to `ld_info_list`.
-        const target: Target = .{
-            .Cross = .{
-                .cpu = Target.Cpu.baseline(Target.current.getArch()),
-                .os = Target.current.getOs(),
-                .abi = abi,
-            },
-        };
-        const standard_ld_path = target.getStandardDynamicLinkerPath(allocator) catch |err| switch (err) {
-            error.OutOfMemory => return error.OutOfMemory,
-            error.UnknownDynamicLinkerPath, error.TargetHasNoDynamicLinker => continue,
-        };
-        errdefer allocator.free(standard_ld_path);
-        try ld_info_list.append(.{
-            .ld_path = standard_ld_path,
-            .abi = abi,
-        });
-    }
-
-    // Best case scenario: the zig compiler is dynamically linked, and we can iterate
-    // over our own shared objects and find a dynamic linker.
-    {
-        const lib_paths = try std.process.getSelfExeSharedLibPaths(allocator);
-        defer allocator.free(lib_paths);
-
-        // This is O(N^M) but typical case here is N=2 and M=10.
-        for (lib_paths) |lib_path| {
-            for (ld_info_list.toSlice()) |ld_info| {
-                const standard_ld_basename = fs.path.basename(ld_info.ld_path);
-                if (std.mem.endsWith(u8, lib_path, standard_ld_basename)) {
-                    return std.mem.dupeZ(allocator, u8, lib_path);
-                }
-            }
-        }
-    }
-
-    // If Zig is statically linked, such as via distributed binary static builds, the above
-    // trick won't work. What are we left with? Try to run the system C compiler and get
-    // it to tell us the dynamic linker path.
-    // TODO: instead of this, look at the shared libs of /usr/bin/env.
-    for (ld_info_list.toSlice()) |ld_info| {
-        const standard_ld_basename = fs.path.basename(ld_info.ld_path);
-
-        const full_ld_path = ccPrintFileName(.{
-            .allocator = allocator,
-            .search_basename = standard_ld_basename,
-            .want_dirname = .full_path,
-        }) catch |err| switch (err) {
-            error.OutOfMemory => return error.OutOfMemory,
-            error.LibCRuntimeNotFound,
-            error.CCompilerExitCode,
-            error.CCompilerCrashed,
-            error.UnableToSpawnCCompiler,
-            => continue,
-        };
-        return full_ld_path;
-    }
-
-    // Finally, we fall back on the standard path.
-    return Target.current.getStandardDynamicLinkerPath(allocator);
-}
-
 const Search = struct {
     path: []const u8,
     version: []const u8,
src-self-hosted/link.zig
@@ -515,7 +515,7 @@ const DarwinPlatform = struct {
                 break :blk ver;
             },
             .None => blk: {
-                assert(comp.target.getOs() == .macosx);
+                assert(comp.target.os.tag == .macosx);
                 result.kind = .MacOS;
                 break :blk "10.14";
             },
@@ -534,7 +534,7 @@ const DarwinPlatform = struct {
         }
 
         if (result.kind == .IPhoneOS) {
-            switch (comp.target.getArch()) {
+            switch (comp.target.cpu.arch) {
                 .i386,
                 .x86_64,
                 => result.kind = .IPhoneOSSimulator,
src-self-hosted/main.zig
@@ -79,9 +79,9 @@ pub fn main() !void {
     } else if (mem.eql(u8, cmd, "libc")) {
         return cmdLibC(allocator, cmd_args);
     } else if (mem.eql(u8, cmd, "targets")) {
-        // TODO figure out the current target rather than using the target that was specified when
-        // compiling the compiler
-        return @import("print_targets.zig").cmdTargets(allocator, cmd_args, stdout, Target.current);
+        const info = try std.zig.system.NativeTargetInfo.detect(allocator);
+        defer info.deinit(allocator);
+        return @import("print_targets.zig").cmdTargets(allocator, cmd_args, stdout, info.target);
     } else if (mem.eql(u8, cmd, "version")) {
         return cmdVersion(allocator, cmd_args);
     } else if (mem.eql(u8, cmd, "zen")) {
src-self-hosted/print_targets.zig
@@ -124,7 +124,7 @@ pub fn cmdTargets(
 
     try jws.objectField("os");
     try jws.beginArray();
-    inline for (@typeInfo(Target.Os).Enum.fields) |field| {
+    inline for (@typeInfo(Target.Os.Tag).Enum.fields) |field| {
         try jws.arrayElem();
         try jws.emitString(field.name);
     }
@@ -201,16 +201,16 @@ pub fn cmdTargets(
         try jws.objectField("cpu");
         try jws.beginObject();
         try jws.objectField("arch");
-        try jws.emitString(@tagName(native_target.getArch()));
+        try jws.emitString(@tagName(native_target.cpu.arch));
 
         try jws.objectField("name");
-        const cpu = native_target.getCpu();
+        const cpu = native_target.cpu;
         try jws.emitString(cpu.model.name);
 
         {
             try jws.objectField("features");
             try jws.beginArray();
-            for (native_target.getArch().allFeaturesList()) |feature, i_usize| {
+            for (native_target.cpu.arch.allFeaturesList()) |feature, i_usize| {
                 const index = @intCast(Target.Cpu.Feature.Set.Index, i_usize);
                 if (cpu.features.isEnabled(index)) {
                     try jws.arrayElem();
@@ -222,9 +222,9 @@ pub fn cmdTargets(
         try jws.endObject();
     }
     try jws.objectField("os");
-    try jws.emitString(@tagName(native_target.getOs()));
+    try jws.emitString(@tagName(native_target.os.tag));
     try jws.objectField("abi");
-    try jws.emitString(@tagName(native_target.getAbi()));
+    try jws.emitString(@tagName(native_target.abi));
     // TODO implement native glibc version detection in self-hosted
     try jws.endObject();
 
src-self-hosted/stage2.zig
@@ -110,6 +110,8 @@ const Error = extern enum {
     WindowsSdkNotFound,
     UnknownDynamicLinkerPath,
     TargetHasNoDynamicLinker,
+    InvalidAbiVersion,
+    InvalidOperatingSystemVersion,
 };
 
 const FILE = std.c.FILE;
@@ -633,11 +635,11 @@ export fn stage2_cmd_targets(zig_triple: [*:0]const u8) c_int {
 
 fn cmdTargets(zig_triple: [*:0]const u8) !void {
     var target = try Target.parse(.{ .arch_os_abi = mem.toSliceConst(u8, zig_triple) });
-    target.Cross.cpu = blk: {
+    target.cpu = blk: {
         const llvm = @import("llvm.zig");
         const llvm_cpu_name = llvm.GetHostCPUName();
         const llvm_cpu_features = llvm.GetNativeFeatures();
-        break :blk try detectNativeCpuWithLLVM(target.getArch(), llvm_cpu_name, llvm_cpu_features);
+        break :blk try detectNativeCpuWithLLVM(target.cpu.arch, llvm_cpu_name, llvm_cpu_features);
     };
     return @import("print_targets.zig").cmdTargets(
         std.heap.c_allocator,
@@ -662,6 +664,14 @@ export fn stage2_target_parse(
         error.MissingArchitecture => return .MissingArchitecture,
         error.InvalidLlvmCpuFeaturesFormat => return .InvalidLlvmCpuFeaturesFormat,
         error.UnexpectedExtraField => return .SemanticAnalyzeFail,
+        error.InvalidAbiVersion => return .InvalidAbiVersion,
+        error.InvalidOperatingSystemVersion => return .InvalidOperatingSystemVersion,
+        error.FileSystem => return .FileSystem,
+        error.SymLinkLoop => return .SymLinkLoop,
+        error.SystemResources => return .SystemResources,
+        error.ProcessFdQuotaExceeded => return .ProcessFdQuotaExceeded,
+        error.SystemFdQuotaExceeded => return .SystemFdQuotaExceeded,
+        error.DeviceBusy => return .DeviceBusy,
     };
     return .None;
 }
@@ -671,108 +681,48 @@ fn stage2TargetParse(
     zig_triple_oz: ?[*:0]const u8,
     mcpu_oz: ?[*:0]const u8,
 ) !void {
-    const target: Target = if (zig_triple_oz) |zig_triple_z| blk: {
+    const target: std.build.Target = if (zig_triple_oz) |zig_triple_z| blk: {
         const zig_triple = mem.toSliceConst(u8, zig_triple_z);
         const mcpu = if (mcpu_oz) |mcpu_z| mem.toSliceConst(u8, mcpu_z) else "baseline";
         var diags: std.Target.ParseOptions.Diagnostics = .{};
-        break :blk Target.parse(.{
-            .arch_os_abi = zig_triple,
-            .cpu_features = mcpu,
-            .diagnostics = &diags,
-        }) catch |err| switch (err) {
-            error.UnknownCpu => {
-                std.debug.warn("Unknown CPU: '{}'\nAvailable CPUs for architecture '{}':\n", .{
-                    diags.cpu_name.?,
-                    @tagName(diags.arch.?),
-                });
-                for (diags.arch.?.allCpuModels()) |cpu| {
-                    std.debug.warn(" {}\n", .{cpu.name});
-                }
-                process.exit(1);
-            },
-            error.UnknownCpuFeature => {
-                std.debug.warn(
-                    \\Unknown CPU feature: '{}'
-                    \\Available CPU features for architecture '{}':
-                    \\
-                , .{
-                    diags.unknown_feature_name,
-                    @tagName(diags.arch.?),
-                });
-                for (diags.arch.?.allFeaturesList()) |feature| {
-                    std.debug.warn(" {}: {}\n", .{ feature.name, feature.description });
-                }
-                process.exit(1);
+        break :blk std.build.Target{
+            .Cross = Target.parse(.{
+                .arch_os_abi = zig_triple,
+                .cpu_features = mcpu,
+                .diagnostics = &diags,
+            }) catch |err| switch (err) {
+                error.UnknownCpu => {
+                    std.debug.warn("Unknown CPU: '{}'\nAvailable CPUs for architecture '{}':\n", .{
+                        diags.cpu_name.?,
+                        @tagName(diags.arch.?),
+                    });
+                    for (diags.arch.?.allCpuModels()) |cpu| {
+                        std.debug.warn(" {}\n", .{cpu.name});
+                    }
+                    process.exit(1);
+                },
+                error.UnknownCpuFeature => {
+                    std.debug.warn(
+                        \\Unknown CPU feature: '{}'
+                        \\Available CPU features for architecture '{}':
+                        \\
+                    , .{
+                        diags.unknown_feature_name,
+                        @tagName(diags.arch.?),
+                    });
+                    for (diags.arch.?.allFeaturesList()) |feature| {
+                        std.debug.warn(" {}: {}\n", .{ feature.name, feature.description });
+                    }
+                    process.exit(1);
+                },
+                else => |e| return e,
             },
-            else => |e| return e,
         };
-    } else Target.Native;
+    } else std.build.Target.Native;
 
     try stage1_target.fromTarget(target);
 }
 
-fn initStage1TargetCpuFeatures(stage1_target: *Stage2Target, cpu: Target.Cpu) !void {
-    const allocator = std.heap.c_allocator;
-    const cache_hash = try std.fmt.allocPrint0(allocator, "{}\n{}", .{
-        cpu.model.name,
-        cpu.features.asBytes(),
-    });
-    errdefer allocator.free(cache_hash);
-
-    const generic_arch_name = cpu.arch.genericName();
-    var builtin_str_buffer = try std.Buffer.allocPrint(allocator,
-        \\Cpu{{
-        \\    .arch = .{},
-        \\    .model = &Target.{}.cpu.{},
-        \\    .features = Target.{}.featureSet(&[_]Target.{}.Feature{{
-        \\
-    , .{
-        @tagName(cpu.arch),
-        generic_arch_name,
-        cpu.model.name,
-        generic_arch_name,
-        generic_arch_name,
-    });
-    defer builtin_str_buffer.deinit();
-
-    var llvm_features_buffer = try std.Buffer.initSize(allocator, 0);
-    defer llvm_features_buffer.deinit();
-
-    for (cpu.arch.allFeaturesList()) |feature, index_usize| {
-        const index = @intCast(Target.Cpu.Feature.Set.Index, index_usize);
-        const is_enabled = cpu.features.isEnabled(index);
-
-        if (feature.llvm_name) |llvm_name| {
-            const plus_or_minus = "-+"[@boolToInt(is_enabled)];
-            try llvm_features_buffer.appendByte(plus_or_minus);
-            try llvm_features_buffer.append(llvm_name);
-            try llvm_features_buffer.append(",");
-        }
-
-        if (is_enabled) {
-            // TODO some kind of "zig identifier escape" function rather than
-            // unconditionally using @"" syntax
-            try builtin_str_buffer.append("        .@\"");
-            try builtin_str_buffer.append(feature.name);
-            try builtin_str_buffer.append("\",\n");
-        }
-    }
-
-    try builtin_str_buffer.append(
-        \\    }),
-        \\};
-        \\
-    );
-
-    assert(mem.endsWith(u8, llvm_features_buffer.toSliceConst(), ","));
-    llvm_features_buffer.shrink(llvm_features_buffer.len() - 1);
-
-    stage1_target.llvm_cpu_name = if (cpu.model.llvm_name) |s| s.ptr else null;
-    stage1_target.llvm_cpu_features = llvm_features_buffer.toOwnedSlice().ptr;
-    stage1_target.builtin_str = builtin_str_buffer.toOwnedSlice().ptr;
-    stage1_target.cache_hash = cache_hash.ptr;
-}
-
 // ABI warning
 const Stage2LibCInstallation = extern struct {
     include_dir: [*:0]const u8,
@@ -952,10 +902,13 @@ const Stage2Target = extern struct {
 
     llvm_cpu_name: ?[*:0]const u8,
     llvm_cpu_features: ?[*:0]const u8,
-    builtin_str: ?[*:0]const u8,
+    cpu_builtin_str: ?[*:0]const u8,
     cache_hash: ?[*:0]const u8,
+    os_builtin_str: ?[*:0]const u8,
+
+    dynamic_linker: ?[*:0]const u8,
 
-    fn toTarget(in_target: Stage2Target) Target {
+    fn toTarget(in_target: Stage2Target) std.build.Target {
         if (in_target.is_native) return .Native;
 
         const in_arch = in_target.arch - 1; // skip over ZigLLVM_UnknownArch
@@ -965,39 +918,244 @@ const Stage2Target = extern struct {
         return .{
             .Cross = .{
                 .cpu = Target.Cpu.baseline(enumInt(Target.Cpu.Arch, in_arch)),
-                .os = enumInt(Target.Os, in_os),
+                .os = Target.Os.defaultVersionRange(enumInt(Target.Os.Tag, in_os)),
                 .abi = enumInt(Target.Abi, in_abi),
             },
         };
     }
 
-    fn fromTarget(self: *Stage2Target, target: Target) !void {
-        const cpu = switch (target) {
+    fn fromTarget(self: *Stage2Target, build_target: std.build.Target) !void {
+        const allocator = std.heap.c_allocator;
+        var dynamic_linker: ?[*:0]u8 = null;
+        const target = switch (build_target) {
             .Native => blk: {
-                // TODO self-host CPU model and feature detection instead of relying on LLVM
+                const info = try std.zig.system.NativeTargetInfo.detect(std.heap.c_allocator);
+                if (info.dynamic_linker) |dl| {
+                    dynamic_linker = dl.ptr;
+                }
+
+                // TODO we want to just use info.target but implementing CPU model & feature detection is todo
+                // so here we rely on LLVM
                 const llvm = @import("llvm.zig");
                 const llvm_cpu_name = llvm.GetHostCPUName();
                 const llvm_cpu_features = llvm.GetNativeFeatures();
-                break :blk try detectNativeCpuWithLLVM(target.getArch(), llvm_cpu_name, llvm_cpu_features);
+                const arch = std.Target.current.cpu.arch;
+                var t = info.target;
+                t.cpu = try detectNativeCpuWithLLVM(arch, llvm_cpu_name, llvm_cpu_features);
+                break :blk t;
             },
-            .Cross => target.getCpu(),
+            .Cross => |t| t,
         };
+
+        var cache_hash = try std.Buffer.allocPrint(allocator, "{}\n{}\n", .{
+            target.cpu.model.name,
+            target.cpu.features.asBytes(),
+        });
+        defer cache_hash.deinit();
+
+        const generic_arch_name = target.cpu.arch.genericName();
+        var cpu_builtin_str_buffer = try std.Buffer.allocPrint(allocator,
+            \\Cpu{{
+            \\    .arch = .{},
+            \\    .model = &Target.{}.cpu.{},
+            \\    .features = Target.{}.featureSet(&[_]Target.{}.Feature{{
+            \\
+        , .{
+            @tagName(target.cpu.arch),
+            generic_arch_name,
+            target.cpu.model.name,
+            generic_arch_name,
+            generic_arch_name,
+        });
+        defer cpu_builtin_str_buffer.deinit();
+
+        var llvm_features_buffer = try std.Buffer.initSize(allocator, 0);
+        defer llvm_features_buffer.deinit();
+
+        for (target.cpu.arch.allFeaturesList()) |feature, index_usize| {
+            const index = @intCast(Target.Cpu.Feature.Set.Index, index_usize);
+            const is_enabled = target.cpu.features.isEnabled(index);
+
+            if (feature.llvm_name) |llvm_name| {
+                const plus_or_minus = "-+"[@boolToInt(is_enabled)];
+                try llvm_features_buffer.appendByte(plus_or_minus);
+                try llvm_features_buffer.append(llvm_name);
+                try llvm_features_buffer.append(",");
+            }
+
+            if (is_enabled) {
+                // TODO some kind of "zig identifier escape" function rather than
+                // unconditionally using @"" syntax
+                try cpu_builtin_str_buffer.append("        .@\"");
+                try cpu_builtin_str_buffer.append(feature.name);
+                try cpu_builtin_str_buffer.append("\",\n");
+            }
+        }
+
+        try cpu_builtin_str_buffer.append(
+            \\    }),
+            \\};
+            \\
+        );
+
+        assert(mem.endsWith(u8, llvm_features_buffer.toSliceConst(), ","));
+        llvm_features_buffer.shrink(llvm_features_buffer.len() - 1);
+
+        var os_builtin_str_buffer = try std.Buffer.allocPrint(allocator,
+            \\Os{{
+            \\    .tag = .{},
+            \\    .version_range = .{{
+        , .{@tagName(target.os.tag)});
+        defer os_builtin_str_buffer.deinit();
+
+        // We'll re-use the OS version range builtin string for the cache hash.
+        const os_builtin_str_ver_start_index = os_builtin_str_buffer.len();
+
+        @setEvalBranchQuota(2000);
+        switch (target.os.tag) {
+            .freestanding,
+            .ananas,
+            .cloudabi,
+            .dragonfly,
+            .fuchsia,
+            .ios,
+            .kfreebsd,
+            .lv2,
+            .solaris,
+            .haiku,
+            .minix,
+            .rtems,
+            .nacl,
+            .cnk,
+            .aix,
+            .cuda,
+            .nvcl,
+            .amdhsa,
+            .ps4,
+            .elfiamcu,
+            .tvos,
+            .watchos,
+            .mesa3d,
+            .contiki,
+            .amdpal,
+            .hermit,
+            .hurd,
+            .wasi,
+            .emscripten,
+            .uefi,
+            .other,
+            => try os_builtin_str_buffer.append(" .none = {} }\n"),
+
+            .freebsd,
+            .macosx,
+            .netbsd,
+            .openbsd,
+            => try os_builtin_str_buffer.print(
+                \\.semver = .{{
+                \\        .min = .{{
+                \\            .major = {},
+                \\            .minor = {},
+                \\            .patch = {},
+                \\        }},
+                \\        .max = .{{
+                \\            .major = {},
+                \\            .minor = {},
+                \\            .patch = {},
+                \\        }},
+                \\    }}}},
+            , .{
+                target.os.version_range.semver.min.major,
+                target.os.version_range.semver.min.minor,
+                target.os.version_range.semver.min.patch,
+
+                target.os.version_range.semver.max.major,
+                target.os.version_range.semver.max.minor,
+                target.os.version_range.semver.max.patch,
+            }),
+
+            .linux => try os_builtin_str_buffer.print(
+                \\.linux = .{{
+                \\        .range = .{{
+                \\            .min = .{{
+                \\                .major = {},
+                \\                .minor = {},
+                \\                .patch = {},
+                \\            }},
+                \\            .max = .{{
+                \\                .major = {},
+                \\                .minor = {},
+                \\                .patch = {},
+                \\            }},
+                \\        }},
+                \\        .glibc = .{{
+                \\            .major = {},
+                \\            .minor = {},
+                \\            .patch = {},
+                \\        }},
+                \\    }}}},
+                \\
+            , .{
+                target.os.version_range.linux.range.min.major,
+                target.os.version_range.linux.range.min.minor,
+                target.os.version_range.linux.range.min.patch,
+
+                target.os.version_range.linux.range.max.major,
+                target.os.version_range.linux.range.max.minor,
+                target.os.version_range.linux.range.max.patch,
+
+                target.os.version_range.linux.glibc.major,
+                target.os.version_range.linux.glibc.minor,
+                target.os.version_range.linux.glibc.patch,
+            }),
+
+            .windows => try os_builtin_str_buffer.print(
+                \\.semver = .{{
+                \\        .min = .{},
+                \\        .max = .{},
+                \\    }}}},
+            , .{
+                @tagName(target.os.version_range.windows.min),
+                @tagName(target.os.version_range.windows.max),
+            }),
+        }
+        try os_builtin_str_buffer.append("};\n");
+
+        try cache_hash.append(
+            os_builtin_str_buffer.toSlice()[os_builtin_str_ver_start_index..os_builtin_str_buffer.len()],
+        );
+
+        const glibc_version = if (target.isGnuLibC()) blk: {
+            const stage1_glibc = try std.heap.c_allocator.create(Stage2GLibCVersion);
+            const stage2_glibc = target.os.version_range.linux.glibc;
+            stage1_glibc.* = .{
+                .major = stage2_glibc.major,
+                .minor = stage2_glibc.minor,
+                .patch = stage2_glibc.patch,
+            };
+            break :blk stage1_glibc;
+        } else null;
+
         self.* = .{
-            .arch = @enumToInt(target.getArch()) + 1, // skip over ZigLLVM_UnknownArch
+            .arch = @enumToInt(target.cpu.arch) + 1, // skip over ZigLLVM_UnknownArch
             .vendor = 0,
-            .os = @enumToInt(target.getOs()),
-            .abi = @enumToInt(target.getAbi()),
-            .llvm_cpu_name = null,
-            .llvm_cpu_features = null,
-            .builtin_str = null,
-            .cache_hash = null,
-            .is_native = target == .Native,
-            .glibc_version = null,
+            .os = @enumToInt(target.os.tag),
+            .abi = @enumToInt(target.abi),
+            .llvm_cpu_name = if (target.cpu.model.llvm_name) |s| s.ptr else null,
+            .llvm_cpu_features = llvm_features_buffer.toOwnedSlice().ptr,
+            .cpu_builtin_str = cpu_builtin_str_buffer.toOwnedSlice().ptr,
+            .os_builtin_str = os_builtin_str_buffer.toOwnedSlice().ptr,
+            .cache_hash = cache_hash.toOwnedSlice().ptr,
+            .is_native = build_target == .Native,
+            .glibc_version = glibc_version,
+            .dynamic_linker = dynamic_linker,
         };
-        try initStage1TargetCpuFeatures(self, cpu);
     }
 };
 
+fn enumInt(comptime Enum: type, int: c_int) Enum {
+    return @intToEnum(Enum, @intCast(@TagType(Enum), int));
+}
+
 // ABI warning
 const Stage2GLibCVersion = extern struct {
     major: u32,
@@ -1005,26 +1163,6 @@ const Stage2GLibCVersion = extern struct {
     patch: u32,
 };
 
-// ABI warning
-export fn stage2_detect_dynamic_linker(in_target: *const Stage2Target, out_ptr: *[*:0]u8, out_len: *usize) Error {
-    const target = in_target.toTarget();
-    const result = @import("introspect.zig").detectDynamicLinker(
-        std.heap.c_allocator,
-        target,
-    ) catch |err| switch (err) {
-        error.OutOfMemory => return .OutOfMemory,
-        error.UnknownDynamicLinkerPath => return .UnknownDynamicLinkerPath,
-        error.TargetHasNoDynamicLinker => return .TargetHasNoDynamicLinker,
-    };
-    out_ptr.* = result.ptr;
-    out_len.* = result.len;
-    return .None;
-}
-
-fn enumInt(comptime Enum: type, int: c_int) Enum {
-    return @intToEnum(Enum, @intCast(@TagType(Enum), int));
-}
-
 // ABI warning
 const Stage2NativePaths = extern struct {
     include_dirs_ptr: [*][*:0]u8,
src-self-hosted/util.zig
@@ -34,25 +34,3 @@ pub fn initializeAllTargets() void {
     llvm.InitializeAllAsmPrinters();
     llvm.InitializeAllAsmParsers();
 }
-
-pub fn getTriple(allocator: *std.mem.Allocator, self: std.Target) !std.Buffer {
-    var result = try std.Buffer.initSize(allocator, 0);
-    errdefer result.deinit();
-
-    // LLVM WebAssembly output support requires the target to be activated at
-    // build type with -DCMAKE_LLVM_EXPIERMENTAL_TARGETS_TO_BUILD=WebAssembly.
-    //
-    // LLVM determines the output format based on the abi suffix,
-    // defaulting to an object based on the architecture. The default format in
-    // LLVM 6 sets the wasm arch output incorrectly to ELF. We need to
-    // explicitly set this ourself in order for it to work.
-    //
-    // This is fixed in LLVM 7 and you will be able to get wasm output by
-    // using the target triple `wasm32-unknown-unknown-unknown`.
-    const env_name = if (self.isWasm()) "wasm" else @tagName(self.getAbi());
-
-    var out = &std.io.BufferOutStream.init(&result).stream;
-    try out.print("{}-unknown-{}-{}", .{ @tagName(self.getArch()), @tagName(self.getOs()), env_name });
-
-    return result;
-}
test/stage1/behavior/asm.zig
@@ -1,9 +1,10 @@
 const std = @import("std");
-const config = @import("builtin");
 const expect = std.testing.expect;
 
+const is_x86_64_linux = std.Target.current.cpu.arch == .x86_64 and std.Target.current.os.tag == .linux;
+
 comptime {
-    if (config.arch == config.Arch.x86_64 and config.os == config.Os.linux) {
+    if (is_x86_64_linux) {
         asm (
             \\.globl this_is_my_alias;
             \\.type this_is_my_alias, @function;
@@ -13,7 +14,7 @@ comptime {
 }
 
 test "module level assembly" {
-    if (config.arch == config.Arch.x86_64 and config.os == config.Os.linux) {
+    if (is_x86_64_linux) {
         expect(this_is_my_alias() == 1234);
     }
 }
test/stage1/behavior/byteswap.zig
@@ -1,6 +1,5 @@
 const std = @import("std");
 const expect = std.testing.expect;
-const builtin = @import("builtin");
 
 test "@byteSwap integers" {
     const ByteSwapIntTest = struct {
@@ -41,10 +40,10 @@ test "@byteSwap integers" {
 
 test "@byteSwap vectors" {
     // https://github.com/ziglang/zig/issues/3563
-    if (builtin.os == .dragonfly) return error.SkipZigTest;
+    if (std.Target.current.os.tag == .dragonfly) return error.SkipZigTest;
 
     // https://github.com/ziglang/zig/issues/3317
-    if (builtin.arch == .mipsel) return error.SkipZigTest;
+    if (std.Target.current.cpu.arch == .mipsel) return error.SkipZigTest;
 
     const ByteSwapVectorTest = struct {
         fn run() void {
test/stage1/behavior/namespace_depends_on_compile_var.zig
@@ -1,5 +1,5 @@
-const builtin = @import("builtin");
-const expect = @import("std").testing.expect;
+const std = @import("std");
+const expect = std.testing.expect;
 
 test "namespace depends on compile var" {
     if (some_namespace.a_bool) {
@@ -8,7 +8,7 @@ test "namespace depends on compile var" {
         expect(!some_namespace.a_bool);
     }
 }
-const some_namespace = switch (builtin.os) {
-    builtin.Os.linux => @import("namespace_depends_on_compile_var/a.zig"),
+const some_namespace = switch (std.builtin.os.tag) {
+    .linux => @import("namespace_depends_on_compile_var/a.zig"),
     else => @import("namespace_depends_on_compile_var/b.zig"),
 };
test/stage1/behavior/vector.zig
@@ -2,7 +2,6 @@ const std = @import("std");
 const mem = std.mem;
 const expect = std.testing.expect;
 const expectEqual = std.testing.expectEqual;
-const builtin = @import("builtin");
 
 test "implicit cast vector to array - bool" {
     const S = struct {
@@ -114,7 +113,7 @@ test "array to vector" {
 
 test "vector casts of sizes not divisable by 8" {
     // https://github.com/ziglang/zig/issues/3563
-    if (builtin.os == .dragonfly) return error.SkipZigTest;
+    if (std.Target.current.os.tag == .dragonfly) return error.SkipZigTest;
 
     const S = struct {
         fn doTheTest() void {
test/tests.zig
@@ -31,7 +31,7 @@ pub const RunTranslatedCContext = @import("src/run_translated_c.zig").RunTransla
 pub const CompareOutputContext = @import("src/compare_output.zig").CompareOutputContext;
 
 const TestTarget = struct {
-    target: Target = .Native,
+    target: build.Target = .Native,
     mode: builtin.Mode = .Debug,
     link_libc: bool = false,
     single_threaded: bool = false,