mirror of
https://codeberg.org/ziglang/zig.git
synced 2026-03-08 02:24:33 +01:00
std.fs.path: make relative a pure function
Instead of querying the operating system for current working directory and environment variables, this function now accepts those things as inputs.
This commit is contained in:
parent
b64491f2d6
commit
08447ca47e
15 changed files with 405 additions and 428 deletions
|
|
@ -84,6 +84,7 @@ pub fn main(init: process.Init.Minimal) !void {
|
|||
.io = io,
|
||||
.gpa = arena,
|
||||
.manifest_dir = try local_cache_directory.handle.createDirPathOpen(io, "h", .{}),
|
||||
.cwd = try process.getCwdAlloc(single_threaded_arena.allocator()),
|
||||
},
|
||||
.zig_exe = zig_exe,
|
||||
.env_map = try init.environ.createMap(arena),
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ pub fn main(init: std.process.Init.Minimal) void {
|
|||
}
|
||||
|
||||
if (need_simple) {
|
||||
return mainSimple() catch @panic("test failure\n");
|
||||
return mainSimple() catch @panic("test failure");
|
||||
}
|
||||
|
||||
const args = init.args.toSlice(fba.allocator()) catch @panic("unable to parse command line args");
|
||||
|
|
|
|||
|
|
@ -1738,8 +1738,7 @@ pub fn pathFromRoot(b: *Build, sub_path: []const u8) []u8 {
|
|||
}
|
||||
|
||||
fn pathFromCwd(b: *Build, sub_path: []const u8) []u8 {
|
||||
const cwd = process.getCwdAlloc(b.allocator) catch @panic("OOM");
|
||||
return b.pathResolve(&.{ cwd, sub_path });
|
||||
return b.pathResolve(&.{ b.graph.cache.cwd, sub_path });
|
||||
}
|
||||
|
||||
pub fn pathJoin(b: *Build, paths: []const []const u8) []u8 {
|
||||
|
|
|
|||
|
|
@ -30,6 +30,8 @@ mutex: Io.Mutex = .init,
|
|||
/// and usefulness of the cache for advanced use cases.
|
||||
prefixes_buffer: [4]Directory = undefined,
|
||||
prefixes_len: usize = 0,
|
||||
/// Used to identify prefixes. References external memory.
|
||||
cwd: []const u8,
|
||||
|
||||
pub const Path = @import("Cache/Path.zig");
|
||||
pub const Directory = @import("Cache/Directory.zig");
|
||||
|
|
@ -78,11 +80,12 @@ fn findPrefix(cache: *const Cache, file_path: []const u8) !PrefixedPath {
|
|||
/// Takes ownership of `resolved_path` on success.
|
||||
fn findPrefixResolved(cache: *const Cache, resolved_path: []u8) !PrefixedPath {
|
||||
const gpa = cache.gpa;
|
||||
const cwd = cache.cwd;
|
||||
const prefixes_slice = cache.prefixes();
|
||||
var i: u8 = 1; // Start at 1 to skip over checking the null prefix.
|
||||
while (i < prefixes_slice.len) : (i += 1) {
|
||||
const p = prefixes_slice[i].path.?;
|
||||
const sub_path = getPrefixSubpath(gpa, p, resolved_path) catch |err| switch (err) {
|
||||
const sub_path = getPrefixSubpath(gpa, cwd, p, resolved_path) catch |err| switch (err) {
|
||||
error.NotASubPath => continue,
|
||||
else => |e| return e,
|
||||
};
|
||||
|
|
@ -100,10 +103,10 @@ fn findPrefixResolved(cache: *const Cache, resolved_path: []u8) !PrefixedPath {
|
|||
};
|
||||
}
|
||||
|
||||
fn getPrefixSubpath(allocator: Allocator, prefix: []const u8, path: []u8) ![]u8 {
|
||||
const relative = try std.fs.path.relative(allocator, prefix, path);
|
||||
errdefer allocator.free(relative);
|
||||
var component_iterator = std.fs.path.NativeComponentIterator.init(relative);
|
||||
fn getPrefixSubpath(gpa: Allocator, cwd: []const u8, prefix: []const u8, path: []u8) ![]u8 {
|
||||
const relative = try std.fs.path.relative(gpa, cwd, null, prefix, path);
|
||||
errdefer gpa.free(relative);
|
||||
var component_iterator: std.fs.path.NativeComponentIterator = .init(relative);
|
||||
if (component_iterator.root() != null) {
|
||||
return error.NotASubPath;
|
||||
}
|
||||
|
|
@ -1307,11 +1310,14 @@ fn testGetCurrentFileTimestamp(io: Io, dir: Io.Dir) !Io.Timestamp {
|
|||
}
|
||||
|
||||
test "cache file and then recall it" {
|
||||
const io = std.testing.io;
|
||||
const io = testing.io;
|
||||
|
||||
var tmp = testing.tmpDir(.{});
|
||||
defer tmp.cleanup();
|
||||
|
||||
const cwd = try std.process.getCwdAlloc(testing.allocator);
|
||||
defer testing.allocator.free(cwd);
|
||||
|
||||
const temp_file = "test.txt";
|
||||
const temp_manifest_dir = "temp_manifest_dir";
|
||||
|
||||
|
|
@ -1331,6 +1337,7 @@ test "cache file and then recall it" {
|
|||
.io = io,
|
||||
.gpa = testing.allocator,
|
||||
.manifest_dir = try tmp.dir.createDirPathOpen(io, temp_manifest_dir, .{}),
|
||||
.cwd = cwd,
|
||||
};
|
||||
cache.addPrefix(.{ .path = null, .handle = tmp.dir });
|
||||
defer cache.manifest_dir.close(io);
|
||||
|
|
@ -1371,11 +1378,14 @@ test "cache file and then recall it" {
|
|||
}
|
||||
|
||||
test "check that changing a file makes cache fail" {
|
||||
const io = std.testing.io;
|
||||
const io = testing.io;
|
||||
|
||||
var tmp = testing.tmpDir(.{});
|
||||
defer tmp.cleanup();
|
||||
|
||||
const cwd = try std.process.getCwdAlloc(testing.allocator);
|
||||
defer testing.allocator.free(cwd);
|
||||
|
||||
const temp_file = "cache_hash_change_file_test.txt";
|
||||
const temp_manifest_dir = "cache_hash_change_file_manifest_dir";
|
||||
const original_temp_file_contents = "Hello, world!\n";
|
||||
|
|
@ -1397,6 +1407,7 @@ test "check that changing a file makes cache fail" {
|
|||
.io = io,
|
||||
.gpa = testing.allocator,
|
||||
.manifest_dir = try tmp.dir.createDirPathOpen(io, temp_manifest_dir, .{}),
|
||||
.cwd = cwd,
|
||||
};
|
||||
cache.addPrefix(.{ .path = null, .handle = tmp.dir });
|
||||
defer cache.manifest_dir.close(io);
|
||||
|
|
@ -1448,6 +1459,9 @@ test "no file inputs" {
|
|||
var tmp = testing.tmpDir(.{});
|
||||
defer tmp.cleanup();
|
||||
|
||||
const cwd = try std.process.getCwdAlloc(testing.allocator);
|
||||
defer testing.allocator.free(cwd);
|
||||
|
||||
const temp_manifest_dir = "no_file_inputs_manifest_dir";
|
||||
|
||||
var digest1: HexDigest = undefined;
|
||||
|
|
@ -1457,6 +1471,7 @@ test "no file inputs" {
|
|||
.io = io,
|
||||
.gpa = testing.allocator,
|
||||
.manifest_dir = try tmp.dir.createDirPathOpen(io, temp_manifest_dir, .{}),
|
||||
.cwd = cwd,
|
||||
};
|
||||
cache.addPrefix(.{ .path = null, .handle = tmp.dir });
|
||||
defer cache.manifest_dir.close(io);
|
||||
|
|
@ -1489,11 +1504,14 @@ test "no file inputs" {
|
|||
}
|
||||
|
||||
test "Manifest with files added after initial hash work" {
|
||||
const io = std.testing.io;
|
||||
const io = testing.io;
|
||||
|
||||
var tmp = testing.tmpDir(.{});
|
||||
defer tmp.cleanup();
|
||||
|
||||
const cwd = try std.process.getCwdAlloc(testing.allocator);
|
||||
defer testing.allocator.free(cwd);
|
||||
|
||||
const temp_file1 = "cache_hash_post_file_test1.txt";
|
||||
const temp_file2 = "cache_hash_post_file_test2.txt";
|
||||
const temp_manifest_dir = "cache_hash_post_file_manifest_dir";
|
||||
|
|
@ -1516,6 +1534,7 @@ test "Manifest with files added after initial hash work" {
|
|||
.io = io,
|
||||
.gpa = testing.allocator,
|
||||
.manifest_dir = try tmp.dir.createDirPathOpen(io, temp_manifest_dir, .{}),
|
||||
.cwd = cwd,
|
||||
};
|
||||
cache.addPrefix(.{ .path = null, .handle = tmp.dir });
|
||||
defer cache.manifest_dir.close(io);
|
||||
|
|
|
|||
|
|
@ -537,6 +537,9 @@ test Options {
|
|||
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
||||
defer arena.deinit();
|
||||
|
||||
const cwd = try std.process.getCwdAlloc(std.testing.allocator);
|
||||
defer std.testing.allocator.free(cwd);
|
||||
|
||||
var graph: std.Build.Graph = .{
|
||||
.io = io,
|
||||
.arena = arena.allocator(),
|
||||
|
|
@ -544,6 +547,7 @@ test Options {
|
|||
.io = io,
|
||||
.gpa = arena.allocator(),
|
||||
.manifest_dir = Io.Dir.cwd(),
|
||||
.cwd = cwd,
|
||||
},
|
||||
.zig_exe = "test",
|
||||
.env_map = std.process.Environ.Map.init(arena.allocator()),
|
||||
|
|
|
|||
|
|
@ -750,28 +750,30 @@ fn checksContainStderr(checks: []const StdIo.Check) bool {
|
|||
/// to make sure the child doesn't see paths relative to a cwd other than its own.
|
||||
fn convertPathArg(run: *Run, path: Build.Cache.Path) []const u8 {
|
||||
const b = run.step.owner;
|
||||
const path_str = path.toString(b.graph.arena) catch @panic("OOM");
|
||||
const graph = b.graph;
|
||||
const arena = graph.arena;
|
||||
|
||||
const path_str = path.toString(arena) catch @panic("OOM");
|
||||
if (Dir.path.isAbsolute(path_str)) {
|
||||
// Absolute paths don't need changing.
|
||||
return path_str;
|
||||
}
|
||||
const child_cwd_rel: []const u8 = rel: {
|
||||
const child_lazy_cwd = run.cwd orelse break :rel path_str;
|
||||
const child_cwd = child_lazy_cwd.getPath3(b, &run.step).toString(b.graph.arena) catch @panic("OOM");
|
||||
const child_cwd = child_lazy_cwd.getPath3(b, &run.step).toString(arena) catch @panic("OOM");
|
||||
// Convert it from relative to *our* cwd, to relative to the *child's* cwd.
|
||||
break :rel Dir.path.relative(b.graph.arena, child_cwd, path_str) catch @panic("OOM");
|
||||
break :rel Dir.path.relative(arena, graph.cache.cwd, &graph.env_map, child_cwd, path_str) catch @panic("OOM");
|
||||
};
|
||||
// Not every path can be made relative, e.g. if the path and the child cwd are on different
|
||||
// disk designators on Windows. In that case, `relative` will return an absolute path which we can
|
||||
// just return.
|
||||
if (Dir.path.isAbsolute(child_cwd_rel)) {
|
||||
return child_cwd_rel;
|
||||
}
|
||||
if (Dir.path.isAbsolute(child_cwd_rel)) return child_cwd_rel;
|
||||
|
||||
// We're not done yet. In some cases this path must be prefixed with './':
|
||||
// * On POSIX, the executable name cannot be a single component like 'foo'
|
||||
// * Some executables might treat a leading '-' like a flag, which we must avoid
|
||||
// There's no harm in it, so just *always* apply this prefix.
|
||||
return Dir.path.join(b.graph.arena, &.{ ".", child_cwd_rel }) catch @panic("OOM");
|
||||
return Dir.path.join(arena, &.{ ".", child_cwd_rel }) catch @panic("OOM");
|
||||
}
|
||||
|
||||
const IndexedOutput = struct {
|
||||
|
|
|
|||
|
|
@ -43,6 +43,8 @@ dispatch_queue: dispatch_queue_t,
|
|||
/// of writing. See the comment at the start of `wait` for details.
|
||||
since_event: FSEventStreamEventId,
|
||||
|
||||
cwd_path: []const u8,
|
||||
|
||||
/// All of the symbols we pull from the `dlopen`ed CoreServices framework. If any of these symbols
|
||||
/// is not present, `init` will close the framework and return an error.
|
||||
const ResolvedSymbols = struct {
|
||||
|
|
@ -78,7 +80,7 @@ const ResolvedSymbols = struct {
|
|||
kCFAllocatorUseContext: *const CFAllocatorRef,
|
||||
};
|
||||
|
||||
pub fn init() error{ OpenFrameworkFailed, MissingCoreServicesSymbol }!FsEvents {
|
||||
pub fn init(cwd_path: []const u8) error{ OpenFrameworkFailed, MissingCoreServicesSymbol }!FsEvents {
|
||||
var core_services = std.DynLib.open("/System/Library/Frameworks/CoreServices.framework/CoreServices") catch
|
||||
return error.OpenFrameworkFailed;
|
||||
errdefer core_services.close();
|
||||
|
|
@ -99,6 +101,7 @@ pub fn init() error{ OpenFrameworkFailed, MissingCoreServicesSymbol }!FsEvents {
|
|||
// Not `.since_now`, because this means we can init `FsEvents` *before* we do work in order
|
||||
// to notice any changes which happened during said work.
|
||||
.since_event = resolved_symbols.FSEventsGetCurrentEventId(),
|
||||
.cwd_path = cwd_path,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -120,9 +123,6 @@ pub fn setPaths(fse: *FsEvents, gpa: Allocator, steps: []const *std.Build.Step)
|
|||
defer fse.paths_arena = paths_arena_instance.state;
|
||||
const paths_arena = paths_arena_instance.allocator();
|
||||
|
||||
const cwd_path = try std.process.getCwdAlloc(gpa);
|
||||
defer gpa.free(cwd_path);
|
||||
|
||||
var need_dirs: std.StringArrayHashMapUnmanaged(void) = .empty;
|
||||
defer need_dirs.deinit(gpa);
|
||||
|
||||
|
|
@ -131,7 +131,9 @@ pub fn setPaths(fse: *FsEvents, gpa: Allocator, steps: []const *std.Build.Step)
|
|||
// We take `step` by pointer for a slight memory optimization in a moment.
|
||||
for (steps) |*step| {
|
||||
for (step.*.inputs.table.keys(), step.*.inputs.table.values()) |path, *files| {
|
||||
const resolved_dir = try std.fs.path.resolvePosix(paths_arena, &.{ cwd_path, path.root_dir.path orelse ".", path.sub_path });
|
||||
const resolved_dir = try std.fs.path.resolvePosix(paths_arena, &.{
|
||||
fse.cwd_path, path.root_dir.path orelse ".", path.sub_path,
|
||||
});
|
||||
try need_dirs.put(gpa, resolved_dir, {});
|
||||
for (files.items) |file_name| {
|
||||
const watch_path = if (std.mem.eql(u8, file_name, "."))
|
||||
|
|
|
|||
|
|
@ -482,8 +482,8 @@ pub fn serveFile(
|
|||
});
|
||||
}
|
||||
pub fn serveTarFile(ws: *WebServer, request: *http.Server.Request, paths: []const Cache.Path) !void {
|
||||
const gpa = ws.gpa;
|
||||
const io = ws.graph.io;
|
||||
const graph = ws.graph;
|
||||
const io = graph.io;
|
||||
|
||||
var send_buffer: [0x4000]u8 = undefined;
|
||||
var response = try request.respondStreaming(&send_buffer, .{
|
||||
|
|
@ -495,9 +495,6 @@ pub fn serveTarFile(ws: *WebServer, request: *http.Server.Request, paths: []cons
|
|||
},
|
||||
});
|
||||
|
||||
var cached_cwd_path: ?[]const u8 = null;
|
||||
defer if (cached_cwd_path) |p| gpa.free(p);
|
||||
|
||||
var archiver: std.tar.Writer = .{ .underlying_writer = &response.writer };
|
||||
|
||||
for (paths) |path| {
|
||||
|
|
@ -516,10 +513,7 @@ pub fn serveTarFile(ws: *WebServer, request: *http.Server.Request, paths: []cons
|
|||
// resulting in modules named "" and "src". The compiler needs to tell the build system
|
||||
// about the module graph so that the build system can correctly encode this information in
|
||||
// the tar file.
|
||||
archiver.prefix = path.root_dir.path orelse cwd: {
|
||||
if (cached_cwd_path == null) cached_cwd_path = try std.process.getCwdAlloc(gpa);
|
||||
break :cwd cached_cwd_path.?;
|
||||
};
|
||||
archiver.prefix = path.root_dir.path orelse graph.cache.cwd;
|
||||
try archiver.writeFile(path.sub_path, &file_reader, @intCast(stat.mtime.toSeconds()));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -86,14 +86,14 @@ pub const Argv0 = switch (native_os) {
|
|||
|
||||
const Environ = struct {
|
||||
/// Unmodified data directly from the OS.
|
||||
block: process.Environ.Block = &.{},
|
||||
process_environ: process.Environ = .empty,
|
||||
/// Protected by `mutex`. Determines whether the other fields have been
|
||||
/// memoized based on `block`.
|
||||
/// memoized based on `process_environ`.
|
||||
initialized: bool = false,
|
||||
/// Protected by `mutex`. Memoized based on `block`. Tracks whether the
|
||||
/// Protected by `mutex`. Memoized based on `process_environ`. Tracks whether the
|
||||
/// environment variables are present, ignoring their value.
|
||||
exist: Exist = .{},
|
||||
/// Protected by `mutex`. Memoized based on `block`.
|
||||
/// Protected by `mutex`. Memoized based on `process_environ`.
|
||||
string: String = .{},
|
||||
/// ZIG_PROGRESS
|
||||
zig_progress_handle: std.Progress.ParentFileError!u31 = error.EnvironmentVariableMissing,
|
||||
|
|
@ -1186,7 +1186,7 @@ pub fn init(
|
|||
.have_signal_handler = false,
|
||||
.argv0 = options.argv0,
|
||||
.worker_threads = .init(null),
|
||||
.environ = .{ .block = options.environ.block },
|
||||
.environ = .{ .process_environ = options.environ },
|
||||
.robust_cancel = options.robust_cancel,
|
||||
};
|
||||
|
||||
|
|
@ -12693,7 +12693,7 @@ fn scanEnviron(t: *Threaded) void {
|
|||
comptime assert(@sizeOf(Environ.String) == 0);
|
||||
}
|
||||
} else {
|
||||
for (t.environ.block) |opt_line| {
|
||||
for (t.environ.process_environ.block) |opt_line| {
|
||||
const line = opt_line.?;
|
||||
var line_i: usize = 0;
|
||||
while (line[line_i] != 0 and line[line_i] != '=') : (line_i += 1) {}
|
||||
|
|
@ -12837,7 +12837,7 @@ fn processSpawnPosix(userdata: ?*anyopaque, options: process.SpawnOptions) proce
|
|||
.zig_progress_fd = prog_fd,
|
||||
})).ptr;
|
||||
}
|
||||
break :m (try process.Environ.createBlockPosix(.{ .block = t.environ.block }, arena, .{
|
||||
break :m (try process.Environ.createBlockPosix(t.environ.process_environ, arena, .{
|
||||
.zig_progress_fd = prog_fd,
|
||||
})).ptr;
|
||||
};
|
||||
|
|
@ -12934,6 +12934,7 @@ fn processSpawnPosix(userdata: ?*anyopaque, options: process.SpawnOptions) proce
|
|||
|
||||
return .{
|
||||
.id = pid,
|
||||
.thread_handle = {},
|
||||
.stdin = switch (options.stdin) {
|
||||
.pipe => .{ .handle = stdin_pipe[1] },
|
||||
else => null,
|
||||
|
|
|
|||
|
|
@ -13,16 +13,15 @@
|
|||
//! https://github.com/WebAssembly/wasi-filesystem/issues/17#issuecomment-1430639353
|
||||
|
||||
const builtin = @import("builtin");
|
||||
const native_os = builtin.target.os.tag;
|
||||
|
||||
const std = @import("../std.zig");
|
||||
const debug = std.debug;
|
||||
const assert = debug.assert;
|
||||
const assert = std.debug.assert;
|
||||
const testing = std.testing;
|
||||
const mem = std.mem;
|
||||
const ascii = std.ascii;
|
||||
const Allocator = mem.Allocator;
|
||||
const windows = std.os.windows;
|
||||
const process = std.process;
|
||||
const native_os = builtin.target.os.tag;
|
||||
const Allocator = std.mem.Allocator;
|
||||
const eqlIgnoreCaseWtf8 = std.os.windows.eqlIgnoreCaseWtf8;
|
||||
const eqlIgnoreCaseWtf16 = std.os.windows.eqlIgnoreCaseWtf16;
|
||||
|
||||
pub const sep_windows: u8 = '\\';
|
||||
pub const sep_posix: u8 = '/';
|
||||
|
|
@ -281,7 +280,7 @@ pub fn isAbsolute(path: []const u8) bool {
|
|||
}
|
||||
|
||||
fn isAbsoluteWindowsImpl(comptime T: type, path: []const T) bool {
|
||||
return switch (windows.getWin32PathType(T, path)) {
|
||||
return switch (getWin32PathType(T, path)) {
|
||||
// Unambiguously absolute
|
||||
.drive_absolute, .unc_absolute, .local_device, .root_local_device => true,
|
||||
// Unambiguously relative
|
||||
|
|
@ -515,13 +514,13 @@ test parsePathPosix {
|
|||
|
||||
pub fn WindowsPath2(comptime T: type) type {
|
||||
return struct {
|
||||
kind: windows.Win32PathType,
|
||||
kind: Win32PathType,
|
||||
root: []const T,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn parsePathWindows(comptime T: type, path: []const T) WindowsPath2(T) {
|
||||
const kind = windows.getWin32PathType(T, path);
|
||||
const kind = getWin32PathType(T, path);
|
||||
const root = root: switch (kind) {
|
||||
.drive_absolute, .drive_relative => {
|
||||
const drive_letter_len = getDriveLetter(T, path).len;
|
||||
|
|
@ -731,7 +730,7 @@ fn parseUNC(comptime T: type, path: []const T) WindowsUNC(T) {
|
|||
// For the share, there can be any number of path separators between the server
|
||||
// and the share, so we want to skip over all of them instead of just looking for
|
||||
// the first one.
|
||||
var it = std.mem.tokenizeAny(T, path[server_end + 1 ..], any_sep);
|
||||
var it = mem.tokenizeAny(T, path[server_end + 1 ..], any_sep);
|
||||
const share = it.next() orelse return .{
|
||||
.server = path[2..server_end],
|
||||
.sep_after_server = true,
|
||||
|
|
@ -803,8 +802,8 @@ const DiskDesignatorKind = enum { drive, unc };
|
|||
/// `p1` and `p2` are both assumed to be the `kind` provided.
|
||||
fn compareDiskDesignators(comptime T: type, kind: DiskDesignatorKind, p1: []const T, p2: []const T) bool {
|
||||
const eql = switch (T) {
|
||||
u8 => windows.eqlIgnoreCaseWtf8,
|
||||
u16 => windows.eqlIgnoreCaseWtf16,
|
||||
u8 => eqlIgnoreCaseWtf8,
|
||||
u16 => eqlIgnoreCaseWtf16,
|
||||
else => @compileError("only u8 (WTF-8) and u16 (WTF-16LE) is supported"),
|
||||
};
|
||||
switch (kind) {
|
||||
|
|
@ -1094,10 +1093,14 @@ pub fn resolveWindows(allocator: Allocator, paths: []const []const u8) Allocator
|
|||
}
|
||||
|
||||
/// This function is like a series of `cd` statements executed one after another.
|
||||
///
|
||||
/// It resolves "." and ".." to the best of its ability, but will not convert relative paths to
|
||||
/// an absolute path, use Io.Dir.realpath instead.
|
||||
///
|
||||
/// ".." components may persist in the resolved path if the resolved path is relative.
|
||||
///
|
||||
/// The result does not have a trailing path separator.
|
||||
///
|
||||
/// This function does not perform any syscalls. Executing this series of path
|
||||
/// lookups on the actual filesystem may produce different results due to
|
||||
/// symlinks.
|
||||
|
|
@ -1494,25 +1497,54 @@ fn testBasenameWindows(input: []const u8, expected_output: []const u8) !void {
|
|||
try testing.expectEqualSlices(u8, expected_output, basenameWindows(input));
|
||||
}
|
||||
|
||||
pub const RelativeError = std.process.GetCwdAllocError;
|
||||
|
||||
/// Returns the relative path from `from` to `to`. If `from` and `to` each
|
||||
/// resolve to the same path (after calling `resolve` on each), a zero-length
|
||||
/// string is returned.
|
||||
/// On Windows, the result is not guaranteed to be relative, as the paths may be
|
||||
/// on different volumes. In that case, the result will be the canonicalized absolute
|
||||
/// path of `to`.
|
||||
pub fn relative(allocator: Allocator, from: []const u8, to: []const u8) RelativeError![]u8 {
|
||||
/// Returns the non-absolute path from `from` to `to`.
|
||||
///
|
||||
/// Other than memory allocation, this is a pure function; the result solely
|
||||
/// depends on the input parameters.
|
||||
///
|
||||
/// If `from` and `to` each resolve to the same path (after calling `resolve`
|
||||
/// on each), a zero-length string is returned.
|
||||
///
|
||||
/// See `relativePosix` and `relativeWindows` for operating system specific
|
||||
/// details and for how `env_map` is used.
|
||||
pub fn relative(
|
||||
gpa: Allocator,
|
||||
cwd: []const u8,
|
||||
env_map: ?*const std.process.Environ.Map,
|
||||
from: []const u8,
|
||||
to: []const u8,
|
||||
) Allocator.Error![]u8 {
|
||||
if (native_os == .windows) {
|
||||
return relativeWindows(allocator, from, to);
|
||||
return relativeWindows(gpa, cwd, env_map, from, to);
|
||||
} else {
|
||||
return relativePosix(allocator, from, to);
|
||||
return relativePosix(gpa, cwd, from, to);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn relativeWindows(allocator: Allocator, from: []const u8, to: []const u8) ![]u8 {
|
||||
if (native_os != .windows) @compileError("this function relies on Windows-specific semantics");
|
||||
|
||||
/// Returns the non-absolute path from `from` to `to` according to Windows rules.
|
||||
///
|
||||
/// Other than memory allocation, this is a pure function; the result solely
|
||||
/// depends on the input parameters.
|
||||
///
|
||||
/// If `from` and `to` each resolve to the same path (after calling `resolve`
|
||||
/// on each), a zero-length string is returned.
|
||||
///
|
||||
/// The result is not guaranteed to be relative, as the paths may be on
|
||||
/// different volumes. In that case, the result will be the canonicalized
|
||||
/// absolute path of `to`.
|
||||
///
|
||||
/// Per-drive CWDs are stored in special semi-hidden environment variables of
|
||||
/// the format `=<drive-letter>:`, e.g. `=C:`. This type of CWD is purely a
|
||||
/// shell concept, so there's no guarantee that it'll be set or that it'll even
|
||||
/// be accurate. This is the only reason for the `env_map` parameter. `null` is
|
||||
/// treated equivalent to the environment variable missing.
|
||||
pub fn relativeWindows(
|
||||
gpa: Allocator,
|
||||
cwd: []const u8,
|
||||
env_map: ?*const std.process.Environ.Map,
|
||||
from: []const u8,
|
||||
to: []const u8,
|
||||
) Allocator.Error![]u8 {
|
||||
const parsed_from = parsePathWindows(u8, from);
|
||||
const parsed_to = parsePathWindows(u8, to);
|
||||
|
||||
|
|
@ -1533,14 +1565,14 @@ pub fn relativeWindows(allocator: Allocator, from: []const u8, to: []const u8) !
|
|||
};
|
||||
|
||||
if (result_is_always_to) {
|
||||
return windowsResolveAgainstCwd(allocator, to, parsed_to);
|
||||
return windowsResolveAgainstCwd(gpa, cwd, env_map, to, parsed_to);
|
||||
}
|
||||
|
||||
const resolved_from = try windowsResolveAgainstCwd(allocator, from, parsed_from);
|
||||
defer allocator.free(resolved_from);
|
||||
const resolved_from = try windowsResolveAgainstCwd(gpa, cwd, env_map, from, parsed_from);
|
||||
defer gpa.free(resolved_from);
|
||||
var clean_up_resolved_to = true;
|
||||
const resolved_to = try windowsResolveAgainstCwd(allocator, to, parsed_to);
|
||||
defer if (clean_up_resolved_to) allocator.free(resolved_to);
|
||||
const resolved_to = try windowsResolveAgainstCwd(gpa, cwd, env_map, to, parsed_to);
|
||||
defer if (clean_up_resolved_to) gpa.free(resolved_to);
|
||||
|
||||
const parsed_resolved_from = parsePathWindows(u8, resolved_from);
|
||||
const parsed_resolved_to = parsePathWindows(u8, resolved_to);
|
||||
|
|
@ -1569,18 +1601,18 @@ pub fn relativeWindows(allocator: Allocator, from: []const u8, to: []const u8) !
|
|||
var from_it = mem.tokenizeAny(u8, resolved_from[parsed_resolved_from.root.len..], "/\\");
|
||||
var to_it = mem.tokenizeAny(u8, resolved_to[parsed_resolved_to.root.len..], "/\\");
|
||||
while (true) {
|
||||
const from_component = from_it.next() orelse return allocator.dupe(u8, to_it.rest());
|
||||
const from_component = from_it.next() orelse return gpa.dupe(u8, to_it.rest());
|
||||
const to_rest = to_it.rest();
|
||||
if (to_it.next()) |to_component| {
|
||||
if (windows.eqlIgnoreCaseWtf8(from_component, to_component))
|
||||
if (eqlIgnoreCaseWtf8(from_component, to_component))
|
||||
continue;
|
||||
}
|
||||
var up_index_end = "..".len;
|
||||
while (from_it.next()) |_| {
|
||||
up_index_end += "\\..".len;
|
||||
}
|
||||
const result = try allocator.alloc(u8, up_index_end + @intFromBool(to_rest.len > 0) + to_rest.len);
|
||||
errdefer allocator.free(result);
|
||||
const result = try gpa.alloc(u8, up_index_end + @intFromBool(to_rest.len > 0) + to_rest.len);
|
||||
errdefer gpa.free(result);
|
||||
|
||||
result[0..2].* = "..".*;
|
||||
var result_index: usize = 2;
|
||||
|
|
@ -1597,85 +1629,60 @@ pub fn relativeWindows(allocator: Allocator, from: []const u8, to: []const u8) !
|
|||
result_index += to_component.len;
|
||||
}
|
||||
|
||||
return allocator.realloc(result, result_index);
|
||||
return gpa.realloc(result, result_index);
|
||||
}
|
||||
return [_]u8{};
|
||||
}
|
||||
|
||||
fn windowsResolveAgainstCwd(allocator: Allocator, path: []const u8, parsed: WindowsPath2(u8)) ![]u8 {
|
||||
fn windowsResolveAgainstCwd(
|
||||
gpa: Allocator,
|
||||
cwd: []const u8,
|
||||
env_map: ?*const std.process.Environ.Map,
|
||||
path: []const u8,
|
||||
parsed: WindowsPath2(u8),
|
||||
) ![]u8 {
|
||||
// Space for 256 WTF-16 code units; potentially 3 WTF-8 bytes per WTF-16 code unit
|
||||
var temp_allocator_state = std.heap.stackFallback(256 * 3, allocator);
|
||||
var temp_allocator_state = std.heap.stackFallback(256 * 3, gpa);
|
||||
return switch (parsed.kind) {
|
||||
.drive_absolute,
|
||||
.unc_absolute,
|
||||
.root_local_device,
|
||||
.local_device,
|
||||
=> try resolveWindows(allocator, &.{path}),
|
||||
.relative => blk: {
|
||||
const temp_allocator = temp_allocator_state.get();
|
||||
=> try resolveWindows(gpa, &.{path}),
|
||||
|
||||
const peb_cwd = windows.peb().ProcessParameters.CurrentDirectory.DosPath;
|
||||
const cwd_w = (peb_cwd.Buffer.?)[0 .. peb_cwd.Length / 2];
|
||||
.relative => try resolveWindows(gpa, &.{ cwd, path }),
|
||||
|
||||
const wtf8_len = std.unicode.calcWtf8Len(cwd_w);
|
||||
const wtf8_buf = try temp_allocator.alloc(u8, wtf8_len);
|
||||
defer temp_allocator.free(wtf8_buf);
|
||||
assert(std.unicode.wtf16LeToWtf8(wtf8_buf, cwd_w) == wtf8_len);
|
||||
|
||||
break :blk try resolveWindows(allocator, &.{ wtf8_buf, path });
|
||||
},
|
||||
.rooted => blk: {
|
||||
const peb_cwd = windows.peb().ProcessParameters.CurrentDirectory.DosPath;
|
||||
const cwd_w = (peb_cwd.Buffer.?)[0 .. peb_cwd.Length / 2];
|
||||
const parsed_cwd = parsePathWindows(u16, cwd_w);
|
||||
const parsed_cwd = parsePathWindows(u8, cwd);
|
||||
switch (parsed_cwd.kind) {
|
||||
.drive_absolute => {
|
||||
var drive_buf = "_:\\".*;
|
||||
drive_buf[0] = @truncate(cwd_w[0]);
|
||||
break :blk try resolveWindows(allocator, &.{ &drive_buf, path });
|
||||
drive_buf[0] = cwd[0];
|
||||
break :blk try resolveWindows(gpa, &.{ &drive_buf, path });
|
||||
},
|
||||
.unc_absolute => {
|
||||
const temp_allocator = temp_allocator_state.get();
|
||||
var root_buf = try temp_allocator.alloc(u8, parsed_cwd.root.len * 3);
|
||||
defer temp_allocator.free(root_buf);
|
||||
|
||||
const wtf8_len = std.unicode.wtf16LeToWtf8(root_buf, parsed_cwd.root);
|
||||
const root = root_buf[0..wtf8_len];
|
||||
break :blk try resolveWindows(allocator, &.{ root, path });
|
||||
break :blk try resolveWindows(gpa, &.{ parsed_cwd.root, path });
|
||||
},
|
||||
// Effectively a malformed CWD, give up and just return a normalized path
|
||||
else => break :blk try resolveWindows(allocator, &.{path}),
|
||||
else => break :blk try resolveWindows(gpa, &.{path}),
|
||||
}
|
||||
},
|
||||
.drive_relative => blk: {
|
||||
const temp_allocator = temp_allocator_state.get();
|
||||
const drive_cwd = drive_cwd: {
|
||||
const peb_cwd = windows.peb().ProcessParameters.CurrentDirectory.DosPath;
|
||||
const cwd_w = (peb_cwd.Buffer.?)[0 .. peb_cwd.Length / 2];
|
||||
const parsed_cwd = parsePathWindows(u16, cwd_w);
|
||||
const parsed_cwd = parsePathWindows(u8, cwd);
|
||||
|
||||
if (parsed_cwd.kind == .drive_absolute) {
|
||||
const drive_letter_w = parsed_cwd.root[0];
|
||||
const drive_letters_match = drive_letter_w <= 0x7F and
|
||||
ascii.toUpper(@intCast(drive_letter_w)) == ascii.toUpper(parsed.root[0]);
|
||||
if (drive_letters_match) {
|
||||
const wtf8_len = std.unicode.calcWtf8Len(cwd_w);
|
||||
const wtf8_buf = try temp_allocator.alloc(u8, wtf8_len);
|
||||
assert(std.unicode.wtf16LeToWtf8(wtf8_buf, cwd_w) == wtf8_len);
|
||||
break :drive_cwd wtf8_buf[0..];
|
||||
}
|
||||
std.ascii.toUpper(@intCast(drive_letter_w)) == std.ascii.toUpper(parsed.root[0]);
|
||||
if (drive_letters_match)
|
||||
break :drive_cwd cwd;
|
||||
|
||||
// Per-drive CWD's are stored in special semi-hidden environment variables
|
||||
// of the format `=<drive-letter>:`, e.g. `=C:`. This type of CWD is
|
||||
// purely a shell concept, so there's no guarantee that it'll be set
|
||||
// or that it'll even be accurate.
|
||||
var key_buf = std.unicode.wtf8ToWtf16LeStringLiteral("=_:").*;
|
||||
key_buf[1] = parsed.root[0];
|
||||
if (std.process.getenvW(&key_buf)) |drive_cwd_w| {
|
||||
const wtf8_len = std.unicode.calcWtf8Len(drive_cwd_w);
|
||||
const wtf8_buf = try temp_allocator.alloc(u8, wtf8_len);
|
||||
assert(std.unicode.wtf16LeToWtf8(wtf8_buf, drive_cwd_w) == wtf8_len);
|
||||
break :drive_cwd wtf8_buf[0..];
|
||||
if (env_map) |m| {
|
||||
if (m.get(&.{ '=', parsed.root[0], ':' })) |v| {
|
||||
break :drive_cwd try temp_allocator.dupe(u8, v);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1686,16 +1693,20 @@ fn windowsResolveAgainstCwd(allocator: Allocator, path: []const u8, parsed: Wind
|
|||
break :drive_cwd drive_buf;
|
||||
};
|
||||
defer temp_allocator.free(drive_cwd);
|
||||
break :blk try resolveWindows(allocator, &.{ drive_cwd, path });
|
||||
break :blk try resolveWindows(gpa, &.{ drive_cwd, path });
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
pub fn relativePosix(allocator: Allocator, from: []const u8, to: []const u8) ![]u8 {
|
||||
if (native_os == .windows) @compileError("this function relies on semantics that do not apply to Windows");
|
||||
|
||||
const cwd = try process.getCwdAlloc(allocator);
|
||||
defer allocator.free(cwd);
|
||||
/// Returns the non-absolute path from `from` to `to` according to Windows rules.
|
||||
///
|
||||
/// Other than memory allocation, this is a pure function; the result solely
|
||||
/// depends on the input parameters.
|
||||
///
|
||||
/// If `from` and `to` each resolve to the same path (after calling `resolve`
|
||||
/// on each), a zero-length string is returned.
|
||||
///
|
||||
pub fn relativePosix(allocator: Allocator, cwd: []const u8, from: []const u8, to: []const u8) Allocator.Error![]u8 {
|
||||
const resolved_from = try resolvePosix(allocator, &[_][]const u8{ cwd, from });
|
||||
defer allocator.free(resolved_from);
|
||||
const resolved_to = try resolvePosix(allocator, &[_][]const u8{ cwd, to });
|
||||
|
|
@ -1736,69 +1747,67 @@ pub fn relativePosix(allocator: Allocator, from: []const u8, to: []const u8) ![]
|
|||
}
|
||||
|
||||
test relative {
|
||||
if (native_os == .windows) {
|
||||
try testRelativeWindows("c:/blah\\blah", "d:/games", "D:\\games");
|
||||
try testRelativeWindows("c:/aaaa/bbbb", "c:/aaaa", "..");
|
||||
try testRelativeWindows("c:/aaaa/bbbb", "c:/cccc", "..\\..\\cccc");
|
||||
try testRelativeWindows("c:/aaaa/bbbb", "C:/aaaa/bbbb", "");
|
||||
try testRelativeWindows("c:/aaaa/bbbb", "c:/aaaa/cccc", "..\\cccc");
|
||||
try testRelativeWindows("c:/aaaa/", "c:/aaaa/cccc", "cccc");
|
||||
try testRelativeWindows("c:/", "c:\\aaaa\\bbbb", "aaaa\\bbbb");
|
||||
try testRelativeWindows("c:/aaaa/bbbb", "d:\\", "D:\\");
|
||||
try testRelativeWindows("c:/AaAa/bbbb", "c:/aaaa/bbbb", "");
|
||||
try testRelativeWindows("c:/aaaaa/", "c:/aaaa/cccc", "..\\aaaa\\cccc");
|
||||
try testRelativeWindows("C:\\foo\\bar\\baz\\quux", "C:\\", "..\\..\\..\\..");
|
||||
try testRelativeWindows("C:\\foo\\test", "C:\\foo\\test\\bar\\package.json", "bar\\package.json");
|
||||
try testRelativeWindows("C:\\foo\\bar\\baz-quux", "C:\\foo\\bar\\baz", "..\\baz");
|
||||
try testRelativeWindows("C:\\foo\\bar\\baz", "C:\\foo\\bar\\baz-quux", "..\\baz-quux");
|
||||
try testRelativeWindows("\\\\foo\\bar", "\\\\foo\\bar\\baz", "baz");
|
||||
try testRelativeWindows("\\\\foo\\bar\\baz", "\\\\foo\\bar", "..");
|
||||
try testRelativeWindows("\\\\foo\\bar\\baz-quux", "\\\\foo\\bar\\baz", "..\\baz");
|
||||
try testRelativeWindows("\\\\foo/bar\\baz-quux", "//foo\\bar/baz", "..\\baz");
|
||||
try testRelativeWindows("\\\\foo\\bar\\baz", "\\\\foo\\bar\\baz-quux", "..\\baz-quux");
|
||||
try testRelativeWindows("C:\\baz-quux", "C:\\baz", "..\\baz");
|
||||
try testRelativeWindows("C:\\baz", "C:\\baz-quux", "..\\baz-quux");
|
||||
try testRelativeWindows("\\\\foo\\baz-quux", "\\\\foo\\baz", "\\\\foo\\baz");
|
||||
try testRelativeWindows("\\\\foo\\baz", "\\\\foo\\baz-quux", "\\\\foo\\baz-quux");
|
||||
try testRelativeWindows("C:\\baz", "\\\\foo\\bar\\baz", "\\\\foo\\bar\\baz");
|
||||
try testRelativeWindows("\\\\foo\\bar\\baz", "C:\\baz", "C:\\baz");
|
||||
try testRelativeWindows("c:/blah\\blah", "d:/games", "D:\\games");
|
||||
try testRelativeWindows("c:/aaaa/bbbb", "c:/aaaa", "..");
|
||||
try testRelativeWindows("c:/aaaa/bbbb", "c:/cccc", "..\\..\\cccc");
|
||||
try testRelativeWindows("c:/aaaa/bbbb", "C:/aaaa/bbbb", "");
|
||||
try testRelativeWindows("c:/aaaa/bbbb", "c:/aaaa/cccc", "..\\cccc");
|
||||
try testRelativeWindows("c:/aaaa/", "c:/aaaa/cccc", "cccc");
|
||||
try testRelativeWindows("c:/", "c:\\aaaa\\bbbb", "aaaa\\bbbb");
|
||||
try testRelativeWindows("c:/aaaa/bbbb", "d:\\", "D:\\");
|
||||
try testRelativeWindows("c:/AaAa/bbbb", "c:/aaaa/bbbb", "");
|
||||
try testRelativeWindows("c:/aaaaa/", "c:/aaaa/cccc", "..\\aaaa\\cccc");
|
||||
try testRelativeWindows("C:\\foo\\bar\\baz\\quux", "C:\\", "..\\..\\..\\..");
|
||||
try testRelativeWindows("C:\\foo\\test", "C:\\foo\\test\\bar\\package.json", "bar\\package.json");
|
||||
try testRelativeWindows("C:\\foo\\bar\\baz-quux", "C:\\foo\\bar\\baz", "..\\baz");
|
||||
try testRelativeWindows("C:\\foo\\bar\\baz", "C:\\foo\\bar\\baz-quux", "..\\baz-quux");
|
||||
try testRelativeWindows("\\\\foo\\bar", "\\\\foo\\bar\\baz", "baz");
|
||||
try testRelativeWindows("\\\\foo\\bar\\baz", "\\\\foo\\bar", "..");
|
||||
try testRelativeWindows("\\\\foo\\bar\\baz-quux", "\\\\foo\\bar\\baz", "..\\baz");
|
||||
try testRelativeWindows("\\\\foo/bar\\baz-quux", "//foo\\bar/baz", "..\\baz");
|
||||
try testRelativeWindows("\\\\foo\\bar\\baz", "\\\\foo\\bar\\baz-quux", "..\\baz-quux");
|
||||
try testRelativeWindows("C:\\baz-quux", "C:\\baz", "..\\baz");
|
||||
try testRelativeWindows("C:\\baz", "C:\\baz-quux", "..\\baz-quux");
|
||||
try testRelativeWindows("\\\\foo\\baz-quux", "\\\\foo\\baz", "\\\\foo\\baz");
|
||||
try testRelativeWindows("\\\\foo\\baz", "\\\\foo\\baz-quux", "\\\\foo\\baz-quux");
|
||||
try testRelativeWindows("C:\\baz", "\\\\foo\\bar\\baz", "\\\\foo\\bar\\baz");
|
||||
try testRelativeWindows("\\\\foo\\bar\\baz", "C:\\baz", "C:\\baz");
|
||||
|
||||
try testRelativeWindows("c:blah\\blah", "c:foo", "..\\..\\foo");
|
||||
try testRelativeWindows("c:foo", "c:foo\\bar", "bar");
|
||||
try testRelativeWindows("\\blah\\blah", "\\foo", "..\\..\\foo");
|
||||
try testRelativeWindows("\\foo", "\\foo\\bar", "bar");
|
||||
try testRelativeWindows("c:blah\\blah", "c:foo", "..\\..\\foo");
|
||||
try testRelativeWindows("c:foo", "c:foo\\bar", "bar");
|
||||
try testRelativeWindows("\\blah\\blah", "\\foo", "..\\..\\foo");
|
||||
try testRelativeWindows("\\foo", "\\foo\\bar", "bar");
|
||||
|
||||
try testRelativeWindows("a/b/c", "a\\b", "..");
|
||||
try testRelativeWindows("a/b/c", "a", "..\\..");
|
||||
try testRelativeWindows("a/b/c", "a\\b\\c\\d", "d");
|
||||
try testRelativeWindows("a/b/c", "a\\b", "..");
|
||||
try testRelativeWindows("a/b/c", "a", "..\\..");
|
||||
try testRelativeWindows("a/b/c", "a\\b\\c\\d", "d");
|
||||
|
||||
try testRelativeWindows("\\\\FOO\\bar\\baz", "\\\\foo\\BAR\\BAZ", "");
|
||||
// Unicode-aware case-insensitive path comparison
|
||||
try testRelativeWindows("\\\\кириллица\\ελληνικά\\português", "\\\\КИРИЛЛИЦА\\ΕΛΛΗΝΙΚΆ\\PORTUGUÊS", "");
|
||||
} else {
|
||||
try testRelativePosix("/var/lib", "/var", "..");
|
||||
try testRelativePosix("/var/lib", "/bin", "../../bin");
|
||||
try testRelativePosix("/var/lib", "/var/lib", "");
|
||||
try testRelativePosix("/var/lib", "/var/apache", "../apache");
|
||||
try testRelativePosix("/var/", "/var/lib", "lib");
|
||||
try testRelativePosix("/", "/var/lib", "var/lib");
|
||||
try testRelativePosix("/foo/test", "/foo/test/bar/package.json", "bar/package.json");
|
||||
try testRelativePosix("/Users/a/web/b/test/mails", "/Users/a/web/b", "../..");
|
||||
try testRelativePosix("/foo/bar/baz-quux", "/foo/bar/baz", "../baz");
|
||||
try testRelativePosix("/foo/bar/baz", "/foo/bar/baz-quux", "../baz-quux");
|
||||
try testRelativePosix("/baz-quux", "/baz", "../baz");
|
||||
try testRelativePosix("/baz", "/baz-quux", "../baz-quux");
|
||||
}
|
||||
try testRelativeWindows("\\\\FOO\\bar\\baz", "\\\\foo\\BAR\\BAZ", "");
|
||||
// Unicode-aware case-insensitive path comparison
|
||||
try testRelativeWindows("\\\\кириллица\\ελληνικά\\português", "\\\\КИРИЛЛИЦА\\ΕΛΛΗΝΙΚΆ\\PORTUGUÊS", "");
|
||||
|
||||
try testRelativePosix("/var/lib", "/var", "..");
|
||||
try testRelativePosix("/var/lib", "/bin", "../../bin");
|
||||
try testRelativePosix("/var/lib", "/var/lib", "");
|
||||
try testRelativePosix("/var/lib", "/var/apache", "../apache");
|
||||
try testRelativePosix("/var/", "/var/lib", "lib");
|
||||
try testRelativePosix("/", "/var/lib", "var/lib");
|
||||
try testRelativePosix("/foo/test", "/foo/test/bar/package.json", "bar/package.json");
|
||||
try testRelativePosix("/Users/a/web/b/test/mails", "/Users/a/web/b", "../..");
|
||||
try testRelativePosix("/foo/bar/baz-quux", "/foo/bar/baz", "../baz");
|
||||
try testRelativePosix("/foo/bar/baz", "/foo/bar/baz-quux", "../baz-quux");
|
||||
try testRelativePosix("/baz-quux", "/baz", "../baz");
|
||||
try testRelativePosix("/baz", "/baz-quux", "../baz-quux");
|
||||
}
|
||||
|
||||
fn testRelativePosix(from: []const u8, to: []const u8, expected_output: []const u8) !void {
|
||||
const result = try relativePosix(testing.allocator, from, to);
|
||||
const result = try relativePosix(testing.allocator, ".", from, to);
|
||||
defer testing.allocator.free(result);
|
||||
try testing.expectEqualStrings(expected_output, result);
|
||||
}
|
||||
|
||||
fn testRelativeWindows(from: []const u8, to: []const u8, expected_output: []const u8) !void {
|
||||
const result = try relativeWindows(testing.allocator, from, to);
|
||||
const result = try relativeWindows(testing.allocator, ".", null, from, to);
|
||||
defer testing.allocator.free(result);
|
||||
try testing.expectEqualStrings(expected_output, result);
|
||||
}
|
||||
|
|
@ -2554,3 +2563,124 @@ pub const fmtAsUtf8Lossy = std.unicode.fmtUtf8;
|
|||
/// a lossy conversion if the path contains any unpaired surrogates.
|
||||
/// Unpaired surrogates are replaced by the replacement character (U+FFFD).
|
||||
pub const fmtWtf16LeAsUtf8Lossy = std.unicode.fmtUtf16Le;
|
||||
|
||||
/// Similar to `RTL_PATH_TYPE`, but without the `UNKNOWN` path type.
|
||||
pub const Win32PathType = enum {
|
||||
/// `\\server\share\foo`
|
||||
unc_absolute,
|
||||
/// `C:\foo`
|
||||
drive_absolute,
|
||||
/// `C:foo`
|
||||
drive_relative,
|
||||
/// `\foo`
|
||||
rooted,
|
||||
/// `foo`
|
||||
relative,
|
||||
/// `\\.\foo`, `\\?\foo`
|
||||
local_device,
|
||||
/// `\\.`, `\\?`
|
||||
root_local_device,
|
||||
};
|
||||
|
||||
/// Get the path type of a Win32 namespace path.
|
||||
/// Similar to `RtlDetermineDosPathNameType_U`.
|
||||
/// If `T` is `u16`, then `path` should be encoded as WTF-16LE.
|
||||
pub fn getWin32PathType(comptime T: type, path: []const T) Win32PathType {
|
||||
if (path.len < 1) return .relative;
|
||||
|
||||
const windows_path = std.fs.path.PathType.windows;
|
||||
if (windows_path.isSep(T, path[0])) {
|
||||
// \x
|
||||
if (path.len < 2 or !windows_path.isSep(T, path[1])) return .rooted;
|
||||
// \\. or \\?
|
||||
if (path.len > 2 and (path[2] == mem.nativeToLittle(T, '.') or path[2] == mem.nativeToLittle(T, '?'))) {
|
||||
// exactly \\. or \\? with nothing trailing
|
||||
if (path.len == 3) return .root_local_device;
|
||||
// \\.\x or \\?\x
|
||||
if (windows_path.isSep(T, path[3])) return .local_device;
|
||||
}
|
||||
// \\x
|
||||
return .unc_absolute;
|
||||
} else {
|
||||
// Some choice has to be made about how non-ASCII code points as drive-letters are handled, since
|
||||
// path[0] is a different size for WTF-16 vs WTF-8, leading to a potential mismatch in classification
|
||||
// for a WTF-8 path and its WTF-16 equivalent. For example, `€:\` encoded in WTF-16 is three code
|
||||
// units `<0x20AC>:\` whereas `€:\` encoded as WTF-8 is 6 code units `<0xE2><0x82><0xAC>:\` so
|
||||
// checking path[0], path[1] and path[2] would not behave the same between WTF-8/WTF-16.
|
||||
//
|
||||
// `RtlDetermineDosPathNameType_U` exclusively deals with WTF-16 and considers
|
||||
// `€:\` a drive-absolute path, but code points that take two WTF-16 code units to encode get
|
||||
// classified as a relative path (e.g. with U+20000 as the drive-letter that'd be encoded
|
||||
// in WTF-16 as `<0xD840><0xDC00>:\` and be considered a relative path).
|
||||
//
|
||||
// The choice made here is to emulate the behavior of `RtlDetermineDosPathNameType_U` for both
|
||||
// WTF-16 and WTF-8. This is because, while unlikely and not supported by the Disk Manager GUI,
|
||||
// drive letters are not actually restricted to A-Z. Using `SetVolumeMountPointW` will allow you
|
||||
// to set any byte value as a drive letter, and going through `IOCTL_MOUNTMGR_CREATE_POINT` will
|
||||
// allow you to set any WTF-16 code unit as a drive letter.
|
||||
//
|
||||
// Non-A-Z drive letters don't interact well with most of Windows, but certain things do work, e.g.
|
||||
// `cd /D €:\` will work, filesystem functions still work, etc.
|
||||
//
|
||||
// The unfortunate part of this is that this makes handling WTF-8 more complicated as we can't
|
||||
// just check path[0], path[1], path[2].
|
||||
const colon_i: usize = switch (T) {
|
||||
u8 => i: {
|
||||
const code_point_len = std.unicode.utf8ByteSequenceLength(path[0]) catch return .relative;
|
||||
// Conveniently, 4-byte sequences in WTF-8 have the same starting code point
|
||||
// as 2-code-unit sequences in WTF-16.
|
||||
if (code_point_len > 3) return .relative;
|
||||
break :i code_point_len;
|
||||
},
|
||||
u16 => 1,
|
||||
else => @compileError("unsupported type: " ++ @typeName(T)),
|
||||
};
|
||||
// x
|
||||
if (path.len < colon_i + 1 or path[colon_i] != mem.nativeToLittle(T, ':')) return .relative;
|
||||
// x:\
|
||||
if (path.len > colon_i + 1 and windows_path.isSep(T, path[colon_i + 1])) return .drive_absolute;
|
||||
// x:
|
||||
return .drive_relative;
|
||||
}
|
||||
}
|
||||
|
||||
test getWin32PathType {
|
||||
try std.testing.expectEqual(.relative, getWin32PathType(u8, ""));
|
||||
try std.testing.expectEqual(.relative, getWin32PathType(u8, "x"));
|
||||
try std.testing.expectEqual(.relative, getWin32PathType(u8, "x\\"));
|
||||
|
||||
try std.testing.expectEqual(.root_local_device, getWin32PathType(u8, "//."));
|
||||
try std.testing.expectEqual(.root_local_device, getWin32PathType(u8, "/\\?"));
|
||||
try std.testing.expectEqual(.root_local_device, getWin32PathType(u8, "\\\\?"));
|
||||
|
||||
try std.testing.expectEqual(.local_device, getWin32PathType(u8, "//./x"));
|
||||
try std.testing.expectEqual(.local_device, getWin32PathType(u8, "/\\?\\x"));
|
||||
try std.testing.expectEqual(.local_device, getWin32PathType(u8, "\\\\?\\x"));
|
||||
// local device paths require a path separator after the root, otherwise it is considered a UNC path
|
||||
try std.testing.expectEqual(.unc_absolute, getWin32PathType(u8, "\\\\?x"));
|
||||
try std.testing.expectEqual(.unc_absolute, getWin32PathType(u8, "//.x"));
|
||||
|
||||
try std.testing.expectEqual(.unc_absolute, getWin32PathType(u8, "//"));
|
||||
try std.testing.expectEqual(.unc_absolute, getWin32PathType(u8, "\\\\x"));
|
||||
try std.testing.expectEqual(.unc_absolute, getWin32PathType(u8, "//x"));
|
||||
|
||||
try std.testing.expectEqual(.rooted, getWin32PathType(u8, "\\x"));
|
||||
try std.testing.expectEqual(.rooted, getWin32PathType(u8, "/"));
|
||||
|
||||
try std.testing.expectEqual(.drive_relative, getWin32PathType(u8, "x:"));
|
||||
try std.testing.expectEqual(.drive_relative, getWin32PathType(u8, "x:abc"));
|
||||
try std.testing.expectEqual(.drive_relative, getWin32PathType(u8, "x:a/b/c"));
|
||||
|
||||
try std.testing.expectEqual(.drive_absolute, getWin32PathType(u8, "x:\\"));
|
||||
try std.testing.expectEqual(.drive_absolute, getWin32PathType(u8, "x:\\abc"));
|
||||
try std.testing.expectEqual(.drive_absolute, getWin32PathType(u8, "x:/a/b/c"));
|
||||
|
||||
// Non-ASCII code point that is encoded as one WTF-16 code unit is considered a valid drive letter
|
||||
try std.testing.expectEqual(.drive_absolute, getWin32PathType(u8, "€:\\"));
|
||||
try std.testing.expectEqual(.drive_absolute, getWin32PathType(u16, std.unicode.wtf8ToWtf16LeStringLiteral("€:\\")));
|
||||
try std.testing.expectEqual(.drive_relative, getWin32PathType(u8, "€:"));
|
||||
try std.testing.expectEqual(.drive_relative, getWin32PathType(u16, std.unicode.wtf8ToWtf16LeStringLiteral("€:")));
|
||||
// But code points that are encoded as two WTF-16 code units are not
|
||||
try std.testing.expectEqual(.relative, getWin32PathType(u8, "\u{10000}:\\"));
|
||||
try std.testing.expectEqual(.relative, getWin32PathType(u16, std.unicode.wtf8ToWtf16LeStringLiteral("\u{10000}:\\")));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2927,7 +2927,7 @@ pub fn CreateSymbolicLink(
|
|||
// Already an NT path, no need to do anything to it
|
||||
break :target_path target_path;
|
||||
} else {
|
||||
switch (getWin32PathType(u16, target_path)) {
|
||||
switch (std.fs.path.getWin32PathType(u16, target_path)) {
|
||||
// Rooted paths need to avoid getting put through wToPrefixedFileW
|
||||
// (and they are treated as relative in this context)
|
||||
// Note: It seems that rooted paths in symbolic links are relative to
|
||||
|
|
@ -4235,7 +4235,7 @@ pub const RemoveDotDirsError = error{TooManyParentDirs};
|
|||
/// 2) all repeating back slashes have been collapsed
|
||||
/// 3) the path is a relative one (does not start with a back slash)
|
||||
pub fn removeDotDirsSanitized(comptime T: type, path: []T) RemoveDotDirsError!usize {
|
||||
std.debug.assert(path.len == 0 or path[0] != '\\');
|
||||
assert(path.len == 0 or path[0] != '\\');
|
||||
|
||||
var write_idx: usize = 0;
|
||||
var read_idx: usize = 0;
|
||||
|
|
@ -4251,7 +4251,7 @@ pub fn removeDotDirsSanitized(comptime T: type, path: []T) RemoveDotDirsError!us
|
|||
}
|
||||
if (after_dot == '.' and (read_idx + 2 == path.len or path[read_idx + 2] == '\\')) {
|
||||
if (write_idx == 0) return error.TooManyParentDirs;
|
||||
std.debug.assert(write_idx >= 2);
|
||||
assert(write_idx >= 2);
|
||||
write_idx -= 1;
|
||||
while (true) {
|
||||
write_idx -= 1;
|
||||
|
|
@ -4353,7 +4353,7 @@ pub fn wToPrefixedFileW(dir: ?HANDLE, path: [:0]const u16) Wtf16ToPrefixedFileWE
|
|||
path_space.data[path_space.len] = 0;
|
||||
return path_space;
|
||||
} else {
|
||||
const path_type = getWin32PathType(u16, path);
|
||||
const path_type = std.fs.path.getWin32PathType(u16, path);
|
||||
var path_space: PathSpace = undefined;
|
||||
if (path_type == .local_device) {
|
||||
switch (getLocalDevicePathType(u16, path)) {
|
||||
|
|
@ -4491,8 +4491,8 @@ pub fn wToPrefixedFileW(dir: ?HANDLE, path: [:0]const u16) Wtf16ToPrefixedFileWE
|
|||
if (path_type == .unc_absolute) {
|
||||
// Now add in the UNC, the `C` should overwrite the first `\` of the
|
||||
// FullPathName, ultimately resulting in `\??\UNC\<the rest of the path>`
|
||||
std.debug.assert(path_space.data[path_buf_offset] == '\\');
|
||||
std.debug.assert(path_space.data[path_buf_offset + 1] == '\\');
|
||||
assert(path_space.data[path_buf_offset] == '\\');
|
||||
assert(path_space.data[path_buf_offset + 1] == '\\');
|
||||
const unc = [_]u16{ 'U', 'N', 'C' };
|
||||
path_space.data[nt_prefix.len..][0..unc.len].* = unc;
|
||||
}
|
||||
|
|
@ -4500,127 +4500,6 @@ pub fn wToPrefixedFileW(dir: ?HANDLE, path: [:0]const u16) Wtf16ToPrefixedFileWE
|
|||
}
|
||||
}
|
||||
|
||||
/// Similar to `RTL_PATH_TYPE`, but without the `UNKNOWN` path type.
|
||||
pub const Win32PathType = enum {
|
||||
/// `\\server\share\foo`
|
||||
unc_absolute,
|
||||
/// `C:\foo`
|
||||
drive_absolute,
|
||||
/// `C:foo`
|
||||
drive_relative,
|
||||
/// `\foo`
|
||||
rooted,
|
||||
/// `foo`
|
||||
relative,
|
||||
/// `\\.\foo`, `\\?\foo`
|
||||
local_device,
|
||||
/// `\\.`, `\\?`
|
||||
root_local_device,
|
||||
};
|
||||
|
||||
/// Get the path type of a Win32 namespace path.
|
||||
/// Similar to `RtlDetermineDosPathNameType_U`.
|
||||
/// If `T` is `u16`, then `path` should be encoded as WTF-16LE.
|
||||
pub fn getWin32PathType(comptime T: type, path: []const T) Win32PathType {
|
||||
if (path.len < 1) return .relative;
|
||||
|
||||
const windows_path = std.fs.path.PathType.windows;
|
||||
if (windows_path.isSep(T, path[0])) {
|
||||
// \x
|
||||
if (path.len < 2 or !windows_path.isSep(T, path[1])) return .rooted;
|
||||
// \\. or \\?
|
||||
if (path.len > 2 and (path[2] == mem.nativeToLittle(T, '.') or path[2] == mem.nativeToLittle(T, '?'))) {
|
||||
// exactly \\. or \\? with nothing trailing
|
||||
if (path.len == 3) return .root_local_device;
|
||||
// \\.\x or \\?\x
|
||||
if (windows_path.isSep(T, path[3])) return .local_device;
|
||||
}
|
||||
// \\x
|
||||
return .unc_absolute;
|
||||
} else {
|
||||
// Some choice has to be made about how non-ASCII code points as drive-letters are handled, since
|
||||
// path[0] is a different size for WTF-16 vs WTF-8, leading to a potential mismatch in classification
|
||||
// for a WTF-8 path and its WTF-16 equivalent. For example, `€:\` encoded in WTF-16 is three code
|
||||
// units `<0x20AC>:\` whereas `€:\` encoded as WTF-8 is 6 code units `<0xE2><0x82><0xAC>:\` so
|
||||
// checking path[0], path[1] and path[2] would not behave the same between WTF-8/WTF-16.
|
||||
//
|
||||
// `RtlDetermineDosPathNameType_U` exclusively deals with WTF-16 and considers
|
||||
// `€:\` a drive-absolute path, but code points that take two WTF-16 code units to encode get
|
||||
// classified as a relative path (e.g. with U+20000 as the drive-letter that'd be encoded
|
||||
// in WTF-16 as `<0xD840><0xDC00>:\` and be considered a relative path).
|
||||
//
|
||||
// The choice made here is to emulate the behavior of `RtlDetermineDosPathNameType_U` for both
|
||||
// WTF-16 and WTF-8. This is because, while unlikely and not supported by the Disk Manager GUI,
|
||||
// drive letters are not actually restricted to A-Z. Using `SetVolumeMountPointW` will allow you
|
||||
// to set any byte value as a drive letter, and going through `IOCTL_MOUNTMGR_CREATE_POINT` will
|
||||
// allow you to set any WTF-16 code unit as a drive letter.
|
||||
//
|
||||
// Non-A-Z drive letters don't interact well with most of Windows, but certain things do work, e.g.
|
||||
// `cd /D €:\` will work, filesystem functions still work, etc.
|
||||
//
|
||||
// The unfortunate part of this is that this makes handling WTF-8 more complicated as we can't
|
||||
// just check path[0], path[1], path[2].
|
||||
const colon_i: usize = switch (T) {
|
||||
u8 => i: {
|
||||
const code_point_len = std.unicode.utf8ByteSequenceLength(path[0]) catch return .relative;
|
||||
// Conveniently, 4-byte sequences in WTF-8 have the same starting code point
|
||||
// as 2-code-unit sequences in WTF-16.
|
||||
if (code_point_len > 3) return .relative;
|
||||
break :i code_point_len;
|
||||
},
|
||||
u16 => 1,
|
||||
else => @compileError("unsupported type: " ++ @typeName(T)),
|
||||
};
|
||||
// x
|
||||
if (path.len < colon_i + 1 or path[colon_i] != mem.nativeToLittle(T, ':')) return .relative;
|
||||
// x:\
|
||||
if (path.len > colon_i + 1 and windows_path.isSep(T, path[colon_i + 1])) return .drive_absolute;
|
||||
// x:
|
||||
return .drive_relative;
|
||||
}
|
||||
}
|
||||
|
||||
test getWin32PathType {
|
||||
try std.testing.expectEqual(.relative, getWin32PathType(u8, ""));
|
||||
try std.testing.expectEqual(.relative, getWin32PathType(u8, "x"));
|
||||
try std.testing.expectEqual(.relative, getWin32PathType(u8, "x\\"));
|
||||
|
||||
try std.testing.expectEqual(.root_local_device, getWin32PathType(u8, "//."));
|
||||
try std.testing.expectEqual(.root_local_device, getWin32PathType(u8, "/\\?"));
|
||||
try std.testing.expectEqual(.root_local_device, getWin32PathType(u8, "\\\\?"));
|
||||
|
||||
try std.testing.expectEqual(.local_device, getWin32PathType(u8, "//./x"));
|
||||
try std.testing.expectEqual(.local_device, getWin32PathType(u8, "/\\?\\x"));
|
||||
try std.testing.expectEqual(.local_device, getWin32PathType(u8, "\\\\?\\x"));
|
||||
// local device paths require a path separator after the root, otherwise it is considered a UNC path
|
||||
try std.testing.expectEqual(.unc_absolute, getWin32PathType(u8, "\\\\?x"));
|
||||
try std.testing.expectEqual(.unc_absolute, getWin32PathType(u8, "//.x"));
|
||||
|
||||
try std.testing.expectEqual(.unc_absolute, getWin32PathType(u8, "//"));
|
||||
try std.testing.expectEqual(.unc_absolute, getWin32PathType(u8, "\\\\x"));
|
||||
try std.testing.expectEqual(.unc_absolute, getWin32PathType(u8, "//x"));
|
||||
|
||||
try std.testing.expectEqual(.rooted, getWin32PathType(u8, "\\x"));
|
||||
try std.testing.expectEqual(.rooted, getWin32PathType(u8, "/"));
|
||||
|
||||
try std.testing.expectEqual(.drive_relative, getWin32PathType(u8, "x:"));
|
||||
try std.testing.expectEqual(.drive_relative, getWin32PathType(u8, "x:abc"));
|
||||
try std.testing.expectEqual(.drive_relative, getWin32PathType(u8, "x:a/b/c"));
|
||||
|
||||
try std.testing.expectEqual(.drive_absolute, getWin32PathType(u8, "x:\\"));
|
||||
try std.testing.expectEqual(.drive_absolute, getWin32PathType(u8, "x:\\abc"));
|
||||
try std.testing.expectEqual(.drive_absolute, getWin32PathType(u8, "x:/a/b/c"));
|
||||
|
||||
// Non-ASCII code point that is encoded as one WTF-16 code unit is considered a valid drive letter
|
||||
try std.testing.expectEqual(.drive_absolute, getWin32PathType(u8, "€:\\"));
|
||||
try std.testing.expectEqual(.drive_absolute, getWin32PathType(u16, std.unicode.wtf8ToWtf16LeStringLiteral("€:\\")));
|
||||
try std.testing.expectEqual(.drive_relative, getWin32PathType(u8, "€:"));
|
||||
try std.testing.expectEqual(.drive_relative, getWin32PathType(u16, std.unicode.wtf8ToWtf16LeStringLiteral("€:")));
|
||||
// But code points that are encoded as two WTF-16 code units are not
|
||||
try std.testing.expectEqual(.relative, getWin32PathType(u8, "\u{10000}:\\"));
|
||||
try std.testing.expectEqual(.relative, getWin32PathType(u16, std.unicode.wtf8ToWtf16LeStringLiteral("\u{10000}:\\")));
|
||||
}
|
||||
|
||||
/// Returns true if the path starts with `\??\`, which is indicative of an NT path
|
||||
/// but is not enough to fully distinguish between NT paths and Win32 paths, as
|
||||
/// `\??\` is not actually a distinct prefix but rather the path to a special virtual
|
||||
|
|
@ -4663,7 +4542,7 @@ const LocalDevicePathType = enum {
|
|||
/// Asserts `path` is of type `Win32PathType.local_device`.
|
||||
fn getLocalDevicePathType(comptime T: type, path: []const T) LocalDevicePathType {
|
||||
if (std.debug.runtime_safety) {
|
||||
std.debug.assert(getWin32PathType(T, path) == .local_device);
|
||||
assert(std.fs.path.getWin32PathType(T, path) == .local_device);
|
||||
}
|
||||
|
||||
const backslash = mem.nativeToLittle(T, '\\');
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ vector: Vector,
|
|||
|
||||
pub const Vector = switch (native_os) {
|
||||
.windows => []const u16, // WTF-16 encoded
|
||||
.freestanding, .other => void,
|
||||
else => []const [*:0]const u8,
|
||||
};
|
||||
|
||||
|
|
@ -57,7 +58,7 @@ pub const Iterator = struct {
|
|||
/// Returned slice is pointing to the iterator's internal buffer.
|
||||
/// On Windows, the result is encoded as [WTF-8](https://wtf-8.codeberg.page/).
|
||||
/// On other platforms, the result is an opaque sequence of bytes with no particular encoding.
|
||||
pub fn next(it: *Iterator) ?([:0]const u8) {
|
||||
pub fn next(it: *Iterator) ?[:0]const u8 {
|
||||
return it.inner.next();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ pub const Id = switch (native_os) {
|
|||
/// On Windows this is the hProcess.
|
||||
/// On POSIX this is the pid.
|
||||
id: ?Id,
|
||||
thread_handle: if (native_os == .windows) std.os.windows.HANDLE else void = {},
|
||||
thread_handle: if (native_os == .windows) std.os.windows.HANDLE else void,
|
||||
/// The writing end of the child process's standard input pipe.
|
||||
/// Usage requires `process.SpawnOptions.StdIo.pipe`.
|
||||
stdin: ?File,
|
||||
|
|
|
|||
|
|
@ -12,11 +12,6 @@ const posix = std.posix;
|
|||
const mem = std.mem;
|
||||
|
||||
/// Unmodified, unprocessed data provided by the operating system.
|
||||
///
|
||||
/// On Windows this might point to memory in the PEB.
|
||||
///
|
||||
/// On WASI without libc, this is void because the environment has to be
|
||||
/// queried and heap-allocated at runtime.
|
||||
block: Block,
|
||||
|
||||
pub const empty: Environ = .{
|
||||
|
|
@ -26,12 +21,19 @@ pub const empty: Environ = .{
|
|||
},
|
||||
};
|
||||
|
||||
/// On WASI without libc, this is `void` because the environment has to be
|
||||
/// queried and heap-allocated at runtime.
|
||||
///
|
||||
/// On Windows, the memory pointed at by the PEB changes when the environment
|
||||
/// is modified, so a long-lived pointer cannot be used. Therefore, on this
|
||||
/// operating system `void` is also used.
|
||||
pub const Block = switch (native_os) {
|
||||
.windows => [*:0]const u16,
|
||||
.windows => void,
|
||||
.wasi => switch (builtin.link_libc) {
|
||||
false => void,
|
||||
true => [:null]const ?[*:0]const u8,
|
||||
},
|
||||
.freestanding, .other => void,
|
||||
else => [:null]const ?[*:0]const u8,
|
||||
};
|
||||
|
||||
|
|
@ -345,7 +347,7 @@ pub fn createMap(env: Environ, allocator: Allocator) CreateMapError!Map {
|
|||
errdefer result.deinit();
|
||||
|
||||
if (native_os == .windows) {
|
||||
const ptr = env.block;
|
||||
const ptr = std.os.windows.peb().ProcessParameters.Environment;
|
||||
|
||||
var i: usize = 0;
|
||||
while (ptr[i] != 0) {
|
||||
|
|
|
|||
|
|
@ -11,118 +11,63 @@ const native_os = builtin.os.tag;
|
|||
|
||||
const start_sym_name = if (native_arch.isMIPS()) "__start" else "_start";
|
||||
|
||||
// The self-hosted compiler is not fully capable of handling all of this start.zig file.
|
||||
// Until then, we have simplified logic here for self-hosted. TODO remove this once
|
||||
// self-hosted is capable enough to handle all of the real start.zig logic.
|
||||
pub const simplified_logic = switch (builtin.zig_backend) {
|
||||
.stage2_aarch64,
|
||||
.stage2_arm,
|
||||
.stage2_powerpc,
|
||||
.stage2_sparc64,
|
||||
.stage2_spirv,
|
||||
.stage2_x86,
|
||||
=> true,
|
||||
else => false,
|
||||
};
|
||||
|
||||
comptime {
|
||||
// No matter what, we import the root file, so that any export, test, comptime
|
||||
// decls there get run.
|
||||
_ = root;
|
||||
|
||||
if (simplified_logic) {
|
||||
if (builtin.output_mode == .Exe) {
|
||||
if ((builtin.link_libc or builtin.object_format == .c) and @hasDecl(root, "main")) {
|
||||
if (!@typeInfo(@TypeOf(root.main)).@"fn".calling_convention.eql(.c)) {
|
||||
@export(&main2, .{ .name = "main" });
|
||||
}
|
||||
} else if (builtin.os.tag == .windows) {
|
||||
if (!@hasDecl(root, "wWinMainCRTStartup") and !@hasDecl(root, "mainCRTStartup")) {
|
||||
@export(&wWinMainCRTStartup2, .{ .name = "wWinMainCRTStartup" });
|
||||
}
|
||||
} else if (builtin.os.tag == .opencl or builtin.os.tag == .vulkan) {
|
||||
if (@hasDecl(root, "main"))
|
||||
@export(&spirvMain2, .{ .name = "main" });
|
||||
} else {
|
||||
if (!@hasDecl(root, "_start")) {
|
||||
@export(&_start2, .{ .name = "_start" });
|
||||
}
|
||||
}
|
||||
if (builtin.output_mode == .Lib and builtin.link_mode == .dynamic) {
|
||||
if (native_os == .windows and !@hasDecl(root, "_DllMainCRTStartup")) {
|
||||
@export(&_DllMainCRTStartup, .{ .name = "_DllMainCRTStartup" });
|
||||
}
|
||||
} else {
|
||||
if (builtin.output_mode == .Lib and builtin.link_mode == .dynamic) {
|
||||
if (native_os == .windows and !@hasDecl(root, "_DllMainCRTStartup")) {
|
||||
@export(&_DllMainCRTStartup, .{ .name = "_DllMainCRTStartup" });
|
||||
} else if (builtin.output_mode == .Exe or @hasDecl(root, "main")) {
|
||||
if (builtin.link_libc and @hasDecl(root, "main")) {
|
||||
if (native_arch.isWasm()) {
|
||||
@export(&mainWithoutEnv, .{ .name = "__main_argc_argv" });
|
||||
} else if (!@typeInfo(@TypeOf(root.main)).@"fn".calling_convention.eql(.c)) {
|
||||
@export(&main, .{ .name = "main" });
|
||||
}
|
||||
} else if (builtin.output_mode == .Exe or @hasDecl(root, "main")) {
|
||||
if (builtin.link_libc and @hasDecl(root, "main")) {
|
||||
if (native_arch.isWasm()) {
|
||||
@export(&mainWithoutEnv, .{ .name = "__main_argc_argv" });
|
||||
} else if (!@typeInfo(@TypeOf(root.main)).@"fn".calling_convention.eql(.c)) {
|
||||
@export(&main, .{ .name = "main" });
|
||||
}
|
||||
} else if (native_os == .windows and builtin.link_libc and @hasDecl(root, "wWinMain")) {
|
||||
if (!@typeInfo(@TypeOf(root.wWinMain)).@"fn".calling_convention.eql(.c)) {
|
||||
@export(&wWinMain, .{ .name = "wWinMain" });
|
||||
}
|
||||
} else if (native_os == .windows) {
|
||||
if (!@hasDecl(root, "WinMain") and !@hasDecl(root, "WinMainCRTStartup") and
|
||||
!@hasDecl(root, "wWinMain") and !@hasDecl(root, "wWinMainCRTStartup"))
|
||||
{
|
||||
@export(&WinStartup, .{ .name = "wWinMainCRTStartup" });
|
||||
} else if (@hasDecl(root, "WinMain") and !@hasDecl(root, "WinMainCRTStartup") and
|
||||
!@hasDecl(root, "wWinMain") and !@hasDecl(root, "wWinMainCRTStartup"))
|
||||
{
|
||||
@compileError("WinMain not supported; declare wWinMain or main instead");
|
||||
} else if (@hasDecl(root, "wWinMain") and !@hasDecl(root, "wWinMainCRTStartup") and
|
||||
!@hasDecl(root, "WinMain") and !@hasDecl(root, "WinMainCRTStartup"))
|
||||
{
|
||||
@export(&wWinMainCRTStartup, .{ .name = "wWinMainCRTStartup" });
|
||||
}
|
||||
} else if (native_os == .uefi) {
|
||||
if (!@hasDecl(root, "EfiMain")) @export(&EfiMain, .{ .name = "EfiMain" });
|
||||
} else if (native_os == .wasi) {
|
||||
const wasm_start_sym = switch (builtin.wasi_exec_model) {
|
||||
.reactor => "_initialize",
|
||||
.command => "_start",
|
||||
};
|
||||
if (!@hasDecl(root, wasm_start_sym) and @hasDecl(root, "main")) {
|
||||
// Only call main when defined. For WebAssembly it's allowed to pass `-fno-entry` in which
|
||||
// case it's not required to provide an entrypoint such as main.
|
||||
@export(&wasi_start, .{ .name = wasm_start_sym });
|
||||
}
|
||||
} else if (native_arch.isWasm() and native_os == .freestanding) {
|
||||
} else if (native_os == .windows and builtin.link_libc and @hasDecl(root, "wWinMain")) {
|
||||
if (!@typeInfo(@TypeOf(root.wWinMain)).@"fn".calling_convention.eql(.c)) {
|
||||
@export(&wWinMain, .{ .name = "wWinMain" });
|
||||
}
|
||||
} else if (native_os == .windows) {
|
||||
if (!@hasDecl(root, "WinMain") and !@hasDecl(root, "WinMainCRTStartup") and
|
||||
!@hasDecl(root, "wWinMain") and !@hasDecl(root, "wWinMainCRTStartup"))
|
||||
{
|
||||
@export(&WinStartup, .{ .name = "wWinMainCRTStartup" });
|
||||
} else if (@hasDecl(root, "WinMain") and !@hasDecl(root, "WinMainCRTStartup") and
|
||||
!@hasDecl(root, "wWinMain") and !@hasDecl(root, "wWinMainCRTStartup"))
|
||||
{
|
||||
@compileError("WinMain not supported; declare wWinMain or main instead");
|
||||
} else if (@hasDecl(root, "wWinMain") and !@hasDecl(root, "wWinMainCRTStartup") and
|
||||
!@hasDecl(root, "WinMain") and !@hasDecl(root, "WinMainCRTStartup"))
|
||||
{
|
||||
@export(&wWinMainCRTStartup, .{ .name = "wWinMainCRTStartup" });
|
||||
}
|
||||
} else if (native_os == .uefi) {
|
||||
if (!@hasDecl(root, "EfiMain")) @export(&EfiMain, .{ .name = "EfiMain" });
|
||||
} else if (native_os == .wasi) {
|
||||
const wasm_start_sym = switch (builtin.wasi_exec_model) {
|
||||
.reactor => "_initialize",
|
||||
.command => "_start",
|
||||
};
|
||||
if (!@hasDecl(root, wasm_start_sym) and @hasDecl(root, "main")) {
|
||||
// Only call main when defined. For WebAssembly it's allowed to pass `-fno-entry` in which
|
||||
// case it's not required to provide an entrypoint such as main.
|
||||
if (!@hasDecl(root, start_sym_name) and @hasDecl(root, "main")) @export(&wasm_freestanding_start, .{ .name = start_sym_name });
|
||||
} else switch (native_os) {
|
||||
.other, .freestanding, .@"3ds", .vita => {},
|
||||
else => if (!@hasDecl(root, start_sym_name)) @export(&_start, .{ .name = start_sym_name }),
|
||||
@export(&wasi_start, .{ .name = wasm_start_sym });
|
||||
}
|
||||
} else if (native_arch.isWasm() and native_os == .freestanding) {
|
||||
// Only call main when defined. For WebAssembly it's allowed to pass `-fno-entry` in which
|
||||
// case it's not required to provide an entrypoint such as main.
|
||||
if (!@hasDecl(root, start_sym_name) and @hasDecl(root, "main")) @export(&wasm_freestanding_start, .{ .name = start_sym_name });
|
||||
} else switch (native_os) {
|
||||
.other, .freestanding, .@"3ds", .vita => {},
|
||||
else => if (!@hasDecl(root, start_sym_name)) @export(&_start, .{ .name = start_sym_name }),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Simplified start code for stage2 until it supports more language features ///
|
||||
|
||||
fn main2() callconv(.c) c_int {
|
||||
return callMain();
|
||||
}
|
||||
|
||||
fn _start2() callconv(.withStackAlign(.c, 1)) noreturn {
|
||||
std.process.exit(callMain());
|
||||
}
|
||||
|
||||
fn spirvMain2() callconv(.kernel) void {
|
||||
root.main();
|
||||
}
|
||||
|
||||
fn wWinMainCRTStartup2() callconv(.c) noreturn {
|
||||
std.process.exit(callMain());
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
fn _DllMainCRTStartup(
|
||||
hinstDLL: std.os.windows.HINSTANCE,
|
||||
fdwReason: std.os.windows.DWORD,
|
||||
|
|
@ -142,15 +87,15 @@ fn _DllMainCRTStartup(
|
|||
fn wasm_freestanding_start() callconv(.c) void {
|
||||
// This is marked inline because for some reason LLVM in
|
||||
// release mode fails to inline it, and we want fewer call frames in stack traces.
|
||||
_ = @call(.always_inline, callMain, .{});
|
||||
_ = @call(.always_inline, callMain, .{ {}, {} });
|
||||
}
|
||||
|
||||
fn wasi_start() callconv(.c) void {
|
||||
// The function call is marked inline because for some reason LLVM in
|
||||
// release mode fails to inline it, and we want fewer call frames in stack traces.
|
||||
switch (builtin.wasi_exec_model) {
|
||||
.reactor => _ = @call(.always_inline, callMain, .{}),
|
||||
.command => std.os.wasi.proc_exit(@call(.always_inline, callMain, .{})),
|
||||
.reactor => _ = @call(.always_inline, callMain, .{ {}, {} }),
|
||||
.command => std.os.wasi.proc_exit(@call(.always_inline, callMain, .{ {}, {} })),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -524,13 +469,10 @@ fn WinStartup() callconv(.withStackAlign(.c, 1)) noreturn {
|
|||
|
||||
std.debug.maybeEnableSegfaultHandler();
|
||||
|
||||
const peb = std.os.windows.peb();
|
||||
const cmd_line = std.os.windows.peb().ProcessParameters.CommandLine;
|
||||
const cmd_line_w = cmd_line.Buffer.?[0..@divExact(cmd_line.Length, 2)];
|
||||
|
||||
std.os.windows.ntdll.RtlExitUserProcess(callMain(
|
||||
cmd_line.Buffer.?[0..@divExact(cmd_line.Length, 2)],
|
||||
peb.ProcessParameters.Environment,
|
||||
));
|
||||
std.os.windows.ntdll.RtlExitUserProcess(callMain(cmd_line_w, {}));
|
||||
}
|
||||
|
||||
fn wWinMainCRTStartup() callconv(.withStackAlign(.c, 1)) noreturn {
|
||||
|
|
@ -637,6 +579,7 @@ fn posixCallMainAndExit(argc_argv_ptr: [*]usize) callconv(.c) noreturn {
|
|||
}
|
||||
|
||||
fn expandStackSize(phdrs: []elf.Phdr) void {
|
||||
@disableInstrumentation();
|
||||
for (phdrs) |*phdr| {
|
||||
switch (phdr.p_type) {
|
||||
elf.PT_GNU_STACK => {
|
||||
|
|
@ -674,7 +617,7 @@ fn expandStackSize(phdrs: []elf.Phdr) void {
|
|||
inline fn callMainWithArgs(argc: usize, argv: [*][*:0]u8, envp: [:null]?[*:0]u8) u8 {
|
||||
if (std.Options.debug_threaded_io) |t| {
|
||||
if (@sizeOf(std.Io.Threaded.Argv0) != 0) t.argv0.value = argv[0];
|
||||
t.environ = .{ .block = envp };
|
||||
t.environ = .{ .process_environ = .{ .block = envp } };
|
||||
}
|
||||
std.debug.maybeEnableSegfaultHandler();
|
||||
return callMain(argv[0..argc], envp);
|
||||
|
|
@ -735,8 +678,8 @@ inline fn callMain(args: std.process.Args.Vector, environ: std.process.Environ.B
|
|||
defer arena_allocator.deinit();
|
||||
|
||||
var threaded: std.Io.Threaded = .init(gpa, .{
|
||||
.argv0 = if (@sizeOf(std.Io.Threaded.Argv0) != 0) .{ .value = args[0] } else .{},
|
||||
.environ = .{ .block = environ },
|
||||
.argv0 = .init(.{ .value = args }),
|
||||
.environ = .{ .process_environ = .{ .block = environ } },
|
||||
});
|
||||
defer threaded.deinit();
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue