Commit 26f2f9bf1c

Andrew Kelley <andrew@ziglang.org>
2020-09-26 21:42:07
stage2: implement -fno-emit-bin
we are now passing the cli tests
1 parent 9b83112
src/link/C.zig
@@ -30,7 +30,7 @@ pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Optio
     if (options.use_llvm) return error.LLVMHasNoCBackend;
     if (options.use_lld) return error.LLDHasNoCBackend;
 
-    const file = try options.directory.handle.createFile(sub_path, .{ .truncate = true, .read = true, .mode = link.determineMode(options) });
+    const file = try options.emit.?.directory.handle.createFile(sub_path, .{ .truncate = true, .read = true, .mode = link.determineMode(options) });
     errdefer file.close();
 
     var c_file = try allocator.create(C);
src/link/Coff.zig
@@ -120,7 +120,7 @@ pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Optio
     if (options.use_llvm) return error.LLVM_BackendIsTODO_ForCoff; // TODO
     if (options.use_lld) return error.LLD_LinkingIsTODO_ForCoff; // TODO
 
-    const file = try options.directory.handle.createFile(sub_path, .{
+    const file = try options.emit.?.directory.handle.createFile(sub_path, .{
         .truncate = false,
         .read = true,
         .mode = link.determineMode(options),
src/link/Elf.zig
@@ -229,7 +229,7 @@ pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Optio
 
     if (options.use_llvm) return error.LLVMBackendUnimplementedForELF; // TODO
 
-    const file = try options.directory.handle.createFile(sub_path, .{
+    const file = try options.emit.?.directory.handle.createFile(sub_path, .{
         .truncate = false,
         .read = true,
         .mode = link.determineMode(options),
@@ -1218,7 +1218,7 @@ fn linkWithLLD(self: *Elf, comp: *Compilation) !void {
     defer arena_allocator.deinit();
     const arena = &arena_allocator.allocator;
 
-    const directory = self.base.options.directory; // Just an alias to make it shorter to type.
+    const directory = self.base.options.emit.?.directory; // Just an alias to make it shorter to type.
 
     // If there is no Zig code to compile, then we should skip flushing the output file because it
     // will not be part of the linker line anyway.
@@ -1401,7 +1401,7 @@ fn linkWithLLD(self: *Elf, comp: *Compilation) !void {
         try argv.append("-pie");
     }
 
-    const full_out_path = try directory.join(arena, &[_][]const u8{self.base.options.sub_path});
+    const full_out_path = try directory.join(arena, &[_][]const u8{self.base.options.emit.?.sub_path});
     try argv.append("-o");
     try argv.append(full_out_path);
 
src/link/MachO.zig
@@ -142,7 +142,7 @@ pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Optio
     if (options.use_llvm) return error.LLVM_BackendIsTODO_ForMachO; // TODO
     if (options.use_lld) return error.LLD_LinkingIsTODO_ForMachO; // TODO
 
-    const file = try options.directory.handle.createFile(sub_path, .{
+    const file = try options.emit.?.directory.handle.createFile(sub_path, .{
         .truncate = false,
         .read = true,
         .mode = link.determineMode(options),
src/link/Wasm.zig
@@ -59,7 +59,7 @@ pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Optio
     if (options.use_lld) return error.LLD_LinkingIsTODO_ForWasm; // TODO
 
     // TODO: read the file and keep vaild parts instead of truncating
-    const file = try options.directory.handle.createFile(sub_path, .{ .truncate = true, .read = true });
+    const file = try options.emit.?.directory.handle.createFile(sub_path, .{ .truncate = true, .read = true });
     errdefer file.close();
 
     const wasm = try createEmpty(allocator, options);
src/Compilation.zig
@@ -500,8 +500,6 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation {
             break :pic explicit;
         } else must_pic;
 
-        const emit_bin = options.emit_bin orelse fatal("-fno-emit-bin not supported yet", .{}); // TODO
-
         // Make a decision on whether to use Clang for translate-c and compiling C files.
         const use_clang = if (options.use_clang) |explicit| explicit else blk: {
             if (build_options.have_llvm) {
@@ -667,13 +665,29 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation {
         } else null;
         errdefer if (module) |zm| zm.deinit();
 
+        const error_return_tracing = !options.strip and switch (options.optimize_mode) {
+            .Debug, .ReleaseSafe => true,
+            .ReleaseFast, .ReleaseSmall => false,
+        };
+
         // For resource management purposes.
         var owned_link_dir: ?std.fs.Dir = null;
         errdefer if (owned_link_dir) |*dir| dir.close();
 
-        const bin_directory = emit_bin.directory orelse blk: {
-            if (module) |zm| break :blk zm.zig_cache_artifact_directory;
-
+        const bin_file_emit: ?link.Emit = blk: {
+            const emit_bin = options.emit_bin orelse break :blk null;
+            if (emit_bin.directory) |directory| {
+                break :blk link.Emit{
+                    .directory = directory,
+                    .sub_path = emit_bin.basename,
+                };
+            }
+            if (module) |zm| {
+                break :blk link.Emit{
+                    .directory = zm.zig_cache_artifact_directory,
+                    .sub_path = emit_bin.basename,
+                };
+            }
             // We could use the cache hash as is no problem, however, we increase
             // the likelihood of cache hits by adding the first C source file
             // path name (not contents) to the hash. This way if the user is compiling
@@ -694,17 +708,14 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation {
                 .handle = artifact_dir,
                 .path = try options.local_cache_directory.join(arena, &[_][]const u8{artifact_sub_dir}),
             };
-            break :blk link_artifact_directory;
-        };
-
-        const error_return_tracing = !options.strip and switch (options.optimize_mode) {
-            .Debug, .ReleaseSafe => true,
-            .ReleaseFast, .ReleaseSmall => false,
+            break :blk link.Emit{
+                .directory = link_artifact_directory,
+                .sub_path = emit_bin.basename,
+            };
         };
 
         const bin_file = try link.File.openPath(gpa, .{
-            .directory = bin_directory,
-            .sub_path = emit_bin.basename,
+            .emit = bin_file_emit,
             .root_name = root_name,
             .module = module,
             .target = options.target,
@@ -815,43 +826,46 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation {
         comp.c_object_table.putAssumeCapacityNoClobber(c_object, {});
     }
 
-    // If we need to build glibc for the target, add work items for it.
-    // We go through the work queue so that building can be done in parallel.
-    if (comp.wantBuildGLibCFromSource()) {
-        try comp.addBuildingGLibCJobs();
-    }
-    if (comp.wantBuildMuslFromSource()) {
-        try comp.work_queue.write(&[_]Job{
-            .{ .musl_crt_file = .crti_o },
-            .{ .musl_crt_file = .crtn_o },
-            .{ .musl_crt_file = .crt1_o },
-            .{ .musl_crt_file = .scrt1_o },
-            .{ .musl_crt_file = .libc_a },
-        });
-    }
-    if (comp.wantBuildMinGWW64FromSource()) {
-        @panic("TODO");
-    }
-    if (comp.wantBuildLibUnwindFromSource()) {
-        try comp.work_queue.writeItem(.{ .libunwind = {} });
-    }
-    if (build_options.have_llvm and comp.bin_file.options.output_mode != .Obj and
-        comp.bin_file.options.link_libcpp)
-    {
-        try comp.work_queue.writeItem(.libcxx);
-        try comp.work_queue.writeItem(.libcxxabi);
+    if (comp.bin_file.options.emit != null) {
+        // If we need to build glibc for the target, add work items for it.
+        // We go through the work queue so that building can be done in parallel.
+        if (comp.wantBuildGLibCFromSource()) {
+            try comp.addBuildingGLibCJobs();
+        }
+        if (comp.wantBuildMuslFromSource()) {
+            try comp.work_queue.write(&[_]Job{
+                .{ .musl_crt_file = .crti_o },
+                .{ .musl_crt_file = .crtn_o },
+                .{ .musl_crt_file = .crt1_o },
+                .{ .musl_crt_file = .scrt1_o },
+                .{ .musl_crt_file = .libc_a },
+            });
+        }
+        if (comp.wantBuildMinGWW64FromSource()) {
+            @panic("TODO");
+        }
+        if (comp.wantBuildLibUnwindFromSource()) {
+            try comp.work_queue.writeItem(.{ .libunwind = {} });
+        }
+        if (build_options.have_llvm and comp.bin_file.options.output_mode != .Obj and
+            comp.bin_file.options.link_libcpp)
+        {
+            try comp.work_queue.writeItem(.libcxx);
+            try comp.work_queue.writeItem(.libcxxabi);
+        }
+        if (is_exe_or_dyn_lib and !comp.bin_file.options.is_compiler_rt_or_libc and
+            build_options.is_stage1)
+        {
+            try comp.work_queue.writeItem(.{ .libcompiler_rt = {} });
+            if (!comp.bin_file.options.link_libc) {
+                try comp.work_queue.writeItem(.{ .zig_libc = {} });
+            }
+        }
     }
+
     if (build_options.is_stage1 and comp.bin_file.options.use_llvm) {
         try comp.work_queue.writeItem(.{ .stage1_module = {} });
     }
-    if (is_exe_or_dyn_lib and !comp.bin_file.options.is_compiler_rt_or_libc and
-        build_options.is_stage1)
-    {
-        try comp.work_queue.writeItem(.{ .libcompiler_rt = {} });
-        if (!comp.bin_file.options.link_libc) {
-            try comp.work_queue.writeItem(.{ .zig_libc = {} });
-        }
-    }
 
     return comp;
 }
@@ -2408,8 +2422,8 @@ fn buildStaticLibFromZig(comp: *Compilation, src_basename: []const u8, out: *?CR
 
     assert(out.* == null);
     out.* = Compilation.CRTFile{
-        .full_object_path = try sub_compilation.bin_file.options.directory.join(comp.gpa, &[_][]const u8{
-            sub_compilation.bin_file.options.sub_path,
+        .full_object_path = try sub_compilation.bin_file.options.emit.?.directory.join(comp.gpa, &[_][]const u8{
+            sub_compilation.bin_file.options.emit.?.sub_path,
         }),
         .lock = sub_compilation.bin_file.toOwnedLock(),
     };
@@ -2452,6 +2466,7 @@ fn updateStage1Module(comp: *Compilation) !void {
     man.hash.add(comp.bin_file.options.dll_export_fns);
     man.hash.add(comp.bin_file.options.function_sections);
     man.hash.add(comp.is_test);
+    man.hash.add(comp.bin_file.options.emit != null);
     man.hash.add(comp.emit_h != null);
     man.hash.add(comp.emit_asm != null);
     man.hash.add(comp.emit_llvm_ir != null);
@@ -2517,12 +2532,14 @@ fn updateStage1Module(comp: *Compilation) !void {
         comp.is_test,
     ) orelse return error.OutOfMemory;
 
-    const bin_basename = try std.zig.binNameAlloc(arena, .{
-        .root_name = comp.bin_file.options.root_name,
-        .target = target,
-        .output_mode = .Obj,
-    });
-    const emit_bin_path = try directory.join(arena, &[_][]const u8{bin_basename});
+    const emit_bin_path = if (comp.bin_file.options.emit != null) blk: {
+        const bin_basename = try std.zig.binNameAlloc(arena, .{
+            .root_name = comp.bin_file.options.root_name,
+            .target = target,
+            .output_mode = .Obj,
+        });
+        break :blk try directory.join(arena, &[_][]const u8{bin_basename});
+    } else "";
     const emit_h_path = try stage1LocPath(arena, comp.emit_h, directory);
     const emit_asm_path = try stage1LocPath(arena, comp.emit_asm, directory);
     const emit_llvm_ir_path = try stage1LocPath(arena, comp.emit_llvm_ir, directory);
@@ -2697,8 +2714,8 @@ pub fn build_crt_file(
     try comp.crt_files.ensureCapacity(comp.gpa, comp.crt_files.count() + 1);
 
     comp.crt_files.putAssumeCapacityNoClobber(basename, .{
-        .full_object_path = try sub_compilation.bin_file.options.directory.join(comp.gpa, &[_][]const u8{
-            sub_compilation.bin_file.options.sub_path,
+        .full_object_path = try sub_compilation.bin_file.options.emit.?.directory.join(comp.gpa, &[_][]const u8{
+            sub_compilation.bin_file.options.emit.?.sub_path,
         }),
         .lock = sub_compilation.bin_file.toOwnedLock(),
     });
src/libcxx.zig
@@ -189,7 +189,10 @@ pub fn buildLibCXX(comp: *Compilation) !void {
 
     assert(comp.libcxx_static_lib == null);
     comp.libcxx_static_lib = Compilation.CRTFile{
-        .full_object_path = try sub_compilation.bin_file.options.directory.join(comp.gpa, &[_][]const u8{basename}),
+        .full_object_path = try sub_compilation.bin_file.options.emit.?.directory.join(
+            comp.gpa,
+            &[_][]const u8{basename},
+        ),
         .lock = sub_compilation.bin_file.toOwnedLock(),
     };
 }
@@ -303,7 +306,10 @@ pub fn buildLibCXXABI(comp: *Compilation) !void {
 
     assert(comp.libcxxabi_static_lib == null);
     comp.libcxxabi_static_lib = Compilation.CRTFile{
-        .full_object_path = try sub_compilation.bin_file.options.directory.join(comp.gpa, &[_][]const u8{basename}),
+        .full_object_path = try sub_compilation.bin_file.options.emit.?.directory.join(
+            comp.gpa,
+            &[_][]const u8{basename},
+        ),
         .lock = sub_compilation.bin_file.toOwnedLock(),
     };
 }
src/libunwind.zig
@@ -126,7 +126,10 @@ pub fn buildStaticLib(comp: *Compilation) !void {
 
     assert(comp.libunwind_static_lib == null);
     comp.libunwind_static_lib = Compilation.CRTFile{
-        .full_object_path = try sub_compilation.bin_file.options.directory.join(comp.gpa, &[_][]const u8{basename}),
+        .full_object_path = try sub_compilation.bin_file.options.emit.?.directory.join(
+            comp.gpa,
+            &[_][]const u8{basename},
+        ),
         .lock = sub_compilation.bin_file.toOwnedLock(),
     };
 }
src/link.zig
@@ -16,11 +16,17 @@ const LibCInstallation = @import("libc_installation.zig").LibCInstallation;
 
 pub const producer_string = if (std.builtin.is_test) "zig test" else "zig " ++ build_options.version;
 
-pub const Options = struct {
+pub const Emit = struct {
     /// Where the output will go.
     directory: Compilation.Directory,
     /// Path to the output file, relative to `directory`.
     sub_path: []const u8,
+};
+
+pub const Options = struct {
+    /// This is `null` when -fno-emit-bin is used. When `openPath` or `flush` is called,
+    /// it will have already been null-checked.
+    emit: ?Emit,
     target: std.Target,
     output_mode: std.builtin.OutputMode,
     link_mode: std.builtin.LinkMode,
@@ -141,7 +147,7 @@ pub const File = struct {
     /// and does not cause Illegal Behavior. This operation is not atomic.
     pub fn openPath(allocator: *Allocator, options: Options) !*File {
         const use_stage1 = build_options.is_stage1 and options.use_llvm;
-        if (use_stage1) {
+        if (use_stage1 or options.emit == null) {
             return switch (options.object_format) {
                 .coff, .pe => &(try Coff.createEmpty(allocator, options)).base,
                 .elf => &(try Elf.createEmpty(allocator, options)).base,
@@ -152,6 +158,7 @@ pub const File = struct {
                 .raw => return error.RawObjectFormatUnimplemented,
             };
         }
+        const emit = options.emit.?;
         const use_lld = build_options.have_llvm and options.use_lld; // comptime known false when !have_llvm
         const sub_path = if (use_lld) blk: {
             if (options.module == null) {
@@ -167,8 +174,8 @@ pub const File = struct {
                 };
             }
             // Open a temporary object file, not the final output file because we want to link with LLD.
-            break :blk try std.fmt.allocPrint(allocator, "{}{}", .{ options.sub_path, options.target.oFileExt() });
-        } else options.sub_path;
+            break :blk try std.fmt.allocPrint(allocator, "{}{}", .{ emit.sub_path, options.target.oFileExt() });
+        } else emit.sub_path;
         errdefer if (use_lld) allocator.free(sub_path);
 
         const file: *File = switch (options.object_format) {
@@ -199,7 +206,8 @@ pub const File = struct {
         switch (base.tag) {
             .coff, .elf, .macho => {
                 if (base.file != null) return;
-                base.file = try base.options.directory.handle.createFile(base.options.sub_path, .{
+                const emit = base.options.emit orelse return;
+                base.file = try emit.directory.handle.createFile(emit.sub_path, .{
                     .truncate = false,
                     .read = true,
                     .mode = determineMode(base.options),
@@ -305,14 +313,14 @@ pub const File = struct {
     /// Commit pending changes and write headers. Takes into account final output mode
     /// and `use_lld`, not only `effectiveOutputMode`.
     pub fn flush(base: *File, comp: *Compilation) !void {
+        const emit = base.options.emit orelse return; // -fno-emit-bin
+
         if (comp.clang_preprocessor_mode == .yes) {
             // TODO: avoid extra link step when it's just 1 object file (the `zig cc -c` case)
             // Until then, we do `lld -r -o output.o input.o` even though the output is the same
             // as the input. For the preprocessing case (`zig cc -E -o foo`) we copy the file
             // to the final location.
-            const full_out_path = try base.options.directory.join(comp.gpa, &[_][]const u8{
-                base.options.sub_path,
-            });
+            const full_out_path = try emit.directory.join(comp.gpa, &[_][]const u8{emit.sub_path});
             defer comp.gpa.free(full_out_path);
             assert(comp.c_object_table.count() == 1);
             const the_entry = comp.c_object_table.items()[0];
@@ -402,7 +410,7 @@ pub const File = struct {
         defer arena_allocator.deinit();
         const arena = &arena_allocator.allocator;
 
-        const directory = base.options.directory; // Just an alias to make it shorter to type.
+        const directory = base.options.emit.?.directory; // Just an alias to make it shorter to type.
 
         // If there is no Zig code to compile, then we should skip flushing the output file because it
         // will not be part of the linker line anyway.
@@ -471,10 +479,7 @@ pub const File = struct {
             object_files.appendAssumeCapacity(try arena.dupeZ(u8, p));
         }
 
-        const full_out_path = if (directory.path) |dir_path|
-            try std.fs.path.join(arena, &[_][]const u8{ dir_path, base.options.sub_path })
-        else
-            base.options.sub_path;
+        const full_out_path = try directory.join(arena, &[_][]const u8{base.options.emit.?.sub_path});
         const full_out_path_z = try arena.dupeZ(u8, full_out_path);
 
         if (base.options.verbose_link) {
src/main.zig
@@ -1307,7 +1307,9 @@ fn buildOutputType(
                 };
             }
             if (fs.path.dirname(full_path)) |dirname| {
-                const handle = try fs.cwd().openDir(dirname, .{});
+                const handle = fs.cwd().openDir(dirname, .{}) catch |err| {
+                    fatal("unable to open output directory '{}': {}", .{ dirname, @errorName(err) });
+                };
                 cleanup_emit_bin_dir = handle;
                 break :b Compilation.EmitLoc{
                     .basename = basename,
@@ -1545,7 +1547,7 @@ fn buildOutputType(
         switch (emit_bin) {
             .no => break :blk .none,
             .yes_default_path => break :blk .{
-                .print = comp.bin_file.options.directory.path orelse ".",
+                .print = comp.bin_file.options.emit.?.directory.path orelse ".",
             },
             .yes => |full_path| break :blk .{ .update = full_path },
         }
@@ -1560,7 +1562,7 @@ fn buildOutputType(
     switch (arg_mode) {
         .run, .zig_test => run: {
             const exe_loc = emit_bin_loc orelse break :run;
-            const exe_directory = exe_loc.directory orelse comp.bin_file.options.directory;
+            const exe_directory = exe_loc.directory orelse comp.bin_file.options.emit.?.directory;
             const exe_path = try fs.path.join(arena, &[_][]const u8{
                 exe_directory.path orelse ".", exe_loc.basename,
             });
@@ -1676,8 +1678,8 @@ fn updateModule(gpa: *Allocator, comp: *Compilation, zir_out_path: ?[]const u8,
     } else switch (hook) {
         .none => {},
         .print => |bin_path| try io.getStdOut().writer().print("{s}\n", .{bin_path}),
-        .update => |full_path| _ = try comp.bin_file.options.directory.handle.updateFile(
-            comp.bin_file.options.sub_path,
+        .update => |full_path| _ = try comp.bin_file.options.emit.?.directory.handle.updateFile(
+            comp.bin_file.options.emit.?.sub_path,
             fs.cwd(),
             full_path,
             .{},
@@ -2106,7 +2108,10 @@ pub fn cmdBuild(gpa: *Allocator, arena: *Allocator, args: []const []const u8) !v
 
         try updateModule(gpa, comp, null, .none);
 
-        child_argv.items[argv_index_exe] = try comp.bin_file.options.directory.join(arena, &[_][]const u8{exe_basename});
+        child_argv.items[argv_index_exe] = try comp.bin_file.options.emit.?.directory.join(
+            arena,
+            &[_][]const u8{exe_basename},
+        );
 
         break :lock_and_argv .{
             .child_argv = child_argv.items,
test/cli.zig
@@ -58,7 +58,7 @@ fn printCmd(cwd: []const u8, argv: []const []const u8) void {
     std.debug.warn("\n", .{});
 }
 
-fn exec(cwd: []const u8, argv: []const []const u8) !ChildProcess.ExecResult {
+fn exec(cwd: []const u8, expect_0: bool, argv: []const []const u8) !ChildProcess.ExecResult {
     const max_output_size = 100 * 1024;
     const result = ChildProcess.exec(.{
         .allocator = a,
@@ -72,7 +72,7 @@ fn exec(cwd: []const u8, argv: []const []const u8) !ChildProcess.ExecResult {
     };
     switch (result.term) {
         .Exited => |code| {
-            if (code != 0) {
+            if ((code != 0) == expect_0) {
                 std.debug.warn("The following command exited with error code {}:\n", .{code});
                 printCmd(cwd, argv);
                 std.debug.warn("stderr:\n{}\n", .{result.stderr});
@@ -90,14 +90,14 @@ fn exec(cwd: []const u8, argv: []const []const u8) !ChildProcess.ExecResult {
 }
 
 fn testZigInitLib(zig_exe: []const u8, dir_path: []const u8) !void {
-    _ = try exec(dir_path, &[_][]const u8{ zig_exe, "init-lib" });
-    const test_result = try exec(dir_path, &[_][]const u8{ zig_exe, "build", "test" });
+    _ = try exec(dir_path, true, &[_][]const u8{ zig_exe, "init-lib" });
+    const test_result = try exec(dir_path, true, &[_][]const u8{ zig_exe, "build", "test" });
     testing.expect(std.mem.endsWith(u8, test_result.stderr, "All 1 tests passed.\n"));
 }
 
 fn testZigInitExe(zig_exe: []const u8, dir_path: []const u8) !void {
-    _ = try exec(dir_path, &[_][]const u8{ zig_exe, "init-exe" });
-    const run_result = try exec(dir_path, &[_][]const u8{ zig_exe, "build", "run" });
+    _ = try exec(dir_path, true, &[_][]const u8{ zig_exe, "init-exe" });
+    const run_result = try exec(dir_path, true, &[_][]const u8{ zig_exe, "build", "run" });
     testing.expect(std.mem.eql(u8, run_result.stderr, "info: All your codebase are belong to us.\n"));
 }
 
@@ -131,7 +131,7 @@ fn testGodboltApi(zig_exe: []const u8, dir_path: []const u8) anyerror!void {
     const emit_asm_arg = try std.fmt.allocPrint(a, "-femit-asm={s}", .{example_s_path});
     try args.append(emit_asm_arg);
 
-    _ = try exec(dir_path, args.items);
+    _ = try exec(dir_path, true, args.items);
 
     const out_asm = try std.fs.cwd().readFileAlloc(a, example_s_path, std.math.maxInt(usize));
     testing.expect(std.mem.indexOf(u8, out_asm, "square:") != null);
@@ -140,23 +140,23 @@ fn testGodboltApi(zig_exe: []const u8, dir_path: []const u8) anyerror!void {
 }
 
 fn testMissingOutputPath(zig_exe: []const u8, dir_path: []const u8) !void {
-    _ = try exec(dir_path, &[_][]const u8{ zig_exe, "init-exe" });
-    const output_path = try fs.path.join(a, &[_][]const u8{ "does", "not", "exist" });
+    _ = try exec(dir_path, true, &[_][]const u8{ zig_exe, "init-exe" });
+    const output_path = try fs.path.join(a, &[_][]const u8{ "does", "not", "exist", "foo.exe" });
+    const output_arg = try std.fmt.allocPrint(a, "-femit-bin={s}", .{output_path});
     const source_path = try fs.path.join(a, &[_][]const u8{ "src", "main.zig" });
-    _ = try exec(dir_path, &[_][]const u8{
-        zig_exe, "build-exe", source_path, "--output-dir", output_path,
-    });
+    const result = try exec(dir_path, false, &[_][]const u8{ zig_exe, "build-exe", source_path, output_arg });
+    testing.expect(std.mem.eql(u8, result.stderr, "error: unable to open output directory 'does/not/exist': FileNotFound\n"));
 }
 
 fn testZigFmt(zig_exe: []const u8, dir_path: []const u8) !void {
-    _ = try exec(dir_path, &[_][]const u8{ zig_exe, "init-exe" });
+    _ = try exec(dir_path, true, &[_][]const u8{ zig_exe, "init-exe" });
 
     const unformatted_code = "    // no reason for indent";
 
     const fmt1_zig_path = try fs.path.join(a, &[_][]const u8{ dir_path, "fmt1.zig" });
     try fs.cwd().writeFile(fmt1_zig_path, unformatted_code);
 
-    const run_result1 = try exec(dir_path, &[_][]const u8{ zig_exe, "fmt", fmt1_zig_path });
+    const run_result1 = try exec(dir_path, true, &[_][]const u8{ zig_exe, "fmt", fmt1_zig_path });
     // stderr should be file path + \n
     testing.expect(std.mem.startsWith(u8, run_result1.stderr, fmt1_zig_path));
     testing.expect(run_result1.stderr.len == fmt1_zig_path.len + 1 and run_result1.stderr[run_result1.stderr.len - 1] == '\n');
@@ -164,12 +164,12 @@ fn testZigFmt(zig_exe: []const u8, dir_path: []const u8) !void {
     const fmt2_zig_path = try fs.path.join(a, &[_][]const u8{ dir_path, "fmt2.zig" });
     try fs.cwd().writeFile(fmt2_zig_path, unformatted_code);
 
-    const run_result2 = try exec(dir_path, &[_][]const u8{ zig_exe, "fmt", dir_path });
+    const run_result2 = try exec(dir_path, true, &[_][]const u8{ zig_exe, "fmt", dir_path });
     // running it on the dir, only the new file should be changed
     testing.expect(std.mem.startsWith(u8, run_result2.stderr, fmt2_zig_path));
     testing.expect(run_result2.stderr.len == fmt2_zig_path.len + 1 and run_result2.stderr[run_result2.stderr.len - 1] == '\n');
 
-    const run_result3 = try exec(dir_path, &[_][]const u8{ zig_exe, "fmt", dir_path });
+    const run_result3 = try exec(dir_path, true, &[_][]const u8{ zig_exe, "fmt", dir_path });
     // both files have been formatted, nothing should change now
     testing.expect(run_result3.stderr.len == 0);
 }
BRANCH_TODO
@@ -1,4 +1,3 @@
- * support -fno-emit-bin for godbolt
  * restore the legacy -femit-h feature using the stage1 backend
  * figure out why test-translate-c is failing
  * tests passing with -Dskip-non-native