Skip to content

Commit

Permalink
Add sources for editables
Browse files Browse the repository at this point in the history
  • Loading branch information
palfrey committed May 8, 2024
1 parent b8decb4 commit eb3ea98
Show file tree
Hide file tree
Showing 7 changed files with 127 additions and 49 deletions.
87 changes: 53 additions & 34 deletions crates/requirements-txt/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,9 @@ pub struct EditableRequirement {
pub url: VerbatimUrl,
pub extras: Vec<ExtraName>,
pub path: PathBuf,

/// Path of the original file (where existing)
pub source: Option<PathBuf>,
}

impl EditableRequirement {
Expand All @@ -191,6 +194,7 @@ impl EditableRequirement {
/// We disallow URLs with schemes other than `file://` (e.g., `https://...`).
pub fn parse(
given: &str,
source: Option<&Path>,
working_dir: impl AsRef<Path>,
) -> Result<Self, RequirementsTxtParserError> {
// Identify the extras.
Expand Down Expand Up @@ -265,7 +269,12 @@ impl EditableRequirement {
// Add the verbatim representation of the URL to the `VerbatimUrl`.
let url = url.with_given(requirement.to_string());

Ok(Self { url, extras, path })
Ok(Self {
url,
extras,
path,
source: source.map(|s| s.to_path_buf()),
})
}

/// Identify the extras in an editable URL (e.g., `../editable[dev]`).
Expand Down Expand Up @@ -626,8 +635,9 @@ fn parse_entry(
}
} else if s.eat_if("-e") || s.eat_if("--editable") {
let path_or_url = parse_value(content, s, |c: char| !['\n', '\r', '#'].contains(&c))?;
let editable_requirement = EditableRequirement::parse(path_or_url, working_dir)
.map_err(|err| err.with_offset(start))?;
let editable_requirement =
EditableRequirement::parse(path_or_url, Some(requirements_txt), working_dir)
.map_err(|err| err.with_offset(start))?;
RequirementsTxtStatement::EditableRequirement(editable_requirement)
} else if s.eat_if("-i") || s.eat_if("--index-url") {
let given = parse_value(content, s, |c: char| !['\n', '\r', '#'].contains(&c))?;
Expand Down Expand Up @@ -1951,40 +1961,49 @@ mod test {
.await
.unwrap();

insta::assert_debug_snapshot!(requirements, @r###"
RequirementsTxt {
requirements: [],
constraints: [],
editables: [
EditableRequirement {
url: VerbatimUrl {
url: Url {
scheme: "file",
cannot_be_a_base: false,
username: "",
password: None,
host: None,
port: None,
path: "/foo/bar",
query: None,
fragment: None,
let filter_path = safe_filter_path(temp_dir.path());
let filters = vec![(filter_path.as_str(), "<REQUIREMENTS_DIR>")];
insta::with_settings!({
filters => filters,
}, {
insta::assert_debug_snapshot!(requirements, @r###"
RequirementsTxt {
requirements: [],
constraints: [],
editables: [
EditableRequirement {
url: VerbatimUrl {
url: Url {
scheme: "file",
cannot_be_a_base: false,
username: "",
password: None,
host: None,
port: None,
path: "/foo/bar",
query: None,
fragment: None,
},
given: Some(
"/foo/bar",
),
},
given: Some(
"/foo/bar",
extras: [],
path: "/foo/bar",
source: Some(
"<REQUIREMENTS_DIR>grandchild.txt",
),
},
extras: [],
path: "/foo/bar",
},
],
index_url: None,
extra_index_urls: [],
find_links: [],
no_index: true,
no_binary: None,
only_binary: None,
}
"###);
],
index_url: None,
extra_index_urls: [],
find_links: [],
no_index: true,
no_binary: None,
only_binary: None,
}
"###);
});

Ok(())
}
Expand Down
3 changes: 2 additions & 1 deletion crates/uv-requirements/src/specification.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ impl RequirementsSpecification {
}
}
RequirementsSource::Editable(name) => {
let requirement = EditableRequirement::parse(name, std::env::current_dir()?)
let requirement = EditableRequirement::parse(name, None, std::env::current_dir()?)
.with_context(|| format!("Failed to parse `{name}`"))?;
Self {
project: None,
Expand Down Expand Up @@ -224,6 +224,7 @@ impl RequirementsSpecification {
url,
path,
extras: requirement.extras,
source: Some(pyproject_path.to_path_buf()),
})
} else {
Either::Right(UnresolvedRequirementSpecification {
Expand Down
13 changes: 10 additions & 3 deletions crates/uv-resolver/src/resolution.rs
Original file line number Diff line number Diff line change
Expand Up @@ -545,7 +545,7 @@ pub struct DisplayResolutionGraph<'a> {
/// package.
annotation_style: AnnotationStyle,
/// External sources for each package: requirements, constraints, and overrides.
sources: BTreeMap<PackageName, BTreeSet<SourceAnnotation>>,
sources: BTreeMap<String, BTreeSet<SourceAnnotation>>,
}

impl<'a> From<&'a ResolutionGraph> for DisplayResolutionGraph<'a> {
Expand All @@ -568,7 +568,7 @@ impl<'a> DisplayResolutionGraph<'a> {
#[allow(clippy::fn_params_excessive_bools, clippy::too_many_arguments)]
pub fn new(
underlying: &'a ResolutionGraph,
sources: BTreeMap<PackageName, BTreeSet<SourceAnnotation>>,
sources: BTreeMap<String, BTreeSet<SourceAnnotation>>,
no_emit_packages: &'a [PackageName],
show_hashes: bool,
include_extras: bool,
Expand Down Expand Up @@ -726,7 +726,14 @@ impl std::fmt::Display for DisplayResolutionGraph<'_> {
edges.sort_unstable_by_key(|package| package.name());

// Include all external sources (e.g., requirements files).
let source = self.sources.get(node.name()).cloned().unwrap_or_default();
let source_name: String = match node {
Node::Editable(_package_name, local_editable) => {
local_editable.url.given().unwrap_or_default().to_string()
}
ref other => other.name().to_string(),
};

let source = self.sources.get(&source_name).cloned().unwrap_or_default();

match self.annotation_style {
AnnotationStyle::Line => {
Expand Down
44 changes: 35 additions & 9 deletions crates/uv/src/commands/pip_compile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -354,20 +354,21 @@ pub(crate) async fn pip_compile(
.resolve()
.await?;

let mut sources: BTreeMap<PackageName, BTreeSet<SourceAnnotation>> = BTreeMap::new();
let mut sources: BTreeMap<String, BTreeSet<SourceAnnotation>> = BTreeMap::new();

for requirement in &requirements {
if let Some(path) = &requirement.path {
if path.ends_with("pyproject.toml") {
sources.entry(requirement.name.clone()).or_default().insert(
SourceAnnotation::PyProject {
sources
.entry(requirement.name.to_string())
.or_default()
.insert(SourceAnnotation::PyProject {
path: path.clone(),
project_name: project.as_ref().map(ToString::to_string),
},
);
});
} else {
sources
.entry(requirement.name.clone())
.entry(requirement.name.to_string())
.or_default()
.insert(SourceAnnotation::Requirement(path.clone()));
}
Expand All @@ -377,7 +378,7 @@ pub(crate) async fn pip_compile(
for requirement in &constraints {
if let Some(path) = &requirement.path {
sources
.entry(requirement.name.clone())
.entry(requirement.name.to_string())
.or_default()
.insert(SourceAnnotation::Constraint(path.clone()));
}
Expand All @@ -386,12 +387,32 @@ pub(crate) async fn pip_compile(
for requirement in &overrides {
if let Some(path) = &requirement.path {
sources
.entry(requirement.name.clone())
.entry(requirement.name.to_string())
.or_default()
.insert(SourceAnnotation::Override(path.clone()));
}
}

for editable in &editables {
let package_name = editable.url.given().unwrap_or_default().to_string();
if let Some(source) = &editable.source {
if source.ends_with("pyproject.toml") {
sources
.entry(package_name)
.or_default()
.insert(SourceAnnotation::PyProject {
path: source.clone(),
project_name: project.as_ref().map(ToString::to_string),
});
} else {
sources
.entry(package_name)
.or_default()
.insert(SourceAnnotation::Requirement(source.clone()));
}
}
}

// Collect constraints and overrides.
let constraints = Constraints::from_requirements(constraints);
let overrides = Overrides::from_requirements(overrides);
Expand All @@ -403,7 +424,12 @@ pub(crate) async fn pip_compile(
let start = std::time::Instant::now();

let editables = LocalEditables::from_editables(editables.into_iter().map(|editable| {
let EditableRequirement { url, extras, path } = editable;
let EditableRequirement {
url,
extras,
path,
source: _,
} = editable;
LocalEditable { url, path, extras }
}));

Expand Down
7 changes: 6 additions & 1 deletion crates/uv/src/commands/pip_install.rs
Original file line number Diff line number Diff line change
Expand Up @@ -575,7 +575,12 @@ async fn build_editables(
.with_reporter(DownloadReporter::from(printer).with_length(editables.len() as u64));

let editables = LocalEditables::from_editables(editables.iter().map(|editable| {
let EditableRequirement { url, extras, path } = editable;
let EditableRequirement {
url,
extras,
path,
source: _,
} = editable;
LocalEditable {
url: url.clone(),
extras: extras.clone(),
Expand Down
7 changes: 6 additions & 1 deletion crates/uv/src/commands/pip_sync.rs
Original file line number Diff line number Diff line change
Expand Up @@ -684,7 +684,12 @@ async fn resolve_editables(
.with_reporter(DownloadReporter::from(printer).with_length(uninstalled.len() as u64));

let editables = LocalEditables::from_editables(uninstalled.iter().map(|editable| {
let EditableRequirement { url, path, extras } = editable;
let EditableRequirement {
url,
path,
extras,
source: _,
} = editable;
LocalEditable {
url: url.clone(),
path: path.clone(),
Expand Down
15 changes: 15 additions & 0 deletions crates/uv/tests/pip_compile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3265,8 +3265,11 @@ fn compile_editable() -> Result<()> {
# This file was autogenerated by uv via the following command:
# uv pip compile --cache-dir [CACHE_DIR] --exclude-newer 2024-03-25T00:00:00Z [TEMP_DIR]/requirements.in
-e ${PROJECT_ROOT}/../../scripts/packages/maturin_editable
# via -r [TEMP_DIR]/requirements.in
-e ../../scripts/packages/poetry_editable
# via -r [TEMP_DIR]/requirements.in
-e file://../../scripts/packages/black_editable
# via -r [TEMP_DIR]/requirements.in
aiohttp==3.9.3
# via black
aiosignal==1.3.1
Expand Down Expand Up @@ -3325,6 +3328,7 @@ fn deduplicate_editable() -> Result<()> {
# This file was autogenerated by uv via the following command:
# uv pip compile --cache-dir [CACHE_DIR] --exclude-newer 2024-03-25T00:00:00Z [TEMP_DIR]/requirements.in
-e file://../../scripts/packages/black_editable
# via -r [TEMP_DIR]/requirements.in
aiohttp==3.9.3
# via black
aiosignal==1.3.1
Expand Down Expand Up @@ -3421,6 +3425,7 @@ fn compile_editable_url_requirement() -> Result<()> {
# This file was autogenerated by uv via the following command:
# uv pip compile --cache-dir [CACHE_DIR] --exclude-newer 2024-03-25T00:00:00Z [TEMP_DIR]/requirements.in
-e ../../scripts/packages/hatchling_editable
# via -r [TEMP_DIR]/requirements.in
iniconfig @ git+https://github.com/pytest-dev/iniconfig@9cae43103df70bac6fde7b9f35ad11a9f1be0cb4
# via hatchling-editable
Expand Down Expand Up @@ -3911,6 +3916,7 @@ fn generate_hashes_editable() -> Result<()> {
# This file was autogenerated by uv via the following command:
# uv pip compile --cache-dir [CACHE_DIR] --exclude-newer 2024-03-25T00:00:00Z [TEMP_DIR]/requirements.in --generate-hashes
-e ../../scripts/packages/poetry_editable
# via -r [TEMP_DIR]/requirements.in
anyio==4.3.0 \
--hash=sha256:048e05d0f6caeed70d731f3db756d35dcc1f35747c8c403364a8332c630441b8 \
--hash=sha256:f75253795a87df48568485fd18cdd2a3fa5c4f7c5be8e5e36637733fce06fed6
Expand Down Expand Up @@ -4128,6 +4134,7 @@ coverage = ["example[test]", "extras>=0.0.1,<=0.0.2"]
# 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
-e .
# via -r requirements.in
extras==0.0.1
# via example
iniconfig==2.0.0
Expand Down Expand Up @@ -5476,7 +5483,9 @@ dependencies = [
# 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 --no-deps
-e [TEMP_DIR]/editable1
# via -r requirements.in
-e [TEMP_DIR]/editable2
# via -r requirements.in
----- stderr -----
Built 2 editables in [TIME]
Expand All @@ -5503,6 +5512,7 @@ fn editable_invalid_extra() -> Result<()> {
# This file was autogenerated by uv via the following command:
# uv pip compile --cache-dir [CACHE_DIR] --exclude-newer 2024-03-25T00:00:00Z [TEMP_DIR]/requirements.in
-e ../../scripts/packages/black_editable
# via -r [TEMP_DIR]/requirements.in
----- stderr -----
Built 1 editable in [TIME]
Expand Down Expand Up @@ -6169,6 +6179,7 @@ fn editable_direct_dependency() -> Result<()> {
# This file was autogenerated by uv via the following command:
# uv pip compile --cache-dir [CACHE_DIR] --exclude-newer 2024-03-25T00:00:00Z [TEMP_DIR]/requirements.in --resolution lowest-direct
-e ../../scripts/packages/setuptools_editable
# via -r [TEMP_DIR]/requirements.in
iniconfig==0.1
# via setuptools-editable
Expand Down Expand Up @@ -6458,6 +6469,7 @@ dev = [
# 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
-e .
# via -r requirements.in
anyio @ https://files.pythonhosted.org/packages/bf/cd/d6d9bb1dadf73e7af02d18225cbd2c93f8552e13130484f1c8dcfece292b/anyio-4.2.0-py3-none-any.whl
# via example
idna==3.6
Expand Down Expand Up @@ -6508,6 +6520,7 @@ dev = ["setuptools"]
# 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 --resolution=lowest-direct
-e .
# via -r requirements.in
packaging==24.0
# via setuptools-scm
setuptools==69.2.0
Expand Down Expand Up @@ -6677,6 +6690,7 @@ fn compile_root_uri_editable() -> Result<()> {
# 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
-e ${ROOT_PATH}
# via -r requirements.in
black @ file://[WORKSPACE]/scripts/packages/root_editable/../black_editable
# via root-editable
Expand Down Expand Up @@ -9015,6 +9029,7 @@ fn tool_uv_sources() -> Result<()> {
# This file was autogenerated by uv via the following command:
# uv pip compile --cache-dir [CACHE_DIR] --exclude-newer 2024-03-25T00:00:00Z --preview some_dir/pyproject.toml --extra utils
-e ../poetry_editable
# via foo (some_dir/pyproject.toml)
anyio==4.3.0
# via poetry-editable
boltons @ git+https://github.com/mahmoud/boltons@57fbaa9b673ed85b32458b31baeeae230520e4a0
Expand Down

0 comments on commit eb3ea98

Please sign in to comment.