mirror of
https://codeberg.org/ziglang/zig.git
synced 2025-12-06 13:54:21 +00:00
This function is sometimes used to assume a canonical representation of a path. However, when the `Path` referred to `root_dir` itself, this function previously resolved `sub_path` to ".", which is incorrect; per doc comments, it should set `sub_path` to "". This fix ultimately didn't solve what I was trying to solve, though I'm still PRing it, because it's still *correct*. The background to this commit is quite interesting and worth briefly discussing. I originally worked on this to try and fix a bug in the build system, where if the root package (i.e. the one you `zig build`) depends on package X which itself depends back on the root package (through a `.path` dependency), invalid dependency modules are generated. I hit this case working on ziglang/translate-c, which wants to depend on "examples" (similar to the Zig compiler's "standalone" test cases) which themselves depend back on the translate-c package. However, after this patch just turned that error into another, I realised that this case simply cannot work, because `std.Build` needs to eagerly execute build scripts at `dependency` calls to learn which artifacts, modules, etc, exist. ...at least, that's how the build system is currently designed. One can imagine a world where `dependency` sort of "queues" the call, `artifact` and `module` etc just pretend that the thing exists, and all configure functions are called non-recursively by the runner. The downside is that it becomes impossible to query state set by a dependency's configure script. For instance, if a dependency exposes an artifact, it would become impossible to get that artifact's resolved target in the configure phase. However, as well as allowing recursive package imports (which are certainly kinda nifty), it would also make lazy dependencies far more useful! Right now, lazy dependencies only really work if you use options (`std.Build.option`) to block their usage, since any call to `lazyDependency` causes the dependency to be fetched. However, if we made this change, lazy dependencies could be made far more versatile by only fetching them *if the final step plan requires them*. I'm not 100% sure if this is a good idea or not, but I might open an issue for it soon.
231 lines
7.5 KiB
Zig
231 lines
7.5 KiB
Zig
root_dir: Cache.Directory,
|
|
/// The path, relative to the root dir, that this `Path` represents.
|
|
/// Empty string means the root_dir is the path.
|
|
sub_path: []const u8 = "",
|
|
|
|
pub fn clone(p: Path, arena: Allocator) Allocator.Error!Path {
|
|
return .{
|
|
.root_dir = try p.root_dir.clone(arena),
|
|
.sub_path = try arena.dupe(u8, p.sub_path),
|
|
};
|
|
}
|
|
|
|
pub fn cwd() Path {
|
|
return initCwd("");
|
|
}
|
|
|
|
pub fn initCwd(sub_path: []const u8) Path {
|
|
return .{ .root_dir = Cache.Directory.cwd(), .sub_path = sub_path };
|
|
}
|
|
|
|
pub fn join(p: Path, arena: Allocator, sub_path: []const u8) Allocator.Error!Path {
|
|
if (sub_path.len == 0) return p;
|
|
const parts: []const []const u8 =
|
|
if (p.sub_path.len == 0) &.{sub_path} else &.{ p.sub_path, sub_path };
|
|
return .{
|
|
.root_dir = p.root_dir,
|
|
.sub_path = try fs.path.join(arena, parts),
|
|
};
|
|
}
|
|
|
|
pub fn resolvePosix(p: Path, arena: Allocator, sub_path: []const u8) Allocator.Error!Path {
|
|
if (sub_path.len == 0) return p;
|
|
const new_sub_path = try fs.path.resolvePosix(arena, &.{ p.sub_path, sub_path });
|
|
return .{
|
|
.root_dir = p.root_dir,
|
|
// Use "" instead of "." to represent `root_dir` itself.
|
|
.sub_path = if (std.mem.eql(u8, new_sub_path, ".")) "" else new_sub_path,
|
|
};
|
|
}
|
|
|
|
pub fn joinString(p: Path, gpa: Allocator, sub_path: []const u8) Allocator.Error![]u8 {
|
|
const parts: []const []const u8 =
|
|
if (p.sub_path.len == 0) &.{sub_path} else &.{ p.sub_path, sub_path };
|
|
return p.root_dir.join(gpa, parts);
|
|
}
|
|
|
|
pub fn joinStringZ(p: Path, gpa: Allocator, sub_path: []const u8) Allocator.Error![:0]u8 {
|
|
const parts: []const []const u8 =
|
|
if (p.sub_path.len == 0) &.{sub_path} else &.{ p.sub_path, sub_path };
|
|
return p.root_dir.joinZ(gpa, parts);
|
|
}
|
|
|
|
pub fn openFile(
|
|
p: Path,
|
|
sub_path: []const u8,
|
|
flags: fs.File.OpenFlags,
|
|
) !fs.File {
|
|
var buf: [fs.max_path_bytes]u8 = undefined;
|
|
const joined_path = if (p.sub_path.len == 0) sub_path else p: {
|
|
break :p std.fmt.bufPrint(&buf, "{s}" ++ fs.path.sep_str ++ "{s}", .{
|
|
p.sub_path, sub_path,
|
|
}) catch return error.NameTooLong;
|
|
};
|
|
return p.root_dir.handle.openFile(joined_path, flags);
|
|
}
|
|
|
|
pub fn openDir(
|
|
p: Path,
|
|
sub_path: []const u8,
|
|
args: fs.Dir.OpenOptions,
|
|
) fs.Dir.OpenError!fs.Dir {
|
|
var buf: [fs.max_path_bytes]u8 = undefined;
|
|
const joined_path = if (p.sub_path.len == 0) sub_path else p: {
|
|
break :p std.fmt.bufPrint(&buf, "{s}" ++ fs.path.sep_str ++ "{s}", .{
|
|
p.sub_path, sub_path,
|
|
}) catch return error.NameTooLong;
|
|
};
|
|
return p.root_dir.handle.openDir(joined_path, args);
|
|
}
|
|
|
|
pub fn makeOpenPath(p: Path, sub_path: []const u8, opts: fs.Dir.OpenOptions) !fs.Dir {
|
|
var buf: [fs.max_path_bytes]u8 = undefined;
|
|
const joined_path = if (p.sub_path.len == 0) sub_path else p: {
|
|
break :p std.fmt.bufPrint(&buf, "{s}" ++ fs.path.sep_str ++ "{s}", .{
|
|
p.sub_path, sub_path,
|
|
}) catch return error.NameTooLong;
|
|
};
|
|
return p.root_dir.handle.makeOpenPath(joined_path, opts);
|
|
}
|
|
|
|
pub fn statFile(p: Path, sub_path: []const u8) !fs.Dir.Stat {
|
|
var buf: [fs.max_path_bytes]u8 = undefined;
|
|
const joined_path = if (p.sub_path.len == 0) sub_path else p: {
|
|
break :p std.fmt.bufPrint(&buf, "{s}" ++ fs.path.sep_str ++ "{s}", .{
|
|
p.sub_path, sub_path,
|
|
}) catch return error.NameTooLong;
|
|
};
|
|
return p.root_dir.handle.statFile(joined_path);
|
|
}
|
|
|
|
pub fn atomicFile(
|
|
p: Path,
|
|
sub_path: []const u8,
|
|
options: fs.Dir.AtomicFileOptions,
|
|
buf: *[fs.max_path_bytes]u8,
|
|
) !fs.AtomicFile {
|
|
const joined_path = if (p.sub_path.len == 0) sub_path else p: {
|
|
break :p std.fmt.bufPrint(buf, "{s}" ++ fs.path.sep_str ++ "{s}", .{
|
|
p.sub_path, sub_path,
|
|
}) catch return error.NameTooLong;
|
|
};
|
|
return p.root_dir.handle.atomicFile(joined_path, options);
|
|
}
|
|
|
|
pub fn access(p: Path, sub_path: []const u8, flags: fs.File.OpenFlags) !void {
|
|
var buf: [fs.max_path_bytes]u8 = undefined;
|
|
const joined_path = if (p.sub_path.len == 0) sub_path else p: {
|
|
break :p std.fmt.bufPrint(&buf, "{s}" ++ fs.path.sep_str ++ "{s}", .{
|
|
p.sub_path, sub_path,
|
|
}) catch return error.NameTooLong;
|
|
};
|
|
return p.root_dir.handle.access(joined_path, flags);
|
|
}
|
|
|
|
pub fn makePath(p: Path, sub_path: []const u8) !void {
|
|
var buf: [fs.max_path_bytes]u8 = undefined;
|
|
const joined_path = if (p.sub_path.len == 0) sub_path else p: {
|
|
break :p std.fmt.bufPrint(&buf, "{s}" ++ fs.path.sep_str ++ "{s}", .{
|
|
p.sub_path, sub_path,
|
|
}) catch return error.NameTooLong;
|
|
};
|
|
return p.root_dir.handle.makePath(joined_path);
|
|
}
|
|
|
|
pub fn toString(p: Path, allocator: Allocator) Allocator.Error![]u8 {
|
|
return std.fmt.allocPrint(allocator, "{}", .{p});
|
|
}
|
|
|
|
pub fn toStringZ(p: Path, allocator: Allocator) Allocator.Error![:0]u8 {
|
|
return std.fmt.allocPrintZ(allocator, "{}", .{p});
|
|
}
|
|
|
|
pub fn format(
|
|
self: Path,
|
|
comptime fmt_string: []const u8,
|
|
options: std.fmt.FormatOptions,
|
|
writer: anytype,
|
|
) !void {
|
|
if (fmt_string.len == 1) {
|
|
// Quote-escape the string.
|
|
const stringEscape = std.zig.stringEscape;
|
|
const f = switch (fmt_string[0]) {
|
|
'q' => "",
|
|
'\'' => "\'",
|
|
else => @compileError("unsupported format string: " ++ fmt_string),
|
|
};
|
|
if (self.root_dir.path) |p| {
|
|
try stringEscape(p, f, options, writer);
|
|
if (self.sub_path.len > 0) try stringEscape(fs.path.sep_str, f, options, writer);
|
|
}
|
|
if (self.sub_path.len > 0) {
|
|
try stringEscape(self.sub_path, f, options, writer);
|
|
}
|
|
return;
|
|
}
|
|
if (fmt_string.len > 0)
|
|
std.fmt.invalidFmtError(fmt_string, self);
|
|
if (std.fs.path.isAbsolute(self.sub_path)) {
|
|
try writer.writeAll(self.sub_path);
|
|
return;
|
|
}
|
|
if (self.root_dir.path) |p| {
|
|
try writer.writeAll(p);
|
|
if (self.sub_path.len > 0) {
|
|
try writer.writeAll(fs.path.sep_str);
|
|
try writer.writeAll(self.sub_path);
|
|
}
|
|
return;
|
|
}
|
|
if (self.sub_path.len > 0) {
|
|
try writer.writeAll(self.sub_path);
|
|
return;
|
|
}
|
|
try writer.writeByte('.');
|
|
}
|
|
|
|
pub fn eql(self: Path, other: Path) bool {
|
|
return self.root_dir.eql(other.root_dir) and std.mem.eql(u8, self.sub_path, other.sub_path);
|
|
}
|
|
|
|
pub fn subPathOpt(self: Path) ?[]const u8 {
|
|
return if (self.sub_path.len == 0) null else self.sub_path;
|
|
}
|
|
|
|
pub fn subPathOrDot(self: Path) []const u8 {
|
|
return if (self.sub_path.len == 0) "." else self.sub_path;
|
|
}
|
|
|
|
pub fn stem(p: Path) []const u8 {
|
|
return fs.path.stem(p.sub_path);
|
|
}
|
|
|
|
pub fn basename(p: Path) []const u8 {
|
|
return fs.path.basename(p.sub_path);
|
|
}
|
|
|
|
/// Useful to make `Path` a key in `std.ArrayHashMap`.
|
|
pub const TableAdapter = struct {
|
|
pub const Hash = std.hash.Wyhash;
|
|
|
|
pub fn hash(self: TableAdapter, a: Cache.Path) u32 {
|
|
_ = self;
|
|
const seed = switch (@typeInfo(@TypeOf(a.root_dir.handle.fd))) {
|
|
.pointer => @intFromPtr(a.root_dir.handle.fd),
|
|
.int => @as(u32, @bitCast(a.root_dir.handle.fd)),
|
|
else => @compileError("unimplemented hash function"),
|
|
};
|
|
return @truncate(Hash.hash(seed, a.sub_path));
|
|
}
|
|
pub fn eql(self: TableAdapter, a: Cache.Path, b: Cache.Path, b_index: usize) bool {
|
|
_ = self;
|
|
_ = b_index;
|
|
return a.eql(b);
|
|
}
|
|
};
|
|
|
|
const Path = @This();
|
|
const std = @import("../../std.zig");
|
|
const fs = std.fs;
|
|
const Allocator = std.mem.Allocator;
|
|
const Cache = std.Build.Cache;
|