compiler: rework emit paths and cache modes

Previously, various doc comments heavily disagreed with the
implementation on both what lives where on the filesystem at what time,
and how that was represented in code. Notably, the combination of emit
paths outside the cache and `disable_lld_caching` created a kind of
ad-hoc "cache disable" mechanism -- which didn't actually *work* very
well, 'most everything still ended up in this cache. There was also a
long-standing issue where building using the LLVM backend would put a
random object file in your cwd.

This commit reworks how emit paths are specified in
`Compilation.CreateOptions`, how they are represented internally, and
how the cache usage is specified.

There are now 3 options for `Compilation.CacheMode`:
* `.none`: do not use the cache. The paths we have to emit to are
  relative to the compiler cwd (they're either user-specified, or
  defaults inferred from the root name). If we create any temporary
  files (e.g. the ZCU object when using the LLVM backend) they are
  emitted to a directory in `local_cache/tmp/`, which is deleted once
  the update finishes.
* `.whole`: cache the compilation based on all inputs, including file
  contents. All emit paths are computed by the compiler (and will be
  stored as relative to the local cache directory); it is a CLI error to
  specify an explicit emit path. Artifacts (including temporary files)
  are written to a directory under `local_cache/tmp/`, which is later
  renamed to an appropriate `local_cache/o/`. The caller (who is using
  `--listen`; e.g. the build system) learns the name of this directory,
  and can get the artifacts from it.
* `.incremental`: similar to `.whole`, but Zig source file contents, and
  anything else which incremental compilation can handle changes for, is
  not included in the cache manifest. We don't need to do the dance
  where the output directory is initially in `tmp/`, because our digest
  is computed entirely from CLI inputs.

To be clear, the difference between `CacheMode.whole` and
`CacheMode.incremental` is unchanged. `CacheMode.none` is new
(previously it was sort of poorly imitated with `CacheMode.whole`). The
defined behavior for temporary/intermediate files is new.

`.none` is used for direct CLI invocations like `zig build-exe foo.zig`.
The other cache modes are reserved for `--listen`, and the cache mode in
use is currently just based on the presence of the `-fincremental` flag.

There are two cases in which `CacheMode.whole` is used despite there
being no `--listen` flag: `zig test` and `zig run`. Unless an explicit
`-femit-bin=xxx` argument is passed on the CLI, these subcommands will
use `CacheMode.whole`, so that they can put the output somewhere without
polluting the cwd (plus, caching is potentially more useful for direct
usage of these subcommands).

Users of `--listen` (such as the build system) can now use
`std.zig.EmitArtifact.cacheName` to find out what an output will be
named. This avoids having to synchronize logic between the compiler and
all users of `--listen`.
This commit is contained in:
mlugg 2025-06-06 20:16:26 +01:00
parent 808c15dd39
commit b5f73f8a7b
No known key found for this signature in database
GPG Key ID: 3F5B7DCCBF4AF02E
21 changed files with 625 additions and 842 deletions

View File

@ -1834,47 +1834,16 @@ fn make(step: *Step, options: Step.MakeOptions) !void {
lp.path = b.fmt("{}", .{output_dir});
}
// -femit-bin[=path] (default) Output machine code
if (compile.generated_bin) |bin| {
bin.path = output_dir.joinString(b.allocator, compile.out_filename) catch @panic("OOM");
}
const sep = std.fs.path.sep_str;
// output PDB if someone requested it
if (compile.generated_pdb) |pdb| {
pdb.path = b.fmt("{}" ++ sep ++ "{s}.pdb", .{ output_dir, compile.name });
}
// -femit-implib[=path] (default) Produce an import .lib when building a Windows DLL
if (compile.generated_implib) |implib| {
implib.path = b.fmt("{}" ++ sep ++ "{s}.lib", .{ output_dir, compile.name });
}
// -femit-h[=path] Generate a C header file (.h)
if (compile.generated_h) |lp| {
lp.path = b.fmt("{}" ++ sep ++ "{s}.h", .{ output_dir, compile.name });
}
// -femit-docs[=path] Create a docs/ dir with html documentation
if (compile.generated_docs) |generated_docs| {
generated_docs.path = output_dir.joinString(b.allocator, "docs") catch @panic("OOM");
}
// -femit-asm[=path] Output .s (assembly code)
if (compile.generated_asm) |lp| {
lp.path = b.fmt("{}" ++ sep ++ "{s}.s", .{ output_dir, compile.name });
}
// -femit-llvm-ir[=path] Produce a .ll file with optimized LLVM IR (requires LLVM extensions)
if (compile.generated_llvm_ir) |lp| {
lp.path = b.fmt("{}" ++ sep ++ "{s}.ll", .{ output_dir, compile.name });
}
// -femit-llvm-bc[=path] Produce an optimized LLVM module as a .bc file (requires LLVM extensions)
if (compile.generated_llvm_bc) |lp| {
lp.path = b.fmt("{}" ++ sep ++ "{s}.bc", .{ output_dir, compile.name });
}
// zig fmt: off
if (compile.generated_bin) |lp| lp.path = compile.outputPath(output_dir, .bin);
if (compile.generated_pdb) |lp| lp.path = compile.outputPath(output_dir, .pdb);
if (compile.generated_implib) |lp| lp.path = compile.outputPath(output_dir, .implib);
if (compile.generated_h) |lp| lp.path = compile.outputPath(output_dir, .h);
if (compile.generated_docs) |lp| lp.path = compile.outputPath(output_dir, .docs);
if (compile.generated_asm) |lp| lp.path = compile.outputPath(output_dir, .@"asm");
if (compile.generated_llvm_ir) |lp| lp.path = compile.outputPath(output_dir, .llvm_ir);
if (compile.generated_llvm_bc) |lp| lp.path = compile.outputPath(output_dir, .llvm_bc);
// zig fmt: on
}
if (compile.kind == .lib and compile.linkage != null and compile.linkage.? == .dynamic and
@ -1888,6 +1857,21 @@ fn make(step: *Step, options: Step.MakeOptions) !void {
);
}
}
fn outputPath(c: *Compile, out_dir: std.Build.Cache.Path, ea: std.zig.EmitArtifact) []const u8 {
const arena = c.step.owner.graph.arena;
const name = ea.cacheName(arena, .{
.root_name = c.name,
.target = c.root_module.resolved_target.?.result,
.output_mode = switch (c.kind) {
.lib => .Lib,
.obj, .test_obj => .Obj,
.exe, .@"test" => .Exe,
},
.link_mode = c.linkage,
.version = c.version,
}) catch @panic("OOM");
return out_dir.joinString(arena, name) catch @panic("OOM");
}
pub fn rebuildInFuzzMode(c: *Compile, progress_node: std.Progress.Node) !Path {
const gpa = c.step.owner.allocator;

View File

@ -884,6 +884,35 @@ pub const SimpleComptimeReason = enum(u32) {
}
};
/// Every kind of artifact which the compiler can emit.
pub const EmitArtifact = enum {
bin,
@"asm",
implib,
llvm_ir,
llvm_bc,
docs,
pdb,
h,
/// If using `Server` to communicate with the compiler, it will place requested artifacts in
/// paths under the output directory, where those paths are named according to this function.
/// Returned string is allocated with `gpa` and owned by the caller.
pub fn cacheName(ea: EmitArtifact, gpa: Allocator, opts: BinNameOptions) Allocator.Error![]const u8 {
const suffix: []const u8 = switch (ea) {
.bin => return binNameAlloc(gpa, opts),
.@"asm" => ".s",
.implib => ".lib",
.llvm_ir => ".ll",
.llvm_bc => ".bc",
.docs => "-docs",
.pdb => ".pdb",
.h => ".h",
};
return std.fmt.allocPrint(gpa, "{s}{s}", .{ opts.root_name, suffix });
}
};
test {
_ = Ast;
_ = AstRlAnnotate;

File diff suppressed because it is too large Load Diff

View File

@ -2493,7 +2493,7 @@ fn newEmbedFile(
cache: {
const whole = switch (zcu.comp.cache_use) {
.whole => |whole| whole,
.incremental => break :cache,
.incremental, .none => break :cache,
};
const man = whole.cache_manifest orelse break :cache;
const ip_str = opt_ip_str orelse break :cache; // this will be a compile error
@ -3377,7 +3377,7 @@ pub fn populateTestFunctions(
}
// The linker thread is not running, so we actually need to dispatch this task directly.
@import("../link.zig").doZcuTask(zcu.comp, @intFromEnum(pt.tid), .{ .link_nav = nav_index });
@import("../link.zig").linkTestFunctionsNav(pt, nav_index);
}
}

View File

@ -1019,10 +1019,6 @@ fn buildSharedLib(
defer tracy.end();
const basename = try std.fmt.allocPrint(arena, "lib{s}.so.{d}", .{ lib.name, lib.sover });
const emit_bin = Compilation.EmitLoc{
.directory = bin_directory,
.basename = basename,
};
const version: Version = .{ .major = lib.sover, .minor = 0, .patch = 0 };
const ld_basename = path.basename(comp.getTarget().standardDynamicLinkerPath().get().?);
const soname = if (mem.eql(u8, lib.name, "ld")) ld_basename else basename;
@ -1082,8 +1078,7 @@ fn buildSharedLib(
.root_mod = root_mod,
.root_name = lib.name,
.libc_installation = comp.libc_installation,
.emit_bin = emit_bin,
.emit_h = null,
.emit_bin = .yes_cache,
.verbose_cc = comp.verbose_cc,
.verbose_link = comp.verbose_link,
.verbose_air = comp.verbose_air,

View File

@ -1185,10 +1185,6 @@ fn buildSharedLib(
defer tracy.end();
const basename = try std.fmt.allocPrint(arena, "lib{s}.so.{d}", .{ lib.name, lib.sover });
const emit_bin = Compilation.EmitLoc{
.directory = bin_directory,
.basename = basename,
};
const version: Version = .{ .major = lib.sover, .minor = 0, .patch = 0 };
const ld_basename = path.basename(comp.getTarget().standardDynamicLinkerPath().get().?);
const soname = if (mem.eql(u8, lib.name, "ld")) ld_basename else basename;
@ -1248,8 +1244,7 @@ fn buildSharedLib(
.root_mod = root_mod,
.root_name = lib.name,
.libc_installation = comp.libc_installation,
.emit_bin = emit_bin,
.emit_h = null,
.emit_bin = .yes_cache,
.verbose_cc = comp.verbose_cc,
.verbose_link = comp.verbose_link,
.verbose_air = comp.verbose_air,

View File

@ -122,17 +122,6 @@ pub fn buildLibCxx(comp: *Compilation, prog_node: std.Progress.Node) BuildError!
const output_mode = .Lib;
const link_mode = .static;
const target = comp.root_mod.resolved_target.result;
const basename = try std.zig.binNameAlloc(arena, .{
.root_name = root_name,
.target = target,
.output_mode = output_mode,
.link_mode = link_mode,
});
const emit_bin = Compilation.EmitLoc{
.directory = null, // Put it in the cache directory.
.basename = basename,
};
const cxxabi_include_path = try comp.dirs.zig_lib.join(arena, &.{ "libcxxabi", "include" });
const cxx_include_path = try comp.dirs.zig_lib.join(arena, &.{ "libcxx", "include" });
@ -271,8 +260,7 @@ pub fn buildLibCxx(comp: *Compilation, prog_node: std.Progress.Node) BuildError!
.root_name = root_name,
.thread_pool = comp.thread_pool,
.libc_installation = comp.libc_installation,
.emit_bin = emit_bin,
.emit_h = null,
.emit_bin = .yes_cache,
.c_source_files = c_source_files.items,
.verbose_cc = comp.verbose_cc,
.verbose_link = comp.verbose_link,
@ -327,17 +315,6 @@ pub fn buildLibCxxAbi(comp: *Compilation, prog_node: std.Progress.Node) BuildErr
const output_mode = .Lib;
const link_mode = .static;
const target = comp.root_mod.resolved_target.result;
const basename = try std.zig.binNameAlloc(arena, .{
.root_name = root_name,
.target = target,
.output_mode = output_mode,
.link_mode = link_mode,
});
const emit_bin = Compilation.EmitLoc{
.directory = null, // Put it in the cache directory.
.basename = basename,
};
const cxxabi_include_path = try comp.dirs.zig_lib.join(arena, &.{ "libcxxabi", "include" });
const cxx_include_path = try comp.dirs.zig_lib.join(arena, &.{ "libcxx", "include" });
@ -467,8 +444,7 @@ pub fn buildLibCxxAbi(comp: *Compilation, prog_node: std.Progress.Node) BuildErr
.root_name = root_name,
.thread_pool = comp.thread_pool,
.libc_installation = comp.libc_installation,
.emit_bin = emit_bin,
.emit_h = null,
.emit_bin = .yes_cache,
.c_source_files = c_source_files.items,
.verbose_cc = comp.verbose_cc,
.verbose_link = comp.verbose_link,

View File

@ -45,11 +45,6 @@ pub fn buildTsan(comp: *Compilation, prog_node: std.Progress.Node) BuildError!vo
.link_mode = link_mode,
});
const emit_bin = Compilation.EmitLoc{
.directory = null, // Put it in the cache directory.
.basename = basename,
};
const optimize_mode = comp.compilerRtOptMode();
const strip = comp.compilerRtStrip();
const unwind_tables: std.builtin.UnwindTables =
@ -287,8 +282,7 @@ pub fn buildTsan(comp: *Compilation, prog_node: std.Progress.Node) BuildError!vo
.root_mod = root_mod,
.root_name = root_name,
.libc_installation = comp.libc_installation,
.emit_bin = emit_bin,
.emit_h = null,
.emit_bin = .yes_cache,
.c_source_files = c_source_files.items,
.verbose_cc = comp.verbose_cc,
.verbose_link = comp.verbose_link,

View File

@ -31,7 +31,7 @@ pub fn buildStaticLib(comp: *Compilation, prog_node: std.Progress.Node) BuildErr
const unwind_tables: std.builtin.UnwindTables =
if (target.cpu.arch == .x86 and target.os.tag == .windows) .none else .@"async";
const config = Compilation.Config.resolve(.{
.output_mode = .Lib,
.output_mode = output_mode,
.resolved_target = comp.root_mod.resolved_target,
.is_test = false,
.have_zcu = false,
@ -85,17 +85,6 @@ pub fn buildStaticLib(comp: *Compilation, prog_node: std.Progress.Node) BuildErr
};
const root_name = "unwind";
const link_mode = .static;
const basename = try std.zig.binNameAlloc(arena, .{
.root_name = root_name,
.target = target,
.output_mode = output_mode,
.link_mode = link_mode,
});
const emit_bin = Compilation.EmitLoc{
.directory = null, // Put it in the cache directory.
.basename = basename,
};
var c_source_files: [unwind_src_list.len]Compilation.CSourceFile = undefined;
for (unwind_src_list, 0..) |unwind_src, i| {
var cflags = std.ArrayList([]const u8).init(arena);
@ -160,7 +149,7 @@ pub fn buildStaticLib(comp: *Compilation, prog_node: std.Progress.Node) BuildErr
.main_mod = null,
.thread_pool = comp.thread_pool,
.libc_installation = comp.libc_installation,
.emit_bin = emit_bin,
.emit_bin = .yes_cache,
.function_sections = comp.function_sections,
.c_source_files = &c_source_files,
.verbose_cc = comp.verbose_cc,

View File

@ -252,8 +252,7 @@ pub fn buildCrtFile(comp: *Compilation, in_crt_file: CrtFile, prog_node: std.Pro
.thread_pool = comp.thread_pool,
.root_name = "c",
.libc_installation = comp.libc_installation,
.emit_bin = .{ .directory = null, .basename = "libc.so" },
.emit_h = null,
.emit_bin = .yes_cache,
.verbose_cc = comp.verbose_cc,
.verbose_link = comp.verbose_link,
.verbose_air = comp.verbose_air,

View File

@ -684,10 +684,6 @@ fn buildSharedLib(
defer tracy.end();
const basename = try std.fmt.allocPrint(arena, "lib{s}.so.{d}", .{ lib.name, lib.sover });
const emit_bin = Compilation.EmitLoc{
.directory = bin_directory,
.basename = basename,
};
const version: Version = .{ .major = lib.sover, .minor = 0, .patch = 0 };
const ld_basename = path.basename(comp.getTarget().standardDynamicLinkerPath().get().?);
const soname = if (mem.eql(u8, lib.name, "ld")) ld_basename else basename;
@ -746,8 +742,7 @@ fn buildSharedLib(
.root_mod = root_mod,
.root_name = lib.name,
.libc_installation = comp.libc_installation,
.emit_bin = emit_bin,
.emit_h = null,
.emit_bin = .yes_cache,
.verbose_cc = comp.verbose_cc,
.verbose_link = comp.verbose_link,
.verbose_air = comp.verbose_air,

View File

@ -384,9 +384,11 @@ pub const File = struct {
emit: Path,
file: ?fs.File,
/// When linking with LLD, this linker code will output an object file only at
/// this location, and then this path can be placed on the LLD linker line.
zcu_object_sub_path: ?[]const u8 = null,
/// When using the LLVM backend, the emitted object is written to a file with this name. This
/// object file then becomes a normal link input to LLD or a self-hosted linker.
///
/// To convert this to an actual path, see `Compilation.resolveEmitPath` (with `kind == .temp`).
zcu_object_basename: ?[]const u8 = null,
gc_sections: bool,
print_gc_sections: bool,
build_id: std.zig.BuildId,
@ -433,7 +435,6 @@ pub const File = struct {
export_symbol_names: []const []const u8,
global_base: ?u64,
build_id: std.zig.BuildId,
disable_lld_caching: bool,
hash_style: Lld.Elf.HashStyle,
sort_section: ?Lld.Elf.SortSection,
major_subsystem_version: ?u16,
@ -1083,7 +1084,7 @@ pub const File = struct {
// In this case, an object file is created by the LLVM backend, so
// there is no prelink phase. The Zig code is linked as a standard
// object along with the others.
if (base.zcu_object_sub_path != null) return;
if (base.zcu_object_basename != null) return;
switch (base.tag) {
inline .wasm => |tag| {
@ -1496,6 +1497,31 @@ pub fn doZcuTask(comp: *Compilation, tid: usize, task: ZcuTask) void {
},
}
}
/// After the main pipeline is done, but before flush, the compilation may need to link one final
/// `Nav` into the binary: the `builtin.test_functions` value. Since the link thread isn't running
/// by then, we expose this function which can be called directly.
pub fn linkTestFunctionsNav(pt: Zcu.PerThread, nav_index: InternPool.Nav.Index) void {
const zcu = pt.zcu;
const comp = zcu.comp;
const diags = &comp.link_diags;
if (zcu.llvm_object) |llvm_object| {
llvm_object.updateNav(pt, nav_index) catch |err| switch (err) {
error.OutOfMemory => diags.setAllocFailure(),
};
} else if (comp.bin_file) |lf| {
lf.updateNav(pt, nav_index) catch |err| switch (err) {
error.OutOfMemory => diags.setAllocFailure(),
error.CodegenFail => zcu.assertCodegenFailed(nav_index),
error.Overflow, error.RelocationNotByteAligned => {
switch (zcu.codegenFail(nav_index, "unable to codegen: {s}", .{@errorName(err)})) {
error.CodegenFail => return,
error.OutOfMemory => return diags.setAllocFailure(),
}
// Not a retryable failure.
},
};
}
}
/// Provided by the CLI, processed into `LinkInput` instances at the start of
/// the compilation pipeline.

View File

@ -224,21 +224,16 @@ pub fn createEmpty(
else => 0x1000,
};
// If using LLVM to generate the object file for the zig compilation unit,
// we need a place to put the object file so that it can be subsequently
// handled.
const zcu_object_sub_path = if (!use_llvm)
null
else
try allocPrint(arena, "{s}.obj", .{emit.sub_path});
const coff = try arena.create(Coff);
coff.* = .{
.base = .{
.tag = .coff,
.comp = comp,
.emit = emit,
.zcu_object_sub_path = zcu_object_sub_path,
.zcu_object_basename = if (use_llvm)
try std.fmt.allocPrint(arena, "{s}_zcu.obj", .{fs.path.stem(emit.sub_path)})
else
null,
.stack_size = options.stack_size orelse 16777216,
.gc_sections = options.gc_sections orelse (optimize_mode != .Debug),
.print_gc_sections = options.print_gc_sections,

View File

@ -249,14 +249,6 @@ pub fn createEmpty(
const is_dyn_lib = output_mode == .Lib and link_mode == .dynamic;
const default_sym_version: elf.Versym = if (is_dyn_lib or comp.config.rdynamic) .GLOBAL else .LOCAL;
// If using LLVM to generate the object file for the zig compilation unit,
// we need a place to put the object file so that it can be subsequently
// handled.
const zcu_object_sub_path = if (!use_llvm)
null
else
try std.fmt.allocPrint(arena, "{s}.o", .{emit.sub_path});
var rpath_table: std.StringArrayHashMapUnmanaged(void) = .empty;
try rpath_table.entries.resize(arena, options.rpath_list.len);
@memcpy(rpath_table.entries.items(.key), options.rpath_list);
@ -268,7 +260,10 @@ pub fn createEmpty(
.tag = .elf,
.comp = comp,
.emit = emit,
.zcu_object_sub_path = zcu_object_sub_path,
.zcu_object_basename = if (use_llvm)
try std.fmt.allocPrint(arena, "{s}_zcu.o", .{fs.path.stem(emit.sub_path)})
else
null,
.gc_sections = options.gc_sections orelse (optimize_mode != .Debug and output_mode != .Obj),
.print_gc_sections = options.print_gc_sections,
.stack_size = options.stack_size orelse 16777216,
@ -770,17 +765,13 @@ fn flushInner(self: *Elf, arena: Allocator, tid: Zcu.PerThread.Id) !void {
const gpa = comp.gpa;
const diags = &comp.link_diags;
const module_obj_path: ?Path = if (self.base.zcu_object_sub_path) |path| .{
.root_dir = self.base.emit.root_dir,
.sub_path = if (fs.path.dirname(self.base.emit.sub_path)) |dirname|
try fs.path.join(arena, &.{ dirname, path })
else
path,
const zcu_obj_path: ?Path = if (self.base.zcu_object_basename) |raw| p: {
break :p try comp.resolveEmitPathFlush(arena, .temp, raw);
} else null;
if (self.zigObjectPtr()) |zig_object| try zig_object.flush(self, tid);
if (module_obj_path) |path| openParseObjectReportingFailure(self, path);
if (zcu_obj_path) |path| openParseObjectReportingFailure(self, path);
switch (comp.config.output_mode) {
.Obj => return relocatable.flushObject(self, comp),

View File

@ -41,7 +41,7 @@ pub fn createEmpty(
.tag = .goff,
.comp = comp,
.emit = emit,
.zcu_object_sub_path = emit.sub_path,
.zcu_object_basename = emit.sub_path,
.gc_sections = options.gc_sections orelse false,
.print_gc_sections = options.print_gc_sections,
.stack_size = options.stack_size orelse 0,

View File

@ -1,5 +1,4 @@
base: link.File,
disable_caching: bool,
ofmt: union(enum) {
elf: Elf,
coff: Coff,
@ -231,7 +230,7 @@ pub fn createEmpty(
.tag = .lld,
.comp = comp,
.emit = emit,
.zcu_object_sub_path = try allocPrint(arena, "{s}.{s}", .{ emit.sub_path, obj_file_ext }),
.zcu_object_basename = try allocPrint(arena, "{s}_zcu.{s}", .{ fs.path.stem(emit.sub_path), obj_file_ext }),
.gc_sections = gc_sections,
.print_gc_sections = options.print_gc_sections,
.stack_size = stack_size,
@ -239,7 +238,6 @@ pub fn createEmpty(
.file = null,
.build_id = options.build_id,
},
.disable_caching = options.disable_lld_caching,
.ofmt = switch (target.ofmt) {
.coff => .{ .coff = try .init(comp, options) },
.elf => .{ .elf = try .init(comp, options) },
@ -289,14 +287,11 @@ fn linkAsArchive(lld: *Lld, arena: Allocator) !void {
const full_out_path_z = try arena.dupeZ(u8, full_out_path);
const opt_zcu = comp.zcu;
// 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.
const zcu_obj_path: ?[]const u8 = if (opt_zcu != null) blk: {
const dirname = fs.path.dirname(full_out_path_z) orelse ".";
break :blk try fs.path.join(arena, &.{ dirname, base.zcu_object_sub_path.? });
const zcu_obj_path: ?Cache.Path = if (opt_zcu != null) p: {
break :p try comp.resolveEmitPathFlush(arena, .temp, base.zcu_object_basename.?);
} else null;
log.debug("zcu_obj_path={s}", .{if (zcu_obj_path) |s| s else "(null)"});
log.debug("zcu_obj_path={?}", .{zcu_obj_path});
const compiler_rt_path: ?Cache.Path = if (comp.compiler_rt_strat == .obj)
comp.compiler_rt_obj.?.full_object_path
@ -330,7 +325,7 @@ fn linkAsArchive(lld: *Lld, arena: Allocator) !void {
for (comp.win32_resource_table.keys()) |key| {
object_files.appendAssumeCapacity(try arena.dupeZ(u8, key.status.success.res_path));
}
if (zcu_obj_path) |p| object_files.appendAssumeCapacity(try arena.dupeZ(u8, p));
if (zcu_obj_path) |p| object_files.appendAssumeCapacity(try p.toStringZ(arena));
if (compiler_rt_path) |p| object_files.appendAssumeCapacity(try p.toStringZ(arena));
if (ubsan_rt_path) |p| object_files.appendAssumeCapacity(try p.toStringZ(arena));
@ -368,14 +363,8 @@ fn coffLink(lld: *Lld, arena: Allocator) !void {
const directory = base.emit.root_dir; // Just an alias to make it shorter to type.
const full_out_path = try directory.join(arena, &[_][]const u8{base.emit.sub_path});
// 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.
const module_obj_path: ?[]const u8 = if (comp.zcu != null) p: {
if (fs.path.dirname(full_out_path)) |dirname| {
break :p try fs.path.join(arena, &.{ dirname, base.zcu_object_sub_path.? });
} else {
break :p base.zcu_object_sub_path.?;
}
const zcu_obj_path: ?Cache.Path = if (comp.zcu != null) p: {
break :p try comp.resolveEmitPathFlush(arena, .temp, base.zcu_object_basename.?);
} else null;
const is_lib = comp.config.output_mode == .Lib;
@ -402,8 +391,8 @@ fn coffLink(lld: *Lld, arena: Allocator) !void {
if (comp.c_object_table.count() != 0)
break :blk comp.c_object_table.keys()[0].status.success.object_path;
if (module_obj_path) |p|
break :blk Cache.Path.initCwd(p);
if (zcu_obj_path) |p|
break :blk p;
// TODO I think this is unreachable. Audit this situation when solving the above TODO
// regarding eliding redundant object -> object transformations.
@ -513,9 +502,9 @@ fn coffLink(lld: *Lld, arena: Allocator) !void {
try argv.append(try allocPrint(arena, "-OUT:{s}", .{full_out_path}));
if (comp.implib_emit) |emit| {
const implib_out_path = try emit.root_dir.join(arena, &[_][]const u8{emit.sub_path});
try argv.append(try allocPrint(arena, "-IMPLIB:{s}", .{implib_out_path}));
if (comp.emit_implib) |raw_emit_path| {
const path = try comp.resolveEmitPathFlush(arena, .temp, raw_emit_path);
try argv.append(try allocPrint(arena, "-IMPLIB:{}", .{path}));
}
if (comp.config.link_libc) {
@ -556,8 +545,8 @@ fn coffLink(lld: *Lld, arena: Allocator) !void {
try argv.append(key.status.success.res_path);
}
if (module_obj_path) |p| {
try argv.append(p);
if (zcu_obj_path) |p| {
try argv.append(try p.toString(arena));
}
if (coff.module_definition_file) |def| {
@ -808,14 +797,8 @@ fn elfLink(lld: *Lld, arena: Allocator) !void {
const directory = base.emit.root_dir; // Just an alias to make it shorter to type.
const full_out_path = try directory.join(arena, &[_][]const u8{base.emit.sub_path});
// 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.
const module_obj_path: ?[]const u8 = if (comp.zcu != null) p: {
if (fs.path.dirname(full_out_path)) |dirname| {
break :p try fs.path.join(arena, &.{ dirname, base.zcu_object_sub_path.? });
} else {
break :p base.zcu_object_sub_path.?;
}
const zcu_obj_path: ?Cache.Path = if (comp.zcu != null) p: {
break :p try comp.resolveEmitPathFlush(arena, .temp, base.zcu_object_basename.?);
} else null;
const output_mode = comp.config.output_mode;
@ -862,8 +845,8 @@ fn elfLink(lld: *Lld, arena: Allocator) !void {
if (comp.c_object_table.count() != 0)
break :blk comp.c_object_table.keys()[0].status.success.object_path;
if (module_obj_path) |p|
break :blk Cache.Path.initCwd(p);
if (zcu_obj_path) |p|
break :blk p;
// TODO I think this is unreachable. Audit this situation when solving the above TODO
// regarding eliding redundant object -> object transformations.
@ -1151,8 +1134,8 @@ fn elfLink(lld: *Lld, arena: Allocator) !void {
try argv.append(try key.status.success.object_path.toString(arena));
}
if (module_obj_path) |p| {
try argv.append(p);
if (zcu_obj_path) |p| {
try argv.append(try p.toString(arena));
}
if (comp.tsan_lib) |lib| {
@ -1387,14 +1370,8 @@ fn wasmLink(lld: *Lld, arena: Allocator) !void {
const directory = base.emit.root_dir; // Just an alias to make it shorter to type.
const full_out_path = try directory.join(arena, &[_][]const u8{base.emit.sub_path});
// 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.
const module_obj_path: ?[]const u8 = if (comp.zcu != null) p: {
if (fs.path.dirname(full_out_path)) |dirname| {
break :p try fs.path.join(arena, &.{ dirname, base.zcu_object_sub_path.? });
} else {
break :p base.zcu_object_sub_path.?;
}
const zcu_obj_path: ?Cache.Path = if (comp.zcu != null) p: {
break :p try comp.resolveEmitPathFlush(arena, .temp, base.zcu_object_basename.?);
} else null;
const is_obj = comp.config.output_mode == .Obj;
@ -1419,8 +1396,8 @@ fn wasmLink(lld: *Lld, arena: Allocator) !void {
if (comp.c_object_table.count() != 0)
break :blk comp.c_object_table.keys()[0].status.success.object_path;
if (module_obj_path) |p|
break :blk Cache.Path.initCwd(p);
if (zcu_obj_path) |p|
break :blk p;
// TODO I think this is unreachable. Audit this situation when solving the above TODO
// regarding eliding redundant object -> object transformations.
@ -1610,8 +1587,8 @@ fn wasmLink(lld: *Lld, arena: Allocator) !void {
for (comp.c_object_table.keys()) |key| {
try argv.append(try key.status.success.object_path.toString(arena));
}
if (module_obj_path) |p| {
try argv.append(p);
if (zcu_obj_path) |p| {
try argv.append(try p.toString(arena));
}
if (compiler_rt_path) |p| {

View File

@ -173,13 +173,6 @@ pub fn createEmpty(
const output_mode = comp.config.output_mode;
const link_mode = comp.config.link_mode;
// If using LLVM to generate the object file for the zig compilation unit,
// we need a place to put the object file so that it can be subsequently
// handled.
const zcu_object_sub_path = if (!use_llvm)
null
else
try std.fmt.allocPrint(arena, "{s}.o", .{emit.sub_path});
const allow_shlib_undefined = options.allow_shlib_undefined orelse false;
const self = try arena.create(MachO);
@ -188,7 +181,10 @@ pub fn createEmpty(
.tag = .macho,
.comp = comp,
.emit = emit,
.zcu_object_sub_path = zcu_object_sub_path,
.zcu_object_basename = if (use_llvm)
try std.fmt.allocPrint(arena, "{s}_zcu.o", .{fs.path.stem(emit.sub_path)})
else
null,
.gc_sections = options.gc_sections orelse (optimize_mode != .Debug),
.print_gc_sections = options.print_gc_sections,
.stack_size = options.stack_size orelse 16777216,
@ -351,21 +347,16 @@ pub fn flush(
const sub_prog_node = prog_node.start("MachO Flush", 0);
defer sub_prog_node.end();
const directory = self.base.emit.root_dir;
const module_obj_path: ?Path = if (self.base.zcu_object_sub_path) |path| .{
.root_dir = directory,
.sub_path = if (fs.path.dirname(self.base.emit.sub_path)) |dirname|
try fs.path.join(arena, &.{ dirname, path })
else
path,
const zcu_obj_path: ?Path = if (self.base.zcu_object_basename) |raw| p: {
break :p try comp.resolveEmitPathFlush(arena, .temp, raw);
} else null;
// --verbose-link
if (comp.verbose_link) try self.dumpArgv(comp);
if (self.getZigObject()) |zo| try zo.flush(self, tid);
if (self.base.isStaticLib()) return relocatable.flushStaticLib(self, comp, module_obj_path);
if (self.base.isObject()) return relocatable.flushObject(self, comp, module_obj_path);
if (self.base.isStaticLib()) return relocatable.flushStaticLib(self, comp, zcu_obj_path);
if (self.base.isObject()) return relocatable.flushObject(self, comp, zcu_obj_path);
var positionals = std.ArrayList(link.Input).init(gpa);
defer positionals.deinit();
@ -387,7 +378,7 @@ pub fn flush(
positionals.appendAssumeCapacity(try link.openObjectInput(diags, key.status.success.object_path));
}
if (module_obj_path) |path| try positionals.append(try link.openObjectInput(diags, path));
if (zcu_obj_path) |path| try positionals.append(try link.openObjectInput(diags, path));
if (comp.config.any_sanitize_thread) {
try positionals.append(try link.openObjectInput(diags, comp.tsan_lib.?.full_object_path));
@ -636,12 +627,9 @@ fn dumpArgv(self: *MachO, comp: *Compilation) !void {
const directory = self.base.emit.root_dir;
const full_out_path = try directory.join(arena, &[_][]const u8{self.base.emit.sub_path});
const module_obj_path: ?[]const u8 = if (self.base.zcu_object_sub_path) |path| blk: {
if (fs.path.dirname(full_out_path)) |dirname| {
break :blk try fs.path.join(arena, &.{ dirname, path });
} else {
break :blk path;
}
const zcu_obj_path: ?[]const u8 = if (self.base.zcu_object_basename) |raw| p: {
const p = try comp.resolveEmitPathFlush(arena, .temp, raw);
break :p try p.toString(arena);
} else null;
var argv = std.ArrayList([]const u8).init(arena);
@ -670,7 +658,7 @@ fn dumpArgv(self: *MachO, comp: *Compilation) !void {
try argv.append(try key.status.success.object_path.toString(arena));
}
if (module_obj_path) |p| {
if (zcu_obj_path) |p| {
try argv.append(p);
}
} else {
@ -762,7 +750,7 @@ fn dumpArgv(self: *MachO, comp: *Compilation) !void {
try argv.append(try key.status.success.object_path.toString(arena));
}
if (module_obj_path) |p| {
if (zcu_obj_path) |p| {
try argv.append(p);
}

View File

@ -2951,21 +2951,16 @@ pub fn createEmpty(
const output_mode = comp.config.output_mode;
const wasi_exec_model = comp.config.wasi_exec_model;
// If using LLVM to generate the object file for the zig compilation unit,
// we need a place to put the object file so that it can be subsequently
// handled.
const zcu_object_sub_path = if (!use_llvm)
null
else
try std.fmt.allocPrint(arena, "{s}.o", .{emit.sub_path});
const wasm = try arena.create(Wasm);
wasm.* = .{
.base = .{
.tag = .wasm,
.comp = comp,
.emit = emit,
.zcu_object_sub_path = zcu_object_sub_path,
.zcu_object_basename = if (use_llvm)
try std.fmt.allocPrint(arena, "{s}_zcu.o", .{fs.path.stem(emit.sub_path)})
else
null,
// Garbage collection is so crucial to WebAssembly that we design
// the linker around the assumption that it will be on in the vast
// majority of cases, and therefore express "no garbage collection"
@ -3834,15 +3829,9 @@ pub fn flush(
if (comp.verbose_link) Compilation.dump_argv(wasm.dump_argv_list.items);
if (wasm.base.zcu_object_sub_path) |path| {
const module_obj_path: Path = .{
.root_dir = wasm.base.emit.root_dir,
.sub_path = if (fs.path.dirname(wasm.base.emit.sub_path)) |dirname|
try fs.path.join(arena, &.{ dirname, path })
else
path,
};
openParseObjectReportingFailure(wasm, module_obj_path);
if (wasm.base.zcu_object_basename) |raw| {
const zcu_obj_path: Path = try comp.resolveEmitPathFlush(arena, .temp, raw);
openParseObjectReportingFailure(wasm, zcu_obj_path);
try prelink(wasm, prog_node);
}

View File

@ -41,7 +41,7 @@ pub fn createEmpty(
.tag = .xcoff,
.comp = comp,
.emit = emit,
.zcu_object_sub_path = emit.sub_path,
.zcu_object_basename = emit.sub_path,
.gc_sections = options.gc_sections orelse false,
.print_gc_sections = options.print_gc_sections,
.stack_size = options.stack_size orelse 0,

View File

@ -699,55 +699,21 @@ const Emit = union(enum) {
yes_default_path,
yes: []const u8,
const Resolved = struct {
data: ?Compilation.EmitLoc,
dir: ?fs.Dir,
fn deinit(self: *Resolved) void {
if (self.dir) |*dir| {
dir.close();
}
}
};
fn resolve(emit: Emit, default_basename: []const u8, output_to_cache: bool) !Resolved {
var resolved: Resolved = .{ .data = null, .dir = null };
errdefer resolved.deinit();
switch (emit) {
.no => {},
.yes_default_path => {
resolved.data = Compilation.EmitLoc{
.directory = if (output_to_cache) null else .{
.path = null,
.handle = fs.cwd(),
},
.basename = default_basename,
};
},
.yes => |full_path| {
const basename = fs.path.basename(full_path);
if (fs.path.dirname(full_path)) |dirname| {
const handle = try fs.cwd().openDir(dirname, .{});
resolved = .{
.dir = handle,
.data = Compilation.EmitLoc{
.basename = basename,
.directory = .{
.path = dirname,
.handle = handle,
},
},
};
} else {
resolved.data = Compilation.EmitLoc{
.basename = basename,
.directory = .{ .path = null, .handle = fs.cwd() },
};
const OutputToCacheReason = enum { listen, @"zig run", @"zig test" };
fn resolve(emit: Emit, default_basename: []const u8, output_to_cache: ?OutputToCacheReason) Compilation.CreateOptions.Emit {
return switch (emit) {
.no => .no,
.yes_default_path => if (output_to_cache != null) .yes_cache else .{ .yes_path = default_basename },
.yes => |path| if (output_to_cache) |reason| {
switch (reason) {
.listen => fatal("--listen incompatible with explicit output path '{s}'", .{path}),
.@"zig run", .@"zig test" => fatal(
"'{s}' with explicit output path '{s}' requires explicit '-femit-bin=path' or '-fno-emit-bin'",
.{ @tagName(reason), path },
),
}
},
}
return resolved;
} else .{ .yes_path = path },
};
}
};
@ -2830,7 +2796,7 @@ fn buildOutputType(
.link => {
create_module.opts.output_mode = if (is_shared_lib) .Lib else .Exe;
if (emit_bin != .no) {
emit_bin = if (out_path) |p| .{ .yes = p } else EmitBin.yes_a_out;
emit_bin = if (out_path) |p| .{ .yes = p } else .yes_a_out;
}
if (emit_llvm) {
fatal("-emit-llvm cannot be used when linking", .{});
@ -3208,7 +3174,17 @@ fn buildOutputType(
var cleanup_emit_bin_dir: ?fs.Dir = null;
defer if (cleanup_emit_bin_dir) |*dir| dir.close();
const output_to_cache = listen != .none;
// For `zig run` and `zig test`, we don't want to put the binary in the cwd by default. So, if
// the binary is requested with no explicit path (as is the default), we emit to the cache.
const output_to_cache: ?Emit.OutputToCacheReason = switch (listen) {
.stdio, .ip4 => .listen,
.none => if (arg_mode == .run and emit_bin == .yes_default_path)
.@"zig run"
else if (arg_mode == .zig_test and emit_bin == .yes_default_path)
.@"zig test"
else
null,
};
const optional_version = if (have_version) version else null;
const root_name = if (provided_name) |n| n else main_mod.fully_qualified_name;
@ -3225,150 +3201,48 @@ fn buildOutputType(
},
};
const a_out_basename = switch (target.ofmt) {
.coff => "a.exe",
else => "a.out",
};
const emit_bin_loc: ?Compilation.EmitLoc = switch (emit_bin) {
.no => null,
.yes_default_path => Compilation.EmitLoc{
.directory = blk: {
switch (arg_mode) {
.run, .zig_test => break :blk null,
.build, .cc, .cpp, .translate_c, .zig_test_obj => {
if (output_to_cache) {
break :blk null;
} else {
break :blk .{ .path = null, .handle = fs.cwd() };
}
},
}
},
.basename = if (clang_preprocessor_mode == .pch)
try std.fmt.allocPrint(arena, "{s}.pch", .{root_name})
else
try std.zig.binNameAlloc(arena, .{
const emit_bin_resolved: Compilation.CreateOptions.Emit = switch (emit_bin) {
.no => .no,
.yes_default_path => emit: {
if (output_to_cache != null) break :emit .yes_cache;
const name = switch (clang_preprocessor_mode) {
.pch => try std.fmt.allocPrint(arena, "{s}.pch", .{root_name}),
else => try std.zig.binNameAlloc(arena, .{
.root_name = root_name,
.target = target,
.output_mode = create_module.resolved_options.output_mode,
.link_mode = create_module.resolved_options.link_mode,
.version = optional_version,
}),
};
break :emit .{ .yes_path = name };
},
.yes => |full_path| b: {
const basename = fs.path.basename(full_path);
if (fs.path.dirname(full_path)) |dirname| {
const handle = fs.cwd().openDir(dirname, .{}) catch |err| {
fatal("unable to open output directory '{s}': {s}", .{ dirname, @errorName(err) });
};
cleanup_emit_bin_dir = handle;
break :b Compilation.EmitLoc{
.basename = basename,
.directory = .{
.path = dirname,
.handle = handle,
},
};
} else {
break :b Compilation.EmitLoc{
.basename = basename,
.directory = .{ .path = null, .handle = fs.cwd() },
};
}
},
.yes_a_out => Compilation.EmitLoc{
.directory = .{ .path = null, .handle = fs.cwd() },
.basename = a_out_basename,
.yes => |path| if (output_to_cache != null) {
assert(output_to_cache == .listen); // there was an explicit bin path
fatal("--listen incompatible with explicit output path '{s}'", .{path});
} else .{ .yes_path = path },
.yes_a_out => emit: {
assert(output_to_cache == null);
break :emit .{ .yes_path = switch (target.ofmt) {
.coff => "a.exe",
else => "a.out",
} };
},
};
const default_h_basename = try std.fmt.allocPrint(arena, "{s}.h", .{root_name});
var emit_h_resolved = emit_h.resolve(default_h_basename, output_to_cache) catch |err| {
switch (emit_h) {
.yes => |p| {
fatal("unable to open directory from argument '-femit-h', '{s}': {s}", .{
p, @errorName(err),
});
},
.yes_default_path => {
fatal("unable to open directory from arguments '--name' or '-fsoname', '{s}': {s}", .{
default_h_basename, @errorName(err),
});
},
.no => unreachable,
}
};
defer emit_h_resolved.deinit();
const emit_h_resolved = emit_h.resolve(default_h_basename, output_to_cache);
const default_asm_basename = try std.fmt.allocPrint(arena, "{s}.s", .{root_name});
var emit_asm_resolved = emit_asm.resolve(default_asm_basename, output_to_cache) catch |err| {
switch (emit_asm) {
.yes => |p| {
fatal("unable to open directory from argument '-femit-asm', '{s}': {s}", .{
p, @errorName(err),
});
},
.yes_default_path => {
fatal("unable to open directory from arguments '--name' or '-fsoname', '{s}': {s}", .{
default_asm_basename, @errorName(err),
});
},
.no => unreachable,
}
};
defer emit_asm_resolved.deinit();
const emit_asm_resolved = emit_asm.resolve(default_asm_basename, output_to_cache);
const default_llvm_ir_basename = try std.fmt.allocPrint(arena, "{s}.ll", .{root_name});
var emit_llvm_ir_resolved = emit_llvm_ir.resolve(default_llvm_ir_basename, output_to_cache) catch |err| {
switch (emit_llvm_ir) {
.yes => |p| {
fatal("unable to open directory from argument '-femit-llvm-ir', '{s}': {s}", .{
p, @errorName(err),
});
},
.yes_default_path => {
fatal("unable to open directory from arguments '--name' or '-fsoname', '{s}': {s}", .{
default_llvm_ir_basename, @errorName(err),
});
},
.no => unreachable,
}
};
defer emit_llvm_ir_resolved.deinit();
const emit_llvm_ir_resolved = emit_llvm_ir.resolve(default_llvm_ir_basename, output_to_cache);
const default_llvm_bc_basename = try std.fmt.allocPrint(arena, "{s}.bc", .{root_name});
var emit_llvm_bc_resolved = emit_llvm_bc.resolve(default_llvm_bc_basename, output_to_cache) catch |err| {
switch (emit_llvm_bc) {
.yes => |p| {
fatal("unable to open directory from argument '-femit-llvm-bc', '{s}': {s}", .{
p, @errorName(err),
});
},
.yes_default_path => {
fatal("unable to open directory from arguments '--name' or '-fsoname', '{s}': {s}", .{
default_llvm_bc_basename, @errorName(err),
});
},
.no => unreachable,
}
};
defer emit_llvm_bc_resolved.deinit();
const emit_llvm_bc_resolved = emit_llvm_bc.resolve(default_llvm_bc_basename, output_to_cache);
var emit_docs_resolved = emit_docs.resolve("docs", output_to_cache) catch |err| {
switch (emit_docs) {
.yes => |p| {
fatal("unable to open directory from argument '-femit-docs', '{s}': {s}", .{
p, @errorName(err),
});
},
.yes_default_path => {
fatal("unable to open directory 'docs': {s}", .{@errorName(err)});
},
.no => unreachable,
}
};
defer emit_docs_resolved.deinit();
const emit_docs_resolved = emit_docs.resolve("docs", output_to_cache);
const is_exe_or_dyn_lib = switch (create_module.resolved_options.output_mode) {
.Obj => false,
@ -3378,7 +3252,7 @@ fn buildOutputType(
// Note that cmake when targeting Windows will try to execute
// zig cc to make an executable and output an implib too.
const implib_eligible = is_exe_or_dyn_lib and
emit_bin_loc != null and target.os.tag == .windows;
emit_bin_resolved != .no and target.os.tag == .windows;
if (!implib_eligible) {
if (!emit_implib_arg_provided) {
emit_implib = .no;
@ -3387,22 +3261,18 @@ fn buildOutputType(
}
}
const default_implib_basename = try std.fmt.allocPrint(arena, "{s}.lib", .{root_name});
var emit_implib_resolved = switch (emit_implib) {
.no => Emit.Resolved{ .data = null, .dir = null },
.yes => |p| emit_implib.resolve(default_implib_basename, output_to_cache) catch |err| {
fatal("unable to open directory from argument '-femit-implib', '{s}': {s}", .{
p, @errorName(err),
const emit_implib_resolved: Compilation.CreateOptions.Emit = switch (emit_implib) {
.no => .no,
.yes => emit_implib.resolve(default_implib_basename, output_to_cache),
.yes_default_path => emit: {
if (output_to_cache != null) break :emit .yes_cache;
const p = try fs.path.join(arena, &.{
fs.path.dirname(emit_bin_resolved.yes_path) orelse ".",
default_implib_basename,
});
},
.yes_default_path => Emit.Resolved{
.data = Compilation.EmitLoc{
.directory = emit_bin_loc.?.directory,
.basename = default_implib_basename,
},
.dir = null,
break :emit .{ .yes_path = p };
},
};
defer emit_implib_resolved.deinit();
var thread_pool: ThreadPool = undefined;
try thread_pool.init(.{
@ -3456,7 +3326,7 @@ fn buildOutputType(
src.src_path = try dirs.local_cache.join(arena, &.{sub_path});
}
if (build_options.have_llvm and emit_asm != .no) {
if (build_options.have_llvm and emit_asm_resolved != .no) {
// LLVM has no way to set this non-globally.
const argv = [_][*:0]const u8{ "zig (LLVM option parsing)", "--x86-asm-syntax=intel" };
@import("codegen/llvm/bindings.zig").ParseCommandLineOptions(argv.len, &argv);
@ -3472,23 +3342,11 @@ fn buildOutputType(
fatal("--debug-incremental requires -fincremental", .{});
}
const disable_lld_caching = !output_to_cache;
const cache_mode: Compilation.CacheMode = b: {
// Once incremental compilation is the default, we'll want some smarter logic here,
// considering things like the backend in use and whether there's a ZCU.
if (output_to_cache == null) break :b .none;
if (incremental) break :b .incremental;
if (disable_lld_caching) break :b .incremental;
if (!create_module.resolved_options.have_zcu) break :b .whole;
// TODO: once we support incremental compilation for the LLVM backend
// via saving the LLVM module into a bitcode file and restoring it,
// along with compiler state, this clause can be removed so that
// incremental cache mode is used for LLVM backend too.
if (create_module.resolved_options.use_llvm) break :b .whole;
// Eventually, this default should be `.incremental`. However, since incremental
// compilation is currently an opt-in feature, it makes a strictly worse default cache mode
// than `.whole`.
// https://github.com/ziglang/zig/issues/21165
break :b .whole;
};
@ -3510,13 +3368,13 @@ fn buildOutputType(
.main_mod = main_mod,
.root_mod = root_mod,
.std_mod = std_mod,
.emit_bin = emit_bin_loc,
.emit_h = emit_h_resolved.data,
.emit_asm = emit_asm_resolved.data,
.emit_llvm_ir = emit_llvm_ir_resolved.data,
.emit_llvm_bc = emit_llvm_bc_resolved.data,
.emit_docs = emit_docs_resolved.data,
.emit_implib = emit_implib_resolved.data,
.emit_bin = emit_bin_resolved,
.emit_h = emit_h_resolved,
.emit_asm = emit_asm_resolved,
.emit_llvm_ir = emit_llvm_ir_resolved,
.emit_llvm_bc = emit_llvm_bc_resolved,
.emit_docs = emit_docs_resolved,
.emit_implib = emit_implib_resolved,
.lib_directories = create_module.lib_directories.items,
.rpath_list = create_module.rpath_list.items,
.symbol_wrap_set = symbol_wrap_set,
@ -3599,7 +3457,6 @@ fn buildOutputType(
.test_filters = test_filters.items,
.test_name_prefix = test_name_prefix,
.test_runner_path = test_runner_path,
.disable_lld_caching = disable_lld_caching,
.cache_mode = cache_mode,
.subsystem = subsystem,
.debug_compile_errors = debug_compile_errors,
@ -3744,13 +3601,8 @@ fn buildOutputType(
}) {
dev.checkAny(&.{ .run_command, .test_command });
if (test_exec_args.items.len == 0 and target.ofmt == .c) default_exec_args: {
if (test_exec_args.items.len == 0 and target.ofmt == .c and emit_bin_resolved != .no) {
// Default to using `zig run` to execute the produced .c code from `zig test`.
const c_code_loc = emit_bin_loc orelse break :default_exec_args;
const c_code_directory = c_code_loc.directory orelse comp.bin_file.?.emit.root_dir;
const c_code_path = try fs.path.join(arena, &[_][]const u8{
c_code_directory.path orelse ".", c_code_loc.basename,
});
try test_exec_args.appendSlice(arena, &.{ self_exe_path, "run" });
if (dirs.zig_lib.path) |p| {
try test_exec_args.appendSlice(arena, &.{ "-I", p });
@ -3775,7 +3627,7 @@ fn buildOutputType(
if (create_module.dynamic_linker) |dl| {
try test_exec_args.appendSlice(arena, &.{ "--dynamic-linker", dl });
}
try test_exec_args.append(arena, c_code_path);
try test_exec_args.append(arena, null); // placeholder for the path of the emitted C source file
}
try runOrTest(
@ -4354,12 +4206,22 @@ fn runOrTest(
runtime_args_start: ?usize,
link_libc: bool,
) !void {
const lf = comp.bin_file orelse return;
// A naive `directory.join` here will indeed get the correct path to the binary,
// however, in the case of cwd, we actually want `./foo` so that the path can be executed.
const exe_path = try fs.path.join(arena, &[_][]const u8{
lf.emit.root_dir.path orelse ".", lf.emit.sub_path,
});
const raw_emit_bin = comp.emit_bin orelse return;
const exe_path = switch (comp.cache_use) {
.none => p: {
if (fs.path.isAbsolute(raw_emit_bin)) break :p raw_emit_bin;
// Use `fs.path.join` to make a file in the cwd is still executed properly.
break :p try fs.path.join(arena, &.{
".",
raw_emit_bin,
});
},
.whole, .incremental => try comp.dirs.local_cache.join(arena, &.{
"o",
&Cache.binToHex(comp.digest.?),
raw_emit_bin,
}),
};
var argv = std.ArrayList([]const u8).init(gpa);
defer argv.deinit();
@ -5087,16 +4949,6 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
};
};
const exe_basename = try std.zig.binNameAlloc(arena, .{
.root_name = "build",
.target = resolved_target.result,
.output_mode = .Exe,
});
const emit_bin: Compilation.EmitLoc = .{
.directory = null, // Use the local zig-cache.
.basename = exe_basename,
};
process.raiseFileDescriptorLimit();
const cwd_path = try introspect.getResolvedCwd(arena);
@ -5357,8 +5209,7 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
.config = config,
.root_mod = root_mod,
.main_mod = build_mod,
.emit_bin = emit_bin,
.emit_h = null,
.emit_bin = .yes_cache,
.self_exe_path = self_exe_path,
.thread_pool = &thread_pool,
.verbose_cc = verbose_cc,
@ -5386,8 +5237,11 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
// Since incremental compilation isn't done yet, we use cache_mode = whole
// above, and thus the output file is already closed.
//try comp.makeBinFileExecutable();
child_argv.items[argv_index_exe] =
try dirs.local_cache.join(arena, &.{comp.cache_use.whole.bin_sub_path.?});
child_argv.items[argv_index_exe] = try dirs.local_cache.join(arena, &.{
"o",
&Cache.binToHex(comp.digest.?),
comp.emit_bin.?,
});
}
if (process.can_spawn) {
@ -5504,16 +5358,6 @@ fn jitCmd(
.is_explicit_dynamic_linker = false,
};
const exe_basename = try std.zig.binNameAlloc(arena, .{
.root_name = options.cmd_name,
.target = resolved_target.result,
.output_mode = .Exe,
});
const emit_bin: Compilation.EmitLoc = .{
.directory = null, // Use the global zig-cache.
.basename = exe_basename,
};
const self_exe_path = fs.selfExePathAlloc(arena) catch |err| {
fatal("unable to find self exe path: {s}", .{@errorName(err)});
};
@ -5605,8 +5449,7 @@ fn jitCmd(
.config = config,
.root_mod = root_mod,
.main_mod = root_mod,
.emit_bin = emit_bin,
.emit_h = null,
.emit_bin = .yes_cache,
.self_exe_path = self_exe_path,
.thread_pool = &thread_pool,
.cache_mode = .whole,
@ -5637,7 +5480,11 @@ fn jitCmd(
};
}
const exe_path = try dirs.global_cache.join(arena, &.{comp.cache_use.whole.bin_sub_path.?});
const exe_path = try dirs.global_cache.join(arena, &.{
"o",
&Cache.binToHex(comp.digest.?),
comp.emit_bin.?,
});
child_argv.appendAssumeCapacity(exe_path);
}

View File

@ -314,7 +314,7 @@ const Eval = struct {
const digest = body[@sizeOf(EbpHdr)..][0..Cache.bin_digest_len];
const result_dir = ".local-cache" ++ std.fs.path.sep_str ++ "o" ++ std.fs.path.sep_str ++ Cache.binToHex(digest.*);
const bin_name = try std.zig.binNameAlloc(arena, .{
const bin_name = try std.zig.EmitArtifact.bin.cacheName(arena, .{
.root_name = "root", // corresponds to the module name "root"
.target = eval.target.resolved,
.output_mode = .Exe,