Skip to content

Commit

Permalink
Support environment variables in index URLs in requirements files
Browse files Browse the repository at this point in the history
  • Loading branch information
charliermarsh committed Feb 28, 2024
1 parent 995fba8 commit 26992b1
Show file tree
Hide file tree
Showing 6 changed files with 70 additions and 37 deletions.
35 changes: 27 additions & 8 deletions crates/distribution-types/src/index_url.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use std::borrow::Cow;
use std::fmt::{Display, Formatter};
use std::ops::Deref;
use std::path::PathBuf;
Expand All @@ -8,16 +9,18 @@ use once_cell::sync::Lazy;
use serde::{Deserialize, Serialize};
use url::Url;

use pep508_rs::{split_scheme, Scheme};
use pep508_rs::{split_scheme, Scheme, VerbatimUrl};
use uv_fs::normalize_url_path;

use crate::Verbatim;

static PYPI_URL: Lazy<Url> = Lazy::new(|| Url::parse("https://pypi.org/simple").unwrap());

/// The url of an index, newtype'd to avoid mixing it with file urls.
#[derive(Debug, Clone, Hash, Eq, PartialEq, Serialize, Deserialize)]
pub enum IndexUrl {
Pypi,
Url(Url),
Url(VerbatimUrl),
}

impl Display for IndexUrl {
Expand All @@ -29,17 +32,33 @@ impl Display for IndexUrl {
}
}

impl Verbatim for IndexUrl {
fn verbatim(&self) -> Cow<'_, str> {
match self {
Self::Pypi => Cow::Borrowed("https://pypi.org/simple"),
Self::Url(url) => url.verbatim(),
}
}
}

impl FromStr for IndexUrl {
type Err = url::ParseError;

fn from_str(url: &str) -> Result<Self, Self::Err> {
Ok(Self::from(Url::parse(url)?))
fn from_str(s: &str) -> Result<Self, Self::Err> {
let url = Url::parse(s)?;
if url == *PYPI_URL {
Ok(Self::Pypi)
} else {
Ok(Self::Url(
VerbatimUrl::from_url(url).with_given(s.to_owned()),
))
}
}
}

impl From<Url> for IndexUrl {
fn from(url: Url) -> Self {
if url == *PYPI_URL {
impl From<VerbatimUrl> for IndexUrl {
fn from(url: VerbatimUrl) -> Self {
if *url.raw() == *PYPI_URL {
Self::Pypi
} else {
Self::Url(url)
Expand All @@ -51,7 +70,7 @@ impl From<IndexUrl> for Url {
fn from(index: IndexUrl) -> Self {
match index {
IndexUrl::Pypi => PYPI_URL.clone(),
IndexUrl::Url(url) => url,
IndexUrl::Url(url) => url.to_url(),
}
}
}
Expand Down
18 changes: 12 additions & 6 deletions crates/pep508-rs/src/verbatim_url.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use std::path::{Component, Path, PathBuf};

use once_cell::sync::Lazy;
use regex::Regex;
use url::Url;
use url::{ParseError, Url};

/// A wrapper around [`Url`] that preserves the original string.
#[derive(Debug, Clone, Eq, derivative::Derivative)]
Expand All @@ -29,12 +29,16 @@ pub struct VerbatimUrl {

impl VerbatimUrl {
/// Parse a URL from a string, expanding any environment variables.
pub fn parse(given: impl AsRef<str>) -> Result<Self, VerbatimUrlError> {
let url = Url::parse(&expand_env_vars(given.as_ref(), true))
.map_err(|err| VerbatimUrlError::Url(given.as_ref().to_owned(), err))?;
pub fn parse(given: impl AsRef<str>) -> Result<Self, ParseError> {
let url = Url::parse(&expand_env_vars(given.as_ref(), true))?;
Ok(Self { url, given: None })
}

/// Create a [`VerbatimUrl`] from a [`Url`].
pub fn from_url(url: Url) -> Self {
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<str>, working_dir: impl AsRef<Path>) -> Self {
Expand Down Expand Up @@ -115,7 +119,9 @@ impl std::str::FromStr for VerbatimUrl {
type Err = VerbatimUrlError;

fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::parse(s).map(|url| url.with_given(s.to_owned()))
Self::parse(s)
.map(|url| url.with_given(s.to_owned()))
.map_err(|e| VerbatimUrlError::Url(s.to_owned(), e))
}
}

Expand All @@ -138,7 +144,7 @@ impl Deref for VerbatimUrl {
pub enum VerbatimUrlError {
/// Failed to parse a URL.
#[error("{0}")]
Url(String, #[source] url::ParseError),
Url(String, #[source] ParseError),

/// Received a relative path, but no working directory was provided.
#[error("relative path without a working directory: {0}")]
Expand Down
40 changes: 22 additions & 18 deletions crates/requirements-txt/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,9 @@ enum RequirementsTxtStatement {
/// `-e`
EditableRequirement(EditableRequirement),
/// `--index-url`
IndexUrl(Url),
IndexUrl(VerbatimUrl),
/// `--extra-index-url`
ExtraIndexUrl(Url),
ExtraIndexUrl(VerbatimUrl),
/// `--find-links`
FindLinks(FindLink),
/// `--no-index`
Expand Down Expand Up @@ -308,9 +308,9 @@ pub struct RequirementsTxt {
/// Editables with `-e`.
pub editables: Vec<EditableRequirement>,
/// The index URL, specified with `--index-url`.
pub index_url: Option<Url>,
pub index_url: Option<VerbatimUrl>,
/// The extra index URLs, specified with `--extra-index-url`.
pub extra_index_urls: Vec<Url>,
pub extra_index_urls: Vec<VerbatimUrl>,
/// The find links locations, specified with `--find-links`.
pub find_links: Vec<FindLink>,
/// Whether to ignore the index, specified with `--no-index`.
Expand Down Expand Up @@ -482,22 +482,26 @@ fn parse_entry(
.map_err(|err| err.with_offset(start))?;
RequirementsTxtStatement::EditableRequirement(editable_requirement)
} else if s.eat_if("-i") || s.eat_if("--index-url") {
let url = parse_value(s, |c: char| !['\n', '\r'].contains(&c))?;
let url = Url::parse(url).map_err(|err| RequirementsTxtParserError::Url {
source: err,
url: url.to_string(),
start,
end: s.cursor(),
})?;
let given = parse_value(s, |c: char| !['\n', '\r'].contains(&c))?;
let url = VerbatimUrl::parse(given)
.map(|url| url.with_given(given.to_owned()))
.map_err(|err| RequirementsTxtParserError::Url {
source: err,
url: given.to_string(),
start,
end: s.cursor(),
})?;
RequirementsTxtStatement::IndexUrl(url)
} else if s.eat_if("--extra-index-url") {
let url = parse_value(s, |c: char| !['\n', '\r'].contains(&c))?;
let url = Url::parse(url).map_err(|err| RequirementsTxtParserError::Url {
source: err,
url: url.to_string(),
start,
end: s.cursor(),
})?;
let given = parse_value(s, |c: char| !['\n', '\r'].contains(&c))?;
let url = VerbatimUrl::parse(given)
.map(|url| url.with_given(given.to_owned()))
.map_err(|err| RequirementsTxtParserError::Url {
source: err,
url: given.to_string(),
start,
end: s.cursor(),
})?;
RequirementsTxtStatement::ExtraIndexUrl(url)
} else if s.eat_if("--no-index") {
RequirementsTxtStatement::NoIndex
Expand Down
3 changes: 2 additions & 1 deletion crates/uv-client/src/flat_index.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ use distribution_types::{
RegistryBuiltDist, RegistrySourceDist, SourceDist,
};
use pep440_rs::Version;
use pep508_rs::VerbatimUrl;
use platform_tags::Tags;
use pypi_types::{Hashes, Yanked};
use uv_auth::safe_copy_url_auth;
Expand Down Expand Up @@ -197,7 +198,7 @@ impl<'a> FlatIndexClient<'a> {
Some((
DistFilename::try_from_normalized_filename(&file.filename)?,
file,
IndexUrl::Url(url.clone()),
IndexUrl::Url(VerbatimUrl::unknown(url.clone())),
))
})
.collect();
Expand Down
5 changes: 4 additions & 1 deletion crates/uv-distribution/src/index/registry_wheel_index.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use rustc_hash::FxHashMap;

use distribution_types::{CachedRegistryDist, FlatIndexLocation, IndexLocations, IndexUrl};
use pep440_rs::Version;
use pep508_rs::VerbatimUrl;
use platform_tags::Tags;
use uv_cache::{Cache, CacheBucket, WheelCache};
use uv_fs::{directories, symlinks};
Expand Down Expand Up @@ -83,7 +84,9 @@ impl<'a> RegistryWheelIndex<'a> {
.flat_index()
.filter_map(|flat_index| match flat_index {
FlatIndexLocation::Path(_) => None,
FlatIndexLocation::Url(url) => Some(IndexUrl::Url(url.clone())),
FlatIndexLocation::Url(url) => {
Some(IndexUrl::Url(VerbatimUrl::unknown(url.clone())))
}
})
.collect();

Expand Down
6 changes: 3 additions & 3 deletions crates/uv/src/commands/pip_compile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use rustc_hash::FxHashSet;
use tempfile::tempdir_in;
use tracing::debug;

use distribution_types::{IndexLocations, LocalEditable};
use distribution_types::{IndexLocations, LocalEditable, Verbatim};
use pep508_rs::Requirement;
use platform_host::Platform;
use platform_tags::Tags;
Expand Down Expand Up @@ -357,11 +357,11 @@ pub(crate) async fn pip_compile(
// If necessary, include the `--index-url` and `--extra-index-url` locations.
if include_index_url {
if let Some(index) = index_locations.index() {
writeln!(writer, "--index-url {index}")?;
writeln!(writer, "--index-url {}", index.verbatim())?;
wrote_index = true;
}
for extra_index in index_locations.extra_index() {
writeln!(writer, "--extra-index-url {extra_index}")?;
writeln!(writer, "--extra-index-url {}", extra_index.verbatim())?;
wrote_index = true;
}
}
Expand Down

0 comments on commit 26992b1

Please sign in to comment.