diff --git a/src/bin/julialauncher.rs b/src/bin/julialauncher.rs index 4b183ae1..154ed2a3 100644 --- a/src/bin/julialauncher.rs +++ b/src/bin/julialauncher.rs @@ -325,13 +325,6 @@ enum JuliaupChannelSource { Default, } -fn resolve_channel_alias(config_data: &JuliaupConfig, channel: &str) -> Result { - match config_data.installed_channels.get(channel) { - Some(JuliaupConfigChannel::AliasChannel { target, args: _ }) => Ok(target.to_string()), - _ => Ok(channel.to_string()), - } -} - fn get_julia_path_from_channel( versions_db: &JuliaupVersionDB, config_data: &JuliaupConfig, @@ -340,8 +333,13 @@ fn get_julia_path_from_channel( juliaup_channel_source: JuliaupChannelSource, paths: &juliaup::global_paths::GlobalPaths, ) -> Result<(PathBuf, Vec)> { - // First resolve any aliases - let resolved_channel = resolve_channel_alias(config_data, channel)?; + // First check if the channel is an alias and extract its args + let (resolved_channel, alias_args) = match config_data.installed_channels.get(channel) { + Some(JuliaupConfigChannel::AliasChannel { target, args }) => { + (target.to_string(), args.clone().unwrap_or_default()) + } + _ => (channel.to_string(), Vec::new()), + }; let channel_valid = is_valid_channel(versions_db, &resolved_channel)?; @@ -353,6 +351,7 @@ fn get_julia_path_from_channel( &resolved_channel, juliaupconfig_path, channel_info, + alias_args.clone(), ); } @@ -393,6 +392,7 @@ fn get_julia_path_from_channel( &resolved_channel, juliaupconfig_path, channel_info, + alias_args, ); } else { return Err(anyhow!( @@ -445,15 +445,17 @@ fn get_julia_path_from_installed_channel( channel: &str, juliaupconfig_path: &Path, channel_info: &JuliaupConfigChannel, + alias_args: Vec, ) -> Result<(PathBuf, Vec)> { match channel_info { JuliaupConfigChannel::AliasChannel { .. } => { bail!("Unexpected alias channel after resolution: {channel}"); } - JuliaupConfigChannel::LinkedChannel { command, args } => Ok(( - PathBuf::from(command), - args.as_ref().map_or_else(Vec::new, |v| v.clone()), - )), + JuliaupConfigChannel::LinkedChannel { command, args } => { + let mut combined_args = alias_args; + combined_args.extend(args.as_ref().map_or_else(Vec::new, |v| v.clone())); + Ok((PathBuf::from(command), combined_args)) + } JuliaupConfigChannel::SystemChannel { version } => { let path = &config_data .installed_versions.get(version) @@ -475,7 +477,7 @@ fn get_julia_path_from_installed_channel( juliaupconfig_path.display() ) })?; - Ok((absolute_path.into_path_buf(), Vec::new())) + Ok((absolute_path.into_path_buf(), alias_args)) } JuliaupConfigChannel::DirectDownloadChannel { path, @@ -518,7 +520,7 @@ fn get_julia_path_from_installed_channel( juliaupconfig_path.display() ) })?; - Ok((absolute_path.into_path_buf(), Vec::new())) + Ok((absolute_path.into_path_buf(), alias_args)) } } } diff --git a/src/command_api.rs b/src/command_api.rs index 474ff943..e20440c8 100644 --- a/src/command_api.rs +++ b/src/command_api.rs @@ -45,13 +45,21 @@ pub fn run_command_api(command: &str, paths: &GlobalPaths) -> Result<()> { for (key, value) in &config_file.data.installed_channels { let curr = match &value { - JuliaupConfigChannel::AliasChannel { target, args: _ } => { + JuliaupConfigChannel::DirectDownloadChannel { path, url: _, local_etag: _, server_etag: _, version } => { JuliaupChannelInfo { name: key.clone(), - file: format!("alias-to-{target}"), + file: paths.juliauphome + .join(path) + .join("bin") + .join(format!("julia{}", std::env::consts::EXE_SUFFIX)) + .normalize() + .with_context(|| "Normalizing the path for an entry from the config file failed while running the getconfig1 API command.")? + .into_path_buf() + .to_string_lossy() + .to_string(), args: Vec::new(), - version: format!("alias to {target}"), - arch: String::new(), + version: version.clone(), + arch: "".to_string(), } } JuliaupConfigChannel::SystemChannel { version: fullversion } => { @@ -111,21 +119,13 @@ pub fn run_command_api(command: &str, paths: &GlobalPaths) -> Result<()> { Err(_) => continue, } } - JuliaupConfigChannel::DirectDownloadChannel { path, url: _, local_etag: _, server_etag: _, version } => { + JuliaupConfigChannel::AliasChannel { target, args } => { JuliaupChannelInfo { name: key.clone(), - file: paths.juliauphome - .join(path) - .join("bin") - .join(format!("julia{}", std::env::consts::EXE_SUFFIX)) - .normalize() - .with_context(|| "Normalizing the path for an entry from the config file failed while running the getconfig1 API command.")? - .into_path_buf() - .to_string_lossy() - .to_string(), - args: Vec::new(), - version: version.clone(), - arch: "".to_string(), + file: format!("alias-to-{target}"), + args: args.clone().unwrap_or_default(), + version: format!("alias to {target}"), + arch: String::new(), } } }; diff --git a/src/command_remove.rs b/src/command_remove.rs index 13f33678..3a2a8b4d 100644 --- a/src/command_remove.rs +++ b/src/command_remove.rs @@ -43,12 +43,12 @@ pub fn run_command_remove(channel: &str, paths: &GlobalPaths) -> Result<()> { // Determine what type of channel is being removed for better messaging let channel_type = match channel_info { - JuliaupConfigChannel::AliasChannel { target, args: _ } => { + JuliaupConfigChannel::DirectDownloadChannel { .. } => "channel".to_string(), + JuliaupConfigChannel::SystemChannel { .. } => "channel".to_string(), + JuliaupConfigChannel::LinkedChannel { .. } => "linked channel".to_string(), + JuliaupConfigChannel::AliasChannel { target, .. } => { format!("alias (pointing to '{target}')") } - JuliaupConfigChannel::LinkedChannel { .. } => "linked channel".to_string(), - JuliaupConfigChannel::SystemChannel { .. } => "channel".to_string(), - JuliaupConfigChannel::DirectDownloadChannel { .. } => "channel".to_string(), }; if let JuliaupConfigChannel::DirectDownloadChannel { diff --git a/src/command_status.rs b/src/command_status.rs index b3b20d9b..b60db359 100644 --- a/src/command_status.rs +++ b/src/command_status.rs @@ -14,28 +14,93 @@ use cli_table::{ use itertools::Itertools; use numeric_sort::cmp; -fn get_alias_update_info( - target: &str, +fn format_linked_command(command: &str, args: &Option>) -> String { + let mut combined_command = String::new(); + + if command.contains(' ') { + combined_command.push('\"'); + combined_command.push_str(command); + combined_command.push('\"'); + } else { + combined_command.push_str(command); + } + + if let Some(args) = args { + for arg in args { + combined_command.push(' '); + if arg.contains(' ') { + combined_command.push('\"'); + combined_command.push_str(arg); + combined_command.push('\"'); + } else { + combined_command.push_str(arg); + } + } + } + + format!("Linked to `{combined_command}`") +} + +fn format_version(channel: &JuliaupConfigChannel) -> String { + match channel { + JuliaupConfigChannel::DirectDownloadChannel { version, .. } => { + format!("Development version {version}") + } + JuliaupConfigChannel::SystemChannel { version } => version.clone(), + JuliaupConfigChannel::LinkedChannel { command, args } => { + format_linked_command(command, args) + } + JuliaupConfigChannel::AliasChannel { target, args } => match args { + Some(args) if !args.is_empty() => { + format!("Alias to `{target}` with args: {:?}", args) + } + _ => format!("Alias to `{target}`"), + }, + } +} + +fn get_update_info( + channel_name: &str, + channel: &JuliaupConfigChannel, config_file: &JuliaupReadonlyConfigFile, versiondb_data: &JuliaupVersionDB, -) -> Option { - // Check if the target channel has updates available - match config_file.data.installed_channels.get(target) { - Some(JuliaupConfigChannel::SystemChannel { version }) => { - match versiondb_data.available_channels.get(target) { - Some(channel) if channel.version != *version => { +) -> String { + match channel { + JuliaupConfigChannel::DirectDownloadChannel { + local_etag, + server_etag, + .. + } => (local_etag != server_etag).then(|| "Update available".to_string()), + JuliaupConfigChannel::SystemChannel { version } => { + match versiondb_data.available_channels.get(channel_name) { + Some(channel) if &channel.version != version => { Some(format!("Update to {} available", channel.version)) } _ => None, } } - Some(JuliaupConfigChannel::DirectDownloadChannel { - local_etag, - server_etag, - .. - }) => (local_etag != server_etag).then(|| "Update available".to_string()), - _ => None, // Target channel doesn't exist or not updatable + JuliaupConfigChannel::LinkedChannel { .. } => None, + JuliaupConfigChannel::AliasChannel { target, .. } => { + // Check if the target channel has updates available + match config_file.data.installed_channels.get(target) { + Some(JuliaupConfigChannel::DirectDownloadChannel { + local_etag, + server_etag, + .. + }) => (local_etag != server_etag).then(|| "Update available".to_string()), + Some(JuliaupConfigChannel::SystemChannel { version }) => { + match versiondb_data.available_channels.get(target) { + Some(channel) if channel.version != *version => { + Some(format!("Update to {} available", channel.version)) + } + _ => None, + } + } + _ => None, // Target channel doesn't exist or not updatable + } + } } + .unwrap_or_default() } #[derive(Table)] @@ -57,90 +122,19 @@ pub fn run_command_status(paths: &GlobalPaths) -> Result<()> { let versiondb_data = load_versions_db(paths).with_context(|| "`status` command failed to load versions db.")?; - let rows_in_table: Vec<_> = config_file + let rows_in_table: Vec = config_file .data .installed_channels .iter() - .sorted_by(|a, b| cmp(&a.0.to_string(), &b.0.to_string())) - .map(|i| -> ChannelRow { - ChannelRow { - default: match config_file.data.default { - Some(ref default_value) => { - if i.0 == default_value { - "*" - } else { - "" - } - } - None => "", - }, - name: i.0.to_string(), - version: match i.1 { - JuliaupConfigChannel::SystemChannel { version } => version.clone(), - JuliaupConfigChannel::DirectDownloadChannel { - path: _, - url: _, - local_etag: _, - server_etag: _, - version, - } => { - format!("Development version {version}") - } - JuliaupConfigChannel::LinkedChannel { command, args } => { - let mut combined_command = String::new(); - - if command.contains(' ') { - combined_command.push('\"'); - combined_command.push_str(command); - combined_command.push('\"'); - } else { - combined_command.push_str(command); - } - - if let Some(args) = args { - for i in args { - combined_command.push(' '); - if i.contains(' ') { - combined_command.push('\"'); - combined_command.push_str(i); - combined_command.push('\"'); - } else { - combined_command.push_str(i); - } - } - } - format!("Linked to `{combined_command}`") - } - JuliaupConfigChannel::AliasChannel { target, args } => match args { - Some(args) if !args.is_empty() => { - format!("Alias to `{target}` with args: {:?}", args) - } - _ => format!("Alias to `{target}`"), - }, - }, - update: { - let update_option = match i.1 { - JuliaupConfigChannel::SystemChannel { version } => { - match versiondb_data.available_channels.get(i.0) { - Some(channel) if &channel.version != version => { - Some(format!("Update to {} available", channel.version)) - } - _ => None, - } - } - JuliaupConfigChannel::LinkedChannel { .. } => None, - JuliaupConfigChannel::AliasChannel { target, args: _ } => { - get_alias_update_info(target, &config_file, &versiondb_data) - } - JuliaupConfigChannel::DirectDownloadChannel { - local_etag, - server_etag, - .. - } => (local_etag != server_etag).then(|| "Update available".to_string()), - }; - update_option.unwrap_or_default() - }, - } + .sorted_by(|(channel_name_a, _), (channel_name_b, _)| cmp(channel_name_a, channel_name_b)) + .map(|(channel_name, channel)| ChannelRow { + default: match &config_file.data.default { + Some(ref default_value) if channel_name == default_value => "*", + _ => "", + }, + name: channel_name.to_string(), + version: format_version(channel), + update: get_update_info(channel_name, channel, &config_file, &versiondb_data), }) .collect(); diff --git a/src/command_update.rs b/src/command_update.rs index a6bc1973..95b69525 100644 --- a/src/command_update.rs +++ b/src/command_update.rs @@ -13,7 +13,7 @@ use std::path::PathBuf; fn resolve_channel_alias(config_db: &JuliaupConfig, channel_name: &str) -> Result { match config_db.installed_channels.get(channel_name) { - Some(JuliaupConfigChannel::AliasChannel { target, args: _ }) => Ok(target.to_string()), + Some(JuliaupConfigChannel::AliasChannel { target, .. }) => Ok(target.to_string()), Some(_) => Ok(channel_name.to_string()), None => bail!("Channel '{}' not found", channel_name), } @@ -30,6 +30,45 @@ fn update_channel( &config_db.installed_channels.get(channel).ok_or_else(|| anyhow!("Trying to get the installed version for a channel that does not exist in the config database."))?.clone(); match current_version { + JuliaupConfigChannel::DirectDownloadChannel { + path, + url, + local_etag, + server_etag, + version, + } => { + if local_etag != server_etag { + // We only do this so that we use `version` on both Windows and Linux to prevent a compiler warning/error + if version.is_empty() { + eprintln!( + "Channel {channel} version is empty, you may need to manually codesign this channel if you trust the contents of this pull request." + ); + } + eprintln!("{} channel {channel}", style("Updating").green().bold()); + + let channel_data = + install_from_url(&url::Url::parse(url)?, &PathBuf::from(path), paths)?; + + config_db + .installed_channels + .insert(channel.clone(), channel_data); + + #[cfg(not(windows))] + if config_db.settings.create_channel_symlinks { + create_symlink( + &JuliaupConfigChannel::DirectDownloadChannel { + path: path.clone(), + url: url.clone(), + local_etag: local_etag.clone(), + server_etag: server_etag.clone(), + version: version.clone(), + }, + channel, + paths, + )?; + } + } + } JuliaupConfigChannel::SystemChannel { version } => { let should_version = version_db.available_channels.get(channel); @@ -72,10 +111,7 @@ fn update_channel( ); } } - JuliaupConfigChannel::LinkedChannel { - command: _, - args: _, - } => { + JuliaupConfigChannel::LinkedChannel { .. } => { if !ignore_non_updatable_channel { bail!( "Failed to update '{}' because it is a linked channel.", @@ -84,46 +120,7 @@ fn update_channel( } } JuliaupConfigChannel::AliasChannel { .. } => { - unreachable!("Alias channels should be resolved before calling update_channel. This is a programming error."); - } - JuliaupConfigChannel::DirectDownloadChannel { - path, - url, - local_etag, - server_etag, - version, - } => { - if local_etag != server_etag { - // We only do this so that we use `version` on both Windows and Linux to prevent a compiler warning/error - if version.is_empty() { - eprintln!( - "Channel {channel} version is empty, you may need to manually codesign this channel if you trust the contents of this pull request." - ); - } - eprintln!("{} channel {channel}", style("Updating").green().bold()); - - let channel_data = - install_from_url(&url::Url::parse(url)?, &PathBuf::from(path), paths)?; - - config_db - .installed_channels - .insert(channel.clone(), channel_data); - - #[cfg(not(windows))] - if config_db.settings.create_channel_symlinks { - create_symlink( - &JuliaupConfigChannel::DirectDownloadChannel { - path: path.clone(), - url: url.clone(), - local_etag: local_etag.clone(), - server_etag: server_etag.clone(), - version: version.clone(), - }, - channel, - paths, - )?; - } - } + unreachable!("Alias channels should be resolved before calling update_channel. Please submit a bug report."); } } diff --git a/src/config_file.rs b/src/config_file.rs index 6052bb40..48067197 100644 --- a/src/config_file.rs +++ b/src/config_file.rs @@ -54,7 +54,7 @@ pub enum JuliaupConfigChannel { AliasChannel { #[serde(rename = "Target")] target: String, - #[serde(rename = "Args", skip_serializing_if = "Option::is_none")] + #[serde(rename = "Args")] args: Option>, }, } diff --git a/src/operations.rs b/src/operations.rs index 801b63e0..9fd7f1b8 100644 --- a/src/operations.rs +++ b/src/operations.rs @@ -757,18 +757,7 @@ pub fn garbage_collect_versions( for (installed_version, detail) in &config_data.installed_versions { if config_data.installed_channels.iter().all(|j| match &j.1 { JuliaupConfigChannel::SystemChannel { version } => version != installed_version, - JuliaupConfigChannel::LinkedChannel { - command: _, - args: _, - } => true, - JuliaupConfigChannel::AliasChannel { target: _, args: _ } => true, - JuliaupConfigChannel::DirectDownloadChannel { - path: _, - url: _, - local_etag: _, - server_etag: _, - version: _, - } => true, + _ => true, }) { let path_to_delete = paths.juliauphome.join(&detail.path).canonicalize()?; let display = path_to_delete.display(); @@ -993,30 +982,23 @@ pub fn create_symlink( let updating = _remove_symlink(&symlink_path)?; match channel { - JuliaupConfigChannel::AliasChannel { target: _, args: _ } => { - // Aliases don't create symlinks directly, they are resolved at runtime - Ok(()) - } JuliaupConfigChannel::SystemChannel { version } => { create_system_channel_symlink(version, symlink_name, &symlink_path, paths, &updating) } - JuliaupConfigChannel::DirectDownloadChannel { - path, - url: _, - local_etag: _, - server_etag: _, - version, - } => create_direct_download_symlink( - path, - version, - symlink_name, - &symlink_path, - paths, - &updating, - ), + JuliaupConfigChannel::DirectDownloadChannel { path, version, .. } => { + create_direct_download_symlink( + path, + version, + symlink_name, + &symlink_path, + paths, + &updating, + ) + } JuliaupConfigChannel::LinkedChannel { command, args } => { create_linked_channel_shim(command, args, symlink_name, &symlink_path, &updating) } + JuliaupConfigChannel::AliasChannel { .. } => Ok(()), // Aliases have their symlinks resolved at runtime }?; if updating.is_none() { diff --git a/tests/command_link.rs b/tests/command_link.rs index ed6964be..30c9eeaa 100644 --- a/tests/command_link.rs +++ b/tests/command_link.rs @@ -105,6 +105,38 @@ fn command_link_alias_with_args_works() { .stderr(predicate::str::contains("args: [\"--some-arg\"]")); } +#[test] +fn alias_with_args_passes_through() { + let env = TestEnv::new(); + + // First install a Julia version + env.juliaup().arg("add").arg("1.10.10").assert().success(); + + // Create an alias with args that will be passed to Julia + env.juliaup() + .arg("link") + .arg("julia_with_threads") + .arg("+1.10.10") + .arg("--") + .arg("--threads=4") + .arg("--startup-file=no") + .assert() + .success() + .stderr(predicate::str::contains( + "args: [\"--threads=4\", \"--startup-file=no\"]", + )); + + // Test that the args are actually passed through when running Julia + // Julia with --threads=4 should report 4 threads + env.julia() + .arg("+julia_with_threads") + .arg("-e") + .arg("println(Threads.nthreads())") + .assert() + .success() + .stdout("4\n"); +} + #[test] fn command_link_duplicate_channel() { let env = TestEnv::new();