Merge pull request #18778 from ziglang/system-package-mode

Implement system package mode and lazy dependencies
This commit is contained in:
Andrew Kelley 2024-02-04 01:44:12 -08:00 committed by GitHub
commit d3fc2648cc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
21 changed files with 1217 additions and 767 deletions

View File

@ -45,7 +45,7 @@ pub fn build(b: *std.Build) !void {
});
const docgen_cmd = b.addRunArtifact(docgen_exe);
docgen_cmd.addArgs(&.{ "--zig", b.zig_exe });
docgen_cmd.addArgs(&.{ "--zig", b.graph.zig_exe });
if (b.zig_lib_dir) |p| {
docgen_cmd.addArg("--zig-lib-dir");
docgen_cmd.addDirectoryArg(p);
@ -884,7 +884,7 @@ fn findConfigH(b: *std.Build, config_h_path_option: ?[]const u8) ?[]const u8 {
}
}
var check_dir = fs.path.dirname(b.zig_exe).?;
var check_dir = fs.path.dirname(b.graph.zig_exe).?;
while (true) {
var dir = fs.cwd().openDir(check_dir, .{}) catch unreachable;
defer dir.close();

View File

@ -53,7 +53,7 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {
const self = @fieldParentPtr(GenerateDef, "step", step);
const arena = b.allocator;
var man = b.cache.obtain();
var man = b.graph.cache.obtain();
defer man.deinit();
// Random bytes to make GenerateDef unique. Refresh this with new

View File

@ -46,11 +46,6 @@ pub fn main() !void {
return error.InvalidArgs;
};
const host: std.Build.ResolvedTarget = .{
.query = .{},
.result = try std.zig.system.resolveTargetQuery(.{}),
};
const build_root_directory: std.Build.Cache.Directory = .{
.path = build_root,
.handle = try std.fs.cwd().openDir(build_root, .{}),
@ -66,27 +61,29 @@ pub fn main() !void {
.handle = try std.fs.cwd().makeOpenPath(global_cache_root, .{}),
};
var cache: std.Build.Cache = .{
.gpa = arena,
.manifest_dir = try local_cache_directory.handle.makeOpenPath("h", .{}),
var graph: std.Build.Graph = .{
.arena = arena,
.cache = .{
.gpa = arena,
.manifest_dir = try local_cache_directory.handle.makeOpenPath("h", .{}),
},
.zig_exe = zig_exe,
.env_map = try process.getEnvMap(arena),
.global_cache_root = global_cache_directory,
};
cache.addPrefix(.{ .path = null, .handle = std.fs.cwd() });
cache.addPrefix(build_root_directory);
cache.addPrefix(local_cache_directory);
cache.addPrefix(global_cache_directory);
cache.hash.addBytes(builtin.zig_version_string);
graph.cache.addPrefix(.{ .path = null, .handle = std.fs.cwd() });
graph.cache.addPrefix(build_root_directory);
graph.cache.addPrefix(local_cache_directory);
graph.cache.addPrefix(global_cache_directory);
graph.cache.hash.addBytes(builtin.zig_version_string);
const builder = try std.Build.create(
arena,
zig_exe,
&graph,
build_root_directory,
local_cache_directory,
global_cache_directory,
host,
&cache,
dependencies.root_deps,
);
defer builder.destroy();
var targets = ArrayList([]const u8).init(arena);
var debug_log_scopes = ArrayList([]const u8).init(arena);
@ -100,64 +97,67 @@ pub fn main() !void {
var color: Color = .auto;
var seed: u32 = 0;
var prominent_compile_errors: bool = false;
const stderr_stream = io.getStdErr().writer();
const stdout_stream = io.getStdOut().writer();
var help_menu: bool = false;
var steps_menu: bool = false;
var output_tmp_nonce: ?[16]u8 = null;
while (nextArg(args, &arg_idx)) |arg| {
if (mem.startsWith(u8, arg, "-D")) {
if (mem.startsWith(u8, arg, "-Z")) {
if (arg.len != 18) fatalWithHint("bad argument: '{s}'", .{arg});
output_tmp_nonce = arg[2..18].*;
} else if (mem.startsWith(u8, arg, "-D")) {
const option_contents = arg[2..];
if (option_contents.len == 0) {
std.debug.print("Expected option name after '-D'\n\n", .{});
usageAndErr(builder, false, stderr_stream);
}
if (option_contents.len == 0)
fatalWithHint("expected option name after '-D'", .{});
if (mem.indexOfScalar(u8, option_contents, '=')) |name_end| {
const option_name = option_contents[0..name_end];
const option_value = option_contents[name_end + 1 ..];
if (try builder.addUserInputOption(option_name, option_value))
usageAndErr(builder, false, stderr_stream);
fatal(" access the help menu with 'zig build -h'", .{});
} else {
if (try builder.addUserInputFlag(option_contents))
usageAndErr(builder, false, stderr_stream);
fatal(" access the help menu with 'zig build -h'", .{});
}
} else if (mem.startsWith(u8, arg, "-")) {
if (mem.eql(u8, arg, "--verbose")) {
builder.verbose = true;
} else if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) {
return usage(builder, false, stdout_stream);
help_menu = true;
} else if (mem.eql(u8, arg, "-p") or mem.eql(u8, arg, "--prefix")) {
install_prefix = nextArg(args, &arg_idx) orelse {
std.debug.print("Expected argument after {s}\n\n", .{arg});
usageAndErr(builder, false, stderr_stream);
};
install_prefix = nextArgOrFatal(args, &arg_idx);
} else if (mem.eql(u8, arg, "-l") or mem.eql(u8, arg, "--list-steps")) {
return steps(builder, false, stdout_stream);
steps_menu = true;
} else if (mem.startsWith(u8, arg, "-fsys=")) {
const name = arg["-fsys=".len..];
graph.system_library_options.put(arena, name, .user_enabled) catch @panic("OOM");
} else if (mem.startsWith(u8, arg, "-fno-sys=")) {
const name = arg["-fno-sys=".len..];
graph.system_library_options.put(arena, name, .user_disabled) catch @panic("OOM");
} else if (mem.eql(u8, arg, "--release")) {
builder.release_mode = .any;
} else if (mem.startsWith(u8, arg, "--release=")) {
const text = arg["--release=".len..];
builder.release_mode = std.meta.stringToEnum(std.Build.ReleaseMode, text) orelse {
fatalWithHint("expected [off|any|fast|safe|small] in '{s}', found '{s}'", .{
arg, text,
});
};
} else if (mem.eql(u8, arg, "--host-target")) {
graph.host_query_options.arch_os_abi = nextArgOrFatal(args, &arg_idx);
} else if (mem.eql(u8, arg, "--host-cpu")) {
graph.host_query_options.cpu_features = nextArgOrFatal(args, &arg_idx);
} else if (mem.eql(u8, arg, "--host-dynamic-linker")) {
graph.host_query_options.dynamic_linker = nextArgOrFatal(args, &arg_idx);
} else if (mem.eql(u8, arg, "--prefix-lib-dir")) {
dir_list.lib_dir = nextArg(args, &arg_idx) orelse {
std.debug.print("Expected argument after {s}\n\n", .{arg});
usageAndErr(builder, false, stderr_stream);
};
dir_list.lib_dir = nextArgOrFatal(args, &arg_idx);
} else if (mem.eql(u8, arg, "--prefix-exe-dir")) {
dir_list.exe_dir = nextArg(args, &arg_idx) orelse {
std.debug.print("Expected argument after {s}\n\n", .{arg});
usageAndErr(builder, false, stderr_stream);
};
dir_list.exe_dir = nextArgOrFatal(args, &arg_idx);
} else if (mem.eql(u8, arg, "--prefix-include-dir")) {
dir_list.include_dir = nextArg(args, &arg_idx) orelse {
std.debug.print("Expected argument after {s}\n\n", .{arg});
usageAndErr(builder, false, stderr_stream);
};
dir_list.include_dir = nextArgOrFatal(args, &arg_idx);
} else if (mem.eql(u8, arg, "--sysroot")) {
const sysroot = nextArg(args, &arg_idx) orelse {
std.debug.print("Expected argument after {s}\n\n", .{arg});
usageAndErr(builder, false, stderr_stream);
};
builder.sysroot = sysroot;
builder.sysroot = nextArgOrFatal(args, &arg_idx);
} else if (mem.eql(u8, arg, "--maxrss")) {
const max_rss_text = nextArg(args, &arg_idx) orelse {
std.debug.print("Expected argument after {s}\n\n", .{arg});
usageAndErr(builder, false, stderr_stream);
};
const max_rss_text = nextArgOrFatal(args, &arg_idx);
max_rss = std.fmt.parseIntSizeSuffix(max_rss_text, 10) catch |err| {
std.debug.print("invalid byte size: '{s}': {s}\n", .{
max_rss_text, @errorName(err),
@ -167,66 +167,50 @@ pub fn main() !void {
} else if (mem.eql(u8, arg, "--skip-oom-steps")) {
skip_oom_steps = true;
} else if (mem.eql(u8, arg, "--search-prefix")) {
const search_prefix = nextArg(args, &arg_idx) orelse {
std.debug.print("Expected argument after {s}\n\n", .{arg});
usageAndErr(builder, false, stderr_stream);
};
const search_prefix = nextArgOrFatal(args, &arg_idx);
builder.addSearchPrefix(search_prefix);
} else if (mem.eql(u8, arg, "--libc")) {
const libc_file = nextArg(args, &arg_idx) orelse {
std.debug.print("Expected argument after {s}\n\n", .{arg});
usageAndErr(builder, false, stderr_stream);
};
builder.libc_file = libc_file;
builder.libc_file = nextArgOrFatal(args, &arg_idx);
} else if (mem.eql(u8, arg, "--color")) {
const next_arg = nextArg(args, &arg_idx) orelse {
std.debug.print("Expected [auto|on|off] after {s}\n\n", .{arg});
usageAndErr(builder, false, stderr_stream);
};
const next_arg = nextArg(args, &arg_idx) orelse
fatalWithHint("expected [auto|on|off] after '{s}'", .{arg});
color = std.meta.stringToEnum(Color, next_arg) orelse {
std.debug.print("Expected [auto|on|off] after {s}, found '{s}'\n\n", .{ arg, next_arg });
usageAndErr(builder, false, stderr_stream);
fatalWithHint("expected [auto|on|off] after '{s}', found '{s}'", .{
arg, next_arg,
});
};
} else if (mem.eql(u8, arg, "--summary")) {
const next_arg = nextArg(args, &arg_idx) orelse {
std.debug.print("Expected [all|failures|none] after {s}\n\n", .{arg});
usageAndErr(builder, false, stderr_stream);
};
const next_arg = nextArg(args, &arg_idx) orelse
fatalWithHint("expected [all|failures|none] after '{s}'", .{arg});
summary = std.meta.stringToEnum(Summary, next_arg) orelse {
std.debug.print("Expected [all|failures|none] after {s}, found '{s}'\n\n", .{ arg, next_arg });
usageAndErr(builder, false, stderr_stream);
fatalWithHint("expected [all|failures|none] after '{s}', found '{s}'", .{
arg, next_arg,
});
};
} else if (mem.eql(u8, arg, "--zig-lib-dir")) {
builder.zig_lib_dir = .{ .cwd_relative = nextArg(args, &arg_idx) orelse {
std.debug.print("Expected argument after {s}\n\n", .{arg});
usageAndErr(builder, false, stderr_stream);
} };
builder.zig_lib_dir = .{ .cwd_relative = nextArgOrFatal(args, &arg_idx) };
} else if (mem.eql(u8, arg, "--seed")) {
const next_arg = nextArg(args, &arg_idx) orelse {
std.debug.print("Expected u32 after {s}\n\n", .{arg});
usageAndErr(builder, false, stderr_stream);
};
const next_arg = nextArg(args, &arg_idx) orelse
fatalWithHint("expected u32 after '{s}'", .{arg});
seed = std.fmt.parseUnsigned(u32, next_arg, 0) catch |err| {
std.debug.print("unable to parse seed '{s}' as 32-bit integer: {s}\n", .{
fatal("unable to parse seed '{s}' as 32-bit integer: {s}\n", .{
next_arg, @errorName(err),
});
process.exit(1);
};
} else if (mem.eql(u8, arg, "--debug-log")) {
const next_arg = nextArg(args, &arg_idx) orelse {
std.debug.print("Expected argument after {s}\n\n", .{arg});
usageAndErr(builder, false, stderr_stream);
};
const next_arg = nextArgOrFatal(args, &arg_idx);
try debug_log_scopes.append(next_arg);
} else if (mem.eql(u8, arg, "--debug-pkg-config")) {
builder.debug_pkg_config = true;
} else if (mem.eql(u8, arg, "--debug-compile-errors")) {
builder.debug_compile_errors = true;
} else if (mem.eql(u8, arg, "--system")) {
// The usage text shows another argument after this parameter
// but it is handled by the parent process. The build runner
// only sees this flag.
graph.system_package_mode = true;
} else if (mem.eql(u8, arg, "--glibc-runtimes")) {
builder.glibc_runtimes_dir = nextArg(args, &arg_idx) orelse {
std.debug.print("Expected argument after {s}\n\n", .{arg});
usageAndErr(builder, false, stderr_stream);
};
builder.glibc_runtimes_dir = nextArgOrFatal(args, &arg_idx);
} else if (mem.eql(u8, arg, "--verbose-link")) {
builder.verbose_link = true;
} else if (mem.eql(u8, arg, "--verbose-air")) {
@ -292,19 +276,26 @@ pub fn main() !void {
builder.args = argsRest(args, arg_idx);
break;
} else {
std.debug.print("Unrecognized argument: {s}\n\n", .{arg});
usageAndErr(builder, false, stderr_stream);
fatalWithHint("unrecognized argument: '{s}'", .{arg});
}
} else {
try targets.append(arg);
}
}
const host_query = std.Build.parseTargetQuery(graph.host_query_options) catch |err| switch (err) {
error.ParseFailed => process.exit(1),
};
builder.host = .{
.query = .{},
.result = try std.zig.system.resolveTargetQuery(host_query),
};
const stderr = std.io.getStdErr();
const ttyconf = get_tty_conf(color, stderr);
switch (ttyconf) {
.no_color => try builder.env_map.put("NO_COLOR", "1"),
.escape_codes => try builder.env_map.put("YES_COLOR", "1"),
.no_color => try graph.env_map.put("NO_COLOR", "1"),
.escape_codes => try graph.env_map.put("YES_COLOR", "1"),
.windows_api => {},
}
@ -319,8 +310,39 @@ pub fn main() !void {
try builder.runBuild(root);
}
if (builder.validateUserInputDidItFail())
usageAndErr(builder, true, stderr_stream);
if (graph.needed_lazy_dependencies.entries.len != 0) {
var buffer: std.ArrayListUnmanaged(u8) = .{};
for (graph.needed_lazy_dependencies.keys()) |k| {
try buffer.appendSlice(arena, k);
try buffer.append(arena, '\n');
}
const s = std.fs.path.sep_str;
const tmp_sub_path = "tmp" ++ s ++ (output_tmp_nonce orelse fatal("missing -Z arg", .{}));
local_cache_directory.handle.writeFile2(.{
.sub_path = tmp_sub_path,
.data = buffer.items,
.flags = .{ .exclusive = true },
}) catch |err| {
fatal("unable to write configuration results to '{}{s}': {s}", .{
local_cache_directory, tmp_sub_path, @errorName(err),
});
};
process.exit(3); // Indicate configure phase failed with meaningful stdout.
}
if (builder.validateUserInputDidItFail()) {
fatal(" access the help menu with 'zig build -h'", .{});
}
validateSystemLibraryOptions(builder);
const stdout_writer = io.getStdOut().writer();
if (help_menu)
return usage(builder, stdout_writer);
if (steps_menu)
return steps(builder, stdout_writer);
var run: Run = .{
.max_rss = max_rss,
@ -389,7 +411,7 @@ fn runStepNames(
for (0..step_names.len) |i| {
const step_name = step_names[step_names.len - i - 1];
const s = b.top_level_steps.get(step_name) orelse {
std.debug.print("no step named '{s}'. Access the help menu with 'zig build -h'\n", .{step_name});
std.debug.print("no step named '{s}'\n access the help menu with 'zig build -h'\n", .{step_name});
process.exit(1);
};
step_stack.putAssumeCapacity(&s.step, {});
@ -1037,13 +1059,7 @@ fn printErrorMessages(b: *std.Build, failing_step: *Step, run: *const Run) !void
}
}
fn steps(builder: *std.Build, already_ran_build: bool, out_stream: anytype) !void {
// run the build script to collect the options
if (!already_ran_build) {
builder.resolveInstallPrefix(null, .{});
try builder.runBuild(root);
}
fn steps(builder: *std.Build, out_stream: anytype) !void {
const allocator = builder.allocator;
for (builder.top_level_steps.values()) |top_level_step| {
const name = if (&top_level_step.step == builder.default_step)
@ -1054,33 +1070,25 @@ fn steps(builder: *std.Build, already_ran_build: bool, out_stream: anytype) !voi
}
}
fn usage(builder: *std.Build, already_ran_build: bool, out_stream: anytype) !void {
// run the build script to collect the options
if (!already_ran_build) {
builder.resolveInstallPrefix(null, .{});
try builder.runBuild(root);
}
fn usage(b: *std.Build, out_stream: anytype) !void {
try out_stream.print(
\\
\\Usage: {s} build [steps] [options]
\\
\\Steps:
\\
, .{builder.zig_exe});
try steps(builder, true, out_stream);
, .{b.graph.zig_exe});
try steps(b, out_stream);
try out_stream.writeAll(
\\
\\General Options:
\\ -p, --prefix [path] Override default install prefix
\\ --prefix-lib-dir [path] Override default library directory path
\\ --prefix-exe-dir [path] Override default executable directory path
\\ --prefix-include-dir [path] Override default include directory path
\\ -p, --prefix [path] Where to install files (default: zig-out)
\\ --prefix-lib-dir [path] Where to install libraries
\\ --prefix-exe-dir [path] Where to install executables
\\ --prefix-include-dir [path] Where to install C header files
\\
\\ --sysroot [path] Set the system root directory (usually /)
\\ --search-prefix [path] Add a path to look for binaries, libraries, headers
\\ --libc [file] Provide a file which specifies libc paths
\\ --release[=mode] Request release mode, optionally specifying a
\\ preferred optimization mode: fast, safe, small
\\
\\ -fdarling, -fno-darling Integration with system-installed Darling to
\\ execute macOS programs on Linux hosts
@ -1116,16 +1124,15 @@ fn usage(builder: *std.Build, already_ran_build: bool, out_stream: anytype) !voi
\\
);
const allocator = builder.allocator;
if (builder.available_options_list.items.len == 0) {
const arena = b.allocator;
if (b.available_options_list.items.len == 0) {
try out_stream.print(" (none)\n", .{});
} else {
for (builder.available_options_list.items) |option| {
const name = try fmt.allocPrint(allocator, " -D{s}=[{s}]", .{
for (b.available_options_list.items) |option| {
const name = try fmt.allocPrint(arena, " -D{s}=[{s}]", .{
option.name,
@tagName(option.type_id),
});
defer allocator.free(name);
try out_stream.print("{s:<30} {s}\n", .{ name, option.description });
if (option.enum_options) |enum_options| {
const padding = " " ** 33;
@ -1137,6 +1144,37 @@ fn usage(builder: *std.Build, already_ran_build: bool, out_stream: anytype) !voi
}
}
try out_stream.writeAll(
\\
\\System Integration Options:
\\ --search-prefix [path] Add a path to look for binaries, libraries, headers
\\ --sysroot [path] Set the system root directory (usually /)
\\ --libc [file] Provide a file which specifies libc paths
\\
\\ --host-target [triple] Use the provided target as the host
\\ --host-cpu [cpu] Use the provided CPU as the host
\\ --host-dynamic-linker [path] Use the provided dynamic linker as the host
\\
\\ --system [pkgdir] Disable package fetching; enable all integrations
\\ -fsys=[name] Enable a system integration
\\ -fno-sys=[name] Disable a system integration
\\
\\ Available System Integrations: Enabled:
\\
);
if (b.graph.system_library_options.entries.len == 0) {
try out_stream.writeAll(" (none) -\n");
} else {
for (b.graph.system_library_options.keys(), b.graph.system_library_options.values()) |k, v| {
const status = switch (v) {
.declared_enabled => "yes",
.declared_disabled => "no",
.user_enabled, .user_disabled => unreachable, // already emitted error
};
try out_stream.print(" {s:<43} {s}\n", .{ k, status });
}
}
try out_stream.writeAll(
\\
\\Advanced Options:
@ -1161,17 +1199,19 @@ fn usage(builder: *std.Build, already_ran_build: bool, out_stream: anytype) !voi
);
}
fn usageAndErr(builder: *std.Build, already_ran_build: bool, out_stream: anytype) noreturn {
usage(builder, already_ran_build, out_stream) catch {};
process.exit(1);
}
fn nextArg(args: [][:0]const u8, idx: *usize) ?[:0]const u8 {
if (idx.* >= args.len) return null;
defer idx.* += 1;
return args[idx.*];
}
fn nextArgOrFatal(args: [][:0]const u8, idx: *usize) [:0]const u8 {
return nextArg(args, idx) orelse {
std.debug.print("expected argument after '{s}'\n access the help menu with 'zig build -h'\n", .{args[idx.*]});
process.exit(1);
};
}
fn argsRest(args: [][:0]const u8, idx: usize) ?[][:0]const u8 {
if (idx >= args.len) return null;
return args[idx..];
@ -1202,3 +1242,32 @@ fn renderOptions(ttyconf: std.io.tty.Config) std.zig.ErrorBundle.RenderOptions {
.include_reference_trace = ttyconf != .no_color,
};
}
fn fatalWithHint(comptime f: []const u8, args: anytype) noreturn {
std.debug.print(f ++ "\n access the help menu with 'zig build -h'\n", args);
process.exit(1);
}
fn fatal(comptime f: []const u8, args: anytype) noreturn {
std.debug.print(f ++ "\n", args);
process.exit(1);
}
fn validateSystemLibraryOptions(b: *std.Build) void {
var bad = false;
for (b.graph.system_library_options.keys(), b.graph.system_library_options.values()) |k, v| {
switch (v) {
.user_disabled, .user_enabled => {
// The user tried to enable or disable a system library integration, but
// the build script did not recognize that option.
std.debug.print("system library name not recognized by build script: '{s}'\n", .{k});
bad = true;
},
.declared_disabled, .declared_enabled => {},
}
}
if (bad) {
std.debug.print(" access the help menu with 'zig build -h'\n", .{});
process.exit(1);
}
}

View File

@ -22,6 +22,8 @@ pub const Cache = @import("Build/Cache.zig");
pub const Step = @import("Build/Step.zig");
pub const Module = @import("Build/Module.zig");
/// Shared state among all Build instances.
graph: *Graph,
install_tls: TopLevelStep,
uninstall_tls: TopLevelStep,
allocator: Allocator,
@ -38,9 +40,7 @@ verbose_cimport: bool,
verbose_llvm_cpu_features: bool,
reference_trace: ?u32 = null,
invalid_user_input: bool,
zig_exe: [:0]const u8,
default_step: *Step,
env_map: *EnvMap,
top_level_steps: std.StringArrayHashMapUnmanaged(*TopLevelStep),
install_prefix: []const u8,
dest_dir: ?[]const u8,
@ -49,14 +49,12 @@ exe_dir: []const u8,
h_dir: []const u8,
install_path: []const u8,
sysroot: ?[]const u8 = null,
search_prefixes: ArrayList([]const u8),
search_prefixes: std.ArrayListUnmanaged([]const u8),
libc_file: ?[]const u8 = null,
installed_files: ArrayList(InstalledFile),
/// Path to the directory containing build.zig.
build_root: Cache.Directory,
cache_root: Cache.Directory,
global_cache_root: Cache.Directory,
cache: *Cache,
zig_lib_dir: ?LazyPath,
pkg_config_pkg_list: ?(PkgConfigError![]const PkgConfigPkg) = null,
args: ?[][]const u8 = null,
@ -98,8 +96,47 @@ initialized_deps: *InitializedDepMap,
/// A mapping from dependency names to package hashes.
available_deps: AvailableDeps,
release_mode: ReleaseMode,
pub const ReleaseMode = enum {
off,
any,
fast,
safe,
small,
};
/// Shared state among all Build instances.
/// Settings that are here rather than in Build are not configurable per-package.
pub const Graph = struct {
arena: Allocator,
system_library_options: std.StringArrayHashMapUnmanaged(SystemLibraryMode) = .{},
system_package_mode: bool = false,
cache: Cache,
zig_exe: [:0]const u8,
env_map: EnvMap,
global_cache_root: Cache.Directory,
host_query_options: std.Target.Query.ParseOptions = .{},
needed_lazy_dependencies: std.StringArrayHashMapUnmanaged(void) = .{},
};
const AvailableDeps = []const struct { []const u8, []const u8 };
const SystemLibraryMode = enum {
/// User asked for the library to be disabled.
/// The build runner has not confirmed whether the setting is recognized yet.
user_disabled,
/// User asked for the library to be enabled.
/// The build runner has not confirmed whether the setting is recognized yet.
user_enabled,
/// The build runner has confirmed that this setting is recognized.
/// System integration with this library has been resolved to off.
declared_disabled,
/// The build runner has confirmed that this setting is recognized.
/// System integration with this library has been resolved to on.
declared_enabled,
};
const InitializedDepMap = std.HashMap(InitializedDepKey, *Dependency, InitializedDepContext, std.hash_map.default_max_load_percentage);
const InitializedDepKey = struct {
build_root_string: []const u8,
@ -208,28 +245,20 @@ pub const DirList = struct {
};
pub fn create(
allocator: Allocator,
zig_exe: [:0]const u8,
graph: *Graph,
build_root: Cache.Directory,
cache_root: Cache.Directory,
global_cache_root: Cache.Directory,
host: ResolvedTarget,
cache: *Cache,
available_deps: AvailableDeps,
) !*Build {
const env_map = try allocator.create(EnvMap);
env_map.* = try process.getEnvMap(allocator);
const arena = graph.arena;
const initialized_deps = try arena.create(InitializedDepMap);
initialized_deps.* = InitializedDepMap.initContext(arena, .{ .allocator = arena });
const initialized_deps = try allocator.create(InitializedDepMap);
initialized_deps.* = InitializedDepMap.initContext(allocator, .{ .allocator = allocator });
const self = try allocator.create(Build);
const self = try arena.create(Build);
self.* = .{
.zig_exe = zig_exe,
.graph = graph,
.build_root = build_root,
.cache_root = cache_root,
.global_cache_root = global_cache_root,
.cache = cache,
.verbose = false,
.verbose_link = false,
.verbose_cc = false,
@ -239,20 +268,19 @@ pub fn create(
.verbose_cimport = false,
.verbose_llvm_cpu_features = false,
.invalid_user_input = false,
.allocator = allocator,
.user_input_options = UserInputOptionsMap.init(allocator),
.available_options_map = AvailableOptionsMap.init(allocator),
.available_options_list = ArrayList(AvailableOption).init(allocator),
.allocator = arena,
.user_input_options = UserInputOptionsMap.init(arena),
.available_options_map = AvailableOptionsMap.init(arena),
.available_options_list = ArrayList(AvailableOption).init(arena),
.top_level_steps = .{},
.default_step = undefined,
.env_map = env_map,
.search_prefixes = ArrayList([]const u8).init(allocator),
.search_prefixes = .{},
.install_prefix = undefined,
.lib_dir = undefined,
.exe_dir = undefined,
.h_dir = undefined,
.dest_dir = env_map.get("DESTDIR"),
.installed_files = ArrayList(InstalledFile).init(allocator),
.dest_dir = graph.env_map.get("DESTDIR"),
.installed_files = ArrayList(InstalledFile).init(arena),
.install_tls = .{
.step = Step.init(.{
.id = .top_level,
@ -273,14 +301,15 @@ pub fn create(
.zig_lib_dir = null,
.install_path = undefined,
.args = null,
.host = host,
.modules = std.StringArrayHashMap(*Module).init(allocator),
.named_writefiles = std.StringArrayHashMap(*Step.WriteFile).init(allocator),
.host = undefined,
.modules = std.StringArrayHashMap(*Module).init(arena),
.named_writefiles = std.StringArrayHashMap(*Step.WriteFile).init(arena),
.initialized_deps = initialized_deps,
.available_deps = available_deps,
.release_mode = .off,
};
try self.top_level_steps.put(allocator, self.install_tls.step.name, &self.install_tls);
try self.top_level_steps.put(allocator, self.uninstall_tls.step.name, &self.uninstall_tls);
try self.top_level_steps.put(arena, self.install_tls.step.name, &self.install_tls);
try self.top_level_steps.put(arena, self.uninstall_tls.step.name, &self.uninstall_tls);
self.default_step = &self.install_tls.step;
return self;
}
@ -297,10 +326,17 @@ fn createChild(
return child;
}
fn createChildOnly(parent: *Build, dep_name: []const u8, build_root: Cache.Directory, pkg_deps: AvailableDeps, user_input_options: UserInputOptionsMap) !*Build {
fn createChildOnly(
parent: *Build,
dep_name: []const u8,
build_root: Cache.Directory,
pkg_deps: AvailableDeps,
user_input_options: UserInputOptionsMap,
) !*Build {
const allocator = parent.allocator;
const child = try allocator.create(Build);
child.* = .{
.graph = parent.graph,
.allocator = allocator,
.install_tls = .{
.step = Step.init(.{
@ -332,9 +368,7 @@ fn createChildOnly(parent: *Build, dep_name: []const u8, build_root: Cache.Direc
.verbose_llvm_cpu_features = parent.verbose_llvm_cpu_features,
.reference_trace = parent.reference_trace,
.invalid_user_input = false,
.zig_exe = parent.zig_exe,
.default_step = undefined,
.env_map = parent.env_map,
.top_level_steps = .{},
.install_prefix = undefined,
.dest_dir = parent.dest_dir,
@ -348,8 +382,6 @@ fn createChildOnly(parent: *Build, dep_name: []const u8, build_root: Cache.Direc
.installed_files = ArrayList(InstalledFile).init(allocator),
.build_root = build_root,
.cache_root = parent.cache_root,
.global_cache_root = parent.global_cache_root,
.cache = parent.cache,
.zig_lib_dir = parent.zig_lib_dir,
.debug_log_scopes = parent.debug_log_scopes,
.debug_compile_errors = parent.debug_compile_errors,
@ -366,6 +398,7 @@ fn createChildOnly(parent: *Build, dep_name: []const u8, build_root: Cache.Direc
.named_writefiles = std.StringArrayHashMap(*Step.WriteFile).init(allocator),
.initialized_deps = parent.initialized_deps,
.available_deps = pkg_deps,
.release_mode = parent.release_mode,
};
try child.top_level_steps.put(allocator, child.install_tls.step.name, &child.install_tls);
try child.top_level_steps.put(allocator, child.uninstall_tls.step.name, &child.uninstall_tls);
@ -543,7 +576,7 @@ fn hashUserInputOptionsMap(allocator: Allocator, user_input_options: UserInputOp
fn determineAndApplyInstallPrefix(b: *Build) !void {
// Create an installation directory local to this package. This will be used when
// dependant packages require a standard prefix, such as include directories for C headers.
var hash = b.cache.hash;
var hash = b.graph.cache.hash;
// Random bytes to make unique. Refresh this with new random bytes when
// implementation is modified in a non-backwards-compatible way.
hash.add(@as(u32, 0xd8cb0055));
@ -558,12 +591,6 @@ fn determineAndApplyInstallPrefix(b: *Build) !void {
b.resolveInstallPrefix(install_prefix, .{});
}
pub fn destroy(b: *Build) void {
b.env_map.deinit();
b.top_level_steps.deinit(b.allocator);
b.allocator.destroy(b);
}
/// This function is intended to be called by lib/build_runner.zig, not a build.zig file.
pub fn resolveInstallPrefix(self: *Build, install_prefix: ?[]const u8, dir_list: DirList) void {
if (self.dest_dir) |dest_dir| {
@ -1216,20 +1243,33 @@ pub const StandardOptimizeOptionOptions = struct {
preferred_optimize_mode: ?std.builtin.OptimizeMode = null,
};
pub fn standardOptimizeOption(self: *Build, options: StandardOptimizeOptionOptions) std.builtin.OptimizeMode {
pub fn standardOptimizeOption(b: *Build, options: StandardOptimizeOptionOptions) std.builtin.OptimizeMode {
if (options.preferred_optimize_mode) |mode| {
if (self.option(bool, "release", "optimize for end users") orelse false) {
if (b.option(bool, "release", "optimize for end users") orelse (b.release_mode != .off)) {
return mode;
} else {
return .Debug;
}
} else {
return self.option(
std.builtin.OptimizeMode,
"optimize",
"Prioritize performance, safety, or binary size (-O flag)",
) orelse .Debug;
}
if (b.option(
std.builtin.OptimizeMode,
"optimize",
"Prioritize performance, safety, or binary size",
)) |mode| {
return mode;
}
return switch (b.release_mode) {
.off => .Debug,
.any => {
std.debug.print("the project does not declare a preferred optimization mode. choose: --release=fast, --release=safe, or --release=small\n", .{});
process.exit(1);
},
.fast => .ReleaseFast,
.safe => .ReleaseSafe,
.small => .ReleaseSmall,
};
}
pub const StandardTargetOptionsArgs = struct {
@ -1244,6 +1284,54 @@ pub fn standardTargetOptions(b: *Build, args: StandardTargetOptionsArgs) Resolve
return b.resolveTargetQuery(query);
}
pub fn parseTargetQuery(options: std.Target.Query.ParseOptions) error{ParseFailed}!std.Target.Query {
var diags: Target.Query.ParseOptions.Diagnostics = .{};
var opts_copy = options;
opts_copy.diagnostics = &diags;
return std.Target.Query.parse(options) catch |err| switch (err) {
error.UnknownCpuModel => {
std.debug.print("unknown CPU: '{s}'\navailable CPUs for architecture '{s}':\n", .{
diags.cpu_name.?, @tagName(diags.arch.?),
});
for (diags.arch.?.allCpuModels()) |cpu| {
std.debug.print(" {s}\n", .{cpu.name});
}
return error.ParseFailed;
},
error.UnknownCpuFeature => {
std.debug.print(
\\unknown CPU feature: '{s}'
\\available CPU features for architecture '{s}':
\\
, .{
diags.unknown_feature_name.?,
@tagName(diags.arch.?),
});
for (diags.arch.?.allFeaturesList()) |feature| {
std.debug.print(" {s}: {s}\n", .{ feature.name, feature.description });
}
return error.ParseFailed;
},
error.UnknownOperatingSystem => {
std.debug.print(
\\unknown OS: '{s}'
\\available operating systems:
\\
, .{diags.os_name.?});
inline for (std.meta.fields(Target.Os.Tag)) |field| {
std.debug.print(" {s}\n", .{field.name});
}
return error.ParseFailed;
},
else => |e| {
std.debug.print("unable to parse target '{s}': {s}\n", .{
options.arch_os_abi, @errorName(e),
});
return error.ParseFailed;
},
};
}
/// Exposes standard `zig build` options for choosing a target.
pub fn standardTargetOptionsQueryOnly(b: *Build, args: StandardTargetOptionsArgs) Target.Query {
const maybe_triple = b.option(
@ -1251,60 +1339,28 @@ pub fn standardTargetOptionsQueryOnly(b: *Build, args: StandardTargetOptionsArgs
"target",
"The CPU architecture, OS, and ABI to build for",
);
const mcpu = b.option([]const u8, "cpu", "Target CPU features to add or subtract");
const mcpu = b.option(
[]const u8,
"cpu",
"Target CPU features to add or subtract",
);
const dynamic_linker = b.option(
[]const u8,
"dynamic-linker",
"Path to interpreter on the target system",
);
if (maybe_triple == null and mcpu == null) {
if (maybe_triple == null and mcpu == null and dynamic_linker == null)
return args.default_target;
}
const triple = maybe_triple orelse "native";
var diags: Target.Query.ParseOptions.Diagnostics = .{};
const selected_target = Target.Query.parse(.{
const selected_target = parseTargetQuery(.{
.arch_os_abi = triple,
.cpu_features = mcpu,
.diagnostics = &diags,
.dynamic_linker = dynamic_linker,
}) catch |err| switch (err) {
error.UnknownCpuModel => {
log.err("Unknown CPU: '{s}'\nAvailable CPUs for architecture '{s}':", .{
diags.cpu_name.?,
@tagName(diags.arch.?),
});
for (diags.arch.?.allCpuModels()) |cpu| {
log.err(" {s}", .{cpu.name});
}
b.markInvalidUserInput();
return args.default_target;
},
error.UnknownCpuFeature => {
log.err(
\\Unknown CPU feature: '{s}'
\\Available CPU features for architecture '{s}':
\\
, .{
diags.unknown_feature_name.?,
@tagName(diags.arch.?),
});
for (diags.arch.?.allFeaturesList()) |feature| {
log.err(" {s}: {s}", .{ feature.name, feature.description });
}
b.markInvalidUserInput();
return args.default_target;
},
error.UnknownOperatingSystem => {
log.err(
\\Unknown OS: '{s}'
\\Available operating systems:
\\
, .{diags.os_name.?});
inline for (std.meta.fields(Target.Os.Tag)) |field| {
log.err(" {s}", .{field.name});
}
b.markInvalidUserInput();
return args.default_target;
},
else => |e| {
log.err("Unable to parse target '{s}': {s}\n", .{ triple, @errorName(e) });
error.ParseFailed => {
b.markInvalidUserInput();
return args.default_target;
},
@ -1367,7 +1423,7 @@ pub fn addUserInputOption(self: *Build, name_raw: []const u8, value_raw: []const
});
},
.flag => {
log.warn("Option '-D{s}={s}' conflicts with flag '-D{s}'.", .{ name, value, name });
log.warn("option '-D{s}={s}' conflicts with flag '-D{s}'.", .{ name, value, name });
return true;
},
.map => |*map| {
@ -1427,17 +1483,17 @@ fn markInvalidUserInput(self: *Build) void {
self.invalid_user_input = true;
}
pub fn validateUserInputDidItFail(self: *Build) bool {
// make sure all args are used
var it = self.user_input_options.iterator();
pub fn validateUserInputDidItFail(b: *Build) bool {
// Make sure all args are used.
var it = b.user_input_options.iterator();
while (it.next()) |entry| {
if (!entry.value_ptr.used) {
log.err("Invalid option: -D{s}", .{entry.key_ptr.*});
self.markInvalidUserInput();
log.err("invalid option: -D{s}", .{entry.key_ptr.*});
b.markInvalidUserInput();
}
}
return self.invalid_user_input;
return b.invalid_user_input;
}
fn allocPrintCmd(ally: Allocator, opt_cwd: ?[]const u8, argv: []const []const u8) ![]u8 {
@ -1593,7 +1649,7 @@ pub fn findProgram(self: *Build, names: []const []const u8, paths: []const []con
return fs.realpathAlloc(self.allocator, full_path) catch continue;
}
}
if (self.env_map.get("PATH")) |PATH| {
if (self.graph.env_map.get("PATH")) |PATH| {
for (names) |name| {
if (fs.path.isAbsolute(name)) {
return name;
@ -1639,7 +1695,7 @@ pub fn runAllowFail(
child.stdin_behavior = .Ignore;
child.stdout_behavior = .Pipe;
child.stderr_behavior = stderr_behavior;
child.env_map = self.env_map;
child.env_map = &self.graph.env_map;
try child.spawn();
@ -1685,8 +1741,8 @@ pub fn run(b: *Build, argv: []const []const u8) []u8 {
};
}
pub fn addSearchPrefix(self: *Build, search_prefix: []const u8) void {
self.search_prefixes.append(self.dupePath(search_prefix)) catch @panic("OOM");
pub fn addSearchPrefix(b: *Build, search_prefix: []const u8) void {
b.search_prefixes.append(b.allocator, b.dupePath(search_prefix)) catch @panic("OOM");
}
pub fn getInstallPath(self: *Build, dir: InstallDir, dest_rel_path: []const u8) []const u8 {
@ -1747,21 +1803,63 @@ pub const Dependency = struct {
}
};
pub fn dependency(b: *Build, name: []const u8, args: anytype) *Dependency {
fn findPkgHashOrFatal(b: *Build, name: []const u8) []const u8 {
for (b.available_deps) |dep| {
if (mem.eql(u8, dep[0], name)) return dep[1];
}
const full_path = b.pathFromRoot("build.zig.zon");
std.debug.panic("no dependency named '{s}' in '{s}'. All packages used in build.zig must be declared in this file", .{ name, full_path });
}
fn markNeededLazyDep(b: *Build, pkg_hash: []const u8) void {
b.graph.needed_lazy_dependencies.put(b.graph.arena, pkg_hash, {}) catch @panic("OOM");
}
/// When this function is called, it means that the current build does, in
/// fact, require this dependency. If the dependency is already fetched, it
/// proceeds in the same manner as `dependency`. However if the dependency was
/// not fetched, then when the build script is finished running, the build will
/// not proceed to the make phase. Instead, the parent process will
/// additionally fetch all the lazy dependencies that were actually required by
/// running the build script, rebuild the build script, and then run it again.
/// In other words, if this function returns `null` it means that the only
/// purpose of completing the configure phase is to find out all the other lazy
/// dependencies that are also required.
/// It is allowed to use this function for non-lazy dependencies, in which case
/// it will never return `null`. This allows toggling laziness via
/// build.zig.zon without changing build.zig logic.
pub fn lazyDependency(b: *Build, name: []const u8, args: anytype) ?*Dependency {
const build_runner = @import("root");
const deps = build_runner.dependencies;
const pkg_hash = for (b.available_deps) |dep| {
if (mem.eql(u8, dep[0], name)) break dep[1];
} else {
const full_path = b.pathFromRoot("build.zig.zon");
std.debug.print("no dependency named '{s}' in '{s}'. All packages used in build.zig must be declared in this file.\n", .{ name, full_path });
process.exit(1);
};
const pkg_hash = findPkgHashOrFatal(b, name);
inline for (@typeInfo(deps.packages).Struct.decls) |decl| {
if (mem.eql(u8, decl.name, pkg_hash)) {
const pkg = @field(deps.packages, decl.name);
const available = !@hasDecl(pkg, "available") or pkg.available;
if (!available) {
markNeededLazyDep(b, pkg_hash);
return null;
}
return dependencyInner(b, name, pkg.build_root, if (@hasDecl(pkg, "build_zig")) pkg.build_zig else null, pkg.deps, args);
}
}
unreachable; // Bad @dependencies source
}
pub fn dependency(b: *Build, name: []const u8, args: anytype) *Dependency {
const build_runner = @import("root");
const deps = build_runner.dependencies;
const pkg_hash = findPkgHashOrFatal(b, name);
inline for (@typeInfo(deps.packages).Struct.decls) |decl| {
if (mem.eql(u8, decl.name, pkg_hash)) {
const pkg = @field(deps.packages, decl.name);
if (@hasDecl(pkg, "available")) {
std.debug.panic("dependency '{s}{s}' is marked as lazy in build.zig.zon which means it must use the lazyDependency function instead", .{ b.dep_prefix, name });
}
return dependencyInner(b, name, pkg.build_root, if (@hasDecl(pkg, "build_zig")) pkg.build_zig else null, pkg.deps, args);
}
}
@ -2281,9 +2379,14 @@ pub const ResolvedTarget = struct {
/// Converts a target query into a fully resolved target that can be passed to
/// various parts of the API.
pub fn resolveTargetQuery(b: *Build, query: Target.Query) ResolvedTarget {
// This context will likely be required in the future when the target is
// resolved via a WASI API or via the build protocol.
_ = b;
if (query.isNative()) {
var adjusted = b.host;
if (query.ofmt) |ofmt| {
adjusted.query.ofmt = ofmt;
adjusted.result.ofmt = ofmt;
}
return adjusted;
}
return .{
.query = query,
@ -2296,6 +2399,40 @@ pub fn wantSharedLibSymLinks(target: Target) bool {
return target.os.tag != .windows;
}
pub const SystemIntegrationOptionConfig = struct {
/// If left as null, then the default will depend on system_package_mode.
default: ?bool = null,
};
pub fn systemIntegrationOption(
b: *Build,
name: []const u8,
config: SystemIntegrationOptionConfig,
) bool {
const gop = b.graph.system_library_options.getOrPut(b.allocator, name) catch @panic("OOM");
if (gop.found_existing) switch (gop.value_ptr.*) {
.user_disabled => {
gop.value_ptr.* = .declared_disabled;
return false;
},
.user_enabled => {
gop.value_ptr.* = .declared_enabled;
return true;
},
.declared_disabled => return false,
.declared_enabled => return true,
} else {
gop.key_ptr.* = b.dupe(name);
if (config.default orelse b.graph.system_package_mode) {
gop.value_ptr.* = .declared_enabled;
return true;
} else {
gop.value_ptr.* = .declared_disabled;
return false;
}
}
}
test {
_ = Cache;
_ = Step;

View File

@ -314,7 +314,7 @@ pub fn evalZigProcess(
try handleVerbose(s.owner, null, argv);
var child = std.ChildProcess.init(argv, arena);
child.env_map = b.env_map;
child.env_map = &b.graph.env_map;
child.stdin_behavior = .Pipe;
child.stdout_behavior = .Pipe;
child.stderr_behavior = .Pipe;

View File

@ -923,7 +923,7 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {
var zig_args = ArrayList([]const u8).init(arena);
defer zig_args.deinit();
try zig_args.append(b.zig_exe);
try zig_args.append(b.graph.zig_exe);
const cmd = switch (self.kind) {
.lib => "build-lib",
@ -933,6 +933,16 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {
};
try zig_args.append(cmd);
if (!mem.eql(u8, b.graph.host_query_options.arch_os_abi, "native")) {
try zig_args.appendSlice(&.{ "--host-target", b.graph.host_query_options.arch_os_abi });
}
if (b.graph.host_query_options.cpu_features) |cpu| {
try zig_args.appendSlice(&.{ "--host-cpu", cpu });
}
if (b.graph.host_query_options.dynamic_linker) |dl| {
try zig_args.appendSlice(&.{ "--host-dynamic-linker", dl });
}
if (b.reference_trace) |some| {
try zig_args.append(try std.fmt.allocPrint(arena, "-freference-trace={d}", .{some}));
}
@ -1393,7 +1403,7 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {
try zig_args.append(b.cache_root.path orelse ".");
try zig_args.append("--global-cache-dir");
try zig_args.append(b.global_cache_root.path orelse ".");
try zig_args.append(b.graph.global_cache_root.path orelse ".");
try zig_args.append("--name");
try zig_args.append(self.name);

View File

@ -171,7 +171,7 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {
const gpa = b.allocator;
const arena = b.allocator;
var man = b.cache.obtain();
var man = b.graph.cache.obtain();
defer man.deinit();
// Random bytes to make ConfigHeader unique. Refresh this with new

View File

@ -52,7 +52,7 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {
var argv: std.ArrayListUnmanaged([]const u8) = .{};
try argv.ensureUnusedCapacity(arena, 2 + 1 + self.paths.len + 2 * self.exclude_paths.len);
argv.appendAssumeCapacity(b.zig_exe);
argv.appendAssumeCapacity(b.graph.zig_exe);
argv.appendAssumeCapacity("fmt");
if (self.check) {

View File

@ -94,7 +94,7 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {
const b = step.owner;
const self = @fieldParentPtr(ObjCopy, "step", step);
var man = b.cache.obtain();
var man = b.graph.cache.obtain();
defer man.deinit();
// Random bytes to make ObjCopy unique. Refresh this with new random
@ -133,7 +133,7 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {
};
var argv = std.ArrayList([]const u8).init(b.allocator);
try argv.appendSlice(&.{ b.zig_exe, "objcopy" });
try argv.appendSlice(&.{ b.graph.zig_exe, "objcopy" });
if (self.only_section) |only_section| {
try argv.appendSlice(&.{ "-j", only_section });

View File

@ -222,7 +222,7 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {
const basename = "options.zig";
// Hash contents to file name.
var hash = b.cache.hash;
var hash = b.graph.cache.hash;
// Random bytes to make unique. Refresh this with new random bytes when
// implementation is modified in a non-backwards-compatible way.
hash.add(@as(u32, 0xad95e922));
@ -301,27 +301,28 @@ test Options {
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
defer arena.deinit();
const host: std.Build.ResolvedTarget = .{
.query = .{},
.result = try std.zig.system.resolveTargetQuery(.{}),
};
var cache: std.Build.Cache = .{
.gpa = arena.allocator(),
.manifest_dir = std.fs.cwd(),
var graph: std.Build.Graph = .{
.arena = arena.allocator(),
.cache = .{
.gpa = arena.allocator(),
.manifest_dir = std.fs.cwd(),
},
.zig_exe = "test",
.env_map = std.process.EnvMap.init(arena.allocator()),
.global_cache_root = .{ .path = "test", .handle = std.fs.cwd() },
};
var builder = try std.Build.create(
arena.allocator(),
"test",
&graph,
.{ .path = "test", .handle = std.fs.cwd() },
.{ .path = "test", .handle = std.fs.cwd() },
.{ .path = "test", .handle = std.fs.cwd() },
host,
&cache,
&.{},
);
defer builder.destroy();
builder.host = .{
.query = .{},
.result = try std.zig.system.resolveTargetQuery(.{}),
};
const options = builder.addOptions();

View File

@ -463,7 +463,7 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {
var argv_list = ArrayList([]const u8).init(arena);
var output_placeholders = ArrayList(IndexedOutput).init(arena);
var man = b.cache.obtain();
var man = b.graph.cache.obtain();
defer man.deinit();
for (self.argv.items) |arg| {
@ -1036,7 +1036,7 @@ fn spawnChildAndCollect(
child.cwd = b.build_root.path;
child.cwd_dir = b.build_root.handle;
}
child.env_map = self.env_map orelse b.env_map;
child.env_map = self.env_map orelse &b.graph.env_map;
child.request_resource_usage_statistics = true;
child.stdin_behavior = switch (self.stdio) {

View File

@ -121,7 +121,7 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {
const self = @fieldParentPtr(TranslateC, "step", step);
var argv_list = std.ArrayList([]const u8).init(b.allocator);
try argv_list.append(b.zig_exe);
try argv_list.append(b.graph.zig_exe);
try argv_list.append("translate-c");
if (self.link_libc) {
try argv_list.append("-lc");

View File

@ -190,7 +190,7 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {
// If, for example, a hard-coded path was used as the location to put WriteFile
// files, then two WriteFiles executing in parallel might clobber each other.
var man = b.cache.obtain();
var man = b.graph.cache.obtain();
defer man.deinit();
// Random bytes to make WriteFile unique. Refresh this with

View File

@ -468,7 +468,7 @@ pub fn zigTriple(self: Query, allocator: Allocator) Allocator.Error![]u8 {
}
if (self.glibc_version) |v| {
const name = @tagName(self.abi orelse builtin.target.abi);
const name = if (self.abi) |abi| @tagName(abi) else "gnu";
try result.ensureUnusedCapacity(name.len + 2);
result.appendAssumeCapacity('-');
result.appendSliceAssumeCapacity(name);

View File

@ -298,7 +298,9 @@ pub const ChildProcess = struct {
// we could make this work with multiple allocators but YAGNI
if (stdout.allocator.ptr != stderr.allocator.ptr or
stdout.allocator.vtable != stderr.allocator.vtable)
@panic("ChildProcess.collectOutput only supports 1 allocator");
{
unreachable; // ChildProcess.collectOutput only supports 1 allocator
}
var poller = std.io.poll(stdout.allocator, enum { stdout, stderr }, .{
.stdout = child.stdout.?,

View File

@ -4530,6 +4530,9 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: *std.P
log.err("{}: failed to parse clang diagnostics: {s}", .{ err, stderr });
return comp.failCObj(c_object, "clang exited with code {d}", .{code});
};
zig_cache_tmp_dir.deleteFile(out_diag_path) catch |err| {
log.warn("failed to delete '{s}': {s}", .{ out_diag_path, @errorName(err) });
};
return comp.failCObjWithOwnedDiagBundle(c_object, bundle);
}
},

View File

@ -31,6 +31,8 @@ arena: std.heap.ArenaAllocator,
location: Location,
location_tok: std.zig.Ast.TokenIndex,
hash_tok: std.zig.Ast.TokenIndex,
name_tok: std.zig.Ast.TokenIndex,
lazy_status: LazyStatus,
parent_package_root: Package.Path,
parent_manifest_ast: ?*const std.zig.Ast,
prog_node: *std.Progress.Node,
@ -64,6 +66,15 @@ oom_flag: bool,
/// the root source file.
module: ?*Package.Module,
pub const LazyStatus = enum {
/// Not lazy.
eager,
/// Lazy, found.
available,
/// Lazy, not found.
unavailable,
};
/// Contains shared state among all `Fetch` tasks.
pub const JobQueue = struct {
mutex: std.Thread.Mutex = .{},
@ -80,14 +91,27 @@ pub const JobQueue = struct {
thread_pool: *ThreadPool,
wait_group: WaitGroup = .{},
global_cache: Cache.Directory,
/// If true then, no fetching occurs, and:
/// * The `global_cache` directory is assumed to be the direct parent
/// directory of on-disk packages rather than having the "p/" directory
/// prefix inside of it.
/// * An error occurs if any non-lazy packages are not already present in
/// the package cache directory.
/// * Missing hash field causes an error, and no fetching occurs so it does
/// not print the correct hash like usual.
read_only: bool,
recursive: bool,
/// Dumps hash information to stdout which can be used to troubleshoot why
/// two hashes of the same package do not match.
/// If this is true, `recursive` must be false.
debug_hash: bool,
work_around_btrfs_bug: bool,
/// Set of hashes that will be additionally fetched even if they are marked
/// as lazy.
unlazy_set: UnlazySet = .{},
pub const Table = std.AutoArrayHashMapUnmanaged(Manifest.MultiHashHexDigest, *Fetch);
pub const UnlazySet = std.AutoArrayHashMapUnmanaged(Manifest.MultiHashHexDigest, void);
pub fn deinit(jq: *JobQueue) void {
if (jq.all_fetches.items.len == 0) return;
@ -141,11 +165,37 @@ pub const JobQueue = struct {
// The first one is a dummy package for the current project.
continue;
}
try buf.writer().print(
\\ pub const {} = struct {{
\\
, .{std.zig.fmtId(&hash)});
lazy: {
switch (fetch.lazy_status) {
.eager => break :lazy,
.available => {
try buf.appendSlice(
\\ pub const available = true;
\\
);
break :lazy;
},
.unavailable => {
try buf.appendSlice(
\\ pub const available = false;
\\ };
\\
);
continue;
},
}
}
try buf.writer().print(
\\ pub const build_root = "{q}";
\\
, .{ std.zig.fmtId(&hash), fetch.package_root });
, .{fetch.package_root});
if (fetch.has_build_zig) {
try buf.writer().print(
@ -270,7 +320,8 @@ pub fn run(f: *Fetch) RunError!void {
// We want to fail unless the resolved relative path has a
// prefix of "p/$hash/".
const digest_len = @typeInfo(Manifest.MultiHashHexDigest).Array.len;
const expected_prefix = f.parent_package_root.sub_path[0 .. "p/".len + digest_len];
const prefix_len: usize = if (f.job_queue.read_only) 0 else "p/".len;
const expected_prefix = f.parent_package_root.sub_path[0 .. prefix_len + digest_len];
if (!std.mem.startsWith(u8, pkg_root.sub_path, expected_prefix)) {
return f.fail(
f.location_tok,
@ -311,8 +362,11 @@ pub fn run(f: *Fetch) RunError!void {
const s = fs.path.sep_str;
if (remote.hash) |expected_hash| {
const pkg_sub_path = "p" ++ s ++ expected_hash;
const prefixed_pkg_sub_path = "p" ++ s ++ expected_hash;
const prefix_len: usize = if (f.job_queue.read_only) "p/".len else 0;
const pkg_sub_path = prefixed_pkg_sub_path[prefix_len..];
if (cache_root.handle.access(pkg_sub_path, .{})) |_| {
assert(f.lazy_status != .unavailable);
f.package_root = .{
.root_dir = cache_root,
.sub_path = try arena.dupe(u8, pkg_sub_path),
@ -322,7 +376,22 @@ pub fn run(f: *Fetch) RunError!void {
if (!f.job_queue.recursive) return;
return queueJobsForDeps(f);
} else |err| switch (err) {
error.FileNotFound => {},
error.FileNotFound => {
switch (f.lazy_status) {
.eager => {},
.available => if (!f.job_queue.unlazy_set.contains(expected_hash)) {
f.lazy_status = .unavailable;
return;
},
.unavailable => unreachable,
}
if (f.job_queue.read_only) return f.fail(
f.name_tok,
try eb.printString("package not found at '{}{s}'", .{
cache_root, pkg_sub_path,
}),
);
},
else => |e| {
try eb.addRootErrorMessage(.{
.msg = try eb.printString("unable to open global package cache directory '{}{s}': {s}", .{
@ -332,6 +401,12 @@ pub fn run(f: *Fetch) RunError!void {
return error.FetchFailed;
},
}
} else {
try eb.addRootErrorMessage(.{
.msg = try eb.addString("dependency is missing hash field"),
.src_loc = try f.srcLoc(f.location_tok),
});
return error.FetchFailed;
}
// Fetch and unpack the remote into a temporary directory.
@ -602,6 +677,8 @@ fn queueJobsForDeps(f: *Fetch) RunError!void {
.location = location,
.location_tok = dep.location_tok,
.hash_tok = dep.hash_tok,
.name_tok = dep.name_tok,
.lazy_status = if (dep.lazy) .available else .eager,
.parent_package_root = f.package_root,
.parent_manifest_ast = &f.manifest_ast,
.prog_node = f.prog_node,

View File

@ -12,6 +12,8 @@ pub const Dependency = struct {
hash: ?[]const u8,
hash_tok: Ast.TokenIndex,
node: Ast.Node.Index,
name_tok: Ast.TokenIndex,
lazy: bool,
pub const Location = union(enum) {
url: []const u8,
@ -303,11 +305,14 @@ const Parse = struct {
.hash = null,
.hash_tok = 0,
.node = node,
.name_tok = 0,
.lazy = false,
};
var has_location = false;
for (struct_init.ast.fields) |field_init| {
const name_token = ast.firstToken(field_init) - 2;
dep.name_tok = name_token;
const field_name = try identifierTokenString(p, name_token);
// We could get fancy with reflection and comptime logic here but doing
// things manually provides an opportunity to do any additional verification
@ -342,6 +347,11 @@ const Parse = struct {
else => |e| return e,
};
dep.hash_tok = main_tokens[field_init];
} else if (mem.eql(u8, field_name, "lazy")) {
dep.lazy = parseBool(p, field_init) catch |err| switch (err) {
error.ParseFailure => continue,
else => |e| return e,
};
} else {
// Ignore unknown fields so that we can add fields in future zig
// versions without breaking older zig versions.
@ -374,6 +384,24 @@ const Parse = struct {
}
}
fn parseBool(p: *Parse, node: Ast.Node.Index) !bool {
const ast = p.ast;
const node_tags = ast.nodes.items(.tag);
const main_tokens = ast.nodes.items(.main_token);
if (node_tags[node] != .identifier) {
return fail(p, main_tokens[node], "expected identifier", .{});
}
const ident_token = main_tokens[node];
const token_bytes = ast.tokenSlice(ident_token);
if (mem.eql(u8, token_bytes, "true")) {
return true;
} else if (mem.eql(u8, token_bytes, "false")) {
return false;
} else {
return fail(p, ident_token, "expected boolean", .{});
}
}
fn parseString(p: *Parse, node: Ast.Node.Index) ![]const u8 {
const ast = p.ast;
const node_tags = ast.nodes.items(.tag);

File diff suppressed because it is too large Load Diff

View File

@ -562,7 +562,7 @@ pub fn lowerToBuildSteps(
run.setName(incr_case.base_path);
run.addArgs(&.{
case_base_path_with_dir,
b.zig_exe,
b.graph.zig_exe,
});
run.expectStdOutEqual("");
parent_step.dependOn(&run.step);
@ -653,7 +653,7 @@ pub fn lowerToBuildSteps(
break :no_exec;
}
const run_c = b.addSystemCommand(&.{
b.zig_exe,
b.graph.zig_exe,
"run",
"-cflags",
"-Ilib",

View File

@ -796,7 +796,7 @@ pub fn addCliTests(b: *std.Build) *Step {
{
// Test `zig init`.
const tmp_path = b.makeTempPath();
const init_exe = b.addSystemCommand(&.{ b.zig_exe, "init" });
const init_exe = b.addSystemCommand(&.{ b.graph.zig_exe, "init" });
init_exe.setCwd(.{ .cwd_relative = tmp_path });
init_exe.setName("zig init");
init_exe.expectStdOutEqual("");
@ -810,20 +810,20 @@ pub fn addCliTests(b: *std.Build) *Step {
const bad_out_arg = "-femit-bin=does" ++ s ++ "not" ++ s ++ "exist" ++ s ++ "foo.exe";
const ok_src_arg = "src" ++ s ++ "main.zig";
const expected = "error: unable to open output directory 'does" ++ s ++ "not" ++ s ++ "exist': FileNotFound\n";
const run_bad = b.addSystemCommand(&.{ b.zig_exe, "build-exe", ok_src_arg, bad_out_arg });
const run_bad = b.addSystemCommand(&.{ b.graph.zig_exe, "build-exe", ok_src_arg, bad_out_arg });
run_bad.setName("zig build-exe error message for bad -femit-bin arg");
run_bad.expectExitCode(1);
run_bad.expectStdErrEqual(expected);
run_bad.expectStdOutEqual("");
run_bad.step.dependOn(&init_exe.step);
const run_test = b.addSystemCommand(&.{ b.zig_exe, "build", "test" });
const run_test = b.addSystemCommand(&.{ b.graph.zig_exe, "build", "test" });
run_test.setCwd(.{ .cwd_relative = tmp_path });
run_test.setName("zig build test");
run_test.expectStdOutEqual("");
run_test.step.dependOn(&init_exe.step);
const run_run = b.addSystemCommand(&.{ b.zig_exe, "build", "run" });
const run_run = b.addSystemCommand(&.{ b.graph.zig_exe, "build", "run" });
run_run.setCwd(.{ .cwd_relative = tmp_path });
run_run.setName("zig build run");
run_run.expectStdOutEqual("Run `zig build test` to run the tests.\n");
@ -857,7 +857,7 @@ pub fn addCliTests(b: *std.Build) *Step {
// This is intended to be the exact CLI usage used by godbolt.org.
const run = b.addSystemCommand(&.{
b.zig_exe, "build-obj",
b.graph.zig_exe, "build-obj",
"--cache-dir", tmp_path,
"--name", "example",
"-fno-emit-bin", "-fno-emit-h",
@ -900,7 +900,7 @@ pub fn addCliTests(b: *std.Build) *Step {
subdir.writeFile("fmt3.zig", unformatted_code) catch @panic("unhandled");
// Test zig fmt affecting only the appropriate files.
const run1 = b.addSystemCommand(&.{ b.zig_exe, "fmt", "fmt1.zig" });
const run1 = b.addSystemCommand(&.{ b.graph.zig_exe, "fmt", "fmt1.zig" });
run1.setName("run zig fmt one file");
run1.setCwd(.{ .cwd_relative = tmp_path });
run1.has_side_effects = true;
@ -908,7 +908,7 @@ pub fn addCliTests(b: *std.Build) *Step {
run1.expectStdOutEqual("fmt1.zig\n");
// Test excluding files and directories from a run
const run2 = b.addSystemCommand(&.{ b.zig_exe, "fmt", "--exclude", "fmt2.zig", "--exclude", "subdir", "." });
const run2 = b.addSystemCommand(&.{ b.graph.zig_exe, "fmt", "--exclude", "fmt2.zig", "--exclude", "subdir", "." });
run2.setName("run zig fmt on directory with exclusions");
run2.setCwd(.{ .cwd_relative = tmp_path });
run2.has_side_effects = true;
@ -916,7 +916,7 @@ pub fn addCliTests(b: *std.Build) *Step {
run2.step.dependOn(&run1.step);
// Test excluding non-existent file
const run3 = b.addSystemCommand(&.{ b.zig_exe, "fmt", "--exclude", "fmt2.zig", "--exclude", "nonexistent.zig", "." });
const run3 = b.addSystemCommand(&.{ b.graph.zig_exe, "fmt", "--exclude", "fmt2.zig", "--exclude", "nonexistent.zig", "." });
run3.setName("run zig fmt on directory with non-existent exclusion");
run3.setCwd(.{ .cwd_relative = tmp_path });
run3.has_side_effects = true;
@ -924,7 +924,7 @@ pub fn addCliTests(b: *std.Build) *Step {
run3.step.dependOn(&run2.step);
// running it on the dir, only the new file should be changed
const run4 = b.addSystemCommand(&.{ b.zig_exe, "fmt", "." });
const run4 = b.addSystemCommand(&.{ b.graph.zig_exe, "fmt", "." });
run4.setName("run zig fmt the directory");
run4.setCwd(.{ .cwd_relative = tmp_path });
run4.has_side_effects = true;
@ -932,7 +932,7 @@ pub fn addCliTests(b: *std.Build) *Step {
run4.step.dependOn(&run3.step);
// both files have been formatted, nothing should change now
const run5 = b.addSystemCommand(&.{ b.zig_exe, "fmt", "." });
const run5 = b.addSystemCommand(&.{ b.graph.zig_exe, "fmt", "." });
run5.setName("run zig fmt with nothing to do");
run5.setCwd(.{ .cwd_relative = tmp_path });
run5.has_side_effects = true;
@ -946,7 +946,7 @@ pub fn addCliTests(b: *std.Build) *Step {
write6.step.dependOn(&run5.step);
// Test `zig fmt` handling UTF-16 decoding.
const run6 = b.addSystemCommand(&.{ b.zig_exe, "fmt", "." });
const run6 = b.addSystemCommand(&.{ b.graph.zig_exe, "fmt", "." });
run6.setName("run zig fmt convert UTF-16 to UTF-8");
run6.setCwd(.{ .cwd_relative = tmp_path });
run6.has_side_effects = true;