diff --git a/e2e/workspace/runfiles-library/dependency/main.zig b/e2e/workspace/runfiles-library/dependency/main.zig index c2ec0649..343ca5fb 100644 --- a/e2e/workspace/runfiles-library/dependency/main.zig +++ b/e2e/workspace/runfiles-library/dependency/main.zig @@ -3,13 +3,15 @@ const runfiles = @import("runfiles"); const bazel_builtin = @import("bazel_builtin"); pub fn readData(allocator: std.mem.Allocator) ![]const u8 { - var r = try runfiles.Runfiles.create(.{ .allocator = allocator }) orelse + var r_ = try runfiles.Runfiles.create(.{ .allocator = allocator }) orelse return error.RunfilesNotFound; - defer r.deinit(allocator); + defer r_.deinit(allocator); + + const r = r_.withSourceRepo(bazel_builtin.current_repository); const rpath = "runfiles_library_transitive_dependency/data.txt"; - const file_path = try r.rlocationAlloc(allocator, rpath, bazel_builtin.current_repository) orelse + const file_path = try r.rlocationAlloc(allocator, rpath) orelse return error.RLocationNotFound; defer allocator.free(file_path); diff --git a/e2e/workspace/runfiles-library/main.zig b/e2e/workspace/runfiles-library/main.zig index 8d6efefb..b81cb902 100644 --- a/e2e/workspace/runfiles-library/main.zig +++ b/e2e/workspace/runfiles-library/main.zig @@ -21,7 +21,10 @@ pub fn main() !void { const rpath = try getEnvVar(allocator, "DATA") orelse return error.EnvVarNotFoundDATA; defer allocator.free(rpath); - const file_path = try r.rlocationAlloc(allocator, rpath, "") orelse return error.RLocationNotFound; + const file_path = try r + .withSourceRepo("") + .rlocationAlloc(allocator, rpath) orelse + return error.RLocationNotFound; defer allocator.free(file_path); var file = try std.fs.cwd().openFile(file_path, .{}); @@ -41,7 +44,10 @@ test "read data file" { const rpath = try getEnvVar(std.testing.allocator, "DATA") orelse return error.EnvVarNotFoundDATA; defer std.testing.allocator.free(rpath); - const file_path = try r.rlocationAlloc(std.testing.allocator, rpath, "") orelse return error.RLocationNotFound; + const file_path = try r + .withSourceRepo("") + .rlocationAlloc(std.testing.allocator, rpath) orelse + return error.RLocationNotFound; defer std.testing.allocator.free(file_path); var file = try std.fs.cwd().openFile(file_path, .{}); @@ -61,7 +67,10 @@ test "resolve external dependency rpath" { const rpath = try getEnvVar(std.testing.allocator, "DEPENDENCY_DATA") orelse return error.EnvVarNotFoundDEPENDENCY_DATA; defer std.testing.allocator.free(rpath); - const file_path = try r.rlocationAlloc(std.testing.allocator, rpath, "") orelse return error.RLocationNotFound; + const file_path = try r + .withSourceRepo("") + .rlocationAlloc(std.testing.allocator, rpath) orelse + return error.RLocationNotFound; defer std.testing.allocator.free(file_path); var file = try std.fs.cwd().openFile(file_path, .{}); @@ -88,7 +97,10 @@ test "runfiles in nested binary" { const rpath = try getEnvVar(std.testing.allocator, "BINARY") orelse return error.EnvVarNotFoundBINARY; defer std.testing.allocator.free(rpath); - const binary_path = try r.rlocationAlloc(std.testing.allocator, rpath, "") orelse return error.RLocationNotFound; + const binary_path = try r + .withSourceRepo("") + .rlocationAlloc(std.testing.allocator, rpath) orelse + return error.RLocationNotFound; defer std.testing.allocator.free(binary_path); var env = std.process.EnvMap.init(std.testing.allocator); diff --git a/zig/runfiles/guide.md b/zig/runfiles/guide.md index 015083c7..d7ac60e7 100644 --- a/zig/runfiles/guide.md +++ b/zig/runfiles/guide.md @@ -68,9 +68,9 @@ Follow the steps below to use this runfiles library in a Zig target. 3. Create a `runfiles.Runfiles` object using `runfiles.Runfiles.create`. - See `runfiles.Runfiles` doctest for a worked example. +4. Define the source repository using `runfiles.Runfiles.withSourceRepo`. -4. Use `runfiles.Runfiles.rlocation` or `runfiles.Runfiles.rlocationAlloc` - to look up a runfile path. +5. Use `runfiles.Runfiles.WithSourceRepo.rlocation` or + `runfiles.Runfiles.WithSourceRepo.rlocationAlloc` to look up a runfile path. See `runfiles.Runfiles` doctest for a worked example. diff --git a/zig/runfiles/runfiles.zig b/zig/runfiles/runfiles.zig index 422bcb88..bc6c778a 100644 --- a/zig/runfiles/runfiles.zig +++ b/zig/runfiles/runfiles.zig @@ -25,21 +25,23 @@ test { test Runfiles { var allocator = std.testing.allocator; - var r = try Runfiles.create(.{ .allocator = allocator }) orelse + var r_ = try Runfiles.create(.{ .allocator = allocator }) orelse return error.RunfilesNotFound; - defer r.deinit(allocator); + defer r_.deinit(allocator); + + // Runfiles lookup is subject to repository remapping. You must pass the + // name of the repository relative to which the runfiles path is valid. + // Use the auto-generated `bazel_builtin` module to obtain it. + const source_repo = @import("bazel_builtin").current_repository; + const r = r_.withSourceRepo(source_repo); // Runfiles paths have the form `WORKSPACE/PACKAGE/FILE`. // Use `$(rlocationpath ...)` expansion in your `BUILD.bazel` file to // obtain one. You can forward it to your executable using the `env` or // `args` attribute, or by embedding it in a generated file. const rpath = "rules_zig/zig/runfiles/test-data.txt"; - // Runfiles lookup is subject to repository remapping. You must pass the - // name of the repository relative to which the runfiles path is valid. - // Use the auto-generated `bazel_builtin` module to obtain it. - const source = @import("bazel_builtin").current_repository; - const allocated_path = try r.rlocationAlloc(allocator, rpath, source) orelse + const allocated_path = try r.rlocationAlloc(allocator, rpath) orelse // Runfiles path lookup may return `null`. return error.RPathNotFound; defer allocator.free(allocated_path); diff --git a/zig/runfiles/src/Runfiles.zig b/zig/runfiles/src/Runfiles.zig index 8053fda7..cad2e703 100644 --- a/zig/runfiles/src/Runfiles.zig +++ b/zig/runfiles/src/Runfiles.zig @@ -79,60 +79,93 @@ pub fn deinit(self: *Runfiles, allocator: std.mem.Allocator) void { if (self.repo_mapping) |*repo_mapping| repo_mapping.deinit(allocator); } -pub const RLocationError = error{ - NoSpaceLeft, - NameTooLong, -} || ValidationError; - -/// Resolves the given runfiles location path `rpath`, -/// and returns an absolute path to the item. -/// Note, the returned path may point to a non-existing file. -/// Returns `null` under the manifest based strategy -/// if the runfiles path was not found. -/// -/// Prefer to use Bazel's `$(rlocationpath ...)` expansion in your -/// `BUILD.bazel` file to obtain a runfiles path. -/// -/// The runfiles path is subject to repository mapping and will be resolved -/// relative to the given `source` repository name. +pub const WithSourceRepo = struct { + runfiles: *const Runfiles, + source_repo: []const u8, + + pub const RLocationError = error{ + NoSpaceLeft, + NameTooLong, + } || ValidationError; + + /// Resolves the given runfiles location path `rpath`, + /// and returns an absolute path to the item. + /// Note, the returned path may point to a non-existing file. + /// Returns `null` under the manifest based strategy + /// if the runfiles path was not found. + /// + /// Prefer to use Bazel's `$(rlocationpath ...)` expansion in your + /// `BUILD.bazel` file to obtain a runfiles path. + /// + /// Quoting the [runfiles design][runfiles-design] for further details: + /// + /// > Every language's library will have a similar interface: an + /// > Rlocation(string) method that expects a runfiles-root-relative path + /// > (case-sensitive on Linux/macOS, case-insensitive on Windows) and returns + /// > the absolute path of the file, which is normalized (and lowercase on + /// > Windows) and uses "/" as directory separator on every platform (including + /// > Windows) + /// + /// TODO: Path normalization, in particular lower-case and '/' normalization on + /// Windows, is not yet implemented. + /// + /// [runfiles-design]: https://docs.google.com/document/d/e/2PACX-1vSDIrFnFvEYhKsCMdGdD40wZRBX3m3aZ5HhVj4CtHPmiXKDCxioTUbYsDydjKtFDAzER5eg7OjJWs3V/pub + pub fn rlocation( + self: WithSourceRepo, + rpath: []const u8, + out_buffer: []u8, + ) RLocationError!?[]const u8 { + try validateRPath(rpath); + const rpath_ = self.runfiles.remapRPath(rpath, self.source_repo); + return try self.runfiles.implementation.rlocationUnmapped(rpath_, out_buffer); + } + + /// Allocating variant of `rlocation`. + /// The caller owns the returned path. + pub fn rlocationAlloc( + self: WithSourceRepo, + allocator: std.mem.Allocator, + rpath: []const u8, + ) (error{OutOfMemory} || RLocationError)!?[]const u8 { + try validateRPath(rpath); + const rpath_ = self.runfiles.remapRPath(rpath, self.source_repo); + return try self.runfiles.implementation.rlocationUnmappedAlloc(allocator, rpath_); + } + + pub const ValidationError = error{ + RPathIsAbsolute, + RPathContainsSelfReference, + RPathContainsUpReference, + }; + + fn validateRPath(rpath: []const u8) !void { + var iter = try std.fs.path.componentIterator(rpath); + + if (iter.root() != null) + return error.RPathIsAbsolute; + + while (iter.next()) |component| { + if (std.mem.eql(u8, ".", component.name)) + return error.RPathContainsSelfReference; + + if (std.mem.eql(u8, "..", component.name)) + return error.RPathContainsUpReference; + } + } +}; + +/// Runfiles path lookup is subject to repository mapping and will be resolved +/// relative to the given source repository name `source_repo`. /// Use the automatically generated `bazel_builtin` module to obtain the /// current repository name. /// -/// Quoting the [runfiles design][runfiles-design] for further details: -/// -/// > Every language's library will have a similar interface: an -/// > Rlocation(string) method that expects a runfiles-root-relative path -/// > (case-sensitive on Linux/macOS, case-insensitive on Windows) and returns -/// > the absolute path of the file, which is normalized (and lowercase on -/// > Windows) and uses "/" as directory separator on every platform (including -/// > Windows) -/// -/// TODO: Path normalization, in particular lower-case and '/' normalization on -/// Windows, is not yet implemented. -/// -/// [runfiles-design]: https://docs.google.com/document/d/e/2PACX-1vSDIrFnFvEYhKsCMdGdD40wZRBX3m3aZ5HhVj4CtHPmiXKDCxioTUbYsDydjKtFDAzER5eg7OjJWs3V/pub -pub fn rlocation( - self: *const Runfiles, - rpath: []const u8, - source: []const u8, - out_buffer: []u8, -) RLocationError!?[]const u8 { - try validateRPath(rpath); - const rpath_ = self.remapRPath(rpath, source); - return try self.implementation.rlocationUnmapped(rpath_, out_buffer); -} - -/// Allocating variant of `rlocation`. -/// The caller owns the returned path. -pub fn rlocationAlloc( - self: *const Runfiles, - allocator: std.mem.Allocator, - rpath: []const u8, - source: []const u8, -) (error{OutOfMemory} || RLocationError)!?[]const u8 { - try validateRPath(rpath); - const rpath_ = self.remapRPath(rpath, source); - return try self.implementation.rlocationUnmappedAlloc(allocator, rpath_); +/// The returned `WithSourceRepo` holds a reference to `self` and +/// `source_repo`. +pub fn withSourceRepo(self: *const Runfiles, source_repo: []const u8) WithSourceRepo { + return .{ + .runfiles = self, + .source_repo = source_repo, + }; } /// Set the required environment variables to discover the same runfiles. Use @@ -142,27 +175,6 @@ pub fn environment(self: *const Runfiles, output_env: *std.process.EnvMap) error try self.implementation.environment(output_env); } -pub const ValidationError = error{ - RPathIsAbsolute, - RPathContainsSelfReference, - RPathContainsUpReference, -}; - -fn validateRPath(rpath: []const u8) !void { - var iter = try std.fs.path.componentIterator(rpath); - - if (iter.root() != null) - return error.RPathIsAbsolute; - - while (iter.next()) |component| { - if (std.mem.eql(u8, ".", component.name)) - return error.RPathContainsSelfReference; - - if (std.mem.eql(u8, "..", component.name)) - return error.RPathContainsUpReference; - } -} - fn remapRPath(self: *const Runfiles, rpath: []const u8, source: []const u8) RPath { var rpath_ = RPath.init(rpath); if (self.repo_mapping) |repo_mapping| { @@ -290,9 +302,10 @@ test "Runfiles from manifest" { { var buffer: [std.fs.MAX_PATH_BYTES]u8 = undefined; - const file_path = try runfiles.rlocation( + const file_path = try runfiles + .withSourceRepo("") + .rlocation( "my_module/some/package/some_file", - "", &buffer, ) orelse return error.TestRLocationNotFound; @@ -303,10 +316,11 @@ test "Runfiles from manifest" { } { - const file_path = try runfiles.rlocationAlloc( + const file_path = try runfiles + .withSourceRepo("") + .rlocationAlloc( std.testing.allocator, "other_module/other/package/other_file", - "", ) orelse return error.TestRLocationNotFound; defer std.testing.allocator.free(file_path); @@ -317,10 +331,11 @@ test "Runfiles from manifest" { } { - const file_path = try runfiles.rlocationAlloc( + const file_path = try runfiles + .withSourceRepo("their_module~1.2.3") + .rlocationAlloc( std.testing.allocator, "another_module/other/package/other_file", - "their_module~1.2.3", ) orelse return error.TestRLocationNotFound; defer std.testing.allocator.free(file_path); @@ -388,9 +403,10 @@ test "Runfiles from directory" { { var buffer: [std.fs.MAX_PATH_BYTES]u8 = undefined; - const file_path = try runfiles.rlocation( + const file_path = try runfiles + .withSourceRepo("") + .rlocation( "my_module/some/package/some_file", - "", &buffer, ) orelse return error.TestRLocationNotFound; @@ -401,10 +417,11 @@ test "Runfiles from directory" { } { - const file_path = try runfiles.rlocationAlloc( + const file_path = try runfiles + .withSourceRepo("") + .rlocationAlloc( std.testing.allocator, "other_module/other/package/other_file", - "", ) orelse return error.TestRLocationNotFound; defer std.testing.allocator.free(file_path); @@ -415,10 +432,11 @@ test "Runfiles from directory" { } { - const file_path = try runfiles.rlocationAlloc( + const file_path = try runfiles + .withSourceRepo("their_module~1.2.3") + .rlocationAlloc( std.testing.allocator, "another_module/other/package/other_file", - "their_module~1.2.3", ) orelse return error.TestRLocationNotFound; defer std.testing.allocator.free(file_path); @@ -438,12 +456,13 @@ test "Runfiles from directory" { } test "rpath validation" { - const r = Runfiles{ + const r_ = Runfiles{ .implementation = Implementation{ .directory = .{ .path = "/does-not-exist" } }, .repo_mapping = null, }; + const r = r_.withSourceRepo(""); var buf: [32]u8 = undefined; - try std.testing.expectError(error.RPathIsAbsolute, r.rlocationAlloc(std.testing.allocator, "/absolute/path", "")); - try std.testing.expectError(error.RPathContainsSelfReference, r.rlocation("self/reference/./path", "", &buf)); - try std.testing.expectError(error.RPathContainsUpReference, r.rlocationAlloc(std.testing.allocator, "up/reference/../path", "")); + try std.testing.expectError(error.RPathIsAbsolute, r.rlocationAlloc(std.testing.allocator, "/absolute/path")); + try std.testing.expectError(error.RPathContainsSelfReference, r.rlocation("self/reference/./path", &buf)); + try std.testing.expectError(error.RPathContainsUpReference, r.rlocationAlloc(std.testing.allocator, "up/reference/../path")); } diff --git a/zig/tests/integration_tests/workspace/runfiles/main.zig b/zig/tests/integration_tests/workspace/runfiles/main.zig index 34158ba2..b7b62653 100644 --- a/zig/tests/integration_tests/workspace/runfiles/main.zig +++ b/zig/tests/integration_tests/workspace/runfiles/main.zig @@ -8,13 +8,15 @@ pub fn main() !void { const allocator = arena.allocator(); - var r = try runfiles.Runfiles.create(.{ .allocator = allocator }) orelse + var r_ = try runfiles.Runfiles.create(.{ .allocator = allocator }) orelse return error.RunfilesNotFound; - defer r.deinit(allocator); + defer r_.deinit(allocator); + + const r = r_.withSourceRepo(bazel_builtin.current_repository); const rpath = "__main__/runfiles/data.txt"; - const file_path = try r.rlocationAlloc(allocator, rpath, bazel_builtin.current_repository) orelse { + const file_path = try r.rlocationAlloc(allocator, rpath) orelse { std.log.err("Runfiles location '{s}' not found", .{rpath}); return error.RLocationNotFound; };