Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Alternate short channel matching support #838

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -356,4 +356,7 @@ MigrationBackup/
# Ionide (cross platform F# VS Code tools) working folder
.ionide/

# Apple junk
*.DS_Store

!/src/bin
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ To launch a specific Julia version, say in channel `release`, run `julia +releas

The Julia launcher `julia` automatically determines which specific version of Julia to launch. There are several ways to control and override which Juliaup channel should be used:

1. A command line Julia version specifier, such as `julia +release`.
1. A command line Julia version specifier, such as `julia +release`. For non-version channels, any unambiguous installed channel prefix will launch that channel.
2. The `JULIAUP_CHANNEL` environment variable.
3. A directory override, set with the `juliaup override set` command.
3. The default Juliaup channel.
Expand Down
66 changes: 50 additions & 16 deletions src/bin/julialauncher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -168,40 +168,71 @@ enum JuliaupChannelSource {
Default,
}

fn only<T, I>(mut iter: I) -> Option<T>
where
I: Iterator<Item = T>,
{
let first = iter.next();
if iter.next().is_some() {
// There are multiple elements
return None;
}
first
}

fn get_julia_path_from_channel(
versions_db: &JuliaupVersionDB,
config_data: &JuliaupConfig,
channel: &str,
requested_channel: &str,
juliaupconfig_path: &Path,
juliaup_channel_source: JuliaupChannelSource,
) -> Result<(PathBuf, Vec<String>)> {
let channel_valid = is_valid_channel(versions_db, &channel.to_string())?;
let expanded_channel = if requested_channel
.chars()
.next()
.map(char::is_numeric)
.unwrap_or(false)
{
requested_channel
} else {
match only(
config_data
.installed_channels
.keys()
.filter(|&item| item.starts_with(requested_channel)),
) {
Some(val) => val,
None => requested_channel,
}
};

let channel_valid = is_valid_channel(versions_db, &expanded_channel.to_string())?;
let channel_info = config_data
.installed_channels
.get(channel)
.get(expanded_channel)
.ok_or_else(|| match juliaup_channel_source {
JuliaupChannelSource::CmdLine => {
if channel_valid {
UserError { msg: format!("`{}` is not installed. Please run `juliaup add {}` to install channel or version.", channel, channel) }
UserError { msg: format!("`{}` is not installed. Please run `juliaup add {}` to install channel or version.", expanded_channel, expanded_channel) }
} else {
UserError { msg: format!("Invalid Juliaup channel `{}`. Please run `juliaup list` to get a list of valid channels and versions.", channel) }
UserError { msg: format!("Invalid Juliaup channel `{}`. Please run `juliaup list` to get a list of valid channels and versions.", expanded_channel) }
}
}.into(),
JuliaupChannelSource::EnvVar=> {
if channel_valid {
UserError { msg: format!("`{}` from environment variable JULIAUP_CHANNEL is not installed. Please run `juliaup add {}` to install channel or version.", channel, channel) }
UserError { msg: format!("`{}` from environment variable JULIAUP_CHANNEL is not installed. Please run `juliaup add {}` to install channel or version.", expanded_channel, expanded_channel) }
} else {
UserError { msg: format!("Invalid Juliaup channel `{}` from environment variable JULIAUP_CHANNEL. Please run `juliaup list` to get a list of valid channels and versions.", channel) }
UserError { msg: format!("Invalid Juliaup channel `{}` from environment variable JULIAUP_CHANNEL. Please run `juliaup list` to get a list of valid channels and versions.", expanded_channel) }
}
}.into(),
JuliaupChannelSource::Override=> {
if channel_valid {
UserError { msg: format!("`{}` from directory override is not installed. Please run `juliaup add {}` to install channel or version.", channel, channel) }
UserError { msg: format!("`{}` from directory override is not installed. Please run `juliaup add {}` to install channel or version.", expanded_channel, expanded_channel) }
} else {
UserError { msg: format!("Invalid Juliaup channel `{}` from directory override. Please run `juliaup list` to get a list of valid channels and versions.", channel) }
UserError { msg: format!("Invalid Juliaup channel `{}` from directory override. Please run `juliaup list` to get a list of valid channels and versions.", expanded_channel) }
}
}.into(),
JuliaupChannelSource::Default => UserError {msg: format!("The Juliaup configuration is in an inconsistent state, the currently configured default channel `{}` is not installed.", channel) }
JuliaupChannelSource::Default => UserError {msg: format!("The Juliaup configuration is in an inconsistent state, the currently configured default channel `{}` is not installed.", expanded_channel) }
})?;

match channel_info {
Expand All @@ -214,12 +245,12 @@ fn get_julia_path_from_channel(
JuliaupConfigChannel::SystemChannel { version } => {
let path = &config_data
.installed_versions.get(version)
.ok_or_else(|| anyhow!("The juliaup configuration is in an inconsistent state, the channel {} is pointing to Julia version {}, which is not installed.", channel, version))?.path;
.ok_or_else(|| anyhow!("The juliaup configuration is in an inconsistent state, the channel {} is pointing to Julia version {}, which is not installed.", expanded_channel, version))?.path;

check_channel_uptodate(channel, version, versions_db).with_context(|| {
check_channel_uptodate(expanded_channel, version, versions_db).with_context(|| {
format!(
"The Julia launcher failed while checking whether the channel {} is up-to-date.",
channel
expanded_channel
)
})?;
let absolute_path = juliaupconfig_path
Expand All @@ -245,7 +276,7 @@ fn get_julia_path_from_channel(
version: _,
} => {
if local_etag != server_etag {
if channel.starts_with("nightly") {
if expanded_channel.starts_with("nightly") {
// Nightly is updateable several times per day so this message will show
// more often than not unless folks update a couple of times a day.
// Also, folks using nightly are typically more experienced and need
Expand All @@ -256,12 +287,15 @@ fn get_julia_path_from_channel(
} else {
eprintln!(
"A new version of Julia for the `{}` channel is available. Run:",
channel
expanded_channel
);
eprintln!();
eprintln!(" juliaup update");
eprintln!();
eprintln!("to install the latest Julia for the `{}` channel.", channel);
eprintln!(
"to install the latest Julia for the `{}` channel.",
expanded_channel
);
}
}

Expand Down
4 changes: 3 additions & 1 deletion src/global_paths.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,9 @@ fn get_default_juliaup_home_path() -> Result<PathBuf> {

pub fn get_paths() -> Result<GlobalPaths> {
let juliauphome = get_juliaup_home_path()?;

return get_paths_from_home_path(juliauphome);
}
pub fn get_paths_from_home_path(juliauphome: PathBuf) -> Result<GlobalPaths> {
#[cfg(feature = "selfupdate")]
let my_own_path = std::env::current_exe()
.with_context(|| "Could not determine the path of the running exe.")?;
Expand Down
96 changes: 96 additions & 0 deletions tests/channel_selection.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,44 @@
use assert_cmd::Command;

use anyhow::Result;
use juliaup::config_file::{load_config_db, JuliaupConfigChannel};
use juliaup::global_paths::get_paths_from_home_path;
use normpath::PathExt;
use std::path::PathBuf;

// Simpler reimplementation of get_julia_path_from_channel from julialauncher.rs to help link channels
fn get_julia_path_from_channel(
requested_channel: &str,
juliaup_depot_path: PathBuf,
) -> Result<PathBuf> {
let paths = get_paths_from_home_path(juliaup_depot_path)?;
let config_file = load_config_db(&paths)?;
let config_data = config_file.data;

let juliaupconfig_path = paths.juliaupconfig.as_path();

let channel_info = config_data
.installed_channels
.get(requested_channel)
.unwrap();

let path: &String = if let JuliaupConfigChannel::SystemChannel { version } = channel_info {
&config_data.installed_versions.get(version).unwrap().path
} else {
panic!("whoops")
};

let absolute_path = juliaupconfig_path
.parent()
.unwrap() // unwrap OK because there should always be a parent
.join(path)
.join("bin")
.join(format!("julia{}", std::env::consts::EXE_SUFFIX))
.normalize()?;

return Ok(absolute_path.into_path_buf());
}

#[test]
fn channel_selection() {
let depot_dir = assert_fs::TempDir::new().unwrap();
Expand Down Expand Up @@ -151,4 +190,61 @@ fn channel_selection() {
.assert()
.failure()
.stderr("ERROR: `nightly` is not installed. Please run `juliaup add nightly` to install channel or version.\n");

// Now testing short channel matching
// At this point, installed channels are: 1.6.7, 1.7.3, 1.8.5

// Test that incomplete number matching does not autocomplete:
// https://github.com/JuliaLang/juliaup/pull/838#issuecomment-2206640506
Command::cargo_bin("julia")
.unwrap()
.arg("+1.8")
.arg("-v")
.env("JULIA_DEPOT_PATH", depot_dir.path())
.env("JULIAUP_DEPOT_PATH", depot_dir.path())
.assert()
.failure();

// Test that completion works only when it should for words
let linked_julia_path =
get_julia_path_from_channel("1.6.7", depot_dir.path().to_path_buf().join("juliaup"))
.unwrap();
let linked_julia_version = linked_julia_path.to_str().unwrap();
Command::cargo_bin("juliaup")
.unwrap()
.arg("link")
.arg("ra")
.arg(linked_julia_version)
.env("JULIA_DEPOT_PATH", depot_dir.path())
.env("JULIAUP_DEPOT_PATH", depot_dir.path())
.assert()
.success();

Command::cargo_bin("julia")
.unwrap()
.arg("+r")
.arg("-v")
.env("JULIA_DEPOT_PATH", depot_dir.path())
.env("JULIAUP_DEPOT_PATH", depot_dir.path())
.assert()
.success();

Command::cargo_bin("juliaup")
.unwrap()
.arg("link")
.arg("rb")
.arg(linked_julia_version)
.env("JULIA_DEPOT_PATH", depot_dir.path())
.env("JULIAUP_DEPOT_PATH", depot_dir.path())
.assert()
.success();

Command::cargo_bin("julia")
.unwrap()
.arg("+r")
.arg("-v")
.env("JULIA_DEPOT_PATH", depot_dir.path())
.env("JULIAUP_DEPOT_PATH", depot_dir.path())
.assert()
.failure();
}