diff --git a/piptools/scripts/compile.py b/piptools/scripts/compile.py index 195faa75..313b981f 100755 --- a/piptools/scripts/compile.py +++ b/piptools/scripts/compile.py @@ -332,13 +332,6 @@ def cli( setup_file_found = False for src_file in src_files: is_setup_file = os.path.basename(src_file) in METADATA_FILENAMES - if not is_setup_file and build_deps_targets: - msg = ( - "--build-deps-for and --all-build-deps can be used only with the " - "setup.py, setup.cfg and pyproject.toml specs." - ) - raise click.BadParameter(msg) - if src_file == "-": # pip requires filenames and not files. Since we want to support # piping from stdin, we need to briefly save the input from stdin @@ -430,6 +423,13 @@ def cli( msg = "--extra has effect only with setup.py and PEP-517 input formats" raise click.BadParameter(msg) + if build_deps_targets and not setup_file_found: + msg = ( + "--build-deps-for and --all-build-deps can be used only with the " + "setup.py, setup.cfg and pyproject.toml specs." + ) + raise click.BadParameter(msg) + primary_packages = { key_from_ireq(ireq) for ireq in constraints if not ireq.constraint } diff --git a/tests/test_cli_compile.py b/tests/test_cli_compile.py index a098e045..a92e28f3 100644 --- a/tests/test_cli_compile.py +++ b/tests/test_cli_compile.py @@ -2964,6 +2964,104 @@ def test_build_deps_fail_without_setup_file(runner, tmpdir, option): assert exp in out.stderr +@backtracking_resolver_only +@pytest.mark.parametrize("option", ("--all-build-deps", "--build-deps-for=wheel")) +def test_build_deps_does_not_error_when_setup_included_with_multiple_source_files( + fake_dists_with_build_deps, + runner, + tmp_path, + monkeypatch, + current_resolver, + option, +): + """ + Test that passing ``--build-deps-for`` or ``--all-build-deps`` does not emit an + error as long as at least one file in the sources list is a setup file. + """ + src_pkg_path = pathlib.Path(PACKAGES_PATH) / "small_fake_with_build_deps" + # When used as argument to the runner it is not passed to pip + monkeypatch.setenv("PIP_FIND_LINKS", fake_dists_with_build_deps) + + requirements_path = pathlib.Path(tmp_path) / "requirements.in" + requirements_path.write_text("\n") + + pyproject_toml_path = pathlib.Path(tmp_path) / "pyproject.toml" + pyproject_toml_path.write_text("\n") + + with runner.isolated_filesystem(tmp_path) as tmp_pkg_path: + shutil.copytree(src_pkg_path, tmp_pkg_path, dirs_exist_ok=True) + out = runner.invoke( + cli, + [ + "--allow-unsafe", + "--output-file", + "-", + "--quiet", + "--no-emit-options", + "--no-header", + option, + os.fspath(requirements_path), + os.fspath(pyproject_toml_path), + ], + ) + + exp = ( + "--build-deps-for and --all-build-deps can be used only with the " + "setup.py, setup.cfg and pyproject.toml specs." + ) + assert exp not in out.stderr + assert out.exit_code == 0 + + +@backtracking_resolver_only +@pytest.mark.parametrize("option", ("--all-build-deps", "--build-deps-for=wheel")) +def test_build_deps_does_not_error_when_multiple_setup_included_in_source_files( + fake_dists_with_build_deps, + runner, + tmp_path, + monkeypatch, + current_resolver, + option, +): + """ + Test that passing ``--build-deps-for`` or ``--all-build-deps`` does not emit an + error as long when multiple setup files are included in the source files list. + """ + src_pkg_path = pathlib.Path(PACKAGES_PATH) / "small_fake_with_build_deps" + # When used as argument to the runner it is not passed to pip + monkeypatch.setenv("PIP_FIND_LINKS", fake_dists_with_build_deps) + + pyproject_toml_path = pathlib.Path(tmp_path) / "pyproject.toml" + pyproject_toml_path.write_text("\n") + + pyproject_toml_2_path = pathlib.Path(tmp_path) / "pyproject.toml" + pyproject_toml_2_path.write_text("\n") + + with runner.isolated_filesystem(tmp_path) as tmp_pkg_path: + shutil.copytree(src_pkg_path, tmp_pkg_path, dirs_exist_ok=True) + out = runner.invoke( + cli, + [ + "--allow-unsafe", + "--output-file", + "-", + "--quiet", + "--no-emit-options", + "--no-header", + option, + os.fspath(pyproject_toml_path), + os.fspath(pyproject_toml_2_path), + ], + ) + + exp = ( + "--build-deps-for and --all-build-deps can be used only with the " + "setup.py, setup.cfg and pyproject.toml specs." + ) + assert exp not in out.stderr + assert out.exit_code == 0 + + def test_extras_fail_with_requirements_in(runner, tmpdir): """ Test that passing ``--extra`` with ``requirements.in`` input file fails.