mirror of
https://github.com/astral-sh/uv.git
synced 2025-11-01 20:31:12 +00:00
Use relative paths for --find-links and local registries (#6566)
## Summary See: https://github.com/astral-sh/uv/issues/6458
This commit is contained in:
parent
3902bb498d
commit
7fa265a11b
18 changed files with 834 additions and 264 deletions
|
|
@ -143,8 +143,6 @@ impl Display for FileLocation {
|
||||||
PartialOrd,
|
PartialOrd,
|
||||||
Ord,
|
Ord,
|
||||||
Hash,
|
Hash,
|
||||||
Serialize,
|
|
||||||
Deserialize,
|
|
||||||
rkyv::Archive,
|
rkyv::Archive,
|
||||||
rkyv::Deserialize,
|
rkyv::Deserialize,
|
||||||
rkyv::Serialize,
|
rkyv::Serialize,
|
||||||
|
|
@ -153,6 +151,24 @@ impl Display for FileLocation {
|
||||||
#[archive_attr(derive(Debug))]
|
#[archive_attr(derive(Debug))]
|
||||||
pub struct UrlString(String);
|
pub struct UrlString(String);
|
||||||
|
|
||||||
|
impl serde::Serialize for UrlString {
|
||||||
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: serde::ser::Serializer,
|
||||||
|
{
|
||||||
|
String::serialize(&self.0, serializer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'de> serde::de::Deserialize<'de> for UrlString {
|
||||||
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
|
where
|
||||||
|
D: serde::de::Deserializer<'de>,
|
||||||
|
{
|
||||||
|
String::deserialize(deserializer).map(UrlString)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl UrlString {
|
impl UrlString {
|
||||||
/// Converts a [`UrlString`] to a [`Url`].
|
/// Converts a [`UrlString`] to a [`Url`].
|
||||||
pub fn to_url(&self) -> Url {
|
pub fn to_url(&self) -> Url {
|
||||||
|
|
|
||||||
|
|
@ -112,8 +112,8 @@ impl FromStr for IndexUrl {
|
||||||
type Err = IndexUrlError;
|
type Err = IndexUrlError;
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
let url = if let Ok(path) = Path::new(s).canonicalize() {
|
let url = if Path::new(s).exists() {
|
||||||
VerbatimUrl::from_path(path)?
|
VerbatimUrl::from_absolute_path(std::path::absolute(s)?)?
|
||||||
} else {
|
} else {
|
||||||
VerbatimUrl::parse_url(s)?
|
VerbatimUrl::parse_url(s)?
|
||||||
};
|
};
|
||||||
|
|
@ -247,8 +247,8 @@ impl FromStr for FlatIndexLocation {
|
||||||
type Err = IndexUrlError;
|
type Err = IndexUrlError;
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
let url = if let Ok(path) = Path::new(s).canonicalize() {
|
let url = if Path::new(s).exists() {
|
||||||
VerbatimUrl::from_path(path)?
|
VerbatimUrl::from_absolute_path(std::path::absolute(s)?)?
|
||||||
} else {
|
} else {
|
||||||
VerbatimUrl::parse_url(s)?
|
VerbatimUrl::parse_url(s)?
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -40,11 +40,11 @@ impl UnnamedRequirementUrl for VerbatimUrl {
|
||||||
path: impl AsRef<Path>,
|
path: impl AsRef<Path>,
|
||||||
working_dir: impl AsRef<Path>,
|
working_dir: impl AsRef<Path>,
|
||||||
) -> Result<Self, VerbatimUrlError> {
|
) -> Result<Self, VerbatimUrlError> {
|
||||||
Self::parse_path(path, working_dir)
|
Self::from_path(path, working_dir)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_absolute_path(path: impl AsRef<Path>) -> Result<Self, Self::Err> {
|
fn parse_absolute_path(path: impl AsRef<Path>) -> Result<Self, Self::Err> {
|
||||||
Self::parse_absolute_path(path)
|
Self::from_absolute_path(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_unnamed_url(given: impl AsRef<str>) -> Result<Self, Self::Err> {
|
fn parse_unnamed_url(given: impl AsRef<str>) -> Result<Self, Self::Err> {
|
||||||
|
|
|
||||||
|
|
@ -40,14 +40,29 @@ impl VerbatimUrl {
|
||||||
Self { url, given: None }
|
Self { url, given: None }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a [`VerbatimUrl`] from a file path.
|
/// Parse a URL from a string, expanding any environment variables.
|
||||||
///
|
pub fn parse_url(given: impl AsRef<str>) -> Result<Self, ParseError> {
|
||||||
/// Assumes that the path is absolute.
|
let url = Url::parse(given.as_ref())?;
|
||||||
pub fn from_path(path: impl AsRef<Path>) -> Result<Self, VerbatimUrlError> {
|
Ok(Self { url, given: None })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parse a URL from an absolute or relative path.
|
||||||
|
#[cfg(feature = "non-pep508-extensions")] // PEP 508 arguably only allows absolute file URLs.
|
||||||
|
pub fn from_path(
|
||||||
|
path: impl AsRef<Path>,
|
||||||
|
base_dir: impl AsRef<Path>,
|
||||||
|
) -> Result<Self, VerbatimUrlError> {
|
||||||
|
debug_assert!(base_dir.as_ref().is_absolute(), "base dir must be absolute");
|
||||||
let path = path.as_ref();
|
let path = path.as_ref();
|
||||||
|
|
||||||
// Normalize the path.
|
// Convert the path to an absolute path, if necessary.
|
||||||
let path = normalize_absolute_path(path)
|
let path = if path.is_absolute() {
|
||||||
|
Cow::Borrowed(path)
|
||||||
|
} else {
|
||||||
|
Cow::Owned(base_dir.as_ref().join(path))
|
||||||
|
};
|
||||||
|
|
||||||
|
let path = normalize_absolute_path(&path)
|
||||||
.map_err(|err| VerbatimUrlError::Normalization(path.to_path_buf(), err))?;
|
.map_err(|err| VerbatimUrlError::Normalization(path.to_path_buf(), err))?;
|
||||||
|
|
||||||
// Extract the fragment, if it exists.
|
// Extract the fragment, if it exists.
|
||||||
|
|
@ -65,60 +80,20 @@ impl VerbatimUrl {
|
||||||
Ok(Self { url, given: None })
|
Ok(Self { url, given: None })
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parse a URL from a string, expanding any environment variables.
|
|
||||||
pub fn parse_url(given: impl AsRef<str>) -> Result<Self, ParseError> {
|
|
||||||
let url = Url::parse(given.as_ref())?;
|
|
||||||
Ok(Self { url, given: None })
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Parse a URL from an absolute or relative path.
|
|
||||||
#[cfg(feature = "non-pep508-extensions")] // PEP 508 arguably only allows absolute file URLs.
|
|
||||||
pub fn parse_path(
|
|
||||||
path: impl AsRef<Path>,
|
|
||||||
base_dir: impl AsRef<Path>,
|
|
||||||
) -> Result<Self, VerbatimUrlError> {
|
|
||||||
debug_assert!(base_dir.as_ref().is_absolute(), "base dir must be absolute");
|
|
||||||
let path = path.as_ref();
|
|
||||||
|
|
||||||
// Convert the path to an absolute path, if necessary.
|
|
||||||
let path = if path.is_absolute() {
|
|
||||||
path.to_path_buf()
|
|
||||||
} else {
|
|
||||||
base_dir.as_ref().join(path)
|
|
||||||
};
|
|
||||||
|
|
||||||
let path = normalize_absolute_path(&path)
|
|
||||||
.map_err(|err| VerbatimUrlError::Normalization(path.clone(), err))?;
|
|
||||||
|
|
||||||
// Extract the fragment, if it exists.
|
|
||||||
let (path, fragment) = split_fragment(&path);
|
|
||||||
|
|
||||||
// Convert to a URL.
|
|
||||||
let mut url = Url::from_file_path(path.clone())
|
|
||||||
.map_err(|()| VerbatimUrlError::UrlConversion(path.to_path_buf()))?;
|
|
||||||
|
|
||||||
// Set the fragment, if it exists.
|
|
||||||
if let Some(fragment) = fragment {
|
|
||||||
url.set_fragment(Some(fragment));
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(Self { url, given: None })
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Parse a URL from an absolute path.
|
/// Parse a URL from an absolute path.
|
||||||
pub fn parse_absolute_path(path: impl AsRef<Path>) -> Result<Self, VerbatimUrlError> {
|
pub fn from_absolute_path(path: impl AsRef<Path>) -> Result<Self, VerbatimUrlError> {
|
||||||
let path = path.as_ref();
|
let path = path.as_ref();
|
||||||
|
|
||||||
// Convert the path to an absolute path, if necessary.
|
// Error if the path is relative.
|
||||||
let path = if path.is_absolute() {
|
let path = if path.is_absolute() {
|
||||||
path.to_path_buf()
|
path
|
||||||
} else {
|
} else {
|
||||||
return Err(VerbatimUrlError::WorkingDirectory(path.to_path_buf()));
|
return Err(VerbatimUrlError::WorkingDirectory(path.to_path_buf()));
|
||||||
};
|
};
|
||||||
|
|
||||||
// Normalize the path.
|
// Normalize the path.
|
||||||
let path = normalize_absolute_path(&path)
|
let path = normalize_absolute_path(path)
|
||||||
.map_err(|err| VerbatimUrlError::Normalization(path.clone(), err))?;
|
.map_err(|err| VerbatimUrlError::Normalization(path.to_path_buf(), err))?;
|
||||||
|
|
||||||
// Extract the fragment, if it exists.
|
// Extract the fragment, if it exists.
|
||||||
let (path, fragment) = split_fragment(&path);
|
let (path, fragment) = split_fragment(&path);
|
||||||
|
|
@ -252,14 +227,11 @@ impl Pep508Url for VerbatimUrl {
|
||||||
|
|
||||||
#[cfg(feature = "non-pep508-extensions")]
|
#[cfg(feature = "non-pep508-extensions")]
|
||||||
if let Some(working_dir) = working_dir {
|
if let Some(working_dir) = working_dir {
|
||||||
return Ok(VerbatimUrl::parse_path(path.as_ref(), working_dir)?
|
return Ok(VerbatimUrl::from_path(path.as_ref(), working_dir)?
|
||||||
.with_given(url.to_string()));
|
.with_given(url.to_string()));
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(
|
Ok(VerbatimUrl::from_absolute_path(path.as_ref())?.with_given(url.to_string()))
|
||||||
VerbatimUrl::parse_absolute_path(path.as_ref())?
|
|
||||||
.with_given(url.to_string()),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ex) `https://download.pytorch.org/whl/torch_stable.html`
|
// Ex) `https://download.pytorch.org/whl/torch_stable.html`
|
||||||
|
|
@ -272,11 +244,11 @@ impl Pep508Url for VerbatimUrl {
|
||||||
_ => {
|
_ => {
|
||||||
#[cfg(feature = "non-pep508-extensions")]
|
#[cfg(feature = "non-pep508-extensions")]
|
||||||
if let Some(working_dir) = working_dir {
|
if let Some(working_dir) = working_dir {
|
||||||
return Ok(VerbatimUrl::parse_path(expanded.as_ref(), working_dir)?
|
return Ok(VerbatimUrl::from_path(expanded.as_ref(), working_dir)?
|
||||||
.with_given(url.to_string()));
|
.with_given(url.to_string()));
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(VerbatimUrl::parse_absolute_path(expanded.as_ref())?
|
Ok(VerbatimUrl::from_absolute_path(expanded.as_ref())?
|
||||||
.with_given(url.to_string()))
|
.with_given(url.to_string()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -284,11 +256,11 @@ impl Pep508Url for VerbatimUrl {
|
||||||
// Ex) `../editable/`
|
// Ex) `../editable/`
|
||||||
#[cfg(feature = "non-pep508-extensions")]
|
#[cfg(feature = "non-pep508-extensions")]
|
||||||
if let Some(working_dir) = working_dir {
|
if let Some(working_dir) = working_dir {
|
||||||
return Ok(VerbatimUrl::parse_path(expanded.as_ref(), working_dir)?
|
return Ok(VerbatimUrl::from_path(expanded.as_ref(), working_dir)?
|
||||||
.with_given(url.to_string()));
|
.with_given(url.to_string()));
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(VerbatimUrl::parse_absolute_path(expanded.as_ref())?.with_given(url.to_string()))
|
Ok(VerbatimUrl::from_absolute_path(expanded.as_ref())?.with_given(url.to_string()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -60,7 +60,7 @@ impl UnnamedRequirementUrl for VerbatimParsedUrl {
|
||||||
path: impl AsRef<Path>,
|
path: impl AsRef<Path>,
|
||||||
working_dir: impl AsRef<Path>,
|
working_dir: impl AsRef<Path>,
|
||||||
) -> Result<Self, Self::Err> {
|
) -> Result<Self, Self::Err> {
|
||||||
let verbatim = VerbatimUrl::parse_path(&path, &working_dir)?;
|
let verbatim = VerbatimUrl::from_path(&path, &working_dir)?;
|
||||||
let verbatim_path = verbatim.as_path()?;
|
let verbatim_path = verbatim.as_path()?;
|
||||||
let is_dir = if let Ok(metadata) = verbatim_path.metadata() {
|
let is_dir = if let Ok(metadata) = verbatim_path.metadata() {
|
||||||
metadata.is_dir()
|
metadata.is_dir()
|
||||||
|
|
@ -89,7 +89,7 @@ impl UnnamedRequirementUrl for VerbatimParsedUrl {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_absolute_path(path: impl AsRef<Path>) -> Result<Self, Self::Err> {
|
fn parse_absolute_path(path: impl AsRef<Path>) -> Result<Self, Self::Err> {
|
||||||
let verbatim = VerbatimUrl::parse_absolute_path(&path)?;
|
let verbatim = VerbatimUrl::from_absolute_path(&path)?;
|
||||||
let verbatim_path = verbatim.as_path()?;
|
let verbatim_path = verbatim.as_path()?;
|
||||||
let is_dir = if let Ok(metadata) = verbatim_path.metadata() {
|
let is_dir = if let Ok(metadata) = verbatim_path.metadata() {
|
||||||
metadata.is_dir()
|
metadata.is_dir()
|
||||||
|
|
|
||||||
|
|
@ -507,7 +507,8 @@ impl RequirementSource {
|
||||||
ext,
|
ext,
|
||||||
url,
|
url,
|
||||||
} => Ok(Self::Path {
|
} => Ok(Self::Path {
|
||||||
install_path: relative_to(&install_path, path)?,
|
install_path: relative_to(&install_path, path)
|
||||||
|
.or_else(|_| std::path::absolute(install_path))?,
|
||||||
ext,
|
ext,
|
||||||
url,
|
url,
|
||||||
}),
|
}),
|
||||||
|
|
@ -516,7 +517,8 @@ impl RequirementSource {
|
||||||
editable,
|
editable,
|
||||||
url,
|
url,
|
||||||
} => Ok(Self::Directory {
|
} => Ok(Self::Directory {
|
||||||
install_path: relative_to(&install_path, path)?,
|
install_path: relative_to(&install_path, path)
|
||||||
|
.or_else(|_| std::path::absolute(install_path))?,
|
||||||
editable,
|
editable,
|
||||||
url,
|
url,
|
||||||
}),
|
}),
|
||||||
|
|
@ -744,7 +746,7 @@ impl TryFrom<RequirementSourceWire> for RequirementSource {
|
||||||
// sources in the lockfile, we replace the URL anyway.
|
// sources in the lockfile, we replace the URL anyway.
|
||||||
RequirementSourceWire::Path { path } => {
|
RequirementSourceWire::Path { path } => {
|
||||||
let path = PathBuf::from(path);
|
let path = PathBuf::from(path);
|
||||||
let url = VerbatimUrl::parse_path(&path, &*CWD)?;
|
let url = VerbatimUrl::from_path(&path, &*CWD)?;
|
||||||
Ok(Self::Path {
|
Ok(Self::Path {
|
||||||
ext: DistExtension::from_path(path.as_path())
|
ext: DistExtension::from_path(path.as_path())
|
||||||
.map_err(|err| ParsedUrlError::MissingExtensionPath(path.clone(), err))?,
|
.map_err(|err| ParsedUrlError::MissingExtensionPath(path.clone(), err))?,
|
||||||
|
|
@ -754,7 +756,7 @@ impl TryFrom<RequirementSourceWire> for RequirementSource {
|
||||||
}
|
}
|
||||||
RequirementSourceWire::Directory { directory } => {
|
RequirementSourceWire::Directory { directory } => {
|
||||||
let directory = PathBuf::from(directory);
|
let directory = PathBuf::from(directory);
|
||||||
let url = VerbatimUrl::parse_path(&directory, &*CWD)?;
|
let url = VerbatimUrl::from_path(&directory, &*CWD)?;
|
||||||
Ok(Self::Directory {
|
Ok(Self::Directory {
|
||||||
install_path: directory,
|
install_path: directory,
|
||||||
editable: false,
|
editable: false,
|
||||||
|
|
@ -763,7 +765,7 @@ impl TryFrom<RequirementSourceWire> for RequirementSource {
|
||||||
}
|
}
|
||||||
RequirementSourceWire::Editable { editable } => {
|
RequirementSourceWire::Editable { editable } => {
|
||||||
let editable = PathBuf::from(editable);
|
let editable = PathBuf::from(editable);
|
||||||
let url = VerbatimUrl::parse_path(&editable, &*CWD)?;
|
let url = VerbatimUrl::from_path(&editable, &*CWD)?;
|
||||||
Ok(Self::Directory {
|
Ok(Self::Directory {
|
||||||
install_path: editable,
|
install_path: editable,
|
||||||
editable: true,
|
editable: true,
|
||||||
|
|
@ -788,7 +790,7 @@ pub fn redact_git_credentials(url: &mut Url) {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use pep508_rs::{MarkerTree, VerbatimUrl};
|
use pep508_rs::{MarkerTree, VerbatimUrl};
|
||||||
|
|
||||||
|
|
@ -823,7 +825,7 @@ mod tests {
|
||||||
source: RequirementSource::Directory {
|
source: RequirementSource::Directory {
|
||||||
install_path: PathBuf::from(path),
|
install_path: PathBuf::from(path),
|
||||||
editable: false,
|
editable: false,
|
||||||
url: VerbatimUrl::from_path(Path::new(path)).unwrap(),
|
url: VerbatimUrl::from_absolute_path(path).unwrap(),
|
||||||
},
|
},
|
||||||
origin: None,
|
origin: None,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -484,12 +484,17 @@ fn parse_entry(
|
||||||
} else if s.eat_if("-i") || s.eat_if("--index-url") {
|
} else if s.eat_if("-i") || s.eat_if("--index-url") {
|
||||||
let given = parse_value(content, s, |c: char| !['\n', '\r', '#'].contains(&c))?;
|
let given = parse_value(content, s, |c: char| !['\n', '\r', '#'].contains(&c))?;
|
||||||
let expanded = expand_env_vars(given);
|
let expanded = expand_env_vars(given);
|
||||||
let url = if let Ok(path) = Path::new(expanded.as_ref()).canonicalize() {
|
let url = if let Some(path) = std::path::absolute(expanded.as_ref())
|
||||||
VerbatimUrl::from_path(path).map_err(|err| RequirementsTxtParserError::VerbatimUrl {
|
.ok()
|
||||||
source: err,
|
.filter(|path| path.exists())
|
||||||
url: given.to_string(),
|
{
|
||||||
start,
|
VerbatimUrl::from_absolute_path(path).map_err(|err| {
|
||||||
end: s.cursor(),
|
RequirementsTxtParserError::VerbatimUrl {
|
||||||
|
source: err,
|
||||||
|
url: given.to_string(),
|
||||||
|
start,
|
||||||
|
end: s.cursor(),
|
||||||
|
}
|
||||||
})?
|
})?
|
||||||
} else {
|
} else {
|
||||||
VerbatimUrl::parse_url(expanded.as_ref()).map_err(|err| {
|
VerbatimUrl::parse_url(expanded.as_ref()).map_err(|err| {
|
||||||
|
|
@ -505,12 +510,17 @@ fn parse_entry(
|
||||||
} else if s.eat_if("--extra-index-url") {
|
} else if s.eat_if("--extra-index-url") {
|
||||||
let given = parse_value(content, s, |c: char| !['\n', '\r', '#'].contains(&c))?;
|
let given = parse_value(content, s, |c: char| !['\n', '\r', '#'].contains(&c))?;
|
||||||
let expanded = expand_env_vars(given);
|
let expanded = expand_env_vars(given);
|
||||||
let url = if let Ok(path) = Path::new(expanded.as_ref()).canonicalize() {
|
let url = if let Some(path) = std::path::absolute(expanded.as_ref())
|
||||||
VerbatimUrl::from_path(path).map_err(|err| RequirementsTxtParserError::VerbatimUrl {
|
.ok()
|
||||||
source: err,
|
.filter(|path| path.exists())
|
||||||
url: given.to_string(),
|
{
|
||||||
start,
|
VerbatimUrl::from_absolute_path(path).map_err(|err| {
|
||||||
end: s.cursor(),
|
RequirementsTxtParserError::VerbatimUrl {
|
||||||
|
source: err,
|
||||||
|
url: given.to_string(),
|
||||||
|
start,
|
||||||
|
end: s.cursor(),
|
||||||
|
}
|
||||||
})?
|
})?
|
||||||
} else {
|
} else {
|
||||||
VerbatimUrl::parse_url(expanded.as_ref()).map_err(|err| {
|
VerbatimUrl::parse_url(expanded.as_ref()).map_err(|err| {
|
||||||
|
|
@ -528,12 +538,17 @@ fn parse_entry(
|
||||||
} else if s.eat_if("--find-links") || s.eat_if("-f") {
|
} else if s.eat_if("--find-links") || s.eat_if("-f") {
|
||||||
let given = parse_value(content, s, |c: char| !['\n', '\r', '#'].contains(&c))?;
|
let given = parse_value(content, s, |c: char| !['\n', '\r', '#'].contains(&c))?;
|
||||||
let expanded = expand_env_vars(given);
|
let expanded = expand_env_vars(given);
|
||||||
let url = if let Ok(path) = Path::new(expanded.as_ref()).canonicalize() {
|
let url = if let Some(path) = std::path::absolute(expanded.as_ref())
|
||||||
VerbatimUrl::from_path(path).map_err(|err| RequirementsTxtParserError::VerbatimUrl {
|
.ok()
|
||||||
source: err,
|
.filter(|path| path.exists())
|
||||||
url: given.to_string(),
|
{
|
||||||
start,
|
VerbatimUrl::from_absolute_path(path).map_err(|err| {
|
||||||
end: s.cursor(),
|
RequirementsTxtParserError::VerbatimUrl {
|
||||||
|
source: err,
|
||||||
|
url: given.to_string(),
|
||||||
|
start,
|
||||||
|
end: s.cursor(),
|
||||||
|
}
|
||||||
})?
|
})?
|
||||||
} else {
|
} else {
|
||||||
VerbatimUrl::parse_url(expanded.as_ref()).map_err(|err| {
|
VerbatimUrl::parse_url(expanded.as_ref()).map_err(|err| {
|
||||||
|
|
|
||||||
|
|
@ -142,7 +142,7 @@ impl LoweredRequirement {
|
||||||
// relative to workspace: `packages/current_project`
|
// relative to workspace: `packages/current_project`
|
||||||
// workspace lock root: `../current_workspace`
|
// workspace lock root: `../current_workspace`
|
||||||
// relative to main workspace: `../current_workspace/packages/current_project`
|
// relative to main workspace: `../current_workspace/packages/current_project`
|
||||||
let url = VerbatimUrl::parse_absolute_path(member.root())?;
|
let url = VerbatimUrl::from_absolute_path(member.root())?;
|
||||||
let install_path = url.to_file_path().map_err(|()| {
|
let install_path = url.to_file_path().map_err(|()| {
|
||||||
LoweringError::RelativeTo(io::Error::new(
|
LoweringError::RelativeTo(io::Error::new(
|
||||||
io::ErrorKind::Other,
|
io::ErrorKind::Other,
|
||||||
|
|
@ -360,7 +360,7 @@ fn path_source(
|
||||||
Origin::Project => project_dir,
|
Origin::Project => project_dir,
|
||||||
Origin::Workspace => workspace_root,
|
Origin::Workspace => workspace_root,
|
||||||
};
|
};
|
||||||
let url = VerbatimUrl::parse_path(path, base)?.with_given(path.to_string_lossy());
|
let url = VerbatimUrl::from_path(path, base)?.with_given(path.to_string_lossy());
|
||||||
let install_path = url.to_file_path().map_err(|()| {
|
let install_path = url.to_file_path().map_err(|()| {
|
||||||
LoweringError::RelativeTo(io::Error::new(
|
LoweringError::RelativeTo(io::Error::new(
|
||||||
io::ErrorKind::Other,
|
io::ErrorKind::Other,
|
||||||
|
|
|
||||||
|
|
@ -270,6 +270,9 @@ pub fn canonicalize_executable(path: impl AsRef<Path>) -> std::io::Result<PathBu
|
||||||
/// `lib/python/site-packages/foo/__init__.py` and `lib/python/site-packages` -> `foo/__init__.py`
|
/// `lib/python/site-packages/foo/__init__.py` and `lib/python/site-packages` -> `foo/__init__.py`
|
||||||
/// `lib/marker.txt` and `lib/python/site-packages` -> `../../marker.txt`
|
/// `lib/marker.txt` and `lib/python/site-packages` -> `../../marker.txt`
|
||||||
/// `bin/foo_launcher` and `lib/python/site-packages` -> `../../../bin/foo_launcher`
|
/// `bin/foo_launcher` and `lib/python/site-packages` -> `../../../bin/foo_launcher`
|
||||||
|
///
|
||||||
|
/// Returns `Err` if there is no relative path between `path` and `base` (for example, if the paths
|
||||||
|
/// are on different drives on Windows).
|
||||||
pub fn relative_to(
|
pub fn relative_to(
|
||||||
path: impl AsRef<Path>,
|
path: impl AsRef<Path>,
|
||||||
base: impl AsRef<Path>,
|
base: impl AsRef<Path>,
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ use distribution_types::{
|
||||||
UrlString,
|
UrlString,
|
||||||
};
|
};
|
||||||
use pep440_rs::Version;
|
use pep440_rs::Version;
|
||||||
use pep508_rs::{MarkerEnvironment, MarkerTree, VerbatimUrl, VerbatimUrlError};
|
use pep508_rs::{split_scheme, MarkerEnvironment, MarkerTree, VerbatimUrl, VerbatimUrlError};
|
||||||
use platform_tags::{TagCompatibility, TagPriority, Tags};
|
use platform_tags::{TagCompatibility, TagPriority, Tags};
|
||||||
use pypi_types::{
|
use pypi_types::{
|
||||||
redact_git_credentials, HashDigest, ParsedArchiveUrl, ParsedGitUrl, Requirement,
|
redact_git_credentials, HashDigest, ParsedArchiveUrl, ParsedGitUrl, Requirement,
|
||||||
|
|
@ -762,12 +762,55 @@ impl Lock {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Collect the set of available indexes (both `--index-url` and `--find-links` entries).
|
// Collect the set of available indexes (both `--index-url` and `--find-links` entries).
|
||||||
let indexes = indexes.map(|locations| {
|
let remotes = indexes.map(|locations| {
|
||||||
locations
|
locations
|
||||||
.indexes()
|
.indexes()
|
||||||
.map(IndexUrl::redacted)
|
.filter_map(|index_url| match index_url {
|
||||||
.chain(locations.flat_index().map(FlatIndexLocation::redacted))
|
IndexUrl::Pypi(_) | IndexUrl::Url(_) => {
|
||||||
.map(UrlString::from)
|
Some(UrlString::from(index_url.redacted()))
|
||||||
|
}
|
||||||
|
IndexUrl::Path(_) => None,
|
||||||
|
})
|
||||||
|
.chain(
|
||||||
|
locations
|
||||||
|
.flat_index()
|
||||||
|
.filter_map(|index_url| match index_url {
|
||||||
|
FlatIndexLocation::Url(_) => {
|
||||||
|
Some(UrlString::from(index_url.redacted()))
|
||||||
|
}
|
||||||
|
FlatIndexLocation::Path(_) => None,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.collect::<BTreeSet<_>>()
|
||||||
|
});
|
||||||
|
|
||||||
|
let locals = indexes.map(|locations| {
|
||||||
|
locations
|
||||||
|
.indexes()
|
||||||
|
.filter_map(|index_url| match index_url {
|
||||||
|
IndexUrl::Pypi(_) | IndexUrl::Url(_) => None,
|
||||||
|
IndexUrl::Path(index_url) => {
|
||||||
|
let path = index_url.to_file_path().ok()?;
|
||||||
|
let path = relative_to(&path, workspace.install_path())
|
||||||
|
.or_else(|_| std::path::absolute(path))
|
||||||
|
.ok()?;
|
||||||
|
Some(path)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.chain(
|
||||||
|
locations
|
||||||
|
.flat_index()
|
||||||
|
.filter_map(|index_url| match index_url {
|
||||||
|
FlatIndexLocation::Url(_) => None,
|
||||||
|
FlatIndexLocation::Path(index_url) => {
|
||||||
|
let path = index_url.to_file_path().ok()?;
|
||||||
|
let path = relative_to(&path, workspace.install_path())
|
||||||
|
.or_else(|_| std::path::absolute(path))
|
||||||
|
.ok()?;
|
||||||
|
Some(path)
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
)
|
||||||
.collect::<BTreeSet<_>>()
|
.collect::<BTreeSet<_>>()
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -789,16 +832,29 @@ impl Lock {
|
||||||
while let Some(package) = queue.pop_front() {
|
while let Some(package) = queue.pop_front() {
|
||||||
// If the lockfile references an index that was not provided, we can't validate it.
|
// If the lockfile references an index that was not provided, we can't validate it.
|
||||||
if let Source::Registry(index) = &package.id.source {
|
if let Source::Registry(index) = &package.id.source {
|
||||||
if indexes
|
match index {
|
||||||
.as_ref()
|
RegistrySource::Url(url) => {
|
||||||
.is_some_and(|indexes| !indexes.contains(index))
|
if remotes
|
||||||
{
|
.as_ref()
|
||||||
return Ok(SatisfiesResult::MissingIndex(
|
.is_some_and(|remotes| !remotes.contains(url))
|
||||||
&package.id.name,
|
{
|
||||||
&package.id.version,
|
return Ok(SatisfiesResult::MissingRemoteIndex(
|
||||||
index,
|
&package.id.name,
|
||||||
));
|
&package.id.version,
|
||||||
}
|
url,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RegistrySource::Path(path) => {
|
||||||
|
if locals.as_ref().is_some_and(|locals| !locals.contains(path)) {
|
||||||
|
return Ok(SatisfiesResult::MissingLocalIndex(
|
||||||
|
&package.id.name,
|
||||||
|
&package.id.version,
|
||||||
|
path,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the package is immutable, we don't need to validate it (or its dependencies).
|
// If the package is immutable, we don't need to validate it (or its dependencies).
|
||||||
|
|
@ -935,8 +991,10 @@ pub enum SatisfiesResult<'lock> {
|
||||||
MismatchedOverrides(BTreeSet<Requirement>, BTreeSet<Requirement>),
|
MismatchedOverrides(BTreeSet<Requirement>, BTreeSet<Requirement>),
|
||||||
/// The lockfile is missing a workspace member.
|
/// The lockfile is missing a workspace member.
|
||||||
MissingRoot(PackageName),
|
MissingRoot(PackageName),
|
||||||
/// The lockfile referenced an index that was not provided
|
/// The lockfile referenced a remote index that was not provided
|
||||||
MissingIndex(&'lock PackageName, &'lock Version, &'lock UrlString),
|
MissingRemoteIndex(&'lock PackageName, &'lock Version, &'lock UrlString),
|
||||||
|
/// The lockfile referenced a local index that was not provided
|
||||||
|
MissingLocalIndex(&'lock PackageName, &'lock Version, &'lock PathBuf),
|
||||||
/// The resolver failed to generate metadata for a given package.
|
/// The resolver failed to generate metadata for a given package.
|
||||||
MissingMetadata(&'lock PackageName, &'lock Version),
|
MissingMetadata(&'lock PackageName, &'lock Version),
|
||||||
/// A package in the lockfile contains different `requires-dist` metadata than expected.
|
/// A package in the lockfile contains different `requires-dist` metadata than expected.
|
||||||
|
|
@ -1241,11 +1299,11 @@ impl Package {
|
||||||
fn to_dist(&self, workspace_root: &Path, tags: &Tags) -> Result<Dist, LockError> {
|
fn to_dist(&self, workspace_root: &Path, tags: &Tags) -> Result<Dist, LockError> {
|
||||||
if let Some(best_wheel_index) = self.find_best_wheel(tags) {
|
if let Some(best_wheel_index) = self.find_best_wheel(tags) {
|
||||||
return match &self.id.source {
|
return match &self.id.source {
|
||||||
Source::Registry(url) => {
|
Source::Registry(source) => {
|
||||||
let wheels = self
|
let wheels = self
|
||||||
.wheels
|
.wheels
|
||||||
.iter()
|
.iter()
|
||||||
.map(|wheel| wheel.to_registry_dist(url.to_url()))
|
.map(|wheel| wheel.to_registry_dist(source, workspace_root))
|
||||||
.collect::<Result<_, LockError>>()?;
|
.collect::<Result<_, LockError>>()?;
|
||||||
let reg_built_dist = RegistryBuiltDist {
|
let reg_built_dist = RegistryBuiltDist {
|
||||||
wheels,
|
wheels,
|
||||||
|
|
@ -1295,7 +1353,7 @@ impl Package {
|
||||||
}
|
}
|
||||||
.into()),
|
.into()),
|
||||||
};
|
};
|
||||||
}
|
};
|
||||||
|
|
||||||
if let Some(sdist) = self.to_source_dist(workspace_root)? {
|
if let Some(sdist) = self.to_source_dist(workspace_root)? {
|
||||||
return Ok(Dist::Source(sdist));
|
return Ok(Dist::Source(sdist));
|
||||||
|
|
@ -1388,7 +1446,7 @@ impl Package {
|
||||||
};
|
};
|
||||||
distribution_types::SourceDist::DirectUrl(direct_dist)
|
distribution_types::SourceDist::DirectUrl(direct_dist)
|
||||||
}
|
}
|
||||||
Source::Registry(url) => {
|
Source::Registry(RegistrySource::Url(url)) => {
|
||||||
let Some(ref sdist) = self.sdist else {
|
let Some(ref sdist) = self.sdist else {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
};
|
};
|
||||||
|
|
@ -1418,6 +1476,51 @@ impl Package {
|
||||||
});
|
});
|
||||||
let index = IndexUrl::Url(VerbatimUrl::from_url(url.to_url()));
|
let index = IndexUrl::Url(VerbatimUrl::from_url(url.to_url()));
|
||||||
|
|
||||||
|
let reg_dist = RegistrySourceDist {
|
||||||
|
name: self.id.name.clone(),
|
||||||
|
version: self.id.version.clone(),
|
||||||
|
file,
|
||||||
|
ext,
|
||||||
|
index,
|
||||||
|
wheels: vec![],
|
||||||
|
};
|
||||||
|
distribution_types::SourceDist::Registry(reg_dist)
|
||||||
|
}
|
||||||
|
Source::Registry(RegistrySource::Path(path)) => {
|
||||||
|
let Some(ref sdist) = self.sdist else {
|
||||||
|
return Ok(None);
|
||||||
|
};
|
||||||
|
|
||||||
|
let file_path = sdist.path().ok_or_else(|| LockErrorKind::MissingPath {
|
||||||
|
name: self.id.name.clone(),
|
||||||
|
version: self.id.version.clone(),
|
||||||
|
})?;
|
||||||
|
let file_url = Url::from_file_path(workspace_root.join(path).join(file_path))
|
||||||
|
.map_err(|()| LockErrorKind::PathToUrl)?;
|
||||||
|
let filename = sdist
|
||||||
|
.filename()
|
||||||
|
.ok_or_else(|| LockErrorKind::MissingFilename {
|
||||||
|
id: self.id.clone(),
|
||||||
|
})?;
|
||||||
|
let ext = SourceDistExtension::from_path(filename.as_ref())?;
|
||||||
|
let file = Box::new(distribution_types::File {
|
||||||
|
dist_info_metadata: false,
|
||||||
|
filename: filename.to_string(),
|
||||||
|
hashes: sdist
|
||||||
|
.hash()
|
||||||
|
.map(|hash| vec![hash.0.clone()])
|
||||||
|
.unwrap_or_default(),
|
||||||
|
requires_python: None,
|
||||||
|
size: sdist.size(),
|
||||||
|
upload_time_utc_ms: None,
|
||||||
|
url: FileLocation::AbsoluteUrl(UrlString::from(file_url)),
|
||||||
|
yanked: None,
|
||||||
|
});
|
||||||
|
let index = IndexUrl::Path(
|
||||||
|
VerbatimUrl::from_absolute_path(workspace_root.join(path))
|
||||||
|
.map_err(LockErrorKind::RegistryVerbatimUrl)?,
|
||||||
|
);
|
||||||
|
|
||||||
let reg_dist = RegistrySourceDist {
|
let reg_dist = RegistrySourceDist {
|
||||||
name: self.id.name.clone(),
|
name: self.id.name.clone(),
|
||||||
version: self.id.version.clone(),
|
version: self.id.version.clone(),
|
||||||
|
|
@ -1597,14 +1700,6 @@ impl Package {
|
||||||
self.fork_markers.as_slice()
|
self.fork_markers.as_slice()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return the index URL for this package, if it is a registry source.
|
|
||||||
pub fn index(&self) -> Option<&UrlString> {
|
|
||||||
match &self.id.source {
|
|
||||||
Source::Registry(url) => Some(url),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns all the hashes associated with this [`Package`].
|
/// Returns all the hashes associated with this [`Package`].
|
||||||
fn hashes(&self) -> Vec<HashDigest> {
|
fn hashes(&self) -> Vec<HashDigest> {
|
||||||
let mut hashes = Vec::new();
|
let mut hashes = Vec::new();
|
||||||
|
|
@ -1636,7 +1731,7 @@ impl Package {
|
||||||
|
|
||||||
/// Attempts to construct a `VerbatimUrl` from the given `Path`.
|
/// Attempts to construct a `VerbatimUrl` from the given `Path`.
|
||||||
fn verbatim_url(path: PathBuf, id: &PackageId) -> Result<VerbatimUrl, LockError> {
|
fn verbatim_url(path: PathBuf, id: &PackageId) -> Result<VerbatimUrl, LockError> {
|
||||||
let url = VerbatimUrl::from_path(path).map_err(|err| LockErrorKind::VerbatimUrl {
|
let url = VerbatimUrl::from_absolute_path(path).map_err(|err| LockErrorKind::VerbatimUrl {
|
||||||
id: id.clone(),
|
id: id.clone(),
|
||||||
err,
|
err,
|
||||||
})?;
|
})?;
|
||||||
|
|
@ -1836,11 +1931,17 @@ impl From<PackageId> for PackageIdForDependency {
|
||||||
#[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord, serde::Deserialize)]
|
#[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord, serde::Deserialize)]
|
||||||
#[serde(try_from = "SourceWire")]
|
#[serde(try_from = "SourceWire")]
|
||||||
enum Source {
|
enum Source {
|
||||||
Registry(UrlString),
|
/// A registry or `--find-links` index.
|
||||||
|
Registry(RegistrySource),
|
||||||
|
/// A Git repository.
|
||||||
Git(UrlString, GitSource),
|
Git(UrlString, GitSource),
|
||||||
|
/// A direct HTTP(S) URL.
|
||||||
Direct(UrlString, DirectSource),
|
Direct(UrlString, DirectSource),
|
||||||
|
/// A path to a local source or built archive.
|
||||||
Path(PathBuf),
|
Path(PathBuf),
|
||||||
|
/// A path to a local directory.
|
||||||
Directory(PathBuf),
|
Directory(PathBuf),
|
||||||
|
/// A path to a local directory that should be installed as editable.
|
||||||
Editable(PathBuf),
|
Editable(PathBuf),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1862,7 +1963,7 @@ impl Source {
|
||||||
|
|
||||||
fn from_built_dist(built_dist: &BuiltDist, root: &Path) -> Result<Source, LockError> {
|
fn from_built_dist(built_dist: &BuiltDist, root: &Path) -> Result<Source, LockError> {
|
||||||
match *built_dist {
|
match *built_dist {
|
||||||
BuiltDist::Registry(ref reg_dist) => Ok(Source::from_registry_built_dist(reg_dist)),
|
BuiltDist::Registry(ref reg_dist) => Source::from_registry_built_dist(reg_dist, root),
|
||||||
BuiltDist::DirectUrl(ref direct_dist) => {
|
BuiltDist::DirectUrl(ref direct_dist) => {
|
||||||
Ok(Source::from_direct_built_dist(direct_dist))
|
Ok(Source::from_direct_built_dist(direct_dist))
|
||||||
}
|
}
|
||||||
|
|
@ -1876,7 +1977,7 @@ impl Source {
|
||||||
) -> Result<Source, LockError> {
|
) -> Result<Source, LockError> {
|
||||||
match *source_dist {
|
match *source_dist {
|
||||||
distribution_types::SourceDist::Registry(ref reg_dist) => {
|
distribution_types::SourceDist::Registry(ref reg_dist) => {
|
||||||
Ok(Source::from_registry_source_dist(reg_dist))
|
Source::from_registry_source_dist(reg_dist, root)
|
||||||
}
|
}
|
||||||
distribution_types::SourceDist::DirectUrl(ref direct_dist) => {
|
distribution_types::SourceDist::DirectUrl(ref direct_dist) => {
|
||||||
Ok(Source::from_direct_source_dist(direct_dist))
|
Ok(Source::from_direct_source_dist(direct_dist))
|
||||||
|
|
@ -1893,12 +1994,18 @@ impl Source {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn from_registry_built_dist(reg_dist: &RegistryBuiltDist) -> Source {
|
fn from_registry_built_dist(
|
||||||
Source::from_index_url(®_dist.best_wheel().index)
|
reg_dist: &RegistryBuiltDist,
|
||||||
|
root: &Path,
|
||||||
|
) -> Result<Source, LockError> {
|
||||||
|
Source::from_index_url(®_dist.best_wheel().index, root)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn from_registry_source_dist(reg_dist: &RegistrySourceDist) -> Source {
|
fn from_registry_source_dist(
|
||||||
Source::from_index_url(®_dist.index)
|
reg_dist: &RegistrySourceDist,
|
||||||
|
root: &Path,
|
||||||
|
) -> Result<Source, LockError> {
|
||||||
|
Source::from_index_url(®_dist.index, root)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn from_direct_built_dist(direct_dist: &DirectUrlBuiltDist) -> Source {
|
fn from_direct_built_dist(direct_dist: &DirectUrlBuiltDist) -> Source {
|
||||||
|
|
@ -1923,14 +2030,15 @@ impl Source {
|
||||||
|
|
||||||
fn from_path_built_dist(path_dist: &PathBuiltDist, root: &Path) -> Result<Source, LockError> {
|
fn from_path_built_dist(path_dist: &PathBuiltDist, root: &Path) -> Result<Source, LockError> {
|
||||||
let path = relative_to(&path_dist.install_path, root)
|
let path = relative_to(&path_dist.install_path, root)
|
||||||
|
.or_else(|_| std::path::absolute(&path_dist.install_path))
|
||||||
.map_err(LockErrorKind::DistributionRelativePath)?;
|
.map_err(LockErrorKind::DistributionRelativePath)?;
|
||||||
Ok(Source::Path(path))
|
Ok(Source::Path(path))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn from_path_source_dist(path_dist: &PathSourceDist, root: &Path) -> Result<Source, LockError> {
|
fn from_path_source_dist(path_dist: &PathSourceDist, root: &Path) -> Result<Source, LockError> {
|
||||||
let path = relative_to(&path_dist.install_path, root)
|
let path = relative_to(&path_dist.install_path, root)
|
||||||
|
.or_else(|_| std::path::absolute(&path_dist.install_path))
|
||||||
.map_err(LockErrorKind::DistributionRelativePath)?;
|
.map_err(LockErrorKind::DistributionRelativePath)?;
|
||||||
|
|
||||||
Ok(Source::Path(path))
|
Ok(Source::Path(path))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1939,6 +2047,7 @@ impl Source {
|
||||||
root: &Path,
|
root: &Path,
|
||||||
) -> Result<Source, LockError> {
|
) -> Result<Source, LockError> {
|
||||||
let path = relative_to(&directory_dist.install_path, root)
|
let path = relative_to(&directory_dist.install_path, root)
|
||||||
|
.or_else(|_| std::path::absolute(&directory_dist.install_path))
|
||||||
.map_err(LockErrorKind::DistributionRelativePath)?;
|
.map_err(LockErrorKind::DistributionRelativePath)?;
|
||||||
if directory_dist.editable {
|
if directory_dist.editable {
|
||||||
Ok(Source::Editable(path))
|
Ok(Source::Editable(path))
|
||||||
|
|
@ -1947,10 +2056,23 @@ impl Source {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn from_index_url(index_url: &IndexUrl) -> Source {
|
fn from_index_url(index_url: &IndexUrl, root: &Path) -> Result<Source, LockError> {
|
||||||
// Remove any sensitive credentials from the index URL.
|
match index_url {
|
||||||
let redacted = index_url.redacted();
|
IndexUrl::Pypi(_) | IndexUrl::Url(_) => {
|
||||||
Source::Registry(UrlString::from(redacted.as_ref()))
|
// Remove any sensitive credentials from the index URL.
|
||||||
|
let redacted = index_url.redacted();
|
||||||
|
let source = RegistrySource::Url(UrlString::from(redacted.as_ref()));
|
||||||
|
Ok(Source::Registry(source))
|
||||||
|
}
|
||||||
|
IndexUrl::Path(url) => {
|
||||||
|
let path = url.to_file_path().map_err(|()| LockErrorKind::UrlToPath)?;
|
||||||
|
let path = relative_to(&path, root)
|
||||||
|
.or_else(|_| std::path::absolute(&path))
|
||||||
|
.map_err(LockErrorKind::IndexRelativePath)?;
|
||||||
|
let source = RegistrySource::Path(path);
|
||||||
|
Ok(Source::Registry(source))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn from_git_dist(git_dist: &GitSourceDist) -> Source {
|
fn from_git_dist(git_dist: &GitSourceDist) -> Source {
|
||||||
|
|
@ -1983,9 +2105,17 @@ impl Source {
|
||||||
fn to_toml(&self, table: &mut Table) {
|
fn to_toml(&self, table: &mut Table) {
|
||||||
let mut source_table = InlineTable::new();
|
let mut source_table = InlineTable::new();
|
||||||
match *self {
|
match *self {
|
||||||
Source::Registry(ref url) => {
|
Source::Registry(ref source) => match source {
|
||||||
source_table.insert("registry", Value::from(url.as_ref()));
|
RegistrySource::Url(url) => {
|
||||||
}
|
source_table.insert("registry", Value::from(url.as_ref()));
|
||||||
|
}
|
||||||
|
RegistrySource::Path(path) => {
|
||||||
|
source_table.insert(
|
||||||
|
"registry",
|
||||||
|
Value::from(PortablePath::from(path).to_string()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
Source::Git(ref url, _) => {
|
Source::Git(ref url, _) => {
|
||||||
source_table.insert("git", Value::from(url.as_ref()));
|
source_table.insert("git", Value::from(url.as_ref()));
|
||||||
}
|
}
|
||||||
|
|
@ -2018,10 +2148,15 @@ impl Source {
|
||||||
impl std::fmt::Display for Source {
|
impl std::fmt::Display for Source {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
Source::Registry(url) | Source::Git(url, _) | Source::Direct(url, _) => {
|
Source::Registry(RegistrySource::Url(url))
|
||||||
|
| Source::Git(url, _)
|
||||||
|
| Source::Direct(url, _) => {
|
||||||
write!(f, "{}+{}", self.name(), url)
|
write!(f, "{}+{}", self.name(), url)
|
||||||
}
|
}
|
||||||
Source::Path(path) | Source::Directory(path) | Source::Editable(path) => {
|
Source::Registry(RegistrySource::Path(path))
|
||||||
|
| Source::Path(path)
|
||||||
|
| Source::Directory(path)
|
||||||
|
| Source::Editable(path) => {
|
||||||
write!(f, "{}+{}", self.name(), PortablePath::from(path))
|
write!(f, "{}+{}", self.name(), PortablePath::from(path))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -2060,7 +2195,7 @@ impl Source {
|
||||||
#[serde(untagged)]
|
#[serde(untagged)]
|
||||||
enum SourceWire {
|
enum SourceWire {
|
||||||
Registry {
|
Registry {
|
||||||
registry: UrlString,
|
registry: RegistrySource,
|
||||||
},
|
},
|
||||||
Git {
|
Git {
|
||||||
git: String,
|
git: String,
|
||||||
|
|
@ -2119,6 +2254,64 @@ impl TryFrom<SourceWire> for Source {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The source for a registry, which could be a URL or a relative path.
|
||||||
|
#[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
|
||||||
|
enum RegistrySource {
|
||||||
|
/// Ex) `https://pypi.org/simple`
|
||||||
|
Url(UrlString),
|
||||||
|
/// Ex) `../path/to/local/index`
|
||||||
|
Path(PathBuf),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for RegistrySource {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
RegistrySource::Url(url) => write!(f, "{url}"),
|
||||||
|
RegistrySource::Path(path) => write!(f, "{}", path.display()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'de> serde::de::Deserialize<'de> for RegistrySource {
|
||||||
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
|
where
|
||||||
|
D: serde::de::Deserializer<'de>,
|
||||||
|
{
|
||||||
|
struct Visitor;
|
||||||
|
|
||||||
|
impl<'de> serde::de::Visitor<'de> for Visitor {
|
||||||
|
type Value = RegistrySource;
|
||||||
|
|
||||||
|
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
|
formatter.write_str("a valid URL or a file path")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
|
||||||
|
where
|
||||||
|
E: serde::de::Error,
|
||||||
|
{
|
||||||
|
if split_scheme(value).is_some() {
|
||||||
|
Ok(
|
||||||
|
serde::Deserialize::deserialize(serde::de::value::StrDeserializer::new(
|
||||||
|
value,
|
||||||
|
))
|
||||||
|
.map(RegistrySource::Url)?,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Ok(
|
||||||
|
serde::Deserialize::deserialize(serde::de::value::StrDeserializer::new(
|
||||||
|
value,
|
||||||
|
))
|
||||||
|
.map(RegistrySource::Path)?,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
deserializer.deserialize_str(Visitor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord, serde::Deserialize)]
|
#[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord, serde::Deserialize)]
|
||||||
struct DirectSource {
|
struct DirectSource {
|
||||||
subdirectory: Option<String>,
|
subdirectory: Option<String>,
|
||||||
|
|
@ -2223,6 +2416,13 @@ impl SourceDist {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn path(&self) -> Option<&Path> {
|
||||||
|
match &self {
|
||||||
|
SourceDist::Url { .. } => None,
|
||||||
|
SourceDist::Path { path, .. } => Some(path),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn hash(&self) -> Option<&Hash> {
|
fn hash(&self) -> Option<&Hash> {
|
||||||
match &self {
|
match &self {
|
||||||
SourceDist::Url { metadata, .. } => metadata.hash.as_ref(),
|
SourceDist::Url { metadata, .. } => metadata.hash.as_ref(),
|
||||||
|
|
@ -2304,15 +2504,38 @@ impl SourceDist {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
let url = normalize_file_location(®_dist.file.url)
|
match ®_dist.index {
|
||||||
.map_err(LockErrorKind::InvalidFileUrl)
|
IndexUrl::Pypi(_) | IndexUrl::Url(_) => {
|
||||||
.map_err(LockError::from)?;
|
let url = normalize_file_location(®_dist.file.url)
|
||||||
let hash = reg_dist.file.hashes.iter().max().cloned().map(Hash::from);
|
.map_err(LockErrorKind::InvalidFileUrl)
|
||||||
let size = reg_dist.file.size;
|
.map_err(LockError::from)?;
|
||||||
Ok(Some(SourceDist::Url {
|
let hash = reg_dist.file.hashes.iter().max().cloned().map(Hash::from);
|
||||||
url,
|
let size = reg_dist.file.size;
|
||||||
metadata: SourceDistMetadata { hash, size },
|
Ok(Some(SourceDist::Url {
|
||||||
}))
|
url,
|
||||||
|
metadata: SourceDistMetadata { hash, size },
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
IndexUrl::Path(path) => {
|
||||||
|
let index_path = path.to_file_path().map_err(|()| LockErrorKind::UrlToPath)?;
|
||||||
|
let reg_dist_path = reg_dist
|
||||||
|
.file
|
||||||
|
.url
|
||||||
|
.to_url()
|
||||||
|
.map_err(LockErrorKind::InvalidFileUrl)?
|
||||||
|
.to_file_path()
|
||||||
|
.map_err(|()| LockErrorKind::UrlToPath)?;
|
||||||
|
let path = relative_to(®_dist_path, index_path)
|
||||||
|
.or_else(|_| std::path::absolute(®_dist_path))
|
||||||
|
.map_err(LockErrorKind::DistributionRelativePath)?;
|
||||||
|
let hash = reg_dist.file.hashes.iter().max().cloned().map(Hash::from);
|
||||||
|
let size = reg_dist.file.size;
|
||||||
|
Ok(Some(SourceDist::Path {
|
||||||
|
path,
|
||||||
|
metadata: SourceDistMetadata { hash, size },
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn from_direct_dist(
|
fn from_direct_dist(
|
||||||
|
|
@ -2558,17 +2781,40 @@ impl Wheel {
|
||||||
|
|
||||||
fn from_registry_wheel(wheel: &RegistryBuiltWheel) -> Result<Wheel, LockError> {
|
fn from_registry_wheel(wheel: &RegistryBuiltWheel) -> Result<Wheel, LockError> {
|
||||||
let filename = wheel.filename.clone();
|
let filename = wheel.filename.clone();
|
||||||
let url = normalize_file_location(&wheel.file.url)
|
match &wheel.index {
|
||||||
.map_err(LockErrorKind::InvalidFileUrl)
|
IndexUrl::Pypi(_) | IndexUrl::Url(_) => {
|
||||||
.map_err(LockError::from)?;
|
let url = normalize_file_location(&wheel.file.url)
|
||||||
let hash = wheel.file.hashes.iter().max().cloned().map(Hash::from);
|
.map_err(LockErrorKind::InvalidFileUrl)
|
||||||
let size = wheel.file.size;
|
.map_err(LockError::from)?;
|
||||||
Ok(Wheel {
|
let hash = wheel.file.hashes.iter().max().cloned().map(Hash::from);
|
||||||
url: WheelWireSource::Url { url },
|
let size = wheel.file.size;
|
||||||
hash,
|
Ok(Wheel {
|
||||||
size,
|
url: WheelWireSource::Url { url },
|
||||||
filename,
|
hash,
|
||||||
})
|
size,
|
||||||
|
filename,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
IndexUrl::Path(path) => {
|
||||||
|
let index_path = path.to_file_path().map_err(|()| LockErrorKind::UrlToPath)?;
|
||||||
|
let wheel_path = wheel
|
||||||
|
.file
|
||||||
|
.url
|
||||||
|
.to_url()
|
||||||
|
.map_err(LockErrorKind::InvalidFileUrl)?
|
||||||
|
.to_file_path()
|
||||||
|
.map_err(|()| LockErrorKind::UrlToPath)?;
|
||||||
|
let path = relative_to(&wheel_path, index_path)
|
||||||
|
.or_else(|_| std::path::absolute(&wheel_path))
|
||||||
|
.map_err(LockErrorKind::DistributionRelativePath)?;
|
||||||
|
Ok(Wheel {
|
||||||
|
url: WheelWireSource::Path { path },
|
||||||
|
hash: None,
|
||||||
|
size: None,
|
||||||
|
filename,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn from_direct_dist(direct_dist: &DirectUrlBuiltDist, hashes: &[HashDigest]) -> Wheel {
|
fn from_direct_dist(direct_dist: &DirectUrlBuiltDist, hashes: &[HashDigest]) -> Wheel {
|
||||||
|
|
@ -2593,34 +2839,76 @@ impl Wheel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn to_registry_dist(&self, url: Url) -> Result<RegistryBuiltWheel, LockError> {
|
fn to_registry_dist(
|
||||||
|
&self,
|
||||||
|
source: &RegistrySource,
|
||||||
|
root: &Path,
|
||||||
|
) -> Result<RegistryBuiltWheel, LockError> {
|
||||||
let filename: WheelFilename = self.filename.clone();
|
let filename: WheelFilename = self.filename.clone();
|
||||||
let url_string = match &self.url {
|
|
||||||
WheelWireSource::Url { url } => url.clone(),
|
match source {
|
||||||
WheelWireSource::Filename { .. } => {
|
RegistrySource::Url(index_url) => {
|
||||||
return Err(LockErrorKind::MissingUrl {
|
let file_url = match &self.url {
|
||||||
name: self.filename.name.clone(),
|
WheelWireSource::Url { url } => url,
|
||||||
version: self.filename.version.clone(),
|
WheelWireSource::Path { .. } | WheelWireSource::Filename { .. } => {
|
||||||
}
|
return Err(LockErrorKind::MissingUrl {
|
||||||
.into())
|
name: filename.name,
|
||||||
|
version: filename.version,
|
||||||
|
}
|
||||||
|
.into())
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let file = Box::new(distribution_types::File {
|
||||||
|
dist_info_metadata: false,
|
||||||
|
filename: filename.to_string(),
|
||||||
|
hashes: self.hash.iter().map(|h| h.0.clone()).collect(),
|
||||||
|
requires_python: None,
|
||||||
|
size: self.size,
|
||||||
|
upload_time_utc_ms: None,
|
||||||
|
url: FileLocation::AbsoluteUrl(file_url.clone()),
|
||||||
|
yanked: None,
|
||||||
|
});
|
||||||
|
let index = IndexUrl::Url(VerbatimUrl::from_url(index_url.to_url()));
|
||||||
|
Ok(RegistryBuiltWheel {
|
||||||
|
filename,
|
||||||
|
file,
|
||||||
|
index,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
};
|
RegistrySource::Path(index_path) => {
|
||||||
let file = Box::new(distribution_types::File {
|
let file_path = match &self.url {
|
||||||
dist_info_metadata: false,
|
WheelWireSource::Path { path } => path,
|
||||||
filename: filename.to_string(),
|
WheelWireSource::Url { .. } | WheelWireSource::Filename { .. } => {
|
||||||
hashes: self.hash.iter().map(|h| h.0.clone()).collect(),
|
return Err(LockErrorKind::MissingPath {
|
||||||
requires_python: None,
|
name: filename.name,
|
||||||
size: self.size,
|
version: filename.version,
|
||||||
upload_time_utc_ms: None,
|
}
|
||||||
url: FileLocation::AbsoluteUrl(url_string),
|
.into())
|
||||||
yanked: None,
|
}
|
||||||
});
|
};
|
||||||
let index = IndexUrl::Url(VerbatimUrl::from_url(url));
|
let file_url = Url::from_file_path(root.join(index_path).join(file_path))
|
||||||
Ok(RegistryBuiltWheel {
|
.map_err(|()| LockErrorKind::PathToUrl)?;
|
||||||
filename,
|
let file = Box::new(distribution_types::File {
|
||||||
file,
|
dist_info_metadata: false,
|
||||||
index,
|
filename: filename.to_string(),
|
||||||
})
|
hashes: self.hash.iter().map(|h| h.0.clone()).collect(),
|
||||||
|
requires_python: None,
|
||||||
|
size: self.size,
|
||||||
|
upload_time_utc_ms: None,
|
||||||
|
url: FileLocation::AbsoluteUrl(UrlString::from(file_url)),
|
||||||
|
yanked: None,
|
||||||
|
});
|
||||||
|
let index = IndexUrl::Path(
|
||||||
|
VerbatimUrl::from_absolute_path(root.join(index_path))
|
||||||
|
.map_err(LockErrorKind::RegistryVerbatimUrl)?,
|
||||||
|
);
|
||||||
|
Ok(RegistryBuiltWheel {
|
||||||
|
filename,
|
||||||
|
file,
|
||||||
|
index,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2643,14 +2931,19 @@ struct WheelWire {
|
||||||
#[derive(Clone, Debug, serde::Deserialize, PartialEq, Eq)]
|
#[derive(Clone, Debug, serde::Deserialize, PartialEq, Eq)]
|
||||||
#[serde(untagged)]
|
#[serde(untagged)]
|
||||||
enum WheelWireSource {
|
enum WheelWireSource {
|
||||||
/// Used for all wheels except path wheels.
|
/// Used for all wheels that come from remote sources.
|
||||||
Url {
|
Url {
|
||||||
/// A URL or file path (via `file://`) where the wheel that was locked
|
/// A URL where the wheel that was locked against was found. The location
|
||||||
/// against was found. The location does not need to exist in the future,
|
/// does not need to exist in the future, so this should be treated as
|
||||||
/// so this should be treated as only a hint to where to look and/or
|
/// only a hint to where to look and/or recording where the wheel file
|
||||||
/// recording where the wheel file originally came from.
|
/// originally came from.
|
||||||
url: UrlString,
|
url: UrlString,
|
||||||
},
|
},
|
||||||
|
/// Used for wheels that come from local registries (like `--find-links`).
|
||||||
|
Path {
|
||||||
|
/// The path to the wheel, relative to the index.
|
||||||
|
path: PathBuf,
|
||||||
|
},
|
||||||
/// Used for path wheels.
|
/// Used for path wheels.
|
||||||
///
|
///
|
||||||
/// We only store the filename for path wheel, since we can't store a relative path in the url
|
/// We only store the filename for path wheel, since we can't store a relative path in the url
|
||||||
|
|
@ -2669,6 +2962,9 @@ impl Wheel {
|
||||||
WheelWireSource::Url { url } => {
|
WheelWireSource::Url { url } => {
|
||||||
table.insert("url", Value::from(url.as_ref()));
|
table.insert("url", Value::from(url.as_ref()));
|
||||||
}
|
}
|
||||||
|
WheelWireSource::Path { path } => {
|
||||||
|
table.insert("path", Value::from(PortablePath::from(path).to_string()));
|
||||||
|
}
|
||||||
WheelWireSource::Filename { filename } => {
|
WheelWireSource::Filename { filename } => {
|
||||||
table.insert("filename", Value::from(filename.to_string()));
|
table.insert("filename", Value::from(filename.to_string()));
|
||||||
}
|
}
|
||||||
|
|
@ -2687,7 +2983,6 @@ impl TryFrom<WheelWire> for Wheel {
|
||||||
type Error = String;
|
type Error = String;
|
||||||
|
|
||||||
fn try_from(wire: WheelWire) -> Result<Wheel, String> {
|
fn try_from(wire: WheelWire) -> Result<Wheel, String> {
|
||||||
// If necessary, extract the filename from the URL.
|
|
||||||
let filename = match &wire.url {
|
let filename = match &wire.url {
|
||||||
WheelWireSource::Url { url } => {
|
WheelWireSource::Url { url } => {
|
||||||
let filename = url.filename().map_err(|err| err.to_string())?;
|
let filename = url.filename().map_err(|err| err.to_string())?;
|
||||||
|
|
@ -2695,6 +2990,17 @@ impl TryFrom<WheelWire> for Wheel {
|
||||||
format!("failed to parse `{filename}` as wheel filename: {err}")
|
format!("failed to parse `{filename}` as wheel filename: {err}")
|
||||||
})?
|
})?
|
||||||
}
|
}
|
||||||
|
WheelWireSource::Path { path } => {
|
||||||
|
let filename = path
|
||||||
|
.file_name()
|
||||||
|
.and_then(|file_name| file_name.to_str())
|
||||||
|
.ok_or_else(|| {
|
||||||
|
format!("path `{}` has no filename component", path.display())
|
||||||
|
})?;
|
||||||
|
filename.parse::<WheelFilename>().map_err(|err| {
|
||||||
|
format!("failed to parse `{filename}` as wheel filename: {err}")
|
||||||
|
})?
|
||||||
|
}
|
||||||
WheelWireSource::Filename { filename } => filename.clone(),
|
WheelWireSource::Filename { filename } => filename.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -2914,7 +3220,7 @@ fn normalize_requirement(
|
||||||
url: _,
|
url: _,
|
||||||
} => {
|
} => {
|
||||||
let install_path = uv_fs::normalize_path(&workspace.install_path().join(&install_path));
|
let install_path = uv_fs::normalize_path(&workspace.install_path().join(&install_path));
|
||||||
let url = VerbatimUrl::from_path(&install_path)
|
let url = VerbatimUrl::from_absolute_path(&install_path)
|
||||||
.map_err(LockErrorKind::RequirementVerbatimUrl)?;
|
.map_err(LockErrorKind::RequirementVerbatimUrl)?;
|
||||||
|
|
||||||
Ok(Requirement {
|
Ok(Requirement {
|
||||||
|
|
@ -2935,7 +3241,7 @@ fn normalize_requirement(
|
||||||
url: _,
|
url: _,
|
||||||
} => {
|
} => {
|
||||||
let install_path = uv_fs::normalize_path(&workspace.install_path().join(&install_path));
|
let install_path = uv_fs::normalize_path(&workspace.install_path().join(&install_path));
|
||||||
let url = VerbatimUrl::from_path(&install_path)
|
let url = VerbatimUrl::from_absolute_path(&install_path)
|
||||||
.map_err(LockErrorKind::RequirementVerbatimUrl)?;
|
.map_err(LockErrorKind::RequirementVerbatimUrl)?;
|
||||||
|
|
||||||
Ok(Requirement {
|
Ok(Requirement {
|
||||||
|
|
@ -3098,8 +3404,8 @@ enum LockErrorKind {
|
||||||
/// The kind of the invalid source.
|
/// The kind of the invalid source.
|
||||||
source_type: &'static str,
|
source_type: &'static str,
|
||||||
},
|
},
|
||||||
/// An error that occurs when a distribution indicates that it is sourced from a registry, but
|
/// An error that occurs when a distribution indicates that it is sourced from a remote
|
||||||
/// is missing a URL.
|
/// registry, but is missing a URL.
|
||||||
#[error("found registry distribution {name}=={version} without a valid URL")]
|
#[error("found registry distribution {name}=={version} without a valid URL")]
|
||||||
MissingUrl {
|
MissingUrl {
|
||||||
/// The name of the distribution that is missing a URL.
|
/// The name of the distribution that is missing a URL.
|
||||||
|
|
@ -3107,6 +3413,15 @@ enum LockErrorKind {
|
||||||
/// The version of the distribution that is missing a URL.
|
/// The version of the distribution that is missing a URL.
|
||||||
version: Version,
|
version: Version,
|
||||||
},
|
},
|
||||||
|
/// An error that occurs when a distribution indicates that it is sourced from a local registry,
|
||||||
|
/// but is missing a path.
|
||||||
|
#[error("found registry distribution {name}=={version} without a valid path")]
|
||||||
|
MissingPath {
|
||||||
|
/// The name of the distribution that is missing a path.
|
||||||
|
name: PackageName,
|
||||||
|
/// The version of the distribution that is missing a path.
|
||||||
|
version: Version,
|
||||||
|
},
|
||||||
/// An error that occurs when a distribution indicates that it is sourced from a registry, but
|
/// An error that occurs when a distribution indicates that it is sourced from a registry, but
|
||||||
/// is missing a filename.
|
/// is missing a filename.
|
||||||
#[error("found registry distribution {id} without a valid filename")]
|
#[error("found registry distribution {id} without a valid filename")]
|
||||||
|
|
@ -3137,6 +3452,13 @@ enum LockErrorKind {
|
||||||
#[source]
|
#[source]
|
||||||
std::io::Error,
|
std::io::Error,
|
||||||
),
|
),
|
||||||
|
/// An error that occurs when converting an index URL to a relative path
|
||||||
|
#[error("could not compute relative path between workspace and index")]
|
||||||
|
IndexRelativePath(
|
||||||
|
/// The inner error we forward.
|
||||||
|
#[source]
|
||||||
|
std::io::Error,
|
||||||
|
),
|
||||||
/// An error that occurs when an ambiguous `package.dependency` is
|
/// An error that occurs when an ambiguous `package.dependency` is
|
||||||
/// missing a `version` field.
|
/// missing a `version` field.
|
||||||
#[error(
|
#[error(
|
||||||
|
|
@ -3171,6 +3493,19 @@ enum LockErrorKind {
|
||||||
#[source]
|
#[source]
|
||||||
VerbatimUrlError,
|
VerbatimUrlError,
|
||||||
),
|
),
|
||||||
|
/// An error that occurs when parsing a registry's index URL.
|
||||||
|
#[error("could not convert between URL and path")]
|
||||||
|
RegistryVerbatimUrl(
|
||||||
|
/// The inner error we forward.
|
||||||
|
#[source]
|
||||||
|
VerbatimUrlError,
|
||||||
|
),
|
||||||
|
/// An error that occurs when converting a path to a URL.
|
||||||
|
#[error("failed to convert path to URL")]
|
||||||
|
PathToUrl,
|
||||||
|
/// An error that occurs when converting a URL to a path
|
||||||
|
#[error("failed to convert URL to path")]
|
||||||
|
UrlToPath,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An error that occurs when a source string could not be parsed.
|
/// An error that occurs when a source string could not be parsed.
|
||||||
|
|
|
||||||
|
|
@ -21,8 +21,10 @@ Ok(
|
||||||
),
|
),
|
||||||
version: "4.3.0",
|
version: "4.3.0",
|
||||||
source: Registry(
|
source: Registry(
|
||||||
UrlString(
|
Url(
|
||||||
"https://pypi.org/simple",
|
UrlString(
|
||||||
|
"https://pypi.org/simple",
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
|
@ -71,8 +73,10 @@ Ok(
|
||||||
),
|
),
|
||||||
version: "4.3.0",
|
version: "4.3.0",
|
||||||
source: Registry(
|
source: Registry(
|
||||||
UrlString(
|
Url(
|
||||||
"https://pypi.org/simple",
|
UrlString(
|
||||||
|
"https://pypi.org/simple",
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
}: 0,
|
}: 0,
|
||||||
|
|
|
||||||
|
|
@ -21,8 +21,10 @@ Ok(
|
||||||
),
|
),
|
||||||
version: "4.3.0",
|
version: "4.3.0",
|
||||||
source: Registry(
|
source: Registry(
|
||||||
UrlString(
|
Url(
|
||||||
"https://pypi.org/simple",
|
UrlString(
|
||||||
|
"https://pypi.org/simple",
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
|
@ -78,8 +80,10 @@ Ok(
|
||||||
),
|
),
|
||||||
version: "4.3.0",
|
version: "4.3.0",
|
||||||
source: Registry(
|
source: Registry(
|
||||||
UrlString(
|
Url(
|
||||||
"https://pypi.org/simple",
|
UrlString(
|
||||||
|
"https://pypi.org/simple",
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
}: 0,
|
}: 0,
|
||||||
|
|
|
||||||
|
|
@ -21,8 +21,10 @@ Ok(
|
||||||
),
|
),
|
||||||
version: "0.1.0",
|
version: "0.1.0",
|
||||||
source: Registry(
|
source: Registry(
|
||||||
UrlString(
|
Url(
|
||||||
"https://pypi.org/simple",
|
UrlString(
|
||||||
|
"https://pypi.org/simple",
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
|
@ -63,8 +65,10 @@ Ok(
|
||||||
),
|
),
|
||||||
version: "0.1.0",
|
version: "0.1.0",
|
||||||
source: Registry(
|
source: Registry(
|
||||||
UrlString(
|
Url(
|
||||||
"https://pypi.org/simple",
|
UrlString(
|
||||||
|
"https://pypi.org/simple",
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
|
@ -98,8 +102,10 @@ Ok(
|
||||||
),
|
),
|
||||||
version: "0.1.0",
|
version: "0.1.0",
|
||||||
source: Registry(
|
source: Registry(
|
||||||
UrlString(
|
Url(
|
||||||
"https://pypi.org/simple",
|
UrlString(
|
||||||
|
"https://pypi.org/simple",
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
|
@ -122,8 +128,10 @@ Ok(
|
||||||
),
|
),
|
||||||
version: "0.1.0",
|
version: "0.1.0",
|
||||||
source: Registry(
|
source: Registry(
|
||||||
UrlString(
|
Url(
|
||||||
"https://pypi.org/simple",
|
UrlString(
|
||||||
|
"https://pypi.org/simple",
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
}: 0,
|
}: 0,
|
||||||
|
|
@ -133,8 +141,10 @@ Ok(
|
||||||
),
|
),
|
||||||
version: "0.1.0",
|
version: "0.1.0",
|
||||||
source: Registry(
|
source: Registry(
|
||||||
UrlString(
|
Url(
|
||||||
"https://pypi.org/simple",
|
UrlString(
|
||||||
|
"https://pypi.org/simple",
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
}: 1,
|
}: 1,
|
||||||
|
|
|
||||||
|
|
@ -21,8 +21,10 @@ Ok(
|
||||||
),
|
),
|
||||||
version: "0.1.0",
|
version: "0.1.0",
|
||||||
source: Registry(
|
source: Registry(
|
||||||
UrlString(
|
Url(
|
||||||
"https://pypi.org/simple",
|
UrlString(
|
||||||
|
"https://pypi.org/simple",
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
|
@ -63,8 +65,10 @@ Ok(
|
||||||
),
|
),
|
||||||
version: "0.1.0",
|
version: "0.1.0",
|
||||||
source: Registry(
|
source: Registry(
|
||||||
UrlString(
|
Url(
|
||||||
"https://pypi.org/simple",
|
UrlString(
|
||||||
|
"https://pypi.org/simple",
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
|
@ -98,8 +102,10 @@ Ok(
|
||||||
),
|
),
|
||||||
version: "0.1.0",
|
version: "0.1.0",
|
||||||
source: Registry(
|
source: Registry(
|
||||||
UrlString(
|
Url(
|
||||||
"https://pypi.org/simple",
|
UrlString(
|
||||||
|
"https://pypi.org/simple",
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
|
@ -122,8 +128,10 @@ Ok(
|
||||||
),
|
),
|
||||||
version: "0.1.0",
|
version: "0.1.0",
|
||||||
source: Registry(
|
source: Registry(
|
||||||
UrlString(
|
Url(
|
||||||
"https://pypi.org/simple",
|
UrlString(
|
||||||
|
"https://pypi.org/simple",
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
}: 0,
|
}: 0,
|
||||||
|
|
@ -133,8 +141,10 @@ Ok(
|
||||||
),
|
),
|
||||||
version: "0.1.0",
|
version: "0.1.0",
|
||||||
source: Registry(
|
source: Registry(
|
||||||
UrlString(
|
Url(
|
||||||
"https://pypi.org/simple",
|
UrlString(
|
||||||
|
"https://pypi.org/simple",
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
}: 1,
|
}: 1,
|
||||||
|
|
|
||||||
|
|
@ -21,8 +21,10 @@ Ok(
|
||||||
),
|
),
|
||||||
version: "0.1.0",
|
version: "0.1.0",
|
||||||
source: Registry(
|
source: Registry(
|
||||||
UrlString(
|
Url(
|
||||||
"https://pypi.org/simple",
|
UrlString(
|
||||||
|
"https://pypi.org/simple",
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
|
@ -63,8 +65,10 @@ Ok(
|
||||||
),
|
),
|
||||||
version: "0.1.0",
|
version: "0.1.0",
|
||||||
source: Registry(
|
source: Registry(
|
||||||
UrlString(
|
Url(
|
||||||
"https://pypi.org/simple",
|
UrlString(
|
||||||
|
"https://pypi.org/simple",
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
|
@ -98,8 +102,10 @@ Ok(
|
||||||
),
|
),
|
||||||
version: "0.1.0",
|
version: "0.1.0",
|
||||||
source: Registry(
|
source: Registry(
|
||||||
UrlString(
|
Url(
|
||||||
"https://pypi.org/simple",
|
UrlString(
|
||||||
|
"https://pypi.org/simple",
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
|
@ -122,8 +128,10 @@ Ok(
|
||||||
),
|
),
|
||||||
version: "0.1.0",
|
version: "0.1.0",
|
||||||
source: Registry(
|
source: Registry(
|
||||||
UrlString(
|
Url(
|
||||||
"https://pypi.org/simple",
|
UrlString(
|
||||||
|
"https://pypi.org/simple",
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
}: 0,
|
}: 0,
|
||||||
|
|
@ -133,8 +141,10 @@ Ok(
|
||||||
),
|
),
|
||||||
version: "0.1.0",
|
version: "0.1.0",
|
||||||
source: Registry(
|
source: Registry(
|
||||||
UrlString(
|
Url(
|
||||||
"https://pypi.org/simple",
|
UrlString(
|
||||||
|
"https://pypi.org/simple",
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
}: 1,
|
}: 1,
|
||||||
|
|
|
||||||
|
|
@ -270,7 +270,7 @@ impl Workspace {
|
||||||
})
|
})
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
let url = VerbatimUrl::from_path(&member.root)
|
let url = VerbatimUrl::from_absolute_path(&member.root)
|
||||||
.expect("path is valid URL")
|
.expect("path is valid URL")
|
||||||
.with_given(member.root.to_string_lossy());
|
.with_given(member.root.to_string_lossy());
|
||||||
Some(Requirement {
|
Some(Requirement {
|
||||||
|
|
|
||||||
|
|
@ -723,9 +723,15 @@ impl ValidatedLock {
|
||||||
debug!("Ignoring existing lockfile due to missing root package: `{name}`");
|
debug!("Ignoring existing lockfile due to missing root package: `{name}`");
|
||||||
Ok(Self::Preferable(lock))
|
Ok(Self::Preferable(lock))
|
||||||
}
|
}
|
||||||
SatisfiesResult::MissingIndex(name, version, index) => {
|
SatisfiesResult::MissingRemoteIndex(name, version, index) => {
|
||||||
debug!(
|
debug!(
|
||||||
"Ignoring existing lockfile due to missing index: `{name}` `{version}` from `{index}`"
|
"Ignoring existing lockfile due to missing remote index: `{name}` `{version}` from `{index}`"
|
||||||
|
);
|
||||||
|
Ok(Self::Preferable(lock))
|
||||||
|
}
|
||||||
|
SatisfiesResult::MissingLocalIndex(name, version, index) => {
|
||||||
|
debug!(
|
||||||
|
"Ignoring existing lockfile due to missing local index: `{name}` `{version}` from `{}`", index.display()
|
||||||
);
|
);
|
||||||
Ok(Self::Preferable(lock))
|
Ok(Self::Preferable(lock))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6493,7 +6493,28 @@ fn lock_warn_missing_transitive_lower_bounds() -> Result<()> {
|
||||||
fn lock_find_links_local_wheel() -> Result<()> {
|
fn lock_find_links_local_wheel() -> Result<()> {
|
||||||
let context = TestContext::new("3.12");
|
let context = TestContext::new("3.12");
|
||||||
|
|
||||||
let pyproject_toml = context.temp_dir.child("pyproject.toml");
|
// Populate the `--find-links` entries.
|
||||||
|
fs_err::create_dir_all(context.temp_dir.join("links"))?;
|
||||||
|
|
||||||
|
for entry in fs_err::read_dir(context.workspace_root.join("scripts/links"))? {
|
||||||
|
let entry = entry?;
|
||||||
|
let path = entry.path();
|
||||||
|
if path
|
||||||
|
.file_name()
|
||||||
|
.and_then(|file_name| file_name.to_str())
|
||||||
|
.is_some_and(|file_name| file_name.starts_with("tqdm-"))
|
||||||
|
{
|
||||||
|
let dest = context
|
||||||
|
.temp_dir
|
||||||
|
.join("links")
|
||||||
|
.join(path.file_name().unwrap());
|
||||||
|
fs_err::copy(&path, &dest)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let workspace = context.temp_dir.child("workspace");
|
||||||
|
|
||||||
|
let pyproject_toml = workspace.child("pyproject.toml");
|
||||||
pyproject_toml.write_str(&formatdoc! { r#"
|
pyproject_toml.write_str(&formatdoc! { r#"
|
||||||
[project]
|
[project]
|
||||||
name = "project"
|
name = "project"
|
||||||
|
|
@ -6504,19 +6525,20 @@ fn lock_find_links_local_wheel() -> Result<()> {
|
||||||
[tool.uv]
|
[tool.uv]
|
||||||
find-links = ["{}"]
|
find-links = ["{}"]
|
||||||
"#,
|
"#,
|
||||||
context.workspace_root.join("scripts/links/").portable_display(),
|
context.temp_dir.join("links/").portable_display(),
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
uv_snapshot!(context.filters(), context.lock(), @r###"
|
uv_snapshot!(context.filters(), context.lock().current_dir(&workspace), @r###"
|
||||||
success: true
|
success: true
|
||||||
exit_code: 0
|
exit_code: 0
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
|
Using Python 3.12.[X] interpreter at: [PYTHON-3.12]
|
||||||
Resolved 2 packages in [TIME]
|
Resolved 2 packages in [TIME]
|
||||||
"###);
|
"###);
|
||||||
|
|
||||||
let lock = fs_err::read_to_string(context.temp_dir.join("uv.lock")).unwrap();
|
let lock = fs_err::read_to_string(workspace.join("uv.lock")).unwrap();
|
||||||
|
|
||||||
insta::with_settings!({
|
insta::with_settings!({
|
||||||
filters => context.filters(),
|
filters => context.filters(),
|
||||||
|
|
@ -6543,34 +6565,37 @@ fn lock_find_links_local_wheel() -> Result<()> {
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tqdm"
|
name = "tqdm"
|
||||||
version = "1000.0.0"
|
version = "1000.0.0"
|
||||||
source = { registry = "file://[WORKSPACE]/scripts/links" }
|
source = { registry = "../links" }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "file://[WORKSPACE]/scripts/links/tqdm-1000.0.0-py3-none-any.whl" },
|
{ path = "tqdm-1000.0.0-py3-none-any.whl" },
|
||||||
]
|
]
|
||||||
"###
|
"###
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Re-run with `--locked`.
|
// Re-run with `--locked`.
|
||||||
uv_snapshot!(context.filters(), context.lock().arg("--locked"), @r###"
|
uv_snapshot!(context.filters(), context.lock().arg("--locked").current_dir(&workspace), @r###"
|
||||||
success: true
|
success: true
|
||||||
exit_code: 0
|
exit_code: 0
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
|
Using Python 3.12.[X] interpreter at: [PYTHON-3.12]
|
||||||
Resolved 2 packages in [TIME]
|
Resolved 2 packages in [TIME]
|
||||||
"###);
|
"###);
|
||||||
|
|
||||||
// Install from the lockfile.
|
// Install from the lockfile.
|
||||||
uv_snapshot!(context.filters(), context.sync().arg("--frozen"), @r###"
|
uv_snapshot!(context.filters(), context.sync().arg("--frozen").current_dir(&workspace), @r###"
|
||||||
success: true
|
success: true
|
||||||
exit_code: 0
|
exit_code: 0
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
|
Using Python 3.12.[X] interpreter at: [PYTHON-3.12]
|
||||||
|
Creating virtualenv at: .venv
|
||||||
Prepared 1 package in [TIME]
|
Prepared 1 package in [TIME]
|
||||||
Installed 2 packages in [TIME]
|
Installed 2 packages in [TIME]
|
||||||
+ project==0.1.0 (from file://[TEMP_DIR]/)
|
+ project==0.1.0 (from file://[TEMP_DIR]/workspace)
|
||||||
+ tqdm==1000.0.0
|
+ tqdm==1000.0.0
|
||||||
"###);
|
"###);
|
||||||
|
|
||||||
|
|
@ -6582,7 +6607,28 @@ fn lock_find_links_local_wheel() -> Result<()> {
|
||||||
fn lock_find_links_local_sdist() -> Result<()> {
|
fn lock_find_links_local_sdist() -> Result<()> {
|
||||||
let context = TestContext::new("3.12");
|
let context = TestContext::new("3.12");
|
||||||
|
|
||||||
let pyproject_toml = context.temp_dir.child("pyproject.toml");
|
// Populate the `--find-links` entries.
|
||||||
|
fs_err::create_dir_all(context.temp_dir.join("links"))?;
|
||||||
|
|
||||||
|
for entry in fs_err::read_dir(context.workspace_root.join("scripts/links"))? {
|
||||||
|
let entry = entry?;
|
||||||
|
let path = entry.path();
|
||||||
|
if path
|
||||||
|
.file_name()
|
||||||
|
.and_then(|file_name| file_name.to_str())
|
||||||
|
.is_some_and(|file_name| file_name.starts_with("tqdm-"))
|
||||||
|
{
|
||||||
|
let dest = context
|
||||||
|
.temp_dir
|
||||||
|
.join("links")
|
||||||
|
.join(path.file_name().unwrap());
|
||||||
|
fs_err::copy(&path, &dest)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let workspace = context.temp_dir.child("workspace");
|
||||||
|
|
||||||
|
let pyproject_toml = workspace.child("pyproject.toml");
|
||||||
pyproject_toml.write_str(&formatdoc! { r#"
|
pyproject_toml.write_str(&formatdoc! { r#"
|
||||||
[project]
|
[project]
|
||||||
name = "project"
|
name = "project"
|
||||||
|
|
@ -6593,19 +6639,20 @@ fn lock_find_links_local_sdist() -> Result<()> {
|
||||||
[tool.uv]
|
[tool.uv]
|
||||||
find-links = ["{}"]
|
find-links = ["{}"]
|
||||||
"#,
|
"#,
|
||||||
context.workspace_root.join("scripts/links/").portable_display(),
|
context.temp_dir.join("links/").portable_display(),
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
uv_snapshot!(context.filters(), context.lock(), @r###"
|
uv_snapshot!(context.filters(), context.lock().current_dir(&workspace), @r###"
|
||||||
success: true
|
success: true
|
||||||
exit_code: 0
|
exit_code: 0
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
|
Using Python 3.12.[X] interpreter at: [PYTHON-3.12]
|
||||||
Resolved 2 packages in [TIME]
|
Resolved 2 packages in [TIME]
|
||||||
"###);
|
"###);
|
||||||
|
|
||||||
let lock = fs_err::read_to_string(context.temp_dir.join("uv.lock")).unwrap();
|
let lock = fs_err::read_to_string(workspace.join("uv.lock")).unwrap();
|
||||||
|
|
||||||
insta::with_settings!({
|
insta::with_settings!({
|
||||||
filters => context.filters(),
|
filters => context.filters(),
|
||||||
|
|
@ -6632,32 +6679,35 @@ fn lock_find_links_local_sdist() -> Result<()> {
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tqdm"
|
name = "tqdm"
|
||||||
version = "999.0.0"
|
version = "999.0.0"
|
||||||
source = { registry = "file://[WORKSPACE]/scripts/links" }
|
source = { registry = "../links" }
|
||||||
sdist = { url = "file://[WORKSPACE]/scripts/links/tqdm-999.0.0.tar.gz" }
|
sdist = { path = "tqdm-999.0.0.tar.gz" }
|
||||||
"###
|
"###
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Re-run with `--locked`.
|
// Re-run with `--locked`.
|
||||||
uv_snapshot!(context.filters(), context.lock().arg("--locked"), @r###"
|
uv_snapshot!(context.filters(), context.lock().arg("--locked").current_dir(&workspace), @r###"
|
||||||
success: true
|
success: true
|
||||||
exit_code: 0
|
exit_code: 0
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
|
Using Python 3.12.[X] interpreter at: [PYTHON-3.12]
|
||||||
Resolved 2 packages in [TIME]
|
Resolved 2 packages in [TIME]
|
||||||
"###);
|
"###);
|
||||||
|
|
||||||
// Install from the lockfile.
|
// Install from the lockfile.
|
||||||
uv_snapshot!(context.filters(), context.sync().arg("--frozen"), @r###"
|
uv_snapshot!(context.filters(), context.sync().arg("--frozen").current_dir(&workspace), @r###"
|
||||||
success: true
|
success: true
|
||||||
exit_code: 0
|
exit_code: 0
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
|
Using Python 3.12.[X] interpreter at: [PYTHON-3.12]
|
||||||
|
Creating virtualenv at: .venv
|
||||||
Prepared 2 packages in [TIME]
|
Prepared 2 packages in [TIME]
|
||||||
Installed 2 packages in [TIME]
|
Installed 2 packages in [TIME]
|
||||||
+ project==0.1.0 (from file://[TEMP_DIR]/)
|
+ project==0.1.0 (from file://[TEMP_DIR]/workspace)
|
||||||
+ tqdm==999.0.0
|
+ tqdm==999.0.0
|
||||||
"###);
|
"###);
|
||||||
|
|
||||||
|
|
@ -6857,6 +6907,14 @@ fn lock_local_index() -> Result<()> {
|
||||||
let tqdm = root.child("tqdm");
|
let tqdm = root.child("tqdm");
|
||||||
fs_err::create_dir_all(&tqdm)?;
|
fs_err::create_dir_all(&tqdm)?;
|
||||||
|
|
||||||
|
let wheel = tqdm.child("tqdm-1000.0.0-py3-none-any.whl");
|
||||||
|
fs_err::copy(
|
||||||
|
context
|
||||||
|
.workspace_root
|
||||||
|
.join("scripts/links/tqdm-1000.0.0-py3-none-any.whl"),
|
||||||
|
&wheel,
|
||||||
|
)?;
|
||||||
|
|
||||||
let index = tqdm.child("index.html");
|
let index = tqdm.child("index.html");
|
||||||
index.write_str(&formatdoc! {r#"
|
index.write_str(&formatdoc! {r#"
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
|
|
@ -6867,14 +6925,14 @@ fn lock_local_index() -> Result<()> {
|
||||||
<body>
|
<body>
|
||||||
<h1>Links for tqdm</h1>
|
<h1>Links for tqdm</h1>
|
||||||
<a
|
<a
|
||||||
href="{}/tqdm-1000.0.0-py3-none-any.whl"
|
href="{}"
|
||||||
data-requires-python=">=3.8"
|
data-requires-python=">=3.8"
|
||||||
>
|
>
|
||||||
tqdm-1000.0.0-py3-none-any.whl
|
tqdm-1000.0.0-py3-none-any.whl
|
||||||
</a>
|
</a>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
"#, Url::from_directory_path(context.workspace_root.join("scripts/links/")).unwrap().as_str()})?;
|
"#, Url::from_file_path(wheel).unwrap().as_str()})?;
|
||||||
|
|
||||||
let context = TestContext::new("3.12");
|
let context = TestContext::new("3.12");
|
||||||
|
|
||||||
|
|
@ -6931,9 +6989,9 @@ fn lock_local_index() -> Result<()> {
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tqdm"
|
name = "tqdm"
|
||||||
version = "1000.0.0"
|
version = "1000.0.0"
|
||||||
source = { registry = "file://[TMP]" }
|
source = { registry = "../../[TMP]/simple-html" }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "file://[WORKSPACE]/scripts/links//tqdm-1000.0.0-py3-none-any.whl" },
|
{ path = "tqdm/tqdm-1000.0.0-py3-none-any.whl" },
|
||||||
]
|
]
|
||||||
"###
|
"###
|
||||||
);
|
);
|
||||||
|
|
@ -10423,3 +10481,128 @@ fn lock_dropped_dev_extra() -> Result<()> {
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Use a trailing slash on the declared index.
|
||||||
|
#[test]
|
||||||
|
fn lock_trailing_slash() -> Result<()> {
|
||||||
|
let context = TestContext::new("3.12");
|
||||||
|
|
||||||
|
let pyproject_toml = context.temp_dir.child("pyproject.toml");
|
||||||
|
pyproject_toml.write_str(
|
||||||
|
r#"
|
||||||
|
[project]
|
||||||
|
name = "project"
|
||||||
|
version = "0.1.0"
|
||||||
|
requires-python = ">=3.12"
|
||||||
|
dependencies = ["anyio==3.7.0"]
|
||||||
|
|
||||||
|
[tool.uv]
|
||||||
|
index-url = "https://pypi.org/simple/"
|
||||||
|
"#,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
uv_snapshot!(context.filters(), context.lock(), @r###"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
Resolved 4 packages in [TIME]
|
||||||
|
"###);
|
||||||
|
|
||||||
|
let lock = fs_err::read_to_string(context.temp_dir.join("uv.lock")).unwrap();
|
||||||
|
|
||||||
|
insta::with_settings!({
|
||||||
|
filters => context.filters(),
|
||||||
|
}, {
|
||||||
|
assert_snapshot!(
|
||||||
|
lock, @r###"
|
||||||
|
version = 1
|
||||||
|
requires-python = ">=3.12"
|
||||||
|
|
||||||
|
[options]
|
||||||
|
exclude-newer = "2024-03-25T00:00:00Z"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anyio"
|
||||||
|
version = "3.7.0"
|
||||||
|
source = { registry = "https://pypi.org/simple/" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "idna" },
|
||||||
|
{ name = "sniffio" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/c6/b3/fefbf7e78ab3b805dec67d698dc18dd505af7a18a8dd08868c9b4fa736b5/anyio-3.7.0.tar.gz", hash = "sha256:275d9973793619a5374e1c89a4f4ad3f4b0a5510a2b5b939444bee8f4c4d37ce", size = 142737 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/68/fe/7ce1926952c8a403b35029e194555558514b365ad77d75125f521a2bec62/anyio-3.7.0-py3-none-any.whl", hash = "sha256:eddca883c4175f14df8aedce21054bfca3adb70ffe76a9f607aef9d7fa2ea7f0", size = 80873 },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "idna"
|
||||||
|
version = "3.6"
|
||||||
|
source = { registry = "https://pypi.org/simple/" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/bf/3f/ea4b9117521a1e9c50344b909be7886dd00a519552724809bb1f486986c2/idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca", size = 175426 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c2/e7/a82b05cf63a603df6e68d59ae6a68bf5064484a0718ea5033660af4b54a9/idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f", size = 61567 },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "project"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = { editable = "." }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "anyio" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.metadata]
|
||||||
|
requires-dist = [{ name = "anyio", specifier = "==3.7.0" }]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "sniffio"
|
||||||
|
version = "1.3.1"
|
||||||
|
source = { registry = "https://pypi.org/simple/" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235 },
|
||||||
|
]
|
||||||
|
"###
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Re-run with `--locked`.
|
||||||
|
uv_snapshot!(context.filters(), context.lock().arg("--locked"), @r###"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
Resolved 4 packages in [TIME]
|
||||||
|
"###);
|
||||||
|
|
||||||
|
// Re-run with `--offline`. We shouldn't need a network connection to validate an
|
||||||
|
// already-correct lockfile with immutable metadata.
|
||||||
|
uv_snapshot!(context.filters(), context.lock().arg("--locked").arg("--offline").arg("--no-cache"), @r###"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
Resolved 4 packages in [TIME]
|
||||||
|
"###);
|
||||||
|
|
||||||
|
// Install from the lockfile.
|
||||||
|
uv_snapshot!(context.filters(), context.sync().arg("--frozen"), @r###"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
Prepared 4 packages in [TIME]
|
||||||
|
Installed 4 packages in [TIME]
|
||||||
|
+ anyio==3.7.0
|
||||||
|
+ idna==3.6
|
||||||
|
+ project==0.1.0 (from file://[TEMP_DIR]/)
|
||||||
|
+ sniffio==1.3.1
|
||||||
|
"###);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue