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