mirror of
https://github.com/astral-sh/uv.git
synced 2025-11-03 05:03:46 +00:00
Retain and respect settings in tool upgrades (#5937)
## Summary We now persist the `ResolverInstallerOptions` when writing out a tool receipt. When upgrading, we grab the saved options, and merge with the command-line arguments and user-level filesystem settings (CLI > receipt > filesystem).
This commit is contained in:
parent
44f94524f3
commit
f89403f4f6
27 changed files with 604 additions and 131 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
|
@ -5179,6 +5179,7 @@ dependencies = [
|
|||
"uv-fs",
|
||||
"uv-installer",
|
||||
"uv-python",
|
||||
"uv-settings",
|
||||
"uv-state",
|
||||
"uv-virtualenv",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ workspace = true
|
|||
cache-key = { workspace = true }
|
||||
distribution-filename = { workspace = true }
|
||||
pep440_rs = { workspace = true }
|
||||
pep508_rs = { workspace = true }
|
||||
pep508_rs = { workspace = true, features = ["serde"] }
|
||||
platform-tags = { workspace = true }
|
||||
pypi-types = { workspace = true }
|
||||
uv-fs = { workspace = true }
|
||||
|
|
|
|||
|
|
@ -290,7 +290,8 @@ impl From<VerbatimUrl> for FlatIndexLocation {
|
|||
/// The index locations to use for fetching packages. By default, uses the PyPI index.
|
||||
///
|
||||
/// From a pip perspective, this type merges `--index-url`, `--extra-index-url`, and `--find-links`.
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||
#[serde(rename_all = "kebab-case", deny_unknown_fields)]
|
||||
pub struct IndexLocations {
|
||||
index: Option<IndexUrl>,
|
||||
extra_index: Vec<IndexUrl>,
|
||||
|
|
|
|||
|
|
@ -228,7 +228,7 @@ fn parse_scripts(
|
|||
scripts_from_ini(extras, python_minor, ini)
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
|
||||
#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
|
|
|
|||
|
|
@ -197,6 +197,27 @@ impl From<Url> for VerbatimUrl {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
impl serde::Serialize for VerbatimUrl {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
self.url.serialize(serializer)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
impl<'de> serde::Deserialize<'de> for VerbatimUrl {
|
||||
fn deserialize<D>(deserializer: D) -> Result<VerbatimUrl, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
let url = Url::deserialize(deserializer)?;
|
||||
Ok(VerbatimUrl::from_url(url))
|
||||
}
|
||||
}
|
||||
|
||||
impl Pep508Url for VerbatimUrl {
|
||||
type Err = VerbatimUrlError;
|
||||
|
||||
|
|
|
|||
|
|
@ -2759,9 +2759,6 @@ pub struct ToolUpgradeArgs {
|
|||
|
||||
#[command(flatten)]
|
||||
pub build: BuildArgs,
|
||||
|
||||
#[command(flatten)]
|
||||
pub refresh: RefreshArgs,
|
||||
}
|
||||
|
||||
#[derive(Args)]
|
||||
|
|
|
|||
|
|
@ -306,9 +306,17 @@ pub fn resolver_installer_options(
|
|||
},
|
||||
find_links: index_args.find_links,
|
||||
upgrade: flag(upgrade, no_upgrade),
|
||||
upgrade_package: Some(upgrade_package),
|
||||
upgrade_package: if upgrade_package.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(upgrade_package)
|
||||
},
|
||||
reinstall: flag(reinstall, no_reinstall),
|
||||
reinstall_package: Some(reinstall_package),
|
||||
reinstall_package: if reinstall_package.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(reinstall_package)
|
||||
},
|
||||
index_strategy,
|
||||
keyring_provider,
|
||||
resolution,
|
||||
|
|
@ -320,14 +328,26 @@ pub fn resolver_installer_options(
|
|||
config_settings: config_setting
|
||||
.map(|config_settings| config_settings.into_iter().collect::<ConfigSettings>()),
|
||||
no_build_isolation: flag(no_build_isolation, build_isolation),
|
||||
no_build_isolation_package: Some(no_build_isolation_package),
|
||||
no_build_isolation_package: if no_build_isolation_package.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(no_build_isolation_package)
|
||||
},
|
||||
exclude_newer,
|
||||
link_mode,
|
||||
compile_bytecode: flag(compile_bytecode, no_compile_bytecode),
|
||||
no_build: flag(no_build, build),
|
||||
no_build_package: Some(no_build_package),
|
||||
no_build_package: if no_build_package.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(no_build_package)
|
||||
},
|
||||
no_binary: flag(no_binary, binary),
|
||||
no_binary_package: Some(no_binary_package),
|
||||
no_binary_package: if no_binary_package.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(no_binary_package)
|
||||
},
|
||||
no_sources: if no_sources { Some(true) } else { None },
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use uv_auth::{self, KeyringProvider};
|
||||
|
||||
/// Keyring provider type to use for credential lookup.
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, serde::Deserialize)]
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
|
||||
#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
|
|
|
|||
|
|
@ -32,7 +32,8 @@ impl Display for BuildKind {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, PartialEq, Eq)]
|
||||
#[derive(Debug, Default, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||
#[serde(rename_all = "kebab-case", deny_unknown_fields)]
|
||||
pub struct BuildOptions {
|
||||
no_binary: NoBinary,
|
||||
no_build: NoBuild,
|
||||
|
|
@ -111,7 +112,8 @@ impl BuildOptions {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, PartialEq, Eq)]
|
||||
#[derive(Debug, Default, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||
#[serde(rename_all = "kebab-case", deny_unknown_fields)]
|
||||
pub enum NoBinary {
|
||||
/// Allow installation of any wheel.
|
||||
#[default]
|
||||
|
|
@ -206,7 +208,8 @@ impl NoBinary {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, PartialEq, Eq)]
|
||||
#[derive(Debug, Default, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||
#[serde(rename_all = "kebab-case", deny_unknown_fields)]
|
||||
pub enum NoBuild {
|
||||
/// Allow building wheels from any source distribution.
|
||||
#[default]
|
||||
|
|
@ -305,7 +308,7 @@ impl NoBuild {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, serde::Deserialize)]
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
|
||||
#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
|
|
|
|||
|
|
@ -80,7 +80,7 @@ impl<'de> serde::Deserialize<'de> for ConfigSettingValue {
|
|||
/// list of strings.
|
||||
///
|
||||
/// See: <https://peps.python.org/pep-0517/#config-settings>
|
||||
#[derive(Debug, Default, Clone)]
|
||||
#[derive(Debug, Default, Clone, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct ConfigSettings(BTreeMap<String, ConfigSettingValue>);
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,8 @@ use rustc_hash::FxHashMap;
|
|||
use uv_cache::{Refresh, Timestamp};
|
||||
|
||||
/// Whether to reinstall packages.
|
||||
#[derive(Debug, Default, Clone)]
|
||||
#[derive(Debug, Default, Clone, serde::Serialize, serde::Deserialize)]
|
||||
#[serde(rename_all = "kebab-case", deny_unknown_fields)]
|
||||
pub enum Reinstall {
|
||||
/// Don't reinstall any packages; respect the existing installation.
|
||||
#[default]
|
||||
|
|
@ -58,7 +59,8 @@ impl From<Reinstall> for Refresh {
|
|||
}
|
||||
|
||||
/// Whether to allow package upgrades.
|
||||
#[derive(Debug, Default, Clone)]
|
||||
#[derive(Debug, Default, Clone, serde::Serialize, serde::Deserialize)]
|
||||
#[serde(rename_all = "kebab-case", deny_unknown_fields)]
|
||||
pub enum Upgrade {
|
||||
/// Prefer pinned versions from the existing lockfile, if possible.
|
||||
#[default]
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||
#[serde(rename_all = "kebab-case", deny_unknown_fields)]
|
||||
pub enum SourceStrategy {
|
||||
/// Use `tool.uv.sources` when resolving dependencies.
|
||||
#[default]
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ use uv_normalize::PackageName;
|
|||
use crate::resolver::ForkSet;
|
||||
use crate::{DependencyMode, Manifest, ResolverMarkers};
|
||||
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, serde::Deserialize)]
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
|
||||
#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use std::{fmt::Debug, num::NonZeroUsize, path::PathBuf};
|
||||
|
||||
use serde::Deserialize;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use distribution_types::{FlatIndexLocation, IndexUrl};
|
||||
use install_wheel_rs::linker::LinkMode;
|
||||
|
|
@ -212,7 +212,9 @@ pub struct ResolverOptions {
|
|||
/// Shared settings, relevant to all operations that must resolve and install dependencies. The
|
||||
/// union of [`InstallerOptions`] and [`ResolverOptions`].
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug, Clone, Default, Deserialize, CombineOptions, OptionsMetadata)]
|
||||
#[derive(
|
||||
Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, CombineOptions, OptionsMetadata,
|
||||
)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct ResolverInstallerOptions {
|
||||
|
|
@ -1243,3 +1245,90 @@ impl From<ResolverInstallerOptions> for InstallerOptions {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The options persisted alongside an installed tool.
|
||||
///
|
||||
/// A mirror of [`ResolverInstallerOptions`], without upgrades and reinstalls, which shouldn't be
|
||||
/// persisted in a tool receipt.
|
||||
#[derive(
|
||||
Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, CombineOptions, OptionsMetadata,
|
||||
)]
|
||||
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct ToolOptions {
|
||||
pub index_url: Option<IndexUrl>,
|
||||
pub extra_index_url: Option<Vec<IndexUrl>>,
|
||||
pub no_index: Option<bool>,
|
||||
pub find_links: Option<Vec<FlatIndexLocation>>,
|
||||
pub index_strategy: Option<IndexStrategy>,
|
||||
pub keyring_provider: Option<KeyringProviderType>,
|
||||
pub resolution: Option<ResolutionMode>,
|
||||
pub prerelease: Option<PrereleaseMode>,
|
||||
pub config_settings: Option<ConfigSettings>,
|
||||
pub no_build_isolation: Option<bool>,
|
||||
pub no_build_isolation_package: Option<Vec<PackageName>>,
|
||||
pub exclude_newer: Option<ExcludeNewer>,
|
||||
pub link_mode: Option<LinkMode>,
|
||||
pub compile_bytecode: Option<bool>,
|
||||
pub no_sources: Option<bool>,
|
||||
pub no_build: Option<bool>,
|
||||
pub no_build_package: Option<Vec<PackageName>>,
|
||||
pub no_binary: Option<bool>,
|
||||
pub no_binary_package: Option<Vec<PackageName>>,
|
||||
}
|
||||
|
||||
impl From<ResolverInstallerOptions> for ToolOptions {
|
||||
fn from(value: ResolverInstallerOptions) -> Self {
|
||||
Self {
|
||||
index_url: value.index_url,
|
||||
extra_index_url: value.extra_index_url,
|
||||
no_index: value.no_index,
|
||||
find_links: value.find_links,
|
||||
index_strategy: value.index_strategy,
|
||||
keyring_provider: value.keyring_provider,
|
||||
resolution: value.resolution,
|
||||
prerelease: value.prerelease,
|
||||
config_settings: value.config_settings,
|
||||
no_build_isolation: value.no_build_isolation,
|
||||
no_build_isolation_package: value.no_build_isolation_package,
|
||||
exclude_newer: value.exclude_newer,
|
||||
link_mode: value.link_mode,
|
||||
compile_bytecode: value.compile_bytecode,
|
||||
no_sources: value.no_sources,
|
||||
no_build: value.no_build,
|
||||
no_build_package: value.no_build_package,
|
||||
no_binary: value.no_binary,
|
||||
no_binary_package: value.no_binary_package,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ToolOptions> for ResolverInstallerOptions {
|
||||
fn from(value: ToolOptions) -> Self {
|
||||
Self {
|
||||
index_url: value.index_url,
|
||||
extra_index_url: value.extra_index_url,
|
||||
no_index: value.no_index,
|
||||
find_links: value.find_links,
|
||||
index_strategy: value.index_strategy,
|
||||
keyring_provider: value.keyring_provider,
|
||||
resolution: value.resolution,
|
||||
prerelease: value.prerelease,
|
||||
config_settings: value.config_settings,
|
||||
no_build_isolation: value.no_build_isolation,
|
||||
no_build_isolation_package: value.no_build_isolation_package,
|
||||
exclude_newer: value.exclude_newer,
|
||||
link_mode: value.link_mode,
|
||||
compile_bytecode: value.compile_bytecode,
|
||||
no_sources: value.no_sources,
|
||||
upgrade: None,
|
||||
upgrade_package: None,
|
||||
reinstall: None,
|
||||
reinstall_package: None,
|
||||
no_build: value.no_build,
|
||||
no_build_package: value.no_build_package,
|
||||
no_binary: value.no_binary,
|
||||
no_binary_package: value.no_binary_package,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,10 +19,11 @@ pep508_rs = { workspace = true }
|
|||
pypi-types = { workspace = true }
|
||||
uv-cache = { workspace = true }
|
||||
uv-fs = { workspace = true }
|
||||
uv-state = { workspace = true }
|
||||
uv-python = { workspace = true }
|
||||
uv-virtualenv = { workspace = true }
|
||||
uv-installer = { workspace = true }
|
||||
uv-python = { workspace = true }
|
||||
uv-settings = { workspace = true }
|
||||
uv-state = { workspace = true }
|
||||
uv-virtualenv = { workspace = true }
|
||||
|
||||
dirs-sys = { workspace = true }
|
||||
fs-err = { workspace = true }
|
||||
|
|
|
|||
|
|
@ -42,15 +42,6 @@ impl ToolReceipt {
|
|||
}
|
||||
}
|
||||
|
||||
// Ignore raw document in comparison.
|
||||
impl PartialEq for ToolReceipt {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.tool.eq(&other.tool)
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for ToolReceipt {}
|
||||
|
||||
impl From<Tool> for ToolReceipt {
|
||||
fn from(tool: Tool) -> Self {
|
||||
ToolReceipt {
|
||||
|
|
|
|||
|
|
@ -2,16 +2,17 @@ use std::path::PathBuf;
|
|||
|
||||
use serde::Deserialize;
|
||||
use toml_edit::value;
|
||||
use toml_edit::Array;
|
||||
use toml_edit::Table;
|
||||
use toml_edit::Value;
|
||||
use toml_edit::{Array, Item};
|
||||
|
||||
use pypi_types::{Requirement, VerbatimParsedUrl};
|
||||
use uv_fs::PortablePath;
|
||||
use uv_settings::ToolOptions;
|
||||
|
||||
/// A tool entry.
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
#[serde(try_from = "ToolWire", into = "ToolWire")]
|
||||
pub struct Tool {
|
||||
/// The requirements requested by the user during installation.
|
||||
|
|
@ -20,18 +21,22 @@ pub struct Tool {
|
|||
python: Option<String>,
|
||||
/// A mapping of entry point names to their metadata.
|
||||
entrypoints: Vec<ToolEntrypoint>,
|
||||
/// The [`ToolOptions`] used to install this tool.
|
||||
options: ToolOptions,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
pub struct ToolWire {
|
||||
pub requirements: Vec<RequirementWire>,
|
||||
pub python: Option<String>,
|
||||
pub entrypoints: Vec<ToolEntrypoint>,
|
||||
struct ToolWire {
|
||||
requirements: Vec<RequirementWire>,
|
||||
python: Option<String>,
|
||||
entrypoints: Vec<ToolEntrypoint>,
|
||||
#[serde(default)]
|
||||
options: ToolOptions,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum RequirementWire {
|
||||
enum RequirementWire {
|
||||
/// A [`Requirement`] following our uv-specific schema.
|
||||
Requirement(Requirement),
|
||||
/// A PEP 508-compatible requirement. We no longer write these, but there might be receipts out
|
||||
|
|
@ -49,6 +54,7 @@ impl From<Tool> for ToolWire {
|
|||
.collect(),
|
||||
python: tool.python,
|
||||
entrypoints: tool.entrypoints,
|
||||
options: tool.options,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -68,6 +74,7 @@ impl TryFrom<ToolWire> for Tool {
|
|||
.collect(),
|
||||
python: tool.python,
|
||||
entrypoints: tool.entrypoints,
|
||||
options: tool.options,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -112,6 +119,7 @@ impl Tool {
|
|||
requirements: Vec<Requirement>,
|
||||
python: Option<String>,
|
||||
entrypoints: impl Iterator<Item = ToolEntrypoint>,
|
||||
options: ToolOptions,
|
||||
) -> Self {
|
||||
let mut entrypoints: Vec<_> = entrypoints.collect();
|
||||
entrypoints.sort();
|
||||
|
|
@ -119,9 +127,16 @@ impl Tool {
|
|||
requirements,
|
||||
python,
|
||||
entrypoints,
|
||||
options,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new [`Tool`] with the given [`ToolOptions`].
|
||||
#[must_use]
|
||||
pub fn with_options(self, options: ToolOptions) -> Self {
|
||||
Self { options, ..self }
|
||||
}
|
||||
|
||||
/// Returns the TOML table for this tool.
|
||||
pub(crate) fn to_toml(&self) -> Result<Table, toml_edit::ser::Error> {
|
||||
let mut table = Table::new();
|
||||
|
|
@ -160,6 +175,17 @@ impl Tool {
|
|||
value(entrypoints)
|
||||
});
|
||||
|
||||
if self.options != ToolOptions::default() {
|
||||
let serialized =
|
||||
serde::Serialize::serialize(&self.options, toml_edit::ser::ValueSerializer::new())?;
|
||||
let Value::InlineTable(serialized) = serialized else {
|
||||
return Err(toml_edit::ser::Error::Custom(
|
||||
"Expected an inline table".to_string(),
|
||||
));
|
||||
};
|
||||
table.insert("options", Item::Table(serialized.into_table()));
|
||||
}
|
||||
|
||||
Ok(table)
|
||||
}
|
||||
|
||||
|
|
@ -174,6 +200,10 @@ impl Tool {
|
|||
pub fn python(&self) -> &Option<String> {
|
||||
&self.python
|
||||
}
|
||||
|
||||
pub fn options(&self) -> &ToolOptions {
|
||||
&self.options
|
||||
}
|
||||
}
|
||||
|
||||
impl ToolEntrypoint {
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ use uv_fs::replace_symlink;
|
|||
use uv_fs::Simplified;
|
||||
use uv_installer::SitePackages;
|
||||
use uv_python::PythonEnvironment;
|
||||
use uv_settings::ToolOptions;
|
||||
use uv_shell::Shell;
|
||||
use uv_tool::{entrypoint_paths, find_executable_directory, InstalledTools, Tool, ToolEntrypoint};
|
||||
use uv_warnings::warn_user;
|
||||
|
|
@ -72,11 +73,12 @@ pub(crate) fn install_executables(
|
|||
environment: &PythonEnvironment,
|
||||
name: &PackageName,
|
||||
installed_tools: &InstalledTools,
|
||||
printer: Printer,
|
||||
options: ToolOptions,
|
||||
force: bool,
|
||||
python: Option<String>,
|
||||
requirements: Vec<Requirement>,
|
||||
action: InstallAction,
|
||||
printer: Printer,
|
||||
) -> anyhow::Result<ExitStatus> {
|
||||
let site_packages = SitePackages::from_environment(environment)?;
|
||||
let installed = site_packages.get_packages(name);
|
||||
|
|
@ -199,6 +201,7 @@ pub(crate) fn install_executables(
|
|||
target_entry_points
|
||||
.into_iter()
|
||||
.map(|(name, _, target_path)| ToolEntrypoint::new(name, target_path)),
|
||||
options,
|
||||
);
|
||||
installed_tools.add_tool_receipt(name, tool)?;
|
||||
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ use uv_python::{
|
|||
EnvironmentPreference, PythonDownloads, PythonInstallation, PythonPreference, PythonRequest,
|
||||
};
|
||||
use uv_requirements::{RequirementsSource, RequirementsSpecification};
|
||||
use uv_settings::{ResolverInstallerOptions, ToolOptions};
|
||||
use uv_tool::InstalledTools;
|
||||
use uv_warnings::{warn_user, warn_user_once};
|
||||
|
||||
|
|
@ -37,6 +38,7 @@ pub(crate) async fn install(
|
|||
with: &[RequirementsSource],
|
||||
python: Option<String>,
|
||||
force: bool,
|
||||
options: ResolverInstallerOptions,
|
||||
settings: ResolverInstallerSettings,
|
||||
preview: PreviewMode,
|
||||
python_preference: PythonPreference,
|
||||
|
|
@ -75,6 +77,7 @@ pub(crate) async fn install(
|
|||
|
||||
// Initialize any shared state.
|
||||
let state = SharedState::default();
|
||||
|
||||
let client_builder = BaseClientBuilder::new()
|
||||
.connectivity(connectivity)
|
||||
.native_tls(native_tls);
|
||||
|
|
@ -177,6 +180,9 @@ pub(crate) async fn install(
|
|||
requirements
|
||||
};
|
||||
|
||||
// Convert to tool options.
|
||||
let options = ToolOptions::from(options);
|
||||
|
||||
let installed_tools = InstalledTools::from_settings()?.init()?;
|
||||
let _lock = installed_tools.acquire_lock()?;
|
||||
|
||||
|
|
@ -236,12 +242,21 @@ pub(crate) async fn install(
|
|||
if requirements == receipt {
|
||||
// And the user didn't request a reinstall or upgrade...
|
||||
if !force && settings.reinstall.is_none() && settings.upgrade.is_none() {
|
||||
// We're done.
|
||||
if *tool_receipt.options() != options {
|
||||
// ...but the options differ, we need to update the receipt.
|
||||
installed_tools.add_tool_receipt(
|
||||
&from.name,
|
||||
tool_receipt.clone().with_options(options),
|
||||
)?;
|
||||
}
|
||||
|
||||
// We're done, though we might need to update the receipt.
|
||||
writeln!(
|
||||
printer.stderr(),
|
||||
"`{from}` is already installed",
|
||||
from = from.cyan()
|
||||
)?;
|
||||
|
||||
return Ok(ExitStatus::Success);
|
||||
}
|
||||
}
|
||||
|
|
@ -333,10 +348,11 @@ pub(crate) async fn install(
|
|||
&environment,
|
||||
&from.name,
|
||||
&installed_tools,
|
||||
printer,
|
||||
options,
|
||||
force || invalid_tool_receipt,
|
||||
python,
|
||||
requirements,
|
||||
InstallAction::Install,
|
||||
printer,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ use uv_client::Connectivity;
|
|||
use uv_configuration::{Concurrency, PreviewMode};
|
||||
use uv_normalize::PackageName;
|
||||
use uv_requirements::RequirementsSpecification;
|
||||
use uv_settings::{Combine, ResolverInstallerOptions, ToolOptions};
|
||||
use uv_tool::InstalledTools;
|
||||
use uv_warnings::warn_user_once;
|
||||
|
||||
|
|
@ -22,7 +23,8 @@ use uv_warnings::warn_user_once;
|
|||
pub(crate) async fn upgrade(
|
||||
name: Option<PackageName>,
|
||||
connectivity: Connectivity,
|
||||
settings: ResolverInstallerSettings,
|
||||
args: ResolverInstallerOptions,
|
||||
filesystem: ResolverInstallerOptions,
|
||||
concurrency: Concurrency,
|
||||
native_tls: bool,
|
||||
cache: &Cache,
|
||||
|
|
@ -107,14 +109,19 @@ pub(crate) async fn upgrade(
|
|||
}
|
||||
};
|
||||
|
||||
// Resolve the appropriate settings, preferring: CLI > receipt > user.
|
||||
let options = args.clone().combine(
|
||||
ResolverInstallerOptions::from(existing_tool_receipt.options().clone())
|
||||
.combine(filesystem.clone()),
|
||||
);
|
||||
let settings = ResolverInstallerSettings::from(options.clone());
|
||||
|
||||
// Resolve the requirements.
|
||||
let requirements = existing_tool_receipt.requirements();
|
||||
let spec = RequirementsSpecification::from_requirements(requirements.to_vec());
|
||||
|
||||
// TODO(zanieb): Build the environment in the cache directory then copy into the tool directory.
|
||||
// This lets us confirm the environment is valid before removing an existing install. However,
|
||||
// entrypoints always contain an absolute path to the relevant Python interpreter, which would
|
||||
// be invalidated by moving the environment.
|
||||
// TODO(zanieb): Build the environment in the cache directory then copy into the tool
|
||||
// directory.
|
||||
let environment = update_environment(
|
||||
existing_environment,
|
||||
spec,
|
||||
|
|
@ -139,11 +146,12 @@ pub(crate) async fn upgrade(
|
|||
&environment,
|
||||
&name,
|
||||
&installed_tools,
|
||||
printer,
|
||||
ToolOptions::from(options),
|
||||
true,
|
||||
existing_tool_receipt.python().to_owned(),
|
||||
requirements.to_vec(),
|
||||
InstallAction::Update,
|
||||
printer,
|
||||
)?;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ use owo_colors::OwoColorize;
|
|||
use tracing::{debug, instrument};
|
||||
|
||||
use settings::PipTreeSettings;
|
||||
use uv_cache::{Cache, Refresh};
|
||||
use uv_cache::{Cache, Refresh, Timestamp};
|
||||
use uv_cli::{
|
||||
compat::CompatArgs, CacheCommand, CacheNamespace, Cli, Commands, PipCommand, PipNamespace,
|
||||
ProjectCommand,
|
||||
|
|
@ -780,6 +780,7 @@ async fn run(cli: Cli) -> Result<ExitStatus> {
|
|||
&requirements,
|
||||
args.python,
|
||||
args.force,
|
||||
args.options,
|
||||
args.settings,
|
||||
globals.preview,
|
||||
globals.python_preference,
|
||||
|
|
@ -812,12 +813,13 @@ async fn run(cli: Cli) -> Result<ExitStatus> {
|
|||
show_settings!(args);
|
||||
|
||||
// Initialize the cache.
|
||||
let cache = cache.init()?.with_refresh(args.refresh);
|
||||
let cache = cache.init()?.with_refresh(Refresh::All(Timestamp::now()));
|
||||
|
||||
commands::tool_upgrade(
|
||||
args.name,
|
||||
globals.connectivity,
|
||||
args.settings,
|
||||
args.args,
|
||||
args.filesystem,
|
||||
Concurrency::default(),
|
||||
globals.native_tls,
|
||||
&cache,
|
||||
|
|
|
|||
|
|
@ -320,6 +320,7 @@ pub(crate) struct ToolInstallSettings {
|
|||
pub(crate) with_requirements: Vec<PathBuf>,
|
||||
pub(crate) python: Option<String>,
|
||||
pub(crate) refresh: Refresh,
|
||||
pub(crate) options: ResolverInstallerOptions,
|
||||
pub(crate) settings: ResolverInstallerSettings,
|
||||
pub(crate) force: bool,
|
||||
pub(crate) editable: bool,
|
||||
|
|
@ -342,6 +343,15 @@ impl ToolInstallSettings {
|
|||
python,
|
||||
} = args;
|
||||
|
||||
let options = resolver_installer_options(installer, build).combine(
|
||||
filesystem
|
||||
.map(FilesystemOptions::into_options)
|
||||
.map(|options| options.top_level)
|
||||
.unwrap_or_default(),
|
||||
);
|
||||
|
||||
let settings = ResolverInstallerSettings::from(options.clone());
|
||||
|
||||
Self {
|
||||
package,
|
||||
from,
|
||||
|
|
@ -354,10 +364,50 @@ impl ToolInstallSettings {
|
|||
force,
|
||||
editable,
|
||||
refresh: Refresh::from(refresh),
|
||||
settings: ResolverInstallerSettings::combine(
|
||||
resolver_installer_options(installer, build),
|
||||
filesystem,
|
||||
),
|
||||
options,
|
||||
settings,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The resolved settings to use for a `tool upgrade` invocation.
|
||||
#[allow(clippy::struct_excessive_bools)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct ToolUpgradeSettings {
|
||||
pub(crate) name: Option<PackageName>,
|
||||
pub(crate) args: ResolverInstallerOptions,
|
||||
pub(crate) filesystem: ResolverInstallerOptions,
|
||||
}
|
||||
|
||||
impl ToolUpgradeSettings {
|
||||
/// Resolve the [`ToolUpgradeSettings`] from the CLI and filesystem configuration.
|
||||
#[allow(clippy::needless_pass_by_value)]
|
||||
pub(crate) fn resolve(args: ToolUpgradeArgs, filesystem: Option<FilesystemOptions>) -> Self {
|
||||
let ToolUpgradeArgs {
|
||||
name,
|
||||
all,
|
||||
mut installer,
|
||||
build,
|
||||
} = args;
|
||||
|
||||
if installer.upgrade {
|
||||
// If `--upgrade` was passed explicitly, warn.
|
||||
warn_user_once!("`--upgrade` is enabled by default on `uv tool upgrade`");
|
||||
} else if installer.upgrade_package.is_empty() {
|
||||
// If neither `--upgrade` nor `--upgrade-package` were passed in, assume `--upgrade`.
|
||||
installer.upgrade = true;
|
||||
}
|
||||
|
||||
let args = resolver_installer_options(installer, build);
|
||||
let filesystem = filesystem
|
||||
.map(FilesystemOptions::into_options)
|
||||
.map(|options| options.top_level)
|
||||
.unwrap_or_default();
|
||||
|
||||
Self {
|
||||
name: name.filter(|_| !all),
|
||||
args,
|
||||
filesystem,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -379,46 +429,6 @@ impl ToolListSettings {
|
|||
}
|
||||
}
|
||||
|
||||
/// The resolved settings to use for a `tool upgrade` invocation.
|
||||
#[allow(clippy::struct_excessive_bools)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct ToolUpgradeSettings {
|
||||
pub(crate) name: Option<PackageName>,
|
||||
pub(crate) settings: ResolverInstallerSettings,
|
||||
pub(crate) refresh: Refresh,
|
||||
}
|
||||
|
||||
impl ToolUpgradeSettings {
|
||||
/// Resolve the [`ToolUpgradeSettings`] from the CLI and filesystem configuration.
|
||||
#[allow(clippy::needless_pass_by_value)]
|
||||
pub(crate) fn resolve(args: ToolUpgradeArgs, filesystem: Option<FilesystemOptions>) -> Self {
|
||||
let ToolUpgradeArgs {
|
||||
name,
|
||||
all,
|
||||
mut installer,
|
||||
build,
|
||||
refresh,
|
||||
} = args;
|
||||
|
||||
if installer.upgrade {
|
||||
// If `--upgrade` was passed explicitly, warn.
|
||||
warn_user_once!("`--upgrade` is enabled by default on `uv tool upgrade`");
|
||||
} else if installer.upgrade_package.is_empty() {
|
||||
// If neither `--upgrade` nor `--upgrade-package` were passed in, assume `--upgrade`.
|
||||
installer.upgrade = true;
|
||||
}
|
||||
|
||||
Self {
|
||||
name: name.filter(|_| !all),
|
||||
settings: ResolverInstallerSettings::combine(
|
||||
resolver_installer_options(installer, build),
|
||||
filesystem,
|
||||
),
|
||||
refresh: Refresh::from(refresh),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The resolved settings to use for a `tool uninstall` invocation.
|
||||
#[allow(clippy::struct_excessive_bools)]
|
||||
#[derive(Debug, Clone)]
|
||||
|
|
@ -1668,31 +1678,6 @@ impl From<ResolverOptions> for ResolverSettings {
|
|||
}
|
||||
}
|
||||
|
||||
/// The resolved settings to use for an invocation of the uv CLI with both resolver and installer
|
||||
/// capabilities.
|
||||
///
|
||||
/// Represents the shared settings that are used across all uv commands outside the `pip` API.
|
||||
/// Analogous to the settings contained in the `[tool.uv]` table, combined with [`ResolverInstallerArgs`].
|
||||
#[allow(clippy::struct_excessive_bools)]
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub(crate) struct ResolverInstallerSettings {
|
||||
pub(crate) index_locations: IndexLocations,
|
||||
pub(crate) index_strategy: IndexStrategy,
|
||||
pub(crate) keyring_provider: KeyringProviderType,
|
||||
pub(crate) resolution: ResolutionMode,
|
||||
pub(crate) prerelease: PrereleaseMode,
|
||||
pub(crate) config_setting: ConfigSettings,
|
||||
pub(crate) no_build_isolation: bool,
|
||||
pub(crate) no_build_isolation_package: Vec<PackageName>,
|
||||
pub(crate) exclude_newer: Option<ExcludeNewer>,
|
||||
pub(crate) link_mode: LinkMode,
|
||||
pub(crate) compile_bytecode: bool,
|
||||
pub(crate) sources: SourceStrategy,
|
||||
pub(crate) upgrade: Upgrade,
|
||||
pub(crate) reinstall: Reinstall,
|
||||
pub(crate) build_options: BuildOptions,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct ResolverInstallerSettingsRef<'a> {
|
||||
pub(crate) index_locations: &'a IndexLocations,
|
||||
|
|
@ -1712,6 +1697,32 @@ pub(crate) struct ResolverInstallerSettingsRef<'a> {
|
|||
pub(crate) build_options: &'a BuildOptions,
|
||||
}
|
||||
|
||||
/// The resolved settings to use for an invocation of the uv CLI with both resolver and installer
|
||||
/// capabilities.
|
||||
///
|
||||
/// Represents the shared settings that are used across all uv commands outside the `pip` API.
|
||||
/// Analogous to the settings contained in the `[tool.uv]` table, combined with [`ResolverInstallerArgs`].
|
||||
#[allow(clippy::struct_excessive_bools)]
|
||||
#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
|
||||
#[serde(rename_all = "kebab-case", deny_unknown_fields)]
|
||||
pub(crate) struct ResolverInstallerSettings {
|
||||
pub(crate) index_locations: IndexLocations,
|
||||
pub(crate) index_strategy: IndexStrategy,
|
||||
pub(crate) keyring_provider: KeyringProviderType,
|
||||
pub(crate) resolution: ResolutionMode,
|
||||
pub(crate) prerelease: PrereleaseMode,
|
||||
pub(crate) config_setting: ConfigSettings,
|
||||
pub(crate) no_build_isolation: bool,
|
||||
pub(crate) no_build_isolation_package: Vec<PackageName>,
|
||||
pub(crate) exclude_newer: Option<ExcludeNewer>,
|
||||
pub(crate) link_mode: LinkMode,
|
||||
pub(crate) compile_bytecode: bool,
|
||||
pub(crate) sources: SourceStrategy,
|
||||
pub(crate) upgrade: Upgrade,
|
||||
pub(crate) reinstall: Reinstall,
|
||||
pub(crate) build_options: BuildOptions,
|
||||
}
|
||||
|
||||
impl ResolverInstallerSettings {
|
||||
/// Reconcile the [`ResolverInstallerSettings`] from the CLI and filesystem configuration.
|
||||
pub(crate) fn combine(
|
||||
|
|
|
|||
|
|
@ -2444,6 +2444,39 @@ fn resolve_tool() -> anyhow::Result<()> {
|
|||
},
|
||||
),
|
||||
),
|
||||
options: ResolverInstallerOptions {
|
||||
index_url: None,
|
||||
extra_index_url: None,
|
||||
no_index: None,
|
||||
find_links: None,
|
||||
index_strategy: None,
|
||||
keyring_provider: None,
|
||||
resolution: Some(
|
||||
LowestDirect,
|
||||
),
|
||||
prerelease: None,
|
||||
config_settings: None,
|
||||
no_build_isolation: None,
|
||||
no_build_isolation_package: None,
|
||||
exclude_newer: Some(
|
||||
ExcludeNewer(
|
||||
2024-03-25T00:00:00Z,
|
||||
),
|
||||
),
|
||||
link_mode: Some(
|
||||
Clone,
|
||||
),
|
||||
compile_bytecode: None,
|
||||
no_sources: None,
|
||||
upgrade: None,
|
||||
upgrade_package: None,
|
||||
reinstall: None,
|
||||
reinstall_package: None,
|
||||
no_build: None,
|
||||
no_build_package: None,
|
||||
no_binary: None,
|
||||
no_binary_package: None,
|
||||
},
|
||||
settings: ResolverInstallerSettings {
|
||||
index_locations: IndexLocations {
|
||||
index: None,
|
||||
|
|
|
|||
|
|
@ -87,6 +87,9 @@ fn tool_install() {
|
|||
{ name = "black", install-path = "[TEMP_DIR]/bin/black" },
|
||||
{ name = "blackd", install-path = "[TEMP_DIR]/bin/blackd" },
|
||||
]
|
||||
|
||||
[tool.options]
|
||||
exclude-newer = "2024-03-25T00:00:00Z"
|
||||
"###);
|
||||
});
|
||||
|
||||
|
|
@ -166,6 +169,9 @@ fn tool_install() {
|
|||
entrypoints = [
|
||||
{ name = "flask", install-path = "[TEMP_DIR]/bin/flask" },
|
||||
]
|
||||
|
||||
[tool.options]
|
||||
exclude-newer = "2024-03-25T00:00:00Z"
|
||||
"###);
|
||||
});
|
||||
}
|
||||
|
|
@ -304,6 +310,9 @@ fn tool_install_version() {
|
|||
{ name = "black", install-path = "[TEMP_DIR]/bin/black" },
|
||||
{ name = "blackd", install-path = "[TEMP_DIR]/bin/blackd" },
|
||||
]
|
||||
|
||||
[tool.options]
|
||||
exclude-newer = "2024-03-25T00:00:00Z"
|
||||
"###);
|
||||
});
|
||||
|
||||
|
|
@ -383,6 +392,9 @@ fn tool_install_editable() {
|
|||
entrypoints = [
|
||||
{ name = "black", install-path = "[TEMP_DIR]/bin/black" },
|
||||
]
|
||||
|
||||
[tool.options]
|
||||
exclude-newer = "2024-03-25T00:00:00Z"
|
||||
"###);
|
||||
});
|
||||
|
||||
|
|
@ -420,6 +432,9 @@ fn tool_install_editable() {
|
|||
entrypoints = [
|
||||
{ name = "black", install-path = "[TEMP_DIR]/bin/black" },
|
||||
]
|
||||
|
||||
[tool.options]
|
||||
exclude-newer = "2024-03-25T00:00:00Z"
|
||||
"###);
|
||||
});
|
||||
|
||||
|
|
@ -462,6 +477,9 @@ fn tool_install_editable() {
|
|||
{ name = "black", install-path = "[TEMP_DIR]/bin/black" },
|
||||
{ name = "blackd", install-path = "[TEMP_DIR]/bin/blackd" },
|
||||
]
|
||||
|
||||
[tool.options]
|
||||
exclude-newer = "2024-03-25T00:00:00Z"
|
||||
"###);
|
||||
});
|
||||
}
|
||||
|
|
@ -508,6 +526,9 @@ fn tool_install_remove_on_empty() -> Result<()> {
|
|||
{ name = "black", install-path = "[TEMP_DIR]/bin/black" },
|
||||
{ name = "blackd", install-path = "[TEMP_DIR]/bin/blackd" },
|
||||
]
|
||||
|
||||
[tool.options]
|
||||
exclude-newer = "2024-03-25T00:00:00Z"
|
||||
"###);
|
||||
});
|
||||
|
||||
|
|
@ -597,6 +618,9 @@ fn tool_install_remove_on_empty() -> Result<()> {
|
|||
{ name = "black", install-path = "[TEMP_DIR]/bin/black" },
|
||||
{ name = "blackd", install-path = "[TEMP_DIR]/bin/blackd" },
|
||||
]
|
||||
|
||||
[tool.options]
|
||||
exclude-newer = "2024-03-25T00:00:00Z"
|
||||
"###);
|
||||
});
|
||||
|
||||
|
|
@ -670,6 +694,9 @@ fn tool_install_editable_from() {
|
|||
entrypoints = [
|
||||
{ name = "black", install-path = "[TEMP_DIR]/bin/black" },
|
||||
]
|
||||
|
||||
[tool.options]
|
||||
exclude-newer = "2024-03-25T00:00:00Z"
|
||||
"###);
|
||||
});
|
||||
|
||||
|
|
@ -822,6 +849,9 @@ fn tool_install_already_installed() {
|
|||
{ name = "black", install-path = "[TEMP_DIR]/bin/black" },
|
||||
{ name = "blackd", install-path = "[TEMP_DIR]/bin/blackd" },
|
||||
]
|
||||
|
||||
[tool.options]
|
||||
exclude-newer = "2024-03-25T00:00:00Z"
|
||||
"###);
|
||||
});
|
||||
|
||||
|
|
@ -856,6 +886,9 @@ fn tool_install_already_installed() {
|
|||
{ name = "black", install-path = "[TEMP_DIR]/bin/black" },
|
||||
{ name = "blackd", install-path = "[TEMP_DIR]/bin/blackd" },
|
||||
]
|
||||
|
||||
[tool.options]
|
||||
exclude-newer = "2024-03-25T00:00:00Z"
|
||||
"###);
|
||||
});
|
||||
|
||||
|
|
@ -1164,6 +1197,9 @@ fn tool_install_entry_point_exists() {
|
|||
{ name = "black", install-path = "[TEMP_DIR]/bin/black" },
|
||||
{ name = "blackd", install-path = "[TEMP_DIR]/bin/blackd" },
|
||||
]
|
||||
|
||||
[tool.options]
|
||||
exclude-newer = "2024-03-25T00:00:00Z"
|
||||
"###);
|
||||
});
|
||||
|
||||
|
|
@ -1197,6 +1233,9 @@ fn tool_install_entry_point_exists() {
|
|||
{ name = "black", install-path = "[TEMP_DIR]/bin/black" },
|
||||
{ name = "blackd", install-path = "[TEMP_DIR]/bin/blackd" },
|
||||
]
|
||||
|
||||
[tool.options]
|
||||
exclude-newer = "2024-03-25T00:00:00Z"
|
||||
"###);
|
||||
});
|
||||
|
||||
|
|
@ -1427,6 +1466,9 @@ fn tool_install_unnamed_package() {
|
|||
{ name = "black", install-path = "[TEMP_DIR]/bin/black" },
|
||||
{ name = "blackd", install-path = "[TEMP_DIR]/bin/blackd" },
|
||||
]
|
||||
|
||||
[tool.options]
|
||||
exclude-newer = "2024-03-25T00:00:00Z"
|
||||
"###);
|
||||
});
|
||||
|
||||
|
|
@ -1539,6 +1581,9 @@ fn tool_install_unnamed_from() {
|
|||
{ name = "black", install-path = "[TEMP_DIR]/bin/black" },
|
||||
{ name = "blackd", install-path = "[TEMP_DIR]/bin/blackd" },
|
||||
]
|
||||
|
||||
[tool.options]
|
||||
exclude-newer = "2024-03-25T00:00:00Z"
|
||||
"###);
|
||||
});
|
||||
|
||||
|
|
@ -1629,6 +1674,9 @@ fn tool_install_unnamed_with() {
|
|||
{ name = "black", install-path = "[TEMP_DIR]/bin/black" },
|
||||
{ name = "blackd", install-path = "[TEMP_DIR]/bin/blackd" },
|
||||
]
|
||||
|
||||
[tool.options]
|
||||
exclude-newer = "2024-03-25T00:00:00Z"
|
||||
"###);
|
||||
});
|
||||
|
||||
|
|
@ -1696,6 +1744,9 @@ fn tool_install_requirements_txt() {
|
|||
{ name = "black", install-path = "[TEMP_DIR]/bin/black" },
|
||||
{ name = "blackd", install-path = "[TEMP_DIR]/bin/blackd" },
|
||||
]
|
||||
|
||||
[tool.options]
|
||||
exclude-newer = "2024-03-25T00:00:00Z"
|
||||
"###);
|
||||
});
|
||||
|
||||
|
|
@ -1739,6 +1790,9 @@ fn tool_install_requirements_txt() {
|
|||
{ name = "black", install-path = "[TEMP_DIR]/bin/black" },
|
||||
{ name = "blackd", install-path = "[TEMP_DIR]/bin/blackd" },
|
||||
]
|
||||
|
||||
[tool.options]
|
||||
exclude-newer = "2024-03-25T00:00:00Z"
|
||||
"###);
|
||||
});
|
||||
}
|
||||
|
|
@ -1801,6 +1855,9 @@ fn tool_install_requirements_txt_arguments() {
|
|||
{ name = "black", install-path = "[TEMP_DIR]/bin/black" },
|
||||
{ name = "blackd", install-path = "[TEMP_DIR]/bin/blackd" },
|
||||
]
|
||||
|
||||
[tool.options]
|
||||
exclude-newer = "2024-03-25T00:00:00Z"
|
||||
"###);
|
||||
});
|
||||
|
||||
|
|
@ -1915,6 +1972,9 @@ fn tool_install_upgrade() {
|
|||
{ name = "black", install-path = "[TEMP_DIR]/bin/black" },
|
||||
{ name = "blackd", install-path = "[TEMP_DIR]/bin/blackd" },
|
||||
]
|
||||
|
||||
[tool.options]
|
||||
exclude-newer = "2024-03-25T00:00:00Z"
|
||||
"###);
|
||||
});
|
||||
|
||||
|
|
@ -1945,6 +2005,9 @@ fn tool_install_upgrade() {
|
|||
{ name = "black", install-path = "[TEMP_DIR]/bin/black" },
|
||||
{ name = "blackd", install-path = "[TEMP_DIR]/bin/blackd" },
|
||||
]
|
||||
|
||||
[tool.options]
|
||||
exclude-newer = "2024-03-25T00:00:00Z"
|
||||
"###);
|
||||
});
|
||||
|
||||
|
|
@ -1983,6 +2046,9 @@ fn tool_install_upgrade() {
|
|||
{ name = "black", install-path = "[TEMP_DIR]/bin/black" },
|
||||
{ name = "blackd", install-path = "[TEMP_DIR]/bin/blackd" },
|
||||
]
|
||||
|
||||
[tool.options]
|
||||
exclude-newer = "2024-03-25T00:00:00Z"
|
||||
"###);
|
||||
});
|
||||
|
||||
|
|
@ -2021,6 +2087,9 @@ fn tool_install_upgrade() {
|
|||
{ name = "black", install-path = "[TEMP_DIR]/bin/black" },
|
||||
{ name = "blackd", install-path = "[TEMP_DIR]/bin/blackd" },
|
||||
]
|
||||
|
||||
[tool.options]
|
||||
exclude-newer = "2024-03-25T00:00:00Z"
|
||||
"###);
|
||||
});
|
||||
}
|
||||
|
|
@ -2282,14 +2351,14 @@ fn tool_install_bad_receipt() -> Result<()> {
|
|||
/// Test installing a tool with a malformed `.dist-info` directory (i.e., a `.dist-info` directory
|
||||
/// that isn't properly normalized).
|
||||
#[test]
|
||||
fn tool_install_malformed() {
|
||||
fn tool_install_malformed_dist_info() {
|
||||
let context = TestContext::new("3.12")
|
||||
.with_filtered_counts()
|
||||
.with_filtered_exe_suffix();
|
||||
let tool_dir = context.temp_dir.child("tools");
|
||||
let bin_dir = context.temp_dir.child("bin");
|
||||
|
||||
// Install `black`
|
||||
// Install `babel`
|
||||
uv_snapshot!(context.filters(), context.tool_install()
|
||||
.arg("babel")
|
||||
.env("UV_TOOL_DIR", tool_dir.as_os_str())
|
||||
|
|
@ -2346,6 +2415,164 @@ fn tool_install_malformed() {
|
|||
entrypoints = [
|
||||
{ name = "pybabel", install-path = "[TEMP_DIR]/bin/pybabel" },
|
||||
]
|
||||
|
||||
[tool.options]
|
||||
exclude-newer = "2024-03-25T00:00:00Z"
|
||||
"###);
|
||||
});
|
||||
}
|
||||
|
||||
/// Test installing, then re-installing with different settings.
|
||||
#[test]
|
||||
fn tool_install_settings() {
|
||||
let context = TestContext::new("3.12")
|
||||
.with_filtered_counts()
|
||||
.with_filtered_exe_suffix();
|
||||
let tool_dir = context.temp_dir.child("tools");
|
||||
let bin_dir = context.temp_dir.child("bin");
|
||||
|
||||
// Install `black`
|
||||
uv_snapshot!(context.filters(), context.tool_install()
|
||||
.arg("flask>=3")
|
||||
.arg("--resolution=lowest-direct")
|
||||
.env("UV_TOOL_DIR", tool_dir.as_os_str())
|
||||
.env("XDG_BIN_HOME", bin_dir.as_os_str())
|
||||
.env("PATH", bin_dir.as_os_str()), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
warning: `uv tool install` is experimental and may change without warning
|
||||
Resolved [N] packages in [TIME]
|
||||
Prepared [N] packages in [TIME]
|
||||
Installed [N] packages in [TIME]
|
||||
+ blinker==1.7.0
|
||||
+ click==8.1.7
|
||||
+ flask==3.0.0
|
||||
+ itsdangerous==2.1.2
|
||||
+ jinja2==3.1.3
|
||||
+ markupsafe==2.1.5
|
||||
+ werkzeug==3.0.1
|
||||
Installed 1 executable: flask
|
||||
"###);
|
||||
|
||||
tool_dir.child("flask").assert(predicate::path::is_dir());
|
||||
tool_dir
|
||||
.child("flask")
|
||||
.child("uv-receipt.toml")
|
||||
.assert(predicate::path::exists());
|
||||
|
||||
let executable = bin_dir.child(format!("flask{}", std::env::consts::EXE_SUFFIX));
|
||||
assert!(executable.exists());
|
||||
|
||||
// On Windows, we can't snapshot an executable file.
|
||||
#[cfg(not(windows))]
|
||||
insta::with_settings!({
|
||||
filters => context.filters(),
|
||||
}, {
|
||||
assert_snapshot!(fs_err::read_to_string(executable).unwrap(), @r###"
|
||||
#![TEMP_DIR]/tools/flask/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import sys
|
||||
from flask.cli import main
|
||||
if __name__ == "__main__":
|
||||
sys.argv[0] = re.sub(r"(-script\.pyw|\.exe)?$", "", sys.argv[0])
|
||||
sys.exit(main())
|
||||
"###);
|
||||
|
||||
});
|
||||
|
||||
insta::with_settings!({
|
||||
filters => context.filters(),
|
||||
}, {
|
||||
// We should have a tool receipt
|
||||
assert_snapshot!(fs_err::read_to_string(tool_dir.join("flask").join("uv-receipt.toml")).unwrap(), @r###"
|
||||
[tool]
|
||||
requirements = [{ name = "flask", specifier = ">=3" }]
|
||||
entrypoints = [
|
||||
{ name = "flask", install-path = "[TEMP_DIR]/bin/flask" },
|
||||
]
|
||||
|
||||
[tool.options]
|
||||
resolution = "lowest-direct"
|
||||
exclude-newer = "2024-03-25T00:00:00Z"
|
||||
"###);
|
||||
});
|
||||
|
||||
// Reinstall with `highest`. This is a no-op, since we _do_ have a compatible version installed.
|
||||
uv_snapshot!(context.filters(), context.tool_install()
|
||||
.arg("flask>=3")
|
||||
.arg("--resolution=highest")
|
||||
.env("UV_TOOL_DIR", tool_dir.as_os_str())
|
||||
.env("XDG_BIN_HOME", bin_dir.as_os_str())
|
||||
.env("PATH", bin_dir.as_os_str()), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
warning: `uv tool install` is experimental and may change without warning
|
||||
`flask>=3` is already installed
|
||||
"###);
|
||||
|
||||
// It should update the receipt though.
|
||||
insta::with_settings!({
|
||||
filters => context.filters(),
|
||||
}, {
|
||||
// We should have a tool receipt
|
||||
assert_snapshot!(fs_err::read_to_string(tool_dir.join("flask").join("uv-receipt.toml")).unwrap(), @r###"
|
||||
[tool]
|
||||
requirements = [{ name = "flask", specifier = ">=3" }]
|
||||
entrypoints = [
|
||||
{ name = "flask", install-path = "[TEMP_DIR]/bin/flask" },
|
||||
]
|
||||
|
||||
[tool.options]
|
||||
resolution = "highest"
|
||||
exclude-newer = "2024-03-25T00:00:00Z"
|
||||
"###);
|
||||
});
|
||||
|
||||
// Reinstall with `highest` and `--upgrade`. This should change the setting and install a higher
|
||||
// version.
|
||||
uv_snapshot!(context.filters(), context.tool_install()
|
||||
.arg("flask>=3")
|
||||
.arg("--resolution=highest")
|
||||
.arg("--upgrade")
|
||||
.env("UV_TOOL_DIR", tool_dir.as_os_str())
|
||||
.env("XDG_BIN_HOME", bin_dir.as_os_str())
|
||||
.env("PATH", bin_dir.as_os_str()), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
warning: `uv tool install` is experimental and may change without warning
|
||||
Resolved [N] packages in [TIME]
|
||||
Prepared [N] packages in [TIME]
|
||||
Uninstalled [N] packages in [TIME]
|
||||
Installed [N] packages in [TIME]
|
||||
- flask==3.0.0
|
||||
+ flask==3.0.2
|
||||
Installed 1 executable: flask
|
||||
"###);
|
||||
|
||||
insta::with_settings!({
|
||||
filters => context.filters(),
|
||||
}, {
|
||||
// We should have a tool receipt
|
||||
assert_snapshot!(fs_err::read_to_string(tool_dir.join("flask").join("uv-receipt.toml")).unwrap(), @r###"
|
||||
[tool]
|
||||
requirements = [{ name = "flask", specifier = ">=3" }]
|
||||
entrypoints = [
|
||||
{ name = "flask", install-path = "[TEMP_DIR]/bin/flask" },
|
||||
]
|
||||
|
||||
[tool.options]
|
||||
resolution = "highest"
|
||||
exclude-newer = "2024-03-25T00:00:00Z"
|
||||
"###);
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -197,6 +197,9 @@ fn tool_list_deprecated() -> Result<()> {
|
|||
{ name = "black", install-path = "[TEMP_DIR]/bin/black" },
|
||||
{ name = "blackd", install-path = "[TEMP_DIR]/bin/blackd" },
|
||||
]
|
||||
|
||||
[tool.options]
|
||||
exclude-newer = "2024-03-25T00:00:00Z"
|
||||
"###);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -203,8 +203,7 @@ fn test_tool_upgrade_settings() {
|
|||
Installed 2 executables: black, blackd
|
||||
"###);
|
||||
|
||||
// Upgrade `black`. It should respect `lowest-direct`, but doesn't right now, so it's
|
||||
// unintentionally upgraded.
|
||||
// Upgrade `black`. This should be a no-op, since the resolution is set to `lowest-direct`.
|
||||
uv_snapshot!(context.filters(), context.tool_upgrade()
|
||||
.arg("black")
|
||||
.env("UV_TOOL_DIR", tool_dir.as_os_str())
|
||||
|
|
@ -214,6 +213,24 @@ fn test_tool_upgrade_settings() {
|
|||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
warning: `uv tool upgrade` is experimental and may change without warning
|
||||
Resolved [N] packages in [TIME]
|
||||
Audited [N] packages in [TIME]
|
||||
Updated 2 executables: black, blackd
|
||||
"###);
|
||||
|
||||
// Upgrade `black`, but override the resolution.
|
||||
uv_snapshot!(context.filters(), context.tool_upgrade()
|
||||
.arg("black")
|
||||
.arg("--resolution=highest")
|
||||
.env("UV_TOOL_DIR", tool_dir.as_os_str())
|
||||
.env("XDG_BIN_HOME", bin_dir.as_os_str())
|
||||
.env("PATH", bin_dir.as_os_str()), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
warning: `uv tool upgrade` is experimental and may change without warning
|
||||
Resolved [N] packages in [TIME]
|
||||
|
|
|
|||
|
|
@ -2426,10 +2426,6 @@ uv tool upgrade [OPTIONS] <NAME>
|
|||
</ul>
|
||||
</dd><dt><code>--quiet</code>, <code>-q</code></dt><dd><p>Do not print any output</p>
|
||||
|
||||
</dd><dt><code>--refresh</code></dt><dd><p>Refresh all cached data</p>
|
||||
|
||||
</dd><dt><code>--refresh-package</code> <i>refresh-package</i></dt><dd><p>Refresh cached data for a specific package</p>
|
||||
|
||||
</dd><dt><code>--reinstall</code></dt><dd><p>Reinstall all packages, regardless of whether they’re already installed. Implies <code>--refresh</code></p>
|
||||
|
||||
</dd><dt><code>--reinstall-package</code> <i>reinstall-package</i></dt><dd><p>Reinstall a specific package, regardless of whether it’s already installed. Implies <code>--refresh-package</code></p>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue