Skip to content

Bias towards local directories for bare editable requirements #3995

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 8 additions & 5 deletions crates/requirements-txt/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ use tracing::instrument;
use unscanny::{Pattern, Scanner};
use url::Url;

use crate::requirement::EditableError;
use distribution_types::{UnresolvedRequirement, UnresolvedRequirementSpecification};
use pep508_rs::{
expand_env_vars, split_scheme, strip_host, Pep508Error, RequirementOrigin, Scheme, VerbatimUrl,
Expand All @@ -57,11 +56,12 @@ use uv_configuration::{NoBinary, NoBuild, PackageNameSpecifier};
use uv_fs::{normalize_url_path, Simplified};
use uv_warnings::warn_user;

use crate::requirement::EditableError;
pub use crate::requirement::RequirementsTxtRequirement;

mod requirement;

/// We emit one of those for each requirements.txt entry
/// We emit one of those for each `requirements.txt` entry.
enum RequirementsTxtStatement {
/// `-r` inclusion filename
Requirements {
Expand Down Expand Up @@ -500,7 +500,8 @@ fn parse_entry(
Some(requirements_txt)
};

let (requirement, hashes) = parse_requirement_and_hashes(s, content, source, working_dir)?;
let (requirement, hashes) =
parse_requirement_and_hashes(s, content, source, working_dir, true)?;
let requirement =
requirement
.into_editable()
Expand Down Expand Up @@ -579,7 +580,8 @@ fn parse_entry(
Some(requirements_txt)
};

let (requirement, hashes) = parse_requirement_and_hashes(s, content, source, working_dir)?;
let (requirement, hashes) =
parse_requirement_and_hashes(s, content, source, working_dir, false)?;
RequirementsTxtStatement::RequirementEntry(RequirementEntry {
requirement,
hashes,
Expand Down Expand Up @@ -643,6 +645,7 @@ fn parse_requirement_and_hashes(
content: &str,
source: Option<&Path>,
working_dir: &Path,
editable: bool,
) -> Result<(RequirementsTxtRequirement, Vec<String>), RequirementsTxtParserError> {
// PEP 508 requirement
let start = s.cursor();
Expand Down Expand Up @@ -699,7 +702,7 @@ fn parse_requirement_and_hashes(
}
}

let requirement = RequirementsTxtRequirement::parse(requirement, working_dir)
let requirement = RequirementsTxtRequirement::parse(requirement, working_dir, editable)
.map(|requirement| {
if let Some(source) = source {
requirement.with_origin(RequirementOrigin::File(source.to_path_buf()))
Expand Down
14 changes: 13 additions & 1 deletion crates/requirements-txt/src/requirement.rs
Original file line number Diff line number Diff line change
Expand Up @@ -119,10 +119,22 @@ impl RequirementsTxtRequirement {
pub fn parse(
input: &str,
working_dir: impl AsRef<Path>,
editable: bool,
) -> Result<Self, Box<Pep508Error<VerbatimParsedUrl>>> {
// Attempt to parse as a PEP 508-compliant requirement.
match pep508_rs::Requirement::parse(input, &working_dir) {
Ok(requirement) => Ok(Self::Named(requirement)),
Ok(requirement) => {
// As a special-case, interpret `dagster` as `./dagster` if we're in editable mode.
if editable && requirement.version_or_url.is_none() {
Ok(Self::Unnamed(UnnamedRequirement::parse(
input,
&working_dir,
&mut TracingReporter,
)?))
} else {
Ok(Self::Named(requirement))
}
}
Err(err) => match err.message {
Pep508ErrorSource::UnsupportedRequirement(_) => {
// If that fails, attempt to parse as a direct URL requirement.
Expand Down
10 changes: 6 additions & 4 deletions crates/uv-requirements/src/specification.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,16 +86,18 @@ impl RequirementsSpecification {
) -> Result<Self> {
Ok(match source {
RequirementsSource::Package(name) => {
let requirement = RequirementsTxtRequirement::parse(name, std::env::current_dir()?)
.with_context(|| format!("Failed to parse: `{name}`"))?;
let requirement =
RequirementsTxtRequirement::parse(name, std::env::current_dir()?, false)
.with_context(|| format!("Failed to parse: `{name}`"))?;
Self {
requirements: vec![UnresolvedRequirementSpecification::from(requirement)],
..Self::default()
}
}
RequirementsSource::Editable(name) => {
let requirement = RequirementsTxtRequirement::parse(name, std::env::current_dir()?)
.with_context(|| format!("Failed to parse: `{name}`"))?;
let requirement =
RequirementsTxtRequirement::parse(name, std::env::current_dir()?, true)
.with_context(|| format!("Failed to parse: `{name}`"))?;
Self {
requirements: vec![UnresolvedRequirementSpecification::from(
requirement.into_editable()?,
Expand Down
42 changes: 35 additions & 7 deletions crates/uv/tests/pip_install.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1114,22 +1114,50 @@ fn install_editable_pep_508_cli() {
}

#[test]
fn invalid_editable_no_version() -> Result<()> {
fn install_editable_bare_cli() {
let context = TestContext::new("3.12");

let packages_dir = context.workspace_root.join("scripts/packages");

uv_snapshot!(context.filters(), context.install()
.arg("-e")
.arg("black_editable")
.current_dir(&packages_dir), @r###"
success: true
exit_code: 0
----- stdout -----

----- stderr -----
Resolved 1 package in [TIME]
Downloaded 1 package in [TIME]
Installed 1 package in [TIME]
+ black==0.1.0 (from file://[WORKSPACE]/scripts/packages/black_editable)
"###
);
}

#[test]
fn install_editable_bare_requirements_txt() -> Result<()> {
let context = TestContext::new("3.12");

let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.write_str("-e black")?;
requirements_txt.write_str("-e black_editable")?;

let packages_dir = context.workspace_root.join("scripts/packages");

uv_snapshot!(context.filters(), context.install()
.arg("-r")
.arg("requirements.txt"), @r###"
success: false
exit_code: 2
.arg(requirements_txt.path())
.current_dir(&packages_dir), @r###"
success: true
exit_code: 0
----- stdout -----

----- stderr -----
error: Unsupported editable requirement in `requirements.txt`
Caused by: Editable `black` must refer to a local directory
Resolved 1 package in [TIME]
Downloaded 1 package in [TIME]
Installed 1 package in [TIME]
+ black==0.1.0 (from file://[WORKSPACE]/scripts/packages/black_editable)
"###
);

Expand Down
Loading