diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 68b5603e..ae854fe3 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -56,7 +56,7 @@ jobs: - target: aarch64-apple-darwin os: macos-latest suffix: arm64-darwin - run_tests: false + run_tests: true - target: x86_64-unknown-linux-musl os: ubuntu-latest suffix: x86_64-linux @@ -65,7 +65,7 @@ jobs: - target: x86_64-apple-darwin os: macos-latest suffix: x86_64-darwin - run_tests: true + run_tests: false uses: ./.github/workflows/build-and-test-target.yaml with: diff --git a/src/internal/commands/builtin/up.rs b/src/internal/commands/builtin/up.rs index 2db95329..fb597608 100644 --- a/src/internal/commands/builtin/up.rs +++ b/src/internal/commands/builtin/up.rs @@ -62,14 +62,15 @@ use crate::omni_warning; #[derive(Debug, Clone)] struct UpCommandArgs { cache_enabled: bool, - fail_on_upgrade: bool, clone_suggested: UpCommandArgsCloneSuggestedOptions, - trust: UpCommandArgsTrustOptions, - update_repository: bool, - update_user_config: UpCommandArgsUpdateUserConfigOptions, + fail_on_upgrade: bool, prompt: bool, prompt_all: bool, prompt_ids: HashSet, + trust: UpCommandArgsTrustOptions, + update_repository: bool, + update_user_config: UpCommandArgsUpdateUserConfigOptions, + upgrade: bool, } impl UpCommandArgs { @@ -141,6 +142,11 @@ impl UpCommandArgs { "yes", "ask", "no", ])), ) + .arg( + clap::Arg::new("upgrade") + .long("upgrade") + .action(clap::ArgAction::SetTrue), + ) .try_get_matches_from(&parse_argv); let matches = match matches { @@ -227,16 +233,17 @@ impl UpCommandArgs { Self { cache_enabled: !*matches.get_one::("no-cache").unwrap_or(&false), - fail_on_upgrade: *matches.get_one::("fail-on-upgrade").unwrap_or(&false), clone_suggested, - trust, + fail_on_upgrade: *matches.get_one::("fail-on-upgrade").unwrap_or(&false), prompt, prompt_all, prompt_ids, + trust, update_repository: *matches .get_one::("update-repository") .unwrap_or(&false), update_user_config, + upgrade: *matches.get_one::("upgrade").unwrap_or(&false), } } } @@ -1375,6 +1382,20 @@ impl BuiltinCommand for UpCommand { ), required: false, }, + SyntaxOptArg { + name: "--upgrade".to_string(), + desc: Some( + concat!( + "Whether we should upgrade the resources when the currently-installed ", + "version already matches version constraints. If false, this also means ", + "that if an already installed version for another repository matches ", + "version contraints, we will avoid downloading and building a more ", + "recent version \x1B[90m(default: false)\x1B[0m", + ) + .to_string(), + ), + required: false, + }, ], }) } @@ -1626,7 +1647,8 @@ impl BuiltinCommand for UpCommand { let options = options .clone() .cache(self.cli_args().cache_enabled) - .fail_on_upgrade(self.cli_args().fail_on_upgrade); + .fail_on_upgrade(self.cli_args().fail_on_upgrade) + .upgrade(self.cli_args().upgrade); if let Err(err) = up_config.up(&options) { self.handle_sync_operation( SyncUpdateOperation::OmniError(format!( @@ -1686,6 +1708,7 @@ impl BuiltinCommand for UpCommand { println!("--trust"); println!("--update-repository"); println!("--update-user-config"); + println!("--upgrade"); Ok(()) } diff --git a/src/internal/commands/utils.rs b/src/internal/commands/utils.rs index e6d7907f..e91337cb 100644 --- a/src/internal/commands/utils.rs +++ b/src/internal/commands/utils.rs @@ -57,10 +57,8 @@ where let absolute_path = if path.is_absolute() { path.to_path_buf() - } else if path.starts_with("~") { - let home_dir = std::env::var("HOME").expect("Failed to determine user's home directory"); - let path = path.strip_prefix("~").expect("Failed to strip prefix"); - PathBuf::from(home_dir).join(path) + } else if let Ok(path) = path.strip_prefix("~") { + PathBuf::from(user_home()).join(path) } else { match frompath { Some(frompath) => frompath.as_ref().join(path), diff --git a/src/internal/config/config_value.rs b/src/internal/config/config_value.rs index 744fdab6..9ecbdf56 100644 --- a/src/internal/config/config_value.rs +++ b/src/internal/config/config_value.rs @@ -452,6 +452,31 @@ impl ConfigValue { None } + pub fn as_bool_forced(&self) -> Option { + if let Some(ConfigData::Value(value)) = self.value.as_ref().map(|data| data.as_ref()) { + match value { + serde_yaml::Value::Null => return None, + serde_yaml::Value::Bool(value) => return Some(*value), + serde_yaml::Value::String(value) => match value.to_lowercase().as_str() { + "true" | "yes" | "y" | "on" | "1" => return Some(true), + "false" | "no" | "n" | "off" | "0" => return Some(false), + _ => return None, + }, + serde_yaml::Value::Number(value) => match value.as_i64() { + Some(value) => return Some(value != 0), + None => match value.as_f64() { + Some(value) => return Some(value != 0.0), + None => return None, + }, + }, + serde_yaml::Value::Sequence(_) => return None, + serde_yaml::Value::Mapping(_) => return None, + serde_yaml::Value::Tagged(_) => return None, + } + } + None + } + #[allow(dead_code)] pub fn is_float(&self) -> bool { self.as_float().is_some() @@ -599,6 +624,13 @@ impl ConfigValue { None } + pub fn get_as_bool_forced(&self, key: &str) -> Option { + if let Some(value) = self.get(key) { + return value.as_bool_forced(); + } + None + } + pub fn get_as_float(&self, key: &str) -> Option { if let Some(value) = self.get(key) { return value.as_float(); diff --git a/src/internal/config/parser/askpass.rs b/src/internal/config/parser/askpass.rs index 28099c95..87dc7142 100644 --- a/src/internal/config/parser/askpass.rs +++ b/src/internal/config/parser/askpass.rs @@ -39,13 +39,13 @@ impl AskPassConfig { Self { enabled: config_value - .get_as_bool("enabled") + .get_as_bool_forced("enabled") .unwrap_or(Self::DEFAULT_ENABLED), enable_gui: config_value - .get_as_bool("enable_gui") + .get_as_bool_forced("enable_gui") .unwrap_or(Self::DEFAULT_ENABLE_GUI), prefer_gui: config_value - .get_as_bool("prefer_gui") + .get_as_bool_forced("prefer_gui") .unwrap_or(Self::DEFAULT_PREFER_GUI), } } diff --git a/src/internal/config/parser/cd.rs b/src/internal/config/parser/cd.rs index 0214d591..3735c6b4 100644 --- a/src/internal/config/parser/cd.rs +++ b/src/internal/config/parser/cd.rs @@ -27,7 +27,7 @@ impl CdConfig { Self { fast_search: config_value - .get_as_bool("fast_search") + .get_as_bool_forced("fast_search") .unwrap_or(Self::DEFAULT_FAST_SEARCH), path_match_min_score: config_value .get_as_float("path_match_min_score") diff --git a/src/internal/config/parser/clone.rs b/src/internal/config/parser/clone.rs index 3068fdca..a053632f 100644 --- a/src/internal/config/parser/clone.rs +++ b/src/internal/config/parser/clone.rs @@ -34,7 +34,7 @@ impl CloneConfig { Self { auto_up: config_value - .get_as_bool("auto_up") + .get_as_bool_forced("auto_up") .unwrap_or(Self::DEFAULT_AUTO_UP), ls_remote_timeout, } diff --git a/src/internal/config/parser/org.rs b/src/internal/config/parser/org.rs index 79f42560..0f2664b3 100644 --- a/src/internal/config/parser/org.rs +++ b/src/internal/config/parser/org.rs @@ -47,7 +47,7 @@ impl OrgConfig { Self { handle: config_value.get_as_str("handle").unwrap().to_string(), - trusted: config_value.get_as_bool("trusted").unwrap_or(false), + trusted: config_value.get_as_bool_forced("trusted").unwrap_or(false), worktree: config_value.get_as_str("worktree"), repo_path_format: config_value.get_as_str("repo_path_format"), } diff --git a/src/internal/config/parser/path_repo_updates.rs b/src/internal/config/parser/path_repo_updates.rs index 5cad3cad..90edb513 100644 --- a/src/internal/config/parser/path_repo_updates.rs +++ b/src/internal/config/parser/path_repo_updates.rs @@ -104,16 +104,16 @@ impl PathRepoUpdatesConfig { Self { enabled: config_value - .get_as_bool("enabled") + .get_as_bool_forced("enabled") .unwrap_or(Self::DEFAULT_ENABLED), self_update, on_command_not_found, pre_auth: config_value - .get_as_bool("pre_auth") + .get_as_bool_forced("pre_auth") .unwrap_or(Self::DEFAULT_PRE_AUTH), pre_auth_timeout, background_updates: config_value - .get_as_bool("background_updates") + .get_as_bool_forced("background_updates") .unwrap_or(Self::DEFAULT_BACKGROUND_UPDATES), background_updates_timeout, interval, diff --git a/src/internal/config/parser/up_command.rs b/src/internal/config/parser/up_command.rs index 9a52adb2..9f165be3 100644 --- a/src/internal/config/parser/up_command.rs +++ b/src/internal/config/parser/up_command.rs @@ -10,6 +10,7 @@ pub struct UpCommandConfig { pub notify_workdir_config_updated: bool, pub notify_workdir_config_available: bool, pub preferred_tools: Vec, + pub upgrade: bool, } impl Default for UpCommandConfig { @@ -19,6 +20,7 @@ impl Default for UpCommandConfig { notify_workdir_config_updated: Self::DEFAULT_NOTIFY_WORKDIR_CONFIG_UPDATED, notify_workdir_config_available: Self::DEFAULT_NOTIFY_WORKDIR_CONFIG_AVAILABLE, preferred_tools: Vec::new(), + upgrade: Self::DEFAULT_UPGRADE, } } } @@ -27,6 +29,7 @@ impl UpCommandConfig { const DEFAULT_AUTO_BOOTSTRAP: bool = true; const DEFAULT_NOTIFY_WORKDIR_CONFIG_UPDATED: bool = true; const DEFAULT_NOTIFY_WORKDIR_CONFIG_AVAILABLE: bool = true; + const DEFAULT_UPGRADE: bool = false; pub(super) fn from_config_value(config_value: Option) -> Self { let config_value = match config_value { @@ -34,34 +37,40 @@ impl UpCommandConfig { None => return Self::default(), }; - let config_value = match config_value.reject_scope(&ConfigScope::Workdir) { - Some(config_value) => config_value, - None => return Self::default(), - }; + // For the values that we don't support overriding in the workdir + let config_value_global = config_value + .reject_scope(&ConfigScope::Workdir) + .unwrap_or_default(); let preferred_tools = - if let Some(preferred_tools) = config_value.get_as_array("preferred_tools") { + if let Some(preferred_tools) = config_value_global.get_as_array("preferred_tools") { preferred_tools .iter() .filter_map(|value| value.as_str().map(|value| value.to_string())) .collect() - } else if let Some(preferred_tool) = config_value.get_as_str_forced("preferred_tools") { + } else if let Some(preferred_tool) = + config_value_global.get_as_str_forced("preferred_tools") + { vec![preferred_tool] } else { Vec::new() }; Self { - auto_bootstrap: config_value - .get_as_bool("auto_bootstrap") + auto_bootstrap: config_value_global + .get_as_bool_forced("auto_bootstrap") .unwrap_or(Self::DEFAULT_AUTO_BOOTSTRAP), - notify_workdir_config_updated: config_value - .get_as_bool("notify_workdir_config_updated") + notify_workdir_config_updated: config_value_global + .get_as_bool_forced("notify_workdir_config_updated") .unwrap_or(Self::DEFAULT_NOTIFY_WORKDIR_CONFIG_UPDATED), - notify_workdir_config_available: config_value - .get_as_bool("notify_workdir_config_available") + notify_workdir_config_available: config_value_global + .get_as_bool_forced("notify_workdir_config_available") .unwrap_or(Self::DEFAULT_NOTIFY_WORKDIR_CONFIG_AVAILABLE), preferred_tools, + // The upgrade option is fine to handle as a workdir option too + upgrade: config_value + .get_as_bool_forced("upgrade") + .unwrap_or(Self::DEFAULT_UPGRADE), } } } diff --git a/src/internal/config/up/asdf_base.rs b/src/internal/config/up/asdf_base.rs index f4fe2ee2..52a2563d 100644 --- a/src/internal/config/up/asdf_base.rs +++ b/src/internal/config/up/asdf_base.rs @@ -15,9 +15,11 @@ use tokio::process::Command as TokioCommand; use walkdir::WalkDir; use crate::internal::cache::asdf_operation::AsdfOperationUpdateCachePluginVersions; +use crate::internal::cache::utils as cache_utils; use crate::internal::cache::AsdfOperationCache; use crate::internal::cache::CacheObject; use crate::internal::cache::UpEnvironmentsCache; +use crate::internal::config; use crate::internal::config::global_config; use crate::internal::config::up::homebrew::HomebrewInstall; use crate::internal::config::up::utils::data_path_dir_hash; @@ -26,6 +28,7 @@ use crate::internal::config::up::utils::ProgressHandler; use crate::internal::config::up::utils::RunConfig; use crate::internal::config::up::utils::UpProgressHandler; use crate::internal::config::up::utils::VersionMatcher; +use crate::internal::config::up::utils::VersionParser; use crate::internal::config::up::UpConfigHomebrew; use crate::internal::config::up::UpConfigNix; use crate::internal::config::up::UpConfigTool; @@ -172,6 +175,11 @@ fn is_asdf_tool_version_installed(tool: &str, version: &str) -> bool { false } +#[derive(Debug, Serialize, Deserialize, Clone, Default)] +pub struct UpConfigAsdfBaseParams { + pub tool_url: Option, +} + #[derive(Debug, Serialize, Deserialize, Clone, Default)] pub struct UpConfigAsdfBase { /// The name of the tool to install. @@ -196,6 +204,11 @@ pub struct UpConfigAsdfBase { /// The version of the tool to install, as specified in the config file. pub version: String, + /// Whether to always upgrade the tool or use the latest matching + /// already installed version. + #[serde(default, skip_serializing_if = "cache_utils::is_false")] + pub upgrade: bool, + /// A list of directories to install the tool for. #[serde(default, skip_serializing_if = "BTreeSet::is_empty")] pub dirs: BTreeSet, @@ -251,10 +264,11 @@ pub struct UpConfigAsdfBase { } impl UpConfigAsdfBase { - pub fn new(tool: &str, version: &str, dirs: BTreeSet) -> Self { + pub fn new(tool: &str, version: &str, dirs: BTreeSet, upgrade: bool) -> Self { UpConfigAsdfBase { tool: tool.to_string(), version: version.to_string(), + upgrade, dirs: dirs.clone(), ..UpConfigAsdfBase::default() } @@ -279,23 +293,16 @@ impl UpConfigAsdfBase { } pub fn from_config_value(tool: &str, config_value: Option<&ConfigValue>) -> Self { - Self::from_config_value_with_params(tool, None, config_value) - } - - pub fn from_config_value_with_url( - tool: &str, - tool_url: &str, - config_value: Option<&ConfigValue>, - ) -> Self { - Self::from_config_value_with_params(tool, Some(tool_url.to_string()), config_value) + Self::from_config_value_with_params(tool, config_value, UpConfigAsdfBaseParams::default()) } - fn from_config_value_with_params( + pub fn from_config_value_with_params( tool: &str, - tool_url: Option, config_value: Option<&ConfigValue>, + params: UpConfigAsdfBaseParams, ) -> Self { let mut version = "latest".to_string(); + let mut upgrade = false; let mut dirs = BTreeSet::new(); let mut override_tool_url = None; @@ -332,7 +339,7 @@ impl UpConfigAsdfBase { } if let Some(url) = config_value.get_as_str_forced("url") { - let set_override = match &tool_url { + let set_override = match ¶ms.tool_url { None => true, Some(tool_url) => url != *tool_url, }; @@ -340,6 +347,10 @@ impl UpConfigAsdfBase { override_tool_url = Some(url.to_string()); } } + + if let Some(value) = config_value.get_as_bool_forced("upgrade") { + upgrade = value; + } } } @@ -358,7 +369,7 @@ impl UpConfigAsdfBase { (tool, tool_real_name, tool_url) } - None => (tool.to_string(), None, tool_url), + None => (tool.to_string(), None, params.tool_url.clone()), }; UpConfigAsdfBase { @@ -367,6 +378,7 @@ impl UpConfigAsdfBase { tool_url, override_tool_url, version, + upgrade, dirs, config_value: config_value.cloned(), ..UpConfigAsdfBase::default() @@ -825,7 +837,7 @@ impl UpConfigAsdfBase { if !output.status.success() { return Err(UpError::Exec(format!( - "failed to list installed versions for {} (exit: {}): {}", + "failed to list installed versions for {} ({}): {}", self.name(), output.status, String::from_utf8_lossy(&output.stderr), @@ -849,13 +861,32 @@ impl UpConfigAsdfBase { &self, versions: &AsdfOperationUpdateCachePluginVersions, ) -> Result { - let matcher = VersionMatcher::new(&self.version); + self.resolve_version_from_str(&self.version, versions) + } + + fn latest_version( + &self, + versions: &AsdfOperationUpdateCachePluginVersions, + ) -> Result { + let version_str = self.resolve_version_from_str("latest", versions)?; + Ok(VersionParser::parse(&version_str) + .expect("failed to parse version string") + .major() + .to_string()) + } + + fn resolve_version_from_str( + &self, + match_version: &str, + versions: &AsdfOperationUpdateCachePluginVersions, + ) -> Result { + let matcher = VersionMatcher::new(match_version); let version = versions.get(&matcher).ok_or_else(|| { UpError::Exec(format!( "no {} version found matching {}", self.name(), - self.version, + match_version, )) })?; @@ -940,14 +971,55 @@ impl UpConfigAsdfBase { is_asdf_tool_version_installed(&self.tool, version) } + fn upgrade_version(&self, options: &UpOptions) -> bool { + self.upgrade || options.upgrade || config(".").up_command.upgrade + } + fn resolve_and_install_version( &self, options: &UpOptions, progress_handler: &UpProgressHandler, ) -> Result { - let versions = self.list_versions(options, progress_handler)?; - let mut version = match self.resolve_version(&versions) { - Ok(version) => version, + let mut versions = None; + + // If the options do not include upgrade, then we can try using + // an already-installed version if any matches the requirements + if !self.upgrade_version(options) { + if let Ok(installed_versions) = + self.list_installed_versions_from_plugin(progress_handler) + { + let resolve_str = match self.version.as_str() { + "latest" => { + let list_versions = self.list_versions(options, progress_handler)?; + versions = Some(list_versions.clone()); + let latest = self.latest_version(&list_versions)?; + progress_handler.progress( + format!("considering installed versions matching {}", latest) + .light_black(), + ); + latest + } + _ => self.version.clone(), + }; + + match self.resolve_version_from_str(&resolve_str, &installed_versions) { + Ok(installed_version) => { + progress_handler.progress("found matching installed version".to_string()); + return self.install_version(&installed_version, options, progress_handler); + } + Err(_err) => { + progress_handler.progress("no matching version installed".to_string()); + } + } + } + } + + let versions = match versions { + Some(versions) => versions, + None => self.list_versions(options, progress_handler)?, + }; + let version = match self.resolve_version(&versions) { + Ok(available_version) => available_version, Err(err) => { // If the versions are not fresh of now, and we failed to // resolve the version, we should try to refresh the @@ -986,8 +1058,8 @@ impl UpConfigAsdfBase { "falling back to installed version {}", installed_version.light_yellow() )); - version = installed_version; - install_version = self.install_version(&version, options, progress_handler); + install_version = + self.install_version(&installed_version, options, progress_handler); } Err(_err) => {} } @@ -1003,11 +1075,15 @@ impl UpConfigAsdfBase { progress_handler: &dyn ProgressHandler, ) -> Result { let installed = if self.is_version_installed(version) { - progress_handler.progress(format!("using {} {}", self.name(), version)); + progress_handler.progress(format!("using {} {}", self.name(), version.light_yellow())); false } else { - progress_handler.progress(format!("installing {} {}", self.name(), version)); + progress_handler.progress(format!( + "installing {} {}", + self.name(), + version.light_yellow() + )); let mut asdf_install = asdf_async_command(); asdf_install.arg("install"); diff --git a/src/internal/config/up/github_release.rs b/src/internal/config/up/github_release.rs index 7c35f5ea..ba2d76bd 100644 --- a/src/internal/config/up/github_release.rs +++ b/src/internal/config/up/github_release.rs @@ -30,6 +30,7 @@ use crate::internal::cache::GithubReleaseOperationCache; use crate::internal::cache::GithubReleaseVersion; use crate::internal::cache::GithubReleases; use crate::internal::cache::UpEnvironmentsCache; +use crate::internal::config; use crate::internal::config::global_config; use crate::internal::config::parser::GithubAuthConfig; use crate::internal::config::up::utils::cleanup_path; @@ -427,6 +428,11 @@ struct UpConfigGithubRelease { #[serde(default, skip_serializing_if = "Option::is_none")] pub version: Option, + /// Whether to always upgrade the tool or use the latest matching + /// already installed version. + #[serde(default, skip_serializing_if = "cache_utils::is_false")] + pub upgrade: bool, + /// Whether to install the pre-release version of the tool /// if it is the most recent matching version #[serde(default, skip_serializing_if = "cache_utils::is_false")] @@ -511,6 +517,7 @@ impl Default for UpConfigGithubRelease { UpConfigGithubRelease { repository: "".to_string(), version: None, + upgrade: false, prerelease: false, build: false, binary: true, @@ -595,6 +602,11 @@ impl UpConfigGithubRelease { .get("version") .map(|v| v.as_str_forced()) .unwrap_or(None); + let upgrade = table + .get("upgrade") + .map(|v| v.as_bool_forced()) + .unwrap_or(None) + .unwrap_or(false); let prerelease = table .get("prerelease") .map(|v| v.as_bool()) @@ -634,6 +646,7 @@ impl UpConfigGithubRelease { UpConfigGithubRelease { repository, version, + upgrade, prerelease, build, binary, @@ -761,60 +774,106 @@ impl UpConfigGithubRelease { Ok(()) } + fn upgrade_release(&self, options: &UpOptions) -> bool { + self.upgrade || options.upgrade || config(".").up_command.upgrade + } + fn resolve_and_download_release( &self, options: &UpOptions, progress_handler: &UpProgressHandler, ) -> Result { - let releases = self.list_releases(options, progress_handler)?; - let release = match self.resolve_release(&releases) { - Ok(release) => release, - Err(err) => { - // If the release is not fresh of now, and we failed to - // resolve the release, we should try to refresh the - // release list and try again - if options.read_cache && !releases.is_fresh() { - progress_handler.progress("no matching release found in cache".to_string()); - - let releases = self.list_releases( - &UpOptions { - read_cache: false, - ..options.clone() - }, - progress_handler, - )?; - - self.resolve_release(&releases).inspect_err(|err| { - progress_handler.error_with_message(err.message()); - })? - } else { - progress_handler.error_with_message(err.message()); - return Err(err); + let mut version = "".to_string(); + let mut download_release = Err(UpError::Exec("did not even try".to_string())); + let mut releases = None; + + // If the options do not include upgrade, then we can try using + // an already-installed version if any matches the requirements + if !self.upgrade_release(options) { + let resolve_str = match self.version.as_ref() { + Some(version) if version != "latest" => version.to_string(), + _ => { + let list_releases = self.list_releases(options, progress_handler)?; + releases = Some(list_releases.clone()); + let latest = self.latest_release_version(&list_releases)?; + progress_handler.progress( + format!("considering installed versions matching {}", latest).light_black(), + ); + latest } - } - }; - - let mut version = release.version(); + }; - // Try installing the release found - let mut download_release = self.download_release(options, &release, progress_handler); - if download_release.is_err() && !options.fail_on_upgrade { - // If we get here and there is an issue downloading the release, - // list all installed versions and check if one of those could - // fit the requirement, in which case we can fallback to it let installed_versions = self.list_installed_versions(progress_handler)?; - match self.resolve_version(&installed_versions) { + match self.resolve_version_from_str(&resolve_str, &installed_versions) { Ok(installed_version) => { progress_handler.progress(format!( - "falling back to {} {}", - self.repository, + "found matching installed version {}", installed_version.light_yellow(), )); version = installed_version; download_release = Ok(false); } - Err(_err) => {} + Err(_err) => { + progress_handler.progress("no matching version installed".to_string()); + } + } + } + + if version.is_empty() { + let releases = match releases { + Some(releases) => releases, + None => self.list_releases(options, progress_handler)?, + }; + let release = match self.resolve_release(&releases) { + Ok(release) => release, + Err(err) => { + // If the release is not fresh of now, and we failed to + // resolve the release, we should try to refresh the + // release list and try again + if options.read_cache && !releases.is_fresh() { + progress_handler.progress("no matching release found in cache".to_string()); + + let releases = self.list_releases( + &UpOptions { + read_cache: false, + ..options.clone() + }, + progress_handler, + )?; + + self.resolve_release(&releases).inspect_err(|err| { + progress_handler.error_with_message(err.message()); + })? + } else { + progress_handler.error_with_message(err.message()); + return Err(err); + } + } + }; + + version = release.version(); + + // Try installing the release found + download_release = self.download_release(options, &release, progress_handler); + if download_release.is_err() && !options.fail_on_upgrade { + // If we get here and there is an issue downloading the release, + // list all installed versions and check if one of those could + // fit the requirement, in which case we can fallback to it + let installed_versions = self.list_installed_versions(progress_handler)?; + match self.resolve_version(&installed_versions) { + Ok(installed_version) => { + progress_handler.progress(format!( + "falling back to {} {}", + self.repository, + installed_version.light_yellow(), + )); + + version = installed_version; + download_release = Ok(false); + } + Err(_err) => {} + } } } @@ -1060,11 +1119,27 @@ impl UpConfigGithubRelease { } fn resolve_release(&self, releases: &GithubReleases) -> Result { - let version = self.version.clone().unwrap_or_else(|| "latest".to_string()); + let match_version = self.version.clone().unwrap_or_else(|| "latest".to_string()); + self.resolve_release_from_str(&match_version, releases) + } + fn latest_release_version(&self, releases: &GithubReleases) -> Result { + let latest = self.resolve_release_from_str("latest", releases)?; + let version_str = latest.version(); + Ok(VersionParser::parse(&version_str) + .expect("failed to parse version string") + .major() + .to_string()) + } + + fn resolve_release_from_str( + &self, + match_version: &str, + releases: &GithubReleases, + ) -> Result { let (_version, release) = releases .get( - GithubReleasesSelector::new(&version) + GithubReleasesSelector::new(match_version) .prerelease(self.prerelease) .build(self.build) .binary(self.binary) @@ -1078,7 +1153,7 @@ impl UpConfigGithubRelease { .ok_or_else(|| { let errmsg = format!( "no matching release found for {} {}", - self.repository, version, + self.repository, match_version, ); UpError::Exec(errmsg) })?; @@ -1117,7 +1192,15 @@ impl UpConfigGithubRelease { fn resolve_version(&self, versions: &[String]) -> Result { let match_version = self.version.clone().unwrap_or_else(|| "latest".to_string()); - let mut matcher = VersionMatcher::new(&match_version); + self.resolve_version_from_str(&match_version, versions) + } + + fn resolve_version_from_str( + &self, + match_version: &str, + versions: &[String], + ) -> Result { + let mut matcher = VersionMatcher::new(match_version); matcher.prerelease(self.prerelease); matcher.build(self.build); matcher.prefix(true); diff --git a/src/internal/config/up/golang.rs b/src/internal/config/up/golang.rs index 2ff9ffc1..01aab4bb 100644 --- a/src/internal/config/up/golang.rs +++ b/src/internal/config/up/golang.rs @@ -10,6 +10,7 @@ use once_cell::sync::OnceCell; use serde::Deserialize; use serde::Serialize; +use crate::internal::cache::utils as cache_utils; use crate::internal::cache::utils::CacheObject; use crate::internal::cache::UpEnvironmentsCache; use crate::internal::commands::utils::abs_path; @@ -30,6 +31,8 @@ struct UpConfigGolangSerialized { version: Option, #[serde(skip_serializing_if = "Option::is_none")] version_file: Option, + #[serde(default, skip_serializing_if = "cache_utils::is_false")] + upgrade: bool, #[serde(skip_serializing_if = "BTreeSet::is_empty")] dirs: BTreeSet, } @@ -38,6 +41,7 @@ struct UpConfigGolangSerialized { pub struct UpConfigGolang { pub version: Option, pub version_file: Option, + pub upgrade: bool, pub dirs: BTreeSet, #[serde(skip)] pub asdf_base: OnceCell, @@ -51,6 +55,7 @@ impl Serialize for UpConfigGolang { let mut serialized = UpConfigGolangSerialized { version: self.version.clone(), version_file: self.version_file.clone(), + upgrade: self.upgrade, dirs: self.dirs.clone(), }; @@ -67,6 +72,7 @@ impl UpConfigGolang { let mut version = None; let mut version_file = None; let mut dirs = BTreeSet::new(); + let mut upgrade = false; if let Some(config_value) = config_value { if let Some(value) = config_value.as_str() { @@ -101,6 +107,10 @@ impl UpConfigGolang { } } } + + if let Some(value) = config_value.get_as_bool_forced("upgrade") { + upgrade = value; + } } } @@ -108,6 +118,7 @@ impl UpConfigGolang { asdf_base: OnceCell::new(), version, version_file, + upgrade, dirs, } } @@ -145,7 +156,7 @@ impl UpConfigGolang { }; let mut asdf_base = - UpConfigAsdfBase::new("golang", version.as_ref(), self.dirs.clone()); + UpConfigAsdfBase::new("golang", version.as_ref(), self.dirs.clone(), self.upgrade); asdf_base.add_detect_version_func(detect_version_from_gomod); asdf_base.add_post_install_func(setup_individual_gopath); diff --git a/src/internal/config/up/homebrew.rs b/src/internal/config/up/homebrew.rs index 25c71a1c..d34ca5c1 100644 --- a/src/internal/config/up/homebrew.rs +++ b/src/internal/config/up/homebrew.rs @@ -10,10 +10,12 @@ use serde::Deserialize; use serde::Serialize; use tokio::process::Command as TokioCommand; +use crate::internal::cache::utils as cache_utils; use crate::internal::cache::CacheObject; use crate::internal::cache::HomebrewInstalled; use crate::internal::cache::HomebrewOperationCache; use crate::internal::cache::UpEnvironmentsCache; +use crate::internal::config; use crate::internal::config::up::utils::get_command_output; use crate::internal::config::up::utils::run_progress; use crate::internal::config::up::utils::ProgressHandler; @@ -443,6 +445,8 @@ pub struct HomebrewTap { name: String, #[serde(skip_serializing_if = "Option::is_none")] url: Option, + #[serde(skip_serializing_if = "cache_utils::is_false")] + upgrade: bool, #[serde(skip)] was_handled: OnceCell, @@ -522,6 +526,7 @@ impl HomebrewTap { return Some(Self { name: tap_str.to_string(), url: None, + upgrade: false, was_handled: OnceCell::new(), }); } else if let Some(tap_hash) = config_value.as_table() { @@ -543,6 +548,7 @@ impl HomebrewTap { fn parse_config(name: String, config_value: &ConfigValue) -> Self { let mut url = None; + let mut upgrade = false; if let Some(tap_str) = config_value.as_str() { url = Some(tap_str.to_string()); @@ -550,11 +556,18 @@ impl HomebrewTap { if let Some(url_value) = config_value.get("url") { url = Some(url_value.as_str().unwrap().to_string()); } + + if let Some(upgrade_value) = config_value.get("upgrade") { + if let Some(upgrade_bool) = upgrade_value.as_bool_forced() { + upgrade = upgrade_bool; + } + } } Self { name, url, + upgrade, was_handled: OnceCell::new(), } } @@ -563,6 +576,7 @@ impl HomebrewTap { Self { name: name.to_string(), url: None, + upgrade: false, was_handled: OnceCell::new(), } } @@ -660,12 +674,20 @@ impl HomebrewTap { false } + fn upgrade_tap(&self, options: &UpOptions) -> bool { + self.upgrade || options.upgrade || config(".").up_command.upgrade + } + fn update_tap( &self, options: &UpOptions, progress_handler: &UpProgressHandler, ) -> Result { - if options.read_cache && !HomebrewOperationCache::get().should_update_tap(&self.name) { + if !self.upgrade_tap(options) { + progress_handler.progress("already tapped".light_black()); + return Ok(false); + } else if options.read_cache && !HomebrewOperationCache::get().should_update_tap(&self.name) + { progress_handler.progress("already up to date".light_black()); return Ok(false); } @@ -822,6 +844,7 @@ pub struct HomebrewInstall { install_type: HomebrewInstallType, name: String, version: Option, + upgrade: bool, #[serde(skip)] was_handled: OnceCell, @@ -832,18 +855,33 @@ impl Serialize for HomebrewInstall { where S: ::serde::ser::Serializer, { - let mut install = HashMap::new(); - install.insert( - self.name.clone(), - self.version.clone().unwrap_or("latest".to_string()), - ); + match ( + self.install_type.clone(), + self.upgrade, + self.version.clone(), + ) { + (HomebrewInstallType::Formula, false, None) => self.name.serialize(serializer), + (HomebrewInstallType::Formula, false, Some(version)) => { + let mut install = HashMap::new(); + install.insert(self.name.clone(), version); + install.serialize(serializer) + } + (install_type, upgrade, version) => { + let mut install = HashMap::new(); + + let install_type = match install_type { + HomebrewInstallType::Formula => "formula", + HomebrewInstallType::Cask => "cask", + }; + install.insert(install_type.to_string(), self.name.clone()); - if self.install_type == HomebrewInstallType::Cask { - let mut cask = HashMap::new(); - cask.insert("cask".to_string(), install); - cask.serialize(serializer) - } else { - install.serialize(serializer) + if let Some(version) = &version { + install.insert("version".to_string(), version.clone()); + } + install.insert("upgrade".to_string(), upgrade.to_string()); + + install.serialize(serializer) + } } } } @@ -854,6 +892,7 @@ impl HomebrewInstall { install_type: HomebrewInstallType::Formula, name: name.to_string(), version: None, + upgrade: false, was_handled: OnceCell::new(), } } @@ -884,6 +923,7 @@ impl HomebrewInstall { let mut install_type = HomebrewInstallType::Formula; let mut version = None; let mut name = None; + let mut upgrade = false; if let Some(formula_config) = formula_config_value.as_table() { let mut rest_of_config = formula_config_value.clone(); @@ -902,6 +942,12 @@ impl HomebrewInstall { let parse_version = if rest_of_config.is_str() { Some(rest_of_config) } else { + if let Some(upgrade_value) = rest_of_config.get("upgrade") { + if let Some(upgrade_bool) = upgrade_value.as_bool_forced() { + upgrade = upgrade_bool; + } + } + rest_of_config.get("version") }; @@ -923,17 +969,13 @@ impl HomebrewInstall { install_type, name, version, + upgrade, was_handled: OnceCell::new(), }); } } } else if let Some(formula) = formulae.as_str() { - installs.push(Self { - install_type: HomebrewInstallType::Formula, - name: formula.to_string(), - version: None, - was_handled: OnceCell::new(), - }); + installs.push(Self::new_formula(&formula)); } installs @@ -950,6 +992,7 @@ impl HomebrewInstall { install_type, name: cached.name.clone(), version: cached.version.clone(), + upgrade: false, was_handled: OnceCell::new(), } } @@ -1397,6 +1440,10 @@ impl HomebrewInstall { None } + fn upgrade_install(&self, options: &UpOptions) -> bool { + self.upgrade || options.upgrade || config(".").up_command.upgrade + } + fn install( &self, options: &UpOptions, @@ -1405,6 +1452,12 @@ impl HomebrewInstall { ) -> Result { if !installed { self.extract_package(options, progress_handler)?; + } else if !self.upgrade_install(options) { + if let Some(progress_handler) = progress_handler { + progress_handler.progress("already installed".light_black()) + } + + return Ok(false); } else if options.read_cache && !HomebrewOperationCache::get().should_update_install( &self.name, diff --git a/src/internal/config/up/mod.rs b/src/internal/config/up/mod.rs index 184ee4a1..00e61a46 100644 --- a/src/internal/config/up/mod.rs +++ b/src/internal/config/up/mod.rs @@ -35,6 +35,7 @@ pub(crate) mod asdf_base; pub(crate) use asdf_base::asdf_tool_path; pub(crate) use asdf_base::AsdfToolUpVersion; pub(crate) use asdf_base::UpConfigAsdfBase; +pub(crate) use asdf_base::UpConfigAsdfBaseParams; pub(crate) mod error; pub(crate) use error::UpError; diff --git a/src/internal/config/up/nodejs.rs b/src/internal/config/up/nodejs.rs index 042d70f8..87296a62 100644 --- a/src/internal/config/up/nodejs.rs +++ b/src/internal/config/up/nodejs.rs @@ -56,11 +56,11 @@ impl UpConfigNodejsParams { let mut params = Self::default(); if let Some(config_value) = &config_value { - if let Some(value) = config_value.get_as_bool("install_engines") { + if let Some(value) = config_value.get_as_bool_forced("install_engines") { params.install_engines = value; } - if let Some(value) = config_value.get_as_bool("install_packages") { + if let Some(value) = config_value.get_as_bool_forced("install_packages") { params.install_packages = value; } } diff --git a/src/internal/config/up/options.rs b/src/internal/config/up/options.rs index bdda874a..0a5e1a90 100644 --- a/src/internal/config/up/options.rs +++ b/src/internal/config/up/options.rs @@ -6,6 +6,7 @@ pub struct UpOptions<'a> { pub read_cache: bool, pub write_cache: bool, pub fail_on_upgrade: bool, + pub upgrade: bool, #[serde(skip)] pub lock_file: Option<&'a std::fs::File>, } @@ -16,6 +17,7 @@ impl Default for UpOptions<'_> { read_cache: true, write_cache: true, fail_on_upgrade: false, + upgrade: false, lock_file: None, } } @@ -42,6 +44,11 @@ impl<'a> UpOptions<'a> { self } + pub fn upgrade(mut self, upgrade: bool) -> Self { + self.upgrade = upgrade; + self + } + pub fn lock_file(mut self, lock_file: &'a std::fs::File) -> Self { self.lock_file = Some(lock_file); self diff --git a/src/internal/config/up/python.rs b/src/internal/config/up/python.rs index b37c2ea8..aeb4f7e4 100644 --- a/src/internal/config/up/python.rs +++ b/src/internal/config/up/python.rs @@ -256,7 +256,11 @@ fn setup_python_venv_per_dir( RunConfig::default(), )?; - progress_handler.progress(format!("venv created for python {} in {}", version, dir,)); + progress_handler.progress(format!( + "venv created for python {} in {}", + version, + if dir.is_empty() { "." } else { &dir } + )); } // Update the cache diff --git a/src/internal/config/up/tool.rs b/src/internal/config/up/tool.rs index 9443b7f5..6bf362a7 100644 --- a/src/internal/config/up/tool.rs +++ b/src/internal/config/up/tool.rs @@ -10,6 +10,7 @@ use crate::internal::config::global_config; use crate::internal::config::up::utils::UpProgressHandler; use crate::internal::config::up::UpConfig; use crate::internal::config::up::UpConfigAsdfBase; +use crate::internal::config::up::UpConfigAsdfBaseParams; use crate::internal::config::up::UpConfigBundler; use crate::internal::config::up::UpConfigCustom; use crate::internal::config::up::UpConfigGithubReleases; @@ -144,10 +145,12 @@ impl UpConfigTool { } } "bash" => Some(UpConfigTool::Bash( - UpConfigAsdfBase::from_config_value_with_url( + UpConfigAsdfBase::from_config_value_with_params( "bash", - "https://github.com/XaF/asdf-bash", config_value, + UpConfigAsdfBaseParams { + tool_url: Some("https://github.com/xaf/asdf-bash".into()), + }, ), )), "bundler" | "bundle" => Some(UpConfigTool::Bundler( diff --git a/src/internal/config/up/utils/version.rs b/src/internal/config/up/utils/version.rs index c1d7fae2..37ce8a23 100644 --- a/src/internal/config/up/utils/version.rs +++ b/src/internal/config/up/utils/version.rs @@ -138,6 +138,10 @@ impl VersionParser { version.build = vec![]; version.satisfies(requirements) } + + pub fn major(&self) -> u64 { + self.version.major + } } #[derive(Debug, Serialize, Deserialize, Clone, Default)] diff --git a/tests/test_omni_help.bats b/tests/test_omni_help.bats index be54e140..0bd1b079 100644 --- a/tests/test_omni_help.bats +++ b/tests/test_omni_help.bats @@ -264,7 +264,7 @@ omni - omnipotent tool (v$version) Sets up or tear down a repository depending on its up configuration -Usage: omni down [--no-cache] [--fail-on-upgrade] [--bootstrap] [--clone-suggested] [--prompt] [--prompt-all] [--trust] [--update-repository] [--update-user-config] +Usage: omni down [--no-cache] [--fail-on-upgrade] [--bootstrap] [--clone-suggested] [--prompt] [--prompt-all] [--trust] [--update-repository] [--update-user-config] [--upgrade] --no-cache Whether we should disable the cache while running the command (default: no) @@ -297,6 +297,12 @@ Usage: omni down [--no-cache] [--fail-on-upgrade] [--bootstrap] [--clone-suggest configuration will be copied to the home directory of the user to be loaded on every omni call (default: no) + --upgrade Whether we should upgrade the resources when the currently-installed + version already matches version constraints. If false, this also means + that if an already installed version for another repository matches + version contraints, we will avoid downloading and building a more + recent version (default: false) + Source: builtin EOF @@ -399,7 +405,7 @@ omni - omnipotent tool (v$version) Sets up or tear down a repository depending on its up configuration -Usage: omni up [--no-cache] [--fail-on-upgrade] [--bootstrap] [--clone-suggested] [--prompt] [--prompt-all] [--trust] [--update-repository] [--update-user-config] +Usage: omni up [--no-cache] [--fail-on-upgrade] [--bootstrap] [--clone-suggested] [--prompt] [--prompt-all] [--trust] [--update-repository] [--update-user-config] [--upgrade] --no-cache Whether we should disable the cache while running the command (default: no) @@ -432,6 +438,12 @@ Usage: omni up [--no-cache] [--fail-on-upgrade] [--bootstrap] [--clone-suggested configuration will be copied to the home directory of the user to be loaded on every omni call (default: no) + --upgrade Whether we should upgrade the resources when the currently-installed + version already matches version constraints. If false, this also means + that if an already installed version for another repository matches + version contraints, we will avoid downloading and building a more + recent version (default: false) + Source: builtin EOF diff --git a/tests/test_omni_up_homebrew.bats b/tests/test_omni_up_homebrew.bats index d277dead..f96fb6f9 100644 --- a/tests/test_omni_up_homebrew.bats +++ b/tests/test_omni_up_homebrew.bats @@ -40,7 +40,7 @@ EOF } # bats test_tags=omni:up,omni:up:homebrew,omni:up:homebrew:install -@test "omni up homebrew operation calls to upgrade formula" { +@test "omni up homebrew operation calls to upgrade formula when upgrade is passed on the command line" { cat > .omni.yaml <&- + echo "STATUS: $status" + echo "OUTPUT: $output" + [ "$status" -eq 0 ] +} + +# bats test_tags=omni:up,omni:up:homebrew,omni:up:homebrew:install +@test "omni up homebrew operation calls to upgrade formula when upgrade is configured at the work directory level" { + cat > .omni.yaml <&- + echo "STATUS: $status" + echo "OUTPUT: $output" + [ "$status" -eq 0 ] +} + +# bats test_tags=omni:up,omni:up:homebrew,omni:up:homebrew:install +@test "omni up homebrew operation calls to upgrade formula when upgrade is configured for the formula" { + cat > .omni.yaml <&- + echo "STATUS: $status" + echo "OUTPUT: $output" + [ "$status" -eq 0 ] +} + +# bats test_tags=omni:up,omni:up:homebrew,omni:up:homebrew:install +@test "omni up homebrew operation does not upgrade formula if it is already installed and upgrade is not configured" { + cat > .omni.yaml <&- echo "STATUS: $status" echo "OUTPUT: $output" @@ -79,7 +142,7 @@ EOF } # bats test_tags=omni:up,omni:up:homebrew,omni:up:homebrew:install -@test "omni up homebrew operation calls to upgrade cask" { +@test "omni up homebrew operation calls to upgrade cask when upgrade is passed on the command line" { cat > .omni.yaml <&- + echo "STATUS: $status" + echo "OUTPUT: $output" + [ "$status" -eq 0 ] +} + +# bats test_tags=omni:up,omni:up:homebrew,omni:up:homebrew:install +@test "omni up homebrew operation calls to upgrade cask when upgrade is configured at the work directory level" { + cat > .omni.yaml <&- + echo "STATUS: $status" + echo "OUTPUT: $output" + [ "$status" -eq 0 ] +} + +# bats test_tags=omni:up,omni:up:homebrew,omni:up:homebrew:install +@test "omni up homebrew operation calls to upgrade cask when upgrade is configured for the cask" { + cat > .omni.yaml <&- + echo "STATUS: $status" + echo "OUTPUT: $output" + [ "$status" -eq 0 ] +} + +# bats test_tags=omni:up,omni:up:homebrew,omni:up:homebrew:install +@test "omni up homebrew operation does not upgrade cask if it is already installed and upgrade is not configured" { + cat > .omni.yaml <&- echo "STATUS: $status" echo "OUTPUT: $output" diff --git a/tests/test_omni_up_python.bats b/tests/test_omni_up_python.bats index a16989c1..6ce6c5e8 100644 --- a/tests/test_omni_up_python.bats +++ b/tests/test_omni_up_python.bats @@ -28,6 +28,8 @@ add_asdf_python_calls() { venv=true cache_versions=false list_versions=true + upgrade=false + no_upgrade_installed=false for arg in "$@"; do case $arg in @@ -63,6 +65,14 @@ add_asdf_python_calls() { list_versions="${arg#list_versions=}" shift ;; + upgrade=*) + upgrade="${arg#upgrade=}" + shift + ;; + no_upgrade_installed=*) + no_upgrade_installed="${arg#no_upgrade_installed=}" + shift + ;; *) echo "Unknown argument: $arg" return 1 @@ -97,6 +107,16 @@ python EOF fi + if [ "$upgrade" = "false" ]; then + if [ "$no_upgrade_installed" = "false" ]; then + add_command asdf list python + else + add_command asdf list python < .omni.yaml <&- + echo "STATUS: $status" + echo "OUTPUT: $output" + [ "$status" -eq 0 ] +} + +# bats test_tags=omni:up,omni:up:python,omni:up:python:brew +@test "omni up python operation (latest) with upgrade configured at the work directory level" { + cat > .omni.yaml <&- + echo "STATUS: $status" + echo "OUTPUT: $output" + [ "$status" -eq 0 ] +} + +# bats test_tags=omni:up,omni:up:python,omni:up:python:brew +@test "omni up python operation (latest) with upgrade configured as a command-line parameter" { + cat > .omni.yaml <&- + echo "STATUS: $status" + echo "OUTPUT: $output" + [ "$status" -eq 0 ] +} + +# bats test_tags=omni:up,omni:up:python,omni:up:python:brew +@test "omni up python operation (latest) with upgrade disabled and only an older major installed" { + cat > .omni.yaml <&- + echo "STATUS: $status" + echo "OUTPUT: $output" + [ "$status" -eq 0 ] +} + +# bats test_tags=omni:up,omni:up:python,omni:up:python:brew +@test "omni up python operation (2) with upgrade disabled and a version 2 installed" { + cat > .omni.yaml <&- + echo "STATUS: $status" + echo "OUTPUT: $output" + [ "$status" -eq 0 ] +} + +# bats test_tags=omni:up,omni:up:python,omni:up:python:brew +@test "omni up python operation (latest) with upgrade disabled and the current major installed" { + cat > .omni.yaml <&- + echo "STATUS: $status" + echo "OUTPUT: $output" + [ "$status" -eq 0 ] +} + +# bats test_tags=omni:up,omni:up:python,omni:up:python:brew +@test "omni up python operation (3) with upgrade disabled and a version 3 installed" { + cat > .omni.yaml <&- + echo "STATUS: $status" + echo "OUTPUT: $output" + [ "$status" -eq 0 ] +} + # bats test_tags=omni:up,omni:up:python,omni:up:python:brew @test "omni up python operation (*) using brew for dependencies" { cat > .omni.yaml <1.3.5 <=1.4.0` will match any version between `1.2.0` included and `1.3.0` excluded, or between `1.3.5` excluded and `1.4.0` included. diff --git a/website/contents/reference/01-configuration/0102-parameters/010250-up/010250-bash.md b/website/contents/reference/01-configuration/0102-parameters/010250-up/010250-bash.md index dce26c1a..0d65abb1 100644 --- a/website/contents/reference/01-configuration/0102-parameters/010250-up/010250-bash.md +++ b/website/contents/reference/01-configuration/0102-parameters/010250-up/010250-bash.md @@ -23,6 +23,7 @@ The following parameters can be used: | `dir` | path | Relative path (or list of relative paths) to the directory in the project for which to use the node version | | `url` | string | The URL to download the tool from, in case the tool is not registered in [`asdf-plugins`](https://github.com/asdf-vm/asdf-plugins) or if you want to use a custom version. | | `version` | string | The version of the tool to install; see [version handling](#version-handling) below for more details. | +| `upgrade` | boolean | whether or not to always upgrade to the most up to date matching version, even if an already-installed version matches the requirements *(default: false)* | ### Version handling @@ -39,8 +40,8 @@ The following strings can be used to specify the version: | `<1.2.3` | Must be lower than `1.2.3` | | `<=1.2.3` | Must be lower or equal to `1.2.3` | | `1.2.x` | Accepts `1.2.0`, `1.2.1`, etc. but will not accept `1.3.0` | -| `*` | Matches any version (will default to `latest`) | -| `latest` | Latest release | +| `*` | Matches any version (same as `latest`, except that when `upgrade` is `false`, will match any installed version) | +| `latest` | Latest release (when `upgrade` is set to `false`, will only match with installed versions of the latest major) | | `auto` | Lookup for any version files in the project directory (`.tool-versions` or `.bash-version`) and apply version parsing | The version also supports the `||` operator to specify ranges. This operator is not compatible with the `latest` and `auto` keywords. For instance, `1.2.x || >1.3.5 <=1.4.0` will match any version between `1.2.0` included and `1.3.0` excluded, or between `1.3.5` excluded and `1.4.0` included. diff --git a/website/contents/reference/01-configuration/0102-parameters/010250-up/010250-github-release.md b/website/contents/reference/01-configuration/0102-parameters/010250-up/010250-github-release.md index e7596a78..d31233eb 100644 --- a/website/contents/reference/01-configuration/0102-parameters/010250-up/010250-github-release.md +++ b/website/contents/reference/01-configuration/0102-parameters/010250-up/010250-github-release.md @@ -37,6 +37,7 @@ This supports authenticated requests using [the `gh` command line interface](htt |------------------|-----------|-------------------------------------------------------| | `repository` | string | The name of the repository to download the release from, in the `/` format; can also be provided as an object with the `owner` and `name` keys | | `version` | string | The version of the tool to install; see [version handling](#version-handling) below for more details. | +| `upgrade` | boolean | whether or not to always upgrade to the most up to date matching release, even if an already-installed version matches the requirements *(default: false)* | | `prerelease` | boolean | Whether to download a prerelease version or only match stable releases; this will also apply to versions with prerelease specification, e.g. `1.2.3-alpha` *(default: `false`)* | | `build` | boolean | Whether to download a version with build specification, e.g. `1.2.3+build` *(default: `false`)* | | `binary` | boolean | Whether to download an asset that is not archived and consider it a binary file *(default: `true`)* | @@ -72,8 +73,8 @@ The following strings can be used to specify the version: | `<1.2.3` | Must be lower than `1.2.3` | | `<=1.2.3` | Must be lower or equal to `1.2.3` | | `1.2.x` | Accepts `1.2.0`, `1.2.1`, etc. but will not accept `1.3.0` | -| `*` | Matches any version (will default to `latest`) | -| `latest` | Latest release | +| `*` | Matches any version (same as `latest`, except that when `upgrade` is `false`, will match any installed version) | +| `latest` | Latest release (when `upgrade` is set to `false`, will only match with installed versions of the latest major) | | `auto` | Lookup for any version files in the project directory (`.tool-versions`, `.go-version`, `.golang-version` or `.go.mod`) and apply version parsing | The version also supports the `||` operator to specify ranges. This operator is not compatible with the `latest` and `auto` keywords. For instance, `1.2.x || >1.3.5 <=1.4.0` will match any version between `1.2.0` included and `1.3.0` excluded, or between `1.3.5` excluded and `1.4.0` included. diff --git a/website/contents/reference/01-configuration/0102-parameters/010250-up/010250-go.md b/website/contents/reference/01-configuration/0102-parameters/010250-up/010250-go.md index 49d05148..67e197f1 100644 --- a/website/contents/reference/01-configuration/0102-parameters/010250-up/010250-go.md +++ b/website/contents/reference/01-configuration/0102-parameters/010250-up/010250-go.md @@ -24,6 +24,7 @@ The following parameters can be used: | `url` | string | The URL to download the tool from, in case the tool is not registered in [`asdf-plugins`](https://github.com/asdf-vm/asdf-plugins) or if you want to use a custom version. | | `version` | string | The version of the tool to install; see [version handling](#version-handling) below for more details. | | `version_file` | path | Relative path to the `go.mod` file where the golang version to install can be read from | +| `upgrade` | boolean | whether or not to always upgrade to the most up to date matching version, even if an already-installed version matches the requirements *(default: false)* | ### Version handling @@ -40,8 +41,8 @@ The following strings can be used to specify the version: | `<1.2.3` | Must be lower than `1.2.3` | | `<=1.2.3` | Must be lower or equal to `1.2.3` | | `1.2.x` | Accepts `1.2.0`, `1.2.1`, etc. but will not accept `1.3.0` | -| `*` | Matches any version (will default to `latest`) | -| `latest` | Latest release | +| `*` | Matches any version (same as `latest`, except that when `upgrade` is `false`, will match any installed version) | +| `latest` | Latest release (when `upgrade` is set to `false`, will only match with installed versions of the latest major) | | `auto` | Lookup for any version files in the project directory (`.tool-versions`, `.go-version`, `.golang-version` or `.go.mod`) and apply version parsing | The version also supports the `||` operator to specify ranges. This operator is not compatible with the `latest` and `auto` keywords. For instance, `1.2.x || >1.3.5 <=1.4.0` will match any version between `1.2.0` included and `1.3.0` excluded, or between `1.3.5` excluded and `1.4.0` included. diff --git a/website/contents/reference/01-configuration/0102-parameters/010250-up/010250-homebrew.md b/website/contents/reference/01-configuration/0102-parameters/010250-up/010250-homebrew.md index 5cb40e79..95faac83 100644 --- a/website/contents/reference/01-configuration/0102-parameters/010250-up/010250-homebrew.md +++ b/website/contents/reference/01-configuration/0102-parameters/010250-up/010250-homebrew.md @@ -33,6 +33,7 @@ If `brew` is not available on the system, this step will be ignored. |------------------|-----------|-------------------------------------------------------| | `repo` | string | The name of the repository to tap, in the `/` format | | `url` | string | The URL to tap the repository from (necessary if not following the `https://github.com//homebrew-` format) | +| `upgrade` | boolean | whether or not to always try and update the tap *(default: false)* | ### `install` @@ -42,6 +43,8 @@ If `brew` is not available on the system, this step will be ignored. | `formula` | string | The name of the formula to install (cannot be used along with `cask`) | | `cask` | string | The name of the cask to install (cannot be used along with `formula`) | | `version` | string | The version to install for the formula or cask | +| `upgrade` | boolean | whether or not to always try and update the formula or cask *(default: false)* | + ## Examples diff --git a/website/contents/reference/01-configuration/0102-parameters/010250-up/010250-node.md b/website/contents/reference/01-configuration/0102-parameters/010250-up/010250-node.md index b92b84d4..b1c2d16d 100644 --- a/website/contents/reference/01-configuration/0102-parameters/010250-up/010250-node.md +++ b/website/contents/reference/01-configuration/0102-parameters/010250-up/010250-node.md @@ -25,6 +25,7 @@ The following parameters can be used: | `install_packages` | boolean | If set to `true`, the packages specified in the `package.json` file will be installed, using the preferred package manager between `npm`, `yarn` and `pnpm`. The preferred package manager is determined from any lock file found in the project directory, as well as from the `engines` section of the `package.json` file. *(default: `true`)* | | `url` | string | The URL to download the tool from, in case the tool is not registered in [`asdf-plugins`](https://github.com/asdf-vm/asdf-plugins) or if you want to use a custom version. | | `version` | string | The version of the tool to install; see [version handling](#version-handling) for more details. | +| `upgrade` | boolean | whether or not to always upgrade to the most up to date matching version, even if an already-installed version matches the requirements *(default: false)* | ### Version handling @@ -41,8 +42,8 @@ The following strings can be used to specify the version: | `<1.2.3` | Must be lower than `1.2.3` | | `<=1.2.3` | Must be lower or equal to `1.2.3` | | `1.2.x` | Accepts `1.2.0`, `1.2.1`, etc. but will not accept `1.3.0` | -| `*` | Matches any version (will default to `latest`) | -| `latest` | Latest release | +| `*` | Matches any version (same as `latest`, except that when `upgrade` is `false`, will match any installed version) | +| `latest` | Latest release (when `upgrade` is set to `false`, will only match with installed versions of the latest major) | | `auto` | Lookup for any version files in the project directory (`.tool-versions`, `.node-version`, `.nodejs-version`, `package.json` or `.nvmrc`) and apply version parsing | The version also supports the `||` operator to specify ranges. This operator is not compatible with the `latest` and `auto` keywords. For instance, `1.2.x || >1.3.5 <=1.4.0` will match any version between `1.2.0` included and `1.3.0` excluded, or between `1.3.5` excluded and `1.4.0` included. diff --git a/website/contents/reference/01-configuration/0102-parameters/010250-up/010250-python.md b/website/contents/reference/01-configuration/0102-parameters/010250-up/010250-python.md index 8592352a..86002bdc 100644 --- a/website/contents/reference/01-configuration/0102-parameters/010250-up/010250-python.md +++ b/website/contents/reference/01-configuration/0102-parameters/010250-up/010250-python.md @@ -20,6 +20,7 @@ The following parameters can be used: | `pip` | path | Relative path (or list of relative paths) to the requirements files to be used as parameter to `pip install -r` for installing dependencies; if using the word `auto`, omni will try to install the `requirements.txt` file in each specified `dir` (or in each discovered directory with `version: auto`) if it exists | | `url` | string | The URL to download the tool from, in case the tool is not registered in [`asdf-plugins`](https://github.com/asdf-vm/asdf-plugins) or if you want to use a custom version. | | `version` | string | The version of the tool to install; see [version handling](#version-handling) below for more details. | +| `upgrade` | boolean | whether or not to always upgrade to the most up to date matching version, even if an already-installed version matches the requirements *(default: false)* | ### Version handling @@ -36,8 +37,8 @@ The following strings can be used to specify the version: | `<1.2.3` | Must be lower than `1.2.3` | | `<=1.2.3` | Must be lower or equal to `1.2.3` | | `1.2.x` | Accepts `1.2.0`, `1.2.1`, etc. but will not accept `1.3.0` | -| `*` | Matches any version (will default to `latest`) | -| `latest` | Latest release (default) | +| `*` | Matches any version (same as `latest`, except that when `upgrade` is `false`, will match any installed version) | +| `latest` | Latest release (when `upgrade` is set to `false`, will only match with installed versions of the latest major) | | `auto` | Lookup for any version files in the project directory (`.tool-versions` or `.python-version`) and apply version parsing | The version also supports the `||` operator to specify ranges. This operator is not compatible with the `latest` and `auto` keywords. For instance, `1.2.x || >1.3.5 <=1.4.0` will match any version between `1.2.0` included and `1.3.0` excluded, or between `1.3.5` excluded and `1.4.0` included. diff --git a/website/contents/reference/01-configuration/0102-parameters/010250-up/010250-ruby.md b/website/contents/reference/01-configuration/0102-parameters/010250-up/010250-ruby.md index 723d33f4..ddd08c18 100644 --- a/website/contents/reference/01-configuration/0102-parameters/010250-up/010250-ruby.md +++ b/website/contents/reference/01-configuration/0102-parameters/010250-up/010250-ruby.md @@ -19,6 +19,7 @@ The following parameters can be used: | `dir` | path | Relative path (or list of relative paths) to the directory in the project for which to use the node version | | `url` | string | The URL to download the tool from, in case the tool is not registered in [`asdf-plugins`](https://github.com/asdf-vm/asdf-plugins) or if you want to use a custom version. | | `version` | string | The version of the tool to install; see [version handling](#version-handling) below for more details. | +| `upgrade` | boolean | whether or not to always upgrade to the most up to date matching version, even if an already-installed version matches the requirements *(default: false)* | ### Version handling @@ -35,8 +36,8 @@ The following strings can be used to specify the version: | `<1.2.3` | Must be lower than `1.2.3` | | `<=1.2.3` | Must be lower or equal to `1.2.3` | | `1.2.x` | Accepts `1.2.0`, `1.2.1`, etc. but will not accept `1.3.0` | -| `*` | Matches any version (will default to `latest`) | -| `latest` | Latest release | +| `*` | Matches any version (same as `latest`, except that when `upgrade` is `false`, will match any installed version) | +| `latest` | Latest release (when `upgrade` is set to `false`, will only match with installed versions of the latest major) | | `auto` | Lookup for any version files in the project directory (`.tool-versions` or `.ruby-version`) and apply version parsing | The version also supports the `||` operator to specify ranges. This operator is not compatible with the `latest` and `auto` keywords. For instance, `1.2.x || >1.3.5 <=1.4.0` will match any version between `1.2.0` included and `1.3.0` excluded, or between `1.3.5` excluded and `1.4.0` included. diff --git a/website/contents/reference/01-configuration/0102-parameters/010250-up/010250-rust.md b/website/contents/reference/01-configuration/0102-parameters/010250-up/010250-rust.md index e09a9109..5bc6626f 100644 --- a/website/contents/reference/01-configuration/0102-parameters/010250-up/010250-rust.md +++ b/website/contents/reference/01-configuration/0102-parameters/010250-up/010250-rust.md @@ -19,6 +19,7 @@ The following parameters can be used: | `dir` | path | Relative path (or list of relative paths) to the directory in the project for which to use the node version | | `url` | string | The URL to download the tool from, in case the tool is not registered in [`asdf-plugins`](https://github.com/asdf-vm/asdf-plugins) or if you want to use a custom version. | | `version` | string | The version of the tool to install; see [version handling](#version-handling) below for more details. | +| `upgrade` | boolean | whether or not to always upgrade to the most up to date matching version, even if an already-installed version matches the requirements *(default: false)* | ### Version handling @@ -35,8 +36,8 @@ The following strings can be used to specify the version: | `<1.2.3` | Must be lower than `1.2.3` | | `<=1.2.3` | Must be lower or equal to `1.2.3` | | `1.2.x` | Accepts `1.2.0`, `1.2.1`, etc. but will not accept `1.3.0` | -| `*` | Matches any version (will default to `latest`) | -| `latest` | Latest release | +| `*` | Matches any version (same as `latest`, except that when `upgrade` is `false`, will match any installed version) | +| `latest` | Latest release (when `upgrade` is set to `false`, will only match with installed versions of the latest major) | | `auto` | Lookup for any version files in the project directory (`.tool-versions` or `.rust-version`) and apply version parsing | The version also supports the `||` operator to specify ranges. This operator is not compatible with the `latest` and `auto` keywords. For instance, `1.2.x || >1.3.5 <=1.4.0` will match any version between `1.2.0` included and `1.3.0` excluded, or between `1.3.5` excluded and `1.4.0` included. diff --git a/website/contents/reference/01-configuration/0102-parameters/010250-up/010250-terraform.md b/website/contents/reference/01-configuration/0102-parameters/010250-up/010250-terraform.md index 9f143072..e2e396d4 100644 --- a/website/contents/reference/01-configuration/0102-parameters/010250-up/010250-terraform.md +++ b/website/contents/reference/01-configuration/0102-parameters/010250-up/010250-terraform.md @@ -19,6 +19,7 @@ The following parameters can be used: | `dir` | path | Relative path (or list of relative paths) to the directory in the project for which to use the node version | | `url` | string | The URL to download the tool from, in case the tool is not registered in [`asdf-plugins`](https://github.com/asdf-vm/asdf-plugins) or if you want to use a custom version. | | `version` | string | The version of the tool to install; see [version handling](#version-handling) below for more details. | +| `upgrade` | boolean | whether or not to always upgrade to the most up to date matching version, even if an already-installed version matches the requirements *(default: false)* | ### Version handling @@ -35,8 +36,8 @@ The following strings can be used to specify the version: | `<1.2.3` | Must be lower than `1.2.3` | | `<=1.2.3` | Must be lower or equal to `1.2.3` | | `1.2.x` | Accepts `1.2.0`, `1.2.1`, etc. but will not accept `1.3.0` | -| `*` | Matches any version (will default to `latest`) | -| `latest` | Latest release | +| `*` | Matches any version (same as `latest`, except that when `upgrade` is `false`, will match any installed version) | +| `latest` | Latest release (when `upgrade` is set to `false`, will only match with installed versions of the latest major) | | `auto` | Lookup for any version files in the project directory (`.tool-versions` or `.terraform-version`) and apply version parsing | The version also supports the `||` operator to specify ranges. This operator is not compatible with the `latest` and `auto` keywords. For instance, `1.2.x || >1.3.5 <=1.4.0` will match any version between `1.2.0` included and `1.3.0` excluded, or between `1.3.5` excluded and `1.4.0` included. diff --git a/website/contents/reference/01-configuration/0102-parameters/010250-up_command.md b/website/contents/reference/01-configuration/0102-parameters/010250-up_command.md index 336b8c3d..a2279d3a 100644 --- a/website/contents/reference/01-configuration/0102-parameters/010250-up_command.md +++ b/website/contents/reference/01-configuration/0102-parameters/010250-up_command.md @@ -14,6 +14,7 @@ Configuration related to the `omni up` command. | `notify_workdir_config_updated` | boolean | whether or not to print a message on the prompt if the `up` configuration of the work directory has been updated since the last `omni up` *(default: true)* | | `notify_workdir_config_available` | boolean | whether or not to print a message on the prompt if the current work directory has an available `up` configuration but `omni up` has not been run yet *(default: true)* | | `preferred_tools` | list | list of preferred tools for [`any` operations](up/any) when running `omni up`; those tools will be preferred over others, in the order they are defined | +| `upgrade` | boolean | whether or not to always upgrade to the most up to date matching version of the dependencies when running `omni up`, even if an already-installed version matches the requirements *(default: false)* | ## Example