Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replace cli flag --prebuilt-platform with --build-host #6859

Open
wants to merge 57 commits into
base: main
Choose a base branch
from

Conversation

lukewilliamboswell
Copy link
Collaborator

@lukewilliamboswell lukewilliamboswell commented Jul 2, 2024

This PR is built on top of #6808, which should be merge first.

This PR makes progress towards decoupling roc from the host toolchains, by moving host rebuilding behind a compiler flag so it is no longer the default behaviour. It is still available where required using --build-host however will give a warning;

WARNING: platforms are responsible for building hosts, this flag will be removed when internal test platforms have a build script

I was originally working towards removing rebuilding in one go in #6696, however I realised this was a very large and difficult change due to the need to change all of the cli tests.

This PR achieves the same effect, effectively removing platform rebuilding from the cli for end users -- while keeping the cli tests as they are. This enables the cli tests to be adapted one by one instead of all at once. Once all of the tests have a build script, host rebuilding functionality can be entirely removed along with the associated dependencies.

Application authors who use a platform release from a URL package are not affected by this change. These users will continue to use a prebuilt platform.

Platform authors need to include a build script in their platform to build host binaries. This change will make running platform example less convenient if they are not using a URL release, as the platform binaries will need to be built first in a separate step. For example this workflow;

  1. Build the platform binary roc build.roc using host toolchain e.g. cargo or zig
  2. Run the example application roc examples/hello-world.roc

WIP

WIP

WIP

ignore some cli tests that are going to move

try fix for linux valgrind tests

try a legacy host for valgrind

fix roc_glue tests
let platform_main_roc = match &loaded.entry_point {
EntryPoint::Executable { platform_path, .. } => platform_path.to_path_buf(),
_ => unreachable!(),
};

// For example, if we're loading the platform from a URL, it's automatically prebuilt
// even if the --prebuilt-platform CLI flag wasn't set.
let is_platform_prebuilt = prebuilt_requested || loaded.uses_prebuilt_platform;
Copy link
Collaborator Author

@lukewilliamboswell lukewilliamboswell Jul 9, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I should add a check here; if loaded.uses_prebuilt_platform is true and also build_host_requested then the platform is a URL release and we have also been instructed to rebuild the host with the flag --build-host. These two are incompatible because roc cannot build the host.


// file name for a stubbed app dynamic library file
pub fn stub_app_lib_file_name(&self) -> String {
format!("libapp.{}", self.dynamic_library_file_ext())
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On Windows I think it may just be app.dll, on unix* systems we need the lib prepended

@lukewilliamboswell lukewilliamboswell changed the title WIP replace cli flag --prebuilt-platform with --build-host Replace cli flag --prebuilt-platform with --build-host Aug 14, 2024
Comment on lines +1300 to +1302
// Copy preprocessed host to executable location.
// The surgical linker will modify that copy in-place.
//std::fs::copy(&preprocessed_path, &output_exe_path).unwrap();
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO - investigate why this is commented out.

crates/cli/tests/cli_run.rs Outdated Show resolved Hide resolved
}

#[test]
#[cfg_attr(windows, ignore)]
fn nqueens() {
test_benchmark("nQueens.roc", &["6"], "4\n", UseValgrind::Yes)
test_benchmark("nQueens.roc", vec!["6"], "4\n", UseValgrind::Yes)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why does this use vec now?

Comment on lines +80 to +107
/// Normalise the output for comparison in tests by replacing with a placeholder
fn normalize_for_tests(input: &str) -> String {
// normalise from windows line endings to unix line endings
let without_clrf = input.replace("\r\n", "\n");

// remove ANSI color codes
let without_ansi = roc_reporting::report::strip_colors(without_clrf.as_str());

// replace timings with a placeholder
let regex = Regex::new(r" in (\d+) ms\.").expect("Invalid regex pattern");
let replacement = " in <ignored for test> ms.";
let without_timings = regex.replace_all(&without_ansi, replacement);

// replace file paths with a placeholder
let filepath_regex =
Regex::new(r"\[([^:]+):(\d+)\]").expect("Invalid filepath regex pattern");
let filepath_replacement = "[<ignored for tests>:$2]";
let without_filepaths = filepath_regex.replace_all(&without_timings, filepath_replacement);

// replace error summary timings
let error_summary_regex =
Regex::new(r"(\d+) error(?:s)? and (\d+) warning(?:s)? found in \d+ ms")
.expect("Invalid error summary regex pattern");
let error_summary_replacement = "$1 error and $2 warning found in <ignored for test> ms";
error_summary_regex
.replace_all(&without_filepaths, error_summary_replacement)
.to_string()
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice :) I don't think we had this before

)
let expected_ending = "Roc <3 Rust!\n🔨 Building host ...\n";
let runner = Run::new_roc()
.arg(CMD_RUN)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assuming that run still tries to run a program even if there are errors, that is probably not what we want to use here...

@lukewilliamboswell
Copy link
Collaborator Author

TODO

  • ensure that roc without a subcommand behaves like roc dev does today and doesn't run with any errors
  • run cli tests without any subcommand.
  • implement ROC_EXPERIMENTAL_DEFER_ERRORS flag

see https://roc.zulipchat.com/#narrow/stream/395097-compiler-development/topic/which.20roc.20subcommand.20for.20roc_cli.20tests/near/467492272

@lukewilliamboswell lukewilliamboswell marked this pull request as ready for review September 5, 2024 03:12
@Anton-4
Copy link
Collaborator

Anton-4 commented Sep 6, 2024

I'll get started on moving the build process out from under valgrind like we currently do on main

@Anton-4
Copy link
Collaborator

Anton-4 commented Sep 6, 2024

I'm not a fan of this java style:

impl Run {
    pub fn new<S>(exe: S) -> Self
    where
        S: AsRef<OsStr>,
    {
        let exe: OsString = exe.as_ref().into();
        Self {
            args: vec![exe],
            stdin_vals: vec![],
            env: vec![],
            cwd: None,
            run_with_valgrind: false,
        }
    }

    pub fn new_roc() -> Self {
        Self::new(path_to_roc_binary())
    }

    pub fn command(&self) -> Command {
        let mut cmd = Command::new(&self.args[0]);
        for arg in self.args[1..].iter() {
            cmd.arg(arg);
        }
        for (k, v) in self.env.iter() {
            cmd.env(k, v);
        }
        cmd
    }

    pub fn valgrind_command(&self) -> (Command, NamedTempFile) {
        let mut cmd = Command::new("valgrind");

I can't easily see anymore which values a function actually uses, the old version on main was not great either but I am going to take a stab at a functional refactor.

Sidenote: Be careful with grouping things in utils files/folders, this can slow compilation because many crates can end up relying on a fat utils crate/file with many dependencies. Also make sure to put dependencies that are only required for testing under dev-dependencies in Cargo.toml

@Anton-4
Copy link
Collaborator

Anton-4 commented Sep 6, 2024

This will take a while, btw I'm not getting rid of a lot, just mostly shuffling things around, feeling what makes the most sense and is least complex.

I'm also going to try removing all the valgrind xml handling code, we may not be using that anymore.

@smores56
Copy link
Sponsor Collaborator

smores56 commented Sep 8, 2024

I've reviewed everything except for build/src/program.rs. I think it all looks good except for two comments I left. I'll let you know once I've gotten to that file.

@smores56
Copy link
Sponsor Collaborator

smores56 commented Sep 8, 2024

@Anton-4 the new builder style is pretty popular in Rust because of a lack of optional or named parameters. If it had those, then we wouldn't need builders. What are you planning to do instead? I think something like this could work:

#[derive(Default)]
pub struct RunParams {
    args: Vec<OsString>,
    env: Vec<(String, String)>,
    stdin_vals: Vec<&'static str>,
    cwd: Option<OsString>,
    run_with_valgrind: bool,
}

pub fn run_command(cmd: Command, params: RunParams);

let out =
    run_command(
        ROC_CLI,
        RunParams {
            args: ["abc", "def"].into(),
            env: [("ROC_TEST", "1")].into(),
            ..Default::default(),
        }
    ).unwrap();

.run();
out.assert_clean_success();
});
}

fn test_benchmark(
Copy link
Sponsor Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like that the above tests show all of the steps we're taking: build the host if necessary, run the test with these args, check the output. We don't seem to do that for the benchmark tests. Are you planning on making these two groups work the same way eventually, or is there a reason we have them working differently?

use std::path::PathBuf;
use std::process::{Command, ExitStatus, Stdio};
use std::sync::Mutex;
use tempfile::NamedTempFile;

lazy_static::lazy_static! {
Copy link
Sponsor Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick: LazyLock has been stabilized, we should prefer that instead of an external library.

@Anton-4
Copy link
Collaborator

Anton-4 commented Sep 9, 2024

What are you planning to do instead?

Right now, I just have two functions roc_run_default and roc_run. roc_run_default has less arguments, and roc_run has all of them. I'm going to try to finish my code today to see if that approach works.

I think something like this could work

That also seems like a nice alternative with low complexity, I'll keep it in mind.

@Anton-4
Copy link
Collaborator

Anton-4 commented Sep 9, 2024

I'm going to try to finish my code today to see if that approach works.

Still need to do some refactoring of the glue tests and valgrind tests, targeting tomorrow now...

@Anton-4
Copy link
Collaborator

Anton-4 commented Sep 10, 2024

So... looks like the Builder does make the best trade-offs after all 😄

Just functions does not work because we need to able to clone Command for that, which is not possible.
And the struct calls end up looking pretty messy:

let roc_cmd_out =
            run_roc_maybe_both_linkers(
                RocCmdParams {
                    sub_command: CMD_BUILD,
                    args: all_to_os_string([
                        roc_file_path.to_str().unwrap(),
                        BUILD_HOST_FLAG,
                        SUPPRESS_BUILD_HOST_WARNING_FLAG,
                        ]),
                    ..Default::default()
                },
                ON_LINUX
            );

Well, lessons learned, I should have asked more questions instead of dismissing it.

@Anton-4
Copy link
Collaborator

Anton-4 commented Sep 10, 2024

I did make some nice improvements in other places though, I'm going to add those here.

@Anton-4
Copy link
Collaborator

Anton-4 commented Sep 10, 2024

Need to make sure I don't forget this:

  • Ensure cli tests run with both linkers on linux

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants