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

Huwper/fix windows run #1

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
40 changes: 21 additions & 19 deletions poetry/utils/env.py
Original file line number Diff line number Diff line change
Expand Up @@ -1123,26 +1123,28 @@ def _bin(self, bin): # type: (str) -> str
"""
Return path to the given executable.
"""
bin_path = self._bin_dir / bin

# Special case for Windows needing to find executable files
# first by extension (and not the extension-less file).
if self._is_windows:
for suffix in os.environ.get("PATHEXT", ".exe").split(os.pathsep):
win_bin_path = bin_path.with_suffix(suffix)
if win_bin_path.exists():
return str(win_bin_path)
# On Windows, some executables can be in the base path
# This is especially true when installing Python with
# the official installer, where python.exe will be at
# the root of the env path.
# This is an edge case and should not be encountered
# in normal uses but this happens in the sonnet script
# that creates a fake virtual environment pointing to
# a base Python install.
win_bin_path = (self._path / bin).with_suffix(suffix)
if win_bin_path.exists():
return str(win_bin_path)
# On Windows, some executables can be in the base path
# This is especially true when installing Python with
# the official installer, where python.exe will be at
# the root of the env path.
# This is an edge case and should not be encountered
# in normal uses but this happens in the sonnet script
# that creates a fake virtual environment pointing to
# a base Python install.
# On windows we also need to manually search the %PATH%
# as the OS won't do it for us.
# Note: shutil.which always searches the current
# directory first on windows, This is the behavior
# windows users will expect.
search_path = os.pathsep.join(
(str(self._bin_dir), str(self._path), os.environ["PATH"])
)
found = shutil.which(bin, path=search_path)
if found:
return found

bin_path = self._bin_dir / bin

if bin_path.exists():
return str(bin_path)
Expand Down
63 changes: 59 additions & 4 deletions tests/console/commands/test_run.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import os
import tempfile

import pytest

from poetry.utils._compat import WINDOWS
from poetry.utils._compat import Path


@pytest.fixture
Expand All @@ -21,7 +25,7 @@ def test_run_passes_all_args(tester, env):
@pytest.mark.skipif(
not WINDOWS, reason="This test asserts Windows-specific compatibility",
)
def test_run_console_scripts_on_windows(tmp_venv, command_tester_factory):
def test_run_console_scripts_on_windows(tmp_venv, command_tester_factory, mocker):
"""Test that `poetry run` on Windows finds console scripts.

On Windows, Poetry installs console scripts of editable
Expand All @@ -47,7 +51,58 @@ def test_run_console_scripts_on_windows(tmp_venv, command_tester_factory):
common use case).

"""
new_environ = {}
new_environ.update(os.environ)
new_environ["PATHEXT"] = ".BAT;.CMD" # ensure environ vars are deterministic
mocker.patch("os.environ", new_environ)

tester = command_tester_factory("run", environment=tmp_venv)
script = tmp_venv._bin_dir / "console_script.cmd"
script.write_text("exit 123")
assert tester.execute("console_script") == 123
bat_script = tmp_venv._bin_dir / "console_script.bat"
cmd_script = tmp_venv._bin_dir / "console_script.cmd"

cmd_script.write_text("exit 15")
bat_script.write_text("exit 30")
assert tester.execute("console_script") == 30
assert tester.execute("console_script.bat") == 30
assert tester.execute("console_script.cmd") == 15


@pytest.mark.skipif(
not WINDOWS, reason="This test asserts Windows-specific compatibility",
)
def test_script_external_to_env(tmp_venv, command_tester_factory, mocker):
"""
If a script exists on the path outside poetry, or in the current directory,
poetry run should still work
"""
new_environ = {}
new_environ.update(os.environ)

tester = command_tester_factory("run", environment=tmp_venv)

# create directory and add it to the PATH
with tempfile.TemporaryDirectory() as tmp_dir_name:
# add script to current directory
script_in_cur_dir = tempfile.NamedTemporaryFile(
"w", dir=".", suffix=".CMD", delete=False
)
script_in_cur_dir.write("exit 30")
script_in_cur_dir.close()

try:
# add script to the new directory
script = Path(tmp_dir_name) / "console_script.cmd"
script.write_text("exit 15")

new_environ[
"PATHEXT"
] = ".BAT;.CMD" # ensure environ vars are deterministic
new_environ["PATH"] = os.environ["PATH"] + os.pathsep + tmp_dir_name
mocker.patch("os.environ", new_environ)

# poetry run will find it as it searched the path
assert tester.execute("console_script") == 15
assert tester.execute(script_in_cur_dir.name) == 30

finally:
os.unlink(script_in_cur_dir.name)