Skip to content

Commit c2b1cd7

Browse files
committed
stage2: implement zig build
As part of this: * add std.process.cleanExit. closes #6395 - use it in several places * adjust the alignment of text in `zig build --help` menu * Cache: support the concept of "unhit" so that we properly keep track of the cache when we find out using the secondary hash that the cache "hit" was actually a miss. Use this to fix false negatives of caching of stage1 build artifacts. * fix not deleting the symlink hash for stage1 build artifacts causing false positives. * implement support for Package arguments in stage1 build artifacts * update and add missing usage text * add --override-lib-dir and --enable-cache CLI options - `--enable-cache` takes the place of `--cache on` * CLI supports -femit-bin=foo combined with --enable-cache to do an "update file" operation. --enable-cache without that argument will build the output into a cache directory and then print the path to stdout (matching master branch behavior). * errors surfacing from main() now print "error: Foo" instead of "error: error.Foo".
1 parent 250664b commit c2b1cd7

File tree

8 files changed

+431
-64
lines changed

8 files changed

+431
-64
lines changed

BRANCH_TODO

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,3 @@
1-
* skip LLD caching when bin directory is not in the cache (so we don't put `id.txt` into the cwd)
2-
(maybe make it an explicit option and have main.zig disable it)
3-
* `zig build`
41
* repair @cImport
52
* make sure zig cc works
63
- using it as a preprocessor (-E)
@@ -22,13 +19,16 @@
2219
* COFF LLD linking
2320
* WASM LLD linking
2421
* --main-pkg-path
22+
* --pkg-begin, --pkg-end
23+
* skip LLD caching when bin directory is not in the cache (so we don't put `id.txt` into the cwd)
24+
(maybe make it an explicit option and have main.zig disable it)
2525
* audit the CLI options for stage2
2626
* audit the base cache hash
2727
* implement proper parsing of LLD stderr/stdout and exposing compile errors
2828
* implement proper parsing of clang stderr/stdout and exposing compile errors
2929
* On operating systems that support it, do an execve for `zig test` and `zig run` rather than child process.
3030
* restore error messages for stage2_add_link_lib
31-
* update zig build to use new CLI
31+
* update std/build.zig to use new CLI
3232

3333
* support cross compiling stage2 with `zig build`
3434
* implement proper compile errors for failing to build glibc crt files and shared libs

lib/std/build.zig

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2294,8 +2294,7 @@ pub const LibExeObjStep = struct {
22942294
if (self.kind == Kind.Test) {
22952295
try builder.spawnChild(zig_args.span());
22962296
} else {
2297-
try zig_args.append("--cache");
2298-
try zig_args.append("on");
2297+
try zig_args.append("--enable-cache");
22992298

23002299
const output_dir_nl = try builder.execFromStep(zig_args.span(), &self.step);
23012300
const build_output_dir = mem.trimRight(u8, output_dir_nl, "\r\n");

lib/std/process.zig

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,19 @@ pub const exit = os.exit;
1919
pub const changeCurDir = os.chdir;
2020
pub const changeCurDirC = os.chdirC;
2121

22+
/// Indicate that we are now terminating with a successful exit code.
23+
/// In debug builds, this is a no-op, so that the calling code's
24+
/// cleanup mechanisms are tested and so that external tools that
25+
/// check for resource leaks can be accurate. In release builds, this
26+
/// calls exit(0), and does not return.
27+
pub fn cleanExit() void {
28+
if (builtin.mode == .Debug) {
29+
return;
30+
} else {
31+
exit(0);
32+
}
33+
}
34+
2235
/// The result is a slice of `out_buffer`, from index `0`.
2336
pub fn getCwd(out_buffer: []u8) ![]u8 {
2437
return os.getcwd(out_buffer);

lib/std/special/build_runner.zig

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -161,16 +161,16 @@ fn usage(builder: *Builder, already_ran_build: bool, out_stream: anytype) !void
161161
try fmt.allocPrint(allocator, "{} (default)", .{top_level_step.step.name})
162162
else
163163
top_level_step.step.name;
164-
try out_stream.print(" {s:22} {}\n", .{ name, top_level_step.description });
164+
try out_stream.print(" {s:<27} {}\n", .{ name, top_level_step.description });
165165
}
166166

167167
try out_stream.writeAll(
168168
\\
169169
\\General Options:
170-
\\ --help Print this help and exit
171-
\\ --verbose Print commands before executing them
172-
\\ --prefix [path] Override default install prefix
173-
\\ --search-prefix [path] Add a path to look for binaries, libraries, headers
170+
\\ --help Print this help and exit
171+
\\ --verbose Print commands before executing them
172+
\\ --prefix [path] Override default install prefix
173+
\\ --search-prefix [path] Add a path to look for binaries, libraries, headers
174174
\\
175175
\\Project-Specific Options:
176176
\\
@@ -185,7 +185,7 @@ fn usage(builder: *Builder, already_ran_build: bool, out_stream: anytype) !void
185185
Builder.typeIdName(option.type_id),
186186
});
187187
defer allocator.free(name);
188-
try out_stream.print("{s:32} {}\n", .{ name, option.description });
188+
try out_stream.print("{s:<29} {}\n", .{ name, option.description });
189189
}
190190
}
191191

src/Cache.zig

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,13 @@ pub const HashHelper = struct {
120120
return copy.final();
121121
}
122122

123+
pub fn peekBin(hh: HashHelper) [bin_digest_len]u8 {
124+
var copy = hh;
125+
var bin_digest: [bin_digest_len]u8 = undefined;
126+
copy.hasher.final(&bin_digest);
127+
return bin_digest;
128+
}
129+
123130
/// Returns a hex encoded hash of the inputs, mutating the state of the hasher.
124131
pub fn final(hh: *HashHelper) [hex_digest_len]u8 {
125132
var bin_digest: [bin_digest_len]u8 = undefined;
@@ -338,19 +345,7 @@ pub const CacheHash = struct {
338345
if (any_file_changed) {
339346
// cache miss
340347
// keep the manifest file open
341-
// reset the hash
342-
self.hash.hasher = hasher_init;
343-
self.hash.hasher.update(&bin_digest);
344-
345-
// Remove files not in the initial hash
346-
for (self.files.items[input_file_count..]) |*file| {
347-
file.deinit(self.cache.gpa);
348-
}
349-
self.files.shrinkRetainingCapacity(input_file_count);
350-
351-
for (self.files.items) |file| {
352-
self.hash.hasher.update(&file.bin_digest);
353-
}
348+
self.unhit(bin_digest, input_file_count);
354349
return false;
355350
}
356351

@@ -366,6 +361,22 @@ pub const CacheHash = struct {
366361
return true;
367362
}
368363

364+
pub fn unhit(self: *CacheHash, bin_digest: [bin_digest_len]u8, input_file_count: usize) void {
365+
// Reset the hash.
366+
self.hash.hasher = hasher_init;
367+
self.hash.hasher.update(&bin_digest);
368+
369+
// Remove files not in the initial hash.
370+
for (self.files.items[input_file_count..]) |*file| {
371+
file.deinit(self.cache.gpa);
372+
}
373+
self.files.shrinkRetainingCapacity(input_file_count);
374+
375+
for (self.files.items) |file| {
376+
self.hash.hasher.update(&file.bin_digest);
377+
}
378+
}
379+
369380
fn populateFileHash(self: *CacheHash, ch_file: *File) !void {
370381
const file = try fs.cwd().openFile(ch_file.path.?, .{});
371382
defer file.close();

src/Compilation.zig

Lines changed: 49 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2200,20 +2200,34 @@ fn updateStage1Module(comp: *Compilation) !void {
22002200
ch.hash.add(comp.bin_file.options.function_sections);
22012201
ch.hash.add(comp.is_test);
22022202

2203+
// Capture the state in case we come back from this branch where the hash doesn't match.
2204+
const prev_hash_state = ch.hash.peekBin();
2205+
const input_file_count = ch.files.items.len;
2206+
22032207
if (try ch.hit()) {
22042208
const digest = ch.final();
22052209

22062210
var prev_digest_buf: [digest.len]u8 = undefined;
22072211
const prev_digest: []u8 = directory.handle.readLink(id_symlink_basename, &prev_digest_buf) catch |err| blk: {
2212+
log.debug("stage1 {} new_digest={} readlink error: {}", .{ mod.root_pkg.root_src_path, digest, @errorName(err) });
22082213
// Handle this as a cache miss.
22092214
break :blk prev_digest_buf[0..0];
22102215
};
22112216
if (mem.eql(u8, prev_digest, &digest)) {
2217+
log.debug("stage1 {} digest={} match - skipping invocation", .{ mod.root_pkg.root_src_path, digest });
22122218
comp.stage1_lock = ch.toOwnedLock();
22132219
return;
22142220
}
2221+
log.debug("stage1 {} prev_digest={} new_digest={}", .{ mod.root_pkg.root_src_path, prev_digest, digest });
2222+
ch.unhit(prev_hash_state, input_file_count);
22152223
}
22162224

2225+
// We are about to change the output file to be different, so we invalidate the build hash now.
2226+
directory.handle.deleteFile(id_symlink_basename) catch |err| switch (err) {
2227+
error.FileNotFound => {},
2228+
else => |e| return e,
2229+
};
2230+
22172231
const stage2_target = try arena.create(stage1.Stage2Target);
22182232
stage2_target.* = .{
22192233
.arch = @enumToInt(target.cpu.arch) + 1, // skip over ZigLLVM_UnknownArch
@@ -2243,16 +2257,7 @@ fn updateStage1Module(comp: *Compilation) !void {
22432257
comp.is_test,
22442258
) orelse return error.OutOfMemory;
22452259

2246-
const stage1_pkg = try arena.create(stage1.Pkg);
2247-
stage1_pkg.* = .{
2248-
.name_ptr = undefined,
2249-
.name_len = 0,
2250-
.path_ptr = undefined,
2251-
.path_len = 0,
2252-
.children_ptr = undefined,
2253-
.children_len = 0,
2254-
.parent = null,
2255-
};
2260+
const stage1_pkg = try createStage1Pkg(arena, "root", mod.root_pkg, null);
22562261
const output_dir = comp.bin_file.options.directory.path orelse ".";
22572262
const test_filter = comp.test_filter orelse ""[0..0];
22582263
const test_name_prefix = comp.test_name_prefix orelse ""[0..0];
@@ -2303,10 +2308,12 @@ fn updateStage1Module(comp: *Compilation) !void {
23032308

23042309
const digest = ch.final();
23052310

2311+
log.debug("stage1 {} final digest={}", .{ mod.root_pkg.root_src_path, digest });
2312+
23062313
// Update the dangling symlink with the digest. If it fails we can continue; it only
23072314
// means that the next invocation will have an unnecessary cache miss.
23082315
directory.handle.symLink(&digest, id_symlink_basename, .{}) catch |err| {
2309-
std.log.warn("failed to save linking hash digest symlink: {}", .{@errorName(err)});
2316+
std.log.warn("failed to save stage1 hash digest symlink: {}", .{@errorName(err)});
23102317
};
23112318
// Again failure here only means an unnecessary cache miss.
23122319
ch.writeManifest() catch |err| {
@@ -2316,3 +2323,34 @@ fn updateStage1Module(comp: *Compilation) !void {
23162323
// other processes clobbering it.
23172324
comp.stage1_lock = ch.toOwnedLock();
23182325
}
2326+
2327+
fn createStage1Pkg(
2328+
arena: *Allocator,
2329+
name: []const u8,
2330+
pkg: *Package,
2331+
parent_pkg: ?*stage1.Pkg,
2332+
) error{OutOfMemory}!*stage1.Pkg {
2333+
const child_pkg = try arena.create(stage1.Pkg);
2334+
2335+
const pkg_children = blk: {
2336+
var children = std.ArrayList(*stage1.Pkg).init(arena);
2337+
var it = pkg.table.iterator();
2338+
while (it.next()) |entry| {
2339+
try children.append(try createStage1Pkg(arena, entry.key, entry.value, child_pkg));
2340+
}
2341+
break :blk children.items;
2342+
};
2343+
2344+
const src_path = try pkg.root_src_directory.join(arena, &[_][]const u8{pkg.root_src_path});
2345+
2346+
child_pkg.* = .{
2347+
.name_ptr = name.ptr,
2348+
.name_len = name.len,
2349+
.path_ptr = src_path.ptr,
2350+
.path_len = src_path.len,
2351+
.children_ptr = pkg_children.ptr,
2352+
.children_len = pkg_children.len,
2353+
.parent = parent_pkg,
2354+
};
2355+
return child_pkg;
2356+
}

0 commit comments

Comments
 (0)