| author | |
| committer | |
| log | 0fc7c9f57c0042cfe6dab9deb3b5fe2f9404d744 |
| tree | f554469401cd9df8a762a098dc0091a79e0caf7d |
| parent | a70307e7ffc608643bbd940796eaeb5bca6bbc8f |
mainly this addresses the following use case:
1. Someone creates a template with build.zig.zon, id field included
(note that zig init does not create this problem since it generates
fresh id every time it runs).
2. User A uses the template, changing package name to "example" but not
id field.
3. User B uses the same template, changing package name also to
"example", also not changing the id field.
Here, both packages have unintentional conflicting logical ids.
By making the field a combination of name checksum + random id, this
accident is avoided. "nonce" is an OK name for this.
Also relaxes errors on remote packages when using `zig fetch`.7 files changed, 100 insertions(+), 59 deletions(-)
build.zig.zon+1-1| ... | @@ -12,5 +12,5 @@ | ... | @@ -12,5 +12,5 @@ |
| 12 | }, | 12 | }, |
| 13 | }, | 13 | }, |
| 14 | .paths = .{""}, | 14 | .paths = .{""}, |
| 15 | .id = 0x1cb6, | 15 | .nonce = 0xc1ce10810000f013, |
| 16 | } | 16 | } |
doc/build.zig.zon.md+17-8| ... | @@ -22,21 +22,30 @@ Zig package namespace. | ... | @@ -22,21 +22,30 @@ Zig package namespace. |
| 22 | 22 | ||
| 23 | Must be a valid bare Zig identifier (don't `@` me), limited to 32 bytes. | 23 | Must be a valid bare Zig identifier (don't `@` me), limited to 32 bytes. |
| 24 | 24 | ||
| 25 | ### `id` | 25 | ### `nonce` |
| 26 | 26 | ||
| 27 | Together with name, this represents a globally unique package identifier. This | 27 | Together with name, this represents a globally unique package identifier. This |
| 28 | field should be initialized with a 16-bit random number when the package is | 28 | field is auto-initialized by the toolchain when the package is first created, |
| 29 | first created, and then *never change*. This allows Zig to unambiguously detect | 29 | and then *never changes*. This allows Zig to unambiguously detect when one |
| 30 | when one package is an updated version of another. | 30 | package is an updated version of another. |
| 31 | 31 | ||
| 32 | When forking a Zig project, this id should be regenerated with a new random | 32 | When forking a Zig project, this nonce should be regenerated if the upstream |
| 33 | number if the upstream project is still maintained. Otherwise, the fork is | 33 | project is still maintained. Otherwise, the fork is *hostile*, attempting to |
| 34 | *hostile*, attempting to take control over the original project's identity. | 34 | take control over the original project's identity. The nonce can be regenerated |
| 35 | by deleting the field and running `zig build`. | ||
| 35 | 36 | ||
| 36 | `0x0000` is invalid because it obviously means a random number wasn't used. | 37 | This 64-bit integer is the combination of a 16-bit id component, a 32-bit |
| 38 | checksum, and 16 bits of reserved zeroes. | ||
| 39 | |||
| 40 | The id component within the nonce has these restrictions: | ||
| 41 | |||
| 42 | `0x0000` is reserved for legacy packages. | ||
| 37 | 43 | ||
| 38 | `0xffff` is reserved to represent "naked" packages. | 44 | `0xffff` is reserved to represent "naked" packages. |
| 39 | 45 | ||
| 46 | The checksum is computed from `name` and serves to protect Zig users from | ||
| 47 | accidental id collisions. | ||
| 48 | |||
| 40 | ### `version` | 49 | ### `version` |
| 41 | 50 | ||
| 42 | String. Required. | 51 | String. Required. |
lib/init/build.zig.zon+11-10| ... | @@ -13,17 +13,18 @@ | ... | @@ -13,17 +13,18 @@ |
| 13 | .version = "0.0.0", | 13 | .version = "0.0.0", |
| 14 | 14 | ||
| 15 | // Together with name, this represents a globally unique package | 15 | // Together with name, this represents a globally unique package |
| 16 | // identifier. This field should be initialized with a 16-bit random number | 16 | // identifier. This field is generated by the Zig toolchain when the |
| 17 | // when the package is first created, and then *never change*. This allows | 17 | // package is first created, and then *never changes*. This allows |
| 18 | // unambiguous detection when one package is an updated version of another. | 18 | // unambiguous detection of one package being an updated version of |
| 19 | // another. | ||
| 19 | // | 20 | // |
| 20 | // When forking a Zig project, this id should be regenerated with a new | 21 | // When forking a Zig project, this id should be regenerated (delete the |
| 21 | // random number if the upstream project is still maintained. Otherwise, | 22 | // field and run `zig build`) if the upstream project is still maintained. |
| 22 | // the fork is *hostile*, attempting to take control over the original | 23 | // Otherwise, the fork is *hostile*, attempting to take control over the |
| 23 | // project's identity. Thus it is recommended to leave the comment on the | 24 | // original project's identity. Thus it is recommended to leave the comment |
| 24 | // following line intact, so that it shows up in code reviews that modify | 25 | // on the following line intact, so that it shows up in code reviews that |
| 25 | // the field. | 26 | // modify the field. |
| 26 | .id = $i, // Changing this has security and trust implications. | 27 | .nonce = $i, // Changing this has security and trust implications. |
| 27 | 28 | ||
| 28 | // Tracks the earliest Zig version that the package considers to be a | 29 | // Tracks the earliest Zig version that the package considers to be a |
| 29 | // supported use case. | 30 | // supported use case. |
src/Package.zig+27-6| ... | @@ -10,9 +10,29 @@ pub const multihash_len = 1 + 1 + Hash.Algo.digest_length; | ... | @@ -10,9 +10,29 @@ pub const multihash_len = 1 + 1 + Hash.Algo.digest_length; |
| 10 | pub const multihash_hex_digest_len = 2 * multihash_len; | 10 | pub const multihash_hex_digest_len = 2 * multihash_len; |
| 11 | pub const MultiHashHexDigest = [multihash_hex_digest_len]u8; | 11 | pub const MultiHashHexDigest = [multihash_hex_digest_len]u8; |
| 12 | 12 | ||
| 13 | pub fn randomId() u16 { | 13 | pub const Nonce = packed struct(u64) { |
| 14 | return std.crypto.random.intRangeLessThan(u16, 0x0001, 0xffff); | 14 | id: u16, |
| 15 | } | 15 | reserved: u16 = 0, |
| 16 | checksum: u32, | ||
| 17 | |||
| 18 | pub fn generate(name: []const u8) Nonce { | ||
| 19 | return .{ | ||
| 20 | .id = std.crypto.random.intRangeLessThan(u16, 0x0001, 0xffff), | ||
| 21 | .checksum = std.hash.Crc32.hash(name), | ||
| 22 | }; | ||
| 23 | } | ||
| 24 | |||
| 25 | pub fn validate(n: Nonce, name: []const u8) bool { | ||
| 26 | switch (n.id) { | ||
| 27 | 0x0000, 0xffff => return false, | ||
| 28 | else => return std.hash.Crc32.hash(name) == n.checksum, | ||
| 29 | } | ||
| 30 | } | ||
| 31 | |||
| 32 | pub fn int(n: Nonce) u64 { | ||
| 33 | return @bitCast(n); | ||
| 34 | } | ||
| 35 | }; | ||
| 16 | 36 | ||
| 17 | /// A user-readable, file system safe hash that identifies an exact package | 37 | /// A user-readable, file system safe hash that identifies an exact package |
| 18 | /// snapshot, including file contents. | 38 | /// snapshot, including file contents. |
| ... | @@ -72,9 +92,10 @@ pub const Hash = struct { | ... | @@ -72,9 +92,10 @@ pub const Hash = struct { |
| 72 | } | 92 | } |
| 73 | 93 | ||
| 74 | /// Produces "$name-$semver-$hashplus". | 94 | /// Produces "$name-$semver-$hashplus". |
| 75 | /// * name is the name field from build.zig.zon, truncated at 32 bytes and must | 95 | /// * name is the name field from build.zig.zon, asserted to be at most 32 |
| 76 | /// be a valid zig identifier | 96 | /// bytes and assumed be a valid zig identifier |
| 77 | /// * semver is the version field from build.zig.zon, truncated at 32 bytes | 97 | /// * semver is the version field from build.zig.zon, asserted to be at |
| 98 | /// most 32 bytes | ||
| 78 | /// * hashplus is the following 39-byte array, base64 encoded using -_ to make | 99 | /// * hashplus is the following 39-byte array, base64 encoded using -_ to make |
| 79 | /// it filesystem safe: | 100 | /// it filesystem safe: |
| 80 | /// - (2 bytes) LE u16 Package ID | 101 | /// - (2 bytes) LE u16 Package ID |
src/Package/Fetch.zig+9-3| ... | @@ -44,6 +44,8 @@ omit_missing_hash_error: bool, | ... | @@ -44,6 +44,8 @@ omit_missing_hash_error: bool, |
| 44 | /// which specifies inclusion rules. This is intended to be true for the first | 44 | /// which specifies inclusion rules. This is intended to be true for the first |
| 45 | /// fetch task and false for the recursive dependencies. | 45 | /// fetch task and false for the recursive dependencies. |
| 46 | allow_missing_paths_field: bool, | 46 | allow_missing_paths_field: bool, |
| 47 | allow_missing_nonce: bool, | ||
| 48 | allow_name_string: bool, | ||
| 47 | /// If true and URL points to a Git repository, will use the latest commit. | 49 | /// If true and URL points to a Git repository, will use the latest commit. |
| 48 | use_latest_commit: bool, | 50 | use_latest_commit: bool, |
| 49 | 51 | ||
| ... | @@ -372,7 +374,7 @@ pub fn run(f: *Fetch) RunError!void { | ... | @@ -372,7 +374,7 @@ pub fn run(f: *Fetch) RunError!void { |
| 372 | }; | 374 | }; |
| 373 | 375 | ||
| 374 | if (remote.hash) |expected_hash| { | 376 | if (remote.hash) |expected_hash| { |
| 375 | var prefixed_pkg_sub_path_buffer: [100]u8 = undefined; | 377 | var prefixed_pkg_sub_path_buffer: [Package.Hash.max_len + 2]u8 = undefined; |
| 376 | prefixed_pkg_sub_path_buffer[0] = 'p'; | 378 | prefixed_pkg_sub_path_buffer[0] = 'p'; |
| 377 | prefixed_pkg_sub_path_buffer[1] = fs.path.sep; | 379 | prefixed_pkg_sub_path_buffer[1] = fs.path.sep; |
| 378 | const hash_slice = expected_hash.toSlice(); | 380 | const hash_slice = expected_hash.toSlice(); |
| ... | @@ -647,8 +649,8 @@ fn loadManifest(f: *Fetch, pkg_root: Cache.Path) RunError!void { | ... | @@ -647,8 +649,8 @@ fn loadManifest(f: *Fetch, pkg_root: Cache.Path) RunError!void { |
| 647 | 649 | ||
| 648 | f.manifest = try Manifest.parse(arena, ast.*, .{ | 650 | f.manifest = try Manifest.parse(arena, ast.*, .{ |
| 649 | .allow_missing_paths_field = f.allow_missing_paths_field, | 651 | .allow_missing_paths_field = f.allow_missing_paths_field, |
| 650 | .allow_missing_id = f.allow_missing_paths_field, | 652 | .allow_missing_nonce = f.allow_missing_nonce, |
| 651 | .allow_name_string = f.allow_missing_paths_field, | 653 | .allow_name_string = f.allow_name_string, |
| 652 | }); | 654 | }); |
| 653 | const manifest = &f.manifest.?; | 655 | const manifest = &f.manifest.?; |
| 654 | 656 | ||
| ... | @@ -750,6 +752,8 @@ fn queueJobsForDeps(f: *Fetch) RunError!void { | ... | @@ -750,6 +752,8 @@ fn queueJobsForDeps(f: *Fetch) RunError!void { |
| 750 | .job_queue = f.job_queue, | 752 | .job_queue = f.job_queue, |
| 751 | .omit_missing_hash_error = false, | 753 | .omit_missing_hash_error = false, |
| 752 | .allow_missing_paths_field = true, | 754 | .allow_missing_paths_field = true, |
| 755 | .allow_missing_nonce = true, | ||
| 756 | .allow_name_string = true, | ||
| 753 | .use_latest_commit = false, | 757 | .use_latest_commit = false, |
| 754 | 758 | ||
| 755 | .package_root = undefined, | 759 | .package_root = undefined, |
| ... | @@ -2319,6 +2323,8 @@ const TestFetchBuilder = struct { | ... | @@ -2319,6 +2323,8 @@ const TestFetchBuilder = struct { |
| 2319 | .job_queue = &self.job_queue, | 2323 | .job_queue = &self.job_queue, |
| 2320 | .omit_missing_hash_error = true, | 2324 | .omit_missing_hash_error = true, |
| 2321 | .allow_missing_paths_field = false, | 2325 | .allow_missing_paths_field = false, |
| 2326 | .allow_missing_nonce = true, // so we can keep using the old testdata .tar.gz | ||
| 2327 | .allow_name_string = true, // so we can keep using the old testdata .tar.gz | ||
| 2322 | .use_latest_commit = true, | 2328 | .use_latest_commit = true, |
| 2323 | 2329 | ||
| 2324 | .package_root = undefined, | 2330 | .package_root = undefined, |
src/Package/Manifest.zig+25-25| ... | @@ -52,7 +52,7 @@ pub const ParseOptions = struct { | ... | @@ -52,7 +52,7 @@ pub const ParseOptions = struct { |
| 52 | /// Deprecated, to be removed after 0.14.0 is tagged. | 52 | /// Deprecated, to be removed after 0.14.0 is tagged. |
| 53 | allow_name_string: bool = true, | 53 | allow_name_string: bool = true, |
| 54 | /// Deprecated, to be removed after 0.14.0 is tagged. | 54 | /// Deprecated, to be removed after 0.14.0 is tagged. |
| 55 | allow_missing_id: bool = true, | 55 | allow_missing_nonce: bool = true, |
| 56 | }; | 56 | }; |
| 57 | 57 | ||
| 58 | pub const Error = Allocator.Error; | 58 | pub const Error = Allocator.Error; |
| ... | @@ -81,7 +81,7 @@ pub fn parse(gpa: Allocator, ast: Ast, options: ParseOptions) Error!Manifest { | ... | @@ -81,7 +81,7 @@ pub fn parse(gpa: Allocator, ast: Ast, options: ParseOptions) Error!Manifest { |
| 81 | .paths = .{}, | 81 | .paths = .{}, |
| 82 | .allow_missing_paths_field = options.allow_missing_paths_field, | 82 | .allow_missing_paths_field = options.allow_missing_paths_field, |
| 83 | .allow_name_string = options.allow_name_string, | 83 | .allow_name_string = options.allow_name_string, |
| 84 | .allow_missing_id = options.allow_missing_id, | 84 | .allow_missing_nonce = options.allow_missing_nonce, |
| 85 | .minimum_zig_version = null, | 85 | .minimum_zig_version = null, |
| 86 | .buf = .{}, | 86 | .buf = .{}, |
| 87 | }; | 87 | }; |
| ... | @@ -157,7 +157,7 @@ const Parse = struct { | ... | @@ -157,7 +157,7 @@ const Parse = struct { |
| 157 | paths: std.StringArrayHashMapUnmanaged(void), | 157 | paths: std.StringArrayHashMapUnmanaged(void), |
| 158 | allow_missing_paths_field: bool, | 158 | allow_missing_paths_field: bool, |
| 159 | allow_name_string: bool, | 159 | allow_name_string: bool, |
| 160 | allow_missing_id: bool, | 160 | allow_missing_nonce: bool, |
| 161 | minimum_zig_version: ?std.SemanticVersion, | 161 | minimum_zig_version: ?std.SemanticVersion, |
| 162 | 162 | ||
| 163 | const InnerError = error{ ParseFailure, OutOfMemory }; | 163 | const InnerError = error{ ParseFailure, OutOfMemory }; |
| ... | @@ -175,7 +175,7 @@ const Parse = struct { | ... | @@ -175,7 +175,7 @@ const Parse = struct { |
| 175 | var have_name = false; | 175 | var have_name = false; |
| 176 | var have_version = false; | 176 | var have_version = false; |
| 177 | var have_included_paths = false; | 177 | var have_included_paths = false; |
| 178 | var have_id = false; | 178 | var nonce: ?Package.Nonce = null; |
| 179 | 179 | ||
| 180 | for (struct_init.ast.fields) |field_init| { | 180 | for (struct_init.ast.fields) |field_init| { |
| 181 | const name_token = ast.firstToken(field_init) - 2; | 181 | const name_token = ast.firstToken(field_init) - 2; |
| ... | @@ -192,9 +192,8 @@ const Parse = struct { | ... | @@ -192,9 +192,8 @@ const Parse = struct { |
| 192 | } else if (mem.eql(u8, field_name, "name")) { | 192 | } else if (mem.eql(u8, field_name, "name")) { |
| 193 | p.name = try parseName(p, field_init); | 193 | p.name = try parseName(p, field_init); |
| 194 | have_name = true; | 194 | have_name = true; |
| 195 | } else if (mem.eql(u8, field_name, "id")) { | 195 | } else if (mem.eql(u8, field_name, "nonce")) { |
| 196 | p.id = try parseId(p, field_init); | 196 | nonce = try parseNonce(p, field_init); |
| 197 | have_id = true; | ||
| 198 | } else if (mem.eql(u8, field_name, "version")) { | 197 | } else if (mem.eql(u8, field_name, "version")) { |
| 199 | p.version_node = field_init; | 198 | p.version_node = field_init; |
| 200 | const version_text = try parseString(p, field_init); | 199 | const version_text = try parseString(p, field_init); |
| ... | @@ -218,14 +217,23 @@ const Parse = struct { | ... | @@ -218,14 +217,23 @@ const Parse = struct { |
| 218 | } | 217 | } |
| 219 | } | 218 | } |
| 220 | 219 | ||
| 221 | if (!have_id and !p.allow_missing_id) { | ||
| 222 | try appendError(p, main_token, "missing top-level 'id' field; suggested value: 0x{x}", .{ | ||
| 223 | Package.randomId(), | ||
| 224 | }); | ||
| 225 | } | ||
| 226 | |||
| 227 | if (!have_name) { | 220 | if (!have_name) { |
| 228 | try appendError(p, main_token, "missing top-level 'name' field", .{}); | 221 | try appendError(p, main_token, "missing top-level 'name' field", .{}); |
| 222 | } else { | ||
| 223 | if (nonce) |n| { | ||
| 224 | if (!n.validate(p.name)) { | ||
| 225 | return fail(p, main_token, "invalid nonce: 0x{x}; if this is a new or forked package, use this value: 0x{x}", .{ | ||
| 226 | n.int(), Package.Nonce.generate(p.name).int(), | ||
| 227 | }); | ||
| 228 | } | ||
| 229 | p.id = n.id; | ||
| 230 | } else if (!p.allow_missing_nonce) { | ||
| 231 | try appendError(p, main_token, "missing top-level 'nonce' field; suggested value: 0x{x}", .{ | ||
| 232 | Package.Nonce.generate(p.name).int(), | ||
| 233 | }); | ||
| 234 | } else { | ||
| 235 | p.id = 0; | ||
| 236 | } | ||
| 229 | } | 237 | } |
| 230 | 238 | ||
| 231 | if (!have_version) { | 239 | if (!have_version) { |
| ... | @@ -377,7 +385,7 @@ const Parse = struct { | ... | @@ -377,7 +385,7 @@ const Parse = struct { |
| 377 | } | 385 | } |
| 378 | } | 386 | } |
| 379 | 387 | ||
| 380 | fn parseId(p: *Parse, node: Ast.Node.Index) !u16 { | 388 | fn parseNonce(p: *Parse, node: Ast.Node.Index) !Package.Nonce { |
| 381 | const ast = p.ast; | 389 | const ast = p.ast; |
| 382 | const node_tags = ast.nodes.items(.tag); | 390 | const node_tags = ast.nodes.items(.tag); |
| 383 | const main_tokens = ast.nodes.items(.main_token); | 391 | const main_tokens = ast.nodes.items(.main_token); |
| ... | @@ -387,20 +395,12 @@ const Parse = struct { | ... | @@ -387,20 +395,12 @@ const Parse = struct { |
| 387 | } | 395 | } |
| 388 | const token_bytes = ast.tokenSlice(main_token); | 396 | const token_bytes = ast.tokenSlice(main_token); |
| 389 | const parsed = std.zig.parseNumberLiteral(token_bytes); | 397 | const parsed = std.zig.parseNumberLiteral(token_bytes); |
| 390 | const n = switch (parsed) { | 398 | switch (parsed) { |
| 391 | .int => |n| n, | 399 | .int => |n| return @bitCast(n), |
| 392 | .big_int, .float => return fail(p, main_token, "expected u16 integer literal, found {s}", .{ | 400 | .big_int, .float => return fail(p, main_token, "expected u64 integer literal, found {s}", .{ |
| 393 | @tagName(parsed), | 401 | @tagName(parsed), |
| 394 | }), | 402 | }), |
| 395 | .failure => |err| return fail(p, main_token, "bad integer literal: {s}", .{@tagName(err)}), | 403 | .failure => |err| return fail(p, main_token, "bad integer literal: {s}", .{@tagName(err)}), |
| 396 | }; | ||
| 397 | const casted = std.math.cast(u16, n) orelse | ||
| 398 | return fail(p, main_token, "integer value {d} does not fit into u16", .{n}); | ||
| 399 | switch (casted) { | ||
| 400 | 0x0000, 0xffff => return fail(p, main_token, "id value 0x{x} reserved; use 0x{x} instead", .{ | ||
| 401 | casted, Package.randomId(), | ||
| 402 | }), | ||
| 403 | else => return casted, | ||
| 404 | } | 404 | } |
| 405 | } | 405 | } |
| 406 | 406 |
src/main.zig+10-6| ... | @@ -4752,10 +4752,10 @@ fn cmdInit(gpa: Allocator, arena: Allocator, args: []const []const u8) !void { | ... | @@ -4752,10 +4752,10 @@ fn cmdInit(gpa: Allocator, arena: Allocator, args: []const []const u8) !void { |
| 4752 | }; | 4752 | }; |
| 4753 | var ok_count: usize = 0; | 4753 | var ok_count: usize = 0; |
| 4754 | 4754 | ||
| 4755 | const id = Package.randomId(); | 4755 | const nonce: Package.Nonce = .generate(sanitized_root_name); |
| 4756 | 4756 | ||
| 4757 | for (template_paths) |template_path| { | 4757 | for (template_paths) |template_path| { |
| 4758 | if (templates.write(arena, fs.cwd(), sanitized_root_name, template_path, id)) |_| { | 4758 | if (templates.write(arena, fs.cwd(), sanitized_root_name, template_path, nonce)) |_| { |
| 4759 | std.log.info("created {s}", .{template_path}); | 4759 | std.log.info("created {s}", .{template_path}); |
| 4760 | ok_count += 1; | 4760 | ok_count += 1; |
| 4761 | } else |err| switch (err) { | 4761 | } else |err| switch (err) { |
| ... | @@ -5225,6 +5225,8 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !void { | ... | @@ -5225,6 +5225,8 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !void { |
| 5225 | .job_queue = &job_queue, | 5225 | .job_queue = &job_queue, |
| 5226 | .omit_missing_hash_error = true, | 5226 | .omit_missing_hash_error = true, |
| 5227 | .allow_missing_paths_field = false, | 5227 | .allow_missing_paths_field = false, |
| 5228 | .allow_missing_nonce = false, | ||
| 5229 | .allow_name_string = false, | ||
| 5228 | .use_latest_commit = false, | 5230 | .use_latest_commit = false, |
| 5229 | 5231 | ||
| 5230 | .package_root = undefined, | 5232 | .package_root = undefined, |
| ... | @@ -7125,6 +7127,8 @@ fn cmdFetch( | ... | @@ -7125,6 +7127,8 @@ fn cmdFetch( |
| 7125 | .job_queue = &job_queue, | 7127 | .job_queue = &job_queue, |
| 7126 | .omit_missing_hash_error = true, | 7128 | .omit_missing_hash_error = true, |
| 7127 | .allow_missing_paths_field = false, | 7129 | .allow_missing_paths_field = false, |
| 7130 | .allow_missing_nonce = true, | ||
| 7131 | .allow_name_string = true, | ||
| 7128 | .use_latest_commit = true, | 7132 | .use_latest_commit = true, |
| 7129 | 7133 | ||
| 7130 | .package_root = undefined, | 7134 | .package_root = undefined, |
| ... | @@ -7464,10 +7468,10 @@ fn loadManifest( | ... | @@ -7464,10 +7468,10 @@ fn loadManifest( |
| 7464 | 0, | 7468 | 0, |
| 7465 | ) catch |err| switch (err) { | 7469 | ) catch |err| switch (err) { |
| 7466 | error.FileNotFound => { | 7470 | error.FileNotFound => { |
| 7467 | const id = Package.randomId(); | 7471 | const nonce: Package.Nonce = .generate(options.root_name); |
| 7468 | var templates = findTemplates(gpa, arena); | 7472 | var templates = findTemplates(gpa, arena); |
| 7469 | defer templates.deinit(); | 7473 | defer templates.deinit(); |
| 7470 | templates.write(arena, options.dir, options.root_name, Package.Manifest.basename, id) catch |e| { | 7474 | templates.write(arena, options.dir, options.root_name, Package.Manifest.basename, nonce) catch |e| { |
| 7471 | fatal("unable to write {s}: {s}", .{ | 7475 | fatal("unable to write {s}: {s}", .{ |
| 7472 | Package.Manifest.basename, @errorName(e), | 7476 | Package.Manifest.basename, @errorName(e), |
| 7473 | }); | 7477 | }); |
| ... | @@ -7525,7 +7529,7 @@ const Templates = struct { | ... | @@ -7525,7 +7529,7 @@ const Templates = struct { |
| 7525 | out_dir: fs.Dir, | 7529 | out_dir: fs.Dir, |
| 7526 | root_name: []const u8, | 7530 | root_name: []const u8, |
| 7527 | template_path: []const u8, | 7531 | template_path: []const u8, |
| 7528 | id: u16, | 7532 | nonce: Package.Nonce, |
| 7529 | ) !void { | 7533 | ) !void { |
| 7530 | if (fs.path.dirname(template_path)) |dirname| { | 7534 | if (fs.path.dirname(template_path)) |dirname| { |
| 7531 | out_dir.makePath(dirname) catch |err| { | 7535 | out_dir.makePath(dirname) catch |err| { |
| ... | @@ -7551,7 +7555,7 @@ const Templates = struct { | ... | @@ -7551,7 +7555,7 @@ const Templates = struct { |
| 7551 | state = .start; | 7555 | state = .start; |
| 7552 | }, | 7556 | }, |
| 7553 | 'i' => { | 7557 | 'i' => { |
| 7554 | try templates.buffer.writer().print("0x{x}", .{id}); | 7558 | try templates.buffer.writer().print("0x{x}", .{nonce.int()}); |
| 7555 | state = .start; | 7559 | state = .start; |
| 7556 | }, | 7560 | }, |
| 7557 | 'v' => { | 7561 | 'v' => { |