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

Handle properly pip --no-binary / --only-binary options in requirements.txt format files. #2834

Merged
merged 3 commits into from
Jan 6, 2023
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/changelog/2814.bugfix.rst
@@ -0,0 +1 @@
Handle properly pip ``--no-binary`` / ``--only-binary`` options in requirements.txt format files.
4 changes: 2 additions & 2 deletions src/tox/tox_env/python/pip/req/args.py
Expand Up @@ -33,8 +33,8 @@ def _global_options(parser: ArgumentParser) -> None:
parser.add_argument("-r", "--requirement", action=AddUniqueAction, dest="requirements")
parser.add_argument("-e", "--editable", action=AddUniqueAction, dest="editables")
parser.add_argument("-f", "--find-links", action=AddUniqueAction)
parser.add_argument("--no-binary", choices=[":all:", ":none:"]) # TODO: colon separated package names
parser.add_argument("--only-binary", choices=[":all:", ":none:"]) # TODO: colon separated package names
parser.add_argument("--no-binary")
parser.add_argument("--only-binary")
parser.add_argument("--prefer-binary", action="store_true", default=False)
parser.add_argument("--require-hashes", action="store_true", default=False)
parser.add_argument("--pre", action="store_true", default=False)
Expand Down
13 changes: 10 additions & 3 deletions src/tox/tox_env/python/pip/req/file.py
Expand Up @@ -15,7 +15,7 @@
from packaging.requirements import InvalidRequirement, Requirement

from .args import build_parser
from .util import VCS, get_url_scheme, is_url, url_to_path
from .util import VCS, get_url_scheme, handle_binary_option, is_url, url_to_path

# Matches environment variable-style values in '${MY_VARIABLE_1}' with the variable name consisting of only uppercase
# letters, digits or the '_' (underscore). This follows the POSIX standard defined in IEEE Std 1003.1, 2013 Edition.
Expand Down Expand Up @@ -341,10 +341,17 @@ def _merge_option_line(self, base_opt: Namespace, opt: Namespace, filename: str)
base_opt.trusted_hosts = []
if host not in base_opt.trusted_hosts:
base_opt.trusted_hosts.append(host)

no_binary = base_opt.no_binary if hasattr(base_opt, "no_binary") else set()
only_binary = base_opt.only_binary if hasattr(base_opt, "only_binary") else set()
if opt.no_binary:
base_opt.no_binary = opt.no_binary
handle_binary_option(opt.no_binary, no_binary, only_binary)
if opt.only_binary:
base_opt.only_binary = opt.only_binary
handle_binary_option(opt.only_binary, only_binary, no_binary)
if no_binary:
base_opt.no_binary = no_binary
if only_binary:
base_opt.only_binary = only_binary

@staticmethod
def _break_args_options(line: str) -> tuple[str, str]:
Expand Down
20 changes: 20 additions & 0 deletions src/tox/tox_env/python/pip/req/util.py
Expand Up @@ -4,6 +4,8 @@
from urllib.parse import urlsplit
from urllib.request import url2pathname

from packaging.utils import canonicalize_name

VCS = ["ftp", "ssh", "git", "hg", "bzr", "sftp", "svn"]
VALID_SCHEMAS = ["http", "https", "file"] + VCS

Expand All @@ -26,3 +28,21 @@ def url_to_path(url: str) -> str:
raise ValueError(f"non-local file URIs are not supported on this platform: {url!r}")
path = url2pathname(netloc + path)
return path


def handle_binary_option(value: str, target: set[str], other: set[str]) -> None:
new = value.split(",")
while ":all:" in new:
other.clear()
target.clear()
target.add(":all:")
del new[: new.index(":all:") + 1]
if ":none:" not in new:
return
for name in new:
if name == ":none:":
target.clear()
continue
name = canonicalize_name(name)
other.discard(name)
target.add(name)
39 changes: 33 additions & 6 deletions tests/tox_env/python/pip/req/test_file.py
Expand Up @@ -140,16 +140,43 @@
["--use-feature", "2020-resolver", "--use-feature", "fast-deps"],
id="use-feature multiple duplicate different line",
),
pytest.param("--no-binary :all:", {"no_binary": ":all:"}, [], ["--no-binary", ":all:"], id="no-binary all"),
pytest.param("--no-binary :none:", {"no_binary": ":none:"}, [], ["--no-binary", ":none:"], id="no-binary none"),
pytest.param("--only-binary :all:", {"only_binary": ":all:"}, [], ["--only-binary", ":all:"], id="only-binary all"),
pytest.param("--no-binary :all:", {"no_binary": {":all:"}}, [], ["--no-binary", {":all:"}], id="no-binary all"),
pytest.param("--no-binary :none:", {"no_binary": {":none:"}}, [], [], id="no-binary none"),
pytest.param(
"--only-binary :all:",
{"only_binary": {":all:"}},
[],
["--only-binary", {":all:"}],
id="only-binary all",
),
pytest.param(
"--only-binary :none:",
{"only_binary": ":none:"},
{"only_binary": {":none:"}},
[],
[],
["--only-binary", ":none:"],
id="only-binary none",
),
pytest.param(
"--no-binary=foo --only-binary=foo",
{"only_binary": {"foo"}},
[],
["--only-binary", {"foo"}],
id="no-binary-and-only-binary",
),
pytest.param(
"--no-binary=foo --no-binary=:none:",
{},
[],
[],
id="no-binary-none-last",
),
pytest.param(
"--only-binary=:none: --no-binary=foo",
{"no_binary": {"foo"}},
[],
["--no-binary", {"foo"}],
id="no-binary-none-first",
),
pytest.param("####### example-requirements.txt #######", {}, [], [], id="comment"),
pytest.param("\t##### Requirements without Version Specifiers ######", {}, [], [], id="tab and comment"),
pytest.param(" # start", {}, [], [], id="space and comment"),
Expand Down Expand Up @@ -289,7 +316,7 @@ def test_req_file(tmp_path: Path, req: str, opts: dict[str, Any], requirements:
req_file = RequirementsFile(requirements_txt, constraint=False)
assert req_file.as_root_args == as_args
assert str(req_file) == f"-r {requirements_txt}"
assert vars(req_file.options) == opts
assert vars(req_file.options) == (opts if {":none:"} not in opts.values() else {})
found = [str(i) for i in req_file.requirements]
assert found == requirements

Expand Down