mirror of https://github.com/astral-sh/uv
Parse editable installs (#564)
Parse `-e` for editable installs in `requirements.txt`. Unlike all the other requirements, editable installs don't have the name of the package specified.
This commit is contained in:
parent
3f4d7b7826
commit
366c389385
|
|
@ -2947,6 +2947,7 @@ dependencies = [
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"tracing",
|
"tracing",
|
||||||
"unscanny",
|
"unscanny",
|
||||||
|
"url",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
[workspace]
|
[workspace]
|
||||||
members = ["crates/*"]
|
members = ["crates/*"]
|
||||||
|
exclude = ["scripts"]
|
||||||
resolver = "2"
|
resolver = "2"
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ serde = { workspace = true }
|
||||||
thiserror = { workspace = true }
|
thiserror = { workspace = true }
|
||||||
tracing = { workspace = true }
|
tracing = { workspace = true }
|
||||||
unscanny = { workspace = true }
|
unscanny = { workspace = true }
|
||||||
|
url = { workspace = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
anyhow = { version = "1.0.75" }
|
anyhow = { version = "1.0.75" }
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,6 @@
|
||||||
//! * `-e`
|
//! * `-e`
|
||||||
//!
|
//!
|
||||||
//! Unsupported:
|
//! Unsupported:
|
||||||
//! * `-e <path>`. TBD
|
|
||||||
//! * `<path>`. TBD
|
//! * `<path>`. TBD
|
||||||
//! * `<archive_url>`. TBD
|
//! * `<archive_url>`. TBD
|
||||||
//! * Options without a requirement, such as `--find-links` or `--index-url`
|
//! * Options without a requirement, such as `--find-links` or `--index-url`
|
||||||
|
|
@ -44,6 +43,7 @@ use fs_err as fs;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use tracing::warn;
|
use tracing::warn;
|
||||||
use unscanny::{Pattern, Scanner};
|
use unscanny::{Pattern, Scanner};
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
use pep508_rs::{Pep508Error, Requirement};
|
use pep508_rs::{Pep508Error, Requirement};
|
||||||
|
|
||||||
|
|
@ -63,6 +63,14 @@ enum RequirementsTxtStatement {
|
||||||
},
|
},
|
||||||
/// PEP 508 requirement plus metadata
|
/// PEP 508 requirement plus metadata
|
||||||
RequirementEntry(RequirementEntry),
|
RequirementEntry(RequirementEntry),
|
||||||
|
/// `-e`
|
||||||
|
EditableRequirement(EditableRequirement),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Clone, Eq, PartialEq, Serialize)]
|
||||||
|
pub enum EditableRequirement {
|
||||||
|
Path(PathBuf),
|
||||||
|
Url(Url),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 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
|
||||||
|
|
@ -97,6 +105,8 @@ pub struct RequirementsTxt {
|
||||||
pub requirements: Vec<RequirementEntry>,
|
pub requirements: Vec<RequirementEntry>,
|
||||||
/// Constraints included with `-c`
|
/// Constraints included with `-c`
|
||||||
pub constraints: Vec<Requirement>,
|
pub constraints: Vec<Requirement>,
|
||||||
|
/// Editables with `-e`
|
||||||
|
pub editable: Vec<EditableRequirement>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RequirementsTxt {
|
impl RequirementsTxt {
|
||||||
|
|
@ -182,6 +192,9 @@ impl RequirementsTxt {
|
||||||
RequirementsTxtStatement::RequirementEntry(requirement_entry) => {
|
RequirementsTxtStatement::RequirementEntry(requirement_entry) => {
|
||||||
data.requirements.push(requirement_entry);
|
data.requirements.push(requirement_entry);
|
||||||
}
|
}
|
||||||
|
RequirementsTxtStatement::EditableRequirement(editable) => {
|
||||||
|
data.editable.push(editable);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(data)
|
Ok(data)
|
||||||
|
|
@ -230,12 +243,13 @@ fn parse_entry(
|
||||||
end,
|
end,
|
||||||
}
|
}
|
||||||
} else if s.eat_if("-e") {
|
} else if s.eat_if("-e") {
|
||||||
let (requirement, hashes) = parse_requirement_and_hashes(s, content)?;
|
let path_or_url = parse_value(s, |c: char| !['\n', '\r'].contains(&c))?;
|
||||||
RequirementsTxtStatement::RequirementEntry(RequirementEntry {
|
let editable_requirement = if let Ok(url) = Url::from_str(path_or_url) {
|
||||||
requirement,
|
EditableRequirement::Url(url)
|
||||||
hashes,
|
} else {
|
||||||
editable: true,
|
EditableRequirement::Path(PathBuf::from(path_or_url))
|
||||||
})
|
};
|
||||||
|
RequirementsTxtStatement::EditableRequirement(editable_requirement)
|
||||||
} else if s.at(char::is_ascii_alphanumeric) {
|
} else if s.at(char::is_ascii_alphanumeric) {
|
||||||
let (requirement, hashes) = parse_requirement_and_hashes(s, content)?;
|
let (requirement, hashes) = parse_requirement_and_hashes(s, content)?;
|
||||||
RequirementsTxtStatement::RequirementEntry(RequirementEntry {
|
RequirementsTxtStatement::RequirementEntry(RequirementEntry {
|
||||||
|
|
@ -505,6 +519,7 @@ mod test {
|
||||||
#[test_case(Path::new("poetry-with-hashes.txt"))]
|
#[test_case(Path::new("poetry-with-hashes.txt"))]
|
||||||
#[test_case(Path::new("small.txt"))]
|
#[test_case(Path::new("small.txt"))]
|
||||||
#[test_case(Path::new("whitespace.txt"))]
|
#[test_case(Path::new("whitespace.txt"))]
|
||||||
|
#[test_case(Path::new("editable.txt"))]
|
||||||
fn line_endings(path: &Path) {
|
fn line_endings(path: &Path) {
|
||||||
let working_dir = workspace_test_data_dir().join("requirements-txt");
|
let working_dir = workspace_test_data_dir().join("requirements-txt");
|
||||||
let requirements_txt = working_dir.join(path);
|
let requirements_txt = working_dir.join(path);
|
||||||
|
|
@ -581,4 +596,28 @@ mod test {
|
||||||
fn workspace_test_data_dir() -> PathBuf {
|
fn workspace_test_data_dir() -> PathBuf {
|
||||||
PathBuf::from("./test-data")
|
PathBuf::from("./test-data")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn invalid_editable() {
|
||||||
|
let working_dir = workspace_test_data_dir().join("requirements-txt");
|
||||||
|
let basic = working_dir.join("invalid-requirement");
|
||||||
|
let err = RequirementsTxt::parse(&basic, &working_dir).unwrap_err();
|
||||||
|
let errors = anyhow::Error::new(err)
|
||||||
|
.chain()
|
||||||
|
.map(ToString::to_string)
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
let expected = &[
|
||||||
|
format!(
|
||||||
|
"Couldn't parse requirement in {} position 0 to 15",
|
||||||
|
basic.display()
|
||||||
|
),
|
||||||
|
indoc! {"
|
||||||
|
Expected an alphanumeric character starting the extra name, found 'ö'
|
||||||
|
numpy[ö]==1.29
|
||||||
|
^"
|
||||||
|
}
|
||||||
|
.to_string(),
|
||||||
|
];
|
||||||
|
assert_eq!(errors, expected);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -208,4 +208,5 @@ RequirementsTxt {
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
constraints: [],
|
constraints: [],
|
||||||
|
editable: [],
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -99,4 +99,5 @@ RequirementsTxt {
|
||||||
marker: None,
|
marker: None,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
editable: [],
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -73,4 +73,5 @@ RequirementsTxt {
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
constraints: [],
|
constraints: [],
|
||||||
|
editable: [],
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,58 @@
|
||||||
|
---
|
||||||
|
source: crates/requirements-txt/src/lib.rs
|
||||||
|
expression: actual
|
||||||
|
---
|
||||||
|
RequirementsTxt {
|
||||||
|
requirements: [
|
||||||
|
RequirementEntry {
|
||||||
|
requirement: Requirement {
|
||||||
|
name: PackageName(
|
||||||
|
"numpy",
|
||||||
|
),
|
||||||
|
extras: None,
|
||||||
|
version_or_url: None,
|
||||||
|
marker: None,
|
||||||
|
},
|
||||||
|
hashes: [],
|
||||||
|
editable: false,
|
||||||
|
},
|
||||||
|
RequirementEntry {
|
||||||
|
requirement: Requirement {
|
||||||
|
name: PackageName(
|
||||||
|
"pandas",
|
||||||
|
),
|
||||||
|
extras: Some(
|
||||||
|
[
|
||||||
|
ExtraName(
|
||||||
|
"tabulate",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
version_or_url: Some(
|
||||||
|
Url(
|
||||||
|
Url {
|
||||||
|
scheme: "https",
|
||||||
|
cannot_be_a_base: false,
|
||||||
|
username: "",
|
||||||
|
password: None,
|
||||||
|
host: Some(
|
||||||
|
Domain(
|
||||||
|
"github.com",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
port: None,
|
||||||
|
path: "/pandas-dev/pandas",
|
||||||
|
query: None,
|
||||||
|
fragment: None,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
marker: None,
|
||||||
|
},
|
||||||
|
hashes: [],
|
||||||
|
editable: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
constraints: [],
|
||||||
|
editable: [],
|
||||||
|
}
|
||||||
|
|
@ -5,4 +5,5 @@ expression: actual
|
||||||
RequirementsTxt {
|
RequirementsTxt {
|
||||||
requirements: [],
|
requirements: [],
|
||||||
constraints: [],
|
constraints: [],
|
||||||
|
editable: [],
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -136,4 +136,5 @@ RequirementsTxt {
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
constraints: [],
|
constraints: [],
|
||||||
|
editable: [],
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -52,4 +52,5 @@ RequirementsTxt {
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
constraints: [],
|
constraints: [],
|
||||||
|
editable: [],
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,4 +18,5 @@ RequirementsTxt {
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
constraints: [],
|
constraints: [],
|
||||||
|
editable: [],
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -336,4 +336,5 @@ RequirementsTxt {
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
constraints: [],
|
constraints: [],
|
||||||
|
editable: [],
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -74,4 +74,5 @@ RequirementsTxt {
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
constraints: [],
|
constraints: [],
|
||||||
|
editable: [],
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -54,4 +54,5 @@ RequirementsTxt {
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
constraints: [],
|
constraints: [],
|
||||||
|
editable: [],
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -208,4 +208,5 @@ RequirementsTxt {
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
constraints: [],
|
constraints: [],
|
||||||
|
editable: [],
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -99,4 +99,5 @@ RequirementsTxt {
|
||||||
marker: None,
|
marker: None,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
editable: [],
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -73,4 +73,5 @@ RequirementsTxt {
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
constraints: [],
|
constraints: [],
|
||||||
|
editable: [],
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,4 +5,5 @@ expression: actual
|
||||||
RequirementsTxt {
|
RequirementsTxt {
|
||||||
requirements: [],
|
requirements: [],
|
||||||
constraints: [],
|
constraints: [],
|
||||||
|
editable: [],
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -136,4 +136,5 @@ RequirementsTxt {
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
constraints: [],
|
constraints: [],
|
||||||
|
editable: [],
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -52,4 +52,5 @@ RequirementsTxt {
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
constraints: [],
|
constraints: [],
|
||||||
|
editable: [],
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,4 +18,5 @@ RequirementsTxt {
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
constraints: [],
|
constraints: [],
|
||||||
|
editable: [],
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -336,4 +336,5 @@ RequirementsTxt {
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
constraints: [],
|
constraints: [],
|
||||||
|
editable: [],
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -74,4 +74,5 @@ RequirementsTxt {
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
constraints: [],
|
constraints: [],
|
||||||
|
editable: [],
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -54,4 +54,5 @@ RequirementsTxt {
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
constraints: [],
|
constraints: [],
|
||||||
|
editable: [],
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
|
||||||
|
|
||||||
|
# #
|
||||||
|
# #
|
||||||
|
# #
|
||||||
|
# #
|
||||||
|
# #
|
||||||
|
# #
|
||||||
|
# #
|
||||||
|
|
||||||
|
##
|
||||||
|
|
||||||
|
#
|
||||||
|
|
||||||
|
numpy # #
|
||||||
|
|
||||||
|
\
|
||||||
|
|
||||||
|
pandas [tabulate] @ https://github.com/pandas-dev/pandas \
|
||||||
|
# üh
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# 안녕
|
||||||
|
#
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
numpy[ö]==1.29
|
||||||
Loading…
Reference in New Issue