Stream: ideas

Topic: compiler version management


view this post on Zulip Richard Feldman (Dec 22 2024 at 22:49):

here is a proposal for how the roc CLI could manage its own versions: https://docs.google.com/document/d/1Or0Uf6thmFMKAwE4pcqvZBW6siim5BT2pIwxaD048TU/edit?usp=sharing

any thoughts welcome!

view this post on Zulip Sky Rose (Dec 23 2024 at 01:27):

Love this!

Would this be compatible with asdf for people who want to manage their roc version the same way they manage versions of other tools?

view this post on Zulip Sky Rose (Dec 23 2024 at 01:41):

Could a compiler compile code with a different version? I guess future compilers can have a list of what old versions they're compatible with. But could an old compiler try to compile code with a future version label, in case there weren't breaking changes or in case any incompatible new features aren't used in that codebase? Does this system force us to be really strict about using semver for compiler versions, even if something doesn't feel like a major release?

I'm particularly thinking about an app with many dependencies, all of which have slightly different versions. That could get hard to resolve. (Though I guess this problem exists in most programming ecosystems, but just with implicit language versions instead of explicit.)

view this post on Zulip Richard Feldman (Dec 23 2024 at 01:58):

Sky Rose said:

Would this be compatible with asdf for people who want to manage their roc version the same way they manage versions of other tools?

hm, compatible in what way? :thinking:

view this post on Zulip Sky Rose (Dec 23 2024 at 03:15):

Like, you can specify a version in .tool-versions (or mise.toml), and then that version would be used in that project, and there's a smooth and consistent experience, without roc and asdf fighting each other over which version to use.

view this post on Zulip Sam Mohr (Dec 23 2024 at 03:44):

Yes, that kind of thing would be good for a monorepo as well. I don't think there's an easy way to define deps per monorepo like the compiler does. Not being able to coordinate package versions isn't great, but it's manageable. A .roc-version, or more generally a roc.toml, would help here for larger projects, even if it birfurcates the version setting process

view this post on Zulip Ayaz Hafiz (Dec 23 2024 at 04:06):

I don't think hardcoding the releases URI increases security. If a process has access to install Roc binaries they will need write and execute access on the relevant paths, at which point they could patch the binary to change the URI if they are malicious. Not hardcoding the path allows affordances like not needing to patch each binary if you want to download your own version (a very common thing in enterprises/high-availability situations where you want to have patched binaries or host them yourself)

view this post on Zulip Ayaz Hafiz (Dec 23 2024 at 04:07):

I also don't quite understand the advantage of avoiding symlinks. Is the idea resolving symlinks is too slow? Having the CLI be a wrapper is nice because then if you try to run roc on something with an incompatible version the CLI can resolve it correctly for you, and also manage the installation directory, in a single process.

view this post on Zulip Anton (Dec 23 2024 at 13:02):

It has been a pain to debug code that used execve in the past

view this post on Zulip Richard Feldman (Dec 23 2024 at 13:59):

Ayaz Hafiz said:

I don't think hardcoding the releases URI increases security. If a process has access to install Roc binaries they will need write and execute access on the relevant paths, at which point they could patch the binary to change the URI if they are malicious. Not hardcoding the path allows affordances like not needing to patch each binary if you want to download your own version (a very common thing in enterprises/high-availability situations where you want to have patched binaries or host them yourself)

I figure in situations where someone is running their own patched binaries, or is hosting themselves, they'll just change the hardcoded URL anyway as part of the patch :big_smile:

view this post on Zulip Richard Feldman (Dec 23 2024 at 14:08):

Ayaz Hafiz said:

I also don't quite understand the advantage of avoiding symlinks. Is the idea resolving symlinks is too slow?

part of the reason I don't like it is that I want it to be possible to download a single executable and have it Just Work, not have running an installer being the only possible way to get roc to work.

view this post on Zulip Richard Feldman (Dec 23 2024 at 14:09):

I don't think it's a deal-breaker that symlinks are very slightly slower, but it does bother me to think that every single run of the compiler (and scripts run directly with roc) would be slowed down for the sake of a feature that would be used super rarely

view this post on Zulip Richard Feldman (Dec 23 2024 at 14:09):

Anton said:

It has been a pain to debug code that used execve in the past

hm, do you remember what the problems were? :thinking:

view this post on Zulip Richard Feldman (Dec 23 2024 at 14:10):

Sky Rose said:

Like, you can specify a version in .tool-versions (or mise.toml), and then that version would be used in that project, and there's a smooth and consistent experience, without roc and asdf fighting each other over which version to use.

I don't think the Roc compiler code base should have a concept of other third-party tools, if that's what you mean...as in, I don't think it should recognize files with particular filenames and behave differently if they're present

view this post on Zulip Richard Feldman (Dec 23 2024 at 14:11):

that said, I think the general idea of "this is a build intended to be used in Nix or apt or something else, so here is how to switch versions using that system" seems like it could be used for asdf or something similar as well

view this post on Zulip Anton (Dec 23 2024 at 15:18):

Richard Feldman said:

Anton said:

It has been a pain to debug code that used execve in the past

hm, do you remember what the problems were? :thinking:

I could not get it to forward the correct exit code, this is the code now:

    match unsafe { libc::fork() } {
        0 => unsafe {
            // we are the child

            executable.execve(&argv, &envp);

            // Display a human-friendly error message
            println!("Error {:?}", std::io::Error::last_os_error());

            std::process::exit(1);
        },
        -1 => {
            // something failed

            // Display a human-friendly error message
            println!("Error {:?}", std::io::Error::last_os_error());

            std::process::exit(1)
        }
        pid @ 1.. => {
            let sigchld = Arc::new(AtomicBool::new(false));
            signal_hook::flag::register(signal_hook::consts::SIGCHLD, Arc::clone(&sigchld))
                .unwrap();

            let exit_code = loop {
                match memory.wait_for_child(sigchld.clone()) {
                    ChildProcessMsg::Terminate => {
                        let mut status = 0;
                        let options = 0;
                        unsafe { libc::waitpid(pid, &mut status, options) };

                        // if `WIFEXITED` returns false, `WEXITSTATUS` will just return junk
                        break if libc::WIFEXITED(status) {
                            libc::WEXITSTATUS(status)
                        } else {
                            // we don't have an exit code, but something went wrong if we're in this else
                            1
                        };
                    }
                    ChildProcessMsg::Expect => {
                        let mut writer = std::io::stdout();
                        roc_repl_expect::run::render_expects_in_memory(
                            &mut writer,
                            arena,
                            &mut expectations,
                            &interns,
                            &layout_interner,
                            &memory,
                        )
                        .unwrap();

                        memory.reset();
                    }
                }
            };

            std::process::exit(exit_code)
        }
        _ => unreachable!(),
    }

view this post on Zulip Anton (Dec 23 2024 at 15:21):

We've got the expect stuff in there as well but it seems like using execve to call Roc would have sharp edges

view this post on Zulip Richard Feldman (Dec 23 2024 at 15:27):

ah gotcha

view this post on Zulip Richard Feldman (Dec 23 2024 at 15:27):

I think in this case that wouldn't come up, because we aren't trying to mess with the exit code

view this post on Zulip Richard Feldman (Dec 23 2024 at 15:27):

just literally forward a subset of the command-line arguments and that's it

view this post on Zulip Anton (Dec 23 2024 at 16:10):

The way I understand it we're not trying to mess with the exit code either, we just want to pass it, because you obviously don't want to ignore it.

view this post on Zulip Brendan Hansknecht (Dec 23 2024 at 16:27):

To be fair, that isn't really an issue with execve and all the fork and expect chaos is going way

view this post on Zulip Brendan Hansknecht (Dec 23 2024 at 16:28):

So it should just be execve and on failure exit the process with the errno


Last updated: Jun 16 2026 at 16:19 UTC