Skip to content

Commit

Permalink
Accept setup.py and setup.cfg files in compile
Browse files Browse the repository at this point in the history
  • Loading branch information
charliermarsh committed Mar 23, 2024
1 parent 9da1182 commit f285623
Show file tree
Hide file tree
Showing 4 changed files with 149 additions and 9 deletions.
23 changes: 21 additions & 2 deletions crates/uv-requirements/src/sources.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ pub enum RequirementsSource {
RequirementsTxt(PathBuf),
/// Dependencies were provided via a `pyproject.toml` file (e.g., `pip-compile pyproject.toml`).
PyprojectToml(PathBuf),
/// Dependencies were provided via a `setup.py` file (e.g., `pip-compile setup.py`).
SetupPy(PathBuf),
/// Dependencies were provided via a `setup.cfg` file (e.g., `pip-compile setup.cfg`).
SetupCfg(PathBuf),
}

impl RequirementsSource {
Expand All @@ -26,6 +30,10 @@ impl RequirementsSource {
pub fn from_requirements_file(path: PathBuf) -> Self {
if path.ends_with("pyproject.toml") {
Self::PyprojectToml(path)
} else if path.ends_with("setup.py") {
Self::SetupPy(path)
} else if path.ends_with("setup.cfg") {
Self::SetupCfg(path)
} else {
Self::RequirementsTxt(path)
}
Expand Down Expand Up @@ -74,16 +82,27 @@ impl RequirementsSource {

Self::Package(name)
}

/// Returns `true` if the source allows extras to be specified.
pub fn allows_extras(&self) -> bool {
matches!(
self,
Self::PyprojectToml(_) | Self::SetupPy(_) | Self::SetupCfg(_)
)
}
}

impl std::fmt::Display for RequirementsSource {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Package(package) => write!(f, "{package}"),
Self::Editable(path) => write!(f, "-e {path}"),
Self::RequirementsTxt(path) | Self::PyprojectToml(path) => {
Self::RequirementsTxt(path)
| Self::PyprojectToml(path)
| Self::SetupPy(path)
| Self::SetupCfg(path) => {
write!(f, "{}", path.simplified_display())
}
Self::Package(package) => write!(f, "{package}"),
}
}
}
Expand Down
22 changes: 22 additions & 0 deletions crates/uv-requirements/src/specification.rs
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,28 @@ impl RequirementsSpecification {
}
}
}
RequirementsSource::SetupPy(path) | RequirementsSource::SetupCfg(path) => {
let path = fs_err::canonicalize(path)?;
let source_tree = path.parent().ok_or_else(|| {
anyhow::anyhow!(
"The file `{}` appears to be a `setup.py` or `setup.cfg` file, which must be in a directory",
path.user_display()
)
})?;
Self {
project: None,
requirements: vec![],
constraints: vec![],
overrides: vec![],
editables: vec![],
source_trees: vec![source_tree.to_path_buf()],
extras: FxHashSet::default(),
index_url: None,
extra_index_urls: vec![],
no_index: false,
find_links: vec![],
}
}
})
}

Expand Down
11 changes: 4 additions & 7 deletions crates/uv/src/commands/pip_compile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,14 +80,11 @@ pub(crate) async fn pip_compile(
) -> Result<ExitStatus> {
let start = std::time::Instant::now();

// If the user requests `extras` but does not provide a pyproject toml source
if !matches!(extras, ExtrasSpecification::None)
&& !requirements
.iter()
.any(|source| matches!(source, RequirementsSource::PyprojectToml(_)))
{
// If the user requests `extras` but does not provide a valid source (e.g., a `pyproject.toml`),
// return an error.
if !extras.is_empty() && !requirements.iter().any(RequirementsSource::allows_extras) {
return Err(anyhow!(
"Requesting extras requires a pyproject.toml input file."
"Requesting extras requires a `pyproject.toml`, `setup.cfg`, or `setup.py` file."
));
}

Expand Down
102 changes: 102 additions & 0 deletions crates/uv/tests/pip_compile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -510,6 +510,108 @@ setup(
Ok(())
}

/// Compile a `setup.cfg` file.
#[test]
fn compile_setup_cfg() -> Result<()> {
let context = TestContext::new("3.12");

let setup_cfg = context.temp_dir.child("setup.cfg");
setup_cfg.write_str(
r#"[options]
packages = find:
install_requires=
anyio
[options.extras_require]
dev =
iniconfig; python_version >= "3.7"
mypy; python_version <= "3.8"
"#,
)?;

let setup_py = context.temp_dir.child("setup.py");
setup_py.write_str(
r#"# setup.py
from setuptools import setup
setup(
name="dummypkg",
description="A dummy package",
)
"#,
)?;

uv_snapshot!(context.compile()
.arg("setup.cfg")
.arg("--extra")
.arg("dev"), @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 2023-11-18T12:00:00Z setup.cfg --extra dev
anyio==4.0.0
idna==3.4
# via anyio
iniconfig==2.0.0
sniffio==1.3.0
# via anyio
----- stderr -----
Resolved 4 packages in [TIME]
"###
);

Ok(())
}

/// Compile a `setup.py` file.
#[test]
fn compile_setup_py() -> Result<()> {
let context = TestContext::new("3.12");

let setup_py = context.temp_dir.child("setup.py");
setup_py.write_str(
r#"# setup.py
from setuptools import setup
setup(
name="dummypkg",
description="A dummy package",
install_requires=["anyio"],
extras_require={
"dev": ["iniconfig; python_version >= '3.7'", "mypy; python_version <= '3.8'"],
},
)
"#,
)?;

uv_snapshot!(context.compile()
.arg("setup.py")
.arg("--extra")
.arg("dev"), @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 2023-11-18T12:00:00Z setup.py --extra dev
anyio==4.0.0
idna==3.4
# via anyio
iniconfig==2.0.0
sniffio==1.3.0
# via anyio
----- stderr -----
Resolved 4 packages in [TIME]
"###
);

Ok(())
}

/// Request multiple extras that do not exist as a dependency group in a `pyproject.toml` file.
#[test]
fn compile_pyproject_toml_extras_missing() -> Result<()> {
Expand Down

0 comments on commit f285623

Please sign in to comment.