diff options
| author | Andrew Kelley <andrew@ziglang.org> | 2020-09-22 22:18:19 -0700 |
|---|---|---|
| committer | Andrew Kelley <andrew@ziglang.org> | 2020-09-22 22:18:19 -0700 |
| commit | c2b1cd7c456e274c7ead96e597d147e2df7d317e (patch) | |
| tree | bf924cc095e8bb77dfee6484d6bdc2fb0f7aa2e3 /src/main.zig | |
| parent | 250664bea41cc5dde66e3054ca0c1b6e0feab9be (diff) | |
| download | zig-c2b1cd7c456e274c7ead96e597d147e2df7d317e.tar.gz zig-c2b1cd7c456e274c7ead96e597d147e2df7d317e.zip | |
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".
Diffstat (limited to 'src/main.zig')
| -rw-r--r-- | src/main.zig | 358 |
1 files changed, 332 insertions, 26 deletions
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] <arch><sub>-<os>-<abi> 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", .{}); |
