mirror of
https://github.com/astral-sh/uv.git
synced 2025-08-31 07:47:27 +00:00
Always reinstall local source trees passed to uv pip install
(#12176)
## Summary This ended up being more involved than expected. The gist is that we setup all the packages we want to reinstall upfront (they're passed in on the command-line); but at that point, we don't have names for all the packages that the user has specified. (Consider, e.g., `uv pip install .` -- we don't have a name for `.`, so we can't add it to the list of `Reinstall` packages.) Now, `Reinstall` also accepts paths, so we can augment `Reinstall` based on the user-provided paths. Closes #12038.
This commit is contained in:
parent
05352882ea
commit
72be5ffb25
30 changed files with 471 additions and 214 deletions
3
Cargo.lock
generated
3
Cargo.lock
generated
|
@ -4625,6 +4625,7 @@ dependencies = [
|
|||
"uv-pypi-types",
|
||||
"uv-python",
|
||||
"uv-requirements",
|
||||
"uv-requirements-txt",
|
||||
"uv-resolver",
|
||||
"uv-scripts",
|
||||
"uv-settings",
|
||||
|
@ -4785,6 +4786,7 @@ dependencies = [
|
|||
"nanoid",
|
||||
"rmp-serde",
|
||||
"rustc-hash",
|
||||
"same-file",
|
||||
"serde",
|
||||
"tempfile",
|
||||
"tracing",
|
||||
|
@ -4916,6 +4918,7 @@ dependencies = [
|
|||
"fs-err 3.1.0",
|
||||
"rayon",
|
||||
"rustc-hash",
|
||||
"same-file",
|
||||
"schemars",
|
||||
"serde",
|
||||
"serde-untagged",
|
||||
|
|
|
@ -32,6 +32,7 @@ fs-err = { workspace = true, features = ["tokio"] }
|
|||
nanoid = { workspace = true }
|
||||
rmp-serde = { workspace = true }
|
||||
rustc-hash = { workspace = true }
|
||||
same-file = { workspace = true }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
tempfile = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
|
|
|
@ -223,11 +223,22 @@ impl Cache {
|
|||
}
|
||||
|
||||
/// Returns `true` if a cache entry must be revalidated given the [`Refresh`] policy.
|
||||
pub fn must_revalidate(&self, package: &PackageName) -> bool {
|
||||
pub fn must_revalidate_package(&self, package: &PackageName) -> bool {
|
||||
match &self.refresh {
|
||||
Refresh::None(_) => false,
|
||||
Refresh::All(_) => true,
|
||||
Refresh::Packages(packages, _) => packages.contains(package),
|
||||
Refresh::Packages(packages, _, _) => packages.contains(package),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if a cache entry must be revalidated given the [`Refresh`] policy.
|
||||
pub fn must_revalidate_path(&self, path: &Path) -> bool {
|
||||
match &self.refresh {
|
||||
Refresh::None(_) => false,
|
||||
Refresh::All(_) => true,
|
||||
Refresh::Packages(_, paths, _) => paths
|
||||
.iter()
|
||||
.any(|target| same_file::is_same_file(path, target).unwrap_or(false)),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -239,13 +250,20 @@ impl Cache {
|
|||
&self,
|
||||
entry: &CacheEntry,
|
||||
package: Option<&PackageName>,
|
||||
path: Option<&Path>,
|
||||
) -> io::Result<Freshness> {
|
||||
// Grab the cutoff timestamp, if it's relevant.
|
||||
let timestamp = match &self.refresh {
|
||||
Refresh::None(_) => return Ok(Freshness::Fresh),
|
||||
Refresh::All(timestamp) => timestamp,
|
||||
Refresh::Packages(packages, timestamp) => {
|
||||
if package.is_none_or(|package| packages.contains(package)) {
|
||||
Refresh::Packages(packages, paths, timestamp) => {
|
||||
if package.is_none_or(|package| packages.contains(package))
|
||||
|| path.is_some_and(|path| {
|
||||
paths
|
||||
.iter()
|
||||
.any(|target| same_file::is_same_file(path, target).unwrap_or(false))
|
||||
})
|
||||
{
|
||||
timestamp
|
||||
} else {
|
||||
return Ok(Freshness::Fresh);
|
||||
|
@ -1167,7 +1185,7 @@ pub enum Refresh {
|
|||
/// Don't refresh any entries.
|
||||
None(Timestamp),
|
||||
/// Refresh entries linked to the given packages, if created before the given timestamp.
|
||||
Packages(Vec<PackageName>, Timestamp),
|
||||
Packages(Vec<PackageName>, Vec<PathBuf>, Timestamp),
|
||||
/// Refresh all entries created before the given timestamp.
|
||||
All(Timestamp),
|
||||
}
|
||||
|
@ -1183,7 +1201,7 @@ impl Refresh {
|
|||
if refresh_package.is_empty() {
|
||||
Self::None(timestamp)
|
||||
} else {
|
||||
Self::Packages(refresh_package, timestamp)
|
||||
Self::Packages(refresh_package, vec![], timestamp)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1193,7 +1211,7 @@ impl Refresh {
|
|||
pub fn timestamp(&self) -> Timestamp {
|
||||
match self {
|
||||
Self::None(timestamp) => *timestamp,
|
||||
Self::Packages(_, timestamp) => *timestamp,
|
||||
Self::Packages(.., timestamp) => *timestamp,
|
||||
Self::All(timestamp) => *timestamp,
|
||||
}
|
||||
}
|
||||
|
@ -1220,24 +1238,27 @@ impl Refresh {
|
|||
// Take the `max` of the two timestamps.
|
||||
(Self::None(t1), Refresh::None(t2)) => Refresh::None(max(t1, t2)),
|
||||
(Self::None(t1), Refresh::All(t2)) => Refresh::All(max(t1, t2)),
|
||||
(Self::None(t1), Refresh::Packages(packages, t2)) => {
|
||||
Refresh::Packages(packages, max(t1, t2))
|
||||
(Self::None(t1), Refresh::Packages(packages, paths, t2)) => {
|
||||
Refresh::Packages(packages, paths, max(t1, t2))
|
||||
}
|
||||
|
||||
// If the policy is `All`, refresh all packages.
|
||||
(Self::All(t1), Refresh::None(t2)) => Refresh::All(max(t1, t2)),
|
||||
(Self::All(t1), Refresh::All(t2)) => Refresh::All(max(t1, t2)),
|
||||
(Self::All(t1), Refresh::Packages(_packages, t2)) => Refresh::All(max(t1, t2)),
|
||||
(Self::All(t1), Refresh::Packages(.., t2)) => Refresh::All(max(t1, t2)),
|
||||
|
||||
// If the policy is `Packages`, take the "max" of the two policies.
|
||||
(Self::Packages(packages, t1), Refresh::None(t2)) => {
|
||||
Refresh::Packages(packages, max(t1, t2))
|
||||
(Self::Packages(packages, paths, t1), Refresh::None(t2)) => {
|
||||
Refresh::Packages(packages, paths, max(t1, t2))
|
||||
}
|
||||
(Self::Packages(_packages, t1), Refresh::All(t2)) => Refresh::All(max(t1, t2)),
|
||||
(Self::Packages(packages1, t1), Refresh::Packages(packages2, t2)) => Refresh::Packages(
|
||||
(Self::Packages(.., t1), Refresh::All(t2)) => Refresh::All(max(t1, t2)),
|
||||
(Self::Packages(packages1, paths1, t1), Refresh::Packages(packages2, paths2, t2)) => {
|
||||
Refresh::Packages(
|
||||
packages1.into_iter().chain(packages2).collect(),
|
||||
paths1.into_iter().chain(paths2).collect(),
|
||||
max(t1, t2),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -160,7 +160,7 @@ impl<'a> FlatIndexClient<'a> {
|
|||
let cache_control = match self.client.connectivity() {
|
||||
Connectivity::Online => CacheControl::from(
|
||||
self.cache
|
||||
.freshness(&cache_entry, None)
|
||||
.freshness(&cache_entry, None, None)
|
||||
.map_err(ErrorKind::Io)?,
|
||||
),
|
||||
Connectivity::Offline => CacheControl::AllowStale,
|
||||
|
|
|
@ -347,7 +347,7 @@ impl RegistryClient {
|
|||
let cache_control = match self.connectivity {
|
||||
Connectivity::Online => CacheControl::from(
|
||||
self.cache
|
||||
.freshness(&cache_entry, Some(package_name))
|
||||
.freshness(&cache_entry, Some(package_name), None)
|
||||
.map_err(ErrorKind::Io)?,
|
||||
),
|
||||
Connectivity::Offline => CacheControl::AllowStale,
|
||||
|
@ -632,7 +632,7 @@ impl RegistryClient {
|
|||
let cache_control = match self.connectivity {
|
||||
Connectivity::Online => CacheControl::from(
|
||||
self.cache
|
||||
.freshness(&cache_entry, Some(&filename.name))
|
||||
.freshness(&cache_entry, Some(&filename.name), None)
|
||||
.map_err(ErrorKind::Io)?,
|
||||
),
|
||||
Connectivity::Offline => CacheControl::AllowStale,
|
||||
|
@ -702,7 +702,7 @@ impl RegistryClient {
|
|||
let cache_control = match self.connectivity {
|
||||
Connectivity::Online => CacheControl::from(
|
||||
self.cache
|
||||
.freshness(&cache_entry, Some(&filename.name))
|
||||
.freshness(&cache_entry, Some(&filename.name), None)
|
||||
.map_err(ErrorKind::Io)?,
|
||||
),
|
||||
Connectivity::Offline => CacheControl::AllowStale,
|
||||
|
|
|
@ -32,6 +32,7 @@ either = { workspace = true }
|
|||
fs-err = { workspace = true }
|
||||
rayon = { workspace = true }
|
||||
rustc-hash = { workspace = true }
|
||||
same-file = { workspace = true }
|
||||
schemars = { workspace = true, optional = true }
|
||||
serde = { workspace = true }
|
||||
serde-untagged = { workspace = true }
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
use either::Either;
|
||||
use std::path::{Path, PathBuf};
|
||||
use uv_pep508::PackageName;
|
||||
|
||||
use rustc_hash::FxHashMap;
|
||||
|
@ -18,7 +19,7 @@ pub enum Reinstall {
|
|||
All,
|
||||
|
||||
/// Reinstall only the specified packages.
|
||||
Packages(Vec<PackageName>),
|
||||
Packages(Vec<PackageName>, Vec<PathBuf>),
|
||||
}
|
||||
|
||||
impl Reinstall {
|
||||
|
@ -31,7 +32,7 @@ impl Reinstall {
|
|||
if reinstall_package.is_empty() {
|
||||
Self::None
|
||||
} else {
|
||||
Self::Packages(reinstall_package)
|
||||
Self::Packages(reinstall_package, Vec::new())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -48,11 +49,22 @@ impl Reinstall {
|
|||
}
|
||||
|
||||
/// Returns `true` if the specified package should be reinstalled.
|
||||
pub fn contains(&self, package_name: &PackageName) -> bool {
|
||||
pub fn contains_package(&self, package_name: &PackageName) -> bool {
|
||||
match &self {
|
||||
Self::None => false,
|
||||
Self::All => true,
|
||||
Self::Packages(packages) => packages.contains(package_name),
|
||||
Self::Packages(packages, ..) => packages.contains(package_name),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if the specified path should be reinstalled.
|
||||
pub fn contains_path(&self, path: &Path) -> bool {
|
||||
match &self {
|
||||
Self::None => false,
|
||||
Self::All => true,
|
||||
Self::Packages(.., paths) => paths
|
||||
.iter()
|
||||
.any(|target| same_file::is_same_file(path, target).unwrap_or(false)),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -65,19 +77,46 @@ impl Reinstall {
|
|||
// If either is `All`, the result is `All`.
|
||||
(Self::All, _) | (_, Self::All) => Self::All,
|
||||
// If one is `None`, the result is the other.
|
||||
(Self::Packages(a), Self::None) => Self::Packages(a),
|
||||
(Self::None, Self::Packages(b)) => Self::Packages(b),
|
||||
(Self::Packages(a1, a2), Self::None) => Self::Packages(a1, a2),
|
||||
(Self::None, Self::Packages(b1, b2)) => Self::Packages(b1, b2),
|
||||
// If both are `Packages`, the result is the union of the two.
|
||||
(Self::Packages(mut a), Self::Packages(b)) => {
|
||||
a.extend(b);
|
||||
Self::Packages(a)
|
||||
(Self::Packages(mut a1, mut a2), Self::Packages(b1, b2)) => {
|
||||
a1.extend(b1);
|
||||
a2.extend(b2);
|
||||
Self::Packages(a1, a2)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Add a [`PathBuf`] to the [`Reinstall`] policy.
|
||||
#[must_use]
|
||||
pub fn with_path(self, path: PathBuf) -> Self {
|
||||
match self {
|
||||
Self::None => Self::Packages(vec![], vec![path]),
|
||||
Self::All => Self::All,
|
||||
Self::Packages(packages, mut paths) => {
|
||||
paths.push(path);
|
||||
Self::Packages(packages, paths)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Add a [`Package`] to the [`Reinstall`] policy.
|
||||
#[must_use]
|
||||
pub fn with_package(self, package_name: PackageName) -> Self {
|
||||
match self {
|
||||
Self::None => Self::Packages(vec![package_name], vec![]),
|
||||
Self::All => Self::All,
|
||||
Self::Packages(mut packages, paths) => {
|
||||
packages.push(package_name);
|
||||
Self::Packages(packages, paths)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a [`Reinstall`] strategy to reinstall a single package.
|
||||
pub fn package(package_name: PackageName) -> Self {
|
||||
Self::Packages(vec![package_name])
|
||||
Self::Packages(vec![package_name], vec![])
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -87,7 +126,9 @@ impl From<Reinstall> for Refresh {
|
|||
match value {
|
||||
Reinstall::None => Self::None(Timestamp::now()),
|
||||
Reinstall::All => Self::All(Timestamp::now()),
|
||||
Reinstall::Packages(packages) => Self::Packages(packages, Timestamp::now()),
|
||||
Reinstall::Packages(packages, paths) => {
|
||||
Self::Packages(packages, paths, Timestamp::now())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -200,9 +241,11 @@ impl From<Upgrade> for Refresh {
|
|||
match value {
|
||||
Upgrade::None => Self::None(Timestamp::now()),
|
||||
Upgrade::All => Self::All(Timestamp::now()),
|
||||
Upgrade::Packages(packages) => {
|
||||
Self::Packages(packages.into_keys().collect::<Vec<_>>(), Timestamp::now())
|
||||
}
|
||||
Upgrade::Packages(packages) => Self::Packages(
|
||||
packages.into_keys().collect::<Vec<_>>(),
|
||||
Vec::new(),
|
||||
Timestamp::now(),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,6 +32,14 @@ impl BuildableSource<'_> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Return the source tree of the source, if available.
|
||||
pub fn source_tree(&self) -> Option<&Path> {
|
||||
match self {
|
||||
Self::Dist(dist) => dist.source_tree(),
|
||||
Self::Url(url) => url.source_tree(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the [`Version`] of the source, if available.
|
||||
pub fn version(&self) -> Option<&Version> {
|
||||
match self {
|
||||
|
@ -104,6 +112,14 @@ impl SourceUrl<'_> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Return the source tree of the source, if available.
|
||||
pub fn source_tree(&self) -> Option<&Path> {
|
||||
match self {
|
||||
Self::Directory(dist) => Some(&dist.install_path),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if the source is editable.
|
||||
pub fn is_editable(&self) -> bool {
|
||||
matches!(
|
||||
|
|
|
@ -281,7 +281,7 @@ impl InstalledDist {
|
|||
}
|
||||
|
||||
/// Return the [`Path`] at which the distribution is stored on-disk.
|
||||
pub fn path(&self) -> &Path {
|
||||
pub fn install_path(&self) -> &Path {
|
||||
match self {
|
||||
Self::Registry(dist) => &dist.path,
|
||||
Self::Url(dist) => &dist.path,
|
||||
|
@ -332,7 +332,7 @@ impl InstalledDist {
|
|||
pub fn metadata(&self) -> Result<uv_pypi_types::ResolutionMetadata, InstalledDistError> {
|
||||
match self {
|
||||
Self::Registry(_) | Self::Url(_) => {
|
||||
let path = self.path().join("METADATA");
|
||||
let path = self.install_path().join("METADATA");
|
||||
let contents = fs::read(&path)?;
|
||||
// TODO(zanieb): Update this to use thiserror so we can unpack parse errors downstream
|
||||
uv_pypi_types::ResolutionMetadata::parse_metadata(&contents).map_err(|err| {
|
||||
|
@ -362,7 +362,7 @@ impl InstalledDist {
|
|||
|
||||
/// Return the `INSTALLER` of the distribution.
|
||||
pub fn installer(&self) -> Result<Option<String>, InstalledDistError> {
|
||||
let path = self.path().join("INSTALLER");
|
||||
let path = self.install_path().join("INSTALLER");
|
||||
match fs::read_to_string(path) {
|
||||
Ok(installer) => Ok(Some(installer)),
|
||||
Err(err) if err.kind() == std::io::ErrorKind::NotFound => Ok(None),
|
||||
|
|
|
@ -542,6 +542,14 @@ impl Dist {
|
|||
}
|
||||
}
|
||||
|
||||
/// Return the source tree of the distribution, if available.
|
||||
pub fn source_tree(&self) -> Option<&Path> {
|
||||
match self {
|
||||
Self::Built { .. } => None,
|
||||
Self::Source(source) => source.source_tree(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the version of the distribution, if it is known.
|
||||
pub fn version(&self) -> Option<&Version> {
|
||||
match self {
|
||||
|
@ -657,6 +665,14 @@ impl SourceDist {
|
|||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the source tree of the distribution, if available.
|
||||
pub fn source_tree(&self) -> Option<&Path> {
|
||||
match self {
|
||||
Self::Directory(dist) => Some(&dist.install_path),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl RegistryBuiltDist {
|
||||
|
@ -1305,11 +1321,11 @@ impl Identifier for BuiltDist {
|
|||
|
||||
impl Identifier for InstalledDist {
|
||||
fn distribution_id(&self) -> DistributionId {
|
||||
self.path().distribution_id()
|
||||
self.install_path().distribution_id()
|
||||
}
|
||||
|
||||
fn resource_id(&self) -> ResourceId {
|
||||
self.path().resource_id()
|
||||
self.install_path().resource_id()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
use std::fmt::{Display, Formatter};
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
|
||||
use uv_pep440::Version;
|
||||
|
@ -92,6 +93,14 @@ impl ResolvedDist {
|
|||
Self::Installed { dist } => Some(dist.version()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the source tree of the distribution, if available.
|
||||
pub fn source_tree(&self) -> Option<&Path> {
|
||||
match self {
|
||||
Self::Installable { dist, .. } => dist.source_tree(),
|
||||
Self::Installed { .. } => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ResolvedDistRef<'_> {
|
||||
|
|
|
@ -594,7 +594,7 @@ impl<'a, Context: BuildContext> DistributionDatabase<'a, Context> {
|
|||
Connectivity::Online => CacheControl::from(
|
||||
self.build_context
|
||||
.cache()
|
||||
.freshness(&http_entry, Some(&filename.name))
|
||||
.freshness(&http_entry, Some(&filename.name), None)
|
||||
.map_err(Error::CacheRead)?,
|
||||
),
|
||||
Connectivity::Offline => CacheControl::AllowStale,
|
||||
|
@ -758,7 +758,7 @@ impl<'a, Context: BuildContext> DistributionDatabase<'a, Context> {
|
|||
Connectivity::Online => CacheControl::from(
|
||||
self.build_context
|
||||
.cache()
|
||||
.freshness(&http_entry, Some(&filename.name))
|
||||
.freshness(&http_entry, Some(&filename.name), None)
|
||||
.map_err(Error::CacheRead)?,
|
||||
),
|
||||
Connectivity::Offline => CacheControl::AllowStale,
|
||||
|
|
|
@ -690,7 +690,7 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
|
|||
Connectivity::Online => CacheControl::from(
|
||||
self.build_context
|
||||
.cache()
|
||||
.freshness(&cache_entry, source.name())
|
||||
.freshness(&cache_entry, source.name(), source.source_tree())
|
||||
.map_err(Error::CacheRead)?,
|
||||
),
|
||||
Connectivity::Offline => CacheControl::AllowStale,
|
||||
|
@ -1359,7 +1359,7 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
|
|||
if self
|
||||
.build_context
|
||||
.cache()
|
||||
.freshness(&entry, source.name())
|
||||
.freshness(&entry, source.name(), source.source_tree())
|
||||
.map_err(Error::CacheRead)?
|
||||
.is_fresh()
|
||||
{
|
||||
|
@ -1671,7 +1671,7 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
|
|||
if self
|
||||
.build_context
|
||||
.cache()
|
||||
.freshness(&metadata_entry, source.name())
|
||||
.freshness(&metadata_entry, source.name(), source.source_tree())
|
||||
.map_err(Error::CacheRead)?
|
||||
.is_fresh()
|
||||
{
|
||||
|
|
|
@ -66,9 +66,23 @@ impl<'a> Planner<'a> {
|
|||
let mut reinstalls = vec![];
|
||||
let mut extraneous = vec![];
|
||||
|
||||
// TODO(charlie): There are a few assumptions here that are hard to spot:
|
||||
//
|
||||
// 1. Apparently, we never return direct URL distributions as [`ResolvedDist::Installed`].
|
||||
// If you trace the resolver, we only ever return [`ResolvedDist::Installed`] if you go
|
||||
// through the [`CandidateSelector`], and we only go through the [`CandidateSelector`]
|
||||
// for registry distributions.
|
||||
//
|
||||
// 2. We expect any distribution returned as [`ResolvedDist::Installed`] to hit the
|
||||
// "Requirement already installed" path (hence the `unreachable!`) a few lines below it.
|
||||
// So, e.g., if a package is marked as `--reinstall`, we _expect_ that it's not passed in
|
||||
// as [`ResolvedDist::Installed`] here.
|
||||
for dist in self.resolution.distributions() {
|
||||
// Check if the package should be reinstalled.
|
||||
let reinstall = reinstall.contains(dist.name());
|
||||
let reinstall = reinstall.contains_package(dist.name())
|
||||
|| dist
|
||||
.source_tree()
|
||||
.is_some_and(|source_tree| reinstall.contains_path(source_tree));
|
||||
|
||||
// Check if installation of a binary version of the package should be allowed.
|
||||
let no_binary = build_options.no_binary_package(dist.name());
|
||||
|
@ -108,7 +122,11 @@ impl<'a> Planner<'a> {
|
|||
unreachable!("Installed distribution could not be found in site-packages: {dist}");
|
||||
};
|
||||
|
||||
if cache.must_revalidate(dist.name()) {
|
||||
if cache.must_revalidate_package(dist.name())
|
||||
|| dist
|
||||
.source_tree()
|
||||
.is_some_and(|source_tree| cache.must_revalidate_path(source_tree))
|
||||
{
|
||||
debug!("Must revalidate requirement: {}", dist.name());
|
||||
remote.push(dist.clone());
|
||||
continue;
|
||||
|
|
|
@ -202,9 +202,9 @@ impl SitePackages {
|
|||
// There are multiple installed distributions for the same package.
|
||||
diagnostics.push(SitePackagesDiagnostic::DuplicatePackage {
|
||||
package: package.clone(),
|
||||
paths: std::iter::once(distribution.path().to_owned())
|
||||
.chain(std::iter::once(conflict.path().to_owned()))
|
||||
.chain(distributions.map(|dist| dist.path().to_owned()))
|
||||
paths: std::iter::once(distribution.install_path().to_owned())
|
||||
.chain(std::iter::once(conflict.install_path().to_owned()))
|
||||
.chain(distributions.map(|dist| dist.install_path().to_owned()))
|
||||
.collect(),
|
||||
});
|
||||
continue;
|
||||
|
@ -219,7 +219,7 @@ impl SitePackages {
|
|||
let Ok(metadata) = distribution.metadata() else {
|
||||
diagnostics.push(SitePackagesDiagnostic::MetadataUnavailable {
|
||||
package: package.clone(),
|
||||
path: distribution.path().to_owned(),
|
||||
path: distribution.install_path().to_owned(),
|
||||
});
|
||||
continue;
|
||||
};
|
||||
|
|
|
@ -8,9 +8,11 @@ pub async fn uninstall(
|
|||
let dist = dist.clone();
|
||||
move || match dist {
|
||||
InstalledDist::Registry(_) | InstalledDist::Url(_) => {
|
||||
Ok(uv_install_wheel::uninstall_wheel(dist.path())?)
|
||||
Ok(uv_install_wheel::uninstall_wheel(dist.install_path())?)
|
||||
}
|
||||
InstalledDist::EggInfoDirectory(_) => {
|
||||
Ok(uv_install_wheel::uninstall_egg(dist.install_path())?)
|
||||
}
|
||||
InstalledDist::EggInfoDirectory(_) => Ok(uv_install_wheel::uninstall_egg(dist.path())?),
|
||||
InstalledDist::LegacyEditable(dist) => {
|
||||
Ok(uv_install_wheel::uninstall_legacy_editable(&dist.egg_link)?)
|
||||
}
|
||||
|
|
|
@ -874,7 +874,7 @@ impl InterpreterInfo {
|
|||
|
||||
// Read from the cache.
|
||||
if cache
|
||||
.freshness(&cache_entry, None)
|
||||
.freshness(&cache_entry, None, None)
|
||||
.is_ok_and(Freshness::is_fresh)
|
||||
{
|
||||
if let Ok(data) = fs::read(cache_entry.path()) {
|
||||
|
|
|
@ -1,17 +1,18 @@
|
|||
use std::path::{Path, PathBuf};
|
||||
|
||||
use anyhow::Result;
|
||||
use anyhow::{Context, Result};
|
||||
use console::Term;
|
||||
|
||||
use uv_fs::Simplified;
|
||||
use uv_fs::{Simplified, CWD};
|
||||
use uv_requirements_txt::RequirementsTxtRequirement;
|
||||
use uv_warnings::warn_user;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum RequirementsSource {
|
||||
/// A package was provided on the command line (e.g., `pip install flask`).
|
||||
Package(String),
|
||||
Package(RequirementsTxtRequirement),
|
||||
/// An editable path was provided on the command line (e.g., `pip install -e ../flask`).
|
||||
Editable(String),
|
||||
Editable(RequirementsTxtRequirement),
|
||||
/// Dependencies were provided via a `requirements.txt` file (e.g., `pip install -r requirements.txt`).
|
||||
RequirementsTxt(PathBuf),
|
||||
/// Dependencies were provided via a `pyproject.toml` file (e.g., `pip-compile pyproject.toml`).
|
||||
|
@ -86,7 +87,7 @@ impl RequirementsSource {
|
|||
///
|
||||
/// If the user provided a value that appears to be a `requirements.txt` file or a local
|
||||
/// directory, prompt them to correct it (if the terminal is interactive).
|
||||
pub fn from_package(name: String) -> Result<Self> {
|
||||
pub fn from_package_argument(name: &str) -> Result<Self> {
|
||||
// If the user provided a `requirements.txt` file without `-r` (as in
|
||||
// `uv pip install requirements.txt`), prompt them to correct it.
|
||||
#[allow(clippy::case_sensitive_file_extension_comparisons)]
|
||||
|
@ -120,7 +121,10 @@ impl RequirementsSource {
|
|||
}
|
||||
}
|
||||
|
||||
Ok(Self::Package(name))
|
||||
let requirement = RequirementsTxtRequirement::parse(name, &*CWD, false)
|
||||
.with_context(|| format!("Failed to parse: `{name}`"))?;
|
||||
|
||||
Ok(Self::Package(requirement))
|
||||
}
|
||||
|
||||
/// Parse a [`RequirementsSource`] from a user-provided string, assumed to be a `--with`
|
||||
|
@ -128,7 +132,7 @@ impl RequirementsSource {
|
|||
///
|
||||
/// If the user provided a value that appears to be a `requirements.txt` file or a local
|
||||
/// directory, prompt them to correct it (if the terminal is interactive).
|
||||
pub fn from_with_package(name: String) -> Result<Self> {
|
||||
pub fn from_with_package_argument(name: &str) -> Result<Self> {
|
||||
// If the user provided a `requirements.txt` file without `--with-requirements` (as in
|
||||
// `uvx --with requirements.txt ruff`), prompt them to correct it.
|
||||
#[allow(clippy::case_sensitive_file_extension_comparisons)]
|
||||
|
@ -162,7 +166,26 @@ impl RequirementsSource {
|
|||
}
|
||||
}
|
||||
|
||||
Ok(Self::Package(name))
|
||||
let requirement = RequirementsTxtRequirement::parse(name, &*CWD, false)
|
||||
.with_context(|| format!("Failed to parse: `{name}`"))?;
|
||||
|
||||
Ok(Self::Package(requirement))
|
||||
}
|
||||
|
||||
/// Parse an editable [`RequirementsSource`] (e.g., `uv pip install -e .`).
|
||||
pub fn from_editable(name: &str) -> Result<Self> {
|
||||
let requirement = RequirementsTxtRequirement::parse(name, &*CWD, true)
|
||||
.with_context(|| format!("Failed to parse: `{name}`"))?;
|
||||
|
||||
Ok(Self::Editable(requirement))
|
||||
}
|
||||
|
||||
/// Parse a package [`RequirementsSource`] (e.g., `uv pip install ruff`).
|
||||
pub fn from_package(name: &str) -> Result<Self> {
|
||||
let requirement = RequirementsTxtRequirement::parse(name, &*CWD, false)
|
||||
.with_context(|| format!("Failed to parse: `{name}`"))?;
|
||||
|
||||
Ok(Self::Package(requirement))
|
||||
}
|
||||
|
||||
/// Parse a [`RequirementsSource`] from a user-provided string, assumed to be a path to a source
|
||||
|
@ -188,8 +211,8 @@ impl RequirementsSource {
|
|||
impl std::fmt::Display for RequirementsSource {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Package(package) => write!(f, "{package}"),
|
||||
Self::Editable(path) => write!(f, "-e {path}"),
|
||||
Self::Package(package) => write!(f, "{package:?}"),
|
||||
Self::Editable(path) => write!(f, "-e {path:?}"),
|
||||
Self::RequirementsTxt(path)
|
||||
| Self::PyprojectToml(path)
|
||||
| Self::SetupPy(path)
|
||||
|
|
|
@ -89,24 +89,18 @@ impl RequirementsSpecification {
|
|||
client_builder: &BaseClientBuilder<'_>,
|
||||
) -> Result<Self> {
|
||||
Ok(match source {
|
||||
RequirementsSource::Package(name) => {
|
||||
let requirement = RequirementsTxtRequirement::parse(name, &*CWD, false)
|
||||
.with_context(|| format!("Failed to parse: `{name}`"))?;
|
||||
Self {
|
||||
requirements: vec![UnresolvedRequirementSpecification::from(requirement)],
|
||||
..Self::default()
|
||||
}
|
||||
}
|
||||
RequirementsSource::Editable(name) => {
|
||||
let requirement = RequirementsTxtRequirement::parse(name, &*CWD, true)
|
||||
.with_context(|| format!("Failed to parse: `{name}`"))?;
|
||||
Self {
|
||||
RequirementsSource::Package(requirement) => Self {
|
||||
requirements: vec![UnresolvedRequirementSpecification::from(
|
||||
requirement.into_editable()?,
|
||||
requirement.clone(),
|
||||
)],
|
||||
..Self::default()
|
||||
}
|
||||
}
|
||||
},
|
||||
RequirementsSource::Editable(requirement) => Self {
|
||||
requirements: vec![UnresolvedRequirementSpecification::from(
|
||||
requirement.clone().into_editable()?,
|
||||
)],
|
||||
..Self::default()
|
||||
},
|
||||
RequirementsSource::RequirementsTxt(path) => {
|
||||
if !(path == Path::new("-")
|
||||
|| path.starts_with("http://")
|
||||
|
|
|
@ -14,7 +14,7 @@ impl Exclusions {
|
|||
}
|
||||
|
||||
pub fn reinstall(&self, package: &PackageName) -> bool {
|
||||
self.reinstall.contains(package)
|
||||
self.reinstall.contains_package(package)
|
||||
}
|
||||
|
||||
pub fn upgrade(&self, package: &PackageName) -> bool {
|
||||
|
|
|
@ -382,7 +382,7 @@ fn find_dist_info<'a>(
|
|||
.get_packages(package_name)
|
||||
.iter()
|
||||
.find(|package| package.version() == package_version)
|
||||
.map(|dist| dist.path())
|
||||
.map(|dist| dist.install_path())
|
||||
.ok_or_else(|| Error::MissingToolPackage(package_name.clone()))
|
||||
}
|
||||
|
||||
|
|
|
@ -44,6 +44,7 @@ uv-publish = { workspace = true }
|
|||
uv-pypi-types = { workspace = true }
|
||||
uv-python = { workspace = true, features = ["schemars"] }
|
||||
uv-requirements = { workspace = true }
|
||||
uv-requirements-txt = { workspace = true }
|
||||
uv-resolver = { workspace = true }
|
||||
uv-scripts = { workspace = true }
|
||||
uv-settings = { workspace = true, features = ["schemars"] }
|
||||
|
@ -52,8 +53,8 @@ uv-static = { workspace = true }
|
|||
uv-tool = { workspace = true }
|
||||
uv-trampoline-builder = { workspace = true }
|
||||
uv-types = { workspace = true }
|
||||
uv-virtualenv = { workspace = true }
|
||||
uv-version = { workspace = true }
|
||||
uv-virtualenv = { workspace = true }
|
||||
uv-warnings = { workspace = true }
|
||||
uv-workspace = { workspace = true }
|
||||
|
||||
|
|
|
@ -514,7 +514,7 @@ pub(crate) async fn install(
|
|||
)) => {
|
||||
warn_user!(
|
||||
"Failed to uninstall package at {} due to missing `RECORD` file. Installation may result in an incomplete environment.",
|
||||
dist_info.path().user_display().cyan(),
|
||||
dist_info.install_path().user_display().cyan(),
|
||||
);
|
||||
}
|
||||
Err(uv_installer::UninstallError::Uninstall(
|
||||
|
@ -522,7 +522,7 @@ pub(crate) async fn install(
|
|||
)) => {
|
||||
warn_user!(
|
||||
"Failed to uninstall package at {} due to missing `top-level.txt` file. Installation may result in an incomplete environment.",
|
||||
dist_info.path().user_display().cyan(),
|
||||
dist_info.install_path().user_display().cyan(),
|
||||
);
|
||||
}
|
||||
Err(err) => return Err(err.into()),
|
||||
|
|
|
@ -142,7 +142,7 @@ pub(crate) fn pip_show(
|
|||
printer.stdout(),
|
||||
"Location: {}",
|
||||
distribution
|
||||
.path()
|
||||
.install_path()
|
||||
.parent()
|
||||
.expect("package path is not root")
|
||||
.simplified_display()
|
||||
|
@ -190,7 +190,7 @@ pub(crate) fn pip_show(
|
|||
|
||||
// If requests, show the list of installed files.
|
||||
if files {
|
||||
let path = distribution.path().join("RECORD");
|
||||
let path = distribution.install_path().join("RECORD");
|
||||
let record = read_record_file(&mut File::open(path)?)?;
|
||||
writeln!(printer.stdout(), "Files:")?;
|
||||
for entry in record {
|
||||
|
|
|
@ -176,8 +176,8 @@ pub(crate) async fn pip_uninstall(
|
|||
}
|
||||
|
||||
// Deduplicate, since a package could be listed both by name and editable URL.
|
||||
distributions.sort_unstable_by_key(|dist| dist.path());
|
||||
distributions.dedup_by_key(|dist| dist.path());
|
||||
distributions.sort_unstable_by_key(|dist| dist.install_path());
|
||||
distributions.dedup_by_key(|dist| dist.install_path());
|
||||
distributions
|
||||
};
|
||||
|
||||
|
|
|
@ -109,9 +109,9 @@ pub(crate) async fn install(
|
|||
// Ex) `ruff`
|
||||
Target::Unspecified(from) => {
|
||||
let source = if editable {
|
||||
RequirementsSource::Editable((*from).to_string())
|
||||
RequirementsSource::from_editable(from)?
|
||||
} else {
|
||||
RequirementsSource::Package((*from).to_string())
|
||||
RequirementsSource::from_package(from)?
|
||||
};
|
||||
let requirement = RequirementsSpecification::from_source(&source, &client_builder)
|
||||
.await?
|
||||
|
|
|
@ -27,7 +27,10 @@ use uv_cli::{PythonCommand, PythonNamespace, ToolCommand, ToolNamespace, TopLeve
|
|||
#[cfg(feature = "self-update")]
|
||||
use uv_cli::{SelfCommand, SelfNamespace, SelfUpdateArgs};
|
||||
use uv_fs::{Simplified, CWD};
|
||||
use uv_pep508::VersionOrUrl;
|
||||
use uv_pypi_types::{ParsedDirectoryUrl, ParsedUrl};
|
||||
use uv_requirements::RequirementsSource;
|
||||
use uv_requirements_txt::RequirementsTxtRequirement;
|
||||
use uv_scripts::{Pep723Error, Pep723Item, Pep723Metadata, Pep723Script};
|
||||
use uv_settings::{Combine, FilesystemOptions, Options};
|
||||
use uv_static::EnvVars;
|
||||
|
@ -538,23 +541,18 @@ async fn run(mut cli: Cli) -> Result<ExitStatus> {
|
|||
args.compat_args.validate()?;
|
||||
|
||||
// Resolve the settings from the command-line arguments and workspace configuration.
|
||||
let args = PipInstallSettings::resolve(args, filesystem);
|
||||
let mut args = PipInstallSettings::resolve(args, filesystem);
|
||||
show_settings!(args);
|
||||
|
||||
// Initialize the cache.
|
||||
let cache = cache.init()?.with_refresh(
|
||||
args.refresh
|
||||
.combine(Refresh::from(args.settings.reinstall.clone()))
|
||||
.combine(Refresh::from(args.settings.upgrade.clone())),
|
||||
);
|
||||
|
||||
let mut requirements = Vec::with_capacity(
|
||||
args.package.len() + args.editables.len() + args.requirements.len(),
|
||||
);
|
||||
for package in args.package {
|
||||
requirements.push(RequirementsSource::from_package(package)?);
|
||||
requirements.push(RequirementsSource::from_package_argument(&package)?);
|
||||
}
|
||||
for package in args.editables {
|
||||
requirements.push(RequirementsSource::from_editable(&package)?);
|
||||
}
|
||||
requirements.extend(args.editables.into_iter().map(RequirementsSource::Editable));
|
||||
requirements.extend(
|
||||
args.requirements
|
||||
.into_iter()
|
||||
|
@ -590,6 +588,55 @@ async fn run(mut cli: Cli) -> Result<ExitStatus> {
|
|||
.push(group.name.clone());
|
||||
}
|
||||
|
||||
// Special-case: any source trees specified on the command-line are automatically
|
||||
// reinstalled. This matches user expectations: `uv pip install .` should always
|
||||
// re-build and re-install the package in the current working directory.
|
||||
for requirement in &requirements {
|
||||
let requirement = match requirement {
|
||||
RequirementsSource::Package(requirement) => requirement,
|
||||
RequirementsSource::Editable(requirement) => requirement,
|
||||
_ => continue,
|
||||
};
|
||||
match requirement {
|
||||
RequirementsTxtRequirement::Named(requirement) => {
|
||||
if let Some(VersionOrUrl::Url(url)) = requirement.version_or_url.as_ref() {
|
||||
if let ParsedUrl::Directory(ParsedDirectoryUrl {
|
||||
install_path, ..
|
||||
}) = &url.parsed_url
|
||||
{
|
||||
debug!(
|
||||
"Marking explicit source tree for reinstall: `{}`",
|
||||
install_path.display()
|
||||
);
|
||||
args.settings.reinstall = args
|
||||
.settings
|
||||
.reinstall
|
||||
.with_package(requirement.name.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
RequirementsTxtRequirement::Unnamed(requirement) => {
|
||||
if let ParsedUrl::Directory(ParsedDirectoryUrl { install_path, .. }) =
|
||||
&requirement.url.parsed_url
|
||||
{
|
||||
debug!(
|
||||
"Marking explicit source tree for reinstall: `{}`",
|
||||
install_path.display()
|
||||
);
|
||||
args.settings.reinstall =
|
||||
args.settings.reinstall.with_path(install_path.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize the cache.
|
||||
let cache = cache.init()?.with_refresh(
|
||||
args.refresh
|
||||
.combine(Refresh::from(args.settings.reinstall.clone()))
|
||||
.combine(Refresh::from(args.settings.upgrade.clone())),
|
||||
);
|
||||
|
||||
commands::pip_install(
|
||||
&requirements,
|
||||
&constraints,
|
||||
|
@ -650,7 +697,7 @@ async fn run(mut cli: Cli) -> Result<ExitStatus> {
|
|||
|
||||
let mut sources = Vec::with_capacity(args.package.len() + args.requirements.len());
|
||||
for package in args.package {
|
||||
sources.push(RequirementsSource::from_package(package)?);
|
||||
sources.push(RequirementsSource::from_package_argument(&package)?);
|
||||
}
|
||||
sources.extend(
|
||||
args.requirements
|
||||
|
@ -1003,13 +1050,11 @@ async fn run(mut cli: Cli) -> Result<ExitStatus> {
|
|||
args.with.len() + args.with_editable.len() + args.with_requirements.len(),
|
||||
);
|
||||
for package in args.with {
|
||||
requirements.push(RequirementsSource::from_with_package(package)?);
|
||||
requirements.push(RequirementsSource::from_with_package_argument(&package)?);
|
||||
}
|
||||
for package in args.with_editable {
|
||||
requirements.push(RequirementsSource::from_editable(&package)?);
|
||||
}
|
||||
requirements.extend(
|
||||
args.with_editable
|
||||
.into_iter()
|
||||
.map(RequirementsSource::Editable),
|
||||
);
|
||||
requirements.extend(
|
||||
args.with_requirements
|
||||
.into_iter()
|
||||
|
@ -1070,13 +1115,11 @@ async fn run(mut cli: Cli) -> Result<ExitStatus> {
|
|||
args.with.len() + args.with_editable.len() + args.with_requirements.len(),
|
||||
);
|
||||
for package in args.with {
|
||||
requirements.push(RequirementsSource::from_with_package(package)?);
|
||||
requirements.push(RequirementsSource::from_with_package_argument(&package)?);
|
||||
}
|
||||
for package in args.with_editable {
|
||||
requirements.push(RequirementsSource::from_editable(&package)?);
|
||||
}
|
||||
requirements.extend(
|
||||
args.with_editable
|
||||
.into_iter()
|
||||
.map(RequirementsSource::Editable),
|
||||
);
|
||||
requirements.extend(
|
||||
args.with_requirements
|
||||
.into_iter()
|
||||
|
@ -1493,13 +1536,11 @@ async fn run_project(
|
|||
args.with.len() + args.with_editable.len() + args.with_requirements.len(),
|
||||
);
|
||||
for package in args.with {
|
||||
requirements.push(RequirementsSource::from_with_package(package)?);
|
||||
requirements.push(RequirementsSource::from_with_package_argument(&package)?);
|
||||
}
|
||||
for package in args.with_editable {
|
||||
requirements.push(RequirementsSource::from_editable(&package)?);
|
||||
}
|
||||
requirements.extend(
|
||||
args.with_editable
|
||||
.into_iter()
|
||||
.map(RequirementsSource::Editable),
|
||||
);
|
||||
requirements.extend(
|
||||
args.with_requirements
|
||||
.into_iter()
|
||||
|
@ -1661,14 +1702,16 @@ async fn run_project(
|
|||
|
||||
let requirements = args
|
||||
.packages
|
||||
.into_iter()
|
||||
.map(RequirementsSource::Package)
|
||||
.iter()
|
||||
.map(String::as_str)
|
||||
.map(RequirementsSource::from_package_argument)
|
||||
.chain(
|
||||
args.requirements
|
||||
.into_iter()
|
||||
.map(RequirementsSource::from_requirements_file),
|
||||
.map(RequirementsSource::from_requirements_file)
|
||||
.map(Ok),
|
||||
)
|
||||
.collect::<Vec<_>>();
|
||||
.collect::<Result<Vec<_>>>()?;
|
||||
|
||||
Box::pin(commands::add(
|
||||
project_dir,
|
||||
|
|
|
@ -375,6 +375,7 @@ fn prune_stale_revision() -> Result<()> {
|
|||
|
||||
----- stderr -----
|
||||
Resolved 1 package in [TIME]
|
||||
Prepared 1 package in [TIME]
|
||||
Installed 1 package in [TIME]
|
||||
+ project==0.1.0 (from file://[TEMP_DIR]/)
|
||||
"###);
|
||||
|
|
|
@ -1137,7 +1137,7 @@ fn install_editable() {
|
|||
"###
|
||||
);
|
||||
|
||||
// Install it again (no-op).
|
||||
// Install it again.
|
||||
uv_snapshot!(context.filters(), context.pip_install()
|
||||
.arg("-e")
|
||||
.arg(context.workspace_root.join("scripts/packages/poetry_editable")), @r###"
|
||||
|
@ -1146,7 +1146,11 @@ fn install_editable() {
|
|||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Audited 1 package in [TIME]
|
||||
Resolved 4 packages in [TIME]
|
||||
Prepared 1 package in [TIME]
|
||||
Uninstalled 1 package in [TIME]
|
||||
Installed 1 package in [TIME]
|
||||
~ poetry-editable==0.1.0 (from file://[WORKSPACE]/scripts/packages/poetry_editable)
|
||||
"###
|
||||
);
|
||||
|
||||
|
@ -1161,14 +1165,16 @@ fn install_editable() {
|
|||
|
||||
----- stderr -----
|
||||
Resolved 10 packages in [TIME]
|
||||
Prepared 6 packages in [TIME]
|
||||
Installed 6 packages in [TIME]
|
||||
Prepared 7 packages in [TIME]
|
||||
Uninstalled 1 package in [TIME]
|
||||
Installed 7 packages in [TIME]
|
||||
+ black==24.3.0
|
||||
+ click==8.1.7
|
||||
+ mypy-extensions==1.0.0
|
||||
+ packaging==24.0
|
||||
+ pathspec==0.12.1
|
||||
+ platformdirs==4.2.0
|
||||
~ poetry-editable==0.1.0 (from file://[WORKSPACE]/scripts/packages/poetry_editable)
|
||||
"###
|
||||
);
|
||||
}
|
||||
|
@ -3631,13 +3637,22 @@ fn config_settings_registry() {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn config_settings_path() {
|
||||
fn config_settings_path() -> Result<()> {
|
||||
let context = TestContext::new("3.12");
|
||||
|
||||
let requirements_txt = context.temp_dir.child("requirements.txt");
|
||||
requirements_txt.write_str(&format!(
|
||||
"-e {}",
|
||||
context
|
||||
.workspace_root
|
||||
.join("scripts/packages/setuptools_editable")
|
||||
.display()
|
||||
))?;
|
||||
|
||||
// Install the editable package.
|
||||
uv_snapshot!(context.filters(), context.pip_install()
|
||||
.arg("-e")
|
||||
.arg(context.workspace_root.join("scripts/packages/setuptools_editable")), @r###"
|
||||
.arg("-r")
|
||||
.arg("requirements.txt"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
@ -3660,8 +3675,8 @@ fn config_settings_path() {
|
|||
// Reinstalling with `--editable_mode=compat` should be a no-op; changes in build configuration
|
||||
// don't invalidate the environment.
|
||||
uv_snapshot!(context.filters(), context.pip_install()
|
||||
.arg("-e")
|
||||
.arg(context.workspace_root.join("scripts/packages/setuptools_editable"))
|
||||
.arg("-r")
|
||||
.arg("requirements.txt")
|
||||
.arg("-C")
|
||||
.arg("editable_mode=compat")
|
||||
, @r###"
|
||||
|
@ -3689,8 +3704,8 @@ fn config_settings_path() {
|
|||
// Install the editable package with `--editable_mode=compat`. We should ignore the cached
|
||||
// build configuration and rebuild.
|
||||
uv_snapshot!(context.filters(), context.pip_install()
|
||||
.arg("-e")
|
||||
.arg(context.workspace_root.join("scripts/packages/setuptools_editable"))
|
||||
.arg("-r")
|
||||
.arg("requirements.txt")
|
||||
.arg("-C")
|
||||
.arg("editable_mode=compat")
|
||||
, @r###"
|
||||
|
@ -3711,6 +3726,8 @@ fn config_settings_path() {
|
|||
.site_packages()
|
||||
.join("__editable___setuptools_editable_0_1_0_finder.py");
|
||||
assert!(!finder.exists());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Reinstall a duplicate package in a virtual environment.
|
||||
|
@ -3813,6 +3830,9 @@ fn install_symlink() {
|
|||
fn invalidate_editable_on_change() -> Result<()> {
|
||||
let context = TestContext::new("3.12");
|
||||
|
||||
let requirements_txt = context.temp_dir.child("requirements.txt");
|
||||
requirements_txt.write_str("-e ./editable")?;
|
||||
|
||||
// Create an editable package.
|
||||
let editable_dir = context.temp_dir.child("editable");
|
||||
editable_dir.create_dir_all()?;
|
||||
|
@ -3829,8 +3849,8 @@ requires-python = ">=3.8"
|
|||
)?;
|
||||
|
||||
uv_snapshot!(context.filters(), context.pip_install()
|
||||
.arg("--editable")
|
||||
.arg(editable_dir.path()), @r###"
|
||||
.arg("-r")
|
||||
.arg("requirements.txt"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
@ -3848,8 +3868,8 @@ requires-python = ">=3.8"
|
|||
|
||||
// Installing again should be a no-op.
|
||||
uv_snapshot!(context.filters(), context.pip_install()
|
||||
.arg("--editable")
|
||||
.arg(editable_dir.path()), @r###"
|
||||
.arg("-r")
|
||||
.arg("requirements.txt"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
@ -3873,8 +3893,8 @@ requires-python = ">=3.8"
|
|||
|
||||
// Installing again should update the package.
|
||||
uv_snapshot!(context.filters(), context.pip_install()
|
||||
.arg("--editable")
|
||||
.arg(editable_dir.path()), @r###"
|
||||
.arg("-r")
|
||||
.arg("requirements.txt"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
@ -3897,6 +3917,9 @@ requires-python = ">=3.8"
|
|||
fn editable_dynamic() -> Result<()> {
|
||||
let context = TestContext::new("3.12");
|
||||
|
||||
let requirements_txt = context.temp_dir.child("requirements.txt");
|
||||
requirements_txt.write_str("-e ./editable")?;
|
||||
|
||||
// Create an editable package with dynamic metadata.
|
||||
let editable_dir = context.temp_dir.child("editable");
|
||||
editable_dir.create_dir_all()?;
|
||||
|
@ -3910,16 +3933,16 @@ dynamic = ["dependencies"]
|
|||
requires-python = ">=3.11,<3.13"
|
||||
|
||||
[tool.setuptools.dynamic]
|
||||
dependencies = {file = ["requirements.txt"]}
|
||||
dependencies = {file = ["dependencies.txt"]}
|
||||
"#,
|
||||
)?;
|
||||
|
||||
let requirements_txt = editable_dir.child("requirements.txt");
|
||||
requirements_txt.write_str("anyio==4.0.0")?;
|
||||
let dependencies_txt = editable_dir.child("dependencies.txt");
|
||||
dependencies_txt.write_str("anyio==4.0.0")?;
|
||||
|
||||
uv_snapshot!(context.filters(), context.pip_install()
|
||||
.arg("--editable")
|
||||
.arg(editable_dir.path()), @r###"
|
||||
.arg("-r")
|
||||
.arg("requirements.txt"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
@ -3937,8 +3960,8 @@ dependencies = {file = ["requirements.txt"]}
|
|||
|
||||
// Installing again should not re-install, as we don't special-case dynamic metadata.
|
||||
uv_snapshot!(context.filters(), context.pip_install()
|
||||
.arg("--editable")
|
||||
.arg(editable_dir.path()), @r###"
|
||||
.arg("-r")
|
||||
.arg("requirements.txt"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
@ -3955,6 +3978,9 @@ dependencies = {file = ["requirements.txt"]}
|
|||
fn invalidate_path_on_change() -> Result<()> {
|
||||
let context = TestContext::new("3.12");
|
||||
|
||||
let requirements_txt = context.temp_dir.child("requirements.txt");
|
||||
requirements_txt.write_str("example @ ./editable")?;
|
||||
|
||||
// Create a local package.
|
||||
let editable_dir = context.temp_dir.child("editable");
|
||||
editable_dir.create_dir_all()?;
|
||||
|
@ -3971,14 +3997,13 @@ requires-python = ">=3.8"
|
|||
)?;
|
||||
|
||||
uv_snapshot!(context.filters(), context.pip_install()
|
||||
.arg("example @ .")
|
||||
.current_dir(editable_dir.path()), @r###"
|
||||
.arg("-r")
|
||||
.arg("requirements.txt"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Using Python 3.12.[X] environment at: [VENV]/
|
||||
Resolved 4 packages in [TIME]
|
||||
Prepared 4 packages in [TIME]
|
||||
Installed 4 packages in [TIME]
|
||||
|
@ -3991,14 +4016,13 @@ requires-python = ">=3.8"
|
|||
|
||||
// Installing again should be a no-op.
|
||||
uv_snapshot!(context.filters(), context.pip_install()
|
||||
.arg("example @ .")
|
||||
.current_dir(editable_dir.path()), @r###"
|
||||
.arg("-r")
|
||||
.arg("requirements.txt"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Using Python 3.12.[X] environment at: [VENV]/
|
||||
Audited 1 package in [TIME]
|
||||
"###
|
||||
);
|
||||
|
@ -4017,14 +4041,13 @@ requires-python = ">=3.8"
|
|||
|
||||
// Installing again should update the package.
|
||||
uv_snapshot!(context.filters(), context.pip_install()
|
||||
.arg("example @ .")
|
||||
.current_dir(editable_dir.path()), @r###"
|
||||
.arg("-r")
|
||||
.arg("requirements.txt"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Using Python 3.12.[X] environment at: [VENV]/
|
||||
Resolved 4 packages in [TIME]
|
||||
Prepared 2 packages in [TIME]
|
||||
Uninstalled 2 packages in [TIME]
|
||||
|
@ -4042,6 +4065,9 @@ requires-python = ">=3.8"
|
|||
fn invalidate_path_on_cache_key() -> Result<()> {
|
||||
let context = TestContext::new("3.12");
|
||||
|
||||
let requirements_txt = context.temp_dir.child("requirements.txt");
|
||||
requirements_txt.write_str("example @ ./editable")?;
|
||||
|
||||
// Create a local package.
|
||||
let editable_dir = context.temp_dir.child("editable");
|
||||
editable_dir.create_dir_all()?;
|
||||
|
@ -4054,25 +4080,24 @@ fn invalidate_path_on_cache_key() -> Result<()> {
|
|||
requires-python = ">=3.8"
|
||||
|
||||
[tool.uv]
|
||||
cache-keys = ["constraints.txt", { file = "requirements.txt" }]
|
||||
cache-keys = ["constraints.txt", { file = "overrides.txt" }]
|
||||
"#,
|
||||
)?;
|
||||
|
||||
let requirements_txt = editable_dir.child("requirements.txt");
|
||||
requirements_txt.write_str("idna")?;
|
||||
let overrides_txt = editable_dir.child("overrides.txt");
|
||||
overrides_txt.write_str("idna")?;
|
||||
|
||||
let constraints_txt = editable_dir.child("constraints.txt");
|
||||
constraints_txt.write_str("idna<3.4")?;
|
||||
|
||||
uv_snapshot!(context.filters(), context.pip_install()
|
||||
.arg("example @ .")
|
||||
.current_dir(editable_dir.path()), @r###"
|
||||
.arg("-r")
|
||||
.arg("requirements.txt"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Using Python 3.12.[X] environment at: [VENV]/
|
||||
Resolved 4 packages in [TIME]
|
||||
Prepared 4 packages in [TIME]
|
||||
Installed 4 packages in [TIME]
|
||||
|
@ -4085,14 +4110,13 @@ fn invalidate_path_on_cache_key() -> Result<()> {
|
|||
|
||||
// Installing again should be a no-op.
|
||||
uv_snapshot!(context.filters(), context.pip_install()
|
||||
.arg("example @ .")
|
||||
.current_dir(editable_dir.path()), @r###"
|
||||
.arg("-r")
|
||||
.arg("requirements.txt"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Using Python 3.12.[X] environment at: [VENV]/
|
||||
Audited 1 package in [TIME]
|
||||
"###
|
||||
);
|
||||
|
@ -4102,14 +4126,13 @@ fn invalidate_path_on_cache_key() -> Result<()> {
|
|||
|
||||
// Installing again should update the package.
|
||||
uv_snapshot!(context.filters(), context.pip_install()
|
||||
.arg("example @ .")
|
||||
.current_dir(editable_dir.path()), @r###"
|
||||
.arg("-r")
|
||||
.arg("requirements.txt"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Using Python 3.12.[X] environment at: [VENV]/
|
||||
Resolved 4 packages in [TIME]
|
||||
Prepared 1 package in [TIME]
|
||||
Uninstalled 1 package in [TIME]
|
||||
|
@ -4119,18 +4142,17 @@ fn invalidate_path_on_cache_key() -> Result<()> {
|
|||
);
|
||||
|
||||
// Modify the requirements file.
|
||||
requirements_txt.write_str("flask")?;
|
||||
overrides_txt.write_str("flask")?;
|
||||
|
||||
// Installing again should update the package.
|
||||
uv_snapshot!(context.filters(), context.pip_install()
|
||||
.arg("example @ .")
|
||||
.current_dir(editable_dir.path()), @r###"
|
||||
.arg("-r")
|
||||
.arg("requirements.txt"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Using Python 3.12.[X] environment at: [VENV]/
|
||||
Resolved 4 packages in [TIME]
|
||||
Prepared 1 package in [TIME]
|
||||
Uninstalled 1 package in [TIME]
|
||||
|
@ -4148,20 +4170,19 @@ fn invalidate_path_on_cache_key() -> Result<()> {
|
|||
requires-python = ">=3.8"
|
||||
|
||||
[tool.uv]
|
||||
cache-keys = [{ file = "requirements.txt" }, "constraints.txt"]
|
||||
cache-keys = [{ file = "overrides.txt" }, "constraints.txt"]
|
||||
"#,
|
||||
)?;
|
||||
|
||||
// Installing again should be a no-op, since `pyproject.toml` was not included as a cache key.
|
||||
uv_snapshot!(context.filters(), context.pip_install()
|
||||
.arg("example @ .")
|
||||
.current_dir(editable_dir.path()), @r###"
|
||||
.arg("-r")
|
||||
.arg("requirements.txt"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Using Python 3.12.[X] environment at: [VENV]/
|
||||
Audited 1 package in [TIME]
|
||||
"###
|
||||
);
|
||||
|
@ -4187,14 +4208,13 @@ fn invalidate_path_on_cache_key() -> Result<()> {
|
|||
|
||||
// Installing again should update the package.
|
||||
uv_snapshot!(context.filters(), context.pip_install()
|
||||
.arg("example @ .")
|
||||
.current_dir(editable_dir.path()), @r###"
|
||||
.arg("-r")
|
||||
.arg("requirements.txt"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Using Python 3.12.[X] environment at: [VENV]/
|
||||
Resolved 4 packages in [TIME]
|
||||
Prepared 1 package in [TIME]
|
||||
Uninstalled 1 package in [TIME]
|
||||
|
@ -4208,14 +4228,13 @@ fn invalidate_path_on_cache_key() -> Result<()> {
|
|||
|
||||
// Installing again should update the package.
|
||||
uv_snapshot!(context.filters(), context.pip_install()
|
||||
.arg("example @ .")
|
||||
.current_dir(editable_dir.path()), @r###"
|
||||
.arg("-r")
|
||||
.arg("requirements.txt"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Using Python 3.12.[X] environment at: [VENV]/
|
||||
Resolved 4 packages in [TIME]
|
||||
Prepared 1 package in [TIME]
|
||||
Uninstalled 1 package in [TIME]
|
||||
|
@ -4231,6 +4250,9 @@ fn invalidate_path_on_cache_key() -> Result<()> {
|
|||
fn invalidate_path_on_commit() -> Result<()> {
|
||||
let context = TestContext::new("3.12");
|
||||
|
||||
let requirements_txt = context.temp_dir.child("requirements.txt");
|
||||
requirements_txt.write_str("example @ ./editable")?;
|
||||
|
||||
// Create a local package.
|
||||
let editable_dir = context.temp_dir.child("editable");
|
||||
editable_dir.create_dir_all()?;
|
||||
|
@ -4264,14 +4286,13 @@ fn invalidate_path_on_commit() -> Result<()> {
|
|||
.write_str("1b6638fdb424e993d8354e75c55a3e524050c857")?;
|
||||
|
||||
uv_snapshot!(context.filters(), context.pip_install()
|
||||
.arg("example @ .")
|
||||
.current_dir(editable_dir.path()), @r###"
|
||||
.arg("-r")
|
||||
.arg("requirements.txt"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Using Python 3.12.[X] environment at: [VENV]/
|
||||
Resolved 4 packages in [TIME]
|
||||
Prepared 4 packages in [TIME]
|
||||
Installed 4 packages in [TIME]
|
||||
|
@ -4284,14 +4305,13 @@ fn invalidate_path_on_commit() -> Result<()> {
|
|||
|
||||
// Installing again should be a no-op.
|
||||
uv_snapshot!(context.filters(), context.pip_install()
|
||||
.arg("example @ .")
|
||||
.current_dir(editable_dir.path()), @r###"
|
||||
.arg("-r")
|
||||
.arg("requirements.txt"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Using Python 3.12.[X] environment at: [VENV]/
|
||||
Audited 1 package in [TIME]
|
||||
"###
|
||||
);
|
||||
|
@ -4307,14 +4327,13 @@ fn invalidate_path_on_commit() -> Result<()> {
|
|||
|
||||
// Installing again should update the package.
|
||||
uv_snapshot!(context.filters(), context.pip_install()
|
||||
.arg("example @ .")
|
||||
.current_dir(editable_dir.path()), @r###"
|
||||
.arg("-r")
|
||||
.arg("requirements.txt"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Using Python 3.12.[X] environment at: [VENV]/
|
||||
Resolved 4 packages in [TIME]
|
||||
Prepared 1 package in [TIME]
|
||||
Uninstalled 1 package in [TIME]
|
||||
|
@ -4330,6 +4349,9 @@ fn invalidate_path_on_commit() -> Result<()> {
|
|||
fn invalidate_path_on_env_var() -> Result<()> {
|
||||
let context = TestContext::new("3.12");
|
||||
|
||||
let requirements_txt = context.temp_dir.child("requirements.txt");
|
||||
requirements_txt.write_str(".")?;
|
||||
|
||||
// Create a local package.
|
||||
context.temp_dir.child("pyproject.toml").write_str(
|
||||
r#"[project]
|
||||
|
@ -4345,7 +4367,8 @@ fn invalidate_path_on_env_var() -> Result<()> {
|
|||
|
||||
// Install the package.
|
||||
uv_snapshot!(context.filters(), context.pip_install()
|
||||
.arg(".")
|
||||
.arg("-r")
|
||||
.arg("requirements.txt")
|
||||
.env_remove("FOO"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
|
@ -4364,7 +4387,8 @@ fn invalidate_path_on_env_var() -> Result<()> {
|
|||
|
||||
// Installing again should be a no-op.
|
||||
uv_snapshot!(context.filters(), context.pip_install()
|
||||
.arg(".")
|
||||
.arg("-r")
|
||||
.arg("requirements.txt")
|
||||
.env_remove("FOO"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
|
@ -4377,7 +4401,8 @@ fn invalidate_path_on_env_var() -> Result<()> {
|
|||
|
||||
// Installing again should update the package.
|
||||
uv_snapshot!(context.filters(), context.pip_install()
|
||||
.arg(".")
|
||||
.arg("-r")
|
||||
.arg("requirements.txt")
|
||||
.env("FOO", "BAR"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
|
@ -5646,7 +5671,7 @@ fn already_installed_dependent_editable() {
|
|||
);
|
||||
|
||||
// Request install of the first editable by full path again
|
||||
// We should audit the installed package
|
||||
// We should reinstall the package because it was explicitly requested
|
||||
uv_snapshot!(context.filters(), context.pip_install()
|
||||
.arg("-e")
|
||||
.arg(root_path.join("first_local")), @r###"
|
||||
|
@ -5655,7 +5680,11 @@ fn already_installed_dependent_editable() {
|
|||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Audited 1 package in [TIME]
|
||||
Resolved 1 package in [TIME]
|
||||
Prepared 1 package in [TIME]
|
||||
Uninstalled 1 package in [TIME]
|
||||
Installed 1 package in [TIME]
|
||||
~ first-local==0.1.0 (from file://[WORKSPACE]/scripts/packages/dependent_locals/first_local)
|
||||
"###
|
||||
);
|
||||
|
||||
|
@ -5745,8 +5774,8 @@ fn already_installed_local_path_dependent() {
|
|||
"###
|
||||
);
|
||||
|
||||
// Request install of the first local by full path again
|
||||
// We should audit the installed package
|
||||
// Request install of the first local by full path again.
|
||||
// We should rebuild and reinstall it.
|
||||
uv_snapshot!(context.filters(), context.pip_install()
|
||||
.arg(root_path.join("first_local")), @r###"
|
||||
success: true
|
||||
|
@ -5754,7 +5783,28 @@ fn already_installed_local_path_dependent() {
|
|||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Audited 1 package in [TIME]
|
||||
Resolved 1 package in [TIME]
|
||||
Prepared 1 package in [TIME]
|
||||
Uninstalled 1 package in [TIME]
|
||||
Installed 1 package in [TIME]
|
||||
~ first-local==0.1.0 (from file://[WORKSPACE]/scripts/packages/dependent_locals/first_local)
|
||||
"###
|
||||
);
|
||||
|
||||
// Request install of the first local by full path again, along with its name.
|
||||
// We should rebuild and reinstall it.
|
||||
uv_snapshot!(context.filters(), context.pip_install()
|
||||
.arg(format!("first-local @ {}", root_path.join("first_local").display())), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Resolved 1 package in [TIME]
|
||||
Prepared 1 package in [TIME]
|
||||
Uninstalled 1 package in [TIME]
|
||||
Installed 1 package in [TIME]
|
||||
~ first-local==0.1.0 (from file://[WORKSPACE]/scripts/packages/dependent_locals/first_local)
|
||||
"###
|
||||
);
|
||||
|
||||
|
@ -5792,10 +5842,11 @@ fn already_installed_local_path_dependent() {
|
|||
|
||||
----- stderr -----
|
||||
Resolved 2 packages in [TIME]
|
||||
Prepared 1 package in [TIME]
|
||||
Uninstalled 1 package in [TIME]
|
||||
Installed 1 package in [TIME]
|
||||
Prepared 2 packages in [TIME]
|
||||
Uninstalled 2 packages in [TIME]
|
||||
Installed 2 packages in [TIME]
|
||||
~ first-local==0.1.0 (from file://[WORKSPACE]/scripts/packages/dependent_locals/first_local)
|
||||
~ second-local==0.1.0 (from file://[WORKSPACE]/scripts/packages/dependent_locals/second_local)
|
||||
"###
|
||||
);
|
||||
|
||||
|
@ -5815,12 +5866,16 @@ fn already_installed_local_path_dependent() {
|
|||
|
||||
----- stderr -----
|
||||
Resolved 2 packages in [TIME]
|
||||
Audited 2 packages in [TIME]
|
||||
Prepared 1 package in [TIME]
|
||||
Uninstalled 1 package in [TIME]
|
||||
Installed 1 package in [TIME]
|
||||
~ second-local==0.1.0 (from file://[WORKSPACE]/scripts/packages/dependent_locals/second_local)
|
||||
"###
|
||||
);
|
||||
|
||||
// Request upgrade of the first package
|
||||
// A full path is specified and there's nothing to upgrade to so we should just audit
|
||||
// A full path is specified and there's nothing to upgrade, but because it was passed
|
||||
// explicitly, we reinstall
|
||||
uv_snapshot!(context.filters(), context.pip_install()
|
||||
.arg(root_path.join("first_local"))
|
||||
.arg(root_path.join("second_local"))
|
||||
|
@ -5836,7 +5891,11 @@ fn already_installed_local_path_dependent() {
|
|||
|
||||
----- stderr -----
|
||||
Resolved 2 packages in [TIME]
|
||||
Audited 2 packages in [TIME]
|
||||
Prepared 2 packages in [TIME]
|
||||
Uninstalled 2 packages in [TIME]
|
||||
Installed 2 packages in [TIME]
|
||||
~ first-local==0.1.0 (from file://[WORKSPACE]/scripts/packages/dependent_locals/first_local)
|
||||
~ second-local==0.1.0 (from file://[WORKSPACE]/scripts/packages/dependent_locals/second_local)
|
||||
"###
|
||||
);
|
||||
}
|
||||
|
@ -5957,6 +6016,7 @@ fn already_installed_local_version_of_remote_package() {
|
|||
|
||||
----- stderr -----
|
||||
Resolved 1 package in [TIME]
|
||||
Prepared 1 package in [TIME]
|
||||
Uninstalled 1 package in [TIME]
|
||||
Installed 1 package in [TIME]
|
||||
- anyio==4.3.0
|
||||
|
@ -9878,11 +9938,12 @@ fn no_sources_workspace_discovery() -> Result<()> {
|
|||
|
||||
----- stderr -----
|
||||
Resolved 4 packages in [TIME]
|
||||
Prepared 3 packages in [TIME]
|
||||
Uninstalled 1 package in [TIME]
|
||||
Installed 3 packages in [TIME]
|
||||
Prepared 4 packages in [TIME]
|
||||
Uninstalled 2 packages in [TIME]
|
||||
Installed 4 packages in [TIME]
|
||||
- anyio==2.0.0 (from file://[TEMP_DIR]/anyio)
|
||||
+ anyio==4.3.0
|
||||
~ foo==1.0.0 (from file://[TEMP_DIR]/)
|
||||
+ idna==3.6
|
||||
+ sniffio==1.3.1
|
||||
"###
|
||||
|
@ -9898,11 +9959,12 @@ fn no_sources_workspace_discovery() -> Result<()> {
|
|||
|
||||
----- stderr -----
|
||||
Resolved 2 packages in [TIME]
|
||||
Prepared 1 package in [TIME]
|
||||
Uninstalled 1 package in [TIME]
|
||||
Installed 1 package in [TIME]
|
||||
Prepared 2 packages in [TIME]
|
||||
Uninstalled 2 packages in [TIME]
|
||||
Installed 2 packages in [TIME]
|
||||
- anyio==4.3.0
|
||||
+ anyio==2.0.0 (from file://[TEMP_DIR]/anyio)
|
||||
~ foo==1.0.0 (from file://[TEMP_DIR]/)
|
||||
"###
|
||||
);
|
||||
|
||||
|
|
|
@ -26,6 +26,9 @@ If you're running into caching issues, uv includes a few escape hatches:
|
|||
- To force uv to ignore existing installed versions, pass `--reinstall` to any installation command
|
||||
(e.g., `uv sync --reinstall` or `uv pip install --reinstall ...`).
|
||||
|
||||
As a special case, uv will always rebuild and reinstall any local directory dependencies passed
|
||||
explicitly on the command-line (e.g., `uv pip install .`).
|
||||
|
||||
## Dynamic metadata
|
||||
|
||||
By default, uv will _only_ rebuild and reinstall local directory dependencies (e.g., editables) if
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue