mirror of
https://github.com/astral-sh/uv.git
synced 2025-07-07 21:35:00 +00:00
Resolve requirements prior to nuking tool environments (#4788)
## Summary Closes #4747.
This commit is contained in:
parent
d178d97a40
commit
c6f72d333a
2 changed files with 114 additions and 21 deletions
|
@ -24,7 +24,7 @@ use uv_requirements::RequirementsSpecification;
|
||||||
use uv_tool::{entrypoint_paths, find_executable_directory, InstalledTools, Tool, ToolEntrypoint};
|
use uv_tool::{entrypoint_paths, find_executable_directory, InstalledTools, Tool, ToolEntrypoint};
|
||||||
use uv_warnings::warn_user_once;
|
use uv_warnings::warn_user_once;
|
||||||
|
|
||||||
use crate::commands::project::update_environment;
|
use crate::commands::project::{resolve_environment, sync_environment, update_environment};
|
||||||
use crate::commands::tool::common::resolve_requirements;
|
use crate::commands::tool::common::resolve_requirements;
|
||||||
use crate::commands::{ExitStatus, SharedState};
|
use crate::commands::{ExitStatus, SharedState};
|
||||||
use crate::printer::Printer;
|
use crate::printer::Printer;
|
||||||
|
@ -195,33 +195,62 @@ pub(crate) async fn install(
|
||||||
// entrypoints (without `--force`).
|
// entrypoints (without `--force`).
|
||||||
let reinstall_entry_points = existing_tool_receipt.is_some();
|
let reinstall_entry_points = existing_tool_receipt.is_some();
|
||||||
|
|
||||||
|
// Resolve the requirements.
|
||||||
|
let state = SharedState::default();
|
||||||
|
let spec = RequirementsSpecification::from_requirements(requirements.clone());
|
||||||
|
|
||||||
// TODO(zanieb): Build the environment in the cache directory then copy into the tool directory.
|
// 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,
|
// 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
|
// entrypoints always contain an absolute path to the relevant Python interpreter, which would
|
||||||
// be invalidated by moving the environment.
|
// be invalidated by moving the environment.
|
||||||
let environment = if let Some(environment) = existing_environment {
|
let environment = if let Some(environment) = existing_environment {
|
||||||
environment
|
update_environment(
|
||||||
|
environment,
|
||||||
|
spec,
|
||||||
|
&settings,
|
||||||
|
&state,
|
||||||
|
preview,
|
||||||
|
connectivity,
|
||||||
|
concurrency,
|
||||||
|
native_tls,
|
||||||
|
cache,
|
||||||
|
printer,
|
||||||
|
)
|
||||||
|
.await?
|
||||||
} else {
|
} else {
|
||||||
// TODO(charlie): Resolve, then create the environment, then install. This ensures that
|
// If we're creating a new environment, ensure that we can resolve the requirements prior
|
||||||
// we don't nuke the environment if the resolution fails.
|
// to removing any existing tools.
|
||||||
installed_tools.create_environment(&from.name, interpreter)?
|
let resolution = resolve_environment(
|
||||||
};
|
&interpreter,
|
||||||
|
spec,
|
||||||
|
settings.as_ref().into(),
|
||||||
|
&state,
|
||||||
|
preview,
|
||||||
|
connectivity,
|
||||||
|
concurrency,
|
||||||
|
native_tls,
|
||||||
|
cache,
|
||||||
|
printer,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
// Install the ephemeral requirements.
|
let environment = installed_tools.create_environment(&from.name, interpreter)?;
|
||||||
let spec = RequirementsSpecification::from_requirements(requirements.clone());
|
|
||||||
let environment = update_environment(
|
// Sync the environment with the resolved requirements.
|
||||||
environment,
|
sync_environment(
|
||||||
spec,
|
environment,
|
||||||
&settings,
|
&resolution.into(),
|
||||||
&state,
|
settings.as_ref().into(),
|
||||||
preview,
|
&state,
|
||||||
connectivity,
|
preview,
|
||||||
concurrency,
|
connectivity,
|
||||||
native_tls,
|
concurrency,
|
||||||
cache,
|
native_tls,
|
||||||
printer,
|
cache,
|
||||||
)
|
printer,
|
||||||
.await?;
|
)
|
||||||
|
.await?
|
||||||
|
};
|
||||||
|
|
||||||
let site_packages = SitePackages::from_environment(&environment)?;
|
let site_packages = SitePackages::from_environment(&environment)?;
|
||||||
let installed = site_packages.get_packages(&from.name);
|
let installed = site_packages.get_packages(&from.name);
|
||||||
|
|
|
@ -1398,3 +1398,67 @@ fn tool_install_python_request() {
|
||||||
Installed: black, blackd
|
Installed: black, blackd
|
||||||
"###);
|
"###);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Test preserving a tool environment when new but incompatible requirements are requested.
|
||||||
|
#[test]
|
||||||
|
fn tool_install_preserve_environment() {
|
||||||
|
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("black==24.1.1")
|
||||||
|
.env("UV_TOOL_DIR", tool_dir.as_os_str())
|
||||||
|
.env("XDG_BIN_HOME", 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]
|
||||||
|
+ black==24.1.1
|
||||||
|
+ click==8.1.7
|
||||||
|
+ mypy-extensions==1.0.0
|
||||||
|
+ packaging==24.0
|
||||||
|
+ pathspec==0.12.1
|
||||||
|
+ platformdirs==4.2.0
|
||||||
|
Installed: black, blackd
|
||||||
|
"###);
|
||||||
|
|
||||||
|
// Install `black`, but with an incompatible requirement.
|
||||||
|
uv_snapshot!(context.filters(), context.tool_install()
|
||||||
|
.arg("black==24.1.1")
|
||||||
|
.arg("--with")
|
||||||
|
.arg("packaging==0.0.1")
|
||||||
|
.env("UV_TOOL_DIR", tool_dir.as_os_str())
|
||||||
|
.env("XDG_BIN_HOME", bin_dir.as_os_str()), @r###"
|
||||||
|
success: false
|
||||||
|
exit_code: 2
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
warning: `uv tool install` is experimental and may change without warning.
|
||||||
|
error: Because black==24.1.1 depends on packaging>=22.0 and you require black==24.1.1, we can conclude that you require packaging>=22.0.
|
||||||
|
And because you require packaging==0.0.1, we can conclude that the requirements are unsatisfiable.
|
||||||
|
"###);
|
||||||
|
|
||||||
|
// Install `black`. The tool should already be installed, since we didn't remove the environment.
|
||||||
|
uv_snapshot!(context.filters(), context.tool_install()
|
||||||
|
.arg("black==24.1.1")
|
||||||
|
.env("UV_TOOL_DIR", tool_dir.as_os_str())
|
||||||
|
.env("XDG_BIN_HOME", bin_dir.as_os_str()), @r###"
|
||||||
|
success: false
|
||||||
|
exit_code: 1
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
warning: `uv tool install` is experimental and may change without warning.
|
||||||
|
Tool `black==24.1.1` is already installed
|
||||||
|
"###);
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue