build: Command.expandPath can go in its own dedicated os/path.zig file
parent
800fa99ff2
commit
d65466362d
|
|
@ -404,91 +404,6 @@ pub fn getData(self: Command, comptime DT: type) ?*DT {
|
|||
return if (self.data) |ptr| @ptrCast(@alignCast(ptr)) else null;
|
||||
}
|
||||
|
||||
/// Search for "cmd" in the PATH and return the absolute path. This will
|
||||
/// always allocate if there is a non-null result. The caller must free the
|
||||
/// resulting value.
|
||||
pub fn expandPath(alloc: Allocator, cmd: []const u8) !?[]u8 {
|
||||
// If the command already contains a slash, then we return it as-is
|
||||
// because it is assumed to be absolute or relative.
|
||||
if (std.mem.indexOfScalar(u8, cmd, '/') != null) {
|
||||
return try alloc.dupe(u8, cmd);
|
||||
}
|
||||
|
||||
const PATH = switch (builtin.os.tag) {
|
||||
.windows => blk: {
|
||||
const win_path = std.process.getenvW(std.unicode.utf8ToUtf16LeStringLiteral("PATH")) orelse return null;
|
||||
const path = try std.unicode.utf16LeToUtf8Alloc(alloc, win_path);
|
||||
break :blk path;
|
||||
},
|
||||
else => std.posix.getenvZ("PATH") orelse return null,
|
||||
};
|
||||
defer if (builtin.os.tag == .windows) alloc.free(PATH);
|
||||
|
||||
var path_buf: [std.fs.max_path_bytes]u8 = undefined;
|
||||
var it = std.mem.tokenizeScalar(u8, PATH, std.fs.path.delimiter);
|
||||
var seen_eacces = false;
|
||||
while (it.next()) |search_path| {
|
||||
// We need enough space in our path buffer to store this
|
||||
const path_len = search_path.len + cmd.len + 1;
|
||||
if (path_buf.len < path_len) return error.PathTooLong;
|
||||
|
||||
// Copy in the full path
|
||||
@memcpy(path_buf[0..search_path.len], search_path);
|
||||
path_buf[search_path.len] = std.fs.path.sep;
|
||||
@memcpy(path_buf[search_path.len + 1 ..][0..cmd.len], cmd);
|
||||
path_buf[path_len] = 0;
|
||||
const full_path = path_buf[0..path_len :0];
|
||||
|
||||
// Stat it
|
||||
const f = std.fs.cwd().openFile(
|
||||
full_path,
|
||||
.{},
|
||||
) catch |err| switch (err) {
|
||||
error.FileNotFound => continue,
|
||||
error.AccessDenied => {
|
||||
// Accumulate this and return it later so we can try other
|
||||
// paths that we have access to.
|
||||
seen_eacces = true;
|
||||
continue;
|
||||
},
|
||||
else => return err,
|
||||
};
|
||||
defer f.close();
|
||||
const stat = try f.stat();
|
||||
if (stat.kind != .directory and isExecutable(stat.mode)) {
|
||||
return try alloc.dupe(u8, full_path);
|
||||
}
|
||||
}
|
||||
|
||||
if (seen_eacces) return error.AccessDenied;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
fn isExecutable(mode: std.fs.File.Mode) bool {
|
||||
if (builtin.os.tag == .windows) return true;
|
||||
return mode & 0o0111 != 0;
|
||||
}
|
||||
|
||||
// `uname -n` is the *nix equivalent of `hostname.exe` on Windows
|
||||
test "expandPath: hostname" {
|
||||
const executable = if (builtin.os.tag == .windows) "hostname.exe" else "uname";
|
||||
const path = (try expandPath(testing.allocator, executable)).?;
|
||||
defer testing.allocator.free(path);
|
||||
try testing.expect(path.len > executable.len);
|
||||
}
|
||||
|
||||
test "expandPath: does not exist" {
|
||||
const path = try expandPath(testing.allocator, "thisreallyprobablydoesntexist123");
|
||||
try testing.expect(path == null);
|
||||
}
|
||||
|
||||
test "expandPath: slash" {
|
||||
const path = (try expandPath(testing.allocator, "foo/env")).?;
|
||||
defer testing.allocator.free(path);
|
||||
try testing.expect(path.len == 7);
|
||||
}
|
||||
|
||||
// Copied from Zig. This is a publicly exported function but there is no
|
||||
// way to get it from the std package.
|
||||
fn createNullDelimitedEnvMap(arena: mem.Allocator, env_map: *const EnvMap) ![:null]?[*:0]u8 {
|
||||
|
|
|
|||
|
|
@ -8,9 +8,9 @@ const builtin = @import("builtin");
|
|||
const ApprtRuntime = @import("../apprt/runtime.zig").Runtime;
|
||||
const FontBackend = @import("../font/backend.zig").Backend;
|
||||
const RendererBackend = @import("../renderer/backend.zig").Backend;
|
||||
const Command = @import("../Command.zig");
|
||||
const XCFramework = @import("GhosttyXCFramework.zig");
|
||||
const WasmTarget = @import("../os/wasm/target.zig").Target;
|
||||
const expandPath = @import("../os/path.zig").expand;
|
||||
|
||||
const gtk = @import("gtk.zig");
|
||||
const GitVersion = @import("GitVersion.zig");
|
||||
|
|
@ -332,7 +332,7 @@ pub fn init(b: *std.Build) !Config {
|
|||
if (system_package) break :emit_docs true;
|
||||
|
||||
// We only default to true if we can find pandoc.
|
||||
const path = Command.expandPath(b.allocator, "pandoc") catch
|
||||
const path = expandPath(b.allocator, "pandoc") catch
|
||||
break :emit_docs false;
|
||||
defer if (path) |p| b.allocator.free(p);
|
||||
break :emit_docs path != null;
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ pub const args = @import("args.zig");
|
|||
pub const cgroup = @import("cgroup.zig");
|
||||
pub const hostname = @import("hostname.zig");
|
||||
pub const i18n = @import("i18n.zig");
|
||||
pub const path = @import("path.zig");
|
||||
pub const passwd = @import("passwd.zig");
|
||||
pub const xdg = @import("xdg.zig");
|
||||
pub const windows = @import("windows.zig");
|
||||
|
|
@ -65,6 +66,7 @@ pub const getKernelInfo = kernel_info.getKernelInfo;
|
|||
|
||||
test {
|
||||
_ = i18n;
|
||||
_ = path;
|
||||
|
||||
if (comptime builtin.os.tag == .linux) {
|
||||
_ = kernel_info;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,89 @@
|
|||
const std = @import("std");
|
||||
const builtin = @import("builtin");
|
||||
const Allocator = std.mem.Allocator;
|
||||
const testing = std.testing;
|
||||
|
||||
/// Search for "cmd" in the PATH and return the absolute path. This will
|
||||
/// always allocate if there is a non-null result. The caller must free the
|
||||
/// resulting value.
|
||||
pub fn expand(alloc: Allocator, cmd: []const u8) !?[]u8 {
|
||||
// If the command already contains a slash, then we return it as-is
|
||||
// because it is assumed to be absolute or relative.
|
||||
if (std.mem.indexOfScalar(u8, cmd, '/') != null) {
|
||||
return try alloc.dupe(u8, cmd);
|
||||
}
|
||||
|
||||
const PATH = switch (builtin.os.tag) {
|
||||
.windows => blk: {
|
||||
const win_path = std.process.getenvW(std.unicode.utf8ToUtf16LeStringLiteral("PATH")) orelse return null;
|
||||
const path = try std.unicode.utf16LeToUtf8Alloc(alloc, win_path);
|
||||
break :blk path;
|
||||
},
|
||||
else => std.posix.getenvZ("PATH") orelse return null,
|
||||
};
|
||||
defer if (builtin.os.tag == .windows) alloc.free(PATH);
|
||||
|
||||
var path_buf: [std.fs.max_path_bytes]u8 = undefined;
|
||||
var it = std.mem.tokenizeScalar(u8, PATH, std.fs.path.delimiter);
|
||||
var seen_eacces = false;
|
||||
while (it.next()) |search_path| {
|
||||
// We need enough space in our path buffer to store this
|
||||
const path_len = search_path.len + cmd.len + 1;
|
||||
if (path_buf.len < path_len) return error.PathTooLong;
|
||||
|
||||
// Copy in the full path
|
||||
@memcpy(path_buf[0..search_path.len], search_path);
|
||||
path_buf[search_path.len] = std.fs.path.sep;
|
||||
@memcpy(path_buf[search_path.len + 1 ..][0..cmd.len], cmd);
|
||||
path_buf[path_len] = 0;
|
||||
const full_path = path_buf[0..path_len :0];
|
||||
|
||||
// Stat it
|
||||
const f = std.fs.cwd().openFile(
|
||||
full_path,
|
||||
.{},
|
||||
) catch |err| switch (err) {
|
||||
error.FileNotFound => continue,
|
||||
error.AccessDenied => {
|
||||
// Accumulate this and return it later so we can try other
|
||||
// paths that we have access to.
|
||||
seen_eacces = true;
|
||||
continue;
|
||||
},
|
||||
else => return err,
|
||||
};
|
||||
defer f.close();
|
||||
const stat = try f.stat();
|
||||
if (stat.kind != .directory and isExecutable(stat.mode)) {
|
||||
return try alloc.dupe(u8, full_path);
|
||||
}
|
||||
}
|
||||
|
||||
if (seen_eacces) return error.AccessDenied;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
fn isExecutable(mode: std.fs.File.Mode) bool {
|
||||
if (builtin.os.tag == .windows) return true;
|
||||
return mode & 0o0111 != 0;
|
||||
}
|
||||
|
||||
// `uname -n` is the *nix equivalent of `hostname.exe` on Windows
|
||||
test "expand: hostname" {
|
||||
const executable = if (builtin.os.tag == .windows) "hostname.exe" else "uname";
|
||||
const path = (try expand(testing.allocator, executable)).?;
|
||||
defer testing.allocator.free(path);
|
||||
try testing.expect(path.len > executable.len);
|
||||
}
|
||||
|
||||
test "expand: does not exist" {
|
||||
const path = try expand(testing.allocator, "thisreallyprobablydoesntexist123");
|
||||
try testing.expect(path == null);
|
||||
}
|
||||
|
||||
test "expand: slash" {
|
||||
const path = (try expand(testing.allocator, "foo/env")).?;
|
||||
defer testing.allocator.free(path);
|
||||
try testing.expect(path.len == 7);
|
||||
}
|
||||
Loading…
Reference in New Issue