diff options
| author | Nicolas Sterchele <nicolas@sterchelen.net> | 2023-03-20 09:23:10 +0100 |
|---|---|---|
| committer | Veikka Tuominen <git@vexu.eu> | 2023-05-03 08:39:24 +0300 |
| commit | 13eb7251d37759bd47403db304c6120c706fe353 (patch) | |
| tree | 225d87ef968270968379e2d58b9791b0aa152aa7 /lib/std/Build/Step/WriteFile.zig | |
| parent | 855493bb8b395970921494d3a11ccfeaac30c2dc (diff) | |
| download | zig-13eb7251d37759bd47403db304c6120c706fe353.tar.gz zig-13eb7251d37759bd47403db304c6120c706fe353.zip | |
build: rename std.Build.*Step to std.Build.Step.*
Follow-up actions from #14647
Fixes #14947
Diffstat (limited to 'lib/std/Build/Step/WriteFile.zig')
| -rw-r--r-- | lib/std/Build/Step/WriteFile.zig | 291 |
1 files changed, 291 insertions, 0 deletions
diff --git a/lib/std/Build/Step/WriteFile.zig b/lib/std/Build/Step/WriteFile.zig new file mode 100644 index 0000000000..68f7c37c6c --- /dev/null +++ b/lib/std/Build/Step/WriteFile.zig @@ -0,0 +1,291 @@ +//! WriteFileStep is primarily used to create a directory in an appropriate +//! location inside the local cache which has a set of files that have either +//! been generated during the build, or are copied from the source package. +//! +//! However, this step has an additional capability of writing data to paths +//! relative to the package root, effectively mutating the package's source +//! files. Be careful with the latter functionality; it should not be used +//! during the normal build process, but as a utility run by a developer with +//! intention to update source files, which will then be committed to version +//! control. +const std = @import("std"); +const Step = std.Build.Step; +const fs = std.fs; +const ArrayList = std.ArrayList; +const WriteFileStep = @This(); + +step: Step, +/// The elements here are pointers because we need stable pointers for the +/// GeneratedFile field. +files: std.ArrayListUnmanaged(*File), +output_source_files: std.ArrayListUnmanaged(OutputSourceFile), +generated_directory: std.Build.GeneratedFile, + +pub const base_id = .write_file; + +pub const File = struct { + generated_file: std.Build.GeneratedFile, + sub_path: []const u8, + contents: Contents, +}; + +pub const OutputSourceFile = struct { + contents: Contents, + sub_path: []const u8, +}; + +pub const Contents = union(enum) { + bytes: []const u8, + copy: std.Build.FileSource, +}; + +pub fn create(owner: *std.Build) *WriteFileStep { + const wf = owner.allocator.create(WriteFileStep) catch @panic("OOM"); + wf.* = .{ + .step = Step.init(.{ + .id = .write_file, + .name = "WriteFile", + .owner = owner, + .makeFn = make, + }), + .files = .{}, + .output_source_files = .{}, + .generated_directory = .{ .step = &wf.step }, + }; + return wf; +} + +pub fn add(wf: *WriteFileStep, sub_path: []const u8, bytes: []const u8) void { + const b = wf.step.owner; + const gpa = b.allocator; + const file = gpa.create(File) catch @panic("OOM"); + file.* = .{ + .generated_file = .{ .step = &wf.step }, + .sub_path = b.dupePath(sub_path), + .contents = .{ .bytes = b.dupe(bytes) }, + }; + wf.files.append(gpa, file) catch @panic("OOM"); + + wf.maybeUpdateName(); +} + +/// Place the file into the generated directory within the local cache, +/// along with all the rest of the files added to this step. The parameter +/// here is the destination path relative to the local cache directory +/// associated with this WriteFileStep. It may be a basename, or it may +/// include sub-directories, in which case this step will ensure the +/// required sub-path exists. +/// This is the option expected to be used most commonly with `addCopyFile`. +pub fn addCopyFile(wf: *WriteFileStep, source: std.Build.FileSource, sub_path: []const u8) void { + const b = wf.step.owner; + const gpa = b.allocator; + const file = gpa.create(File) catch @panic("OOM"); + file.* = .{ + .generated_file = .{ .step = &wf.step }, + .sub_path = b.dupePath(sub_path), + .contents = .{ .copy = source }, + }; + wf.files.append(gpa, file) catch @panic("OOM"); + + wf.maybeUpdateName(); + source.addStepDependencies(&wf.step); +} + +/// A path relative to the package root. +/// Be careful with this because it updates source files. This should not be +/// used as part of the normal build process, but as a utility occasionally +/// run by a developer with intent to modify source files and then commit +/// those changes to version control. +/// A file added this way is not available with `getFileSource`. +pub fn addCopyFileToSource(wf: *WriteFileStep, source: std.Build.FileSource, sub_path: []const u8) void { + const b = wf.step.owner; + wf.output_source_files.append(b.allocator, .{ + .contents = .{ .copy = source }, + .sub_path = sub_path, + }) catch @panic("OOM"); + source.addStepDependencies(&wf.step); +} + +/// A path relative to the package root. +/// Be careful with this because it updates source files. This should not be +/// used as part of the normal build process, but as a utility occasionally +/// run by a developer with intent to modify source files and then commit +/// those changes to version control. +/// A file added this way is not available with `getFileSource`. +pub fn addBytesToSource(wf: *WriteFileStep, bytes: []const u8, sub_path: []const u8) void { + const b = wf.step.owner; + wf.output_source_files.append(b.allocator, .{ + .contents = .{ .bytes = bytes }, + .sub_path = sub_path, + }) catch @panic("OOM"); +} + +/// Gets a file source for the given sub_path. If the file does not exist, returns `null`. +pub fn getFileSource(wf: *WriteFileStep, sub_path: []const u8) ?std.Build.FileSource { + for (wf.files.items) |file| { + if (std.mem.eql(u8, file.sub_path, sub_path)) { + return .{ .generated = &file.generated_file }; + } + } + return null; +} + +/// Returns a `FileSource` representing the base directory that contains all the +/// files from this `WriteFileStep`. +pub fn getDirectorySource(wf: *WriteFileStep) std.Build.FileSource { + return .{ .generated = &wf.generated_directory }; +} + +fn maybeUpdateName(wf: *WriteFileStep) void { + if (wf.files.items.len == 1) { + // First time adding a file; update name. + if (std.mem.eql(u8, wf.step.name, "WriteFile")) { + wf.step.name = wf.step.owner.fmt("WriteFile {s}", .{wf.files.items[0].sub_path}); + } + } +} + +fn make(step: *Step, prog_node: *std.Progress.Node) !void { + _ = prog_node; + const b = step.owner; + const wf = @fieldParentPtr(WriteFileStep, "step", step); + + // Writing to source files is kind of an extra capability of this + // WriteFileStep - arguably it should be a different step. But anyway here + // it is, it happens unconditionally and does not interact with the other + // files here. + var any_miss = false; + for (wf.output_source_files.items) |output_source_file| { + if (fs.path.dirname(output_source_file.sub_path)) |dirname| { + b.build_root.handle.makePath(dirname) catch |err| { + return step.fail("unable to make path '{}{s}': {s}", .{ + b.build_root, dirname, @errorName(err), + }); + }; + } + switch (output_source_file.contents) { + .bytes => |bytes| { + b.build_root.handle.writeFile(output_source_file.sub_path, bytes) catch |err| { + return step.fail("unable to write file '{}{s}': {s}", .{ + b.build_root, output_source_file.sub_path, @errorName(err), + }); + }; + any_miss = true; + }, + .copy => |file_source| { + const source_path = file_source.getPath(b); + const prev_status = fs.Dir.updateFile( + fs.cwd(), + source_path, + b.build_root.handle, + output_source_file.sub_path, + .{}, + ) catch |err| { + return step.fail("unable to update file from '{s}' to '{}{s}': {s}", .{ + source_path, b.build_root, output_source_file.sub_path, @errorName(err), + }); + }; + any_miss = any_miss or prev_status == .stale; + }, + } + } + + // The cache is used here not really as a way to speed things up - because writing + // the data to a file would probably be very fast - but as a way to find a canonical + // location to put build artifacts. + + // If, for example, a hard-coded path was used as the location to put WriteFileStep + // files, then two WriteFileSteps executing in parallel might clobber each other. + + var man = b.cache.obtain(); + defer man.deinit(); + + // Random bytes to make WriteFileStep unique. Refresh this with + // new random bytes when WriteFileStep implementation is modified + // in a non-backwards-compatible way. + man.hash.add(@as(u32, 0xd767ee59)); + + for (wf.files.items) |file| { + man.hash.addBytes(file.sub_path); + switch (file.contents) { + .bytes => |bytes| { + man.hash.addBytes(bytes); + }, + .copy => |file_source| { + _ = try man.addFile(file_source.getPath(b), null); + }, + } + } + + if (try step.cacheHit(&man)) { + const digest = man.final(); + for (wf.files.items) |file| { + file.generated_file.path = try b.cache_root.join(b.allocator, &.{ + "o", &digest, file.sub_path, + }); + } + wf.generated_directory.path = try b.cache_root.join(b.allocator, &.{ "o", &digest }); + return; + } + + const digest = man.final(); + const cache_path = "o" ++ fs.path.sep_str ++ digest; + + wf.generated_directory.path = try b.cache_root.join(b.allocator, &.{ "o", &digest }); + + var cache_dir = b.cache_root.handle.makeOpenPath(cache_path, .{}) catch |err| { + return step.fail("unable to make path '{}{s}': {s}", .{ + b.cache_root, cache_path, @errorName(err), + }); + }; + defer cache_dir.close(); + + for (wf.files.items) |file| { + if (fs.path.dirname(file.sub_path)) |dirname| { + cache_dir.makePath(dirname) catch |err| { + return step.fail("unable to make path '{}{s}{c}{s}': {s}", .{ + b.cache_root, cache_path, fs.path.sep, dirname, @errorName(err), + }); + }; + } + switch (file.contents) { + .bytes => |bytes| { + cache_dir.writeFile(file.sub_path, bytes) catch |err| { + return step.fail("unable to write file '{}{s}{c}{s}': {s}", .{ + b.cache_root, cache_path, fs.path.sep, file.sub_path, @errorName(err), + }); + }; + }, + .copy => |file_source| { + const source_path = file_source.getPath(b); + const prev_status = fs.Dir.updateFile( + fs.cwd(), + source_path, + cache_dir, + file.sub_path, + .{}, + ) catch |err| { + return step.fail("unable to update file from '{s}' to '{}{s}{c}{s}': {s}", .{ + source_path, + b.cache_root, + cache_path, + fs.path.sep, + file.sub_path, + @errorName(err), + }); + }; + // At this point we already will mark the step as a cache miss. + // But this is kind of a partial cache hit since individual + // file copies may be avoided. Oh well, this information is + // discarded. + _ = prev_status; + }, + } + + file.generated_file.path = try b.cache_root.join(b.allocator, &.{ + cache_path, file.sub_path, + }); + } + + try step.writeManifest(&man); +} |
