Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add .no_unpack = PATH and --no-unpack to build.zig.zon and zig fetch #22042

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions doc/build.zig.zon.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,13 @@ Boolean.
When this is set to `true`, a package is declared to be lazily fetched. This
makes the dependency only get fetched if it is actually used.

#### `no_unpack`

String. Optional.

When set, disables unpacking the package and instead saves it as a single file
using the given string as its file path.

### `paths`

List. Required.
Expand Down
4 changes: 4 additions & 0 deletions lib/init/build.zig.zon
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,10 @@
// // fetched. This makes the dependency only get fetched if it is
// // actually used.
// .lazy = false,
//
// // When set, disables unpacking the package and instead saves it as a single file
// // using the given string as its file path.
// //.no_unpack = "an/example/package",
//},
},

Expand Down
49 changes: 44 additions & 5 deletions src/Package/Fetch.zig
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ location_tok: std.zig.Ast.TokenIndex,
hash_tok: std.zig.Ast.TokenIndex,
name_tok: std.zig.Ast.TokenIndex,
lazy_status: LazyStatus,
no_unpack: ?[]const u8,
parent_package_root: Cache.Path,
parent_manifest_ast: ?*const std.zig.Ast,
prog_node: std.Progress.Node,
Expand Down Expand Up @@ -351,12 +352,12 @@ pub fn run(f: *Fetch) RunError!void {
.path_or_url => |path_or_url| {
if (fs.cwd().openDir(path_or_url, .{ .iterate = true })) |dir| {
var resource: Resource = .{ .dir = dir };
return f.runResource(path_or_url, &resource, null);
return f.runResource(path_or_url, &resource, null, f.no_unpack);
} else |dir_err| {
const file_err = if (dir_err == error.NotDir) e: {
if (fs.cwd().openFile(path_or_url, .{})) |file| {
var resource: Resource = .{ .file = file };
return f.runResource(path_or_url, &resource, null);
return f.runResource(path_or_url, &resource, null, f.no_unpack);
} else |err| break :e err;
} else dir_err;

Expand All @@ -368,7 +369,7 @@ pub fn run(f: *Fetch) RunError!void {
};
var server_header_buffer: [header_buffer_size]u8 = undefined;
var resource = try f.initResource(uri, &server_header_buffer);
return f.runResource(try uri.path.toRawMaybeAlloc(arena), &resource, null);
return f.runResource(try uri.path.toRawMaybeAlloc(arena), &resource, null, f.no_unpack);
}
},
};
Expand Down Expand Up @@ -434,7 +435,7 @@ pub fn run(f: *Fetch) RunError!void {
);
var server_header_buffer: [header_buffer_size]u8 = undefined;
var resource = try f.initResource(uri, &server_header_buffer);
return f.runResource(try uri.path.toRawMaybeAlloc(arena), &resource, remote.hash);
return f.runResource(try uri.path.toRawMaybeAlloc(arena), &resource, remote.hash, f.no_unpack);
}

pub fn deinit(f: *Fetch) void {
Expand All @@ -448,6 +449,7 @@ fn runResource(
uri_path: []const u8,
resource: *Resource,
remote_hash: ?Package.Hash,
no_unpack: ?[]const u8,
) RunError!void {
defer resource.deinit();
const arena = f.arena.allocator();
Expand Down Expand Up @@ -478,7 +480,7 @@ fn runResource(
defer tmp_directory.handle.close();

// Fetch and unpack a resource into a temporary directory.
var unpack_result = try unpackResource(f, resource, uri_path, tmp_directory);
var unpack_result = try unpackResource(f, resource, uri_path, tmp_directory, no_unpack);

var pkg_path: Cache.Path = .{ .root_dir = tmp_directory, .sub_path = unpack_result.root_dir };

Expand Down Expand Up @@ -746,6 +748,7 @@ fn queueJobsForDeps(f: *Fetch) RunError!void {
.hash_tok = dep.hash_tok,
.name_tok = dep.name_tok,
.lazy_status = if (dep.lazy) .available else .eager,
.no_unpack = dep.no_unpack,
.parent_package_root = f.package_root,
.parent_manifest_ast = &f.manifest_ast,
.prog_node = f.prog_node,
Expand Down Expand Up @@ -1075,8 +1078,43 @@ fn unpackResource(
resource: *Resource,
uri_path: []const u8,
tmp_directory: Cache.Directory,
no_unpack: ?[]const u8,
) RunError!UnpackResult {
const eb = &f.error_bundle;

if (no_unpack) |sub_path| {
if (std.fs.path.dirname(sub_path)) |sub_dir| {
tmp_directory.handle.makePath(sub_dir) catch |e| return f.fail(
f.location_tok,
try eb.printString(
"failed to create temporary path '{}{s}' with {s}",
.{ tmp_directory, sub_dir, @errorName(e) },
),
);
}
var out_file = tmp_directory.handle.createFile(
sub_path,
.{},
) catch |err| return f.fail(f.location_tok, try eb.printString(
"failed to create temporary file: {s}",
.{@errorName(err)},
));
defer out_file.close();
var buf: [4096]u8 = undefined;
while (true) {
const len = resource.reader().readAll(&buf) catch |err| return f.fail(f.location_tok, try eb.printString(
"read stream failed: {s}",
.{@errorName(err)},
));
if (len == 0) break;
out_file.writer().writeAll(buf[0..len]) catch |err| return f.fail(f.location_tok, try eb.printString(
"write temporary file failed: {s}",
.{@errorName(err)},
));
}
return .{};
}

const file_type = switch (resource.*) {
.file => FileType.fromPath(uri_path) orelse
return f.fail(f.location_tok, try eb.printString("unknown file type: '{s}'", .{uri_path})),
Expand Down Expand Up @@ -2317,6 +2355,7 @@ const TestFetchBuilder = struct {
.hash_tok = 0,
.name_tok = 0,
.lazy_status = .eager,
.no_unpack = null,
.parent_package_root = Cache.Path{ .root_dir = Cache.Directory{ .handle = cache_dir, .path = null } },
.parent_manifest_ast = null,
.prog_node = std.Progress.Node.none,
Expand Down
24 changes: 24 additions & 0 deletions src/Package/Manifest.zig
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ pub const Dependency = struct {
node: Ast.Node.Index,
name_tok: Ast.TokenIndex,
lazy: bool,
no_unpack: ?[]const u8,

pub const Location = union(enum) {
url: []const u8,
Expand Down Expand Up @@ -141,6 +142,15 @@ pub fn copyErrorsIntoBundle(
}
}

pub fn getNoUnpackProblem(no_unpack: []const u8) ?[]const u8 {
if (std.mem.indexOfScalar(u8, no_unpack, '\\') != null) return "may not contain backslashes";
var it = std.fs.path.ComponentIterator(.posix, u8).init(no_unpack) catch return "is not a valid path";
while (it.next()) |entry| {
if (std.mem.eql(u8, entry.name, "..")) return "may not contain '..' components";
}
return null;
}

const Parse = struct {
gpa: Allocator,
ast: Ast,
Expand Down Expand Up @@ -287,6 +297,7 @@ const Parse = struct {
.node = node,
.name_tok = 0,
.lazy = false,
.no_unpack = null,
};
var has_location = false;

Expand Down Expand Up @@ -335,6 +346,19 @@ const Parse = struct {
error.ParseFailure => continue,
else => |e| return e,
};
} else if (mem.eql(u8, field_name, "no_unpack")) {
if (dep.no_unpack != null) return fail(p, main_tokens[field_init], "dependency should specify only one 'no_unpack' field", .{});

dep.no_unpack = parseString(p, field_init) catch |err| switch (err) {
error.ParseFailure => continue,
else => |e| return e,
};
if (getNoUnpackProblem(dep.no_unpack.?)) |problem| return fail(
p,
main_tokens[field_init],
"no_unpack value {s}",
.{problem},
);
} else {
// Ignore unknown fields so that we can add fields in future zig
// versions without breaking older zig versions.
Expand Down
14 changes: 14 additions & 0 deletions src/main.zig
Original file line number Diff line number Diff line change
Expand Up @@ -5219,6 +5219,7 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
.hash_tok = 0,
.name_tok = 0,
.lazy_status = .eager,
.no_unpack = null,
.parent_package_root = build_mod.root,
.parent_manifest_ast = null,
.prog_node = fetch_prog_node,
Expand Down Expand Up @@ -7022,6 +7023,7 @@ const usage_fetch =
\\ --save=[name] Add the fetched package to build.zig.zon as name
\\ --save-exact Add the fetched package to build.zig.zon, storing the URL verbatim
\\ --save-exact=[name] Add the fetched package to build.zig.zon as name, storing the URL verbatim
\\ --no-unpack[=path] Don't unpack the package, save to path if gven
\\
;

Expand All @@ -7043,6 +7045,7 @@ fn cmdFetch(
yes: ?[]const u8,
exact: ?[]const u8,
} = .no;
var no_unpack: ?union(enum) { url_basename, with_path: []const u8 } = null;

{
var i: usize = 0;
Expand All @@ -7067,6 +7070,13 @@ fn cmdFetch(
save = .{ .exact = null };
} else if (mem.startsWith(u8, arg, "--save-exact=")) {
save = .{ .exact = arg["--save-exact=".len..] };
} else if (mem.eql(u8, arg, "--no-unpack")) {
no_unpack = .url_basename;
} else if (mem.startsWith(u8, arg, "--no-unpack=")) {
const path = arg["--no-unpack=".len..];
if (Package.Manifest.getNoUnpackProblem(path)) |problem|
fatal("--no-unpack path {s}", .{problem});
no_unpack = .{ .with_path = path };
} else {
fatal("unrecognized parameter: '{s}'", .{arg});
}
Expand Down Expand Up @@ -7121,6 +7131,10 @@ fn cmdFetch(
.hash_tok = 0,
.name_tok = 0,
.lazy_status = .eager,
.no_unpack = if (no_unpack) |n| switch (n) {
.url_basename => std.fs.path.basename(path_or_url),
.with_path => |path| path,
} else null,
.parent_package_root = undefined,
.parent_manifest_ast = null,
.prog_node = root_prog_node,
Expand Down
3 changes: 3 additions & 0 deletions test/standalone/build.zig.zon
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,9 @@
.@"extern" = .{
.path = "extern",
},
.fetch = .{
.path = "fetch",
},
.dep_diamond = .{
.path = "dep_diamond",
},
Expand Down
105 changes: 105 additions & 0 deletions test/standalone/fetch/build.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
const std = @import("std");

pub fn build(b: *std.Build) !void {
const test_step = b.step("test", "Test it");
b.default_step = test_step;

const codegen_exe = b.addExecutable(.{
.name = "codegen",
.target = b.graph.host,
.root_source_file = b.path("codegen.zig"),
});

{
const run_codegen = b.addRunArtifact(codegen_exe);
const example_dir = run_codegen.addOutputDirectoryArg("example");
run_codegen.addArg("N-V-__8AABkAAAD5UlwyjuMCRESo0AsKtkhnbeeaA8Ux-LTi");
run_codegen.addArg("subpath/example_dep_file.txt");
const run = b.addSystemCommand(&.{
b.graph.zig_exe,
"build",
"install",
"--build-file",
});
run.addFileArg(example_dir.path(b, "build.zig"));
run.addArg("--prefix");
const install_dir = run.addOutputDirectoryArg("install");
const check_file = b.addCheckFile(install_dir.path(b, "example_dep_file.txt"), .{
.expected_exact = "This is an example file.\n",
});
test_step.dependOn(&check_file.step);
}

{
const run_codegen = b.addRunArtifact(codegen_exe);
const example_dir = run_codegen.addOutputDirectoryArg("example");
run_codegen.addArg("N-V-__8AABkAAAD5UlwyjuMCRESo0AsKtkhnbeeaA8Ux-LTi");
run_codegen.addArg("../foo.txt");
const run = b.addSystemCommand(&.{
b.graph.zig_exe,
"build",
"install",
"--build-file",
});
run.addFileArg(example_dir.path(b, "build.zig"));
run.addCheck(.{ .expect_stderr_match = "error: no_unpack value may not contain '..' components" });
test_step.dependOn(&run.step);
}

{
const run_codegen = b.addRunArtifact(codegen_exe);
const example_dir = run_codegen.addOutputDirectoryArg("example");
run_codegen.addArg("N-V-__8AABkAAAD5UlwyjuMCRESo0AsKtkhnbeeaA8Ux-LTi");
run_codegen.addArg("foo\\\\bar.txt");
const run = b.addSystemCommand(&.{
b.graph.zig_exe,
"build",
"install",
"--build-file",
});
run.addFileArg(example_dir.path(b, "build.zig"));
run.addCheck(.{ .expect_stderr_match = "error: no_unpack value may not contain backslashes" });
test_step.dependOn(&run.step);
}

{
const run = b.addSystemCommand(&.{
b.graph.zig_exe,
"fetch",
"--no-unpack",
});
run.addFileArg(b.path("example/example_dep_file.txt"));
run.expectStdOutEqual("N-V-__8AABkAAAAPaKynDrx2BXIAr0Nqq1cg7FOngHE8XcYU\n");
test_step.dependOn(&run.step);
}
{
const run = b.addSystemCommand(&.{
b.graph.zig_exe,
"fetch",
"--no-unpack=subpath/example_dep_file.txt",
});
run.addFileArg(b.path("example/example_dep_file.txt"));
run.expectStdOutEqual("N-V-__8AABkAAAD5UlwyjuMCRESo0AsKtkhnbeeaA8Ux-LTi\n");
test_step.dependOn(&run.step);
}
{
const run = b.addSystemCommand(&.{
b.graph.zig_exe,
"fetch",
"--no-unpack=subpath/../example_dep_file.txt",
});
run.addFileArg(b.path("example/example_dep_file.txt"));
run.addCheck(.{ .expect_stderr_match = "error: --no-unpack path may not contain '..' components" });
test_step.dependOn(&run.step);
}
{
const run = b.addSystemCommand(&.{
b.graph.zig_exe,
"fetch",
"--no-unpack=subpath\\example_dep_file.txt",
});
run.addFileArg(b.path("example/example_dep_file.txt"));
run.addCheck(.{ .expect_stderr_match = "error: --no-unpack path may not contain backslashes" });
test_step.dependOn(&run.step);
}
}
Loading