diff --git a/BRANCH_TODO b/BRANCH_TODO index 4a55c8192f..fd5cafc737 100644 --- a/BRANCH_TODO +++ b/BRANCH_TODO @@ -1,6 +1,3 @@ - * skip LLD caching when bin directory is not in the cache (so we don't put `id.txt` into the cwd) - (maybe make it an explicit option and have main.zig disable it) - * `zig build` * repair @cImport * make sure zig cc works - using it as a preprocessor (-E) @@ -22,13 +19,16 @@ * COFF LLD linking * WASM LLD linking * --main-pkg-path + * --pkg-begin, --pkg-end + * skip LLD caching when bin directory is not in the cache (so we don't put `id.txt` into the cwd) + (maybe make it an explicit option and have main.zig disable it) * audit the CLI options for stage2 * audit the base cache hash * implement proper parsing of LLD stderr/stdout and exposing compile errors * implement proper parsing of clang stderr/stdout and exposing compile errors * On operating systems that support it, do an execve for `zig test` and `zig run` rather than child process. * restore error messages for stage2_add_link_lib - * update zig build to use new CLI + * update std/build.zig to use new CLI * support cross compiling stage2 with `zig build` * implement proper compile errors for failing to build glibc crt files and shared libs diff --git a/lib/std/build.zig b/lib/std/build.zig index 69f44bad32..6f8c44ccd3 100644 --- a/lib/std/build.zig +++ b/lib/std/build.zig @@ -2294,8 +2294,7 @@ pub const LibExeObjStep = struct { if (self.kind == Kind.Test) { try builder.spawnChild(zig_args.span()); } else { - try zig_args.append("--cache"); - try zig_args.append("on"); + try zig_args.append("--enable-cache"); const output_dir_nl = try builder.execFromStep(zig_args.span(), &self.step); const build_output_dir = mem.trimRight(u8, output_dir_nl, "\r\n"); diff --git a/lib/std/process.zig b/lib/std/process.zig index 2813d8cbab..5303ada94b 100644 --- a/lib/std/process.zig +++ b/lib/std/process.zig @@ -19,6 +19,19 @@ pub const exit = os.exit; pub const changeCurDir = os.chdir; pub const changeCurDirC = os.chdirC; +/// Indicate that we are now terminating with a successful exit code. +/// In debug builds, this is a no-op, so that the calling code's +/// cleanup mechanisms are tested and so that external tools that +/// check for resource leaks can be accurate. In release builds, this +/// calls exit(0), and does not return. +pub fn cleanExit() void { + if (builtin.mode == .Debug) { + return; + } else { + exit(0); + } +} + /// The result is a slice of `out_buffer`, from index `0`. pub fn getCwd(out_buffer: []u8) ![]u8 { return os.getcwd(out_buffer); diff --git a/lib/std/special/build_runner.zig b/lib/std/special/build_runner.zig index 3c4916a566..43d6b96536 100644 --- a/lib/std/special/build_runner.zig +++ b/lib/std/special/build_runner.zig @@ -161,16 +161,16 @@ fn usage(builder: *Builder, already_ran_build: bool, out_stream: anytype) !void try fmt.allocPrint(allocator, "{} (default)", .{top_level_step.step.name}) else top_level_step.step.name; - try out_stream.print(" {s:22} {}\n", .{ name, top_level_step.description }); + try out_stream.print(" {s:<27} {}\n", .{ name, top_level_step.description }); } try out_stream.writeAll( \\ \\General Options: - \\ --help Print this help and exit - \\ --verbose Print commands before executing them - \\ --prefix [path] Override default install prefix - \\ --search-prefix [path] Add a path to look for binaries, libraries, headers + \\ --help Print this help and exit + \\ --verbose Print commands before executing them + \\ --prefix [path] Override default install prefix + \\ --search-prefix [path] Add a path to look for binaries, libraries, headers \\ \\Project-Specific Options: \\ @@ -185,7 +185,7 @@ fn usage(builder: *Builder, already_ran_build: bool, out_stream: anytype) !void Builder.typeIdName(option.type_id), }); defer allocator.free(name); - try out_stream.print("{s:32} {}\n", .{ name, option.description }); + try out_stream.print("{s:<29} {}\n", .{ name, option.description }); } } diff --git a/src/Cache.zig b/src/Cache.zig index 24c6ae3ac4..7a0c78a1d9 100644 --- a/src/Cache.zig +++ b/src/Cache.zig @@ -120,6 +120,13 @@ pub const HashHelper = struct { return copy.final(); } + pub fn peekBin(hh: HashHelper) [bin_digest_len]u8 { + var copy = hh; + var bin_digest: [bin_digest_len]u8 = undefined; + copy.hasher.final(&bin_digest); + return bin_digest; + } + /// Returns a hex encoded hash of the inputs, mutating the state of the hasher. pub fn final(hh: *HashHelper) [hex_digest_len]u8 { var bin_digest: [bin_digest_len]u8 = undefined; @@ -338,19 +345,7 @@ pub const CacheHash = struct { if (any_file_changed) { // cache miss // keep the manifest file open - // reset the hash - self.hash.hasher = hasher_init; - self.hash.hasher.update(&bin_digest); - - // Remove files not in the initial hash - for (self.files.items[input_file_count..]) |*file| { - file.deinit(self.cache.gpa); - } - self.files.shrinkRetainingCapacity(input_file_count); - - for (self.files.items) |file| { - self.hash.hasher.update(&file.bin_digest); - } + self.unhit(bin_digest, input_file_count); return false; } @@ -366,6 +361,22 @@ pub const CacheHash = struct { return true; } + pub fn unhit(self: *CacheHash, bin_digest: [bin_digest_len]u8, input_file_count: usize) void { + // Reset the hash. + self.hash.hasher = hasher_init; + self.hash.hasher.update(&bin_digest); + + // Remove files not in the initial hash. + for (self.files.items[input_file_count..]) |*file| { + file.deinit(self.cache.gpa); + } + self.files.shrinkRetainingCapacity(input_file_count); + + for (self.files.items) |file| { + self.hash.hasher.update(&file.bin_digest); + } + } + fn populateFileHash(self: *CacheHash, ch_file: *File) !void { const file = try fs.cwd().openFile(ch_file.path.?, .{}); defer file.close(); diff --git a/src/Compilation.zig b/src/Compilation.zig index 9d2d59b98a..d0f2e36080 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -2200,20 +2200,34 @@ fn updateStage1Module(comp: *Compilation) !void { ch.hash.add(comp.bin_file.options.function_sections); ch.hash.add(comp.is_test); + // Capture the state in case we come back from this branch where the hash doesn't match. + const prev_hash_state = ch.hash.peekBin(); + const input_file_count = ch.files.items.len; + if (try ch.hit()) { const digest = ch.final(); var prev_digest_buf: [digest.len]u8 = undefined; const prev_digest: []u8 = directory.handle.readLink(id_symlink_basename, &prev_digest_buf) catch |err| blk: { + log.debug("stage1 {} new_digest={} readlink error: {}", .{ mod.root_pkg.root_src_path, digest, @errorName(err) }); // Handle this as a cache miss. break :blk prev_digest_buf[0..0]; }; if (mem.eql(u8, prev_digest, &digest)) { + log.debug("stage1 {} digest={} match - skipping invocation", .{ mod.root_pkg.root_src_path, digest }); comp.stage1_lock = ch.toOwnedLock(); return; } + log.debug("stage1 {} prev_digest={} new_digest={}", .{ mod.root_pkg.root_src_path, prev_digest, digest }); + ch.unhit(prev_hash_state, input_file_count); } + // We are about to change the output file to be different, so we invalidate the build hash now. + directory.handle.deleteFile(id_symlink_basename) catch |err| switch (err) { + error.FileNotFound => {}, + else => |e| return e, + }; + const stage2_target = try arena.create(stage1.Stage2Target); stage2_target.* = .{ .arch = @enumToInt(target.cpu.arch) + 1, // skip over ZigLLVM_UnknownArch @@ -2243,16 +2257,7 @@ fn updateStage1Module(comp: *Compilation) !void { comp.is_test, ) orelse return error.OutOfMemory; - const stage1_pkg = try arena.create(stage1.Pkg); - stage1_pkg.* = .{ - .name_ptr = undefined, - .name_len = 0, - .path_ptr = undefined, - .path_len = 0, - .children_ptr = undefined, - .children_len = 0, - .parent = null, - }; + const stage1_pkg = try createStage1Pkg(arena, "root", mod.root_pkg, null); const output_dir = comp.bin_file.options.directory.path orelse "."; const test_filter = comp.test_filter orelse ""[0..0]; const test_name_prefix = comp.test_name_prefix orelse ""[0..0]; @@ -2303,10 +2308,12 @@ fn updateStage1Module(comp: *Compilation) !void { const digest = ch.final(); + log.debug("stage1 {} final digest={}", .{ mod.root_pkg.root_src_path, digest }); + // Update the dangling symlink with the digest. If it fails we can continue; it only // means that the next invocation will have an unnecessary cache miss. directory.handle.symLink(&digest, id_symlink_basename, .{}) catch |err| { - std.log.warn("failed to save linking hash digest symlink: {}", .{@errorName(err)}); + std.log.warn("failed to save stage1 hash digest symlink: {}", .{@errorName(err)}); }; // Again failure here only means an unnecessary cache miss. ch.writeManifest() catch |err| { @@ -2316,3 +2323,34 @@ fn updateStage1Module(comp: *Compilation) !void { // other processes clobbering it. comp.stage1_lock = ch.toOwnedLock(); } + +fn createStage1Pkg( + arena: *Allocator, + name: []const u8, + pkg: *Package, + parent_pkg: ?*stage1.Pkg, +) error{OutOfMemory}!*stage1.Pkg { + const child_pkg = try arena.create(stage1.Pkg); + + const pkg_children = blk: { + var children = std.ArrayList(*stage1.Pkg).init(arena); + var it = pkg.table.iterator(); + while (it.next()) |entry| { + try children.append(try createStage1Pkg(arena, entry.key, entry.value, child_pkg)); + } + break :blk children.items; + }; + + const src_path = try pkg.root_src_directory.join(arena, &[_][]const u8{pkg.root_src_path}); + + child_pkg.* = .{ + .name_ptr = name.ptr, + .name_len = name.len, + .path_ptr = src_path.ptr, + .path_len = src_path.len, + .children_ptr = pkg_children.ptr, + .children_len = pkg_children.len, + .parent = parent_pkg, + }; + return child_pkg; +} diff --git a/src/main.zig b/src/main.zig index e88343e124..79fd681120 100644 --- a/src/main.zig +++ b/src/main.zig @@ -35,6 +35,7 @@ const usage = \\ \\Commands: \\ + \\ build Build project from build.zig \\ build-exe Create executable from source or object files \\ build-lib Create library from source or object files \\ build-obj Create object from source or assembly @@ -42,6 +43,8 @@ const usage = \\ c++ Use Zig as a drop-in C++ compiler \\ env Print lib path, std path, compiler id and version \\ fmt Parse file and render in canonical zig format + \\ init-exe Initialize a `zig build` application in the cwd + \\ init-lib Initialize a `zig build` library in the cwd \\ libc Display native libc paths file or validate one \\ run Create executable and run immediately \\ translate-c Convert C code to Zig code @@ -136,6 +139,8 @@ pub fn mainArgs(gpa: *Allocator, arena: *Allocator, args: []const []const u8) !v mem.eql(u8, cmd, "-cc1") or mem.eql(u8, cmd, "-cc1as")) { return punt_to_clang(arena, args); + } else if (mem.eql(u8, cmd, "build")) { + return cmdBuild(gpa, arena, cmd_args); } else if (mem.eql(u8, cmd, "fmt")) { return cmdFmt(gpa, cmd_args); } else if (mem.eql(u8, cmd, "libc")) { @@ -172,18 +177,18 @@ const usage_build_generic = \\Supported file types: \\ .zig Zig source code \\ .zir Zig Intermediate Representation code - \\ (planned) .o ELF object file - \\ (planned) .o MACH-O (macOS) object file - \\ (planned) .obj COFF (Windows) object file - \\ (planned) .lib COFF (Windows) static library - \\ (planned) .a ELF static library - \\ (planned) .so ELF shared object (dynamic link) - \\ (planned) .dll Windows Dynamic Link Library - \\ (planned) .dylib MACH-O (macOS) dynamic library - \\ (planned) .s Target-specific assembly source code - \\ (planned) .S Assembly with C preprocessor (requires LLVM extensions) - \\ (planned) .c C source code (requires LLVM extensions) - \\ (planned) .cpp C++ source code (requires LLVM extensions) + \\ .o ELF object file + \\ .o MACH-O (macOS) object file + \\ .obj COFF (Windows) object file + \\ .lib COFF (Windows) static library + \\ .a ELF static library + \\ .so ELF shared object (dynamic link) + \\ .dll Windows Dynamic Link Library + \\ .dylib MACH-O (macOS) dynamic library + \\ .s Target-specific assembly source code + \\ .S Assembly with C preprocessor (requires LLVM extensions) + \\ .c C source code (requires LLVM extensions) + \\ .cpp C++ source code (requires LLVM extensions) \\ Other C++ extensions: .C .cc .cxx \\ \\General Options: @@ -195,6 +200,8 @@ const usage_build_generic = \\ --show-builtin Output the source of @import("builtin") then exit \\ --cache-dir [path] Override the local cache directory \\ --global-cache-dir [path] Override the global cache directory + \\ --override-lib-dir [path] Override path to Zig installation lib directory + \\ --enable-cache Output to cache directory; print path to stdout \\ \\Compile Options: \\ -target [name] -- see the targets command @@ -357,6 +364,7 @@ pub fn buildOutputType( var test_name_prefix: ?[]const u8 = null; var override_local_cache_dir: ?[]const u8 = null; var override_global_cache_dir: ?[]const u8 = null; + var override_lib_dir: ?[]const u8 = null; var system_libs = std.ArrayList([]const u8).init(gpa); defer system_libs.deinit(); @@ -412,7 +420,7 @@ pub fn buildOutputType( if (mem.startsWith(u8, arg, "-")) { if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) { try io.getStdOut().writeAll(usage_build_generic); - process.exit(0); + return process.cleanExit(); } else if (mem.eql(u8, arg, "--")) { if (arg_mode == .run) { runtime_args_start = i + 1; @@ -547,6 +555,12 @@ pub fn buildOutputType( if (i + 1 >= args.len) fatal("expected parameter after {}", .{arg}); i += 1; override_global_cache_dir = args[i]; + } else if (mem.eql(u8, arg, "--override-lib-dir")) { + if (i + 1 >= args.len) fatal("expected parameter after {}", .{arg}); + i += 1; + override_lib_dir = args[i]; + } else if (mem.eql(u8, arg, "--enable-cache")) { + enable_cache = true; } else if (mem.eql(u8, arg, "--test-cmd-bin")) { try test_exec_args.append(null); } else if (mem.eql(u8, arg, "--test-evented-io")) { @@ -1102,12 +1116,22 @@ pub fn buildOutputType( var cleanup_emit_bin_dir: ?fs.Dir = null; defer if (cleanup_emit_bin_dir) |*dir| dir.close(); + const have_enable_cache = enable_cache orelse false; + const emit_bin_loc: ?Compilation.EmitLoc = switch (emit_bin) { .no => null, .yes_default_path => Compilation.EmitLoc{ - .directory = switch (arg_mode) { - .run, .zig_test => null, - else => .{ .path = null, .handle = fs.cwd() }, + .directory = blk: { + switch (arg_mode) { + .run, .zig_test => break :blk null, + else => { + if (have_enable_cache) { + break :blk null; + } else { + break :blk .{ .path = null, .handle = fs.cwd() }; + } + }, + } }, .basename = try std.zig.binNameAlloc( arena, @@ -1120,6 +1144,12 @@ pub fn buildOutputType( }, .yes => |full_path| b: { const basename = fs.path.basename(full_path); + if (have_enable_cache) { + break :b Compilation.EmitLoc{ + .basename = basename, + .directory = null, + }; + } if (fs.path.dirname(full_path)) |dirname| { const handle = try fs.cwd().openDir(dirname, .{}); cleanup_emit_bin_dir = handle; @@ -1192,9 +1222,15 @@ pub fn buildOutputType( } else null; const self_exe_path = try fs.selfExePathAlloc(arena); - var zig_lib_directory = introspect.findZigLibDirFromSelfExe(arena, self_exe_path) catch |err| { - fatal("unable to find zig installation directory: {}\n", .{@errorName(err)}); - }; + var zig_lib_directory: Compilation.Directory = if (override_lib_dir) |lib_dir| + .{ + .path = lib_dir, + .handle = try fs.cwd().openDir(lib_dir, .{}), + } + else + introspect.findZigLibDirFromSelfExe(arena, self_exe_path) catch |err| { + fatal("unable to find zig installation directory: {}", .{@errorName(err)}); + }; defer zig_lib_directory.handle.close(); const random_seed = blk: { @@ -1337,7 +1373,20 @@ pub fn buildOutputType( return cmdTranslateC(comp, arena); } - try updateModule(gpa, comp, zir_out_path); + const hook: AfterUpdateHook = blk: { + if (!have_enable_cache) + break :blk .none; + + switch (emit_bin) { + .no => break :blk .none, + .yes_default_path => break :blk .{ + .print = comp.bin_file.options.directory.path orelse ".", + }, + .yes => |full_path| break :blk .{ .update = full_path }, + } + }; + + try updateModule(gpa, comp, zir_out_path, hook); if (build_options.have_llvm and only_pp_or_asm) { // this may include dumping the output to stdout @@ -1389,7 +1438,7 @@ pub fn buildOutputType( else => process.exit(1), } if (!watch) - process.exit(0); + return process.cleanExit(); }, else => {}, } @@ -1413,7 +1462,7 @@ pub fn buildOutputType( if (output_mode == .Exe) { try comp.makeBinFileWritable(); } - try updateModule(gpa, comp, zir_out_path); + try updateModule(gpa, comp, zir_out_path, hook); } else if (mem.eql(u8, actual_line, "exit")) { break; } else if (mem.eql(u8, actual_line, "help")) { @@ -1427,7 +1476,13 @@ pub fn buildOutputType( } } -fn updateModule(gpa: *Allocator, comp: *Compilation, zir_out_path: ?[]const u8) !void { +const AfterUpdateHook = union(enum) { + none, + print: []const u8, + update: []const u8, +}; + +fn updateModule(gpa: *Allocator, comp: *Compilation, zir_out_path: ?[]const u8, hook: AfterUpdateHook) !void { try comp.update(); var errors = try comp.getAllErrorsAlloc(); @@ -1437,6 +1492,15 @@ fn updateModule(gpa: *Allocator, comp: *Compilation, zir_out_path: ?[]const u8) for (errors.list) |full_err_msg| { full_err_msg.renderToStdErr(); } + } else switch (hook) { + .none => {}, + .print => |bin_path| try io.getStdOut().writer().print("{s}\n", .{bin_path}), + .update => |full_path| _ = try comp.bin_file.options.directory.handle.updateFile( + comp.bin_file.options.sub_path, + fs.cwd(), + full_path, + .{}, + ), } if (zir_out_path) |zop| { @@ -1535,7 +1599,7 @@ pub fn cmdLibC(gpa: *Allocator, args: []const []const u8) !void { if (mem.eql(u8, arg, "--help")) { const stdout = io.getStdOut().writer(); try stdout.writeAll(usage_libc); - process.exit(0); + return process.cleanExit(); } else { fatal("unrecognized parameter: '{}'", .{arg}); } @@ -1592,7 +1656,7 @@ pub fn cmdInit( if (mem.startsWith(u8, arg, "-")) { if (mem.eql(u8, arg, "--help")) { try io.getStdOut().writeAll(usage_init); - process.exit(0); + return process.cleanExit(); } else { fatal("unrecognized parameter: '{}'", .{arg}); } @@ -1657,6 +1721,248 @@ pub fn cmdInit( } } +pub const usage_build = + \\Usage: zig build [steps] [options] + \\ + \\ Build a project from build.zig. + \\ + \\Options: + \\ --help Print this help and exit + \\ + \\ +; + +pub fn cmdBuild(gpa: *Allocator, arena: *Allocator, args: []const []const u8) !void { + // We want to release all the locks before executing the child process, so we make a nice + // big block here to ensure the cleanup gets run when we extract out our argv. + const lock_and_argv = lock_and_argv: { + const self_exe_path = try fs.selfExePathAlloc(arena); + + var build_file: ?[]const u8 = null; + var override_lib_dir: ?[]const u8 = null; + var override_global_cache_dir: ?[]const u8 = null; + var override_local_cache_dir: ?[]const u8 = null; + var child_argv = std.ArrayList([]const u8).init(arena); + + const argv_index_exe = child_argv.items.len; + _ = try child_argv.addOne(); + + try child_argv.append(self_exe_path); + + const argv_index_build_file = child_argv.items.len; + _ = try child_argv.addOne(); + + const argv_index_cache_dir = child_argv.items.len; + _ = try child_argv.addOne(); + + { + var i: usize = 0; + while (i < args.len) : (i += 1) { + const arg = args[i]; + if (mem.startsWith(u8, arg, "-")) { + if (mem.eql(u8, arg, "--build-file")) { + if (i + 1 >= args.len) fatal("expected argument after '{}'", .{arg}); + i += 1; + build_file = args[i]; + continue; + } else if (mem.eql(u8, arg, "--override-lib-dir")) { + if (i + 1 >= args.len) fatal("expected argument after '{}'", .{arg}); + i += 1; + override_lib_dir = args[i]; + try child_argv.appendSlice(&[_][]const u8{ arg, args[i] }); + continue; + } else if (mem.eql(u8, arg, "--cache-dir")) { + if (i + 1 >= args.len) fatal("expected argument after '{}'", .{arg}); + i += 1; + override_local_cache_dir = args[i]; + try child_argv.appendSlice(&[_][]const u8{ arg, args[i] }); + continue; + } else if (mem.eql(u8, arg, "--global-cache-dir")) { + if (i + 1 >= args.len) fatal("expected argument after '{}'", .{arg}); + i += 1; + override_global_cache_dir = args[i]; + try child_argv.appendSlice(&[_][]const u8{ arg, args[i] }); + continue; + } + } + try child_argv.append(arg); + } + } + + var zig_lib_directory: Compilation.Directory = if (override_lib_dir) |lib_dir| + .{ + .path = lib_dir, + .handle = try fs.cwd().openDir(lib_dir, .{}), + } + else + introspect.findZigLibDirFromSelfExe(arena, self_exe_path) catch |err| { + fatal("unable to find zig installation directory: {}", .{@errorName(err)}); + }; + defer zig_lib_directory.handle.close(); + + const std_special = "std" ++ fs.path.sep_str ++ "special"; + const special_dir_path = try zig_lib_directory.join(arena, &[_][]const u8{std_special}); + + var root_pkg: Package = .{ + .root_src_directory = .{ + .path = special_dir_path, + .handle = try zig_lib_directory.handle.openDir(std_special, .{}), + }, + .root_src_path = "build_runner.zig", + }; + defer root_pkg.root_src_directory.handle.close(); + + var cleanup_build_dir: ?fs.Dir = null; + defer if (cleanup_build_dir) |*dir| dir.close(); + + const cwd_path = try process.getCwdAlloc(arena); + const build_zig_basename = if (build_file) |bf| fs.path.basename(bf) else "build.zig"; + const build_directory: Compilation.Directory = blk: { + if (build_file) |bf| { + if (fs.path.dirname(bf)) |dirname| { + const dir = try fs.cwd().openDir(dirname, .{}); + cleanup_build_dir = dir; + break :blk .{ .path = dirname, .handle = dir }; + } + + break :blk .{ .path = null, .handle = fs.cwd() }; + } + // Search up parent directories until we find build.zig. + var dirname: []const u8 = cwd_path; + while (true) { + const joined_path = try fs.path.join(arena, &[_][]const u8{ dirname, build_zig_basename }); + if (fs.cwd().access(joined_path, .{})) |_| { + const dir = try fs.cwd().openDir(dirname, .{}); + break :blk .{ .path = dirname, .handle = dir }; + } else |err| switch (err) { + error.FileNotFound => { + dirname = fs.path.dirname(dirname) orelse { + std.log.info("{}", .{ + \\Initialize a 'build.zig' template file with `zig init-lib` or `zig init-exe`, + \\or see `zig --help` for more options. + }); + fatal("No 'build.zig' file found, in the current directory or any parent directories.", .{}); + }; + continue; + }, + else => |e| return e, + } + } + }; + child_argv.items[argv_index_build_file] = build_directory.path orelse cwd_path; + + var build_pkg: Package = .{ + .root_src_directory = build_directory, + .root_src_path = build_zig_basename, + }; + try root_pkg.table.put(arena, "@build", &build_pkg); + + var global_cache_directory: Compilation.Directory = l: { + const p = override_global_cache_dir orelse try introspect.resolveGlobalCacheDir(arena); + break :l .{ + .handle = try fs.cwd().makeOpenPath(p, .{}), + .path = p, + }; + }; + defer global_cache_directory.handle.close(); + + var local_cache_directory: Compilation.Directory = l: { + if (override_local_cache_dir) |local_cache_dir_path| { + break :l .{ + .handle = try fs.cwd().makeOpenPath(local_cache_dir_path, .{}), + .path = local_cache_dir_path, + }; + } + const cache_dir_path = try build_directory.join(arena, &[_][]const u8{"zig-cache"}); + break :l .{ + .handle = try build_directory.handle.makeOpenPath("zig-cache", .{}), + .path = cache_dir_path, + }; + }; + defer local_cache_directory.handle.close(); + + child_argv.items[argv_index_cache_dir] = local_cache_directory.path orelse cwd_path; + + gimmeMoreOfThoseSweetSweetFileDescriptors(); + + const cross_target: std.zig.CrossTarget = .{}; + const target_info = try detectNativeTargetInfo(gpa, cross_target); + + const exe_basename = try std.zig.binNameAlloc(arena, "build", target_info.target, .Exe, null, null); + const emit_bin: Compilation.EmitLoc = .{ + .directory = null, // Use the local zig-cache. + .basename = exe_basename, + }; + const random_seed = blk: { + var random_seed: u64 = undefined; + try std.crypto.randomBytes(mem.asBytes(&random_seed)); + break :blk random_seed; + }; + var default_prng = std.rand.DefaultPrng.init(random_seed); + const comp = Compilation.create(gpa, .{ + .zig_lib_directory = zig_lib_directory, + .local_cache_directory = local_cache_directory, + .global_cache_directory = global_cache_directory, + .root_name = "build", + .target = target_info.target, + .is_native_os = cross_target.isNativeOs(), + .dynamic_linker = target_info.dynamic_linker.get(), + .output_mode = .Exe, + .root_pkg = &root_pkg, + .emit_bin = emit_bin, + .emit_h = null, + .optimize_mode = .Debug, + .self_exe_path = self_exe_path, + .rand = &default_prng.random, + }) catch |err| { + fatal("unable to create compilation: {}", .{@errorName(err)}); + }; + defer comp.destroy(); + + try updateModule(gpa, comp, null, .none); + + child_argv.items[argv_index_exe] = try comp.bin_file.options.directory.join(arena, &[_][]const u8{exe_basename}); + + break :lock_and_argv .{ + .child_argv = child_argv.items, + .lock = comp.bin_file.toOwnedLock(), + }; + }; + const child_argv = lock_and_argv.child_argv; + var lock = lock_and_argv.lock; + defer lock.release(); + + const child = try std.ChildProcess.init(child_argv, gpa); + defer child.deinit(); + + child.stdin_behavior = .Inherit; + child.stdout_behavior = .Inherit; + child.stderr_behavior = .Inherit; + + var cmd = std.ArrayList(u8).init(arena); + + const term = try child.spawnAndWait(); + switch (term) { + .Exited => |code| { + if (code == 0) return process.cleanExit(); + try cmd.writer().print("failed with exit code {}:\n", .{code}); + }, + else => { + try cmd.appendSlice("crashed:\n"); + }, + } + + try cmd.append('\n'); + for (child_argv[0 .. child_argv.len - 1]) |arg| { + try cmd.appendSlice(arg); + try cmd.append(' '); + } + try cmd.appendSlice(child_argv[child_argv.len - 1]); + + if (true) // Working around erroneous stage1 compile error: unreachable code on child.deinit() + fatal("The following build command {}", .{cmd.items}); +} + pub const usage_fmt = \\Usage: zig fmt [file]... \\ @@ -1699,7 +2005,7 @@ pub fn cmdFmt(gpa: *Allocator, args: []const []const u8) !void { if (mem.eql(u8, arg, "--help")) { const stdout = io.getStdOut().outStream(); try stdout.writeAll(usage_fmt); - process.exit(0); + return process.cleanExit(); } else if (mem.eql(u8, arg, "--color")) { if (i + 1 >= args.len) { fatal("expected [auto|on|off] after --color", .{}); diff --git a/src/stage1.zig b/src/stage1.zig index 1ff7b4cf4c..380d3cbd9f 100644 --- a/src/stage1.zig +++ b/src/stage1.zig @@ -31,11 +31,11 @@ pub export fn main(argc: c_int, argv: [*]const [*:0]const u8) c_int { defer arena_instance.deinit(); const arena = &arena_instance.allocator; - const args = arena.alloc([]const u8, @intCast(usize, argc)) catch fatal("out of memory", .{}); + const args = arena.alloc([]const u8, @intCast(usize, argc)) catch fatal("{}", .{"OutOfMemory"}); for (args) |*arg, i| { arg.* = mem.spanZ(argv[i]); } - stage2.mainArgs(gpa, arena, args) catch |err| fatal("{}", .{err}); + stage2.mainArgs(gpa, arena, args) catch |err| fatal("{}", .{@errorName(err)}); return 0; }