Commit 5c01818410

Felix (xq) Queißner <git@random-projects.net>
2023-07-25 13:26:32
Introduces `Compile.getEmittedX()` functions, drops `Compile.emit_X`. Resolves #14971
1 parent ce95a3b
Changed files (22)
lib
test
link
glibc_compat
macho
needed_library
search_strategy
src
standalone
embed_generated_file
emit_asm_and_bin
issue_12588
issue_339
issue_5825
issue_794
main_pkg_path
strip_empty_loop
use_alias
lib/std/Build/Step/Compile.zig
@@ -21,6 +21,8 @@ const InstallDir = std.Build.InstallDir;
 const GeneratedFile = std.Build.GeneratedFile;
 const Compile = @This();
 
+const build_util = @import("../util.zig");
+
 pub const base_id: Step.Id = .compile;
 
 step: Step,
@@ -46,14 +48,6 @@ framework_dirs: ArrayList(LazyPath),
 frameworks: StringHashMap(FrameworkLinkInfo),
 verbose_link: bool,
 verbose_cc: bool,
-emit_asm: EmitOption = .default,
-emit_bin: EmitOption = .default,
-emit_implib: EmitOption = .default,
-emit_llvm_bc: EmitOption = .default,
-emit_llvm_ir: EmitOption = .default,
-// Lots of things depend on emit_h having a consistent path,
-// so it is not an EmitOption for now.
-emit_h: bool = false,
 bundle_compiler_rt: ?bool = null,
 single_threaded: ?bool,
 stack_protector: ?bool = null,
@@ -87,6 +81,9 @@ export_symbol_names: []const []const u8 = &.{},
 
 root_src: ?LazyPath,
 out_h_filename: []const u8,
+out_ll_filename: []const u8,
+out_bc_filename: []const u8,
+out_asm_filename: []const u8,
 out_lib_filename: []const u8,
 out_pdb_filename: []const u8,
 modules: std.StringArrayHashMap(*Module),
@@ -210,12 +207,16 @@ use_lld: ?bool,
 /// otherwise.
 expect_errors: []const []const u8 = &.{},
 
-output_path_source: GeneratedFile,
-output_lib_path_source: GeneratedFile,
-output_h_path_source: GeneratedFile,
-output_pdb_path_source: GeneratedFile,
-output_dirname_source: GeneratedFile,
+emit_directory: GeneratedFile,
+
 generated_docs: ?*GeneratedFile,
+generated_asm: ?*GeneratedFile,
+generated_bin: ?*GeneratedFile,
+generated_pdb: ?*GeneratedFile,
+generated_implib: ?*GeneratedFile,
+generated_llvm_bc: ?*GeneratedFile,
+generated_llvm_ir: ?*GeneratedFile,
+generated_h: ?*GeneratedFile,
 
 pub const CSourceFiles = struct {
     files: []const []const u8,
@@ -373,22 +374,6 @@ pub const Kind = enum {
 
 pub const Linkage = enum { dynamic, static };
 
-pub const EmitOption = union(enum) {
-    default: void,
-    no_emit: void,
-    emit: void,
-    emit_to: []const u8,
-
-    fn getArg(self: @This(), b: *std.Build, arg_name: []const u8) ?[]const u8 {
-        return switch (self) {
-            .no_emit => b.fmt("-fno-{s}", .{arg_name}),
-            .default => null,
-            .emit => b.fmt("-f{s}", .{arg_name}),
-            .emit_to => |path| b.fmt("-f{s}={s}", .{ arg_name, path }),
-        };
-    }
-};
-
 pub fn create(owner: *std.Build, options: Options) *Compile {
     const name = owner.dupe(options.name);
     const root_src: ?LazyPath = if (options.root_source_file) |rsrc| rsrc.dupe(owner) else null;
@@ -454,6 +439,9 @@ pub fn create(owner: *std.Build, options: Options) *Compile {
         .version = options.version,
         .out_filename = out_filename,
         .out_h_filename = owner.fmt("{s}.h", .{name}),
+        .out_ll_filename = owner.fmt("{s}.bc", .{name}),
+        .out_bc_filename = owner.fmt("{s}.ll", .{name}),
+        .out_asm_filename = owner.fmt("{s}.s", .{name}),
         .out_lib_filename = undefined,
         .out_pdb_filename = owner.fmt("{s}.pdb", .{name}),
         .major_only_filename = null,
@@ -480,12 +468,16 @@ pub fn create(owner: *std.Build, options: Options) *Compile {
         .installed_path = null,
         .force_undefined_symbols = StringHashMap(void).init(owner.allocator),
 
-        .output_path_source = GeneratedFile{ .step = &self.step },
-        .output_lib_path_source = GeneratedFile{ .step = &self.step },
-        .output_h_path_source = GeneratedFile{ .step = &self.step },
-        .output_pdb_path_source = GeneratedFile{ .step = &self.step },
-        .output_dirname_source = GeneratedFile{ .step = &self.step },
+        .emit_directory = GeneratedFile{ .step = &self.step },
+
         .generated_docs = null,
+        .generated_asm = null,
+        .generated_bin = null,
+        .generated_pdb = null,
+        .generated_implib = null,
+        .generated_llvm_bc = null,
+        .generated_llvm_ir = null,
+        .generated_h = null,
 
         .target_info = target_info,
 
@@ -692,6 +684,7 @@ pub fn isStaticLibrary(self: *Compile) bool {
 }
 
 pub fn producesPdbFile(self: *Compile) bool {
+    // TODO: Is this right? Isn't PDB for *any* PE/COFF file?
     if (!self.target.isWindows() and !self.target.isUefi()) return false;
     if (self.target.getObjectFormat() == .c) return false;
     if (self.strip == true or (self.strip == null and self.optimize == .ReleaseSmall)) return false;
@@ -968,54 +961,75 @@ pub fn setLibCFile(self: *Compile, libc_file: ?LazyPath) void {
     self.libc_file = if (libc_file) |f| f.dupe(b) else null;
 }
 
-pub const getOutputSource = getEmittedBin; // DEPRECATED, use getEmittedBin
-
-/// Returns the generated executable, library or object file.
-/// To run an executable built with zig build, use `run`, or create an install step and invoke it.
-pub fn getEmittedBin(self: *Compile) LazyPath {
-    return .{ .generated = &self.output_path_source };
+fn getEmittedFileGeneric(self: *Compile, output_file: *?*GeneratedFile) LazyPath {
+    if (output_file.*) |g| {
+        return .{ .generated = g };
+    }
+    const arena = self.step.owner.allocator;
+    const generated_file = arena.create(GeneratedFile) catch @panic("OOM");
+    generated_file.* = .{ .step = &self.step };
+    output_file.* = generated_file;
+    return .{ .generated = generated_file };
 }
 
 pub const getOutputDirectorySource = getEmitDirectory; // DEPRECATED, use getEmitDirectory
 
+/// Returns the path to the output directory.
 pub fn getEmitDirectory(self: *Compile) LazyPath {
-    return .{ .generated = &self.output_dirname_source };
+    return .{ .generated = &self.emit_directory };
+}
+
+pub const getOutputSource = getEmittedBin; // DEPRECATED, use getEmittedBin
+
+/// Returns the path to the generated executable, library or object file.
+/// To run an executable built with zig build, use `run`, or create an install step and invoke it.
+pub fn getEmittedBin(self: *Compile) LazyPath {
+    return self.getEmittedFileGeneric(&self.generated_bin);
 }
 
 pub const getOutputLibSource = getEmittedImplib; // DEPRECATED, use getEmittedImplib
 
-/// Returns the generated import library. This function can only be called for libraries.
+/// Returns the path to the generated import library. This function can only be called for libraries.
 pub fn getEmittedImplib(self: *Compile) LazyPath {
     assert(self.kind == .lib);
-    return .{ .generated = &self.output_lib_path_source };
+    return self.getEmittedFileGeneric(&self.generated_implib);
 }
 
 pub const getOutputHSource = getEmittedH; // DEPRECATED, use getEmittedH
 
-/// Returns the generated header file.
-/// This function can only be called for libraries or object files which have `emit_h` set.
+/// Returns the path to the generated header file.
+/// This function can only be called for libraries or objects.
 pub fn getEmittedH(self: *Compile) LazyPath {
     assert(self.kind != .exe and self.kind != .@"test");
-    assert(self.emit_h);
-    return .{ .generated = &self.output_h_path_source };
+    return self.getEmittedFileGeneric(&self.generated_h);
 }
 
 pub const getOutputPdbSource = getEmittedPdb; // DEPRECATED, use getEmittedPdb
 
 /// Returns the generated PDB file. This function can only be called for Windows and UEFI.
 pub fn getEmittedPdb(self: *Compile) LazyPath {
-    // TODO: Is this right? Isn't PDB for *any* PE/COFF file?
-    assert(self.target.isWindows() or self.target.isUefi());
-    return .{ .generated = &self.output_pdb_path_source };
+    assert(self.producesPdbFile());
+    return self.getEmittedFileGeneric(&self.generated_pdb);
 }
 
+/// Returns the path to the generated documentation directory.
 pub fn getEmittedDocs(self: *Compile) LazyPath {
-    if (self.generated_docs) |g| return .{ .generated = g };
-    const arena = self.step.owner.allocator;
-    const generated_file = arena.create(GeneratedFile) catch @panic("OOM");
-    generated_file.* = .{ .step = &self.step };
-    self.generated_docs = generated_file;
-    return .{ .generated = generated_file };
+    return self.getEmittedFileGeneric(&self.generated_docs);
+}
+
+/// Returns the path to the generated assembly code.
+pub fn getEmittedAsm(self: *Compile) LazyPath {
+    return self.getEmittedFileGeneric(&self.generated_asm);
+}
+
+/// Returns the path to the generated LLVM IR.
+pub fn getEmittedLlvmIr(self: *Compile) LazyPath {
+    return self.getEmittedFileGeneric(&self.generated_llvm_ir);
+}
+
+/// Returns the path to the generated LLVM BC.
+pub fn getEmittedLlvmBc(self: *Compile) LazyPath {
+    return self.getEmittedFileGeneric(&self.generated_llvm_bc);
 }
 
 pub fn addAssemblyFile(self: *Compile, source: LazyPath) void {
@@ -1149,6 +1163,12 @@ pub fn setExecCmd(self: *Compile, args: []const ?[]const u8) void {
 }
 
 fn linkLibraryOrObject(self: *Compile, other: *Compile) void {
+    _ = other.getEmittedBin(); // Force emission of the binary
+
+    if (other.kind == .lib and other.target.isWindows()) { // TODO(xq): Is this the correct logic here?
+        _ = other.getEmittedImplib(); // Force emission of the binary
+    }
+
     self.step.dependOn(&other.step);
     self.link_objects.append(.{ .other_step = other }) catch @panic("OOM");
     self.include_dirs.append(.{ .other_step = other }) catch @panic("OOM");
@@ -1268,6 +1288,30 @@ fn constructDepString(
     }
 }
 
+fn getGeneratedFilePath(self: *Compile, comptime tag_name: []const u8, asking_step: ?*Step) []const u8 {
+    const maybe_path: ?*GeneratedFile = @field(self, tag_name);
+
+    const generated_file = maybe_path orelse {
+        std.debug.getStderrMutex().lock();
+        const stderr = std.io.getStdErr();
+
+        build_util.dumpBadGetPathHelp(&self.step, stderr, self.step.owner, asking_step) catch {};
+
+        @panic("missing emit option for " ++ tag_name);
+    };
+
+    const path = generated_file.path orelse {
+        std.debug.getStderrMutex().lock();
+        const stderr = std.io.getStdErr();
+
+        build_util.dumpBadGetPathHelp(&self.step, stderr, self.step.owner, asking_step) catch {};
+
+        @panic(tag_name ++ " is null. Is there a missing step dependency?");
+    };
+
+    return path;
+}
+
 fn make(step: *Step, prog_node: *std.Progress.Node) !void {
     const b = step.owner;
     const self = @fieldParentPtr(Compile, "step", step);
@@ -1353,7 +1397,11 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {
                         break :l;
                     }
 
-                    const full_path_lib = other.getEmittedImplib().getPath(b);
+                    // TODO(xq): Is that the right way?
+                    const full_path_lib = if (other.isDynamicLibrary() and other.target.isWindows())
+                        other.getGeneratedFilePath("generated_implib", &self.step)
+                    else
+                        other.getGeneratedFilePath("generated_bin", &self.step);
                     try zig_args.append(full_path_lib);
 
                     if (other.linkage == Linkage.dynamic and !self.target.isWindows()) {
@@ -1496,14 +1544,36 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {
     if (b.verbose_cc or self.verbose_cc) try zig_args.append("--verbose-cc");
     if (b.verbose_llvm_cpu_features) try zig_args.append("--verbose-llvm-cpu-features");
 
-    if (self.emit_asm.getArg(b, "emit-asm")) |arg| try zig_args.append(arg);
-    if (self.emit_bin.getArg(b, "emit-bin")) |arg| try zig_args.append(arg);
-    if (self.generated_docs != null) try zig_args.append("-femit-docs");
-    if (self.emit_implib.getArg(b, "emit-implib")) |arg| try zig_args.append(arg);
-    if (self.emit_llvm_bc.getArg(b, "emit-llvm-bc")) |arg| try zig_args.append(arg);
-    if (self.emit_llvm_ir.getArg(b, "emit-llvm-ir")) |arg| try zig_args.append(arg);
+    const Emitter = struct {
+        ptr: ?*GeneratedFile,
+        emit_suffix: []const u8,
+    };
 
-    if (self.emit_h) try zig_args.append("-femit-h");
+    const generated_files = [_]Emitter{
+        .{ .ptr = self.generated_asm, .emit_suffix = "asm" },
+        .{ .ptr = self.generated_bin, .emit_suffix = "bin" },
+        .{ .ptr = self.generated_docs, .emit_suffix = "docs" },
+        .{ .ptr = self.generated_implib, .emit_suffix = "implib" },
+        .{ .ptr = self.generated_llvm_bc, .emit_suffix = "llvm-bc" },
+        .{ .ptr = self.generated_llvm_ir, .emit_suffix = "llvm-ir" },
+        .{ .ptr = self.generated_h, .emit_suffix = "h" },
+    };
+    var any_emitted_file = false;
+    for (generated_files) |file| {
+        try zig_args.append(if (file.ptr != null)
+            b.fmt("-femit-{s}", .{file.emit_suffix})
+        else
+            b.fmt("-fno-emit-{s}", .{file.emit_suffix}));
+
+        if (file.ptr != null) any_emitted_file = true;
+    }
+
+    if (!any_emitted_file) {
+        std.debug.getStderrMutex().lock();
+        const stderr = std.io.getStdErr();
+        build_util.dumpBadGetPathHelp(&self.step, stderr, self.step.owner, null) catch {};
+        std.debug.panic("Artifact '{s}' has no emit options set, but it is made. Did you forget to call `getEmitted*()`?.", .{self.name});
+    }
 
     try addFlag(&zig_args, "strip", self.strip);
     try addFlag(&zig_args, "unwind-tables", self.unwind_tables);
@@ -1755,10 +1825,9 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {
                 try zig_args.append(common_include_path);
             },
             .other_step => |other| {
-                if (other.emit_h) {
-                    const h_path = other.getEmittedH().getPath(b);
+                if (other.generated_h) |header| {
                     try zig_args.append("-isystem");
-                    try zig_args.append(fs.path.dirname(h_path).?);
+                    try zig_args.append(fs.path.dirname(header.path.?).?);
                 }
                 if (other.installed_headers.items.len > 0) {
                     try zig_args.append("-I");
@@ -1989,33 +2058,64 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {
     if (maybe_output_bin_path) |output_bin_path| {
         const output_dir = fs.path.dirname(output_bin_path).?;
 
-        self.output_dirname_source.path = output_dir;
-
-        self.output_path_source.path = b.pathJoin(
-            &.{ output_dir, self.out_filename },
-        );
+        self.emit_directory.path = output_dir;
 
-        if (self.kind == .lib) {
-            self.output_lib_path_source.path = b.pathJoin(
-                &.{ output_dir, self.out_lib_filename },
+        // -femit-bin[=path]         (default) Output machine code
+        if (self.generated_bin) |bin| {
+            bin.path = b.pathJoin(
+                &.{ output_dir, self.out_filename },
             );
         }
 
-        if (self.emit_h) {
-            self.output_h_path_source.path = b.pathJoin(
-                &.{ output_dir, self.out_h_filename },
+        // output PDB if someone requested it
+        if (self.generated_pdb) |pdb| {
+            std.debug.assert(self.producesPdbFile());
+            pdb.path = b.pathJoin(
+                &.{ output_dir, self.out_pdb_filename },
             );
         }
 
-        if (self.target.isWindows() or self.target.isUefi()) {
-            self.output_pdb_path_source.path = b.pathJoin(
-                &.{ output_dir, self.out_pdb_filename },
+        // -femit-implib[=path]      (default) Produce an import .lib when building a Windows DLL
+        if (self.kind == .lib) {
+            if (self.generated_implib) |lib| {
+                lib.path = b.pathJoin(
+                    &.{ output_dir, self.out_lib_filename },
+                );
+            }
+        }
+
+        // -femit-h[=path]           Generate a C header file (.h)
+        if (self.generated_h) |lazy_path| {
+            lazy_path.path = b.pathJoin(
+                &.{ output_dir, self.out_h_filename },
             );
         }
 
+        // -femit-docs[=path]        Create a docs/ dir with html documentation
         if (self.generated_docs) |generated_docs| {
             generated_docs.path = b.pathJoin(&.{ output_dir, "docs" });
         }
+
+        // -femit-asm[=path]         Output .s (assembly code)
+        if (self.generated_asm) |lazy_path| {
+            lazy_path.path = b.pathJoin(
+                &.{ output_dir, self.out_asm_filename },
+            );
+        }
+
+        // -femit-llvm-ir[=path]     Produce a .ll file with optimized LLVM IR (requires LLVM extensions)
+        if (self.generated_llvm_ir) |lazy_path| {
+            lazy_path.path = b.pathJoin(
+                &.{ output_dir, self.out_ll_filename },
+            );
+        }
+
+        // -femit-llvm-bc[=path]     Produce an optimized LLVM module as a .bc file (requires LLVM extensions)
+        if (self.generated_llvm_bc) |lazy_path| {
+            lazy_path.path = b.pathJoin(
+                &.{ output_dir, self.out_bc_filename },
+            );
+        }
     }
 
     if (self.kind == .lib and self.linkage != null and self.linkage.? == .dynamic and
lib/std/Build/Step/InstallArtifact.zig
@@ -37,11 +37,12 @@ pub fn create(owner: *std.Build, artifact: *Step.Compile) *InstallArtifact {
                 break :blk InstallDir{ .lib = {} };
             }
         } else null,
-        .h_dir = if (artifact.kind == .lib and artifact.emit_h) .header else null,
+        .h_dir = if (artifact.kind == .lib and artifact.generated_h != null) .header else null,
         .dest_sub_path = null,
     };
     self.step.dependOn(&artifact.step);
 
+    _ = artifact.getEmittedBin(); // force creation
     owner.pushInstalledFile(self.dest_dir, artifact.out_filename);
     if (self.artifact.isDynamicLibrary()) {
         if (artifact.major_only_filename) |name| {
@@ -55,9 +56,11 @@ pub fn create(owner: *std.Build, artifact: *Step.Compile) *InstallArtifact {
         }
     }
     if (self.pdb_dir) |pdb_dir| {
+        _ = artifact.getEmittedPdb(); // force creation
         owner.pushInstalledFile(pdb_dir, artifact.out_pdb_filename);
     }
     if (self.h_dir) |h_dir| {
+        _ = artifact.getEmittedH(); // force creation
         owner.pushInstalledFile(h_dir, artifact.out_h_filename);
     }
     return self;
@@ -66,7 +69,6 @@ pub fn create(owner: *std.Build, artifact: *Step.Compile) *InstallArtifact {
 fn make(step: *Step, prog_node: *std.Progress.Node) !void {
     _ = prog_node;
     const self = @fieldParentPtr(InstallArtifact, "step", step);
-    const src_builder = self.artifact.step.owner;
     const dest_builder = step.owner;
 
     const dest_sub_path = if (self.dest_sub_path) |sub_path| sub_path else self.artifact.out_filename;
@@ -76,7 +78,7 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {
     var all_cached = true;
 
     {
-        const full_src_path = self.artifact.getEmittedBin().getPath(src_builder);
+        const full_src_path = self.artifact.generated_bin.?.path.?;
         const p = fs.Dir.updateFile(cwd, full_src_path, cwd, full_dest_path, .{}) catch |err| {
             return step.fail("unable to update file from '{s}' to '{s}': {s}", .{
                 full_src_path, full_dest_path, @errorName(err),
@@ -93,9 +95,9 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {
     }
     if (self.artifact.isDynamicLibrary() and
         self.artifact.target.isWindows() and
-        self.artifact.emit_implib != .no_emit)
+        self.artifact.generated_implib != null)
     {
-        const full_src_path = self.artifact.getEmittedImplib().getPath(src_builder);
+        const full_src_path = self.artifact.generated_implib.?.path.?;
         const full_implib_path = dest_builder.getInstallPath(self.dest_dir, self.artifact.out_lib_filename);
         const p = fs.Dir.updateFile(cwd, full_src_path, cwd, full_implib_path, .{}) catch |err| {
             return step.fail("unable to update file from '{s}' to '{s}': {s}", .{
@@ -105,7 +107,7 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {
         all_cached = all_cached and p == .fresh;
     }
     if (self.pdb_dir) |pdb_dir| {
-        const full_src_path = self.artifact.getEmittedPdb().getPath(src_builder);
+        const full_src_path = self.artifact.generated_pdb.?.path.?;
         const full_pdb_path = dest_builder.getInstallPath(pdb_dir, self.artifact.out_pdb_filename);
         const p = fs.Dir.updateFile(cwd, full_src_path, cwd, full_pdb_path, .{}) catch |err| {
             return step.fail("unable to update file from '{s}' to '{s}': {s}", .{
@@ -115,7 +117,7 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {
         all_cached = all_cached and p == .fresh;
     }
     if (self.h_dir) |h_dir| {
-        const full_src_path = self.artifact.getEmittedH().getPath(src_builder);
+        const full_src_path = self.artifact.generated_h.?.path.?;
         const full_h_path = dest_builder.getInstallPath(h_dir, self.artifact.out_h_filename);
         const p = fs.Dir.updateFile(cwd, full_src_path, cwd, full_h_path, .{}) catch |err| {
             return step.fail("unable to update file from '{s}' to '{s}': {s}", .{
lib/std/Build/Step/Run.zig
@@ -164,6 +164,10 @@ pub fn enableTestRunnerMode(self: *Run) void {
 }
 
 pub fn addArtifactArg(self: *Run, artifact: *Step.Compile) void {
+    // enforce creation of the binary file by invoking getEmittedBin
+    const bin_file = artifact.getEmittedBin();
+    bin_file.addStepDependencies(&self.step);
+
     self.argv.append(Arg{ .artifact = artifact }) catch @panic("OOM");
     self.step.dependOn(&artifact.step);
 }
@@ -456,8 +460,7 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {
                     // On Windows we don't have rpaths so we have to add .dll search paths to PATH
                     self.addPathForDynLibs(artifact);
                 }
-                const file_path = artifact.installed_path orelse
-                    artifact.getEmittedBin().getPath(b);
+                const file_path = artifact.installed_path orelse artifact.generated_bin.?.path.?; // the path is guaranteed to be set
 
                 try argv_list.append(file_path);
 
lib/std/Build/Step.zig
@@ -423,7 +423,10 @@ pub fn evalZigProcess(
         });
     }
 
-    if (s.cast(Compile)) |compile| if (compile.emit_bin == .no_emit) return result;
+    if (s.cast(Compile)) |compile| {
+        if (compile.generated_bin == null) // TODO(xq): How to handle this properly?!
+            return result;
+    }
 
     return result orelse return s.fail(
         "the following command failed to communicate the compilation result:\n{s}",
lib/std/Build/util.zig
@@ -0,0 +1,53 @@
+const std = @import("std");
+const fs = std.fs;
+
+const Build = std.Build;
+const Step = std.Build.Step;
+
+/// In this function the stderr mutex has already been locked.
+pub fn dumpBadGetPathHelp(
+    s: *Step,
+    stderr: fs.File,
+    src_builder: *Build,
+    asking_step: ?*Step,
+) anyerror!void {
+    const w = stderr.writer();
+    try w.print(
+        \\getPath() was called on a GeneratedFile that wasn't built yet.
+        \\  source package path: {s}
+        \\  Is there a missing Step dependency on step '{s}'?
+        \\
+    , .{
+        src_builder.build_root.path orelse ".",
+        s.name,
+    });
+
+    const tty_config = std.io.tty.detectConfig(stderr);
+    tty_config.setColor(w, .red) catch {};
+    try stderr.writeAll("    The step was created by this stack trace:\n");
+    tty_config.setColor(w, .reset) catch {};
+
+    const debug_info = std.debug.getSelfDebugInfo() catch |err| {
+        try w.print("Unable to dump stack trace: Unable to open debug info: {s}\n", .{@errorName(err)});
+        return;
+    };
+    const ally = debug_info.allocator;
+    std.debug.writeStackTrace(s.getStackTrace(), w, ally, debug_info, tty_config) catch |err| {
+        try stderr.writer().print("Unable to dump stack trace: {s}\n", .{@errorName(err)});
+        return;
+    };
+    if (asking_step) |as| {
+        tty_config.setColor(w, .red) catch {};
+        try stderr.writeAll("    The step that is missing a dependency on the above step was created by this stack trace:\n");
+        tty_config.setColor(w, .reset) catch {};
+
+        std.debug.writeStackTrace(as.getStackTrace(), w, ally, debug_info, tty_config) catch |err| {
+            try stderr.writer().print("Unable to dump stack trace: {s}\n", .{@errorName(err)});
+            return;
+        };
+    }
+
+    tty_config.setColor(w, .red) catch {};
+    try stderr.writeAll("    Hope that helps. Proceeding to panic.\n");
+    tty_config.setColor(w, .reset) catch {};
+}
lib/std/Build.zig
@@ -19,6 +19,8 @@ const NativeTargetInfo = std.zig.system.NativeTargetInfo;
 const Sha256 = std.crypto.hash.sha2.Sha256;
 const Build = @This();
 
+const build_util = @import("Build/util.zig");
+
 pub const Cache = @import("Build/Cache.zig");
 
 /// deprecated: use `Step.Compile`.
@@ -1679,7 +1681,7 @@ pub const LazyPath = union(enum) {
             .generated => |gen| return gen.path orelse {
                 std.debug.getStderrMutex().lock();
                 const stderr = std.io.getStdErr();
-                dumpBadGetPathHelp(gen.step, stderr, src_builder, asking_step) catch {};
+                build_util.dumpBadGetPathHelp(gen.step, stderr, src_builder, asking_step) catch {};
                 @panic("misconfigured build script");
             },
         }
@@ -1694,54 +1696,6 @@ pub const LazyPath = union(enum) {
     }
 };
 
-/// In this function the stderr mutex has already been locked.
-fn dumpBadGetPathHelp(
-    s: *Step,
-    stderr: fs.File,
-    src_builder: *Build,
-    asking_step: ?*Step,
-) anyerror!void {
-    const w = stderr.writer();
-    try w.print(
-        \\getPath() was called on a GeneratedFile that wasn't built yet.
-        \\  source package path: {s}
-        \\  Is there a missing Step dependency on step '{s}'?
-        \\
-    , .{
-        src_builder.build_root.path orelse ".",
-        s.name,
-    });
-
-    const tty_config = std.io.tty.detectConfig(stderr);
-    tty_config.setColor(w, .red) catch {};
-    try stderr.writeAll("    The step was created by this stack trace:\n");
-    tty_config.setColor(w, .reset) catch {};
-
-    const debug_info = std.debug.getSelfDebugInfo() catch |err| {
-        try w.print("Unable to dump stack trace: Unable to open debug info: {s}\n", .{@errorName(err)});
-        return;
-    };
-    const ally = debug_info.allocator;
-    std.debug.writeStackTrace(s.getStackTrace(), w, ally, debug_info, tty_config) catch |err| {
-        try stderr.writer().print("Unable to dump stack trace: {s}\n", .{@errorName(err)});
-        return;
-    };
-    if (asking_step) |as| {
-        tty_config.setColor(w, .red) catch {};
-        try stderr.writeAll("    The step that is missing a dependency on the above step was created by this stack trace:\n");
-        tty_config.setColor(w, .reset) catch {};
-
-        std.debug.writeStackTrace(as.getStackTrace(), w, ally, debug_info, tty_config) catch |err| {
-            try stderr.writer().print("Unable to dump stack trace: {s}\n", .{@errorName(err)});
-            return;
-        };
-    }
-
-    tty_config.setColor(w, .red) catch {};
-    try stderr.writeAll("    Hope that helps. Proceeding to panic.\n");
-    tty_config.setColor(w, .reset) catch {};
-}
-
 /// Allocates a new string for assigning a value to a named macro.
 /// If the value is omitted, it is set to 1.
 /// `name` and `value` need not live longer than the function call.
test/link/glibc_compat/build.zig
@@ -13,6 +13,7 @@ pub fn build(b: *std.Build) void {
             ) catch unreachable,
         });
         exe.linkLibC();
+        _ = exe.getEmittedBin(); // force emission
         test_step.dependOn(&exe.step);
     }
 }
test/link/macho/needed_library/build.zig
@@ -23,6 +23,7 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize
     });
     dylib.addCSourceFile(.{ .file = .{ .path = "a.c" }, .flags = &.{} });
     dylib.linkLibC();
+    _ = dylib.getEmittedBin(); // enforce emission
 
     // -dead_strip_dylibs
     // -needed-la
test/link/macho/search_strategy/build.zig
@@ -60,6 +60,7 @@ fn createScenario(
     static.override_dest_dir = std.Build.InstallDir{
         .custom = "static",
     };
+    _ = static.getEmittedBin(); // enforce emission
 
     const dylib = b.addSharedLibrary(.{
         .name = name,
@@ -72,6 +73,7 @@ fn createScenario(
     dylib.override_dest_dir = std.Build.InstallDir{
         .custom = "dynamic",
     };
+    _ = dylib.getEmittedBin(); // enforce emission
 
     const exe = b.addExecutable(.{
         .name = name,
test/src/Cases.zig
@@ -551,7 +551,10 @@ pub fn lowerToBuildSteps(
             }),
         };
 
-        artifact.emit_bin = if (case.emit_bin) .default else .no_emit;
+        if (case.emit_bin)
+            _ = artifact.getEmittedBin();
+
+        _ = artifact.getEmittedBin(); // TODO(xq): The test cases break if we set all to -fno-emit-X
 
         if (case.link_libc) artifact.linkLibC();
 
test/standalone/embed_generated_file/build.zig
@@ -22,5 +22,7 @@ pub fn build(b: *std.Build) void {
         .source_file = bootloader.getEmittedBin(),
     });
 
+    _ = exe.getEmittedBin(); // enforce emission
+
     test_step.dependOn(&exe.step);
 }
test/standalone/emit_asm_and_bin/build.zig
@@ -8,8 +8,8 @@ pub fn build(b: *std.Build) void {
         .root_source_file = .{ .path = "main.zig" },
         .optimize = b.standardOptimizeOption(.{}),
     });
-    main.emit_asm = .{ .emit_to = b.pathFromRoot("main.s") };
-    main.emit_bin = .{ .emit_to = b.pathFromRoot("main") };
+    _ = main.getEmittedBin(); //  main.emit_asm = .{ .emit_to = b.pathFromRoot("main.s") };
+    _ = main.getEmittedAsm(); //  main.emit_bin = .{ .emit_to = b.pathFromRoot("main") };
 
     test_step.dependOn(&b.addRunArtifact(main).step);
 }
test/standalone/issue_12588/build.zig
@@ -13,9 +13,8 @@ pub fn build(b: *std.Build) void {
         .optimize = optimize,
         .target = target,
     });
-    obj.emit_llvm_ir = .{ .emit_to = b.pathFromRoot("main.ll") };
-    obj.emit_llvm_bc = .{ .emit_to = b.pathFromRoot("main.bc") };
-    obj.emit_bin = .no_emit;
+    _ = obj.getEmittedLlvmIr();
+    _ = obj.getEmittedLlvmBc();
     b.default_step.dependOn(&obj.step);
 
     test_step.dependOn(&obj.step);
test/standalone/issue_339/build.zig
@@ -14,5 +14,7 @@ pub fn build(b: *std.Build) void {
         .optimize = optimize,
     });
 
+    _ = obj.getEmittedBin(); // enforce emission
+
     test_step.dependOn(&obj.step);
 }
test/standalone/issue_5825/build.zig
@@ -27,5 +27,7 @@ pub fn build(b: *std.Build) void {
     exe.linkSystemLibrary("ntdll");
     exe.addObject(obj);
 
+    _ = exe.getEmittedBin(); // enforce emission
+
     test_step.dependOn(&exe.step);
 }
test/standalone/issue_794/build.zig
@@ -8,6 +8,7 @@ pub fn build(b: *std.Build) void {
         .root_source_file = .{ .path = "main.zig" },
     });
     test_artifact.addIncludePath(.{ .path = "a_directory" });
+    _ = test_artifact.getEmittedBin(); // enforce emission
 
     test_step.dependOn(&test_artifact.step);
 }
test/standalone/main_pkg_path/build.zig
@@ -7,7 +7,7 @@ pub fn build(b: *std.Build) void {
     const test_exe = b.addTest(.{
         .root_source_file = .{ .path = "a/test.zig" },
     });
-    test_exe.setMainPkgPath(.{.path="."});
+    test_exe.setMainPkgPath(.{ .path = "." });
 
     test_step.dependOn(&b.addRunArtifact(test_exe).step);
 }
test/standalone/strip_empty_loop/build.zig
@@ -14,5 +14,8 @@ pub fn build(b: *std.Build) void {
         .target = target,
     });
     main.strip = true;
+
+    _ = main.getEmittedBin(); // enforce emission
+
     test_step.dependOn(&main.step);
 }
test/standalone/use_alias/build.zig
@@ -10,7 +10,7 @@ pub fn build(b: *std.Build) void {
         .root_source_file = .{ .path = "main.zig" },
         .optimize = optimize,
     });
-    main.addIncludePath(.{.path="."});
+    main.addIncludePath(.{ .path = "." });
 
     test_step.dependOn(&b.addRunArtifact(main).step);
 }
test/standalone.zig
@@ -140,16 +140,14 @@ pub const build_cases = [_]BuildCase{
         .build_root = "test/standalone/install_raw_hex",
         .import = @import("standalone/install_raw_hex/build.zig"),
     },
-    // TODO take away EmitOption.emit_to option and make it give a FileSource
-    //.{
-    //    .build_root = "test/standalone/emit_asm_and_bin",
-    //    .import = @import("standalone/emit_asm_and_bin/build.zig"),
-    //},
-    // TODO take away EmitOption.emit_to option and make it give a FileSource
-    //.{
-    //    .build_root = "test/standalone/issue_12588",
-    //    .import = @import("standalone/issue_12588/build.zig"),
-    //},
+    .{
+        .build_root = "test/standalone/emit_asm_and_bin",
+        .import = @import("standalone/emit_asm_and_bin/build.zig"),
+    },
+    .{
+        .build_root = "test/standalone/issue_12588",
+        .import = @import("standalone/issue_12588/build.zig"),
+    },
     .{
         .build_root = "test/standalone/child_process",
         .import = @import("standalone/child_process/build.zig"),
test/tests.zig
@@ -588,6 +588,8 @@ pub fn addStandaloneTests(
                 });
                 if (case.link_libc) exe.linkLibC();
 
+                _ = exe.getEmittedBin(); // Force emission
+
                 step.dependOn(&exe.step);
             }
 
build.zig
@@ -59,7 +59,6 @@ pub fn build(b: *std.Build) !void {
         .target = target,
     });
     autodoc_test.overrideZigLibDir(.{ .path = "lib" });
-    autodoc_test.emit_bin = .no_emit; // https://github.com/ziglang/zig/issues/16351
     const install_std_docs = b.addInstallDirectory(.{
         .source_dir = autodoc_test.getEmittedDocs(),
         .install_dir = .prefix,
@@ -196,10 +195,6 @@ pub fn build(b: *std.Build) !void {
     exe.pie = pie;
     exe.sanitize_thread = sanitize_thread;
     exe.entitlements = entitlements;
-    // TODO -femit-bin/-fno-emit-bin should be inferred by the build system
-    // based on whether or not the exe is run or installed.
-    // https://github.com/ziglang/zig/issues/16351
-    if (no_bin) exe.emit_bin = .no_emit;
 
     exe.build_id = b.option(
         std.Build.Step.Compile.BuildId,