mirror of
https://github.com/astral-sh/uv.git
synced 2025-08-03 02:22:19 +00:00
Support file://localhost/
schemes (#2657)
## Summary `uv` was failing to install requirements defined like: ``` file://localhost/Users/crmarsh/Downloads/iniconfig-2.0.0-py3-none-any.whl ``` Closes https://github.com/astral-sh/uv/issues/2652.
This commit is contained in:
parent
7a5571fa5c
commit
8587c440fe
6 changed files with 119 additions and 13 deletions
|
@ -9,7 +9,7 @@ use once_cell::sync::Lazy;
|
|||
use serde::{Deserialize, Serialize};
|
||||
use url::Url;
|
||||
|
||||
use pep508_rs::{expand_env_vars, split_scheme, Scheme, VerbatimUrl};
|
||||
use pep508_rs::{expand_env_vars, split_scheme, strip_host, Scheme, VerbatimUrl};
|
||||
use uv_fs::normalize_url_path;
|
||||
|
||||
use crate::Verbatim;
|
||||
|
@ -124,9 +124,10 @@ impl FromStr for FlatIndexLocation {
|
|||
// Parse the expanded path.
|
||||
if let Some((scheme, path)) = split_scheme(&expanded) {
|
||||
match Scheme::parse(scheme) {
|
||||
// Ex) `file:///home/ferris/project/scripts/...` or `file:../ferris/`
|
||||
// Ex) `file:///home/ferris/project/scripts/...`, `file://localhost/home/ferris/project/scripts/...`, or `file:../ferris/`
|
||||
Some(Scheme::File) => {
|
||||
let path = path.strip_prefix("//").unwrap_or(path);
|
||||
// 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);
|
||||
|
|
|
@ -46,7 +46,7 @@ use uv_fs::normalize_url_path;
|
|||
// Parity with the crates.io version of pep508_rs
|
||||
use crate::verbatim_url::VerbatimUrlError;
|
||||
pub use uv_normalize::{ExtraName, InvalidNameError, PackageName};
|
||||
pub use verbatim_url::{expand_env_vars, split_scheme, Scheme, VerbatimUrl};
|
||||
pub use verbatim_url::{expand_env_vars, split_scheme, strip_host, Scheme, VerbatimUrl};
|
||||
|
||||
mod marker;
|
||||
mod verbatim_url;
|
||||
|
@ -1018,9 +1018,10 @@ fn preprocess_url(
|
|||
|
||||
if let Some((scheme, path)) = split_scheme(&expanded) {
|
||||
match Scheme::parse(scheme) {
|
||||
// Ex) `file:///home/ferris/project/scripts/...` or `file:../editable/`.
|
||||
// Ex) `file:///home/ferris/project/scripts/...`, `file://localhost/home/ferris/project/scripts/...`, or `file:../ferris/`
|
||||
Some(Scheme::File) => {
|
||||
let path = path.strip_prefix("//").unwrap_or(path);
|
||||
// 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);
|
||||
|
@ -1156,9 +1157,10 @@ fn preprocess_unnamed_url(
|
|||
|
||||
if let Some((scheme, path)) = split_scheme(&expanded) {
|
||||
match Scheme::parse(scheme) {
|
||||
// Ex) `file:///home/ferris/project/scripts/...` or `file:../editable/`.
|
||||
// Ex) `file:///home/ferris/project/scripts/...`, `file://localhost/home/ferris/project/scripts/...`, or `file:../ferris/`
|
||||
Some(Scheme::File) => {
|
||||
let path = path.strip_prefix("//").unwrap_or(path);
|
||||
// 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);
|
||||
|
|
|
@ -264,6 +264,24 @@ pub fn split_scheme(s: &str) -> Option<(&str, &str)> {
|
|||
Some((scheme, rest))
|
||||
}
|
||||
|
||||
/// Strip the `file://localhost/` host from a file path.
|
||||
pub fn strip_host(path: &str) -> &str {
|
||||
// Ex) `file://localhost/...`.
|
||||
if let Some(path) = path
|
||||
.strip_prefix("//localhost")
|
||||
.filter(|path| path.starts_with('/'))
|
||||
{
|
||||
return path;
|
||||
}
|
||||
|
||||
// Ex) `file:///...`.
|
||||
if let Some(path) = path.strip_prefix("//") {
|
||||
return path;
|
||||
}
|
||||
|
||||
path
|
||||
}
|
||||
|
||||
/// Split the fragment from a URL.
|
||||
///
|
||||
/// For example, given `file:///home/ferris/project/scripts#hash=somehash`, returns
|
||||
|
|
|
@ -46,7 +46,7 @@ use unscanny::{Pattern, Scanner};
|
|||
use url::Url;
|
||||
|
||||
use pep508_rs::{
|
||||
expand_env_vars, split_scheme, Extras, Pep508Error, Pep508ErrorSource, Requirement,
|
||||
expand_env_vars, split_scheme, strip_host, Extras, Pep508Error, Pep508ErrorSource, Requirement,
|
||||
RequirementsTxtRequirement, Scheme, VerbatimUrl,
|
||||
};
|
||||
#[cfg(feature = "http")]
|
||||
|
@ -104,9 +104,10 @@ impl FindLink {
|
|||
|
||||
if let Some((scheme, path)) = split_scheme(&expanded) {
|
||||
match Scheme::parse(scheme) {
|
||||
// Ex) `file:///home/ferris/project/scripts/...` or `file:../ferris/`
|
||||
// Ex) `file:///home/ferris/project/scripts/...`, `file://localhost/home/ferris/project/scripts/...`, or `file:../ferris/`
|
||||
Some(Scheme::File) => {
|
||||
let path = path.strip_prefix("//").unwrap_or(path);
|
||||
// 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);
|
||||
|
@ -221,7 +222,8 @@ impl EditableRequirement {
|
|||
match Scheme::parse(scheme) {
|
||||
// Ex) `file:///home/ferris/project/scripts/...` or `file:../editable/`
|
||||
Some(Scheme::File) => {
|
||||
let path = path.strip_prefix("//").unwrap_or(path);
|
||||
// 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);
|
||||
|
|
|
@ -1284,7 +1284,7 @@ pub async fn download_and_extract_archive(
|
|||
client: &RegistryClient,
|
||||
) -> Result<ExtractedSource, Error> {
|
||||
match Scheme::parse(url.scheme()) {
|
||||
// Ex) `file:///home/ferris/project/scripts/...` or `file:../editable/`.
|
||||
// Ex) `file:///home/ferris/project/scripts/...`, `file://localhost/home/ferris/project/scripts/...`, or `file:../ferris/`
|
||||
Some(Scheme::File) => {
|
||||
let path = url.to_file_path().expect("URL to be a file path");
|
||||
extract_archive(&path, cache).await
|
||||
|
|
|
@ -2053,6 +2053,89 @@ fn compile_wheel_path_dependency() -> Result<()> {
|
|||
"###
|
||||
);
|
||||
|
||||
// Run the same operation, but this time with an absolute path (rather than a URL), including
|
||||
// the `file://` prefix.
|
||||
let requirements_in = context.temp_dir.child("requirements.in");
|
||||
requirements_in.write_str(&format!("flask @ file://{}", flask_wheel.path().display()))?;
|
||||
|
||||
// In addition to the standard filters, remove the temporary directory from the snapshot.
|
||||
let filter_path = regex::escape(&flask_wheel.user_display().to_string());
|
||||
let filters: Vec<_> = [(filter_path.as_str(), "/[TEMP_DIR]/")]
|
||||
.into_iter()
|
||||
.chain(INSTA_FILTERS.to_vec())
|
||||
.collect();
|
||||
|
||||
uv_snapshot!(filters, context.compile()
|
||||
.arg("requirements.in"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
# This file was autogenerated by uv via the following command:
|
||||
# uv pip compile --cache-dir [CACHE_DIR] --exclude-newer 2024-03-25T00:00:00Z requirements.in
|
||||
blinker==1.7.0
|
||||
# via flask
|
||||
click==8.1.7
|
||||
# via flask
|
||||
flask @ file:///[TEMP_DIR]/
|
||||
itsdangerous==2.1.2
|
||||
# via flask
|
||||
jinja2==3.1.3
|
||||
# via flask
|
||||
markupsafe==2.1.5
|
||||
# via
|
||||
# jinja2
|
||||
# werkzeug
|
||||
werkzeug==3.0.1
|
||||
# via flask
|
||||
|
||||
----- stderr -----
|
||||
Resolved 7 packages in [TIME]
|
||||
"###
|
||||
);
|
||||
|
||||
// Run the same operation, but this time with an absolute path (rather than a URL), including
|
||||
// the `file://localhost/` prefix.
|
||||
let requirements_in = context.temp_dir.child("requirements.in");
|
||||
requirements_in.write_str(&format!(
|
||||
"flask @ file://localhost/{}",
|
||||
flask_wheel.path().display()
|
||||
))?;
|
||||
|
||||
// In addition to the standard filters, remove the temporary directory from the snapshot.
|
||||
let filter_path = regex::escape(&flask_wheel.user_display().to_string());
|
||||
let filters: Vec<_> = [(filter_path.as_str(), "/[TEMP_DIR]/")]
|
||||
.into_iter()
|
||||
.chain(INSTA_FILTERS.to_vec())
|
||||
.collect();
|
||||
|
||||
uv_snapshot!(filters, context.compile()
|
||||
.arg("requirements.in"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
# This file was autogenerated by uv via the following command:
|
||||
# uv pip compile --cache-dir [CACHE_DIR] --exclude-newer 2024-03-25T00:00:00Z requirements.in
|
||||
blinker==1.7.0
|
||||
# via flask
|
||||
click==8.1.7
|
||||
# via flask
|
||||
flask @ file://localhost//[TEMP_DIR]/
|
||||
itsdangerous==2.1.2
|
||||
# via flask
|
||||
jinja2==3.1.3
|
||||
# via flask
|
||||
markupsafe==2.1.5
|
||||
# via
|
||||
# jinja2
|
||||
# werkzeug
|
||||
werkzeug==3.0.1
|
||||
# via flask
|
||||
|
||||
----- stderr -----
|
||||
Resolved 7 packages in [TIME]
|
||||
"###
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue