authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2025-06-18 16:07:20+01:00
committergravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2025-06-18 22:45:05+01:00
log36499c251c592d10a8258b1562bee22e5fb7899a
treefa03fda7f222e30607b62a6e2c25d193f6e96c43
parentf3940ad858937cc4bb2e1b99828183e070f40bf9

std.Build.Step.Run: prefix relative path arguments with './'

This is necessary in two cases: * On POSIX, the exe path (`argv[0]`) must contain a path separator * Some programs might treat a file named e.g. `-foo` as a flag, which can be avoided by passing `./-foo` Rather than detecting these two cases, just always include the prefix; there's no harm in it. Also, if the cwd is specified, include it in the manifest. If the user has set the cwd of a Run step, it is clearly because this affects the behavior of the executable somehow, so that cwd path should be a part of the step's manifest. Resolves: #24216

5 files changed, 65 insertions(+), 3 deletions(-)

lib/std/Build/Step/Run.zig+21-3
...@@ -629,9 +629,22 @@ fn checksContainStderr(checks: []const StdIo.Check) bool {...@@ -629,9 +629,22 @@ fn checksContainStderr(checks: []const StdIo.Check) bool {
629fn convertPathArg(run: *Run, path: Build.Cache.Path) []const u8 {629fn convertPathArg(run: *Run, path: Build.Cache.Path) []const u8 {
630 const b = run.step.owner;630 const b = run.step.owner;
631 const path_str = path.toString(b.graph.arena) catch @panic("OOM");631 const path_str = path.toString(b.graph.arena) catch @panic("OOM");
632 const child_lazy_cwd = run.cwd orelse return path_str;632 if (std.fs.path.isAbsolute(path_str)) {
633 const child_cwd = child_lazy_cwd.getPath3(b, &run.step).toString(b.graph.arena) catch @panic("OOM");633 // Absolute paths don't need changing.
634 return std.fs.path.relative(b.graph.arena, child_cwd, path_str) catch @panic("OOM");634 return path_str;
635 }
636 const child_cwd_rel: []const u8 = rel: {
637 const child_lazy_cwd = run.cwd orelse break :rel path_str;
638 const child_cwd = child_lazy_cwd.getPath3(b, &run.step).toString(b.graph.arena) catch @panic("OOM");
639 // Convert it from relative to *our* cwd, to relative to the *child's* cwd.
640 break :rel std.fs.path.relative(b.graph.arena, child_cwd, path_str) catch @panic("OOM");
641 };
642 assert(!std.fs.path.isAbsolute(child_cwd_rel));
643 // We're not done yet. In some cases this path must be prefixed with './':
644 // * On POSIX, the executable name cannot be a single component like 'foo'
645 // * Some executables might treat a leading '-' like a flag, which we must avoid
646 // There's no harm in it, so just *always* apply this prefix.
647 return std.fs.path.join(b.graph.arena, &.{ ".", child_cwd_rel }) catch @panic("OOM");
635}648}
636649
637const IndexedOutput = struct {650const IndexedOutput = struct {
...@@ -756,6 +769,11 @@ fn make(step: *Step, options: Step.MakeOptions) !void {...@@ -756,6 +769,11 @@ fn make(step: *Step, options: Step.MakeOptions) !void {
756 _ = try man.addFile(lazy_path.getPath2(b, step), null);769 _ = try man.addFile(lazy_path.getPath2(b, step), null);
757 }770 }
758771
772 if (run.cwd) |cwd| {
773 const cwd_path = cwd.getPath3(b, step);
774 _ = man.hash.addBytes(try cwd_path.toString(arena));
775 }
776
759 if (!has_side_effects and try step.cacheHitAndWatch(&man)) {777 if (!has_side_effects and try step.cacheHitAndWatch(&man)) {
760 // cache hit, skip running command778 // cache hit, skip running command
761 const digest = man.final();779 const digest = man.final();
test/standalone/run_cwd/build.zig created+30
...@@ -0,0 +1,30 @@
1pub fn build(b: *Build) void {
2 const exe = b.addExecutable(.{
3 .name = "check_file_exists",
4 .root_module = b.createModule(.{
5 .target = b.graph.host,
6 .optimize = .Debug,
7 .root_source_file = b.path("check_file_exists.zig"),
8 }),
9 });
10
11 const test_step = b.step("test", "Test it");
12 b.default_step = test_step;
13
14 test_step.dependOn(addCheck(b, exe, ".", null));
15 test_step.dependOn(addCheck(b, exe, "..", b.path("..")));
16 test_step.dependOn(addCheck(b, exe, "exe dir", exe.getEmittedBin().dirname()));
17 test_step.dependOn(addCheck(b, exe, "exe dir/..", exe.getEmittedBin().dirname().dirname()));
18 test_step.dependOn(addCheck(b, exe, "./empty_dir", b.path("empty_dir")));
19}
20
21fn addCheck(b: *Build, exe: *Build.Step.Compile, cwd_name: []const u8, opt_cwd: ?Build.LazyPath) *Build.Step {
22 const run = b.addRunArtifact(exe);
23 if (opt_cwd) |cwd| run.setCwd(cwd);
24 run.addFileArg(b.path("file_that_exists.txt"));
25 run.setName(b.fmt("check in '{s}'", .{cwd_name}));
26 run.expectExitCode(0);
27 return &run.step;
28}
29
30const Build = @import("std").Build;
test/standalone/run_cwd/check_file_exists.zig created+14
...@@ -0,0 +1,14 @@
1pub fn main() !void {
2 var arena_state: std.heap.ArenaAllocator = .init(std.heap.page_allocator);
3 defer arena_state.deinit();
4 const arena = arena_state.allocator();
5
6 const args = try std.process.argsAlloc(arena);
7
8 if (args.len != 2) return error.BadUsage;
9 const path = args[1];
10
11 std.fs.cwd().access(path, .{}) catch return error.AccessFailed;
12}
13
14const std = @import("std");
test/standalone/run_cwd/empty_dir/make_git_happy created
test/standalone/run_cwd/file_that_exists.txt created