From 5300246b4b1ce42da789375396ce06201bb98f5b Mon Sep 17 00:00:00 2001 From: Christian Guinard <28689358+christiangnrd@users.noreply.github.com> Date: Tue, 5 Nov 2024 15:14:52 -0400 Subject: [PATCH 1/5] Typo --- src/operations.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/operations.rs b/src/operations.rs index 4ab415ab..88c79ae9 100644 --- a/src/operations.rs +++ b/src/operations.rs @@ -1401,7 +1401,7 @@ pub fn update_version_db(paths: &GlobalPaths) -> Result<()> { None => "release".to_string(), }; - // TODO Figure out how we can learn about the correctn Juliaup channel here + // TODO Figure out how we can learn about the correct Juliaup channel here #[cfg(not(feature = "selfupdate"))] let juliaup_channel = "release".to_string(); From c9ad0c6c6ea91a2c79560ab1471c07a03e5e757b 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 2/5] 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 32d473fa..f37207cd 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 1c40c0f0..16e7f06b 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 33835acf..00e52abc 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 5d7bc76a..df2311d5 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 00000000..a8c2ecac --- /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 98c81ef8..62c42925 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 14ec58df..a8944a30 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 d3cd3eb8..6b98e144 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 07f11d7b..041f65f3 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 92675479..23d33f9e 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 88c79ae9..f78bd3d9 100644 --- a/src/operations.rs +++ b/src/operations.rs @@ -698,6 +698,7 @@ pub fn garbage_collect_versions( command: _, args: _, } => true, + JuliaupConfigChannel::AliasedChannel { channel: _ } => true, JuliaupConfigChannel::DirectDownloadChannel { path: _, url: _, @@ -897,6 +898,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 a93ad4da3cd5c294f98f6d45f4ee114d1721eac1 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 3/5] "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 62c42925..3c144ced 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 041f65f3..fb3325ae 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 d8671b7b8c8566fe763cd0fc43f24f232285788e 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 4/5] 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 00000000..f3687059 --- /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 a71c26bd7c8603e933b5ee5ef544c4b41ddf420f 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 5/5] 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 a8c2ecac..685feb48 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.")?;