Skip to content

Commit

Permalink
install: add --no-binary option
Browse files Browse the repository at this point in the history
  • Loading branch information
abn committed May 14, 2022
1 parent 7fcb643 commit feb11b1
Show file tree
Hide file tree
Showing 7 changed files with 140 additions and 9 deletions.
7 changes: 7 additions & 0 deletions docs/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,7 @@ option is used.
* `--default`: Only include the main dependencies. (**Deprecated**)
* `--sync`: Synchronize the environment with the locked packages and the specified groups.
* `--no-root`: Do not install the root package (your project).
* `--no-binary`: Do not use binary distributions for packages matching given policy. Use package name to disallow a specific package; or `:all:` to disallow and `:none:` to force binary for all packages.
* `--dry-run`: Output the operations but do not execute anything (implicitly enables --verbose).
* `--extras (-E)`: Features to install (multiple values allowed).
* `--no-dev`: Do not install dev dependencies. (**Deprecated**)
Expand All @@ -233,6 +234,12 @@ option is used.
When `--only` is specified, `--with` and `--without` options are ignored.
{{% /note %}}

{{% note %}}
The `--no-binary` option will only work with the new installer. For the old installer,
this is ignored.
{{% /note %}}


## update

In order to get the latest versions of the dependencies and to update the `poetry.lock` file,
Expand Down
21 changes: 21 additions & 0 deletions src/poetry/console/commands/install.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,16 @@ class InstallCommand(InstallerCommand):
option(
"no-root", None, "Do not install the root package (the current project)."
),
option(
"no-binary",
None,
"Do not use binary distributions for packages matching given policy.\n"
"Use package name to disallow a specific package; or <b>:all:</b> to\n"
"disallow and <b>:none:</b> to force binary for all packages. Multiple\n"
"packages can be specified separated by commas.",
flag=False,
multiple=True,
),
option(
"dry-run",
None,
Expand Down Expand Up @@ -98,6 +108,17 @@ def handle(self) -> int:

with_synchronization = True

if self.option("no-binary"):
policy = ",".join(self.option("no-binary", []))
try:
self._installer.no_binary(policy=policy)
except ValueError as e:
self.line_error(
f"<warning>Invalid value (<c1>{policy}</>) for"
f" `<b>--no-binary</b>`</>.\n\n<error>{e}</>"
)
return 1

self._installer.only_groups(self.activated_groups)
self._installer.dry_run(self.option("dry-run"))
self._installer.requires_synchronization(with_synchronization)
Expand Down
49 changes: 40 additions & 9 deletions src/poetry/installation/chooser.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

from packaging.tags import Tag

from poetry.utils.helpers import canonicalize_name
from poetry.utils.patterns import wheel_file_re


Expand Down Expand Up @@ -60,22 +61,52 @@ class Chooser:
def __init__(self, pool: Pool, env: Env) -> None:
self._pool = pool
self._env = env
self._no_binary_policy: set[str] = set()

def set_no_binary_policy(self, policy: str) -> None:
self._no_binary_policy = {
name.strip() if re.match(r":(all|none):", name) else canonicalize_name(name)
for name in policy.split(",")
}

if {":all:", ":none:"} <= self._no_binary_policy:
raise ValueError(
"Ambiguous binary policy containing :all: and :none: given."
)

def allow_binary(self, package_name: str) -> bool:
if ":all:" in self._no_binary_policy:
return False

return (
not self._no_binary_policy
or ":none:" in self._no_binary_policy
or canonicalize_name(package_name) not in self._no_binary_policy
)

def choose_for(self, package: Package) -> Link:
"""
Return the url of the selected archive for a given package.
"""
links = []
for link in self._get_links(package):
if link.is_wheel and not Wheel(link.filename).is_supported_by_environment(
self._env
):
logger.debug(
"Skipping wheel %s as this is not supported by the current"
" environment",
link.filename,
)
continue
if link.is_wheel:
if not self.allow_binary(package.name):
logger.debug(
"Skipping wheel for %s as requested in no binary policy for"
" package (%s)",
link.filename,
package.name,
)
continue

if not Wheel(link.filename).is_supported_by_environment(self._env):
logger.debug(
"Skipping wheel %s as this is not supported by the current"
" environment",
link.filename,
)
continue

if link.ext in {".egg", ".exe", ".msi", ".rpm", ".srpm"}:
logger.debug("Skipping unsupported distribution %s", link.filename)
Expand Down
3 changes: 3 additions & 0 deletions src/poetry/installation/executor.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,9 @@ def updates_count(self) -> int:
def removals_count(self) -> int:
return self._executed["uninstall"]

def set_no_binary_policy(self, policy: str) -> None:
self._chooser.set_no_binary_policy(policy)

def supports_fancy_output(self) -> bool:
return self._io.output.is_decorated() and not self._dry_run

Expand Down
5 changes: 5 additions & 0 deletions src/poetry/installation/installer.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,11 @@ def verbose(self, verbose: bool = True) -> Installer:
def is_verbose(self) -> bool:
return self._verbose

def no_binary(self, policy: str) -> Installer:
if self._executor:
self._executor.set_no_binary_policy(policy=policy)
return self

def only_groups(self, groups: Iterable[str]) -> Installer:
self._groups = groups

Expand Down
26 changes: 26 additions & 0 deletions tests/console/commands/test_install.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,3 +136,29 @@ def test_sync_option_is_passed_to_the_installer(
tester.execute("--sync")

assert tester.command.installer._requires_synchronization


@pytest.mark.parametrize(
("options", "policy"),
[
(
"--no-binary :all:",
{":all:"},
),
("--no-binary :none:", {":none:"}),
("--no-binary pytest", {"pytest"}),
("--no-binary pytest,black", {"black", "pytest"}),
("--no-binary pytest --no-binary black", {"black", "pytest"}),
],
)
def test_no_binary_option_is_passed_to_the_installer(
tester: CommandTester, mocker: MockerFixture, options: str, policy: set[str]
) -> None:
"""
The --no-binary option is passed properly to the installer.
"""
mocker.patch.object(tester.command.installer, "run", return_value=1)

tester.execute(options)

assert tester.command.installer.executor._chooser._no_binary_policy == policy
38 changes: 38 additions & 0 deletions tests/installation/test_chooser.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,44 @@ def test_chooser_chooses_universal_wheel_link_if_available(
assert link.filename == "pytest-3.5.0-py2.py3-none-any.whl"


@pytest.mark.parametrize(
("policy", "filename"),
[
(":all:", "pytest-3.5.0.tar.gz"),
(":none:", "pytest-3.5.0-py2.py3-none-any.whl"),
("black", "pytest-3.5.0-py2.py3-none-any.whl"),
("pytest", "pytest-3.5.0.tar.gz"),
("pytest,black", "pytest-3.5.0.tar.gz"),
],
)
@pytest.mark.parametrize("source_type", ["", "legacy"])
def test_chooser_no_binary_policy(
env: MockEnv,
mock_pypi: None,
mock_legacy: None,
source_type: str,
pool: Pool,
policy: str,
filename: str,
):
chooser = Chooser(pool, env)
chooser.set_no_binary_policy(policy)

package = Package("pytest", "3.5.0")
if source_type == "legacy":
package = Package(
package.name,
package.version.text,
source_type="legacy",
source_reference="foo",
source_url="https://foo.bar/simple/",
)

link = chooser.choose_for(package)

assert link.filename == filename


@pytest.mark.parametrize("source_type", ["", "legacy"])
def test_chooser_chooses_specific_python_universal_wheel_link_if_available(
env: MockEnv, mock_pypi: None, mock_legacy: None, source_type: str, pool: Pool
Expand Down

0 comments on commit feb11b1

Please sign in to comment.