mirror of
https://github.com/astral-sh/uv.git
synced 2025-07-07 21:35:00 +00:00
Preserve verbatim URLs for --find-links
(#4838)
Also gets rid of a lot of duplicated logic for `--find-links`. Closes https://github.com/astral-sh/uv/issues/4797
This commit is contained in:
parent
c0ca0b02b8
commit
32ea636585
8 changed files with 171 additions and 214 deletions
|
@ -1,7 +1,7 @@
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::fmt::{Display, Formatter};
|
use std::fmt::{Display, Formatter};
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::Path;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use itertools::Either;
|
use itertools::Either;
|
||||||
|
@ -9,8 +9,7 @@ use once_cell::sync::Lazy;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use url::{ParseError, Url};
|
use url::{ParseError, Url};
|
||||||
|
|
||||||
use pep508_rs::{expand_env_vars, split_scheme, strip_host, Scheme, VerbatimUrl, VerbatimUrlError};
|
use pep508_rs::{VerbatimUrl, VerbatimUrlError};
|
||||||
use uv_fs::normalize_url_path;
|
|
||||||
|
|
||||||
use crate::Verbatim;
|
use crate::Verbatim;
|
||||||
|
|
||||||
|
@ -91,6 +90,15 @@ impl Verbatim for IndexUrl {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<FlatIndexLocation> for IndexUrl {
|
||||||
|
fn from(location: FlatIndexLocation) -> Self {
|
||||||
|
match location {
|
||||||
|
FlatIndexLocation::Path(url) => Self::Path(url),
|
||||||
|
FlatIndexLocation::Url(url) => Self::Url(url),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// An error that can occur when parsing an [`IndexUrl`].
|
/// An error that can occur when parsing an [`IndexUrl`].
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
pub enum IndexUrlError {
|
pub enum IndexUrlError {
|
||||||
|
@ -173,8 +181,8 @@ impl Deref for IndexUrl {
|
||||||
/// Also known as `--find-links`.
|
/// Also known as `--find-links`.
|
||||||
#[derive(Debug, Clone, Hash, Eq, PartialEq)]
|
#[derive(Debug, Clone, Hash, Eq, PartialEq)]
|
||||||
pub enum FlatIndexLocation {
|
pub enum FlatIndexLocation {
|
||||||
Path(PathBuf),
|
Path(VerbatimUrl),
|
||||||
Url(Url),
|
Url(VerbatimUrl),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "schemars")]
|
#[cfg(feature = "schemars")]
|
||||||
|
@ -197,6 +205,60 @@ impl schemars::JsonSchema for FlatIndexLocation {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl FlatIndexLocation {
|
||||||
|
/// Return the raw URL for the `--find-links` index.
|
||||||
|
pub fn url(&self) -> &Url {
|
||||||
|
match self {
|
||||||
|
Self::Url(url) => url.raw(),
|
||||||
|
Self::Path(url) => url.raw(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the redacted URL for the `--find-links` index, omitting any sensitive credentials.
|
||||||
|
pub fn redacted(&self) -> Cow<'_, Url> {
|
||||||
|
let url = self.url();
|
||||||
|
if url.username().is_empty() && url.password().is_none() {
|
||||||
|
Cow::Borrowed(url)
|
||||||
|
} else {
|
||||||
|
let mut url = url.clone();
|
||||||
|
let _ = url.set_username("");
|
||||||
|
let _ = url.set_password(None);
|
||||||
|
Cow::Owned(url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for FlatIndexLocation {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::Url(url) => Display::fmt(url, f),
|
||||||
|
Self::Path(url) => Display::fmt(url, f),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Verbatim for FlatIndexLocation {
|
||||||
|
fn verbatim(&self) -> Cow<'_, str> {
|
||||||
|
match self {
|
||||||
|
Self::Url(url) => url.verbatim(),
|
||||||
|
Self::Path(url) => url.verbatim(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for FlatIndexLocation {
|
||||||
|
type Err = IndexUrlError;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
let url = if let Ok(path) = Path::new(s).canonicalize() {
|
||||||
|
VerbatimUrl::from_path(path)?
|
||||||
|
} else {
|
||||||
|
VerbatimUrl::parse_url(s)?
|
||||||
|
};
|
||||||
|
Ok(Self::from(url.with_given(s)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl serde::ser::Serialize for FlatIndexLocation {
|
impl serde::ser::Serialize for FlatIndexLocation {
|
||||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
where
|
where
|
||||||
|
@ -216,60 +278,12 @@ impl<'de> serde::de::Deserialize<'de> for FlatIndexLocation {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromStr for FlatIndexLocation {
|
impl From<VerbatimUrl> for FlatIndexLocation {
|
||||||
type Err = url::ParseError;
|
fn from(url: VerbatimUrl) -> Self {
|
||||||
|
if url.scheme() == "file" {
|
||||||
/// Parse a raw string for a `--find-links` entry, which could be a URL or a local path.
|
Self::Path(url)
|
||||||
///
|
|
||||||
/// For example:
|
|
||||||
/// - `file:///home/ferris/project/scripts/...`
|
|
||||||
/// - `file:../ferris/`
|
|
||||||
/// - `../ferris/`
|
|
||||||
/// - `https://download.pytorch.org/whl/torch_stable.html`
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
||||||
// Expand environment variables.
|
|
||||||
let expanded = expand_env_vars(s);
|
|
||||||
|
|
||||||
// Parse the expanded path.
|
|
||||||
if let Some((scheme, path)) = split_scheme(&expanded) {
|
|
||||||
match Scheme::parse(scheme) {
|
|
||||||
// Ex) `file:///home/ferris/project/scripts/...`, `file://localhost/home/ferris/project/scripts/...`, or `file:../ferris/`
|
|
||||||
Some(Scheme::File) => {
|
|
||||||
// Strip the leading slashes, along with the `localhost` host, if present.
|
|
||||||
let path = strip_host(path);
|
|
||||||
|
|
||||||
// Transform, e.g., `/C:/Users/ferris/wheel-0.42.0.tar.gz` to `C:\Users\ferris\wheel-0.42.0.tar.gz`.
|
|
||||||
let path = normalize_url_path(path);
|
|
||||||
|
|
||||||
let path = PathBuf::from(path.as_ref());
|
|
||||||
Ok(Self::Path(path))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ex) `https://download.pytorch.org/whl/torch_stable.html`
|
|
||||||
Some(_) => {
|
|
||||||
let url = Url::parse(expanded.as_ref())?;
|
|
||||||
Ok(Self::Url(url))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ex) `C:\Users\ferris\wheel-0.42.0.tar.gz`
|
|
||||||
None => {
|
|
||||||
let path = PathBuf::from(expanded.as_ref());
|
|
||||||
Ok(Self::Path(path))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// Ex) `../ferris/`
|
Self::Url(url)
|
||||||
let path = PathBuf::from(expanded.as_ref());
|
|
||||||
Ok(Self::Path(path))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for FlatIndexLocation {
|
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
|
||||||
match self {
|
|
||||||
Self::Path(path) => Display::fmt(&path.display(), f),
|
|
||||||
Self::Url(url) => Display::fmt(url, f),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -387,7 +401,7 @@ impl<'a> IndexLocations {
|
||||||
.map(IndexUrl::url)
|
.map(IndexUrl::url)
|
||||||
.chain(self.flat_index.iter().filter_map(|index| match index {
|
.chain(self.flat_index.iter().filter_map(|index| match index {
|
||||||
FlatIndexLocation::Path(_) => None,
|
FlatIndexLocation::Path(_) => None,
|
||||||
FlatIndexLocation::Url(url) => Some(url),
|
FlatIndexLocation::Url(url) => Some(url.raw()),
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -459,32 +473,3 @@ impl From<IndexLocations> for IndexUrls {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
#[cfg(unix)]
|
|
||||||
mod test {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn parse_find_links() {
|
|
||||||
assert_eq!(
|
|
||||||
FlatIndexLocation::from_str("file:///home/ferris/project/scripts/...").unwrap(),
|
|
||||||
FlatIndexLocation::Path(PathBuf::from("/home/ferris/project/scripts/..."))
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
FlatIndexLocation::from_str("file:../ferris/").unwrap(),
|
|
||||||
FlatIndexLocation::Path(PathBuf::from("../ferris/"))
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
FlatIndexLocation::from_str("../ferris/").unwrap(),
|
|
||||||
FlatIndexLocation::Path(PathBuf::from("../ferris/"))
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
FlatIndexLocation::from_str("https://download.pytorch.org/whl/torch_stable.html")
|
|
||||||
.unwrap(),
|
|
||||||
FlatIndexLocation::Url(
|
|
||||||
Url::parse("https://download.pytorch.org/whl/torch_stable.html").unwrap()
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -45,15 +45,13 @@ use unscanny::{Pattern, Scanner};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
use distribution_types::{UnresolvedRequirement, UnresolvedRequirementSpecification};
|
use distribution_types::{UnresolvedRequirement, UnresolvedRequirementSpecification};
|
||||||
use pep508_rs::{
|
use pep508_rs::{expand_env_vars, Pep508Error, RequirementOrigin, VerbatimUrl};
|
||||||
expand_env_vars, split_scheme, strip_host, Pep508Error, RequirementOrigin, Scheme, VerbatimUrl,
|
|
||||||
};
|
|
||||||
use pypi_types::{Requirement, VerbatimParsedUrl};
|
use pypi_types::{Requirement, VerbatimParsedUrl};
|
||||||
#[cfg(feature = "http")]
|
#[cfg(feature = "http")]
|
||||||
use uv_client::BaseClient;
|
use uv_client::BaseClient;
|
||||||
use uv_client::BaseClientBuilder;
|
use uv_client::BaseClientBuilder;
|
||||||
use uv_configuration::{NoBinary, NoBuild, PackageNameSpecifier};
|
use uv_configuration::{NoBinary, NoBuild, PackageNameSpecifier};
|
||||||
use uv_fs::{normalize_url_path, Simplified};
|
use uv_fs::Simplified;
|
||||||
use uv_warnings::warn_user;
|
use uv_warnings::warn_user;
|
||||||
|
|
||||||
use crate::requirement::EditableError;
|
use crate::requirement::EditableError;
|
||||||
|
@ -84,7 +82,7 @@ enum RequirementsTxtStatement {
|
||||||
/// `--extra-index-url`
|
/// `--extra-index-url`
|
||||||
ExtraIndexUrl(VerbatimUrl),
|
ExtraIndexUrl(VerbatimUrl),
|
||||||
/// `--find-links`
|
/// `--find-links`
|
||||||
FindLinks(FindLink),
|
FindLinks(VerbatimUrl),
|
||||||
/// `--no-index`
|
/// `--no-index`
|
||||||
NoIndex,
|
NoIndex,
|
||||||
/// `--no-binary`
|
/// `--no-binary`
|
||||||
|
@ -93,73 +91,6 @@ enum RequirementsTxtStatement {
|
||||||
OnlyBinary(NoBuild),
|
OnlyBinary(NoBuild),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
||||||
pub enum FindLink {
|
|
||||||
Path(PathBuf),
|
|
||||||
Url(Url),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FindLink {
|
|
||||||
/// Parse a raw string for a `--find-links` entry, which could be a URL or a local path.
|
|
||||||
///
|
|
||||||
/// For example:
|
|
||||||
/// - `file:///home/ferris/project/scripts/...`
|
|
||||||
/// - `file:../ferris/`
|
|
||||||
/// - `../ferris/`
|
|
||||||
/// - `https://download.pytorch.org/whl/torch_stable.html`
|
|
||||||
pub fn parse(given: &str, working_dir: impl AsRef<Path>) -> Result<Self, url::ParseError> {
|
|
||||||
// Expand environment variables.
|
|
||||||
let expanded = expand_env_vars(given);
|
|
||||||
|
|
||||||
if let Some((scheme, path)) = split_scheme(&expanded) {
|
|
||||||
match Scheme::parse(scheme) {
|
|
||||||
// Ex) `file:///home/ferris/project/scripts/...`, `file://localhost/home/ferris/project/scripts/...`, or `file:../ferris/`
|
|
||||||
Some(Scheme::File) => {
|
|
||||||
// Strip the leading slashes, along with the `localhost` host, if present.
|
|
||||||
let path = strip_host(path);
|
|
||||||
|
|
||||||
// Transform, e.g., `/C:/Users/ferris/wheel-0.42.0.tar.gz` to `C:\Users\ferris\wheel-0.42.0.tar.gz`.
|
|
||||||
let path = normalize_url_path(path);
|
|
||||||
|
|
||||||
let path = PathBuf::from(path.as_ref());
|
|
||||||
let path = if path.is_absolute() {
|
|
||||||
path
|
|
||||||
} else {
|
|
||||||
working_dir.as_ref().join(path)
|
|
||||||
};
|
|
||||||
Ok(Self::Path(path))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ex) `https://download.pytorch.org/whl/torch_stable.html`
|
|
||||||
Some(_) => {
|
|
||||||
let url = Url::parse(&expanded)?;
|
|
||||||
Ok(Self::Url(url))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ex) `C:/Users/ferris/wheel-0.42.0.tar.gz`
|
|
||||||
_ => {
|
|
||||||
let path = PathBuf::from(expanded.as_ref());
|
|
||||||
let path = if path.is_absolute() {
|
|
||||||
path
|
|
||||||
} else {
|
|
||||||
working_dir.as_ref().join(path)
|
|
||||||
};
|
|
||||||
Ok(Self::Path(path))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Ex) `../ferris/`
|
|
||||||
let path = PathBuf::from(expanded.as_ref());
|
|
||||||
let path = if path.is_absolute() {
|
|
||||||
path
|
|
||||||
} else {
|
|
||||||
working_dir.as_ref().join(path)
|
|
||||||
};
|
|
||||||
Ok(Self::Path(path))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A [Requirement] with additional metadata from the `requirements.txt`, currently only hashes but in
|
/// A [Requirement] with additional metadata from the `requirements.txt`, currently only hashes but in
|
||||||
/// the future also editable and similar information.
|
/// the future also editable and similar information.
|
||||||
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
||||||
|
@ -212,7 +143,7 @@ pub struct RequirementsTxt {
|
||||||
/// The extra index URLs, specified with `--extra-index-url`.
|
/// The extra index URLs, specified with `--extra-index-url`.
|
||||||
pub extra_index_urls: Vec<VerbatimUrl>,
|
pub extra_index_urls: Vec<VerbatimUrl>,
|
||||||
/// The find links locations, specified with `--find-links`.
|
/// The find links locations, specified with `--find-links`.
|
||||||
pub find_links: Vec<FindLink>,
|
pub find_links: Vec<VerbatimUrl>,
|
||||||
/// Whether to ignore the index, specified with `--no-index`.
|
/// Whether to ignore the index, specified with `--no-index`.
|
||||||
pub no_index: bool,
|
pub no_index: bool,
|
||||||
/// Whether to disallow wheels, specified with `--no-binary`.
|
/// Whether to disallow wheels, specified with `--no-binary`.
|
||||||
|
@ -445,8 +376,8 @@ impl RequirementsTxt {
|
||||||
RequirementsTxtStatement::ExtraIndexUrl(url) => {
|
RequirementsTxtStatement::ExtraIndexUrl(url) => {
|
||||||
data.extra_index_urls.push(url);
|
data.extra_index_urls.push(url);
|
||||||
}
|
}
|
||||||
RequirementsTxtStatement::FindLinks(path_or_url) => {
|
RequirementsTxtStatement::FindLinks(url) => {
|
||||||
data.find_links.push(path_or_url);
|
data.find_links.push(url);
|
||||||
}
|
}
|
||||||
RequirementsTxtStatement::NoIndex => {
|
RequirementsTxtStatement::NoIndex => {
|
||||||
data.no_index = true;
|
data.no_index = true;
|
||||||
|
@ -592,16 +523,26 @@ fn parse_entry(
|
||||||
} else if s.eat_if("--no-index") {
|
} else if s.eat_if("--no-index") {
|
||||||
RequirementsTxtStatement::NoIndex
|
RequirementsTxtStatement::NoIndex
|
||||||
} else if s.eat_if("--find-links") || s.eat_if("-f") {
|
} else if s.eat_if("--find-links") || s.eat_if("-f") {
|
||||||
let path_or_url = parse_value(content, s, |c: char| !['\n', '\r', '#'].contains(&c))?;
|
let given = parse_value(content, s, |c: char| !['\n', '\r', '#'].contains(&c))?;
|
||||||
let path_or_url = FindLink::parse(path_or_url, working_dir).map_err(|err| {
|
let expanded = expand_env_vars(given);
|
||||||
RequirementsTxtParserError::Url {
|
let url = if let Ok(path) = Path::new(expanded.as_ref()).canonicalize() {
|
||||||
|
VerbatimUrl::from_path(path).map_err(|err| RequirementsTxtParserError::VerbatimUrl {
|
||||||
source: err,
|
source: err,
|
||||||
url: path_or_url.to_string(),
|
url: given.to_string(),
|
||||||
start,
|
start,
|
||||||
end: s.cursor(),
|
end: s.cursor(),
|
||||||
}
|
})?
|
||||||
})?;
|
} else {
|
||||||
RequirementsTxtStatement::FindLinks(path_or_url)
|
VerbatimUrl::parse_url(expanded.as_ref()).map_err(|err| {
|
||||||
|
RequirementsTxtParserError::Url {
|
||||||
|
source: err,
|
||||||
|
url: given.to_string(),
|
||||||
|
start,
|
||||||
|
end: s.cursor(),
|
||||||
|
}
|
||||||
|
})?
|
||||||
|
};
|
||||||
|
RequirementsTxtStatement::FindLinks(url.with_given(given))
|
||||||
} else if s.eat_if("--no-binary") {
|
} else if s.eat_if("--no-binary") {
|
||||||
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 specifier = PackageNameSpecifier::from_str(given).map_err(|err| {
|
let specifier = PackageNameSpecifier::from_str(given).map_err(|err| {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use std::path::PathBuf;
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use futures::{FutureExt, StreamExt};
|
use futures::{FutureExt, StreamExt};
|
||||||
use reqwest::Response;
|
use reqwest::Response;
|
||||||
|
@ -7,7 +7,6 @@ use url::Url;
|
||||||
|
|
||||||
use distribution_filename::DistFilename;
|
use distribution_filename::DistFilename;
|
||||||
use distribution_types::{File, FileLocation, FlatIndexLocation, IndexUrl};
|
use distribution_types::{File, FileLocation, FlatIndexLocation, IndexUrl};
|
||||||
use pep508_rs::VerbatimUrl;
|
|
||||||
use uv_cache::{Cache, CacheBucket};
|
use uv_cache::{Cache, CacheBucket};
|
||||||
|
|
||||||
use crate::cached_client::{CacheControl, CachedClientError};
|
use crate::cached_client::{CacheControl, CachedClientError};
|
||||||
|
@ -16,6 +15,9 @@ use crate::{Connectivity, Error, ErrorKind, RegistryClient};
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
pub enum FlatIndexError {
|
pub enum FlatIndexError {
|
||||||
|
#[error("Expected a file URL, but received: {0}")]
|
||||||
|
NonFileUrl(Url),
|
||||||
|
|
||||||
#[error("Failed to read `--find-links` directory: {0}")]
|
#[error("Failed to read `--find-links` directory: {0}")]
|
||||||
FindLinksDirectory(PathBuf, #[source] FindLinksDirectoryError),
|
FindLinksDirectory(PathBuf, #[source] FindLinksDirectoryError),
|
||||||
|
|
||||||
|
@ -97,12 +99,17 @@ impl<'a> FlatIndexClient<'a> {
|
||||||
let mut fetches = futures::stream::iter(indexes)
|
let mut fetches = futures::stream::iter(indexes)
|
||||||
.map(|index| async move {
|
.map(|index| async move {
|
||||||
let entries = match index {
|
let entries = match index {
|
||||||
FlatIndexLocation::Path(path) => Self::read_from_directory(path)
|
FlatIndexLocation::Path(url) => {
|
||||||
.map_err(|err| FlatIndexError::FindLinksDirectory(path.clone(), err))?,
|
let path = url
|
||||||
|
.to_file_path()
|
||||||
|
.map_err(|()| FlatIndexError::NonFileUrl(url.to_url()))?;
|
||||||
|
Self::read_from_directory(&path, index)
|
||||||
|
.map_err(|err| FlatIndexError::FindLinksDirectory(path.clone(), err))?
|
||||||
|
}
|
||||||
FlatIndexLocation::Url(url) => self
|
FlatIndexLocation::Url(url) => self
|
||||||
.read_from_url(url)
|
.read_from_url(url, index)
|
||||||
.await
|
.await
|
||||||
.map_err(|err| FlatIndexError::FindLinksUrl(url.clone(), err))?,
|
.map_err(|err| FlatIndexError::FindLinksUrl(url.to_url(), err))?,
|
||||||
};
|
};
|
||||||
if entries.is_empty() {
|
if entries.is_empty() {
|
||||||
warn!("No packages found in `--find-links` entry: {}", index);
|
warn!("No packages found in `--find-links` entry: {}", index);
|
||||||
|
@ -126,7 +133,11 @@ impl<'a> FlatIndexClient<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Read a flat remote index from a `--find-links` URL.
|
/// Read a flat remote index from a `--find-links` URL.
|
||||||
async fn read_from_url(&self, url: &Url) -> Result<FlatIndexEntries, Error> {
|
async fn read_from_url(
|
||||||
|
&self,
|
||||||
|
url: &Url,
|
||||||
|
flat_index: &FlatIndexLocation,
|
||||||
|
) -> Result<FlatIndexEntries, Error> {
|
||||||
let cache_entry = self.cache.entry(
|
let cache_entry = self.cache.entry(
|
||||||
CacheBucket::FlatIndex,
|
CacheBucket::FlatIndex,
|
||||||
"html",
|
"html",
|
||||||
|
@ -189,14 +200,13 @@ impl<'a> FlatIndexClient<'a> {
|
||||||
.await;
|
.await;
|
||||||
match response {
|
match response {
|
||||||
Ok(files) => {
|
Ok(files) => {
|
||||||
let index_url = IndexUrl::Url(VerbatimUrl::from_url(url.clone()));
|
|
||||||
let files = files
|
let files = files
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter_map(|file| {
|
.filter_map(|file| {
|
||||||
Some((
|
Some((
|
||||||
DistFilename::try_from_normalized_filename(&file.filename)?,
|
DistFilename::try_from_normalized_filename(&file.filename)?,
|
||||||
file,
|
file,
|
||||||
index_url.clone(),
|
IndexUrl::from(flat_index.clone()),
|
||||||
))
|
))
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
@ -210,11 +220,10 @@ impl<'a> FlatIndexClient<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Read a flat remote index from a `--find-links` directory.
|
/// Read a flat remote index from a `--find-links` directory.
|
||||||
fn read_from_directory(path: &PathBuf) -> Result<FlatIndexEntries, FindLinksDirectoryError> {
|
fn read_from_directory(
|
||||||
// Absolute paths are required for the URL conversion.
|
path: &Path,
|
||||||
let path = fs_err::canonicalize(path)?;
|
flat_index: &FlatIndexLocation,
|
||||||
let index_url = IndexUrl::Path(VerbatimUrl::from_path(&path)?);
|
) -> Result<FlatIndexEntries, FindLinksDirectoryError> {
|
||||||
|
|
||||||
let mut dists = Vec::new();
|
let mut dists = Vec::new();
|
||||||
for entry in fs_err::read_dir(path)? {
|
for entry in fs_err::read_dir(path)? {
|
||||||
let entry = entry?;
|
let entry = entry?;
|
||||||
|
@ -249,7 +258,7 @@ impl<'a> FlatIndexClient<'a> {
|
||||||
);
|
);
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
dists.push((filename, file, index_url.clone()));
|
dists.push((filename, file, IndexUrl::from(flat_index.clone())));
|
||||||
}
|
}
|
||||||
Ok(FlatIndexEntries::from_entries(dists))
|
Ok(FlatIndexEntries::from_entries(dists))
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,9 +3,8 @@ use std::collections::BTreeMap;
|
||||||
|
|
||||||
use rustc_hash::FxHashMap;
|
use rustc_hash::FxHashMap;
|
||||||
|
|
||||||
use distribution_types::{CachedRegistryDist, FlatIndexLocation, Hashed, IndexLocations, IndexUrl};
|
use distribution_types::{CachedRegistryDist, Hashed, IndexLocations, IndexUrl};
|
||||||
use pep440_rs::Version;
|
use pep440_rs::Version;
|
||||||
use pep508_rs::VerbatimUrl;
|
|
||||||
use platform_tags::Tags;
|
use platform_tags::Tags;
|
||||||
use uv_cache::{Cache, CacheBucket, WheelCache};
|
use uv_cache::{Cache, CacheBucket, WheelCache};
|
||||||
use uv_fs::{directories, files, symlinks};
|
use uv_fs::{directories, files, symlinks};
|
||||||
|
@ -91,15 +90,7 @@ impl<'a> RegistryWheelIndex<'a> {
|
||||||
// Collect into owned `IndexUrl`.
|
// Collect into owned `IndexUrl`.
|
||||||
let flat_index_urls: Vec<IndexUrl> = index_locations
|
let flat_index_urls: Vec<IndexUrl> = index_locations
|
||||||
.flat_index()
|
.flat_index()
|
||||||
.filter_map(|flat_index| match flat_index {
|
.map(|flat_index| IndexUrl::from(flat_index.clone()))
|
||||||
FlatIndexLocation::Path(path) => {
|
|
||||||
let path = fs_err::canonicalize(path).ok()?;
|
|
||||||
Some(IndexUrl::Path(VerbatimUrl::from_path(path).ok()?))
|
|
||||||
}
|
|
||||||
FlatIndexLocation::Url(url) => {
|
|
||||||
Some(IndexUrl::Url(VerbatimUrl::from_url(url.clone())))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
for index_url in index_locations.indexes().chain(flat_index_urls.iter()) {
|
for index_url in index_locations.indexes().chain(flat_index_urls.iter()) {
|
||||||
|
|
|
@ -40,7 +40,7 @@ use distribution_types::{
|
||||||
use pep508_rs::{UnnamedRequirement, UnnamedRequirementUrl};
|
use pep508_rs::{UnnamedRequirement, UnnamedRequirementUrl};
|
||||||
use pypi_types::Requirement;
|
use pypi_types::Requirement;
|
||||||
use pypi_types::VerbatimParsedUrl;
|
use pypi_types::VerbatimParsedUrl;
|
||||||
use requirements_txt::{FindLink, RequirementsTxt, RequirementsTxtRequirement};
|
use requirements_txt::{RequirementsTxt, RequirementsTxtRequirement};
|
||||||
use uv_client::BaseClientBuilder;
|
use uv_client::BaseClientBuilder;
|
||||||
use uv_configuration::{NoBinary, NoBuild};
|
use uv_configuration::{NoBinary, NoBuild};
|
||||||
use uv_distribution::pyproject::PyProjectToml;
|
use uv_distribution::pyproject::PyProjectToml;
|
||||||
|
@ -143,10 +143,7 @@ impl RequirementsSpecification {
|
||||||
find_links: requirements_txt
|
find_links: requirements_txt
|
||||||
.find_links
|
.find_links
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|link| match link {
|
.map(FlatIndexLocation::from)
|
||||||
FindLink::Url(url) => FlatIndexLocation::Url(url),
|
|
||||||
FindLink::Path(path) => FlatIndexLocation::Path(path),
|
|
||||||
})
|
|
||||||
.collect(),
|
.collect(),
|
||||||
no_binary: requirements_txt.no_binary,
|
no_binary: requirements_txt.no_binary,
|
||||||
no_build: requirements_txt.only_binary,
|
no_build: requirements_txt.only_binary,
|
||||||
|
|
|
@ -411,7 +411,7 @@ pub(crate) async fn pip_compile(
|
||||||
// If necessary, include the `--find-links` locations.
|
// If necessary, include the `--find-links` locations.
|
||||||
if include_find_links {
|
if include_find_links {
|
||||||
for flat_index in index_locations.flat_index() {
|
for flat_index in index_locations.flat_index() {
|
||||||
writeln!(writer, "--find-links {flat_index}")?;
|
writeln!(writer, "--find-links {}", flat_index.verbatim())?;
|
||||||
wrote_preamble = true;
|
wrote_preamble = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5083,6 +5083,35 @@ fn emit_find_links() -> Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Emit the `--find-links` locations using a relative path in a requirements file. The verbatim
|
||||||
|
/// path should be preserved.
|
||||||
|
#[test]
|
||||||
|
fn emit_find_links_relative() -> Result<()> {
|
||||||
|
let context = TestContext::new("3.12");
|
||||||
|
let requirements_in = context.temp_dir.child("requirements.in");
|
||||||
|
requirements_in.write_str("-f ./\niniconfig")?;
|
||||||
|
|
||||||
|
uv_snapshot!(context.pip_compile()
|
||||||
|
.arg("requirements.in")
|
||||||
|
.arg("--emit-find-links"), @r###"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
# This file was autogenerated by uv via the following command:
|
||||||
|
# uv pip compile --cache-dir [CACHE_DIR] requirements.in --emit-find-links
|
||||||
|
--find-links ./
|
||||||
|
|
||||||
|
iniconfig==2.0.0
|
||||||
|
# via -r requirements.in
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
Resolved 1 package in [TIME]
|
||||||
|
"###
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// Emit the `--no-binary` and `--only-binary` options.
|
/// Emit the `--no-binary` and `--only-binary` options.
|
||||||
#[test]
|
#[test]
|
||||||
fn emit_build_options() -> Result<()> {
|
fn emit_build_options() -> Result<()> {
|
||||||
|
|
|
@ -1287,20 +1287,25 @@ fn resolve_find_links() -> anyhow::Result<()> {
|
||||||
extra_index: [],
|
extra_index: [],
|
||||||
flat_index: [
|
flat_index: [
|
||||||
Url(
|
Url(
|
||||||
Url {
|
VerbatimUrl {
|
||||||
scheme: "https",
|
url: Url {
|
||||||
cannot_be_a_base: false,
|
scheme: "https",
|
||||||
username: "",
|
cannot_be_a_base: false,
|
||||||
password: None,
|
username: "",
|
||||||
host: Some(
|
password: None,
|
||||||
Domain(
|
host: Some(
|
||||||
"download.pytorch.org",
|
Domain(
|
||||||
|
"download.pytorch.org",
|
||||||
|
),
|
||||||
),
|
),
|
||||||
|
port: None,
|
||||||
|
path: "/whl/torch_stable.html",
|
||||||
|
query: None,
|
||||||
|
fragment: None,
|
||||||
|
},
|
||||||
|
given: Some(
|
||||||
|
"https://download.pytorch.org/whl/torch_stable.html",
|
||||||
),
|
),
|
||||||
port: None,
|
|
||||||
path: "/whl/torch_stable.html",
|
|
||||||
query: None,
|
|
||||||
fragment: None,
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue