Skip to content
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

Use pep517 to load project metadata #1311

Merged
merged 8 commits into from
Mar 6, 2021
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
36 changes: 24 additions & 12 deletions piptools/scripts/compile.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,12 @@
import click
from click.utils import safecall
from pip._internal.commands import create_command
from pip._internal.req.constructors import install_req_from_line
from pip._internal.req.constructors import (
install_req_from_line,
install_req_from_req_string,
)
from pip._internal.utils.misc import redact_auth_from_url
from pip._vendor.pep517 import meta

from .._compat import parse_requirements
from ..cache import DependencyCache
Expand Down Expand Up @@ -330,21 +334,14 @@ def cli(
constraints = []
for src_file in src_files:
is_setup_file = os.path.basename(src_file) == "setup.py"
if is_setup_file or src_file == "-":
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
# to a temporary file and have pip read that. also used for
# reading requirements from install_requires in setup.py.
tmpfile = tempfile.NamedTemporaryFile(mode="wt", delete=False)
if is_setup_file:
from distutils.core import run_setup

dist = run_setup(src_file)
tmpfile.write("\n".join(dist.install_requires))
comes_from = f"{dist.get_name()} ({src_file})"
else:
tmpfile.write(sys.stdin.read())
comes_from = "-r -"
tmpfile.write(sys.stdin.read())
comes_from = "-r -"
tmpfile.flush()
reqs = list(
parse_requirements(
Expand All @@ -357,6 +354,15 @@ def cli(
for req in reqs:
req.comes_from = comes_from
constraints.extend(reqs)
elif is_setup_file:
dist = meta.load(os.path.dirname(os.path.abspath(src_file)))
comes_from = f"{dist.metadata.get_all('Name')[0]} ({src_file})"
constraints.extend(
[
install_req_from_req_string(req, comes_from=comes_from)
for req in dist.requires or []
]
)
else:
constraints.extend(
parse_requirements(
Expand All @@ -378,7 +384,13 @@ def cli(

# Filter out pip environment markers which do not match (PEP496)
constraints = [
req for req in constraints if req.markers is None or req.markers.evaluate()
req
for req in constraints
if req.markers is None
# We explicitly set extra=None to filter out optional requirements
# since evaluating an extra marker with no environment raises UndefinedEnvironmentName
# (see https://packaging.pypa.io/en/latest/markers.html#usage)
or req.markers.evaluate({"extra": None})
]

log.debug("Using indexes:")
Expand Down
69 changes: 57 additions & 12 deletions tests/test_cli_compile.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,17 @@ def test_command_line_overrides_pip_conf(pip_with_index_conf, runner):
assert "Using indexes:\n http://override.com" in out.stderr


def test_command_line_setuptools_read(pip_conf, runner):
@pytest.mark.network
def test_command_line_setuptools_read(runner, make_pip_conf):
make_pip_conf(
dedent(
"""\
[global]
disable-pip-version-check = True
"""
)
)

with open("setup.py", "w") as package:
package.write(
dedent(
Expand All @@ -51,16 +61,13 @@ def test_command_line_setuptools_read(pip_conf, runner):
"""
)
)
out = runner.invoke(cli, ["--no-emit-find-links"])
out = runner.invoke(
cli,
["--no-header", "--no-emit-find-links", "--find-links", MINIMAL_WHEELS_PATH],
)

assert out.stderr == dedent(
"""\
#
# This file is autogenerated by pip-compile
# To update, run:
#
# pip-compile --no-emit-find-links
#
small-fake-a==0.1
# via fake-setuptools-a (setup.py)
"""
Expand All @@ -70,6 +77,7 @@ def test_command_line_setuptools_read(pip_conf, runner):
assert os.path.exists("requirements.txt")


@pytest.mark.network
@pytest.mark.parametrize(
("options", "expected_output_file"),
(
Expand All @@ -85,12 +93,11 @@ def test_command_line_setuptools_read(pip_conf, runner):
(["setup.py", "--output-file", "output.txt"], "output.txt"),
),
)
def test_command_line_setuptools_output_file(
pip_conf, runner, options, expected_output_file
):
def test_command_line_setuptools_output_file(runner, options, expected_output_file):
"""
Test the output files for setup.py as a requirement file.
"""

with open("setup.py", "w") as package:
package.write(
dedent(
Expand All @@ -106,7 +113,8 @@ def test_command_line_setuptools_output_file(
assert os.path.exists(expected_output_file)


def test_command_line_setuptools_nested_output_file(pip_conf, tmpdir, runner):
@pytest.mark.network
def test_command_line_setuptools_nested_output_file(tmpdir, runner):
"""
Test the output file for setup.py in nested folder as a requirement file.
"""
Expand All @@ -127,6 +135,43 @@ def test_command_line_setuptools_nested_output_file(pip_conf, tmpdir, runner):
assert (proj_dir / "requirements.txt").exists()


@pytest.mark.network
def test_setuptools_preserves_environment_markers(
runner, make_package, make_wheel, make_pip_conf, tmpdir
):
make_pip_conf(
dedent(
"""\
[global]
disable-pip-version-check = True
"""
)
)

dists_dir = tmpdir / "dists"

foo_dir = make_package(name="foo", version="1.0")
make_wheel(foo_dir, dists_dir)

bar_dir = make_package(
name="bar", version="2.0", install_requires=['foo ; python_version >= "1"']
)
out = runner.invoke(
cli,
[
str(bar_dir / "setup.py"),
"--no-header",
"--no-annotate",
"--no-emit-find-links",
"--find-links",
str(dists_dir),
],
)

assert out.exit_code == 0, out.stderr
assert out.stderr == 'foo==1.0 ; python_version >= "1"\n'


def test_find_links_option(runner):
with open("requirements.in", "w") as req_in:
req_in.write("-f ./libs3")
Expand Down