Commit d703270545

Andrew Kelley <andrew@ziglang.org>
2023-01-25 01:24:16
zig build system: add LibExeObjStep.installLibraryHeaders
This function is needed when a library exposes one of its own library dependency's headers as part of its own public API. Also, improve error message when a file system error occurs during install file step.
1 parent 3ff1f34
lib/std/build/InstallDirStep.zig
@@ -6,10 +6,14 @@ const Step = build.Step;
 const Builder = build.Builder;
 const InstallDir = std.build.InstallDir;
 const InstallDirStep = @This();
+const log = std.log;
 
 step: Step,
 builder: *Builder,
 options: Options,
+/// This is used by the build system when a file being installed comes from one
+/// package but is being installed by another.
+override_source_builder: ?*Builder = null,
 
 pub const base_id = .install_dir;
 
@@ -53,8 +57,14 @@ pub fn init(
 fn make(step: *Step) !void {
     const self = @fieldParentPtr(InstallDirStep, "step", step);
     const dest_prefix = self.builder.getInstallPath(self.options.install_dir, self.options.install_subdir);
-    const full_src_dir = self.builder.pathFromRoot(self.options.source_dir);
-    var src_dir = try std.fs.cwd().openIterableDir(full_src_dir, .{});
+    const src_builder = self.override_source_builder orelse self.builder;
+    const full_src_dir = src_builder.pathFromRoot(self.options.source_dir);
+    var src_dir = std.fs.cwd().openIterableDir(full_src_dir, .{}) catch |err| {
+        log.err("InstallDirStep: unable to open source directory '{s}': {s}", .{
+            full_src_dir, @errorName(err),
+        });
+        return error.StepFailed;
+    };
     defer src_dir.close();
     var it = try src_dir.walk(self.builder.allocator);
     next_entry: while (try it.next()) |entry| {
@@ -64,13 +74,8 @@ fn make(step: *Step) !void {
             }
         }
 
-        const full_path = self.builder.pathJoin(&.{
-            full_src_dir, entry.path,
-        });
-
-        const dest_path = self.builder.pathJoin(&.{
-            dest_prefix, entry.path,
-        });
+        const full_path = self.builder.pathJoin(&.{ full_src_dir, entry.path });
+        const dest_path = self.builder.pathJoin(&.{ dest_prefix, entry.path });
 
         switch (entry.kind) {
             .Directory => try fs.cwd().makePath(dest_path),
lib/std/build/InstallFileStep.zig
@@ -13,6 +13,9 @@ builder: *Builder,
 source: FileSource,
 dir: InstallDir,
 dest_rel_path: []const u8,
+/// This is used by the build system when a file being installed comes from one
+/// package but is being installed by another.
+override_source_builder: ?*Builder = null,
 
 pub fn init(
     builder: *Builder,
@@ -32,7 +35,8 @@ pub fn init(
 
 fn make(step: *Step) !void {
     const self = @fieldParentPtr(InstallFileStep, "step", step);
+    const src_builder = self.override_source_builder orelse self.builder;
+    const full_src_path = self.source.getPath(src_builder);
     const full_dest_path = self.builder.getInstallPath(self.dir, self.dest_rel_path);
-    const full_src_path = self.source.getPath(self.builder);
     try self.builder.updateFile(full_src_path, full_dest_path);
 }
lib/std/build/LibExeObjStep.zig
@@ -501,6 +501,29 @@ pub fn installHeadersDirectoryOptions(
     a.installed_headers.append(&install_dir.step) catch unreachable;
 }
 
+pub fn installLibraryHeaders(a: *LibExeObjStep, l: *LibExeObjStep) void {
+    assert(l.kind == .lib);
+    const install_step = a.builder.getInstallStep();
+    // Copy each element from installed_headers, modifying the builder
+    // to be the new parent's builder.
+    for (l.installed_headers.items) |step| {
+        const step_copy = switch (step.id) {
+            inline .install_file, .install_dir => |id| blk: {
+                const T = id.Type();
+                const ptr = a.builder.allocator.create(T) catch unreachable;
+                ptr.* = step.cast(T).?.*;
+                ptr.override_source_builder = ptr.builder;
+                ptr.builder = a.builder;
+                break :blk &ptr.step;
+            },
+            else => unreachable,
+        };
+        a.installed_headers.append(step_copy) catch unreachable;
+        install_step.dependOn(step_copy);
+    }
+    a.installed_headers.appendSlice(l.installed_headers.items) catch unreachable;
+}
+
 /// Creates a `RunStep` with an executable built with `addExecutable`.
 /// Add command line arguments with `addArg`.
 pub fn run(exe: *LibExeObjStep) *RunStep {
lib/std/build.zig
@@ -316,9 +316,20 @@ pub const Builder = struct {
         // options to its dependencies. It is the programmatic way to give
         // command line arguments to a build.zig script.
         _ = args;
-        // TODO create a hash based on the args and the package hash, use this
-        // to compute the install prefix.
-        const install_prefix = b.pathJoin(&.{ b.cache_root, "pkg" });
+        const Hasher = std.crypto.auth.siphash.SipHash128(1, 3);
+        // Random bytes to make unique. Refresh this with new random bytes when
+        // implementation is modified in a non-backwards-compatible way.
+        var hash = Hasher.init("ZaEsvQ5ClaA2IdH9");
+        hash.update(b.dep_prefix);
+        // TODO additionally update the hash with `args`.
+
+        var digest: [16]u8 = undefined;
+        hash.final(&digest);
+        var hash_basename: [digest.len * 2]u8 = undefined;
+        _ = std.fmt.bufPrint(&hash_basename, "{s}", .{std.fmt.fmtSliceHexLower(&digest)}) catch
+            unreachable;
+
+        const install_prefix = b.pathJoin(&.{ b.cache_root, "i", &hash_basename });
         b.resolveInstallPrefix(install_prefix, .{});
     }
 
@@ -1600,6 +1611,29 @@ pub const Step = struct {
         install_raw,
         options,
         custom,
+
+        pub fn Type(comptime id: Id) type {
+            return switch (id) {
+                .top_level => Builder.TopLevelStep,
+                .lib_exe_obj => LibExeObjStep,
+                .install_artifact => InstallArtifactStep,
+                .install_file => InstallFileStep,
+                .install_dir => InstallDirStep,
+                .log => LogStep,
+                .remove_dir => RemoveDirStep,
+                .fmt => FmtStep,
+                .translate_c => TranslateCStep,
+                .write_file => WriteFileStep,
+                .run => RunStep,
+                .emulatable_run => EmulatableRunStep,
+                .check_file => CheckFileStep,
+                .check_object => CheckObjectStep,
+                .config_header => ConfigHeaderStep,
+                .install_raw => InstallRawStep,
+                .options => OptionsStep,
+                .custom => @compileError("no type available for custom step"),
+            };
+        }
     };
 
     pub fn init(id: Id, name: []const u8, allocator: Allocator, makeFn: MakeFn) Step {
lib/build_runner.zig
@@ -220,6 +220,11 @@ pub fn main() !void {
                 return usageAndErr(builder, true, stderr_stream);
             },
             error.UncleanExit => process.exit(1),
+            // This error is intended to indicate that the step has already
+            // logged an error message and so printing the error return trace
+            // here would be unwanted extra information, unless the user opts
+            // into it with a debug flag.
+            error.StepFailed => process.exit(1),
             else => return err,
         }
     };