From 26a5afc4aeabde4a621f61d04bc5af796b2feacf Mon Sep 17 00:00:00 2001 From: Christian Guinard <28689358+christiangnrd@users.noreply.github.com> Date: Tue, 5 Nov 2024 16:11:46 -0400 Subject: [PATCH 01/18] Add `alias` command --- README.md | 1 + src/bin/julialauncher.rs | 11 ++++++++ src/bin/juliaup.rs | 2 ++ src/cli.rs | 2 ++ src/command_alias.rs | 55 ++++++++++++++++++++++++++++++++++++++++ src/command_api.rs | 34 +++++++++++++++++++++++++ src/command_status.rs | 4 +++ src/command_update.rs | 11 ++++++++ src/config_file.rs | 4 +++ src/lib.rs | 1 + src/operations.rs | 14 ++++++++++ 11 files changed, 139 insertions(+) create mode 100644 src/command_alias.rs diff --git a/README.md b/README.md index 049347fc3..1bafdb51f 100644 --- a/README.md +++ b/README.md @@ -115,6 +115,7 @@ Here are some of the things you can do with `juliaup`: - `juliaup add 1.6.1~x86` installs the 32 bit version of Julia 1.6.1 on your system. - `juliaup default 1.6~x86` configures the `julia` command to start the latest 1.6.x 32 bit version of Julia you have installed on your system. - `juliaup link dev ~/juliasrc/julia` configures the `dev` channel to use a binary that you provide that is located at `~/juliasrc/julia`. You can then use `dev` as if it was a system provided channel, i.e. make it the default or use it with the `+` version selector. You can use other names than `dev` and link as many versions into `juliaup` as you want. +- `juliaup alias r release` configures the `r` channel to act as if you had requested the `release` channel. - `juliaup self update` installs the latest version, which is necessary if new releases reach the beta channel, etc. - `juliaup self uninstall` uninstalls Juliaup. Note that on some platforms this command is not available, in those situations one should use platform specific methods to uninstall Juliaup. - `juliaup override status` shows all configured directory overrides. diff --git a/src/bin/julialauncher.rs b/src/bin/julialauncher.rs index bd544167e..d4fc952e7 100644 --- a/src/bin/julialauncher.rs +++ b/src/bin/julialauncher.rs @@ -217,6 +217,17 @@ fn get_julia_path_from_channel( args.as_ref().map_or_else(Vec::new, |v| v.clone()), )) } + JuliaupConfigChannel::AliasedChannel { + channel: newchannel, + } => { + return get_julia_path_from_channel( + versions_db, + config_data, + newchannel, + juliaupconfig_path, + juliaup_channel_source, + ) + } JuliaupConfigChannel::SystemChannel { version } => { let path = &config_data .installed_versions.get(version) diff --git a/src/bin/juliaup.rs b/src/bin/juliaup.rs index bc2ab1186..b82f9d0a3 100644 --- a/src/bin/juliaup.rs +++ b/src/bin/juliaup.rs @@ -1,6 +1,7 @@ use anyhow::{Context, Result}; use clap::Parser; use juliaup::cli::{ConfigSubCmd, Juliaup, OverrideSubCmd, SelfSubCmd}; +use juliaup::command_alias::run_command_alias; use juliaup::command_api::run_command_api; use juliaup::command_completions::run_command_completions; #[cfg(not(windows))] @@ -102,6 +103,7 @@ fn main() -> Result<()> { file, args, } => run_command_link(&channel, &file, &args, &paths), + Juliaup::Alias { alias, channel } => run_command_alias(&alias, &channel, &paths), Juliaup::List {} => run_command_list(&paths), Juliaup::Config(subcmd) => match subcmd { #[cfg(not(windows))] diff --git a/src/cli.rs b/src/cli.rs index 5d7bc76af..df2311d58 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -18,6 +18,8 @@ pub enum Juliaup { file: String, args: Vec, }, + /// Link an existing juliaup channel to a custom channel name + Alias { alias: String, channel: String }, /// List all available channels #[clap(alias = "ls")] List {}, diff --git a/src/command_alias.rs b/src/command_alias.rs new file mode 100644 index 000000000..a8c2ecac8 --- /dev/null +++ b/src/command_alias.rs @@ -0,0 +1,55 @@ +use crate::config_file::JuliaupConfigChannel; +use crate::config_file::{load_mut_config_db, save_config_db}; +use crate::global_paths::GlobalPaths; +#[cfg(not(windows))] +use crate::operations::create_symlink; +use crate::operations::is_valid_channel; +use crate::versions_file::load_versions_db; +use anyhow::{bail, Context, Result}; + +pub fn run_command_alias(alias: &str, channel: &str, paths: &GlobalPaths) -> Result<()> { + println!("alias: {}, channel: {}", alias, channel); + let mut config_file = load_mut_config_db(paths) + .with_context(|| "`alias` command failed to load configuration data.")?; + + let versiondb_data = + load_versions_db(paths).with_context(|| "`alias` command failed to load versions db.")?; + + if config_file.data.installed_channels.contains_key(alias) { + bail!("Channel name `{}` is already used.", alias) + } + + if !config_file.data.installed_channels.contains_key(channel) { + eprintln!("WARNING: The channel `{}` does not currently exist. If this was a mistake, run `juliaup remove {}` and try again.", channel, alias); + } + + if is_valid_channel(&versiondb_data, &alias.to_string())? { + eprintln!("WARNING: The channel name `{}` is also a system channel. By creating an alias to this channel you are hiding this system channel.", alias); + } + + config_file.data.installed_channels.insert( + alias.to_string(), + JuliaupConfigChannel::AliasedChannel { + channel: channel.to_string(), + }, + ); + + #[cfg(not(windows))] + let create_symlinks = config_file.data.settings.create_channel_symlinks; + + save_config_db(&mut config_file) + .with_context(|| "`alias` command failed to save configuration db.")?; + + #[cfg(not(windows))] + if create_symlinks { + create_symlink( + &JuliaupConfigChannel::AliasedChannel { + channel: channel.to_string(), + }, + &format!("julia-{}", channel), + paths, + )?; + } + + Ok(()) +} diff --git a/src/command_api.rs b/src/command_api.rs index 98c81ef85..62c429250 100644 --- a/src/command_api.rs +++ b/src/command_api.rs @@ -109,6 +109,40 @@ pub fn run_command_api(command: &str, paths: &GlobalPaths) -> Result<()> { Err(_) => continue, } } + // TODO: fix + JuliaupConfigChannel::AliasedChannel { channel } => { + let mut new_args: Vec = Vec::new(); + + new_args.push("--version".to_string()); + + let res = std::process::Command::new(&command) + .args(&new_args) + .output(); + + match res { + Ok(output) => { + let expected_version_prefix = "julia version "; + + let trimmed_string = std::str::from_utf8(&output.stdout).unwrap().trim(); + + if !trimmed_string.starts_with(expected_version_prefix) { + continue; + } + + let version = + Version::parse(&trimmed_string[expected_version_prefix.len()..])?; + + JuliaupChannelInfo { + name: key.clone(), + file: version.to_string(), + args: Vec::new(), + version: version.to_string(), + arch: "".to_string(), + } + } + Err(_) => continue, + } + } JuliaupConfigChannel::DirectDownloadChannel { path, url: _, local_etag: _, server_etag: _, version } => { JuliaupChannelInfo { name: key.clone(), diff --git a/src/command_status.rs b/src/command_status.rs index 14ec58df4..a8944a306 100644 --- a/src/command_status.rs +++ b/src/command_status.rs @@ -86,6 +86,9 @@ pub fn run_command_status(paths: &GlobalPaths) -> Result<()> { } format!("Linked to `{}`", combined_command) } + JuliaupConfigChannel::AliasedChannel { channel } => { + format!("Aliased to channel `{}`", channel) + } }, update: match i.1 { JuliaupConfigChannel::SystemChannel { version } => { @@ -104,6 +107,7 @@ pub fn run_command_status(paths: &GlobalPaths) -> Result<()> { command: _, args: _, } => "".to_string(), + JuliaupConfigChannel::AliasedChannel { channel: _ } => "".to_string(), JuliaupConfigChannel::DirectDownloadChannel { path: _, url: _, diff --git a/src/command_update.rs b/src/command_update.rs index ac0a3a48d..c0427ea76 100644 --- a/src/command_update.rs +++ b/src/command_update.rs @@ -75,6 +75,17 @@ fn update_channel( ); } } + JuliaupConfigChannel::AliasedChannel { + channel: realchannel, + } => { + return update_channel( + config_db, + realchannel, + version_db, + ignore_non_updatable_channel, + paths, + ) + } JuliaupConfigChannel::DirectDownloadChannel { path, url, diff --git a/src/config_file.rs b/src/config_file.rs index 07f11d7ba..041f65f39 100644 --- a/src/config_file.rs +++ b/src/config_file.rs @@ -51,6 +51,10 @@ pub enum JuliaupConfigChannel { #[serde(rename = "Args")] args: Option>, }, + AliasedChannel { + #[serde(rename = "Channel")] + channel: String, + }, } #[derive(Serialize, Deserialize, Clone, PartialEq)] diff --git a/src/lib.rs b/src/lib.rs index 926754797..23d33f9ed 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,6 +2,7 @@ use anyhow::Context; pub mod cli; pub mod command_add; +pub mod command_alias; pub mod command_api; pub mod command_completions; pub mod command_config_backgroundselfupdate; diff --git a/src/operations.rs b/src/operations.rs index af7cfcb6f..b9f5358b3 100644 --- a/src/operations.rs +++ b/src/operations.rs @@ -759,6 +759,7 @@ pub fn garbage_collect_versions( command: _, args: _, } => true, + JuliaupConfigChannel::AliasedChannel { channel: _ } => true, JuliaupConfigChannel::DirectDownloadChannel { path: _, url: _, @@ -969,6 +970,19 @@ pub fn create_symlink( ) })?; } + JuliaupConfigChannel::AliasedChannel { + channel: newchannel, + } => { + let config_file = load_config_db(paths, None) + .with_context(|| "Configuration file loading failed while trying to create symlink for aliased channel.")?; + if config_file.data.installed_channels.contains_key(newchannel) { + return create_symlink( + config_file.data.installed_channels.get(newchannel).unwrap(), + symlink_name, + paths, + ); + } + } }; if updating.is_none() { From ad208f4a6ecb413dfefe0cae5579faa280d9bc27 Mon Sep 17 00:00:00 2001 From: Christian Guinard <28689358+christiangnrd@users.noreply.github.com> Date: Tue, 5 Nov 2024 20:15:01 -0400 Subject: [PATCH 02/18] "Fix" `api` command --- src/command_api.rs | 228 ++++++++++++++++++++++----------------------- src/config_file.rs | 1 + 2 files changed, 114 insertions(+), 115 deletions(-) diff --git a/src/command_api.rs b/src/command_api.rs index 62c429250..3c144ced5 100644 --- a/src/command_api.rs +++ b/src/command_api.rs @@ -1,5 +1,6 @@ use crate::config_file::load_config_db; use crate::config_file::JuliaupConfigChannel; +use crate::config_file::JuliaupReadonlyConfigFile; use crate::global_paths::GlobalPaths; use crate::utils::parse_versionstring; use anyhow::{bail, Context, Result}; @@ -29,137 +30,134 @@ pub struct JuliaupApiGetinfoReturn { pub other_versions: Vec, } -pub fn run_command_api(command: &str, paths: &GlobalPaths) -> Result<()> { - if command != "getconfig1" { - bail!("Wrong API command."); - } - - let mut ret_value = JuliaupApiGetinfoReturn { - default: None, - other_versions: Vec::new(), - }; - - let config_file = load_config_db(paths, None).with_context(|| { - "Failed to load configuration file while running the getconfig1 API command." - })?; - - for (key, value) in config_file.data.installed_channels { - let curr = match value { - JuliaupConfigChannel::SystemChannel { - version: fullversion, - } => { - let (platform, mut version) = parse_versionstring(&fullversion) - .with_context(|| "Encountered invalid version string in the configuration file while running the getconfig1 API command.")?; - - version.build = semver::BuildMetadata::EMPTY; - - match config_file.data.installed_versions.get(&fullversion) { - Some(channel) => JuliaupChannelInfo { - name: key.clone(), - file: paths.juliauphome - .join(&channel.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.to_string(), - arch: platform - }, - None => bail!("The channel '{}' is configured as a system channel, but no such channel exists in the versions database.", key) - } +fn get_channel_info( + name: &String, + channel: &JuliaupConfigChannel, + config_file: &JuliaupReadonlyConfigFile, + paths: &GlobalPaths, +) -> Result> { + match channel { + JuliaupConfigChannel::SystemChannel { + version: fullversion, + } => { + let (platform, mut version) = parse_versionstring(&fullversion) + .with_context(|| "Encountered invalid version string in the configuration file while running the getconfig1 API command.")?; + + version.build = semver::BuildMetadata::EMPTY; + + match config_file.data.installed_versions.get(&fullversion.clone()) { + Some(channel) => return Ok(Some(JuliaupChannelInfo { + name: name.clone(), + file: paths.juliauphome + .join(&channel.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.to_string(), + arch: platform + })), + None => bail!("The channel '{}' is configured as a system channel, but no such channel exists in the versions database.", name) } - JuliaupConfigChannel::LinkedChannel { command, args } => { - let mut new_args: Vec = Vec::new(); + } + JuliaupConfigChannel::LinkedChannel { command, args } => { + let mut new_args: Vec = Vec::new(); - for i in args.as_ref().unwrap() { - new_args.push(i.to_string()); - } + for i in args.as_ref().unwrap() { + new_args.push(i.to_string()); + } - new_args.push("--version".to_string()); + new_args.push("--version".to_string()); - let res = std::process::Command::new(&command) - .args(&new_args) - .output(); + let res = std::process::Command::new(&command) + .args(&new_args) + .output(); - match res { - Ok(output) => { - let expected_version_prefix = "julia version "; + match res { + Ok(output) => { + let expected_version_prefix = "julia version "; - let trimmed_string = std::str::from_utf8(&output.stdout).unwrap().trim(); + let trimmed_string = std::str::from_utf8(&output.stdout).unwrap().trim(); - if !trimmed_string.starts_with(expected_version_prefix) { - continue; - } + if !trimmed_string.starts_with(expected_version_prefix) { + return Ok(None); + } - let version = - Version::parse(&trimmed_string[expected_version_prefix.len()..])?; + let version = + Version::parse(&trimmed_string[expected_version_prefix.len()..])?; - JuliaupChannelInfo { - name: key.clone(), - file: command.clone(), - args: args.unwrap_or_default(), - version: version.to_string(), - arch: "".to_string(), - } - } - Err(_) => continue, + Ok(Some(JuliaupChannelInfo { + name: name.clone(), + file: command.clone(), + args: args.clone().unwrap_or_default(), + version: version.to_string(), + arch: "".to_string(), + })) } + Err(_) => return Ok(None), } - // TODO: fix - JuliaupConfigChannel::AliasedChannel { channel } => { - let mut new_args: Vec = Vec::new(); - - new_args.push("--version".to_string()); - - let res = std::process::Command::new(&command) - .args(&new_args) - .output(); + } + // TODO: fix + JuliaupConfigChannel::AliasedChannel { channel } => { + let real_channel_info = get_channel_info(name, config_file.data.installed_channels.get(channel).unwrap(), config_file, paths)?; + + match real_channel_info { + Some(info) => { + return Ok(Some(JuliaupChannelInfo { + name: name.clone(), + file: info.file, + args: info.args, + version: info.version, + arch: info.arch, + })) + } + None => return Ok(None), + } + } + JuliaupConfigChannel::DirectDownloadChannel { path, url: _, local_etag: _, server_etag: _, version } => { + return Ok(Some(JuliaupChannelInfo { + name: name.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(), + })) + } + } +} - match res { - Ok(output) => { - let expected_version_prefix = "julia version "; +pub fn run_command_api(command: &str, paths: &GlobalPaths) -> Result<()> { + if command != "getconfig1" { + bail!("Wrong API command."); + } - let trimmed_string = std::str::from_utf8(&output.stdout).unwrap().trim(); + let mut ret_value = JuliaupApiGetinfoReturn { + default: None, + other_versions: Vec::new(), + }; - if !trimmed_string.starts_with(expected_version_prefix) { - continue; - } + let config_file = load_config_db(paths, None).with_context(|| { + "Failed to load configuration file while running the getconfig1 API command." + })?; - let version = - Version::parse(&trimmed_string[expected_version_prefix.len()..])?; + let other_conf = config_file.clone(); - JuliaupChannelInfo { - name: key.clone(), - file: version.to_string(), - args: Vec::new(), - version: version.to_string(), - arch: "".to_string(), - } - } - Err(_) => continue, - } - } - JuliaupConfigChannel::DirectDownloadChannel { path, url: _, local_etag: _, server_etag: _, version } => { - 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(), - } - } + for (key, value) in config_file.data.installed_channels { + let curr = match get_channel_info(&key, &value, &other_conf, paths)? { + Some(channel_info) => channel_info, + None => continue, }; match config_file.data.default { diff --git a/src/config_file.rs b/src/config_file.rs index 041f65f39..fb3325aec 100644 --- a/src/config_file.rs +++ b/src/config_file.rs @@ -140,6 +140,7 @@ pub struct JuliaupConfigFile { pub self_data: JuliaupSelfConfig, } +#[derive(Clone)] pub struct JuliaupReadonlyConfigFile { pub data: JuliaupConfig, #[cfg(feature = "selfupdate")] From 285385625faa0838f6a6647f17c71bbc0ffd1b4a Mon Sep 17 00:00:00 2001 From: Christian Guinard <28689358+christiangnrd@users.noreply.github.com> Date: Sun, 24 Nov 2024 18:58:54 -0400 Subject: [PATCH 03/18] Add test --- tests/command_alias.rs | 49 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 tests/command_alias.rs diff --git a/tests/command_alias.rs b/tests/command_alias.rs new file mode 100644 index 000000000..f36870591 --- /dev/null +++ b/tests/command_alias.rs @@ -0,0 +1,49 @@ +use assert_cmd::Command; + +#[test] +fn command_alias() { + let depot_dir = assert_fs::TempDir::new().unwrap(); + + Command::cargo_bin("juliaup") + .unwrap() + .arg("add") + .arg("1.10.6") + .env("JULIA_DEPOT_PATH", depot_dir.path()) + .env("JULIAUP_DEPOT_PATH", depot_dir.path()) + .assert() + .success() + .stdout(""); + + Command::cargo_bin("julia") + .unwrap() + .arg("+1.10.6") + .arg("-e") + .arg("print(VERSION)") + .env("JULIA_DEPOT_PATH", depot_dir.path()) + .env("JULIAUP_DEPOT_PATH", depot_dir.path()) + .assert() + .success() + .stdout("1.10.6"); + + Command::cargo_bin("juliaup") + .unwrap() + .arg("alias") + .arg("testalias") + .arg("1.10.6") + .env("JULIA_DEPOT_PATH", depot_dir.path()) + .env("JULIAUP_DEPOT_PATH", depot_dir.path()) + .assert() + .success() + .stdout(""); + + Command::cargo_bin("julia") + .unwrap() + .arg("+testalias") + .arg("-e") + .arg("print(VERSION)") + .env("JULIA_DEPOT_PATH", depot_dir.path()) + .env("JULIAUP_DEPOT_PATH", depot_dir.path()) + .assert() + .success() + .stdout("1.10.6"); +} From 91a4b1f75c7ecf6232419964a0b32875b99ad713 Mon Sep 17 00:00:00 2001 From: Christian Guinard <28689358+christiangnrd@users.noreply.github.com> Date: Sun, 24 Nov 2024 19:30:54 -0400 Subject: [PATCH 04/18] Remove debug text --- src/command_alias.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/command_alias.rs b/src/command_alias.rs index a8c2ecac8..685feb482 100644 --- a/src/command_alias.rs +++ b/src/command_alias.rs @@ -8,7 +8,6 @@ use crate::versions_file::load_versions_db; use anyhow::{bail, Context, Result}; pub fn run_command_alias(alias: &str, channel: &str, paths: &GlobalPaths) -> Result<()> { - println!("alias: {}, channel: {}", alias, channel); let mut config_file = load_mut_config_db(paths) .with_context(|| "`alias` command failed to load configuration data.")?; From 5306353d809b29ca5106b1bae2e226786ee10582 Mon Sep 17 00:00:00 2001 From: Christian Guinard <28689358+christiangnrd@users.noreply.github.com> Date: Wed, 28 May 2025 18:21:18 -0300 Subject: [PATCH 05/18] Test --- tests/command_add.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/command_add.rs b/tests/command_add.rs index 5b52f3b9d..b4c454ad8 100644 --- a/tests/command_add.rs +++ b/tests/command_add.rs @@ -25,6 +25,7 @@ fn command_add() { .success() .stdout(""); + #[cfg(not(target_os = "freebsd"))] Command::cargo_bin("juliaup") .unwrap() .arg("add") From d6a9934e359b9591ff221e574faa697d9e7f3d2e Mon Sep 17 00:00:00 2001 From: Ian Butterworth Date: Tue, 26 Aug 2025 16:18:06 -0400 Subject: [PATCH 06/18] add tests for identified issues --- tests/command_alias.rs | 177 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 172 insertions(+), 5 deletions(-) diff --git a/tests/command_alias.rs b/tests/command_alias.rs index f36870591..abba06f12 100644 --- a/tests/command_alias.rs +++ b/tests/command_alias.rs @@ -1,7 +1,80 @@ use assert_cmd::Command; +use predicates::str; #[test] -fn command_alias() { +fn command_alias_basic_functionality() { + let depot_dir = assert_fs::TempDir::new().unwrap(); + + // Create a basic alias pointing to an existing channel - just test the alias creation + Command::cargo_bin("juliaup") + .unwrap() + .arg("alias") + .arg("testalias") + .arg("release") // 'release' is always available in the version db + .env("JULIA_DEPOT_PATH", depot_dir.path()) + .env("JULIAUP_DEPOT_PATH", depot_dir.path()) + .assert() + .success() + .stdout(""); + + // Check that the alias appears in the list + Command::cargo_bin("juliaup") + .unwrap() + .arg("list") + .env("JULIA_DEPOT_PATH", depot_dir.path()) + .env("JULIAUP_DEPOT_PATH", depot_dir.path()) + .assert() + .success() + .stdout(str::contains("testalias")); +} + +#[test] +fn command_alias_circular_reference_simple() { + let temp_dir = tempfile::tempdir().unwrap(); + let depot_path = temp_dir.path(); + + // Create two aliases that will form a circle + Command::cargo_bin("juliaup") + .unwrap() + .args(["alias", "alias1", "release"]) + .env("JULIAUP_DEPOT_PATH", depot_path) + .env("JULIA_DEPOT_PATH", depot_path) + .assert() + .success(); + + Command::cargo_bin("juliaup") + .unwrap() + .args(["alias", "alias2", "alias1"]) + .env("JULIAUP_DEPOT_PATH", depot_path) + .env("JULIA_DEPOT_PATH", depot_path) + .assert() + .success(); + + // Now manually modify the first alias to point to alias2, creating a cycle + // This bypasses the validation in the alias command + // Read the config file directly and modify it to create circular reference + let config_path = depot_path.join("config.toml"); + let mut config_content = std::fs::read_to_string(&config_path).unwrap_or_default(); + + // Replace the alias1 target to create circular reference + config_content = config_content.replace("alias1 = \"release\"", "alias1 = \"alias2\""); + std::fs::write(&config_path, config_content).unwrap(); + + // This demonstrates that circular references aren't properly handled - + // we expect this to fail gracefully rather than hang in infinite recursion + // Since we can't easily test infinite recursion without hanging the test, + // we'll verify that at least the symlink creation worked correctly first + Command::cargo_bin("juliaup") + .unwrap() + .args(["list"]) + .env("JULIAUP_DEPOT_PATH", depot_path) + .env("JULIA_DEPOT_PATH", depot_path) + .assert() + .success(); +} + +#[test] +fn command_alias_circular_reference() { let depot_dir = assert_fs::TempDir::new().unwrap(); Command::cargo_bin("juliaup") @@ -14,21 +87,62 @@ fn command_alias() { .success() .stdout(""); + // Create alias A -> B + Command::cargo_bin("juliaup") + .unwrap() + .arg("alias") + .arg("aliasA") + .arg("aliasB") + .env("JULIA_DEPOT_PATH", depot_dir.path()) + .env("JULIAUP_DEPOT_PATH", depot_dir.path()) + .assert() + .success() + .stdout(""); + + // Create alias B -> A (circular reference) + Command::cargo_bin("juliaup") + .unwrap() + .arg("alias") + .arg("aliasB") + .arg("aliasA") + .env("JULIA_DEPOT_PATH", depot_dir.path()) + .env("JULIAUP_DEPOT_PATH", depot_dir.path()) + .assert() + .success() + .stdout(""); + + // This should fail with circular reference error, not hang Command::cargo_bin("julia") .unwrap() - .arg("+1.10.6") + .arg("+aliasA") .arg("-e") .arg("print(VERSION)") .env("JULIA_DEPOT_PATH", depot_dir.path()) .env("JULIAUP_DEPOT_PATH", depot_dir.path()) + .timeout(std::time::Duration::from_secs(5)) + .assert() + .failure(); // This should fail gracefully, not timeout +} + +#[test] +fn command_alias_chaining() { + let depot_dir = assert_fs::TempDir::new().unwrap(); + + Command::cargo_bin("juliaup") + .unwrap() + .arg("add") + .arg("1.10.6") + .env("JULIA_DEPOT_PATH", depot_dir.path()) + .env("JULIAUP_DEPOT_PATH", depot_dir.path()) .assert() .success() - .stdout("1.10.6"); + .stdout(""); + // Create chain: aliasA -> aliasB -> 1.10.6 Command::cargo_bin("juliaup") .unwrap() .arg("alias") - .arg("testalias") + .arg("aliasB") .arg("1.10.6") .env("JULIA_DEPOT_PATH", depot_dir.path()) .env("JULIAUP_DEPOT_PATH", depot_dir.path()) @@ -36,9 +150,21 @@ fn command_alias() { .success() .stdout(""); + Command::cargo_bin("juliaup") + .unwrap() + .arg("alias") + .arg("aliasA") + .arg("aliasB") + .env("JULIA_DEPOT_PATH", depot_dir.path()) + .env("JULIAUP_DEPOT_PATH", depot_dir.path()) + .assert() + .success() + .stdout(""); + + // Should work through the chain Command::cargo_bin("julia") .unwrap() - .arg("+testalias") + .arg("+aliasA") .arg("-e") .arg("print(VERSION)") .env("JULIA_DEPOT_PATH", depot_dir.path()) @@ -47,3 +173,44 @@ fn command_alias() { .success() .stdout("1.10.6"); } + +#[cfg(not(windows))] +#[test] +fn command_alias_symlink_naming() { + let depot_dir = assert_fs::TempDir::new().unwrap(); + + // Add release channel first + Command::cargo_bin("juliaup") + .unwrap() + .arg("add") + .arg("release") + .env("JULIA_DEPOT_PATH", depot_dir.path()) + .env("JULIAUP_DEPOT_PATH", depot_dir.path()) + .assert() + .success(); + + // Create alias without symlinks enabled + Command::cargo_bin("juliaup") + .unwrap() + .arg("alias") + .arg("testalias") + .arg("release") + .env("JULIA_DEPOT_PATH", depot_dir.path()) + .env("JULIAUP_DEPOT_PATH", depot_dir.path()) + .assert() + .success(); + + // Verify the alias was created by checking it appears in the list + Command::cargo_bin("juliaup") + .unwrap() + .arg("list") + .env("JULIA_DEPOT_PATH", depot_dir.path()) + .env("JULIAUP_DEPOT_PATH", depot_dir.path()) + .assert() + .success() + .stdout(str::contains("testalias")); + + // This test verifies that alias creation works. + // The actual symlink naming fix is tested by the code logic: + // In command_alias.rs line 47, we use alias_name instead of target_channel for symlinks +} From 348f187ec179c9af79be466f2934e3d8cf05f74e Mon Sep 17 00:00:00 2001 From: Ian Butterworth Date: Tue, 26 Aug 2025 17:04:42 -0400 Subject: [PATCH 07/18] bugfix --- src/command_alias.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/command_alias.rs b/src/command_alias.rs index 685feb482..d46ea422c 100644 --- a/src/command_alias.rs +++ b/src/command_alias.rs @@ -45,7 +45,7 @@ pub fn run_command_alias(alias: &str, channel: &str, paths: &GlobalPaths) -> Res &JuliaupConfigChannel::AliasedChannel { channel: channel.to_string(), }, - &format!("julia-{}", channel), + &format!("julia-{}", alias), paths, )?; } From 3ad77dd50f22f2331728fbb30b114b291bf6b14f Mon Sep 17 00:00:00 2001 From: Ian Butterworth Date: Tue, 26 Aug 2025 17:04:57 -0400 Subject: [PATCH 08/18] show aliases in `list` --- src/command_list.rs | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/command_list.rs b/src/command_list.rs index 1a39e9ea4..8016e332e 100644 --- a/src/command_list.rs +++ b/src/command_list.rs @@ -1,5 +1,6 @@ use crate::operations::{channel_to_name, get_channel_variations}; use crate::{global_paths::GlobalPaths, versions_file::load_versions_db}; +use crate::config_file::{load_config_db, JuliaupConfigChannel}; use anyhow::{Context, Result}; use cli_table::{ format::{Border, HorizontalLine, Separator}, @@ -20,6 +21,9 @@ pub fn run_command_list(paths: &GlobalPaths) -> Result<()> { let versiondb_data = load_versions_db(paths).with_context(|| "`list` command failed to load versions db.")?; + let config_data = load_config_db(paths, None) + .with_context(|| "`list` command failed to load configuration data.")?; + let non_db_channels: Vec = (get_channel_variations("nightly")?) .into_iter() .chain(get_channel_variations("x.y-nightly")?) @@ -36,6 +40,23 @@ pub fn run_command_list(paths: &GlobalPaths) -> Result<()> { }) .collect(); + // Add user-created aliases + let alias_rows: Vec = config_data + .data + .installed_channels + .iter() + .filter_map(|(alias_name, channel_config)| { + if let JuliaupConfigChannel::AliasedChannel { channel } = channel_config { + Some(ChannelRow { + name: alias_name.clone(), + version: format!("alias -> {}", channel), + }) + } else { + None + } + }) + .collect(); + let rows_in_table: Vec<_> = versiondb_data .available_channels .iter() @@ -47,6 +68,7 @@ pub fn run_command_list(paths: &GlobalPaths) -> Result<()> { }) .sorted_by(|a, b| cmp(&a.name, &b.name)) .chain(non_db_rows) + .chain(alias_rows) .collect(); print_stdout( From 03a3f15a3084614d8e5233a88e708716e867679d Mon Sep 17 00:00:00 2001 From: Ian Butterworth Date: Tue, 26 Aug 2025 17:05:14 -0400 Subject: [PATCH 09/18] add circular alias detection --- src/bin/julialauncher.rs | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/src/bin/julialauncher.rs b/src/bin/julialauncher.rs index d4fc952e7..04c5c6113 100644 --- a/src/bin/julialauncher.rs +++ b/src/bin/julialauncher.rs @@ -175,6 +175,29 @@ fn get_julia_path_from_channel( juliaupconfig_path: &Path, juliaup_channel_source: JuliaupChannelSource, ) -> Result<(PathBuf, Vec)> { + get_julia_path_from_channel_impl( + versions_db, + config_data, + channel, + juliaupconfig_path, + juliaup_channel_source, + &mut std::collections::HashSet::new(), + ) +} + +fn get_julia_path_from_channel_impl( + versions_db: &JuliaupVersionDB, + config_data: &JuliaupConfig, + channel: &str, + juliaupconfig_path: &Path, + juliaup_channel_source: JuliaupChannelSource, + visited: &mut std::collections::HashSet, +) -> Result<(PathBuf, Vec)> { + // Check for circular references + if visited.contains(channel) { + return Err(anyhow!("Circular alias detected: alias chain contains a cycle involving '{}'", channel)); + } + visited.insert(channel.to_string()); let channel_valid = is_valid_channel(versions_db, &channel.to_string())?; let channel_info = config_data .installed_channels @@ -220,12 +243,13 @@ fn get_julia_path_from_channel( JuliaupConfigChannel::AliasedChannel { channel: newchannel, } => { - return get_julia_path_from_channel( + return get_julia_path_from_channel_impl( versions_db, config_data, newchannel, juliaupconfig_path, juliaup_channel_source, + visited, ) } JuliaupConfigChannel::SystemChannel { version } => { From f49c9c4438663973bf78e17cc4ea279eccdd3dba Mon Sep 17 00:00:00 2001 From: Ian Butterworth Date: Tue, 26 Aug 2025 17:10:23 -0400 Subject: [PATCH 10/18] rustfmt --- src/bin/julialauncher.rs | 5 ++++- src/bin/juliaup.rs | 4 +++- src/cli.rs | 6 +++--- src/command_list.rs | 4 ++-- src/command_status.rs | 2 +- src/operations.rs | 4 ++-- tests/command_alias.rs | 4 ++-- tests/command_completions_test.rs | 6 +++++- 8 files changed, 22 insertions(+), 13 deletions(-) diff --git a/src/bin/julialauncher.rs b/src/bin/julialauncher.rs index 04c5c6113..12b7b7d11 100644 --- a/src/bin/julialauncher.rs +++ b/src/bin/julialauncher.rs @@ -195,7 +195,10 @@ fn get_julia_path_from_channel_impl( ) -> Result<(PathBuf, Vec)> { // Check for circular references if visited.contains(channel) { - return Err(anyhow!("Circular alias detected: alias chain contains a cycle involving '{}'", channel)); + return Err(anyhow!( + "Circular alias detected: alias chain contains a cycle involving '{}'", + channel + )); } visited.insert(channel.to_string()); let channel_valid = is_valid_channel(versions_db, &channel.to_string())?; diff --git a/src/bin/juliaup.rs b/src/bin/juliaup.rs index cd56e2a47..28e9850eb 100644 --- a/src/bin/juliaup.rs +++ b/src/bin/juliaup.rs @@ -150,6 +150,8 @@ fn main() -> Result<()> { #[cfg(not(feature = "selfupdate"))] SelfSubCmd::Uninstall {} => run_command_selfuninstall_unavailable(), }, - Juliaup::Completions { shell } => generate_completion_for_command::(shell, "juliaup"), + Juliaup::Completions { shell } => { + generate_completion_for_command::(shell, "juliaup") + } } } diff --git a/src/cli.rs b/src/cli.rs index a1c80e425..d79d22978 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -4,7 +4,7 @@ use clap::{Parser, ValueEnum}; #[derive(Clone, ValueEnum)] pub enum CompletionShell { Bash, - Elvish, + Elvish, Fish, Nushell, PowerShell, @@ -64,9 +64,9 @@ pub enum Juliaup { #[clap(subcommand, name = "self")] SelfSubCmd(SelfSubCmd), /// Generate tab-completion scripts for your shell - Completions { + Completions { #[arg(value_enum, value_name = "SHELL")] - shell: CompletionShell + shell: CompletionShell, }, // This is used for the cron jobs that we create. By using this UUID for the command // We can identify the cron jobs that were created by juliaup for uninstall purposes diff --git a/src/command_list.rs b/src/command_list.rs index 8016e332e..ab3fedf20 100644 --- a/src/command_list.rs +++ b/src/command_list.rs @@ -1,13 +1,13 @@ +use crate::config_file::{load_config_db, JuliaupConfigChannel}; use crate::operations::{channel_to_name, get_channel_variations}; use crate::{global_paths::GlobalPaths, versions_file::load_versions_db}; -use crate::config_file::{load_config_db, JuliaupConfigChannel}; use anyhow::{Context, Result}; use cli_table::{ format::{Border, HorizontalLine, Separator}, print_stdout, ColorChoice, Table, WithTitle, }; -use numeric_sort::cmp; use itertools::Itertools; +use numeric_sort::cmp; #[derive(Table)] struct ChannelRow { diff --git a/src/command_status.rs b/src/command_status.rs index 717f1595e..6ac05c391 100644 --- a/src/command_status.rs +++ b/src/command_status.rs @@ -10,8 +10,8 @@ use cli_table::{ format::{Border, Justify}, print_stdout, Table, WithTitle, }; -use numeric_sort::cmp; use itertools::Itertools; +use numeric_sort::cmp; #[derive(Table)] struct ChannelRow { diff --git a/src/operations.rs b/src/operations.rs index 320ffd5d5..f6f570f8a 100644 --- a/src/operations.rs +++ b/src/operations.rs @@ -1663,7 +1663,7 @@ fn download_direct_download_etags( let mut requests = Vec::new(); for (channel_name, installed_channel) in &config_data.installed_channels { - if let Some(chan) = channel{ + if let Some(chan) = channel { // TODO: convert to an if-let chain once stabilized https://github.com/rust-lang/rust/pull/132833 if chan != channel_name { continue; @@ -1734,7 +1734,7 @@ fn download_direct_download_etags( let mut requests = Vec::new(); for (channel_name, installed_channel) in &config_data.installed_channels { - if let Some(chan) = channel{ + if let Some(chan) = channel { // TODO: convert to an if-let chain once stabilized https://github.com/rust-lang/rust/pull/132833 if chan != channel_name { continue; diff --git a/tests/command_alias.rs b/tests/command_alias.rs index abba06f12..8a2eeb058 100644 --- a/tests/command_alias.rs +++ b/tests/command_alias.rs @@ -10,7 +10,7 @@ fn command_alias_basic_functionality() { .unwrap() .arg("alias") .arg("testalias") - .arg("release") // 'release' is always available in the version db + .arg("release") // 'release' is always available in the version db .env("JULIA_DEPOT_PATH", depot_dir.path()) .env("JULIAUP_DEPOT_PATH", depot_dir.path()) .assert() @@ -55,7 +55,7 @@ fn command_alias_circular_reference_simple() { // Read the config file directly and modify it to create circular reference let config_path = depot_path.join("config.toml"); let mut config_content = std::fs::read_to_string(&config_path).unwrap_or_default(); - + // Replace the alias1 target to create circular reference config_content = config_content.replace("alias1 = \"release\"", "alias1 = \"alias2\""); std::fs::write(&config_path, config_content).unwrap(); diff --git a/tests/command_completions_test.rs b/tests/command_completions_test.rs index a4d876519..60eb129f5 100644 --- a/tests/command_completions_test.rs +++ b/tests/command_completions_test.rs @@ -50,6 +50,10 @@ fn completions_elvish() { fn completions_nushell() { test_shell_completion( "nushell", - &["module completions", "export extern juliaup", "export use completions"], + &[ + "module completions", + "export extern juliaup", + "export use completions", + ], ); } From 25d8c0e48e678c36850857fab444082e813b8f3f Mon Sep 17 00:00:00 2001 From: Ian Butterworth Date: Tue, 26 Aug 2025 20:05:02 -0400 Subject: [PATCH 11/18] Update command_alias.rs Co-authored-by: Miles Cranmer --- src/command_alias.rs | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/src/command_alias.rs b/src/command_alias.rs index d46ea422c..109b357f5 100644 --- a/src/command_alias.rs +++ b/src/command_alias.rs @@ -33,21 +33,20 @@ pub fn run_command_alias(alias: &str, channel: &str, paths: &GlobalPaths) -> Res }, ); - #[cfg(not(windows))] - let create_symlinks = config_file.data.settings.create_channel_symlinks; - save_config_db(&mut config_file) .with_context(|| "`alias` command failed to save configuration db.")?; #[cfg(not(windows))] - if create_symlinks { - create_symlink( - &JuliaupConfigChannel::AliasedChannel { - channel: channel.to_string(), - }, - &format!("julia-{}", alias), - paths, - )?; + { + if config_file.data.settings.create_channel_symlinks { + create_symlink( + &JuliaupConfigChannel::AliasedChannel { + channel: channel.to_string(), + }, + &format!("julia-{}", alias), + paths, + )?; + } } Ok(()) From 25b29ce988e0ba35456f334b7d3037eef4b962f6 Mon Sep 17 00:00:00 2001 From: Ian Butterworth Date: Tue, 26 Aug 2025 20:20:21 -0400 Subject: [PATCH 12/18] Update operations.rs Co-authored-by: Miles Cranmer --- src/operations.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/operations.rs b/src/operations.rs index f6f570f8a..970bbf55d 100644 --- a/src/operations.rs +++ b/src/operations.rs @@ -975,9 +975,9 @@ pub fn create_symlink( } => { let config_file = load_config_db(paths, None) .with_context(|| "Configuration file loading failed while trying to create symlink for aliased channel.")?; - if config_file.data.installed_channels.contains_key(newchannel) { + if let Some(channel_config) = config_file.data.installed_channels.get(newchannel) { return create_symlink( - config_file.data.installed_channels.get(newchannel).unwrap(), + channel_config, symlink_name, paths, ); From 8530852dcc19291321563f65ae19e794ae456132 Mon Sep 17 00:00:00 2001 From: Ian Butterworth Date: Tue, 26 Aug 2025 21:07:59 -0400 Subject: [PATCH 13/18] Apply suggestions from code review Co-authored-by: Miles Cranmer --- src/bin/julialauncher.rs | 5 ++--- src/command_alias.rs | 2 +- src/command_api.rs | 26 +++++++++++++++----------- src/command_list.rs | 15 ++++++--------- 4 files changed, 24 insertions(+), 24 deletions(-) diff --git a/src/bin/julialauncher.rs b/src/bin/julialauncher.rs index 12b7b7d11..1b04859e4 100644 --- a/src/bin/julialauncher.rs +++ b/src/bin/julialauncher.rs @@ -194,14 +194,13 @@ fn get_julia_path_from_channel_impl( visited: &mut std::collections::HashSet, ) -> Result<(PathBuf, Vec)> { // Check for circular references - if visited.contains(channel) { + if !visited.insert(channel.to_string()) { return Err(anyhow!( "Circular alias detected: alias chain contains a cycle involving '{}'", channel )); } - visited.insert(channel.to_string()); - let channel_valid = is_valid_channel(versions_db, &channel.to_string())?; + let channel_valid = is_valid_channel(versions_db, channel)?; let channel_info = config_data .installed_channels .get(channel) diff --git a/src/command_alias.rs b/src/command_alias.rs index 109b357f5..e86d00a35 100644 --- a/src/command_alias.rs +++ b/src/command_alias.rs @@ -22,7 +22,7 @@ pub fn run_command_alias(alias: &str, channel: &str, paths: &GlobalPaths) -> Res eprintln!("WARNING: The channel `{}` does not currently exist. If this was a mistake, run `juliaup remove {}` and try again.", channel, alias); } - if is_valid_channel(&versiondb_data, &alias.to_string())? { + if is_valid_channel(&versiondb_data, alias)? { eprintln!("WARNING: The channel name `{}` is also a system channel. By creating an alias to this channel you are hiding this system channel.", alias); } diff --git a/src/command_api.rs b/src/command_api.rs index 3c144ced5..bc157afc8 100644 --- a/src/command_api.rs +++ b/src/command_api.rs @@ -103,17 +103,21 @@ fn get_channel_info( } // TODO: fix JuliaupConfigChannel::AliasedChannel { channel } => { - let real_channel_info = get_channel_info(name, config_file.data.installed_channels.get(channel).unwrap(), config_file, paths)?; - - match real_channel_info { - Some(info) => { - return Ok(Some(JuliaupChannelInfo { - name: name.clone(), - file: info.file, - args: info.args, - version: info.version, - arch: info.arch, - })) + match config_file.data.installed_channels.get(channel) { + Some(target_channel) => { + let real_channel_info = get_channel_info(name, target_channel, config_file, paths)?; + match real_channel_info { + Some(info) => { + return Ok(Some(JuliaupChannelInfo { + name: name.clone(), + file: info.file, + args: info.args, + version: info.version, + arch: info.arch, + })) + } + None => return Ok(None), + } } None => return Ok(None), } diff --git a/src/command_list.rs b/src/command_list.rs index ab3fedf20..9b77d2564 100644 --- a/src/command_list.rs +++ b/src/command_list.rs @@ -45,15 +45,12 @@ pub fn run_command_list(paths: &GlobalPaths) -> Result<()> { .data .installed_channels .iter() - .filter_map(|(alias_name, channel_config)| { - if let JuliaupConfigChannel::AliasedChannel { channel } = channel_config { - Some(ChannelRow { - name: alias_name.clone(), - version: format!("alias -> {}", channel), - }) - } else { - None - } + .filter_map(|(alias_name, channel_config)| match channel_config { + JuliaupConfigChannel::AliasedChannel { channel } => Some(ChannelRow { + name: alias_name.clone(), + version: format!("alias -> {}", channel), + }), + _ => None, }) .collect(); From 9338570dda827745fb122fb963fa53e77dcd9996 Mon Sep 17 00:00:00 2001 From: Ian Butterworth Date: Tue, 26 Aug 2025 21:50:42 -0400 Subject: [PATCH 14/18] reverts --- src/bin/julialauncher.rs | 2 +- src/command_alias.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/bin/julialauncher.rs b/src/bin/julialauncher.rs index 1b04859e4..1aa183ac4 100644 --- a/src/bin/julialauncher.rs +++ b/src/bin/julialauncher.rs @@ -200,7 +200,7 @@ fn get_julia_path_from_channel_impl( channel )); } - let channel_valid = is_valid_channel(versions_db, channel)?; + let channel_valid = is_valid_channel(versions_db, &channel.to_string())?; let channel_info = config_data .installed_channels .get(channel) diff --git a/src/command_alias.rs b/src/command_alias.rs index e86d00a35..109b357f5 100644 --- a/src/command_alias.rs +++ b/src/command_alias.rs @@ -22,7 +22,7 @@ pub fn run_command_alias(alias: &str, channel: &str, paths: &GlobalPaths) -> Res eprintln!("WARNING: The channel `{}` does not currently exist. If this was a mistake, run `juliaup remove {}` and try again.", channel, alias); } - if is_valid_channel(&versiondb_data, alias)? { + if is_valid_channel(&versiondb_data, &alias.to_string())? { eprintln!("WARNING: The channel name `{}` is also a system channel. By creating an alias to this channel you are hiding this system channel.", alias); } From 1f978212624a1e048d6b2d1d527d74a3ade68e80 Mon Sep 17 00:00:00 2001 From: Ian Butterworth Date: Tue, 26 Aug 2025 21:59:30 -0400 Subject: [PATCH 15/18] fix clippy issues --- build.rs | 5 ++--- src/command_api.rs | 16 ++++++++-------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/build.rs b/build.rs index a6c908389..81ef1bb2e 100644 --- a/build.rs +++ b/build.rs @@ -21,7 +21,7 @@ fn main() -> Result<()> { let db_path = Path::new(&env::var("CARGO_MANIFEST_DIR").unwrap()) .join("versiondb") - .join(format!("versiondb-{}.json", target_platform)); + .join(format!("versiondb-{target_platform}.json")); let version_db_path = out_path.join("versionsdb.json"); std::fs::copy(&db_path, &version_db_path).unwrap(); @@ -35,8 +35,7 @@ fn main() -> Result<()> { std::fs::write( &bundled_version_path, format!( - "pub const BUNDLED_JULIA_VERSION: &str = {}; pub const BUNDLED_DB_VERSION: &str = {};", - bundled_version_as_string, bundled_dbversion_as_string + "pub const BUNDLED_JULIA_VERSION: &str = {bundled_version_as_string}; pub const BUNDLED_DB_VERSION: &str = {bundled_dbversion_as_string};" ), ) .unwrap(); diff --git a/src/command_api.rs b/src/command_api.rs index bc157afc8..e99da107a 100644 --- a/src/command_api.rs +++ b/src/command_api.rs @@ -40,13 +40,13 @@ fn get_channel_info( JuliaupConfigChannel::SystemChannel { version: fullversion, } => { - let (platform, mut version) = parse_versionstring(&fullversion) + let (platform, mut version) = parse_versionstring(fullversion) .with_context(|| "Encountered invalid version string in the configuration file while running the getconfig1 API command.")?; version.build = semver::BuildMetadata::EMPTY; match config_file.data.installed_versions.get(&fullversion.clone()) { - Some(channel) => return Ok(Some(JuliaupChannelInfo { + Some(channel) => Ok(Some(JuliaupChannelInfo { name: name.clone(), file: paths.juliauphome .join(&channel.path) @@ -73,7 +73,7 @@ fn get_channel_info( new_args.push("--version".to_string()); - let res = std::process::Command::new(&command) + let res = std::process::Command::new(command) .args(&new_args) .output(); @@ -98,7 +98,7 @@ fn get_channel_info( arch: "".to_string(), })) } - Err(_) => return Ok(None), + Err(_) => Ok(None), } } // TODO: fix @@ -108,7 +108,7 @@ fn get_channel_info( let real_channel_info = get_channel_info(name, target_channel, config_file, paths)?; match real_channel_info { Some(info) => { - return Ok(Some(JuliaupChannelInfo { + Ok(Some(JuliaupChannelInfo { name: name.clone(), file: info.file, args: info.args, @@ -116,14 +116,14 @@ fn get_channel_info( arch: info.arch, })) } - None => return Ok(None), + None => Ok(None), } } - None => return Ok(None), + None => Ok(None), } } JuliaupConfigChannel::DirectDownloadChannel { path, url: _, local_etag: _, server_etag: _, version } => { - return Ok(Some(JuliaupChannelInfo { + Ok(Some(JuliaupChannelInfo { name: name.clone(), file: paths.juliauphome .join(path) From 9b975cbdd87bad3a4c6fcc26e8c055cd0346ad39 Mon Sep 17 00:00:00 2001 From: Ian Butterworth Date: Tue, 26 Aug 2025 22:01:09 -0400 Subject: [PATCH 16/18] more clippy fix --- src/bin/julialauncher.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bin/julialauncher.rs b/src/bin/julialauncher.rs index f837bac9b..1970829cb 100644 --- a/src/bin/julialauncher.rs +++ b/src/bin/julialauncher.rs @@ -241,7 +241,7 @@ fn get_julia_path_from_channel_impl( JuliaupConfigChannel::AliasedChannel { channel: newchannel, } => { - return get_julia_path_from_channel_impl( + get_julia_path_from_channel_impl( versions_db, config_data, newchannel, From 178638e565e75dddcade981e421e28b2733f32bc Mon Sep 17 00:00:00 2001 From: Ian Butterworth Date: Tue, 26 Aug 2025 22:12:28 -0400 Subject: [PATCH 17/18] Update src/command_api.rs Co-authored-by: Christian Guinard <28689358+christiangnrd@users.noreply.github.com> --- src/command_api.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/command_api.rs b/src/command_api.rs index e99da107a..d46f0bcac 100644 --- a/src/command_api.rs +++ b/src/command_api.rs @@ -101,7 +101,6 @@ fn get_channel_info( Err(_) => Ok(None), } } - // TODO: fix JuliaupConfigChannel::AliasedChannel { channel } => { match config_file.data.installed_channels.get(channel) { Some(target_channel) => { From e0bbfbff7fe799b93b38777f7aa66a0037f017fe Mon Sep 17 00:00:00 2001 From: Ian Butterworth Date: Tue, 26 Aug 2025 22:48:37 -0400 Subject: [PATCH 18/18] make it clear when an alias is removed --- src/command_remove.rs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/command_remove.rs b/src/command_remove.rs index 47407c21f..2c5c1c1a9 100644 --- a/src/command_remove.rs +++ b/src/command_remove.rs @@ -39,7 +39,7 @@ pub fn run_command_remove(channel: &str, paths: &GlobalPaths) -> Result<()> { ); } - let x = config_file.data.installed_channels.get(channel).unwrap(); + let channel_type = config_file.data.installed_channels.get(channel).unwrap().clone(); if let JuliaupConfigChannel::DirectDownloadChannel { path, @@ -47,7 +47,7 @@ pub fn run_command_remove(channel: &str, paths: &GlobalPaths) -> Result<()> { local_etag: _, server_etag: _, version: _, - } = x + } = &channel_type { let path_to_delete = paths.juliauphome.join(path); @@ -72,7 +72,15 @@ pub fn run_command_remove(channel: &str, paths: &GlobalPaths) -> Result<()> { ) })?; - eprintln!("Julia '{}' successfully removed.", channel); + // Provide specific message based on channel type + match channel_type { + JuliaupConfigChannel::AliasedChannel { channel: target } => { + eprintln!("Alias '{}' (pointing to '{}') successfully removed.", channel, target); + } + _ => { + eprintln!("Julia '{}' successfully removed.", channel); + } + } Ok(()) }