Skip to content

Commit

Permalink
Set argv[0] to .roc file passed to 'roc run'
Browse files Browse the repository at this point in the history
When we run `roc run <file>` or `roc <file>` then Roc will compile a
binary and run it. Before this commit we would set the path to the
compiled binary as argv[0]. This commit changes the behavior to make
argv[0] in the binary correspond to the roc file being ran.

This benefits the use of roc scripts that make use of a shebang:

    #!/usr/bin/env roc

With this change such scripts will be able to read the path to
themselves out of ARGV. This trick is commonly used for instance by bash
scripts in order to access files relative to the script itself.
  • Loading branch information
jwoudenberg committed Oct 21, 2024
1 parent 573d433 commit 1229819
Show file tree
Hide file tree
Showing 3 changed files with 68 additions and 15 deletions.
41 changes: 33 additions & 8 deletions crates/cli/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -986,7 +986,15 @@ pub fn build(
// ManuallyDrop will leak the bytes because we don't drop manually
let bytes = &ManuallyDrop::new(std::fs::read(&binary_path).unwrap());

roc_run(&arena, opt_level, target, args, bytes, expect_metadata)
roc_run(
&arena,
path,
opt_level,
target,
args,
bytes,
expect_metadata,
)
}
BuildAndRunIfNoErrors => {
if problems.fatally_errored {
Expand Down Expand Up @@ -1021,7 +1029,15 @@ pub fn build(
// ManuallyDrop will leak the bytes because we don't drop manually
let bytes = &ManuallyDrop::new(std::fs::read(&binary_path).unwrap());

roc_run(&arena, opt_level, target, args, bytes, expect_metadata)
roc_run(
&arena,
path,
opt_level,
target,
args,
bytes,
expect_metadata,
)
}
}
}
Expand All @@ -1034,6 +1050,7 @@ pub fn build(

fn roc_run<'a, I: IntoIterator<Item = &'a OsStr>>(
arena: &Bump,
script_path: &Path,
opt_level: OptLevel,
target: Target,
args: I,
Expand Down Expand Up @@ -1073,7 +1090,14 @@ fn roc_run<'a, I: IntoIterator<Item = &'a OsStr>>(

Ok(0)
}
_ => roc_run_native(arena, opt_level, args, binary_bytes, expect_metadata),
_ => roc_run_native(
arena,
script_path,
opt_level,
args,
binary_bytes,
expect_metadata,
),
}
}

Expand All @@ -1090,16 +1114,15 @@ fn os_str_as_utf8_bytes(os_str: &OsStr) -> &[u8] {

fn make_argv_envp<'a, I: IntoIterator<Item = S>, S: AsRef<OsStr>>(
arena: &'a Bump,
executable: &ExecutableFile,
script_path: &Path,
args: I,
) -> (
bumpalo::collections::Vec<'a, CString>,
bumpalo::collections::Vec<'a, CString>,
) {
use bumpalo::collections::CollectIn;

let path = executable.as_path();
let path_cstring = CString::new(os_str_as_utf8_bytes(path.as_os_str())).unwrap();
let path_cstring = CString::new(os_str_as_utf8_bytes(script_path.as_os_str())).unwrap();

// argv is an array of pointers to strings passed to the new program
// as its command-line arguments. By convention, the first of these
Expand Down Expand Up @@ -1137,6 +1160,7 @@ fn make_argv_envp<'a, I: IntoIterator<Item = S>, S: AsRef<OsStr>>(
#[cfg(target_family = "unix")]
fn roc_run_native<I: IntoIterator<Item = S>, S: AsRef<OsStr>>(
arena: &Bump,
script_path: &Path,
opt_level: OptLevel,
args: I,
binary_bytes: &[u8],
Expand All @@ -1145,7 +1169,7 @@ fn roc_run_native<I: IntoIterator<Item = S>, S: AsRef<OsStr>>(
use bumpalo::collections::CollectIn;

let executable = roc_run_executable_file_path(binary_bytes)?;
let (argv_cstrings, envp_cstrings) = make_argv_envp(arena, &executable, args);
let (argv_cstrings, envp_cstrings) = make_argv_envp(arena, script_path, args);

let argv: bumpalo::collections::Vec<*const c_char> = argv_cstrings
.iter()
Expand Down Expand Up @@ -1400,6 +1424,7 @@ fn roc_run_executable_file_path(binary_bytes: &[u8]) -> std::io::Result<Executab
#[cfg(not(target_family = "unix"))]
fn roc_run_native<I: IntoIterator<Item = S>, S: AsRef<OsStr>>(
arena: &Bump, // This should be passed an owned value, not a reference, so we can usefully mem::forget it!
script_path: &Path,
opt_level: OptLevel,
args: I,
binary_bytes: &[u8],
Expand All @@ -1411,7 +1436,7 @@ fn roc_run_native<I: IntoIterator<Item = S>, S: AsRef<OsStr>>(
let executable = roc_run_executable_file_path(binary_bytes)?;

// TODO forward the arguments
let (argv_cstrings, envp_cstrings) = make_argv_envp(&arena, &executable, args);
let (argv_cstrings, envp_cstrings) = make_argv_envp(&arena, script_path, args);

let argv: bumpalo::collections::Vec<*const c_char> = argv_cstrings
.iter()
Expand Down
12 changes: 12 additions & 0 deletions crates/cli/tests/cli/argv0.roc
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
app [main] {
pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.15.0/SlwdbJ-3GR7uBWQo6zlmYWNYOxnvo8r6YABXD-45UOw.tar.br",
}

import pf.Stdout
import pf.Arg

main =
args = Arg.list! {}
when List.first args is
Ok argv0 -> Stdout.line argv0
Err ListWasEmpty -> Stdout.line "Failed: argv was empty"
30 changes: 23 additions & 7 deletions crates/cli/tests/cli_run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -305,7 +305,7 @@ mod cli_run {
}
};

let self_path = file.display().to_string();
let self_path = file.parent().unwrap().display().to_string();

let actual_cmd_stdout = ignore_test_timings(&strip_colors(&cmd_output.stdout))
.replace(&self_path, "<ignored for tests>");
Expand Down Expand Up @@ -573,12 +573,12 @@ mod cli_run {
words : List Str
words = ["this", "will", "for", "sure", "be", "a", "large", "string", "so", "when", "we", "split", "it", "it", "will", "use", "seamless", "slices", "which", "affect", "printing"]
[<ignored for tests>:31] x = 42
[<ignored for tests>:33] "Fjoer en ferdjer frieten oan dyn geve lea" = "Fjoer en ferdjer frieten oan dyn geve lea"
[<ignored for tests>:35] "this is line 24" = "this is line 24"
[<ignored for tests>:21] x = "abc"
[<ignored for tests>:21] x = 10
[<ignored for tests>:21] x = (A (B C))
[<ignored for tests>/expects.roc:31] x = 42
[<ignored for tests>/expects.roc:33] "Fjoer en ferdjer frieten oan dyn geve lea" = "Fjoer en ferdjer frieten oan dyn geve lea"
[<ignored for tests>/expects.roc:35] "this is line 24" = "this is line 24"
[<ignored for tests>/expects.roc:21] x = "abc"
[<ignored for tests>/expects.roc:21] x = 10
[<ignored for tests>/expects.roc:21] x = (A (B C))
Program finished!
"#
),
Expand Down Expand Up @@ -1203,6 +1203,22 @@ mod cli_run {
)
}

#[test]
#[serial(cli_platform)]
#[cfg_attr(windows, ignore)]
fn argv0() {
test_roc_app(
"crates/cli/tests/cli",
"argv0.roc",
&[],
&[],
&[],
"<ignored for tests>/argv0.roc\n",
UseValgrind::No,
TestCliCommands::Run,
)
}

#[test]
#[serial(cli_platform)]
#[cfg_attr(windows, ignore)]
Expand Down

0 comments on commit 1229819

Please sign in to comment.