mirror of
https://github.com/astral-sh/uv.git
synced 2025-08-04 10:58:28 +00:00
Respect locked script preferences in uv run --with
(#13283)
## Summary Part of https://github.com/astral-sh/uv/issues/13173, but doesn't close the issue. This just respects preferences if your script uses a lockfile, since we already support that for locked _projects_.
This commit is contained in:
parent
e2d105d045
commit
c12ce84fbd
2 changed files with 108 additions and 8 deletions
|
@ -31,7 +31,7 @@ use uv_python::{
|
|||
VersionFileDiscoveryOptions,
|
||||
};
|
||||
use uv_requirements::{RequirementsSource, RequirementsSpecification};
|
||||
use uv_resolver::Lock;
|
||||
use uv_resolver::{Installable, Lock};
|
||||
use uv_scripts::Pep723Item;
|
||||
use uv_settings::PythonInstallMirrors;
|
||||
use uv_shell::runnable::WindowsRunnable;
|
||||
|
@ -187,6 +187,9 @@ hint: If you are running a script with `{}` in the shebang, you may need to incl
|
|||
// Initialize any output reporters.
|
||||
let download_reporter = PythonDownloadReporter::single(printer);
|
||||
|
||||
// The lockfile used for the base environment.
|
||||
let mut base_lock: Option<(Lock, PathBuf)> = None;
|
||||
|
||||
// Determine whether the command to execute is a PEP 723 script.
|
||||
let temp_dir;
|
||||
let script_interpreter = if let Some(script) = script {
|
||||
|
@ -318,6 +321,10 @@ hint: If you are running a script with `{}` in the shebang, you may need to incl
|
|||
Err(err) => return Err(err.into()),
|
||||
}
|
||||
|
||||
// Respect any locked preferences when resolving `--with` dependencies downstream.
|
||||
let install_path = target.install_path().to_path_buf();
|
||||
base_lock = Some((lock, install_path));
|
||||
|
||||
Some(environment.into_interpreter())
|
||||
} else {
|
||||
// If no lockfile is found, warn against `--locked` and `--frozen`.
|
||||
|
@ -443,9 +450,6 @@ hint: If you are running a script with `{}` in the shebang, you may need to incl
|
|||
None
|
||||
};
|
||||
|
||||
// The lockfile used for the base environment.
|
||||
let mut lock: Option<(Lock, PathBuf)> = None;
|
||||
|
||||
// Discover and sync the base environment.
|
||||
let workspace_cache = WorkspaceCache::default();
|
||||
let temp_dir;
|
||||
|
@ -659,7 +663,7 @@ hint: If you are running a script with `{}` in the shebang, you may need to incl
|
|||
// If we're not syncing, we should still attempt to respect the locked preferences
|
||||
// in any `--with` requirements.
|
||||
if !isolated && !requirements.is_empty() {
|
||||
lock = LockTarget::from(project.workspace())
|
||||
base_lock = LockTarget::from(project.workspace())
|
||||
.read()
|
||||
.await
|
||||
.ok()
|
||||
|
@ -802,7 +806,7 @@ hint: If you are running a script with `{}` in the shebang, you may need to incl
|
|||
Err(err) => return Err(err.into()),
|
||||
}
|
||||
|
||||
lock = Some((
|
||||
base_lock = Some((
|
||||
result.into_lock(),
|
||||
project.workspace().install_path().to_owned(),
|
||||
));
|
||||
|
@ -901,13 +905,14 @@ hint: If you are running a script with `{}` in the shebang, you may need to incl
|
|||
debug!("Syncing ephemeral requirements");
|
||||
|
||||
// Read the build constraints from the lock file.
|
||||
let build_constraints = lock
|
||||
let build_constraints = base_lock
|
||||
.as_ref()
|
||||
.map(|(lock, path)| lock.build_constraints(path));
|
||||
|
||||
let result = CachedEnvironment::from_spec(
|
||||
EnvironmentSpecification::from(spec).with_lock(
|
||||
lock.as_ref()
|
||||
base_lock
|
||||
.as_ref()
|
||||
.map(|(lock, install_path)| (lock, install_path.as_ref())),
|
||||
),
|
||||
build_constraints.unwrap_or_default(),
|
||||
|
|
|
@ -4983,3 +4983,98 @@ fn run_windows_legacy_scripts() -> Result<()> {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// If a `--with` requirement overlaps with a locked script requirement, respect the lockfile as a
|
||||
/// preference.
|
||||
///
|
||||
/// See: <https://github.com/astral-sh/uv/issues/13173>
|
||||
#[test]
|
||||
fn run_pep723_script_with_constraints_lock() -> Result<()> {
|
||||
let context = TestContext::new("3.12");
|
||||
|
||||
let test_script = context.temp_dir.child("main.py");
|
||||
test_script.write_str(indoc! { r#"
|
||||
# /// script
|
||||
# requires-python = ">=3.11"
|
||||
# dependencies = [
|
||||
# "iniconfig<2",
|
||||
# ]
|
||||
# ///
|
||||
|
||||
import iniconfig
|
||||
|
||||
print("Hello, world!")
|
||||
"#
|
||||
})?;
|
||||
|
||||
// Explicitly lock the script.
|
||||
uv_snapshot!(context.filters(), context.lock().arg("--script").arg("main.py"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Resolved 1 package in [TIME]
|
||||
"###);
|
||||
|
||||
let lock = context.read("main.py.lock");
|
||||
|
||||
insta::with_settings!({
|
||||
filters => context.filters(),
|
||||
}, {
|
||||
assert_snapshot!(
|
||||
lock, @r#"
|
||||
version = 1
|
||||
revision = 2
|
||||
requires-python = ">=3.11"
|
||||
|
||||
[options]
|
||||
exclude-newer = "2024-03-25T00:00:00Z"
|
||||
|
||||
[manifest]
|
||||
requirements = [{ name = "iniconfig", specifier = "<2" }]
|
||||
|
||||
[[package]]
|
||||
name = "iniconfig"
|
||||
version = "1.1.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/23/a2/97899f6bd0e873fed3a7e67ae8d3a08b21799430fb4da15cfedf10d6e2c2/iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32", size = 8104, upload-time = "2020-10-14T10:20:18.572Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/9b/dd/b3c12c6d707058fa947864b67f0c4e0c39ef8610988d7baea9578f3c48f3/iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3", size = 4990, upload-time = "2020-10-16T17:37:23.05Z" },
|
||||
]
|
||||
"#
|
||||
);
|
||||
});
|
||||
|
||||
let pyproject_toml = context.temp_dir.child("pyproject.toml");
|
||||
pyproject_toml.write_str(indoc! { r#"
|
||||
[project]
|
||||
name = "foo"
|
||||
version = "1.0.0"
|
||||
requires-python = ">=3.10"
|
||||
dependencies = [
|
||||
"iniconfig",
|
||||
]
|
||||
"#
|
||||
})?;
|
||||
|
||||
uv_snapshot!(context.filters(), context.run().arg("--with").arg(".").arg("main.py"), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
Hello, world!
|
||||
|
||||
----- stderr -----
|
||||
Resolved 1 package in [TIME]
|
||||
Prepared 1 package in [TIME]
|
||||
Installed 1 package in [TIME]
|
||||
+ iniconfig==1.1.1
|
||||
Resolved 2 packages in [TIME]
|
||||
Prepared 1 package in [TIME]
|
||||
Installed 2 packages in [TIME]
|
||||
+ foo==1.0.0 (from file://[TEMP_DIR]/)
|
||||
+ iniconfig==1.1.1
|
||||
");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue