Merge pull request #18138 from ziglang/fetch-save

`zig fetch`: add `--save` flag
This commit is contained in:
Andrew Kelley 2023-11-27 04:47:44 -05:00 committed by GitHub
commit 89572c6342
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 412 additions and 138 deletions

View File

@ -61,7 +61,7 @@ String.
When this is provided, the package is found in a directory relative to the
build root. In this case the package's hash is irrelevant and therefore not
computed.
computed. This field and `url` are mutually exclusive.
### `paths`

View File

@ -15,8 +15,7 @@
// Once all dependencies are fetched, `zig build` no longer requires
// Internet connectivity.
.dependencies = .{
// A future version of Zig will provide a `zig add <url>` subcommand
// for easily adding dependencies.
// See `zig fetch --save <url>` for a command-line interface for adding dependencies.
//.example = .{
// // When updating this field to a new URL, be sure to delete the corresponding
// // `hash`, otherwise you are communicating that you expect to find the old hash at
@ -36,7 +35,7 @@
//
// // When this is provided, the package is found in a directory relative to the
// // build root. In this case the package's hash is irrelevant and therefore not
// // computed.
// // computed. This field and `url` are mutually exclusive.
// .path = "foo",
//},
},
@ -57,5 +56,7 @@
//"build.zig",
//"build.zig.zon",
//"src",
//"LICENSE",
//"README.md",
},
}

View File

@ -26,6 +26,8 @@ pub const Fixups = struct {
omit_nodes: std.AutoHashMapUnmanaged(Ast.Node.Index, void) = .{},
/// These expressions will be replaced with the string value.
replace_nodes_with_string: std.AutoHashMapUnmanaged(Ast.Node.Index, []const u8) = .{},
/// The string value will be inserted directly after the node.
append_string_after_node: std.AutoHashMapUnmanaged(Ast.Node.Index, []const u8) = .{},
/// These nodes will be replaced with a different node.
replace_nodes_with_node: std.AutoHashMapUnmanaged(Ast.Node.Index, Ast.Node.Index) = .{},
/// Change all identifier names matching the key to be value instead.
@ -40,6 +42,7 @@ pub const Fixups = struct {
f.gut_functions.count() +
f.omit_nodes.count() +
f.replace_nodes_with_string.count() +
f.append_string_after_node.count() +
f.replace_nodes_with_node.count() +
f.rename_identifiers.count() +
@intFromBool(f.rebase_imported_paths != null);
@ -50,6 +53,7 @@ pub const Fixups = struct {
f.gut_functions.clearRetainingCapacity();
f.omit_nodes.clearRetainingCapacity();
f.replace_nodes_with_string.clearRetainingCapacity();
f.append_string_after_node.clearRetainingCapacity();
f.replace_nodes_with_node.clearRetainingCapacity();
f.rename_identifiers.clearRetainingCapacity();
@ -61,6 +65,7 @@ pub const Fixups = struct {
f.gut_functions.deinit(gpa);
f.omit_nodes.deinit(gpa);
f.replace_nodes_with_string.deinit(gpa);
f.append_string_after_node.deinit(gpa);
f.replace_nodes_with_node.deinit(gpa);
f.rename_identifiers.deinit(gpa);
f.* = undefined;
@ -912,6 +917,16 @@ fn renderExpression(r: *Render, node: Ast.Node.Index, space: Space) Error!void {
}
}
/// Same as `renderExpression`, but afterwards looks for any
/// append_string_after_node fixups to apply
fn renderExpressionFixup(r: *Render, node: Ast.Node.Index, space: Space) Error!void {
const ais = r.ais;
try renderExpression(r, node, space);
if (r.fixups.append_string_after_node.get(node)) |bytes| {
try ais.writer().writeAll(bytes);
}
}
fn renderArrayType(
r: *Render,
array_type: Ast.full.ArrayType,
@ -2093,10 +2108,11 @@ fn renderStructInit(
// Don't output a space after the = if expression is a multiline string,
// since then it will start on the next line.
const nodes = tree.nodes.items(.tag);
const expr = nodes[struct_init.ast.fields[0]];
const field_node = struct_init.ast.fields[0];
const expr = nodes[field_node];
var space_after_equal: Space = if (expr == .multiline_string_literal) .none else .space;
try renderToken(r, struct_init.ast.lbrace + 3, space_after_equal); // =
try renderExpression(r, struct_init.ast.fields[0], .comma);
try renderExpressionFixup(r, field_node, .comma);
for (struct_init.ast.fields[1..]) |field_init| {
const init_token = tree.firstToken(field_init);
@ -2105,7 +2121,7 @@ fn renderStructInit(
try renderIdentifier(r, init_token - 2, .space, .eagerly_unquote); // name
space_after_equal = if (nodes[field_init] == .multiline_string_literal) .none else .space;
try renderToken(r, init_token - 1, space_after_equal); // =
try renderExpression(r, field_init, .comma);
try renderExpressionFixup(r, field_init, .comma);
}
ais.popIndent();
@ -2118,7 +2134,7 @@ fn renderStructInit(
try renderToken(r, init_token - 3, .none); // .
try renderIdentifier(r, init_token - 2, .space, .eagerly_unquote); // name
try renderToken(r, init_token - 1, .space); // =
try renderExpression(r, field_init, .comma_space);
try renderExpressionFixup(r, field_init, .comma_space);
}
}

View File

@ -520,24 +520,7 @@ fn loadManifest(f: *Fetch, pkg_root: Package.Path) RunError!void {
if (manifest.errors.len > 0) {
const src_path = try eb.printString("{}{s}", .{ pkg_root, Manifest.basename });
const token_starts = ast.tokens.items(.start);
for (manifest.errors) |msg| {
const start_loc = ast.tokenLocation(0, msg.tok);
try eb.addRootErrorMessage(.{
.msg = try eb.addString(msg.msg),
.src_loc = try eb.addSourceLocation(.{
.src_path = src_path,
.span_start = token_starts[msg.tok],
.span_end = @intCast(token_starts[msg.tok] + ast.tokenSlice(msg.tok).len),
.span_main = token_starts[msg.tok] + msg.off,
.line = @intCast(start_loc.line),
.column = @intCast(start_loc.column),
.source_line = try eb.addString(ast.source[start_loc.line_start..start_loc.line_end]),
}),
});
}
try manifest.copyErrorsIntoBundle(ast.*, src_path, eb);
return error.FetchFailed;
}
}

View File

@ -11,6 +11,7 @@ pub const Dependency = struct {
location_tok: Ast.TokenIndex,
hash: ?[]const u8,
hash_tok: Ast.TokenIndex,
node: Ast.Node.Index,
pub const Location = union(enum) {
url: []const u8,
@ -55,7 +56,9 @@ comptime {
name: []const u8,
version: std.SemanticVersion,
version_node: Ast.Node.Index,
dependencies: std.StringArrayHashMapUnmanaged(Dependency),
dependencies_node: Ast.Node.Index,
paths: std.StringArrayHashMapUnmanaged(void),
minimum_zig_version: ?std.SemanticVersion,
@ -68,7 +71,7 @@ pub const ParseOptions = struct {
pub const Error = Allocator.Error;
pub fn parse(gpa: Allocator, ast: std.zig.Ast, options: ParseOptions) Error!Manifest {
pub fn parse(gpa: Allocator, ast: Ast, options: ParseOptions) Error!Manifest {
const node_tags = ast.nodes.items(.tag);
const node_datas = ast.nodes.items(.data);
assert(node_tags[0] == .root);
@ -85,7 +88,9 @@ pub fn parse(gpa: Allocator, ast: std.zig.Ast, options: ParseOptions) Error!Mani
.name = undefined,
.version = undefined,
.version_node = 0,
.dependencies = .{},
.dependencies_node = 0,
.paths = .{},
.allow_missing_paths_field = options.allow_missing_paths_field,
.minimum_zig_version = null,
@ -104,7 +109,9 @@ pub fn parse(gpa: Allocator, ast: std.zig.Ast, options: ParseOptions) Error!Mani
return .{
.name = p.name,
.version = p.version,
.version_node = p.version_node,
.dependencies = try p.dependencies.clone(p.arena),
.dependencies_node = p.dependencies_node,
.paths = try p.paths.clone(p.arena),
.minimum_zig_version = p.minimum_zig_version,
.errors = try p.arena.dupe(ErrorMessage, p.errors.items),
@ -117,6 +124,33 @@ pub fn deinit(man: *Manifest, gpa: Allocator) void {
man.* = undefined;
}
pub fn copyErrorsIntoBundle(
man: Manifest,
ast: Ast,
/// ErrorBundle null-terminated string index
src_path: u32,
eb: *std.zig.ErrorBundle.Wip,
) Allocator.Error!void {
const token_starts = ast.tokens.items(.start);
for (man.errors) |msg| {
const start_loc = ast.tokenLocation(0, msg.tok);
try eb.addRootErrorMessage(.{
.msg = try eb.addString(msg.msg),
.src_loc = try eb.addSourceLocation(.{
.src_path = src_path,
.span_start = token_starts[msg.tok],
.span_end = @intCast(token_starts[msg.tok] + ast.tokenSlice(msg.tok).len),
.span_main = token_starts[msg.tok] + msg.off,
.line = @intCast(start_loc.line),
.column = @intCast(start_loc.column),
.source_line = try eb.addString(ast.source[start_loc.line_start..start_loc.line_end]),
}),
});
}
}
const hex_charset = "0123456789abcdef";
pub fn hex64(x: u64) [16]u8 {
@ -153,14 +187,16 @@ pub fn hexDigest(digest: Digest) MultiHashHexDigest {
const Parse = struct {
gpa: Allocator,
ast: std.zig.Ast,
ast: Ast,
arena: Allocator,
buf: std.ArrayListUnmanaged(u8),
errors: std.ArrayListUnmanaged(ErrorMessage),
name: []const u8,
version: std.SemanticVersion,
version_node: Ast.Node.Index,
dependencies: std.StringArrayHashMapUnmanaged(Dependency),
dependencies_node: Ast.Node.Index,
paths: std.StringArrayHashMapUnmanaged(void),
allow_missing_paths_field: bool,
minimum_zig_version: ?std.SemanticVersion,
@ -188,6 +224,7 @@ const Parse = struct {
// things manually provides an opportunity to do any additional verification
// that is desirable on a per-field basis.
if (mem.eql(u8, field_name, "dependencies")) {
p.dependencies_node = field_init;
try parseDependencies(p, field_init);
} else if (mem.eql(u8, field_name, "paths")) {
have_included_paths = true;
@ -196,6 +233,7 @@ const Parse = struct {
p.name = try parseString(p, field_init);
have_name = true;
} else if (mem.eql(u8, field_name, "version")) {
p.version_node = field_init;
const version_text = try parseString(p, field_init);
p.version = std.SemanticVersion.parse(version_text) catch |err| v: {
try appendError(p, main_tokens[field_init], "unable to parse semantic version: {s}", .{@errorName(err)});
@ -264,6 +302,7 @@ const Parse = struct {
.location_tok = 0,
.hash = null,
.hash_tok = 0,
.node = node,
};
var has_location = false;
@ -548,7 +587,7 @@ test "basic" {
\\}
;
var ast = try std.zig.Ast.parse(gpa, example, .zon);
var ast = try Ast.parse(gpa, example, .zon);
defer ast.deinit(gpa);
try testing.expect(ast.errors.len == 0);
@ -591,7 +630,7 @@ test "minimum_zig_version" {
\\}
;
var ast = try std.zig.Ast.parse(gpa, example, .zon);
var ast = try Ast.parse(gpa, example, .zon);
defer ast.deinit(gpa);
try testing.expect(ast.errors.len == 0);
@ -623,7 +662,7 @@ test "minimum_zig_version - invalid version" {
\\}
;
var ast = try std.zig.Ast.parse(gpa, example, .zon);
var ast = try Ast.parse(gpa, example, .zon);
defer ast.deinit(gpa);
try testing.expect(ast.errors.len == 0);

View File

@ -1255,7 +1255,7 @@ fn buildOutputType(
override_lib_dir = args_iter.nextOrFatal();
} else if (mem.eql(u8, arg, "--debug-log")) {
if (!build_options.enable_logging) {
std.log.warn("Zig was compiled without logging enabled (-Dlog). --debug-log has no effect.", .{});
warn("Zig was compiled without logging enabled (-Dlog). --debug-log has no effect.", .{});
_ = args_iter.nextOrFatal();
} else {
try log_scopes.append(gpa, args_iter.nextOrFatal());
@ -1279,7 +1279,7 @@ fn buildOutputType(
listen = .stdio;
} else if (mem.eql(u8, arg, "--debug-link-snapshot")) {
if (!build_options.enable_link_snapshots) {
std.log.warn("Zig was compiled without linker snapshots enabled (-Dlink-snapshot). --debug-link-snapshot has no effect.", .{});
warn("Zig was compiled without linker snapshots enabled (-Dlink-snapshot). --debug-link-snapshot has no effect.", .{});
} else {
enable_link_snapshots = true;
}
@ -1558,7 +1558,7 @@ fn buildOutputType(
};
} else if (mem.eql(u8, arg, "--debug-compile-errors")) {
if (!crash_report.is_enabled) {
std.log.warn("Zig was compiled in a release mode. --debug-compile-errors has no effect.", .{});
warn("Zig was compiled in a release mode. --debug-compile-errors has no effect.", .{});
} else {
debug_compile_errors = true;
}
@ -1662,7 +1662,7 @@ fn buildOutputType(
},
.def, .unknown => {
if (std.ascii.eqlIgnoreCase(".xml", std.fs.path.extension(arg))) {
std.log.warn("embedded manifest files must have the extension '.manifest'", .{});
warn("embedded manifest files must have the extension '.manifest'", .{});
}
fatal("unrecognized file extension of parameter '{s}'", .{arg});
},
@ -2793,7 +2793,7 @@ fn buildOutputType(
continue;
},
.only_compiler_rt => {
std.log.warn("ignoring superfluous library '{s}': this dependency is fulfilled instead by compiler-rt which zig unconditionally provides", .{lib_name});
warn("ignoring superfluous library '{s}': this dependency is fulfilled instead by compiler-rt which zig unconditionally provides", .{lib_name});
continue;
},
}
@ -4851,7 +4851,6 @@ pub const usage_init =
;
pub fn cmdInit(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
_ = gpa;
{
var i: usize = 0;
while (i < args.len) : (i += 1) {
@ -4868,58 +4867,24 @@ pub fn cmdInit(gpa: Allocator, arena: Allocator, args: []const []const u8) !void
}
}
}
const self_exe_path = try introspect.findZigExePath(arena);
var zig_lib_directory = introspect.findZigLibDirFromSelfExe(arena, self_exe_path) catch |err| {
fatal("unable to find zig installation directory: {s}\n", .{@errorName(err)});
};
defer zig_lib_directory.handle.close();
const s = fs.path.sep_str;
const template_sub_path = "init";
var template_dir = zig_lib_directory.handle.openDir(template_sub_path, .{}) catch |err| {
const path = zig_lib_directory.path orelse ".";
fatal("unable to open zig project template directory '{s}{s}{s}': {s}", .{
path, s, template_sub_path, @errorName(err),
});
};
defer template_dir.close();
var templates = findTemplates(gpa, arena);
defer templates.deinit();
const cwd_path = try process.getCwdAlloc(arena);
const cwd_basename = fs.path.basename(cwd_path);
const max_bytes = 10 * 1024 * 1024;
const s = fs.path.sep_str;
const template_paths = [_][]const u8{
"build.zig",
"build.zig.zon",
Package.build_zig_basename,
Package.Manifest.basename,
"src" ++ s ++ "main.zig",
"src" ++ s ++ "root.zig",
};
var ok_count: usize = 0;
for (template_paths) |template_path| {
if (fs.path.dirname(template_path)) |dirname| {
fs.cwd().makePath(dirname) catch |err| {
fatal("unable to make path '{s}': {s}", .{ dirname, @errorName(err) });
};
}
const contents = template_dir.readFileAlloc(arena, template_path, max_bytes) catch |err| {
fatal("unable to read template file '{s}': {s}", .{ template_path, @errorName(err) });
};
var modified_contents = try std.ArrayList(u8).initCapacity(arena, contents.len);
for (contents) |c| {
if (c == '$') {
try modified_contents.appendSlice(cwd_basename);
} else {
try modified_contents.append(c);
}
}
if (fs.cwd().writeFile2(.{
.sub_path = template_path,
.data = modified_contents.items,
.flags = .{ .exclusive = true },
})) |_| {
if (templates.write(arena, fs.cwd(), cwd_basename, template_path)) |_| {
std.log.info("created {s}", .{template_path});
ok_count += 1;
} else |err| switch (err) {
@ -5057,14 +5022,14 @@ pub fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !voi
try child_argv.appendSlice(args[i .. i + 2]);
i += 1;
if (!build_options.enable_logging) {
std.log.warn("Zig was compiled without logging enabled (-Dlog). --debug-log has no effect.", .{});
warn("Zig was compiled without logging enabled (-Dlog). --debug-log has no effect.", .{});
} else {
try log_scopes.append(gpa, args[i]);
}
continue;
} else if (mem.eql(u8, arg, "--debug-compile-errors")) {
if (!crash_report.is_enabled) {
std.log.warn("Zig was compiled in a release mode. --debug-compile-errors has no effect.", .{});
warn("Zig was compiled in a release mode. --debug-compile-errors has no effect.", .{});
} else {
debug_compile_errors = true;
}
@ -5113,44 +5078,11 @@ pub fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !voi
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 Package.build_zig_basename;
const build_root: Compilation.Directory = blk: {
if (build_file) |bf| {
if (fs.path.dirname(bf)) |dirname| {
const dir = fs.cwd().openDir(dirname, .{}) catch |err| {
fatal("unable to open directory to build file from argument 'build-file', '{s}': {s}", .{ dirname, @errorName(err) });
};
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 = fs.cwd().openDir(dirname, .{}) catch |err| {
fatal("unable to open directory while searching for build.zig file, '{s}': {s}", .{ dirname, @errorName(err) });
};
break :blk .{ .path = dirname, .handle = dir };
} else |err| switch (err) {
error.FileNotFound => {
dirname = fs.path.dirname(dirname) orelse {
std.log.info("{s}", .{
\\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_root.path orelse cwd_path;
const build_root = try findBuildRoot(arena, .{
.cwd_path = cwd_path,
.build_file = build_file,
});
child_argv.items[argv_index_build_file] = build_root.directory.path orelse cwd_path;
var global_cache_directory: Compilation.Directory = l: {
const p = override_global_cache_dir orelse try introspect.resolveGlobalCacheDir(arena);
@ -5170,9 +5102,9 @@ pub fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !voi
.path = local_cache_dir_path,
};
}
const cache_dir_path = try build_root.join(arena, &[_][]const u8{"zig-cache"});
const cache_dir_path = try build_root.directory.join(arena, &[_][]const u8{"zig-cache"});
break :l .{
.handle = try build_root.handle.makeOpenPath("zig-cache", .{}),
.handle = try build_root.directory.handle.makeOpenPath("zig-cache", .{}),
.path = cache_dir_path,
};
};
@ -5215,8 +5147,8 @@ pub fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !voi
};
var build_mod: Package.Module = .{
.root = .{ .root_dir = build_root },
.root_src_path = build_zig_basename,
.root = .{ .root_dir = build_root.directory },
.root_src_path = build_root.build_zig_basename,
.fully_qualified_name = "root.@build",
};
if (build_options.only_core_functionality) {
@ -6904,40 +6836,40 @@ const ClangSearchSanitizer = struct {
.I => {
if (m.I) return;
m.I = true;
if (m.isystem) std.log.warn(wtxt, .{ dir, "I", "isystem" });
if (m.idirafter) std.log.warn(wtxt, .{ dir, "I", "idirafter" });
if (m.iframework) std.log.warn(wtxt, .{ dir, "I", "iframework" });
if (m.isystem) warn(wtxt, .{ dir, "I", "isystem" });
if (m.idirafter) warn(wtxt, .{ dir, "I", "idirafter" });
if (m.iframework) warn(wtxt, .{ dir, "I", "iframework" });
},
.isystem => {
if (m.isystem) return;
m.isystem = true;
if (m.I) std.log.warn(wtxt, .{ dir, "isystem", "I" });
if (m.idirafter) std.log.warn(wtxt, .{ dir, "isystem", "idirafter" });
if (m.iframework) std.log.warn(wtxt, .{ dir, "isystem", "iframework" });
if (m.I) warn(wtxt, .{ dir, "isystem", "I" });
if (m.idirafter) warn(wtxt, .{ dir, "isystem", "idirafter" });
if (m.iframework) warn(wtxt, .{ dir, "isystem", "iframework" });
},
.iwithsysroot => {
if (m.iwithsysroot) return;
m.iwithsysroot = true;
if (m.iframeworkwithsysroot) std.log.warn(wtxt, .{ dir, "iwithsysroot", "iframeworkwithsysroot" });
if (m.iframeworkwithsysroot) warn(wtxt, .{ dir, "iwithsysroot", "iframeworkwithsysroot" });
},
.idirafter => {
if (m.idirafter) return;
m.idirafter = true;
if (m.I) std.log.warn(wtxt, .{ dir, "idirafter", "I" });
if (m.isystem) std.log.warn(wtxt, .{ dir, "idirafter", "isystem" });
if (m.iframework) std.log.warn(wtxt, .{ dir, "idirafter", "iframework" });
if (m.I) warn(wtxt, .{ dir, "idirafter", "I" });
if (m.isystem) warn(wtxt, .{ dir, "idirafter", "isystem" });
if (m.iframework) warn(wtxt, .{ dir, "idirafter", "iframework" });
},
.iframework => {
if (m.iframework) return;
m.iframework = true;
if (m.I) std.log.warn(wtxt, .{ dir, "iframework", "I" });
if (m.isystem) std.log.warn(wtxt, .{ dir, "iframework", "isystem" });
if (m.idirafter) std.log.warn(wtxt, .{ dir, "iframework", "idirafter" });
if (m.I) warn(wtxt, .{ dir, "iframework", "I" });
if (m.isystem) warn(wtxt, .{ dir, "iframework", "isystem" });
if (m.idirafter) warn(wtxt, .{ dir, "iframework", "idirafter" });
},
.iframeworkwithsysroot => {
if (m.iframeworkwithsysroot) return;
m.iframeworkwithsysroot = true;
if (m.iwithsysroot) std.log.warn(wtxt, .{ dir, "iframeworkwithsysroot", "iwithsysroot" });
if (m.iwithsysroot) warn(wtxt, .{ dir, "iframeworkwithsysroot", "iwithsysroot" });
},
}
try self.argv.append(arg);
@ -7097,6 +7029,8 @@ pub const usage_fetch =
\\ -h, --help Print this help and exit
\\ --global-cache-dir [path] Override path to global Zig cache directory
\\ --debug-hash Print verbose hash information to stdout
\\ --save Add the fetched package to build.zig.zon
\\ --save=[name] Add the fetched package to build.zig.zon as name
\\
;
@ -7111,6 +7045,7 @@ fn cmdFetch(
var opt_path_or_url: ?[]const u8 = null;
var override_global_cache_dir: ?[]const u8 = try EnvVar.ZIG_GLOBAL_CACHE_DIR.get(arena);
var debug_hash: bool = false;
var save: union(enum) { no, yes, name: []const u8 } = .no;
{
var i: usize = 0;
@ -7125,10 +7060,12 @@ fn cmdFetch(
if (i + 1 >= args.len) fatal("expected argument after '{s}'", .{arg});
i += 1;
override_global_cache_dir = args[i];
continue;
} else if (mem.eql(u8, arg, "--debug-hash")) {
debug_hash = true;
continue;
} else if (mem.eql(u8, arg, "--save")) {
save = .yes;
} else if (mem.startsWith(u8, arg, "--save=")) {
save = .{ .name = arg["--save=".len..] };
} else {
fatal("unrecognized parameter: '{s}'", .{arg});
}
@ -7214,7 +7151,97 @@ fn cmdFetch(
progress.done = true;
progress.refresh();
try io.getStdOut().writeAll(hex_digest ++ "\n");
const name = switch (save) {
.no => {
try io.getStdOut().writeAll(hex_digest ++ "\n");
return cleanExit();
},
.yes => n: {
const fetched_manifest = fetch.manifest orelse
fatal("unable to determine name; fetched package has no build.zig.zon file", .{});
break :n fetched_manifest.name;
},
.name => |n| n,
};
const cwd_path = try process.getCwdAlloc(arena);
var build_root = try findBuildRoot(arena, .{
.cwd_path = cwd_path,
});
defer build_root.deinit();
// The name to use in case the manifest file needs to be created now.
const init_root_name = fs.path.basename(build_root.directory.path orelse cwd_path);
var manifest, var ast = try loadManifest(gpa, arena, .{
.root_name = init_root_name,
.dir = build_root.directory.handle,
.color = color,
});
defer {
manifest.deinit(gpa);
ast.deinit(gpa);
}
var fixups: Ast.Fixups = .{};
defer fixups.deinit(gpa);
const new_node_init = try std.fmt.allocPrint(arena,
\\.{{
\\ .url = "{}",
\\ .hash = "{}",
\\ }}
, .{
std.zig.fmtEscapes(path_or_url),
std.zig.fmtEscapes(&hex_digest),
});
const new_node_text = try std.fmt.allocPrint(arena, ".{} = {s},\n", .{
std.zig.fmtId(name), new_node_init,
});
const dependencies_init = try std.fmt.allocPrint(arena, ".{{\n {s} }}", .{
new_node_text,
});
const dependencies_text = try std.fmt.allocPrint(arena, ".dependencies = {s},\n", .{
dependencies_init,
});
if (manifest.dependencies.get(name)) |dep| {
if (dep.hash) |h| {
switch (dep.location) {
.url => |u| {
if (mem.eql(u8, h, &hex_digest) and mem.eql(u8, u, path_or_url)) {
std.log.info("existing dependency named '{s}' is up-to-date", .{name});
process.exit(0);
}
},
.path => {},
}
}
warn("overwriting existing dependency named '{s}'", .{name});
try fixups.replace_nodes_with_string.put(gpa, dep.node, new_node_init);
} else if (manifest.dependencies.count() > 0) {
// Add fixup for adding another dependency.
const deps = manifest.dependencies.values();
const last_dep_node = deps[deps.len - 1].node;
try fixups.append_string_after_node.put(gpa, last_dep_node, new_node_text);
} else if (manifest.dependencies_node != 0) {
// Add fixup for replacing the entire dependencies struct.
try fixups.replace_nodes_with_string.put(gpa, manifest.dependencies_node, dependencies_init);
} else {
// Add fixup for adding dependencies struct.
try fixups.append_string_after_node.put(gpa, manifest.version_node, dependencies_text);
}
var rendered = std.ArrayList(u8).init(gpa);
defer rendered.deinit();
try ast.renderToArrayList(&rendered, fixups);
build_root.directory.handle.writeFile(Package.Manifest.basename, rendered.items) catch |err| {
fatal("unable to write {s} file: {s}", .{ Package.Manifest.basename, @errorName(err) });
};
return cleanExit();
}
@ -7279,3 +7306,211 @@ fn defaultWasmEntryName(exec_model: ?std.builtin.WasiExecModel) []const u8 {
}
return "_start";
}
const BuildRoot = struct {
directory: Cache.Directory,
build_zig_basename: []const u8,
cleanup_build_dir: ?fs.Dir,
fn deinit(br: *BuildRoot) void {
if (br.cleanup_build_dir) |*dir| dir.close();
br.* = undefined;
}
};
const FindBuildRootOptions = struct {
build_file: ?[]const u8 = null,
cwd_path: ?[]const u8 = null,
};
fn findBuildRoot(arena: Allocator, options: FindBuildRootOptions) !BuildRoot {
const cwd_path = options.cwd_path orelse try process.getCwdAlloc(arena);
const build_zig_basename = if (options.build_file) |bf|
fs.path.basename(bf)
else
Package.build_zig_basename;
if (options.build_file) |bf| {
if (fs.path.dirname(bf)) |dirname| {
const dir = fs.cwd().openDir(dirname, .{}) catch |err| {
fatal("unable to open directory to build file from argument 'build-file', '{s}': {s}", .{ dirname, @errorName(err) });
};
return .{
.build_zig_basename = build_zig_basename,
.directory = .{ .path = dirname, .handle = dir },
.cleanup_build_dir = dir,
};
}
return .{
.build_zig_basename = build_zig_basename,
.directory = .{ .path = null, .handle = fs.cwd() },
.cleanup_build_dir = null,
};
}
// 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 = fs.cwd().openDir(dirname, .{}) catch |err| {
fatal("unable to open directory while searching for build.zig file, '{s}': {s}", .{ dirname, @errorName(err) });
};
return .{
.build_zig_basename = build_zig_basename,
.directory = .{
.path = dirname,
.handle = dir,
},
.cleanup_build_dir = dir,
};
} else |err| switch (err) {
error.FileNotFound => {
dirname = fs.path.dirname(dirname) orelse {
std.log.info("initialize {s} template file with 'zig init'", .{
Package.build_zig_basename,
});
std.log.info("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,
}
}
}
const LoadManifestOptions = struct {
root_name: []const u8,
dir: fs.Dir,
color: Color,
};
fn loadManifest(
gpa: Allocator,
arena: Allocator,
options: LoadManifestOptions,
) !struct { Package.Manifest, Ast } {
const manifest_bytes = while (true) {
break options.dir.readFileAllocOptions(
arena,
Package.Manifest.basename,
Package.Manifest.max_bytes,
null,
1,
0,
) catch |err| switch (err) {
error.FileNotFound => {
var templates = findTemplates(gpa, arena);
defer templates.deinit();
templates.write(arena, options.dir, options.root_name, Package.Manifest.basename) catch |e| {
fatal("unable to write {s}: {s}", .{
Package.Manifest.basename, @errorName(e),
});
};
continue;
},
else => |e| fatal("unable to load {s}: {s}", .{
Package.Manifest.basename, @errorName(e),
}),
};
};
var ast = try Ast.parse(gpa, manifest_bytes, .zon);
errdefer ast.deinit(gpa);
if (ast.errors.len > 0) {
try printAstErrorsToStderr(gpa, ast, Package.Manifest.basename, options.color);
process.exit(2);
}
var manifest = try Package.Manifest.parse(gpa, ast, .{});
errdefer manifest.deinit(gpa);
if (manifest.errors.len > 0) {
var wip_errors: std.zig.ErrorBundle.Wip = undefined;
try wip_errors.init(gpa);
defer wip_errors.deinit();
const src_path = try wip_errors.addString(Package.Manifest.basename);
try manifest.copyErrorsIntoBundle(ast, src_path, &wip_errors);
var error_bundle = try wip_errors.toOwnedBundle("");
defer error_bundle.deinit(gpa);
error_bundle.renderToStdErr(renderOptions(options.color));
process.exit(2);
}
return .{ manifest, ast };
}
const Templates = struct {
zig_lib_directory: Cache.Directory,
dir: fs.Dir,
buffer: std.ArrayList(u8),
fn deinit(templates: *Templates) void {
templates.zig_lib_directory.handle.close();
templates.dir.close();
templates.buffer.deinit();
templates.* = undefined;
}
fn write(
templates: *Templates,
arena: Allocator,
out_dir: fs.Dir,
root_name: []const u8,
template_path: []const u8,
) !void {
if (fs.path.dirname(template_path)) |dirname| {
out_dir.makePath(dirname) catch |err| {
fatal("unable to make path '{s}': {s}", .{ dirname, @errorName(err) });
};
}
const max_bytes = 10 * 1024 * 1024;
const contents = templates.dir.readFileAlloc(arena, template_path, max_bytes) catch |err| {
fatal("unable to read template file '{s}': {s}", .{ template_path, @errorName(err) });
};
templates.buffer.clearRetainingCapacity();
try templates.buffer.ensureUnusedCapacity(contents.len);
for (contents) |c| {
if (c == '$') {
try templates.buffer.appendSlice(root_name);
} else {
try templates.buffer.append(c);
}
}
return out_dir.writeFile2(.{
.sub_path = template_path,
.data = templates.buffer.items,
.flags = .{ .exclusive = true },
});
}
};
fn findTemplates(gpa: Allocator, arena: Allocator) Templates {
const self_exe_path = introspect.findZigExePath(arena) catch |err| {
fatal("unable to find self exe path: {s}", .{@errorName(err)});
};
var zig_lib_directory = introspect.findZigLibDirFromSelfExe(arena, self_exe_path) catch |err| {
fatal("unable to find zig installation directory: {s}", .{@errorName(err)});
};
const s = fs.path.sep_str;
const template_sub_path = "init";
const template_dir = zig_lib_directory.handle.openDir(template_sub_path, .{}) catch |err| {
const path = zig_lib_directory.path orelse ".";
fatal("unable to open zig project template directory '{s}{s}{s}': {s}", .{
path, s, template_sub_path, @errorName(err),
});
};
return .{
.zig_lib_directory = zig_lib_directory,
.dir = template_dir,
.buffer = std.ArrayList(u8).init(gpa),
};
}