diff --git a/.github/workflows/nix_linux_x86_64.yml b/.github/workflows/nix_linux_x86_64.yml index 17e8e0a013..e751a4542d 100644 --- a/.github/workflows/nix_linux_x86_64.yml +++ b/.github/workflows/nix_linux_x86_64.yml @@ -21,3 +21,11 @@ jobs: - name: execute tests with --release run: /home/big-ci-user/.nix-profile/bin/nix develop -c cargo test --locked --release + + - name: upload args binary for inspection + if: always() + uses: actions/upload-artifact@v3 + with: + name: failing_args_binary + path: examples/interactive/args + retention-days: 4 diff --git a/crates/cli/tests/cli_run.rs b/crates/cli/tests/cli_run.rs index c164b6c0c4..368fd2dd99 100644 --- a/crates/cli/tests/cli_run.rs +++ b/crates/cli/tests/cli_run.rs @@ -61,12 +61,18 @@ mod cli_run { #[cfg(target_os = "macos")] const ALLOW_VALGRIND: bool = false; + #[derive(Debug, PartialEq, Eq)] + enum Arg<'a> { + ExamplePath(&'a str), + PlainText(&'a str), + } + #[derive(Debug, PartialEq, Eq)] struct Example<'a> { filename: &'a str, executable_filename: &'a str, stdin: &'a [&'a str], - input_paths: &'a [&'a str], + arguments: &'a [Arg<'a>], expected_ending: &'a str, use_valgrind: bool, } @@ -223,7 +229,7 @@ mod cli_run { stdin: &[&str], executable_filename: &str, flags: &[&str], - input_paths: &[&str], + args: &[&Arg], expected_ending: &str, ) { assert!(input_paths.is_empty(), "Wasm does not support input files"); @@ -273,10 +279,20 @@ mod cli_run { let file_name = example_file(dir_name, example.filename); let mut app_args: Vec = vec![]; - for file in example.input_paths { - app_args.push(example_file(dir_name, file).to_str().unwrap().to_string()); + for arg in example.arguments { + match arg { + Arg::ExamplePath(file) => { + app_args.push(example_file(dir_name, file).to_str().unwrap().to_string()); + } + Arg::PlainText(arg) => { + app_args.push(arg.to_string()); + } + } } + // workaround for surgical linker issue, see PR #3990 + let mut custom_flags : Vec<&str> = vec![]; + match example.executable_filename { "form" | "hello-gui" | "breakout" | "ruby" => { // Since these require things the build system often doesn't have @@ -296,6 +312,9 @@ mod cli_run { eprintln!("WARNING: skipping testing example {} because it only works in a browser!", example.filename); return; } + "args" => { + custom_flags = vec![LINKER_FLAG, "legacy"]; + } _ => {} } @@ -304,12 +323,13 @@ mod cli_run { &file_name, example.stdin, example.executable_filename, - &[], + &custom_flags, &app_args, example.expected_ending, example.use_valgrind, ); + custom_flags.push(OPTIMIZE_FLAG); // This is mostly because the false interpreter is still very slow - // 25s for the cli tests is just not acceptable during development! #[cfg(not(debug_assertions))] @@ -317,7 +337,7 @@ mod cli_run { &file_name, example.stdin, example.executable_filename, - &[OPTIMIZE_FLAG], + &custom_flags, &app_args, example.expected_ending, example.use_valgrind, @@ -368,7 +388,7 @@ mod cli_run { filename: "main.roc", executable_filename: "helloWorld", stdin: &[], - input_paths: &[], + arguments: &[], expected_ending:"Hello, World!\n", use_valgrind: true, }, @@ -376,7 +396,7 @@ mod cli_run { filename: "main.roc", executable_filename: "rocLovesPlatforms", stdin: &[], - input_paths: &[], + arguments: &[], expected_ending:"Which platform am I running on now?\n", use_valgrind: true, }, @@ -387,7 +407,7 @@ mod cli_run { // filename: "rocLovesC.roc", // executable_filename: "rocLovesC", // stdin: &[], - // input_paths: &[], + // arguments: &[], // expected_ending:"Roc <3 C!\n", // use_valgrind: true, // }, @@ -395,7 +415,7 @@ mod cli_run { filename: "rocLovesRust.roc", executable_filename: "rocLovesRust", stdin: &[], - input_paths: &[], + arguments: &[], expected_ending:"Roc <3 Rust!\n", use_valgrind: true, }, @@ -403,7 +423,7 @@ mod cli_run { filename: "rocLovesSwift.roc", executable_filename: "rocLovesSwift", stdin: &[], - input_paths: &[], + arguments: &[], expected_ending:"Roc <3 Swift!\n", use_valgrind: true, }, @@ -411,7 +431,7 @@ mod cli_run { filename: "rocLovesWebAssembly.roc", executable_filename: "rocLovesWebAssembly", stdin: &[], - input_paths: &[], + arguments: &[], expected_ending:"Roc <3 Web Assembly!\n", use_valgrind: true, }, @@ -419,7 +439,7 @@ mod cli_run { filename: "rocLovesZig.roc", executable_filename: "rocLovesZig", stdin: &[], - input_paths: &[], + arguments: &[], expected_ending:"Roc <3 Zig!\n", use_valgrind: true, }, @@ -427,7 +447,7 @@ mod cli_run { filename: "main.roc", executable_filename: "libhello", stdin: &[], - input_paths: &[], + arguments: &[], expected_ending:"", use_valgrind: true, }, @@ -435,7 +455,7 @@ mod cli_run { filename: "fibonacci.roc", executable_filename: "fibonacci", stdin: &[], - input_paths: &[], + arguments: &[], expected_ending:"55\n", use_valgrind: true, }, @@ -443,7 +463,7 @@ mod cli_run { filename: "Hello.roc", executable_filename: "hello-gui", stdin: &[], - input_paths: &[], + arguments: &[], expected_ending: "", use_valgrind: false, }, @@ -451,7 +471,7 @@ mod cli_run { filename: "breakout.roc", executable_filename: "breakout", stdin: &[], - input_paths: &[], + arguments: &[], expected_ending: "", use_valgrind: false, }, @@ -459,7 +479,7 @@ mod cli_run { filename: "quicksort.roc", executable_filename: "quicksort", stdin: &[], - input_paths: &[], + arguments: &[], expected_ending: "[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2]\n", use_valgrind: true, }, @@ -467,15 +487,23 @@ mod cli_run { // filename: "Quicksort.roc", // executable_filename: "quicksort", // stdin: &[], - // input_paths: &[], + // arguments: &[], // expected_ending: "[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2]\n", // use_valgrind: true, // }, + args:"interactive" => Example { + filename: "args.roc", + executable_filename: "args", + stdin: &[], + arguments: &[Arg::PlainText("hello"), Arg::PlainText("world")], + expected_ending: "hello world\n", + use_valgrind: false, + }, effects:"interactive" => Example { filename: "effects.roc", executable_filename: "effects", stdin: &["hi there!"], - input_paths: &[], + arguments: &[], expected_ending: "hi there!\nIt is known\n", use_valgrind: true, }, @@ -483,7 +511,7 @@ mod cli_run { // filename: "Main.roc", // executable_filename: "tea-example", // stdin: &[], - // input_paths: &[], + // arguments: &[], // expected_ending: "", // use_valgrind: true, // }, @@ -491,7 +519,7 @@ mod cli_run { filename: "form.roc", executable_filename: "form", stdin: &["Giovanni\n", "Giorgio\n"], - input_paths: &[], + arguments: &[], expected_ending: "Hi, Giovanni Giorgio! 👋\n", use_valgrind: false, }, @@ -499,7 +527,7 @@ mod cli_run { filename: "tui.roc", executable_filename: "tui", stdin: &["foo\n"], // NOTE: adding more lines leads to memory leaks - input_paths: &[], + arguments: &[], expected_ending: "Hello Worldfoo!\n", use_valgrind: true, }, @@ -507,7 +535,7 @@ mod cli_run { // filename: "Main.roc", // executable_filename: "custom-malloc-example", // stdin: &[], - // input_paths: &[], + // arguments: &[], // expected_ending: "ms!\nThe list was small!\n", // use_valgrind: true, // }, @@ -515,7 +543,7 @@ mod cli_run { // filename: "Main.roc", // executable_filename: "task-example", // stdin: &[], - // input_paths: &[], + // arguments: &[], // expected_ending: "successfully wrote to file\n", // use_valgrind: true, // }, @@ -524,7 +552,7 @@ mod cli_run { filename: "False.roc", executable_filename: "false", stdin: &[], - input_paths: &["examples/hello.false"], + arguments: &[Arg::ExamplePath("examples/hello.false")], expected_ending:"Hello, World!\n", use_valgrind: false, } @@ -534,7 +562,7 @@ mod cli_run { filename: "static-site.roc", executable_filename: "static-site", stdin: &[], - input_paths: &["input", "output"], + arguments: &[Arg::ExamplePath("input"), Arg::ExamplePath("output")], expected_ending: "Processed 3 files with 3 successes and 0 errors\n", use_valgrind: false, } @@ -564,8 +592,15 @@ mod cli_run { let mut ran_without_optimizations = false; let mut app_args: Vec = vec![]; - for file in benchmark.input_paths { - app_args.push(examples_dir("benchmarks").join(file).to_str().unwrap().to_string()); + for arg in benchmark.arguments { + match arg { + Arg::ExamplePath(file) => { + app_args.push(examples_dir("benchmarks").join(file).to_str().unwrap().to_string()); + } + Arg::PlainText(arg) => { + app_args.push(arg.to_string()); + } + } } BENCHMARKS_BUILD_PLATFORM.call_once( || { @@ -717,7 +752,7 @@ mod cli_run { filename: "NQueens.roc", executable_filename: "nqueens", stdin: &["6"], - input_paths: &[], + arguments: &[], expected_ending: "4\n", use_valgrind: true, }, @@ -725,7 +760,7 @@ mod cli_run { filename: "CFold.roc", executable_filename: "cfold", stdin: &["3"], - input_paths: &[], + arguments: &[], expected_ending: "11 & 11\n", use_valgrind: true, }, @@ -733,7 +768,7 @@ mod cli_run { filename: "Deriv.roc", executable_filename: "deriv", stdin: &["2"], - input_paths: &[], + arguments: &[], expected_ending: "1 count: 6\n2 count: 22\n", use_valgrind: true, }, @@ -741,7 +776,7 @@ mod cli_run { filename: "RBTreeCk.roc", executable_filename: "rbtree-ck", stdin: &["100"], - input_paths: &[], + arguments: &[], expected_ending: "10\n", use_valgrind: true, }, @@ -749,7 +784,7 @@ mod cli_run { filename: "RBTreeInsert.roc", executable_filename: "rbtree-insert", stdin: &[], - input_paths: &[], + arguments: &[], expected_ending: "Node Black 0 {} Empty Empty\n", use_valgrind: true, }, @@ -757,7 +792,7 @@ mod cli_run { // filename: "RBTreeDel.roc", // executable_filename: "rbtree-del", // stdin: &["420"], - // input_paths: &[], + // arguments: &[], // expected_ending: "30\n", // use_valgrind: true, // }, @@ -765,7 +800,7 @@ mod cli_run { filename: "TestAStar.roc", executable_filename: "test-astar", stdin: &[], - input_paths: &[], + arguments: &[], expected_ending: "True\n", use_valgrind: false, }, @@ -773,7 +808,7 @@ mod cli_run { filename: "TestBase64.roc", executable_filename: "test-base64", stdin: &[], - input_paths: &[], + arguments: &[], expected_ending: "encoded: SGVsbG8gV29ybGQ=\ndecoded: Hello World\n", use_valgrind: true, }, @@ -781,7 +816,7 @@ mod cli_run { filename: "Closure.roc", executable_filename: "closure", stdin: &[], - input_paths: &[], + arguments: &[], expected_ending: "", use_valgrind: false, }, @@ -789,7 +824,7 @@ mod cli_run { filename: "Issue2279.roc", executable_filename: "issue2279", stdin: &[], - input_paths: &[], + arguments: &[], expected_ending: "Hello, world!\n", use_valgrind: true, }, @@ -797,7 +832,7 @@ mod cli_run { filename: "QuicksortApp.roc", executable_filename: "quicksortapp", stdin: &[], - input_paths: &[], + arguments: &[], expected_ending: "todo put the correct quicksort answer here", use_valgrind: true, }, diff --git a/crates/cli/tests/known_bad/TypeError.roc b/crates/cli/tests/known_bad/TypeError.roc index c1052dd7b3..2eaec83ae7 100644 --- a/crates/cli/tests/known_bad/TypeError.roc +++ b/crates/cli/tests/known_bad/TypeError.roc @@ -3,7 +3,7 @@ app "type-error" imports [pf.Stdout.{ line }, pf.Task.{ await }] provides [main] to pf -main = +main = \_args -> _ <- await (line "a") _ <- await (line "b") _ <- await (line "c") diff --git a/examples/interactive/.gitignore b/examples/interactive/.gitignore index 6eacf36982..084ea28578 100644 --- a/examples/interactive/.gitignore +++ b/examples/interactive/.gitignore @@ -1,3 +1,4 @@ +args countdown echo effects diff --git a/examples/interactive/args.roc b/examples/interactive/args.roc new file mode 100644 index 0000000000..7b03f98938 --- /dev/null +++ b/examples/interactive/args.roc @@ -0,0 +1,11 @@ +app "args" + packages { pf: "cli-platform/main.roc" } + imports [pf.Stdout, pf.Task] + provides [main] to pf + +main : List Str -> Task.Task {} [] [Write [Stdout]] +main = \args -> + _ <- Task.await (Stdout.line "Here are the args I got from the command line:") + joinedArgs = Str.joinWith args " " + + Stdout.line joinedArgs diff --git a/examples/interactive/cli-platform/main.roc b/examples/interactive/cli-platform/main.roc index 6d3d42a6d5..c78c6ef610 100644 --- a/examples/interactive/cli-platform/main.roc +++ b/examples/interactive/cli-platform/main.roc @@ -1,9 +1,9 @@ platform "cli" - requires {} { main : Task {} [] * } + requires {} { main : List Str -> Task {} [] * } exposes [] packages {} imports [Task.{ Task }, InternalTask, Effect.{ Effect }] provides [mainForHost] -mainForHost : Effect (Result {} []) as Fx -mainForHost = InternalTask.toEffect main +mainForHost : List Str -> Effect (Result {} []) as Fx +mainForHost = \args -> InternalTask.toEffect (main args) diff --git a/examples/interactive/cli-platform/src/lib.rs b/examples/interactive/cli-platform/src/lib.rs index d801fcd8da..80cb6c4d90 100644 --- a/examples/interactive/cli-platform/src/lib.rs +++ b/examples/interactive/cli-platform/src/lib.rs @@ -9,6 +9,7 @@ use core::mem::MaybeUninit; use glue::Metadata; use libc; use roc_std::{RocList, RocResult, RocStr}; +use std::borrow::Borrow; use std::ffi::{CStr, OsStr}; use std::fs::File; use std::os::raw::c_char; @@ -20,7 +21,7 @@ use file_glue::WriteErr; extern "C" { #[link_name = "roc__mainForHost_1_exposed_generic"] - fn roc_main(output: *mut u8); + fn roc_main(output: *mut u8, args: *const RocList); #[link_name = "roc__mainForHost_size"] fn roc_main_size() -> i64; @@ -84,11 +85,16 @@ pub extern "C" fn rust_main() -> i32 { let size = unsafe { roc_main_size() } as usize; let layout = Layout::array::(size).unwrap(); + // TODO: can we be more efficient about reusing the String's memory for RocStr? + let args: RocList = std::env::args_os() + .map(|s| RocStr::from(s.to_string_lossy().borrow())) + .collect(); + unsafe { // TODO allocate on the stack if it's under a certain size let buffer = std::alloc::alloc(layout); - roc_main(buffer); + roc_main(buffer, &args); let result = call_the_closure(buffer); diff --git a/examples/interactive/countdown.roc b/examples/interactive/countdown.roc index 710ee83486..473fdc18a8 100644 --- a/examples/interactive/countdown.roc +++ b/examples/interactive/countdown.roc @@ -3,7 +3,7 @@ app "countdown" imports [pf.Stdin, pf.Stdout, pf.Task.{ await, loop, succeed }] provides [main] to pf -main = +main = \_args -> _ <- await (Stdout.line "\nLet's count down from 10 together - all you have to do is press .") _ <- await Stdin.line loop 10 tick diff --git a/examples/interactive/echo.roc b/examples/interactive/echo.roc index 4de7dd8428..d6c225a76a 100644 --- a/examples/interactive/echo.roc +++ b/examples/interactive/echo.roc @@ -3,8 +3,8 @@ app "echo" imports [pf.Stdin, pf.Stdout, pf.Task] provides [main] to pf -main : Task.Task {} [] [Read [Stdin], Write [Stdout]] -main = +main : List Str -> Task.Task {} [] [Read [Stdin], Write [Stdout]] +main = \_args -> _ <- Task.await (Stdout.line "🗣 Shout into this cave and hear the echo! 👂👂👂") Task.loop {} (\_ -> Task.map tick Step) diff --git a/examples/interactive/form.roc b/examples/interactive/form.roc index 10261464fc..9e8d7c46fb 100644 --- a/examples/interactive/form.roc +++ b/examples/interactive/form.roc @@ -3,8 +3,8 @@ app "form" imports [pf.Stdin, pf.Stdout, pf.Task.{ await, Task }] provides [main] to pf -main : Task {} * [Read [Stdin], Write [Stdout]] -main = +main : List Str -> Task {} * [Read [Stdin], Write [Stdout]] +main = \_args -> _ <- await (Stdout.line "What's your first name?") firstName <- await Stdin.line _ <- await (Stdout.line "What's your last name?") diff --git a/examples/interactive/http-get.roc b/examples/interactive/http-get.roc index a890a2beb7..10d90bed38 100644 --- a/examples/interactive/http-get.roc +++ b/examples/interactive/http-get.roc @@ -3,8 +3,8 @@ app "http-get" imports [pf.Http, pf.Task, pf.Stdin, pf.Stdout] provides [main] to pf -main : Task.Task {} [] [Read [Stdin], Write [Stdout], Network [Http]] -main = +main : List Str -> Task.Task {} [] [Read [Stdin], Write [Stdout], Network [Http]] +main = \_args -> _ <- Task.await (Stdout.line "Please enter a URL to fetch") url <- Task.await Stdin.line