diff --git a/crates/puffin-cli/src/requirements.rs b/crates/puffin-cli/src/requirements.rs index 20cd16dc2..bf02be8d4 100644 --- a/crates/puffin-cli/src/requirements.rs +++ b/crates/puffin-cli/src/requirements.rs @@ -82,13 +82,16 @@ impl RequirementsSpecification { // Include any optional dependencies specified in `extras` project.optional_dependencies.into_iter().flat_map( |optional_dependencies| { - extras.iter().flat_map(move |extra| { - optional_dependencies - .get(extra.as_ref()) - .cloned() - // undefined extra requests are ignored silently - .unwrap_or_default() - }) + optional_dependencies + .iter() + .flat_map(|(name, requirements)| { + if extras.contains(&ExtraName::normalize(name)) { + requirements.clone() + } else { + vec![] + } + }) + .collect::>() }, ), ) diff --git a/crates/puffin-cli/tests/pip_compile.rs b/crates/puffin-cli/tests/pip_compile.rs index b02235d94..3e27f206c 100644 --- a/crates/puffin-cli/tests/pip_compile.rs +++ b/crates/puffin-cli/tests/pip_compile.rs @@ -280,3 +280,56 @@ optional-dependencies.foo = [ Ok(()) } + +/// Resolve a package from an extra with unnormalized names in a `pyproject.toml` file. +#[test] +fn compile_pyproject_toml_extra_name_normalization() -> Result<()> { + let temp_dir = assert_fs::TempDir::new()?; + let cache_dir = assert_fs::TempDir::new()?; + let venv = temp_dir.child(".venv"); + + Command::new(get_cargo_bin(BIN_NAME)) + .arg("venv") + .arg(venv.as_os_str()) + .arg("--cache-dir") + .arg(cache_dir.path()) + .current_dir(&temp_dir) + .assert() + .success(); + venv.assert(predicates::path::is_dir()); + + let pyproject_toml = temp_dir.child("pyproject.toml"); + pyproject_toml.touch()?; + pyproject_toml.write_str( + r#"[build-system] +requires = ["setuptools", "wheel"] + +[project] +name = "project" +dependencies = [] +optional-dependencies."FrIeNdLy-._.-bArD" = [ + "django==5.0b1", +] +"#, + )?; + + insta::with_settings!({ + filters => vec![ + (r"\d+(ms|s)", "[TIME]"), + (r"# .* pip-compile", "# [BIN_PATH] pip-compile"), + (r"--cache-dir .*", "--cache-dir [CACHE_DIR]"), + ] + }, { + assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME)) + .arg("pip-compile") + .arg("pyproject.toml") + .arg("--extra") + .arg("FRiENDlY-...-_-BARd") + .arg("--cache-dir") + .arg(cache_dir.path()) + .env("VIRTUAL_ENV", venv.as_os_str()) + .current_dir(&temp_dir)); + }); + + Ok(()) +} diff --git a/crates/puffin-cli/tests/snapshots/pip_compile__compile_pyproject_toml_extra_name_normalization.snap b/crates/puffin-cli/tests/snapshots/pip_compile__compile_pyproject_toml_extra_name_normalization.snap new file mode 100644 index 000000000..5eac4f49f --- /dev/null +++ b/crates/puffin-cli/tests/snapshots/pip_compile__compile_pyproject_toml_extra_name_normalization.snap @@ -0,0 +1,28 @@ +--- +source: crates/puffin-cli/tests/pip_compile.rs +info: + program: puffin + args: + - pip-compile + - pyproject.toml + - "--extra" + - FRiENDlY-...-_-BARd + - "--cache-dir" + - /var/folders/bc/qlsk3t6x7c9fhhbvvcg68k9c0000gp/T/.tmpFyLXQn + env: + VIRTUAL_ENV: /var/folders/bc/qlsk3t6x7c9fhhbvvcg68k9c0000gp/T/.tmpxfZatv/.venv +--- +success: true +exit_code: 0 +----- stdout ----- +# This file was autogenerated by Puffin v0.0.1 via the following command: +# [BIN_PATH] pip-compile pyproject.toml --extra FRiENDlY-...-_-BARd --cache-dir [CACHE_DIR] +asgiref==3.7.2 + # via django +django==5.0b1 +sqlparse==0.4.4 + # via django + +----- stderr ----- +Resolved 3 packages in [TIME] + diff --git a/crates/puffin-package/src/extra_name.rs b/crates/puffin-package/src/extra_name.rs index 920996127..3c3a38510 100644 --- a/crates/puffin-package/src/extra_name.rs +++ b/crates/puffin-package/src/extra_name.rs @@ -22,6 +22,9 @@ impl Display for ExtraName { static NAME_NORMALIZE: Lazy = Lazy::new(|| Regex::new(r"[-_.]+").unwrap()); impl ExtraName { + /// Collapses any run of the characters `-`, `_` and `.` down to a single `-`. + /// Ex) "---", ".", and "__" all get converted to just "." + /// /// See: /// pub fn normalize(name: impl AsRef) -> Self {