Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions e2e/workspace/runfiles-library/dependency/main.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
20 changes: 16 additions & 4 deletions e2e/workspace/runfiles-library/main.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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, .{});
Expand All @@ -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, .{});
Expand All @@ -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, .{});
Expand All @@ -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);
Expand Down
6 changes: 3 additions & 3 deletions zig/runfiles/guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
16 changes: 9 additions & 7 deletions zig/runfiles/runfiles.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
195 changes: 107 additions & 88 deletions zig/runfiles/src/Runfiles.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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| {
Expand Down Expand Up @@ -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;
Expand All @@ -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);
Expand All @@ -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);
Expand Down Expand Up @@ -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;
Expand All @@ -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);
Expand All @@ -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);
Expand All @@ -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"));
}
Loading