diff --git a/src/Package/Fetch.zig b/src/Package/Fetch.zig index e4e944d18692..0c3c0abe6122 100644 --- a/src/Package/Fetch.zig +++ b/src/Package/Fetch.zig @@ -44,6 +44,7 @@ omit_missing_hash_error: bool, /// which specifies inclusion rules. This is intended to be true for the first /// fetch task and false for the recursive dependencies. allow_missing_paths_field: bool, +tar_strip_components: u32 = 1, // Above this are fields provided as inputs to `run`. // Below this are fields populated by `run`. @@ -674,6 +675,7 @@ fn queueJobsForDeps(f: *Fetch) RunError!void { prog_names[new_fetch_index] = dep_name; new_fetch_index += 1; f.job_queue.all_fetches.appendAssumeCapacity(new_fetch); + new_fetch.* = .{ .arena = std.heap.ArenaAllocator.init(gpa), .location = location, @@ -687,6 +689,7 @@ fn queueJobsForDeps(f: *Fetch) RunError!void { .job_queue = f.job_queue, .omit_missing_hash_error = false, .allow_missing_paths_field = true, + .tar_strip_components = dep.tar_strip_components, .package_root = undefined, .error_bundle = undefined, @@ -867,9 +870,9 @@ const FileType = enum { try std.testing.expectEqual(@as(?FileType, .@"tar.xz"), fromContentDisposition("ATTACHMENT; filename=\"stuff.tar.xz\"")); try std.testing.expectEqual(@as(?FileType, .@"tar.xz"), fromContentDisposition("attachment; FileName=\"stuff.tar.xz\"")); try std.testing.expectEqual(@as(?FileType, .@"tar.gz"), fromContentDisposition("attachment; FileName*=UTF-8\'\'xyz%2Fstuff.tar.gz")); + try std.testing.expectEqual(@as(?FileType, .tar), fromContentDisposition("attachment; FileName=\"stuff.tar\"")); try std.testing.expect(fromContentDisposition("attachment FileName=\"stuff.tar.gz\"") == null); - try std.testing.expect(fromContentDisposition("attachment; FileName=\"stuff.tar\"") == null); try std.testing.expect(fromContentDisposition("attachment; FileName\"stuff.gz\"") == null); try std.testing.expect(fromContentDisposition("attachment; size=42") == null); try std.testing.expect(fromContentDisposition("inline; size=42") == null); @@ -1152,7 +1155,7 @@ fn unpackTarball(f: *Fetch, out_dir: fs.Dir, reader: anytype) RunError!void { std.tar.pipeToFileSystem(out_dir, reader, .{ .diagnostics = &diagnostics, - .strip_components = 1, + .strip_components = f.tar_strip_components, // TODO: we would like to set this to executable_bit_only, but two // things need to happen before that: // 1. the tar implementation needs to support it diff --git a/src/Package/Manifest.zig b/src/Package/Manifest.zig index 589be91357fa..85561495e08c 100644 --- a/src/Package/Manifest.zig +++ b/src/Package/Manifest.zig @@ -14,6 +14,7 @@ pub const Dependency = struct { node: Ast.Node.Index, name_tok: Ast.TokenIndex, lazy: bool, + tar_strip_components: u32 = 1, pub const Location = union(enum) { url: []const u8, @@ -307,6 +308,7 @@ const Parse = struct { .node = node, .name_tok = 0, .lazy = false, + .tar_strip_components = 1, }; var has_location = false; @@ -352,6 +354,11 @@ const Parse = struct { error.ParseFailure => continue, else => |e| return e, }; + } else if (mem.eql(u8, field_name, "tar_strip_components")) { + dep.tar_strip_components = parseInt(u32, 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. @@ -402,6 +409,22 @@ const Parse = struct { } } + fn parseInt(comptime T: type, p: *Parse, node: Ast.Node.Index) !T { + const ast = p.ast; + const node_tags = ast.nodes.items(.tag); + const main_tokens = ast.nodes.items(.main_token); + if (node_tags[node] != .number_literal) { + return fail(p, main_tokens[node], "expected identifier", .{}); + } + const ident_token = main_tokens[node]; + const token_bytes = ast.tokenSlice(ident_token); + + const value: T = std.fmt.parseInt(T, token_bytes, 10) catch { + return fail(p, ident_token, "expected integer", .{}); + }; + return value; + } + fn parseString(p: *Parse, node: Ast.Node.Index) ![]const u8 { const ast = p.ast; const node_tags = ast.nodes.items(.tag); @@ -703,3 +726,43 @@ test "minimum_zig_version - invalid version" { try testing.expect(manifest.minimum_zig_version == null); } + +test "tar_strip_components" { + const gpa = testing.allocator; + + const example = + \\.{ + \\ .name = "foo", + \\ .version = "3.2.1", + \\ .dependencies = .{ + \\ .bar = .{ + \\ .url = "https://example.com/baz.tar.gz", + \\ .tar_strip_components = 123, + \\ }, + \\ }, + \\ .paths = .{""}, + \\} + ; + + var ast = try Ast.parse(gpa, example, .zon); + defer ast.deinit(gpa); + + try testing.expect(ast.errors.len == 0); + + var manifest = try Manifest.parse(gpa, ast, .{}); + defer manifest.deinit(gpa); + + try testing.expect(manifest.errors.len == 0); + try testing.expectEqualStrings("foo", manifest.name); + + try testing.expect(manifest.dependencies.count() == 1); + try testing.expectEqualStrings("bar", manifest.dependencies.keys()[0]); + try testing.expectEqualStrings( + "https://example.com/baz.tar.gz", + manifest.dependencies.values()[0].location.url, + ); + try testing.expectEqual( + 123, + manifest.dependencies.values()[0].tar_strip_components, + ); +}