From 70d51be3a25bcd889bba3c1f67009801b4c32c70 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sun, 26 Nov 2023 17:03:12 -0700 Subject: [PATCH 1/3] std.zig.render: add ability to append strings after nodes --- lib/std/zig/render.zig | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/lib/std/zig/render.zig b/lib/std/zig/render.zig index 23e5ee83b4..32bea85e07 100644 --- a/lib/std/zig/render.zig +++ b/lib/std/zig/render.zig @@ -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); } } From a0c8d54823e2fd41a9bb2a917d97de667dc9f427 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sun, 26 Nov 2023 17:04:01 -0700 Subject: [PATCH 2/3] add Package.Manifest.copyErrorsIntoBundle --- src/Package/Fetch.zig | 19 +--------------- src/Package/Manifest.zig | 49 ++++++++++++++++++++++++++++++++++++---- 2 files changed, 45 insertions(+), 23 deletions(-) diff --git a/src/Package/Fetch.zig b/src/Package/Fetch.zig index 28b8158453..241d24a414 100644 --- a/src/Package/Fetch.zig +++ b/src/Package/Fetch.zig @@ -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; } } diff --git a/src/Package/Manifest.zig b/src/Package/Manifest.zig index c634d8fe3e..e8b954fb10 100644 --- a/src/Package/Manifest.zig +++ b/src/Package/Manifest.zig @@ -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); From 0c0b69891ad0461a7cd14b09a68def350fbf128e Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sun, 26 Nov 2023 17:04:28 -0700 Subject: [PATCH 3/3] `zig fetch`: add `--save` flag ``` --save Add the fetched package to build.zig.zon --save=[name] Add the fetched package to build.zig.zon as name ``` --- doc/build.zig.zon.md | 2 +- lib/init/build.zig.zon | 7 +- src/main.zig | 449 +++++++++++++++++++++++++++++++---------- 3 files changed, 347 insertions(+), 111 deletions(-) diff --git a/doc/build.zig.zon.md b/doc/build.zig.zon.md index 0bc02d92a6..cf7f69e60d 100644 --- a/doc/build.zig.zon.md +++ b/doc/build.zig.zon.md @@ -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` diff --git a/lib/init/build.zig.zon b/lib/init/build.zig.zon index a16fa12185..4af23a37f4 100644 --- a/lib/init/build.zig.zon +++ b/lib/init/build.zig.zon @@ -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 ` subcommand - // for easily adding dependencies. + // See `zig fetch --save ` 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", }, } diff --git a/src/main.zig b/src/main.zig index bbe3aa176b..3f22d85756 100644 --- a/src/main.zig +++ b/src/main.zig @@ -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), + }; +}