Skip to content

Conversation

@KristofferC
Copy link
Member

alternative to #1095

I am not much of a rust programmer so I kind of wrote pseudo code and had Claude Code translate it for me. Should still do some manual cleanups.

In use:

~/JuliaPkgs/TimerOutputs.jl
❯ julia --project -q
Info: Installing Julia 1.10.10 automatically per juliaup settings...
Checking for new Julia versions
Info: Successfully installed Julia 1.10.10.
julia> VERSION
v"1.10.10"

# Change the manifest file

~/JuliaPkgs/TimerOutputs.jl
❯ julia --project -q
Info: Installing Julia 1.10.8 automatically per juliaup settings...
Checking for new Julia versions
Installing Julia 1.10.8+0.aarch64.apple.darwin14
Info: Successfully installed Julia 1.10.8.
julia> VERSION
v"1.10.8"


// Search for manifest files in priority order
// JuliaManifest.toml takes precedence over Manifest.toml
for manifest_name in ["JuliaManifest.toml", "Manifest.toml"] {
Copy link
Member

@DilumAluthge DilumAluthge Oct 30, 2025

Choose a reason for hiding this comment

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

Needs support for {Julia,}Manifest-vX.Y.toml

Copy link
Member

Choose a reason for hiding this comment

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

One idea:

  1. Loop over all files in the directory. Ignore folders.
  2. Collect only the filenames that match the allowed patterns.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I think it would also be reasonable (and in my mind a bit less magical) to say that version selection via the manifest only happens from a JuliaManifest.toml or Manifest.toml file.

The {Julia,}Manifest-vX.Y.toml manifests would then only come into play if one used a higher precedent order override.

@DilumAluthge
Copy link
Member

How should this work if the project directory has multiple versioned manifests, e.g. Manifest-1.10.toml and Manifest-1.12.toml?

@KristofferC
Copy link
Member Author

Probably the highest version?

@KristofferC KristofferC force-pushed the kc/auto_version_select branch from 2510c05 to 842eafa Compare October 31, 2025 18:52
@DilumAluthge
Copy link
Member

DilumAluthge commented Oct 31, 2025

Probably the highest version?

That seems okay.

If the user explicitly provides the desired version (e.g. julia +1.10 --project or julia +1.10.3 --project or julia +lts --project), we'd respect the Julia version specified by julia +ver, right?

@KristofferC
Copy link
Member Author

If the user explicitly provides the desired version (e.g. julia +1.10 --project or julia +1.10.3 --project or julia +lts --project), we'd respect the Julia version specified by julia +ver, right?

Yes, if you look at the priority in the README (which I updated here) it is:

1. A command line Julia version specifier, such as `julia +release`.
2. The `JULIAUP_CHANNEL` environment variable.
3. A directory override, set with the `juliaup override set` command.
3. The default Juliaup channel.
4. Automatic version selection based on the active project.
5. The default Juliaup channel.

So an explicitly provided version has top-priority and will always be used.

@DilumAluthge
Copy link
Member

DilumAluthge commented Oct 31, 2025

BTW I think item 3 should be removed from that list? Redundant with item 5, and IIUC you prefer item 4 over item 5?

EDIT: This was just a confusion over a typo in a GitHub comment. The actual code in the PR was correct.

@KristofferC KristofferC force-pushed the kc/auto_version_select branch 3 times, most recently from 220d16a to 58b6b04 Compare October 31, 2025 22:15
@KristofferC
Copy link
Member Author

I don't fully understand why it is redundant or why this PR makes it more redundant.

@davidanthoff
Copy link
Collaborator

I believe @DilumAluthge was just looking at the list as pasted in the comment here, where there are two 3) items. It is all correct in the PR that modifies the README.

@KristofferC
Copy link
Member Author

Oops, I must have copy pasted from the diff.

@KristofferC
Copy link
Member Author

I realize a better way of testing this is (of course) to call julia itself with the same project flag and env variable and get the truth from that.

@IanButterworth
Copy link
Member

Just had a thought.. how does this handle manifests from julia master, i.e. 1.14, given that there's no "1.14" channel? I guess there isn't a "1.13" either.

@KristofferC
Copy link
Member Author

KristofferC commented Nov 2, 2025

It uses nightly channel if the version is higher than any known version. But maybe it should emit some warning when that happens...

@KristofferC KristofferC force-pushed the kc/auto_version_select branch 3 times, most recently from c846ba4 to 988b305 Compare November 3, 2025 09:36
@tecosaur
Copy link
Member

tecosaur commented Nov 3, 2025

I may be missing something, but why is this ~3x the LOC of #1095? Is this PR larger in scope?

I do notice that #1095 was written before versioned manifests became a thing, but that seems like just a handful of extra lines.

@KristofferC KristofferC marked this pull request as draft November 3, 2025 11:21
@KristofferC
Copy link
Member Author

KristofferC commented Nov 3, 2025

I can remove tests if you want to... But as I have said, you are free to reopen your PR and we can try them on a bunch of cases. But based on

I don't think it's realistic that I'll get around to writing the missing tests, let alone resolving the merge conflicts any time soon.

I thought you didn't plan on getting back to that "any time soon" so I started this. I don't know if four days counts as "any time soon" or if just the fact that someone else started working on it made your desire come back.

However, some things that this does that might be different from that branch:

  • Launch +nightly if the version is > than anyone version we know
  • Launch 1.x+nightly if x is > than any version we now for 1.x
  • Expand ~ to homedir
  • LOAD_PATH
  • Logging
  • Versioned manifests
  • Maybe more, didn't look more.

@tecosaur
Copy link
Member

tecosaur commented Nov 3, 2025

I can remove tests if you want to... But as I have said, you are free to reopen your PR and we can try them on a bunch of cases. But based on

Oh, I'm talking about the non-testing code. Taking a slightly closer look, it seems like ~700 LOC compared to ~400 in the original PR, which doesn't seem too bad given the extra functionality you mentioned.

I thought you didn't plan on getting back to that "any time soon" so I started this. I don't know if four days counts as "any time soon" or if just the fact that someone else started working on it made your desire come back.

Yea, I wasn't thinking of getting back to this any time soon when I said that. I also have a track record of getting around to things later than I expected, which I try to account for.

After making that comment, and given you said that functionality seems needed, I then thought I should just "take a peek" at it that evening to see how bad the merge conflicts really are, and then we talked about how the functionality should work over Slack, and all that moved the work from back-of-mind to front-of-mind. I tend to work on what I'm thinking about.

Net result: I've ended up poking at this months earlier than I thought I would. If there is value in me spinning up that branch again, it's much more viable than it was four days ago. However, if Sonnet 4.5 can't take care of the test writing, or for some other reason the work ends up in limbo again, I don't currently have the motivation in this area to keep working on this for a prolonged period.

Looking at how hard adding project/nightly/versioned manifest support is, and doing it if it's easy, I am up for (but not much more).

@KristofferC KristofferC force-pushed the kc/auto_version_select branch 2 times, most recently from 0a18fe8 to f0e8600 Compare November 3, 2025 13:56
@KristofferC
Copy link
Member Author

KristofferC commented Nov 3, 2025

I rewrote a lot of the implementation here because I realized (while being on the treadmill 🏃‍♂️) that it made a lot of sense to just directly "transpile" the julia code itself that does the project and manifest file resolving. That should make it easier to review, easier to update and more likely to be correct.

As a test of one of the functionality I wanted to use this for I did:

(@v1.12) pkg> app add https://github.com/KristofferC/Rot13.jl
...
[ Info: For package: Rot13 installed apps rot13

which installs the rot13 Julia app. I then set the julia command to run apps with to the juliaup julia and we can see how it detects the julia version in the manifest for the app (which requires JULIA_LOAD_PATH handling support) and uses the correct version:

❯ JULIAUP_LOG=Debug JULIA_APPS_JULIA_CMD=~/juliaup/target/debug/julia rot13 foobar
[2025-11-03T13:54:58Z DEBUG julia] VersionDetect::Found valid project in JULIA_LOAD_PATH entry: /home/kc/.julia/environments/apps/Rot13
[2025-11-03T13:54:58Z DEBUG julia] VersionDetect::Using project file: /home/kc/.julia/environments/apps/Rot13/Project.toml
[2025-11-03T13:54:58Z DEBUG julia] VersionDetect::Detected manifest file: /home/kc/.julia/environments/apps/Rot13/Manifest.toml
[2025-11-03T13:54:58Z DEBUG julia] VersionDetect::Read Julia version from manifest: 1.12.1
sbbone

@KristofferC KristofferC marked this pull request as ready for review November 3, 2025 14:00
@KristofferC KristofferC force-pushed the kc/auto_version_select branch 2 times, most recently from b133d52 to e5e59b0 Compare November 3, 2025 14:03
@MilesCranmer
Copy link
Member

I think runtime opt-in would be good too (since it is a behavioural change), it just means there's a bit more complexity compared to a compilation flag. It is subjective though. Also I don't feel strongly about this so I'll leave it up to you

@KristofferC
Copy link
Member Author

Actually, it might be better to move the new utility functions to a file src/version_selection.rs, and then both the julialauncher.rs file and a tests file tests/version_selection.rs can refer to those functions?

The determine_channel function would have to go there too, then since we use that for the unit tests (and you cannot do the integration tests in test of functions in the binary).

But I mean, If I read the rust manual https://doc.rust-lang.org/book/ch11-03-test-organization.html it says:

The purpose of unit tests is to test each unit of code in isolation from the rest of the code to quickly pinpoint where code is and isn’t working as expected. You’ll put unit tests in the src directory in each file with the code that they’re testing. The convention is to create a module named tests in each file to contain the test functions and to annotate the module with cfg(test).

which is what is done here. Why are we going against what the manual suggests in terms of code organization? If you want, I can just delete some tests.

@KristofferC
Copy link
Member Author

KristofferC commented Nov 14, 2025

Anyway, I tried some kind of reorganization in the last commit.

@MilesCranmer
Copy link
Member

I'm not sure, I would guess they just mean tiny pure unittests of specific properties of helper functions? The tests here feel heavier. Of course this is all subjective. Thanks for moving it though.

For me a useful point of reference was to read through other crates like https://github.com/serde-rs/json, https://github.com/clap-rs/clap/tree/master/clap_complete, https://github.com/dtolnay/anyhow, which tend to avoid the inline tests (for whatever reason)

@davidanthoff
Copy link
Collaborator

I'll respond to the other points when I have more time, but just on this one:

I really think not. If you are worried it is buggy it could either be launched on the pre-release channel and have people test it out or it can be put behind a (run time) opt in. But having it as basically a compile time options seems weird.

It is a runtime opt-in. You can probably cherry-pick 3a2c51a and then 343a424 was the actual runtime if part of the feature flag.

@KristofferC
Copy link
Member Author

KristofferC commented Nov 14, 2025

It is a runtime opt-in.

Oh sorry, I thought we were talking about these type of features https://doc.rust-lang.org/cargo/reference/features.html

Edit: Added it

@MilesCranmer
Copy link
Member

(I did too, oops!)

@KristofferC KristofferC force-pushed the kc/auto_version_select branch from 8b87bc1 to 5e4df44 Compare November 14, 2025 20:06
@KristofferC KristofferC force-pushed the kc/auto_version_select branch from 5e4df44 to f0634f8 Compare November 14, 2025 20:17
@KristofferC KristofferC force-pushed the kc/auto_version_select branch from 6178e7e to 2da70f0 Compare November 14, 2025 20:44
@MilesCranmer
Copy link
Member

Just to check, do u want me to click resolved on comments once you give a thumbs up, or will you click resolved after implementing?

@KristofferC
Copy link
Member Author

Just to check, do u want me to click resolved on comments once you give a thumbs up, or will you click resolved after implementing?

Not sure.


I made the "feature" opt-in for now since that seems to be what people want.

Copy link
Member

@MilesCranmer MilesCranmer left a comment

Choose a reason for hiding this comment

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

I think it's looking pretty good; most comments are to improve the code. Some questions are on the desired behaviour of errors.

.filter(|version| version.major == major && version.minor == minor)
.max()
}
}
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
}
pub fn has_channel(&self, version: &str) -> bool {
self.available_channels.contains_key(version)
}
}


pub fn resolve_auto_channel(required: String, versions_db: &JuliaupVersionDB) -> Result<String> {
// Check if exact version is available
if versions_db.available_channels.contains_key(&required) {
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
if versions_db.available_channels.contains_key(&required) {
if versions_db.has_channel(&required) {

Comment on lines 547 to 550
if versions_db
.available_channels
.contains_key(&versioned_nightly)
{
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
if versions_db
.available_channels
.contains_key(&versioned_nightly)
{
if versions_db.has_channel(&versioned_nightly) {

Comment on lines 347 to 368
// Try --project or JULIA_PROJECT first
if let Some(project_file) = init_active_project_impl(
args,
current_dir,
julia_project.as_deref(),
depot_path.as_deref(),
)? {
return extract_version_from_project(project_file);
}

// Fall back to JULIA_LOAD_PATH
if let Some(ref load_path) = julia_load_path {
if let Some(project_file) =
find_project_from_load_path(load_path, current_dir, depot_path.as_deref())?
{
return extract_version_from_project(project_file);
}
}

// No project found
log::debug!("VersionDetect::No project specification found");
Ok(None)
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
// Try --project or JULIA_PROJECT first
if let Some(project_file) = init_active_project_impl(
args,
current_dir,
julia_project.as_deref(),
depot_path.as_deref(),
)? {
return extract_version_from_project(project_file);
}
// Fall back to JULIA_LOAD_PATH
if let Some(ref load_path) = julia_load_path {
if let Some(project_file) =
find_project_from_load_path(load_path, current_dir, depot_path.as_deref())?
{
return extract_version_from_project(project_file);
}
}
// No project found
log::debug!("VersionDetect::No project specification found");
Ok(None)
// Resolve project file (in priority order)
let maybe_project_file =
// 1. --project flag or JULIA_PROJECT env
init_active_project_impl(
args,
current_dir,
julia_project.as_deref(),
depot_path.as_deref(),
)?
// 2. Fallback to JULIA_LOAD_PATH
.or_else(|| {
julia_load_path.as_ref().and_then(|load_path| {
find_project_from_load_path(load_path, current_dir, depot_path.as_deref())
.ok() // Result<Option<PathBuf>> → Option<Option<PathBuf>>
.flatten() // Option<Option<PathBuf>> → Option<PathBuf>
})
});
// If no project was found, stop here
let Some(project_file) = maybe_project_file else {
log::debug!("VersionDetect::No project specification found");
return Ok(None);
};
// Extract version from the resolved project
extract_version_from_project(project_file)

Just made it a bit more readable to myself

continue;
}

let Ok(Some(project_file)) = load_path_expand_impl(entry, current_dir, depot_path) else {
Copy link
Member

Choose a reason for hiding this comment

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

If load_path_expand_impl returns an Err, it gets skipped. Is that on purpose? Or would we want it to propagate normally?

Copy link
Member

Choose a reason for hiding this comment

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

General question:

If Manifest.toml is invalid, do we want Juliaup to hard error, or should it just log it and fall back?

Copy link
Member Author

@KristofferC KristofferC Nov 16, 2025

Choose a reason for hiding this comment

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

Imo, I think you never want it to hard error. It should print the error and fall back to default.

@IanButterworth
Copy link
Member

I made the "feature" opt-in for now since that seems to be what people want.

You could add a prompt to enable the opt-in first time in interactive mode, like the auto channel install thing.

@KristofferC KristofferC force-pushed the kc/auto_version_select branch from 8c48f93 to 8a41d3a Compare November 18, 2025 14:43
@KristofferC
Copy link
Member Author

Since this is now off by default, maybe it is safe to put this in and ask people to try things out?

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.

6 participants