Only respect preferences across the same indexes (#9302)
Some checks are pending
CI / build binary | linux (push) Blocked by required conditions
CI / build binary | macos aarch64 (push) Blocked by required conditions
CI / build binary | macos x86_64 (push) Blocked by required conditions
CI / build binary | windows (push) Blocked by required conditions
CI / cargo build (msrv) (push) Blocked by required conditions
CI / build binary | freebsd (push) Blocked by required conditions
CI / check system | amazonlinux (push) Blocked by required conditions
CI / ecosystem test | prefecthq/prefect (push) Blocked by required conditions
CI / ecosystem test | pallets/flask (push) Blocked by required conditions
CI / integration test | conda on ubuntu (push) Blocked by required conditions
CI / integration test | free-threaded on linux (push) Blocked by required conditions
CI / integration test | free-threaded on windows (push) Blocked by required conditions
CI / integration test | pypy on ubuntu (push) Blocked by required conditions
CI / integration test | pypy on windows (push) Blocked by required conditions
CI / integration test | graalpy on ubuntu (push) Blocked by required conditions
CI / integration test | graalpy on windows (push) Blocked by required conditions
CI / integration test | github actions (push) Blocked by required conditions
CI / integration test | determine publish changes (push) Blocked by required conditions
CI / integration test | uv publish (push) Blocked by required conditions
CI / check cache | ubuntu (push) Blocked by required conditions
CI / check cache | macos aarch64 (push) Blocked by required conditions
CI / check system | python on debian (push) Blocked by required conditions
CI / check system | python on fedora (push) Blocked by required conditions
CI / check system | python on ubuntu (push) Blocked by required conditions
CI / check system | python on opensuse (push) Blocked by required conditions
CI / check system | python on rocky linux 8 (push) Blocked by required conditions
CI / Determine changes (push) Waiting to run
CI / check system | python on rocky linux 9 (push) Blocked by required conditions
CI / check system | pypy on ubuntu (push) Blocked by required conditions
CI / check system | pyston (push) Blocked by required conditions
CI / check system | alpine (push) Blocked by required conditions
CI / check system | python on macos aarch64 (push) Blocked by required conditions
CI / check system | homebrew python on macos aarch64 (push) Blocked by required conditions
CI / check system | python on macos x86_64 (push) Blocked by required conditions
CI / check system | python3.10 on windows (push) Blocked by required conditions
CI / check system | python3.10 on windows x86 (push) Blocked by required conditions
CI / check system | python3.13 on windows (push) Blocked by required conditions
CI / check system | python3.12 via chocolatey (push) Blocked by required conditions
CI / check system | python3.9 via pyenv (push) Blocked by required conditions
CI / lint (push) Waiting to run
CI / cargo clippy | ubuntu (push) Blocked by required conditions
CI / check system | python3.13 (push) Blocked by required conditions
CI / check system | conda3.11 on linux (push) Blocked by required conditions
CI / check system | conda3.8 on linux (push) Blocked by required conditions
CI / cargo clippy | windows (push) Blocked by required conditions
CI / cargo dev generate-all (push) Blocked by required conditions
CI / cargo shear (push) Waiting to run
CI / check system | conda3.11 on macos (push) Blocked by required conditions
CI / check system | conda3.8 on macos (push) Blocked by required conditions
CI / check system | conda3.11 on windows (push) Blocked by required conditions
CI / check system | conda3.8 on windows (push) Blocked by required conditions
CI / cargo test | ubuntu (push) Blocked by required conditions
CI / cargo test | macos (push) Blocked by required conditions
CI / cargo test | windows (push) Blocked by required conditions
CI / check windows trampoline | aarch64 (push) Blocked by required conditions
CI / check windows trampoline | i686 (push) Blocked by required conditions
CI / check windows trampoline | x86_64 (push) Blocked by required conditions
CI / test windows trampoline | i686 (push) Blocked by required conditions
CI / test windows trampoline | x86_64 (push) Blocked by required conditions
CI / typos (push) Waiting to run
CI / mkdocs (push) Waiting to run
CI / check system | embedded python3.10 on windows (push) Blocked by required conditions
CI / benchmarks (push) Blocked by required conditions

## Summary

The issue here is fairly complex. Consider the following:

```toml
[project]
name = "project"
version = "0.1.0"
requires-python = ">=3.12.0"
dependencies = []

[project.optional-dependencies]
cpu = [
  "torch>=2.5.1",
  "torchvision>=0.20.1",
]
cu124 = [
  "torch>=2.5.1",
  "torchvision>=0.20.1",
]

[tool.uv]
conflicts = [
  [
    { extra = "cpu" },
    { extra = "cu124" },
  ],
]

[tool.uv.sources]
torch = [
  { index = "pytorch-cpu", extra = "cpu", marker = "platform_system != 'Darwin'" },
]
torchvision = [
  { index = "pytorch-cpu", extra = "cpu", marker = "platform_system != 'Darwin'" },
]

[[tool.uv.index]]
name = "pytorch-cpu"
url = "https://download.pytorch.org/whl/cpu"
explicit = true
```

When solving this project, we first pick a PyTorch version from PyPI, to
solve the `cu124` extra, selecting `2.5.1`.

Later, we try to solve the `cpu` extra. In solving that extra, we look
at the PyTorch CPU index. Ideally, we'd select `2.5.1+cpu`... But
`2.5.1` is already a preference. So we choose that.

Now, we only respect preferences for explicit indexes if they came from
the same index.

Closes https://github.com/astral-sh/uv/issues/9295.
This commit is contained in:
Charlie Marsh 2024-11-20 22:26:43 -05:00 committed by GitHub
parent c6482dd038
commit 5e48819dbb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 129 additions and 64 deletions

View file

@ -7,7 +7,7 @@ use uv_configuration::Upgrade;
use uv_fs::CWD; use uv_fs::CWD;
use uv_git::ResolvedRepositoryReference; use uv_git::ResolvedRepositoryReference;
use uv_requirements_txt::RequirementsTxt; use uv_requirements_txt::RequirementsTxt;
use uv_resolver::{Lock, Preference, PreferenceError}; use uv_resolver::{Lock, LockError, Preference, PreferenceError};
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub struct LockedRequirements { pub struct LockedRequirements {
@ -63,7 +63,11 @@ pub async fn read_requirements_txt(
} }
/// Load the preferred requirements from an existing lockfile, applying the upgrade strategy. /// Load the preferred requirements from an existing lockfile, applying the upgrade strategy.
pub fn read_lock_requirements(lock: &Lock, upgrade: &Upgrade) -> LockedRequirements { pub fn read_lock_requirements(
lock: &Lock,
install_path: &Path,
upgrade: &Upgrade,
) -> Result<LockedRequirements, LockError> {
let mut preferences = Vec::new(); let mut preferences = Vec::new();
let mut git = Vec::new(); let mut git = Vec::new();
@ -74,7 +78,7 @@ pub fn read_lock_requirements(lock: &Lock, upgrade: &Upgrade) -> LockedRequireme
} }
// Map each entry in the lockfile to a preference. // Map each entry in the lockfile to a preference.
preferences.push(Preference::from_lock(package)); preferences.push(Preference::from_lock(package, install_path)?);
// Map each entry in the lockfile to a Git SHA. // Map each entry in the lockfile to a Git SHA.
if let Some(git_ref) = package.as_git_ref() { if let Some(git_ref) = package.as_git_ref() {
@ -82,5 +86,5 @@ pub fn read_lock_requirements(lock: &Lock, upgrade: &Upgrade) -> LockedRequireme
} }
} }
LockedRequirements { preferences, git } Ok(LockedRequirements { preferences, git })
} }

View file

@ -4,7 +4,7 @@ use std::fmt::{Display, Formatter};
use tracing::{debug, trace}; use tracing::{debug, trace};
use uv_configuration::IndexStrategy; use uv_configuration::IndexStrategy;
use uv_distribution_types::{CompatibleDist, IncompatibleDist, IncompatibleSource}; use uv_distribution_types::{CompatibleDist, IncompatibleDist, IncompatibleSource, IndexUrl};
use uv_distribution_types::{DistributionMetadata, IncompatibleWheel, Name, PrioritizedDist}; use uv_distribution_types::{DistributionMetadata, IncompatibleWheel, Name, PrioritizedDist};
use uv_normalize::PackageName; use uv_normalize::PackageName;
use uv_pep440::Version; use uv_pep440::Version;
@ -80,6 +80,7 @@ impl CandidateSelector {
preferences: &'a Preferences, preferences: &'a Preferences,
installed_packages: &'a InstalledPackages, installed_packages: &'a InstalledPackages,
exclusions: &'a Exclusions, exclusions: &'a Exclusions,
index: Option<&'a IndexUrl>,
env: &ResolverEnvironment, env: &ResolverEnvironment,
) -> Option<Candidate<'a>> { ) -> Option<Candidate<'a>> {
let is_excluded = exclusions.contains(package_name); let is_excluded = exclusions.contains(package_name);
@ -93,6 +94,7 @@ impl CandidateSelector {
preferences, preferences,
installed_packages, installed_packages,
is_excluded, is_excluded,
index,
env, env,
) { ) {
trace!("Using preference {} {}", preferred.name, preferred.version); trace!("Using preference {} {}", preferred.name, preferred.version);
@ -131,23 +133,39 @@ impl CandidateSelector {
preferences: &'a Preferences, preferences: &'a Preferences,
installed_packages: &'a InstalledPackages, installed_packages: &'a InstalledPackages,
is_excluded: bool, is_excluded: bool,
index: Option<&'a IndexUrl>,
env: &ResolverEnvironment, env: &ResolverEnvironment,
) -> Option<Candidate> { ) -> Option<Candidate> {
// In the branches, we "sort" the preferences by marker-matching through an iterator that // In the branches, we "sort" the preferences by marker-matching through an iterator that
// first has the matching half and then the mismatching half. // first has the matching half and then the mismatching half.
let preferences_match = preferences.get(package_name).filter(|(marker, _version)| { let preferences_match =
preferences
.get(package_name)
.filter(|(marker, _index, _version)| {
// `.unwrap_or(true)` because the universal marker is considered matching. // `.unwrap_or(true)` because the universal marker is considered matching.
marker marker
.map(|marker| env.included_by_marker(marker)) .map(|marker| env.included_by_marker(marker))
.unwrap_or(true) .unwrap_or(true)
}); });
let preferences_mismatch = preferences.get(package_name).filter(|(marker, _version)| { let preferences_mismatch =
preferences
.get(package_name)
.filter(|(marker, _index, _version)| {
marker marker
.map(|marker| !env.included_by_marker(marker)) .map(|marker| !env.included_by_marker(marker))
.unwrap_or(false) .unwrap_or(false)
}); });
let preferences = preferences_match.chain(preferences_mismatch).filter_map(
|(marker, source, version)| {
// If the package is mapped to an explicit index, only consider preferences that
// match the index.
index
.map_or(true, |index| source == Some(index))
.then_some((marker, version))
},
);
self.get_preferred_from_iter( self.get_preferred_from_iter(
preferences_match.chain(preferences_mismatch), preferences,
package_name, package_name,
range, range,
version_maps, version_maps,

View file

@ -1,16 +1,17 @@
use std::path::Path;
use std::str::FromStr; use std::str::FromStr;
use rustc_hash::FxHashMap; use rustc_hash::FxHashMap;
use tracing::trace; use tracing::trace;
use uv_distribution_types::{InstalledDist, InstalledMetadata, InstalledVersion, Name}; use uv_distribution_types::{IndexUrl, InstalledDist, InstalledMetadata, InstalledVersion, Name};
use uv_normalize::PackageName; use uv_normalize::PackageName;
use uv_pep440::{Operator, Version}; use uv_pep440::{Operator, Version};
use uv_pep508::{MarkerTree, VersionOrUrl}; use uv_pep508::{MarkerTree, VersionOrUrl};
use uv_pypi_types::{HashDigest, HashError}; use uv_pypi_types::{HashDigest, HashError};
use uv_requirements_txt::{RequirementEntry, RequirementsTxtRequirement}; use uv_requirements_txt::{RequirementEntry, RequirementsTxtRequirement};
use crate::ResolverEnvironment; use crate::{LockError, ResolverEnvironment};
#[derive(thiserror::Error, Debug)] #[derive(thiserror::Error, Debug)]
pub enum PreferenceError { pub enum PreferenceError {
@ -25,6 +26,8 @@ pub struct Preference {
version: Version, version: Version,
/// The markers on the requirement itself (those after the semicolon). /// The markers on the requirement itself (those after the semicolon).
marker: MarkerTree, marker: MarkerTree,
/// The index URL of the package, if any.
index: Option<IndexUrl>,
/// If coming from a package with diverging versions, the markers of the forks this preference /// If coming from a package with diverging versions, the markers of the forks this preference
/// is part of, otherwise `None`. /// is part of, otherwise `None`.
fork_markers: Vec<MarkerTree>, fork_markers: Vec<MarkerTree>,
@ -60,6 +63,7 @@ impl Preference {
marker: requirement.marker, marker: requirement.marker,
// requirements.txt doesn't have fork annotations. // requirements.txt doesn't have fork annotations.
fork_markers: vec![], fork_markers: vec![],
index: None,
hashes: entry hashes: entry
.hashes .hashes
.iter() .iter()
@ -79,6 +83,7 @@ impl Preference {
name: dist.name().clone(), name: dist.name().clone(),
version: version.clone(), version: version.clone(),
marker: MarkerTree::TRUE, marker: MarkerTree::TRUE,
index: None,
// Installed distributions don't have fork annotations. // Installed distributions don't have fork annotations.
fork_markers: vec![], fork_markers: vec![],
hashes: Vec::new(), hashes: Vec::new(),
@ -86,14 +91,18 @@ impl Preference {
} }
/// Create a [`Preference`] from a locked distribution. /// Create a [`Preference`] from a locked distribution.
pub fn from_lock(package: &crate::lock::Package) -> Self { pub fn from_lock(
Self { package: &crate::lock::Package,
install_path: &Path,
) -> Result<Self, LockError> {
Ok(Self {
name: package.id.name.clone(), name: package.id.name.clone(),
version: package.id.version.clone(), version: package.id.version.clone(),
marker: MarkerTree::TRUE, marker: MarkerTree::TRUE,
index: package.index(install_path)?,
fork_markers: package.fork_markers().to_vec(), fork_markers: package.fork_markers().to_vec(),
hashes: Vec::new(), hashes: Vec::new(),
} })
} }
/// Return the [`PackageName`] of the package for this [`Preference`]. /// Return the [`PackageName`] of the package for this [`Preference`].
@ -107,6 +116,13 @@ impl Preference {
} }
} }
#[derive(Debug, Clone)]
struct Entry {
marker: Option<MarkerTree>,
index: Option<IndexUrl>,
pin: Pin,
}
/// A set of pinned packages that should be preserved during resolution, if possible. /// A set of pinned packages that should be preserved during resolution, if possible.
/// ///
/// The marker is the marker of the fork that resolved to the pin, if any. /// The marker is the marker of the fork that resolved to the pin, if any.
@ -114,15 +130,15 @@ impl Preference {
/// Preferences should be prioritized first by whether their marker matches and then by the order /// Preferences should be prioritized first by whether their marker matches and then by the order
/// they are stored, so that a lockfile has higher precedence than sibling forks. /// they are stored, so that a lockfile has higher precedence than sibling forks.
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone, Default)]
pub struct Preferences(FxHashMap<PackageName, Vec<(Option<MarkerTree>, Pin)>>); pub struct Preferences(FxHashMap<PackageName, Vec<Entry>>);
impl Preferences { impl Preferences {
/// Create a map of pinned packages from an iterator of [`Preference`] entries. /// Create a map of pinned packages from an iterator of [`Preference`] entries.
/// ///
/// The provided [`ResolverEnvironment`] will be used to filter the preferences /// The provided [`ResolverEnvironment`] will be used to filter the preferences
/// to an applicable subset. /// to an applicable subset.
pub fn from_iter<PreferenceIterator: IntoIterator<Item = Preference>>( pub fn from_iter(
preferences: PreferenceIterator, preferences: impl IntoIterator<Item = Preference>,
env: &ResolverEnvironment, env: &ResolverEnvironment,
) -> Self { ) -> Self {
let mut slf = Self::default(); let mut slf = Self::default();
@ -152,6 +168,7 @@ impl Preferences {
if preference.fork_markers.is_empty() { if preference.fork_markers.is_empty() {
slf.insert( slf.insert(
preference.name, preference.name,
preference.index,
None, None,
Pin { Pin {
version: preference.version, version: preference.version,
@ -162,6 +179,7 @@ impl Preferences {
for fork_marker in preference.fork_markers { for fork_marker in preference.fork_markers {
slf.insert( slf.insert(
preference.name.clone(), preference.name.clone(),
preference.index.clone(),
Some(fork_marker), Some(fork_marker),
Pin { Pin {
version: preference.version.clone(), version: preference.version.clone(),
@ -179,13 +197,15 @@ impl Preferences {
pub(crate) fn insert( pub(crate) fn insert(
&mut self, &mut self,
package_name: PackageName, package_name: PackageName,
index: Option<IndexUrl>,
markers: Option<MarkerTree>, markers: Option<MarkerTree>,
pin: impl Into<Pin>, pin: impl Into<Pin>,
) { ) {
self.0 self.0.entry(package_name).or_default().push(Entry {
.entry(package_name) marker: markers,
.or_default() index,
.push((markers, pin.into())); pin: pin.into(),
});
} }
/// Returns an iterator over the preferences. /// Returns an iterator over the preferences.
@ -194,15 +214,19 @@ impl Preferences {
) -> impl Iterator< ) -> impl Iterator<
Item = ( Item = (
&PackageName, &PackageName,
impl Iterator<Item = (Option<&MarkerTree>, &Version)>, impl Iterator<Item = (Option<&MarkerTree>, Option<&IndexUrl>, &Version)>,
), ),
> { > {
self.0.iter().map(|(name, preferences)| { self.0.iter().map(|(name, preferences)| {
( (
name, name,
preferences preferences.iter().map(|entry| {
.iter() (
.map(|(markers, pin)| (markers.as_ref(), pin.version())), entry.marker.as_ref(),
entry.index.as_ref(),
entry.pin.version(),
)
}),
) )
}) })
} }
@ -211,12 +235,14 @@ impl Preferences {
pub(crate) fn get( pub(crate) fn get(
&self, &self,
package_name: &PackageName, package_name: &PackageName,
) -> impl Iterator<Item = (Option<&MarkerTree>, &Version)> { ) -> impl Iterator<Item = (Option<&MarkerTree>, Option<&IndexUrl>, &Version)> {
self.0 self.0.get(package_name).into_iter().flatten().map(|entry| {
.get(package_name) (
.into_iter() entry.marker.as_ref(),
.flatten() entry.index.as_ref(),
.map(|(markers, pin)| (markers.as_ref(), pin.version())) entry.pin.version(),
)
})
} }
/// Return the hashes for a package, if the version matches that of the pin. /// Return the hashes for a package, if the version matches that of the pin.
@ -229,8 +255,8 @@ impl Preferences {
.get(package_name) .get(package_name)
.into_iter() .into_iter()
.flatten() .flatten()
.find(|(_markers, pin)| pin.version() == version) .find(|entry| entry.pin.version() == version)
.map(|(_markers, pin)| pin.hashes()) .map(|entry| entry.pin.hashes())
} }
} }

View file

@ -381,6 +381,7 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
for (package, version) in &resolution.nodes { for (package, version) in &resolution.nodes {
preferences.insert( preferences.insert(
package.name.clone(), package.name.clone(),
package.index.clone(),
resolution.env.try_markers().cloned(), resolution.env.try_markers().cloned(),
version.clone(), version.clone(),
); );
@ -669,14 +670,15 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
diverging_packages: &'a [PackageName], diverging_packages: &'a [PackageName],
) -> impl Iterator<Item = Result<ForkState, ResolveError>> + 'a { ) -> impl Iterator<Item = Result<ForkState, ResolveError>> + 'a {
debug!( debug!(
"Splitting resolution on {}=={} over {} into {} resolution with separate markers", "Splitting resolution on {}=={} over {} into {} resolution{} with separate markers",
current_state.next, current_state.next,
version, version,
diverging_packages diverging_packages
.iter() .iter()
.map(ToString::to_string) .map(ToString::to_string)
.join(", "), .join(", "),
forks.len() forks.len(),
if forks.len() == 1 { "" } else { "s" }
); );
assert!(forks.len() >= 2); assert!(forks.len() >= 2);
// This is a somewhat tortured technique to ensure // This is a somewhat tortured technique to ensure
@ -1075,6 +1077,7 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
preferences, preferences,
&self.installed_packages, &self.installed_packages,
&self.exclusions, &self.exclusions,
index,
env, env,
) else { ) else {
// Short circuit: we couldn't find _any_ versions for a package. // Short circuit: we couldn't find _any_ versions for a package.
@ -1934,6 +1937,7 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
&self.preferences, &self.preferences,
&self.installed_packages, &self.installed_packages,
&self.exclusions, &self.exclusions,
None,
&env, &env,
) else { ) else {
return Ok(None); return Ok(None);

View file

@ -45,7 +45,7 @@ impl AllowedYanks {
allowed_yanks allowed_yanks
.entry(name.clone()) .entry(name.clone())
.or_default() .or_default()
.extend(preferences.map(|(_markers, version)| version.clone())); .extend(preferences.map(|(.., version)| version.clone()));
} }
Self(Arc::new(allowed_yanks)) Self(Arc::new(allowed_yanks))

View file

@ -563,7 +563,8 @@ async fn do_lock(
// If an existing lockfile exists, build up a set of preferences. // If an existing lockfile exists, build up a set of preferences.
let LockedRequirements { preferences, git } = versions_lock let LockedRequirements { preferences, git } = versions_lock
.map(|lock| read_lock_requirements(lock, upgrade)) .map(|lock| read_lock_requirements(lock, workspace.install_path(), upgrade))
.transpose()?
.unwrap_or_default(); .unwrap_or_default();
// Populate the Git resolver. // Populate the Git resolver.

View file

@ -935,8 +935,8 @@ pub(crate) async fn resolve_names(
pub(crate) struct EnvironmentSpecification<'lock> { pub(crate) struct EnvironmentSpecification<'lock> {
/// The requirements to include in the environment. /// The requirements to include in the environment.
requirements: RequirementsSpecification, requirements: RequirementsSpecification,
/// The lockfile from which to extract preferences. /// The lockfile from which to extract preferences, along with the install path.
lock: Option<&'lock Lock>, lock: Option<(&'lock Lock, &'lock Path)>,
} }
impl From<RequirementsSpecification> for EnvironmentSpecification<'_> { impl From<RequirementsSpecification> for EnvironmentSpecification<'_> {
@ -950,7 +950,7 @@ impl From<RequirementsSpecification> for EnvironmentSpecification<'_> {
impl<'lock> EnvironmentSpecification<'lock> { impl<'lock> EnvironmentSpecification<'lock> {
#[must_use] #[must_use]
pub(crate) fn with_lock(self, lock: Option<&'lock Lock>) -> Self { pub(crate) fn with_lock(self, lock: Option<(&'lock Lock, &'lock Path)>) -> Self {
Self { lock, ..self } Self { lock, ..self }
} }
} }
@ -1057,7 +1057,8 @@ pub(crate) async fn resolve_environment<'a>(
// If an existing lockfile exists, build up a set of preferences. // If an existing lockfile exists, build up a set of preferences.
let LockedRequirements { preferences, git } = spec let LockedRequirements { preferences, git } = spec
.lock .lock
.map(|lock| read_lock_requirements(lock, &upgrade)) .map(|(lock, install_path)| read_lock_requirements(lock, install_path, &upgrade))
.transpose()?
.unwrap_or_default(); .unwrap_or_default();
// Populate the Git resolver. // Populate the Git resolver.

View file

@ -372,7 +372,7 @@ pub(crate) async fn run(
}; };
// The lockfile used for the base environment. // The lockfile used for the base environment.
let mut lock: Option<Lock> = None; let mut lock: Option<(Lock, PathBuf)> = None;
// Discover and sync the base environment. // Discover and sync the base environment.
let temp_dir; let temp_dir;
@ -609,7 +609,8 @@ pub(crate) async fn run(
lock = project::lock::read(project.workspace()) lock = project::lock::read(project.workspace())
.await .await
.ok() .ok()
.flatten(); .flatten()
.map(|lock| (lock, project.workspace().install_path().to_owned()));
} }
} else { } else {
// Validate that any referenced dependency groups are defined in the workspace. // Validate that any referenced dependency groups are defined in the workspace.
@ -749,7 +750,10 @@ pub(crate) async fn run(
Err(err) => return Err(err.into()), Err(err) => return Err(err.into()),
} }
lock = Some(result.into_lock()); lock = Some((
result.into_lock(),
project.workspace().install_path().to_owned(),
));
} }
venv.into_interpreter() venv.into_interpreter()
@ -861,7 +865,10 @@ pub(crate) async fn run(
debug!("Syncing ephemeral requirements"); debug!("Syncing ephemeral requirements");
let result = CachedEnvironment::get_or_create( let result = CachedEnvironment::get_or_create(
EnvironmentSpecification::from(spec).with_lock(lock.as_ref()), EnvironmentSpecification::from(spec).with_lock(
lock.as_ref()
.map(|(lock, install_path)| (lock, install_path.as_ref())),
),
base_interpreter.clone(), base_interpreter.clone(),
&settings, &settings,
&state, &state,

View file

@ -6458,7 +6458,11 @@ fn add_index() -> Result<()> {
----- stderr ----- ----- stderr -----
Resolved 4 packages in [TIME] Resolved 4 packages in [TIME]
Audited 3 packages in [TIME] Prepared 1 package in [TIME]
Uninstalled 1 package in [TIME]
Installed 1 package in [TIME]
- jinja2==3.1.3
+ jinja2==3.1.4
"###); "###);
let pyproject_toml = fs_err::read_to_string(context.temp_dir.join("pyproject.toml"))?; let pyproject_toml = fs_err::read_to_string(context.temp_dir.join("pyproject.toml"))?;
@ -6517,14 +6521,14 @@ fn add_index() -> Result<()> {
[[package]] [[package]]
name = "jinja2" name = "jinja2"
version = "3.1.3" version = "3.1.4"
source = { registry = "https://test.pypi.org/simple" } source = { registry = "https://test.pypi.org/simple" }
dependencies = [ dependencies = [
{ name = "markupsafe" }, { name = "markupsafe" },
] ]
sdist = { url = "https://test-files.pythonhosted.org/packages/3e/f0/69ae37cced6b277dc0419dbb1c6e4fb259e5e319a1a971061a2776316bec/Jinja2-3.1.3.tar.gz", hash = "sha256:27fb536952e578492fa66d8681d8967d8bdf1eb36368b1f842b53251c9f0bfe1", size = 268254 } sdist = { url = "https://test-files.pythonhosted.org/packages/ed/55/39036716d19cab0747a5020fc7e907f362fbf48c984b14e62127f7e68e5d/jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369", size = 240245 }
wheels = [ wheels = [
{ url = "https://test-files.pythonhosted.org/packages/47/dc/9d1c0f1ddbedb1e67f7d00e91819b5a9157056ad83bfa64c12ecef8a4f4e/Jinja2-3.1.3-py3-none-any.whl", hash = "sha256:ddd11470e8a1dc4c30e3146400f0130fed7d85886c5f8082f309355b4b0c1128", size = 133236 }, { url = "https://test-files.pythonhosted.org/packages/31/80/3a54838c3fb461f6fec263ebf3a3a41771bd05190238de3486aae8540c36/jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d", size = 133271 },
] ]
[[package]] [[package]]
@ -6633,14 +6637,14 @@ fn add_index() -> Result<()> {
[[package]] [[package]]
name = "jinja2" name = "jinja2"
version = "3.1.3" version = "3.1.4"
source = { registry = "https://test.pypi.org/simple" } source = { registry = "https://test.pypi.org/simple" }
dependencies = [ dependencies = [
{ name = "markupsafe" }, { name = "markupsafe" },
] ]
sdist = { url = "https://test-files.pythonhosted.org/packages/3e/f0/69ae37cced6b277dc0419dbb1c6e4fb259e5e319a1a971061a2776316bec/Jinja2-3.1.3.tar.gz", hash = "sha256:27fb536952e578492fa66d8681d8967d8bdf1eb36368b1f842b53251c9f0bfe1", size = 268254 } sdist = { url = "https://test-files.pythonhosted.org/packages/ed/55/39036716d19cab0747a5020fc7e907f362fbf48c984b14e62127f7e68e5d/jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369", size = 240245 }
wheels = [ wheels = [
{ url = "https://test-files.pythonhosted.org/packages/47/dc/9d1c0f1ddbedb1e67f7d00e91819b5a9157056ad83bfa64c12ecef8a4f4e/Jinja2-3.1.3-py3-none-any.whl", hash = "sha256:ddd11470e8a1dc4c30e3146400f0130fed7d85886c5f8082f309355b4b0c1128", size = 133236 }, { url = "https://test-files.pythonhosted.org/packages/31/80/3a54838c3fb461f6fec263ebf3a3a41771bd05190238de3486aae8540c36/jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d", size = 133271 },
] ]
[[package]] [[package]]
@ -6758,14 +6762,14 @@ fn add_index() -> Result<()> {
[[package]] [[package]]
name = "jinja2" name = "jinja2"
version = "3.1.3" version = "3.1.4"
source = { registry = "https://test.pypi.org/simple" } source = { registry = "https://test.pypi.org/simple" }
dependencies = [ dependencies = [
{ name = "markupsafe" }, { name = "markupsafe" },
] ]
sdist = { url = "https://test-files.pythonhosted.org/packages/3e/f0/69ae37cced6b277dc0419dbb1c6e4fb259e5e319a1a971061a2776316bec/Jinja2-3.1.3.tar.gz", hash = "sha256:27fb536952e578492fa66d8681d8967d8bdf1eb36368b1f842b53251c9f0bfe1", size = 268254 } sdist = { url = "https://test-files.pythonhosted.org/packages/ed/55/39036716d19cab0747a5020fc7e907f362fbf48c984b14e62127f7e68e5d/jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369", size = 240245 }
wheels = [ wheels = [
{ url = "https://test-files.pythonhosted.org/packages/47/dc/9d1c0f1ddbedb1e67f7d00e91819b5a9157056ad83bfa64c12ecef8a4f4e/Jinja2-3.1.3-py3-none-any.whl", hash = "sha256:ddd11470e8a1dc4c30e3146400f0130fed7d85886c5f8082f309355b4b0c1128", size = 133236 }, { url = "https://test-files.pythonhosted.org/packages/31/80/3a54838c3fb461f6fec263ebf3a3a41771bd05190238de3486aae8540c36/jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d", size = 133271 },
] ]
[[package]] [[package]]