mirror of
https://github.com/astral-sh/uv.git
synced 2025-10-21 15:52:15 +00:00
Add support for absolute paths on Windows (#1725)
## Summary The main change is that we need to have an explicit list of protocols we _do_ support (like `https`), so that when we see a Windows absolute path (`C:\...`), we don't treat the `C` as a protocol itself. Closes https://github.com/astral-sh/uv/issues/1539.
This commit is contained in:
parent
8f739c9b23
commit
c05080a3e3
7 changed files with 403 additions and 110 deletions
|
@ -46,7 +46,7 @@ use uv_fs::normalize_url_path;
|
|||
#[cfg(feature = "pyo3")]
|
||||
use uv_normalize::InvalidNameError;
|
||||
use uv_normalize::{ExtraName, PackageName};
|
||||
pub use verbatim_url::{split_scheme, VerbatimUrl};
|
||||
pub use verbatim_url::{split_scheme, Scheme, VerbatimUrl};
|
||||
|
||||
mod marker;
|
||||
mod verbatim_url;
|
||||
|
@ -744,33 +744,54 @@ fn preprocess_url(
|
|||
len: usize,
|
||||
) -> Result<VerbatimUrl, Pep508Error> {
|
||||
let url = if let Some((scheme, path)) = split_scheme(url) {
|
||||
if scheme == "file" {
|
||||
match Scheme::parse(scheme) {
|
||||
// Ex) `file:///home/ferris/project/scripts/...` or `file:../editable/`.
|
||||
let path = path.strip_prefix("//").unwrap_or(path);
|
||||
Some(Scheme::File) => {
|
||||
let path = path.strip_prefix("//").unwrap_or(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);
|
||||
// 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);
|
||||
|
||||
if let Some(working_dir) = working_dir {
|
||||
VerbatimUrl::from_path(path, working_dir).with_given(url.to_string())
|
||||
} else {
|
||||
VerbatimUrl::from_absolute_path(path)
|
||||
.map_err(|err| Pep508Error {
|
||||
message: Pep508ErrorSource::UrlError(err),
|
||||
start,
|
||||
len,
|
||||
input: cursor.to_string(),
|
||||
})?
|
||||
.with_given(url.to_string())
|
||||
if let Some(working_dir) = working_dir {
|
||||
VerbatimUrl::from_path(path, working_dir).with_given(url.to_string())
|
||||
} else {
|
||||
VerbatimUrl::from_absolute_path(path)
|
||||
.map_err(|err| Pep508Error {
|
||||
message: Pep508ErrorSource::UrlError(err),
|
||||
start,
|
||||
len,
|
||||
input: cursor.to_string(),
|
||||
})?
|
||||
.with_given(url.to_string())
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
// Ex) `https://download.pytorch.org/whl/torch_stable.html`
|
||||
VerbatimUrl::from_str(url).map_err(|err| Pep508Error {
|
||||
message: Pep508ErrorSource::UrlError(err),
|
||||
start,
|
||||
len,
|
||||
input: cursor.to_string(),
|
||||
})?
|
||||
Some(_) => {
|
||||
// Ex) `https://download.pytorch.org/whl/torch_stable.html`
|
||||
VerbatimUrl::from_str(url).map_err(|err| Pep508Error {
|
||||
message: Pep508ErrorSource::UrlError(err),
|
||||
start,
|
||||
len,
|
||||
input: cursor.to_string(),
|
||||
})?
|
||||
}
|
||||
|
||||
// Ex) `C:\Users\ferris\wheel-0.42.0.tar.gz`
|
||||
_ => {
|
||||
if let Some(working_dir) = working_dir {
|
||||
VerbatimUrl::from_path(url, working_dir).with_given(url.to_string())
|
||||
} else {
|
||||
VerbatimUrl::from_absolute_path(url)
|
||||
.map_err(|err| Pep508Error {
|
||||
message: Pep508ErrorSource::UrlError(err),
|
||||
start,
|
||||
len,
|
||||
input: cursor.to_string(),
|
||||
})?
|
||||
.with_given(url.to_string())
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Ex) `../editable/`
|
||||
|
|
|
@ -251,6 +251,127 @@ pub fn split_scheme(s: &str) -> Option<(&str, &str)> {
|
|||
Some((scheme, rest))
|
||||
}
|
||||
|
||||
/// A supported URL scheme for PEP 508 direct-URL requirements.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum Scheme {
|
||||
/// `file://...`
|
||||
File,
|
||||
/// `git+git://...`
|
||||
GitGit,
|
||||
/// `git+http://...`
|
||||
GitHttp,
|
||||
/// `git+file://...`
|
||||
GitFile,
|
||||
/// `git+ssh://...`
|
||||
GitSsh,
|
||||
/// `git+https://...`
|
||||
GitHttps,
|
||||
/// `bzr+http://...`
|
||||
BzrHttp,
|
||||
/// `bzr+https://...`
|
||||
BzrHttps,
|
||||
/// `bzr+ssh://...`
|
||||
BzrSsh,
|
||||
/// `bzr+sftp://...`
|
||||
BzrSftp,
|
||||
/// `bzr+ftp://...`
|
||||
BzrFtp,
|
||||
/// `bzr+lp://...`
|
||||
BzrLp,
|
||||
/// `bzr+file://...`
|
||||
BzrFile,
|
||||
/// `hg+file://...`
|
||||
HgFile,
|
||||
/// `hg+http://...`
|
||||
HgHttp,
|
||||
/// `hg+https://...`
|
||||
HgHttps,
|
||||
/// `hg+ssh://...`
|
||||
HgSsh,
|
||||
/// `hg+static-http://...`
|
||||
HgStaticHttp,
|
||||
/// `svn+ssh://...`
|
||||
SvnSsh,
|
||||
/// `svn+http://...`
|
||||
SvnHttp,
|
||||
/// `svn+https://...`
|
||||
SvnHttps,
|
||||
/// `svn+svn://...`
|
||||
SvnSvn,
|
||||
/// `svn+file://...`
|
||||
SvnFile,
|
||||
/// `http://...`
|
||||
Http,
|
||||
/// `https://...`
|
||||
Https,
|
||||
}
|
||||
|
||||
impl Scheme {
|
||||
/// Determine the [`Scheme`] from the given string, if possible.
|
||||
pub fn parse(s: &str) -> Option<Self> {
|
||||
match s {
|
||||
"file" => Some(Self::File),
|
||||
"git+git" => Some(Self::GitGit),
|
||||
"git+http" => Some(Self::GitHttp),
|
||||
"git+file" => Some(Self::GitFile),
|
||||
"git+ssh" => Some(Self::GitSsh),
|
||||
"git+https" => Some(Self::GitHttps),
|
||||
"bzr+http" => Some(Self::BzrHttp),
|
||||
"bzr+https" => Some(Self::BzrHttps),
|
||||
"bzr+ssh" => Some(Self::BzrSsh),
|
||||
"bzr+sftp" => Some(Self::BzrSftp),
|
||||
"bzr+ftp" => Some(Self::BzrFtp),
|
||||
"bzr+lp" => Some(Self::BzrLp),
|
||||
"bzr+file" => Some(Self::BzrFile),
|
||||
"hg+file" => Some(Self::HgFile),
|
||||
"hg+http" => Some(Self::HgHttp),
|
||||
"hg+https" => Some(Self::HgHttps),
|
||||
"hg+ssh" => Some(Self::HgSsh),
|
||||
"hg+static-http" => Some(Self::HgStaticHttp),
|
||||
"svn+ssh" => Some(Self::SvnSsh),
|
||||
"svn+http" => Some(Self::SvnHttp),
|
||||
"svn+https" => Some(Self::SvnHttps),
|
||||
"svn+svn" => Some(Self::SvnSvn),
|
||||
"svn+file" => Some(Self::SvnFile),
|
||||
"http" => Some(Self::Http),
|
||||
"https" => Some(Self::Https),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Scheme {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::File => write!(f, "file"),
|
||||
Self::GitGit => write!(f, "git+git"),
|
||||
Self::GitHttp => write!(f, "git+http"),
|
||||
Self::GitFile => write!(f, "git+file"),
|
||||
Self::GitSsh => write!(f, "git+ssh"),
|
||||
Self::GitHttps => write!(f, "git+https"),
|
||||
Self::BzrHttp => write!(f, "bzr+http"),
|
||||
Self::BzrHttps => write!(f, "bzr+https"),
|
||||
Self::BzrSsh => write!(f, "bzr+ssh"),
|
||||
Self::BzrSftp => write!(f, "bzr+sftp"),
|
||||
Self::BzrFtp => write!(f, "bzr+ftp"),
|
||||
Self::BzrLp => write!(f, "bzr+lp"),
|
||||
Self::BzrFile => write!(f, "bzr+file"),
|
||||
Self::HgFile => write!(f, "hg+file"),
|
||||
Self::HgHttp => write!(f, "hg+http"),
|
||||
Self::HgHttps => write!(f, "hg+https"),
|
||||
Self::HgSsh => write!(f, "hg+ssh"),
|
||||
Self::HgStaticHttp => write!(f, "hg+static-http"),
|
||||
Self::SvnSsh => write!(f, "svn+ssh"),
|
||||
Self::SvnHttp => write!(f, "svn+http"),
|
||||
Self::SvnHttps => write!(f, "svn+https"),
|
||||
Self::SvnSvn => write!(f, "svn+svn"),
|
||||
Self::SvnFile => write!(f, "svn+file"),
|
||||
Self::Http => write!(f, "http"),
|
||||
Self::Https => write!(f, "https"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue