Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 62 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ is-terminal = "0.4"
path-absolutize = "3"
numeric-sort = "0.1"
regex = "1"
toml = "0.8"

[target.'cfg(windows)'.dependencies]
windows = { version = "0.62", features = ["Win32_Foundation", "Win32_UI_Shell", "Win32_Security", "Win32_System_JobObjects", "Win32_System_Console", "Win32_System_Threading", "Services_Store", "Foundation", "Foundation_Collections", "Web_Http", "Web_Http_Headers", "Storage_Streams", "Management_Deployment"] }
Expand Down
17 changes: 16 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -158,10 +158,25 @@ The Julia launcher `julia` automatically determines which specific version of Ju
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](#project-based-version-selection).
5. The default Juliaup channel.

The channel is used in the order listed above, using the first available option.

### Project-based Version Selection

When no explicit channel is specified via command line, environment variable, or directory override, Juliaup will automatically attempt to select an appropriate Julia version based on the active project's requirements.

If a project is specified (via `--project`, `JULIA_PROJECT`, or `JULIA_LOAD_PATH`), Juliaup reads the project's `Manifest.toml` and uses the `julia_version` field to determine which Julia version to use:

1. **Exact match**: If the exact version exists as a channel (e.g., `1.10.5`, `1.12.0-rc1`), it uses that version.
2. **Prerelease versions**: If the version has a prerelease suffix (e.g., `1.12.1-DEV`, `1.13.0-rc1`) and no exact channel exists, it uses the corresponding nightly channel:
- Uses `X.Y-nightly` if available (e.g., `1.12-nightly` for `1.12.1-DEV`)
- Falls back to `nightly` otherwise
3. **Future patch versions**: If the patch version is higher than any known release in that minor series (e.g., `1.10.99` when only `1.10.5` exists), it uses `X.Y-nightly`.
4. **Future minor/major versions**: If the version is higher than any known release (e.g., `1.13.0` when only `1.12.x` exists), it uses `X.Y-nightly` if available, or `nightly` otherwise.
5. **Default fallback**: If no project or manifest is found, it falls back to the default channel.

## Path used by Juliaup

Juliaup will by default use the Julia depot at `~/.julia` to store Julia versions and configuration files. This can be changed by setting
Expand Down
115 changes: 74 additions & 41 deletions src/bin/julialauncher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use juliaup::global_paths::get_paths;
use juliaup::jsonstructs_versionsdb::JuliaupVersionDB;
use juliaup::operations::{is_pr_channel, is_valid_channel};
use juliaup::utils::{print_juliaup_style, JuliaupMessageType};
use juliaup::version_selection::get_auto_channel;
use juliaup::versions_file::load_versions_db;
#[cfg(not(windows))]
use nix::{
Expand Down Expand Up @@ -318,10 +319,19 @@ fn check_channel_uptodate(
Ok(())
}

fn is_nightly_channel(channel: &str) -> bool {
use regex::Regex;
let nightly_re =
Regex::new(r"^((?:nightly|latest)|(\d+\.\d+)-(?:nightly|latest))(~|$)").unwrap();
nightly_re.is_match(channel)
}

#[derive(Debug)]
enum JuliaupChannelSource {
CmdLine,
EnvVar,
Override,
Auto,
Default,
}

Expand Down Expand Up @@ -355,53 +365,57 @@ fn get_julia_path_from_channel(
);
}

// Handle auto-installation for command line channel selection
if let JuliaupChannelSource::CmdLine = juliaup_channel_source {
if channel_valid || is_pr_channel(&resolved_channel) {
// Check the user's auto-install preference
let should_auto_install = match config_data.settings.auto_install_channels {
Some(auto_install) => auto_install, // User has explicitly set a preference
None => {
// User hasn't set a preference - prompt in interactive mode, default to false in non-interactive
if is_interactive() {
handle_auto_install_prompt(&resolved_channel, paths)?
} else {
false
}
// Handle auto-installation for command line channel selection and auto-resolved channels
if matches!(
juliaup_channel_source,
JuliaupChannelSource::CmdLine | JuliaupChannelSource::Auto
) && (channel_valid
|| is_pr_channel(&resolved_channel)
|| is_nightly_channel(&resolved_channel))
{
// Check the user's auto-install preference
let should_auto_install = match config_data.settings.auto_install_channels {
Some(auto_install) => auto_install, // User has explicitly set a preference
None => {
// User hasn't set a preference - prompt in interactive mode, default to false in non-interactive
if is_interactive() {
handle_auto_install_prompt(&resolved_channel, paths)?
} else {
false
}
};
}
};

if should_auto_install {
// Install the channel using juliaup
let is_automatic = config_data.settings.auto_install_channels == Some(true);
spawn_juliaup_add(&resolved_channel, paths, is_automatic)?;

// Reload the config to get the newly installed channel
let updated_config_file = load_config_db(paths, None)
.with_context(|| "Failed to reload configuration after installing channel.")?;

let updated_channel_info = updated_config_file
.data
.installed_channels
.get(&resolved_channel);

if let Some(channel_info) = updated_channel_info {
return get_julia_path_from_installed_channel(
versions_db,
&updated_config_file.data,
&resolved_channel,
juliaupconfig_path,
channel_info,
alias_args,
);
} else {
return Err(anyhow!(
if should_auto_install {
// Install the channel using juliaup
let is_automatic = config_data.settings.auto_install_channels == Some(true);
spawn_juliaup_add(&resolved_channel, paths, is_automatic)?;

// Reload the config to get the newly installed channel
let updated_config_file = load_config_db(paths, None)
.with_context(|| "Failed to reload configuration after installing channel.")?;

let updated_channel_info = updated_config_file
.data
.installed_channels
.get(&resolved_channel);

if let Some(channel_info) = updated_channel_info {
return get_julia_path_from_installed_channel(
versions_db,
&updated_config_file.data,
&resolved_channel,
juliaupconfig_path,
channel_info,
alias_args,
);
} else {
return Err(anyhow!(
"Channel '{resolved_channel}' was installed but could not be found in configuration."
));
}
}
// If we reach here, either installation failed or user declined
}
// If we reach here, either installation failed or user declined
}

// Original error handling for non-command-line sources or invalid channels
Expand All @@ -411,6 +425,8 @@ fn get_julia_path_from_channel(
UserError { msg: format!("`{resolved_channel}` is not installed. Please run `juliaup add {resolved_channel}` to install channel or version.") }
} else if is_pr_channel(&resolved_channel) {
UserError { msg: format!("`{resolved_channel}` is not installed. Please run `juliaup add {resolved_channel}` to install pull request channel if available.") }
} else if is_nightly_channel(&resolved_channel) {
UserError { msg: format!("`{resolved_channel}` is not installed. Please run `juliaup add {resolved_channel}` to install nightly channel.") }
} else {
UserError { msg: format!("Invalid Juliaup channel `{resolved_channel}`. Please run `juliaup list` to get a list of valid channels and versions.") }
}
Expand All @@ -433,6 +449,17 @@ fn get_julia_path_from_channel(
UserError { msg: format!("Invalid Juliaup channel `{resolved_channel}` from directory override. Please run `juliaup list` to get a list of valid channels and versions.") }
}
},
JuliaupChannelSource::Auto => {
if channel_valid {
UserError { msg: format!("`{resolved_channel}` resolved from project manifest is not installed. Please run `juliaup add {resolved_channel}` to install channel or version.") }
} else if is_pr_channel(&resolved_channel) {
UserError { msg: format!("`{resolved_channel}` resolved from project manifest is not installed. Please run `juliaup add {resolved_channel}` to install pull request channel if available.") }
} else if is_nightly_channel(&resolved_channel) {
UserError { msg: format!("`{resolved_channel}` resolved from project manifest is not installed. Please run `juliaup add {resolved_channel}` to install nightly channel.") }
} else {
UserError { msg: format!("Invalid Juliaup channel `{resolved_channel}` resolved from project manifest. Please run `juliaup list` to get a list of valid channels and versions.") }
}
},
JuliaupChannelSource::Default => UserError {msg: format!("The Juliaup configuration is in an inconsistent state, the currently configured default channel `{resolved_channel}` is not installed.") }
};

Expand Down Expand Up @@ -589,6 +616,12 @@ fn run_app() -> Result<i32> {
(channel, JuliaupChannelSource::EnvVar)
} else if let Ok(Some(channel)) = get_override_channel(&config_file) {
(channel, JuliaupChannelSource::Override)
} else if let Ok(Some(channel)) = get_auto_channel(
&args,
&versiondb_data,
config_file.data.settings.manifest_version_detect,
) {
(channel, JuliaupChannelSource::Auto)
} else if let Some(channel) = config_file.data.default.clone() {
(channel, JuliaupChannelSource::Default)
} else {
Expand Down
4 changes: 4 additions & 0 deletions src/bin/juliaup.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use juliaup::cli::{ConfigSubCmd, Juliaup, OverrideSubCmd, SelfSubCmd};
use juliaup::command_api::run_command_api;
use juliaup::command_completions::generate_completion_for_command;
use juliaup::command_config_autoinstall::run_command_config_autoinstall;
use juliaup::command_config_manifestversiondetect::run_command_config_manifestversiondetect;
#[cfg(not(windows))]
use juliaup::command_config_symlinks::run_command_config_symlinks;
use juliaup::command_config_versionsdbupdate::run_command_config_versionsdbupdate;
Expand Down Expand Up @@ -127,6 +128,9 @@ fn main() -> Result<()> {
ConfigSubCmd::AutoInstallChannels { value } => {
run_command_config_autoinstall(value, false, &paths)
}
ConfigSubCmd::ManifestVersionDetect { value } => {
run_command_config_manifestversiondetect(value, false, &paths)
}
},
Juliaup::Api { command } => run_command_api(&command, &paths),
Juliaup::InitialSetupFromLauncher {} => run_command_initial_setup_from_launcher(&paths),
Expand Down
6 changes: 6 additions & 0 deletions src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -188,4 +188,10 @@ pub enum ConfigSubCmd {
/// New value: true, false, or default
value: Option<String>,
},
/// Enable Julia version selection from manifests
#[clap(name = "manifestversiondetect")]
ManifestVersionDetect {
/// New value
value: Option<bool>,
},
}
Loading
Loading