| author | |
| committer | |
| log | fcee1bf9930ad48300863d1b85b1f0a7ee924fbc |
| tree | 437d8d096baf6bb15b5c247e450a5aed5290e176 |
| parent | 18f6ef613c7ae18433f1e7998b787f7a6b840215 |
| parent | c53a556a61fabbfa664d0bc433d0edf8092ee0db |
| signature |
add std.build.ConfigHeaderStep6 files changed, 309 insertions(+), 16 deletions(-)
lib/std/build.zig+13| ... | ... | @@ -21,6 +21,7 @@ const ThisModule = @This(); |
| 21 | 21 | |
| 22 | 22 | pub const CheckFileStep = @import("build/CheckFileStep.zig"); |
| 23 | 23 | pub const CheckObjectStep = @import("build/CheckObjectStep.zig"); |
| 24 | pub const ConfigHeaderStep = @import("build/ConfigHeaderStep.zig"); | |
| 24 | 25 | pub const EmulatableRunStep = @import("build/EmulatableRunStep.zig"); |
| 25 | 26 | pub const FmtStep = @import("build/FmtStep.zig"); |
| 26 | 27 | pub const InstallArtifactStep = @import("build/InstallArtifactStep.zig"); |
| ... | ... | @@ -364,6 +365,17 @@ pub const Builder = struct { |
| 364 | 365 | return run_step; |
| 365 | 366 | } |
| 366 | 367 | |
| 368 | pub fn addConfigHeader( | |
| 369 | b: *Builder, | |
| 370 | source: FileSource, | |
| 371 | style: ConfigHeaderStep.Style, | |
| 372 | values: anytype, | |
| 373 | ) *ConfigHeaderStep { | |
| 374 | const config_header_step = ConfigHeaderStep.create(b, source, style); | |
| 375 | config_header_step.addValues(values); | |
| 376 | return config_header_step; | |
| 377 | } | |
| 378 | ||
| 367 | 379 | /// Allocator.dupe without the need to handle out of memory. |
| 368 | 380 | pub fn dupe(self: *Builder, bytes: []const u8) []u8 { |
| 369 | 381 | return self.allocator.dupe(u8, bytes) catch unreachable; |
| ... | ... | @@ -1427,6 +1439,7 @@ pub const Step = struct { |
| 1427 | 1439 | emulatable_run, |
| 1428 | 1440 | check_file, |
| 1429 | 1441 | check_object, |
| 1442 | config_header, | |
| 1430 | 1443 | install_raw, |
| 1431 | 1444 | options, |
| 1432 | 1445 | custom, |
lib/std/build/ConfigHeaderStep.zig created+245| ... | ... | @@ -0,0 +1,245 @@ |
| 1 | const std = @import("../std.zig"); | |
| 2 | const ConfigHeaderStep = @This(); | |
| 3 | const Step = std.build.Step; | |
| 4 | const Builder = std.build.Builder; | |
| 5 | ||
| 6 | pub const base_id: Step.Id = .config_header; | |
| 7 | ||
| 8 | pub const Style = enum { | |
| 9 | /// The configure format supported by autotools. It uses `#undef foo` to | |
| 10 | /// mark lines that can be substituted with different values. | |
| 11 | autoconf, | |
| 12 | /// The configure format supported by CMake. It uses `@@FOO@@` and | |
| 13 | /// `#cmakedefine` for template substitution. | |
| 14 | cmake, | |
| 15 | }; | |
| 16 | ||
| 17 | pub const Value = union(enum) { | |
| 18 | undef, | |
| 19 | defined, | |
| 20 | boolean: bool, | |
| 21 | int: i64, | |
| 22 | ident: []const u8, | |
| 23 | string: []const u8, | |
| 24 | }; | |
| 25 | ||
| 26 | step: Step, | |
| 27 | builder: *Builder, | |
| 28 | source: std.build.FileSource, | |
| 29 | style: Style, | |
| 30 | values: std.StringHashMap(Value), | |
| 31 | max_bytes: usize = 2 * 1024 * 1024, | |
| 32 | output_dir: []const u8, | |
| 33 | output_basename: []const u8, | |
| 34 | ||
| 35 | pub fn create(builder: *Builder, source: std.build.FileSource, style: Style) *ConfigHeaderStep { | |
| 36 | const self = builder.allocator.create(ConfigHeaderStep) catch @panic("OOM"); | |
| 37 | const name = builder.fmt("configure header {s}", .{source.getDisplayName()}); | |
| 38 | self.* = .{ | |
| 39 | .builder = builder, | |
| 40 | .step = Step.init(base_id, name, builder.allocator, make), | |
| 41 | .source = source, | |
| 42 | .style = style, | |
| 43 | .values = std.StringHashMap(Value).init(builder.allocator), | |
| 44 | .output_dir = undefined, | |
| 45 | .output_basename = "config.h", | |
| 46 | }; | |
| 47 | switch (source) { | |
| 48 | .path => |p| { | |
| 49 | const basename = std.fs.path.basename(p); | |
| 50 | if (std.mem.endsWith(u8, basename, ".h.in")) { | |
| 51 | self.output_basename = basename[0 .. basename.len - 3]; | |
| 52 | } | |
| 53 | }, | |
| 54 | else => {}, | |
| 55 | } | |
| 56 | return self; | |
| 57 | } | |
| 58 | ||
| 59 | pub fn addValues(self: *ConfigHeaderStep, values: anytype) void { | |
| 60 | return addValuesInner(self, values) catch @panic("OOM"); | |
| 61 | } | |
| 62 | ||
| 63 | fn addValuesInner(self: *ConfigHeaderStep, values: anytype) !void { | |
| 64 | inline for (@typeInfo(@TypeOf(values)).Struct.fields) |field| { | |
| 65 | switch (@typeInfo(field.type)) { | |
| 66 | .Null => { | |
| 67 | try self.values.put(field.name, .undef); | |
| 68 | }, | |
| 69 | .Void => { | |
| 70 | try self.values.put(field.name, .defined); | |
| 71 | }, | |
| 72 | .Bool => { | |
| 73 | try self.values.put(field.name, .{ .boolean = @field(values, field.name) }); | |
| 74 | }, | |
| 75 | .ComptimeInt => { | |
| 76 | try self.values.put(field.name, .{ .int = @field(values, field.name) }); | |
| 77 | }, | |
| 78 | .EnumLiteral => { | |
| 79 | try self.values.put(field.name, .{ .ident = @tagName(@field(values, field.name)) }); | |
| 80 | }, | |
| 81 | .Pointer => |ptr| { | |
| 82 | switch (@typeInfo(ptr.child)) { | |
| 83 | .Array => |array| { | |
| 84 | if (ptr.size == .One and array.child == u8) { | |
| 85 | try self.values.put(field.name, .{ .string = @field(values, field.name) }); | |
| 86 | continue; | |
| 87 | } | |
| 88 | }, | |
| 89 | else => {}, | |
| 90 | } | |
| 91 | ||
| 92 | @compileError("unsupported ConfigHeaderStep value type: " ++ | |
| 93 | @typeName(field.type)); | |
| 94 | }, | |
| 95 | else => @compileError("unsupported ConfigHeaderStep value type: " ++ | |
| 96 | @typeName(field.type)), | |
| 97 | } | |
| 98 | } | |
| 99 | } | |
| 100 | ||
| 101 | fn make(step: *Step) !void { | |
| 102 | const self = @fieldParentPtr(ConfigHeaderStep, "step", step); | |
| 103 | const gpa = self.builder.allocator; | |
| 104 | const src_path = self.source.getPath(self.builder); | |
| 105 | const contents = try std.fs.cwd().readFileAlloc(gpa, src_path, self.max_bytes); | |
| 106 | ||
| 107 | // The cache is used here not really as a way to speed things up - because writing | |
| 108 | // the data to a file would probably be very fast - but as a way to find a canonical | |
| 109 | // location to put build artifacts. | |
| 110 | ||
| 111 | // If, for example, a hard-coded path was used as the location to put ConfigHeaderStep | |
| 112 | // files, then two ConfigHeaderStep executing in parallel might clobber each other. | |
| 113 | ||
| 114 | // TODO port the cache system from the compiler to zig std lib. Until then | |
| 115 | // we construct the path directly, and no "cache hit" detection happens; | |
| 116 | // the files are always written. | |
| 117 | // Note there is very similar code over in WriteFileStep | |
| 118 | const Hasher = std.crypto.auth.siphash.SipHash128(1, 3); | |
| 119 | // Random bytes to make ConfigHeaderStep unique. Refresh this with new | |
| 120 | // random bytes when ConfigHeaderStep implementation is modified in a | |
| 121 | // non-backwards-compatible way. | |
| 122 | var hash = Hasher.init("X1pQzdDt91Zlh7Eh"); | |
| 123 | hash.update(self.source.getDisplayName()); | |
| 124 | hash.update(contents); | |
| 125 | ||
| 126 | var digest: [16]u8 = undefined; | |
| 127 | hash.final(&digest); | |
| 128 | var hash_basename: [digest.len * 2]u8 = undefined; | |
| 129 | _ = std.fmt.bufPrint( | |
| 130 | &hash_basename, | |
| 131 | "{s}", | |
| 132 | .{std.fmt.fmtSliceHexLower(&digest)}, | |
| 133 | ) catch unreachable; | |
| 134 | ||
| 135 | self.output_dir = try std.fs.path.join(gpa, &[_][]const u8{ | |
| 136 | self.builder.cache_root, "o", &hash_basename, | |
| 137 | }); | |
| 138 | var dir = std.fs.cwd().makeOpenPath(self.output_dir, .{}) catch |err| { | |
| 139 | std.debug.print("unable to make path {s}: {s}\n", .{ self.output_dir, @errorName(err) }); | |
| 140 | return err; | |
| 141 | }; | |
| 142 | defer dir.close(); | |
| 143 | ||
| 144 | var values_copy = try self.values.clone(); | |
| 145 | defer values_copy.deinit(); | |
| 146 | ||
| 147 | var output = std.ArrayList(u8).init(gpa); | |
| 148 | defer output.deinit(); | |
| 149 | try output.ensureTotalCapacity(contents.len); | |
| 150 | ||
| 151 | try output.appendSlice("/* This file was generated by ConfigHeaderStep using the Zig Build System. */\n"); | |
| 152 | ||
| 153 | switch (self.style) { | |
| 154 | .autoconf => try render_autoconf(contents, &output, &values_copy, src_path), | |
| 155 | .cmake => try render_cmake(contents, &output, &values_copy, src_path), | |
| 156 | } | |
| 157 | ||
| 158 | try dir.writeFile(self.output_basename, output.items); | |
| 159 | } | |
| 160 | ||
| 161 | fn render_autoconf( | |
| 162 | contents: []const u8, | |
| 163 | output: *std.ArrayList(u8), | |
| 164 | values_copy: *std.StringHashMap(Value), | |
| 165 | src_path: []const u8, | |
| 166 | ) !void { | |
| 167 | var any_errors = false; | |
| 168 | var line_index: u32 = 0; | |
| 169 | var line_it = std.mem.split(u8, contents, "\n"); | |
| 170 | while (line_it.next()) |line| : (line_index += 1) { | |
| 171 | if (!std.mem.startsWith(u8, line, "#")) { | |
| 172 | try output.appendSlice(line); | |
| 173 | try output.appendSlice("\n"); | |
| 174 | continue; | |
| 175 | } | |
| 176 | var it = std.mem.tokenize(u8, line[1..], " \t\r"); | |
| 177 | const undef = it.next().?; | |
| 178 | if (!std.mem.eql(u8, undef, "undef")) { | |
| 179 | try output.appendSlice(line); | |
| 180 | try output.appendSlice("\n"); | |
| 181 | continue; | |
| 182 | } | |
| 183 | const name = it.rest(); | |
| 184 | const kv = values_copy.fetchRemove(name) orelse { | |
| 185 | std.debug.print("{s}:{d}: error: unspecified config header value: '{s}'\n", .{ | |
| 186 | src_path, line_index + 1, name, | |
| 187 | }); | |
| 188 | any_errors = true; | |
| 189 | continue; | |
| 190 | }; | |
| 191 | switch (kv.value) { | |
| 192 | .undef => { | |
| 193 | try output.appendSlice("/* #undef "); | |
| 194 | try output.appendSlice(name); | |
| 195 | try output.appendSlice(" */\n"); | |
| 196 | }, | |
| 197 | .defined => { | |
| 198 | try output.appendSlice("#define "); | |
| 199 | try output.appendSlice(name); | |
| 200 | try output.appendSlice("\n"); | |
| 201 | }, | |
| 202 | .boolean => |b| { | |
| 203 | try output.appendSlice("#define "); | |
| 204 | try output.appendSlice(name); | |
| 205 | try output.appendSlice(" "); | |
| 206 | try output.appendSlice(if (b) "true\n" else "false\n"); | |
| 207 | }, | |
| 208 | .int => |i| { | |
| 209 | try output.writer().print("#define {s} {d}\n", .{ name, i }); | |
| 210 | }, | |
| 211 | .ident => |ident| { | |
| 212 | try output.writer().print("#define {s} {s}\n", .{ name, ident }); | |
| 213 | }, | |
| 214 | .string => |string| { | |
| 215 | // TODO: use C-specific escaping instead of zig string literals | |
| 216 | try output.writer().print("#define {s} \"{}\"\n", .{ name, std.zig.fmtEscapes(string) }); | |
| 217 | }, | |
| 218 | } | |
| 219 | } | |
| 220 | ||
| 221 | { | |
| 222 | var it = values_copy.iterator(); | |
| 223 | while (it.next()) |entry| { | |
| 224 | const name = entry.key_ptr.*; | |
| 225 | std.debug.print("{s}: error: config header value unused: '{s}'\n", .{ src_path, name }); | |
| 226 | } | |
| 227 | } | |
| 228 | ||
| 229 | if (any_errors) { | |
| 230 | return error.HeaderConfigFailed; | |
| 231 | } | |
| 232 | } | |
| 233 | ||
| 234 | fn render_cmake( | |
| 235 | contents: []const u8, | |
| 236 | output: *std.ArrayList(u8), | |
| 237 | values_copy: *std.StringHashMap(Value), | |
| 238 | src_path: []const u8, | |
| 239 | ) !void { | |
| 240 | _ = contents; | |
| 241 | _ = output; | |
| 242 | _ = values_copy; | |
| 243 | _ = src_path; | |
| 244 | @panic("TODO: render_cmake is not implemented yet"); | |
| 245 | } |
lib/std/build/LibExeObjStep.zig+11| ... | ... | @@ -28,6 +28,7 @@ const EmulatableRunStep = std.build.EmulatableRunStep; |
| 28 | 28 | const CheckObjectStep = std.build.CheckObjectStep; |
| 29 | 29 | const RunStep = std.build.RunStep; |
| 30 | 30 | const OptionsStep = std.build.OptionsStep; |
| 31 | const ConfigHeaderStep = std.build.ConfigHeaderStep; | |
| 31 | 32 | const LibExeObjStep = @This(); |
| 32 | 33 | |
| 33 | 34 | pub const base_id = .lib_exe_obj; |
| ... | ... | @@ -266,6 +267,7 @@ pub const IncludeDir = union(enum) { |
| 266 | 267 | raw_path: []const u8, |
| 267 | 268 | raw_path_system: []const u8, |
| 268 | 269 | other_step: *LibExeObjStep, |
| 270 | config_header_step: *ConfigHeaderStep, | |
| 269 | 271 | }; |
| 270 | 272 | |
| 271 | 273 | pub const Kind = enum { |
| ... | ... | @@ -932,6 +934,11 @@ pub fn addIncludePath(self: *LibExeObjStep, path: []const u8) void { |
| 932 | 934 | self.include_dirs.append(IncludeDir{ .raw_path = self.builder.dupe(path) }) catch unreachable; |
| 933 | 935 | } |
| 934 | 936 | |
| 937 | pub fn addConfigHeader(self: *LibExeObjStep, config_header: *ConfigHeaderStep) void { | |
| 938 | self.step.dependOn(&config_header.step); | |
| 939 | self.include_dirs.append(.{ .config_header_step = config_header }) catch @panic("OOM"); | |
| 940 | } | |
| 941 | ||
| 935 | 942 | pub fn addLibraryPath(self: *LibExeObjStep, path: []const u8) void { |
| 936 | 943 | self.lib_paths.append(self.builder.dupe(path)) catch unreachable; |
| 937 | 944 | } |
| ... | ... | @@ -1684,6 +1691,10 @@ fn make(step: *Step) !void { |
| 1684 | 1691 | try zig_args.append("-isystem"); |
| 1685 | 1692 | try zig_args.append(fs.path.dirname(h_path).?); |
| 1686 | 1693 | }, |
| 1694 | .config_header_step => |config_header| { | |
| 1695 | try zig_args.append("-I"); | |
| 1696 | try zig_args.append(config_header.output_dir); | |
| 1697 | }, | |
| 1687 | 1698 | } |
| 1688 | 1699 | } |
| 1689 | 1700 |
lib/std/build/RunStep.zig+2-2| ... | ... | @@ -17,7 +17,7 @@ const max_stdout_size = 1 * 1024 * 1024; // 1 MiB |
| 17 | 17 | |
| 18 | 18 | const RunStep = @This(); |
| 19 | 19 | |
| 20 | pub const base_id = .run; | |
| 20 | pub const base_id: Step.Id = .run; | |
| 21 | 21 | |
| 22 | 22 | step: Step, |
| 23 | 23 | builder: *Builder, |
| ... | ... | @@ -59,7 +59,7 @@ pub fn create(builder: *Builder, name: []const u8) *RunStep { |
| 59 | 59 | const self = builder.allocator.create(RunStep) catch unreachable; |
| 60 | 60 | self.* = RunStep{ |
| 61 | 61 | .builder = builder, |
| 62 | .step = Step.init(.run, name, builder.allocator, make), | |
| 62 | .step = Step.init(base_id, name, builder.allocator, make), | |
| 63 | 63 | .argv = ArrayList(Arg).init(builder.allocator), |
| 64 | 64 | .cwd = null, |
| 65 | 65 | .env_map = null, |
lib/std/build/WriteFileStep.zig+16-14| ... | ... | @@ -63,14 +63,15 @@ fn make(step: *Step) !void { |
| 63 | 63 | // files, then two WriteFileSteps executing in parallel might clobber each other. |
| 64 | 64 | |
| 65 | 65 | // TODO port the cache system from the compiler to zig std lib. Until then |
| 66 | // we use blake2b directly and construct the path, and no "cache hit" | |
| 67 | // detection happens; the files are always written. | |
| 68 | var hash = std.crypto.hash.blake2.Blake2b384.init(.{}); | |
| 69 | ||
| 66 | // we directly construct the path, and no "cache hit" detection happens; | |
| 67 | // the files are always written. | |
| 68 | // Note there is similar code over in ConfigHeaderStep. | |
| 69 | const Hasher = std.crypto.auth.siphash.SipHash128(1, 3); | |
| 70 | 70 | // Random bytes to make WriteFileStep unique. Refresh this with |
| 71 | 71 | // new random bytes when WriteFileStep implementation is modified |
| 72 | 72 | // in a non-backwards-compatible way. |
| 73 | hash.update("eagVR1dYXoE7ARDP"); | |
| 73 | var hash = Hasher.init("eagVR1dYXoE7ARDP"); | |
| 74 | ||
| 74 | 75 | { |
| 75 | 76 | var it = self.files.first; |
| 76 | 77 | while (it) |node| : (it = node.next) { |
| ... | ... | @@ -79,21 +80,22 @@ fn make(step: *Step) !void { |
| 79 | 80 | hash.update("|"); |
| 80 | 81 | } |
| 81 | 82 | } |
| 82 | var digest: [48]u8 = undefined; | |
| 83 | var digest: [16]u8 = undefined; | |
| 83 | 84 | hash.final(&digest); |
| 84 | var hash_basename: [64]u8 = undefined; | |
| 85 | _ = fs.base64_encoder.encode(&hash_basename, &digest); | |
| 86 | self.output_dir = try fs.path.join(self.builder.allocator, &[_][]const u8{ | |
| 87 | self.builder.cache_root, | |
| 88 | "o", | |
| 85 | var hash_basename: [digest.len * 2]u8 = undefined; | |
| 86 | _ = std.fmt.bufPrint( | |
| 89 | 87 | &hash_basename, |
| 88 | "{s}", | |
| 89 | .{std.fmt.fmtSliceHexLower(&digest)}, | |
| 90 | ) catch unreachable; | |
| 91 | ||
| 92 | self.output_dir = try fs.path.join(self.builder.allocator, &[_][]const u8{ | |
| 93 | self.builder.cache_root, "o", &hash_basename, | |
| 90 | 94 | }); |
| 91 | // TODO replace with something like fs.makePathAndOpenDir | |
| 92 | fs.cwd().makePath(self.output_dir) catch |err| { | |
| 95 | var dir = fs.cwd().makeOpenPath(self.output_dir, .{}) catch |err| { | |
| 93 | 96 | std.debug.print("unable to make path {s}: {s}\n", .{ self.output_dir, @errorName(err) }); |
| 94 | 97 | return err; |
| 95 | 98 | }; |
| 96 | var dir = try fs.cwd().openDir(self.output_dir, .{}); | |
| 97 | 99 | defer dir.close(); |
| 98 | 100 | { |
| 99 | 101 | var it = self.files.first; |
lib/std/crypto/siphash.zig+22| ... | ... | @@ -87,6 +87,11 @@ fn SipHashStateless(comptime T: type, comptime c_rounds: usize, comptime d_round |
| 87 | 87 | self.msg_len +%= @truncate(u8, b.len); |
| 88 | 88 | } |
| 89 | 89 | |
| 90 | pub fn peek(self: Self) [digest_length]u8 { | |
| 91 | var copy = self; | |
| 92 | return copy.finalResult(); | |
| 93 | } | |
| 94 | ||
| 90 | 95 | pub fn final(self: *Self, b: []const u8) T { |
| 91 | 96 | std.debug.assert(b.len < 8); |
| 92 | 97 | |
| ... | ... | @@ -124,6 +129,12 @@ fn SipHashStateless(comptime T: type, comptime c_rounds: usize, comptime d_round |
| 124 | 129 | return (@as(u128, b2) << 64) | b1; |
| 125 | 130 | } |
| 126 | 131 | |
| 132 | pub fn finalResult(self: *Self) [digest_length]u8 { | |
| 133 | var result: [digest_length]u8 = undefined; | |
| 134 | self.final(&result); | |
| 135 | return result; | |
| 136 | } | |
| 137 | ||
| 127 | 138 | fn round(self: *Self, b: [8]u8) void { |
| 128 | 139 | const m = mem.readIntLittle(u64, b[0..8]); |
| 129 | 140 | self.v3 ^= m; |
| ... | ... | @@ -205,12 +216,23 @@ fn SipHash(comptime T: type, comptime c_rounds: usize, comptime d_rounds: usize) |
| 205 | 216 | self.buf_len += @intCast(u8, b[off + aligned_len ..].len); |
| 206 | 217 | } |
| 207 | 218 | |
| 219 | pub fn peek(self: Self) [mac_length]u8 { | |
| 220 | var copy = self; | |
| 221 | return copy.finalResult(); | |
| 222 | } | |
| 223 | ||
| 208 | 224 | /// Return an authentication tag for the current state |
| 209 | 225 | /// Assumes `out` is less than or equal to `mac_length`. |
| 210 | 226 | pub fn final(self: *Self, out: *[mac_length]u8) void { |
| 211 | 227 | mem.writeIntLittle(T, out, self.state.final(self.buf[0..self.buf_len])); |
| 212 | 228 | } |
| 213 | 229 | |
| 230 | pub fn finalResult(self: *Self) [mac_length]u8 { | |
| 231 | var result: [mac_length]u8 = undefined; | |
| 232 | self.final(&result); | |
| 233 | return result; | |
| 234 | } | |
| 235 | ||
| 214 | 236 | /// Return an authentication tag for a message and a key |
| 215 | 237 | pub fn create(out: *[mac_length]u8, msg: []const u8, key: *const [key_length]u8) void { |
| 216 | 238 | var ctx = Self.init(key); |