Skip to content

Commit

Permalink
Fix incorrect sys.argv[0] path when calling project scripts (#6737)
Browse files Browse the repository at this point in the history
Co-authored-by: Christopher Dignam <[email protected]>
Co-authored-by: Randy Döring <[email protected]>
  • Loading branch information
3 people authored Jan 23, 2023
1 parent e1504ed commit 33242c2
Show file tree
Hide file tree
Showing 4 changed files with 81 additions and 1 deletion.
21 changes: 20 additions & 1 deletion src/poetry/console/commands/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from cleo.helpers import argument

from poetry.console.commands.env_command import EnvCommand
from poetry.utils._compat import WINDOWS


if TYPE_CHECKING:
Expand Down Expand Up @@ -44,7 +45,25 @@ def _module(self) -> Module:

return module

def run_script(self, script: str | dict[str, str], args: str) -> int:
def run_script(self, script: str | dict[str, str], args: list[str]) -> int:
"""Runs an entry point script defined in the section ``[tool.poetry.scripts]``.
When a script exists in the venv bin folder, i.e. after ``poetry install``,
then ``sys.argv[0]`` must be set to the full path of the executable, so
``poetry run foo`` and ``poetry shell``, ``foo`` have the same ``sys.argv[0]``
that points to the full path.
Otherwise (when an entry point script does not exist), ``sys.argv[0]`` is the
script name only, i.e. ``poetry run foo`` has ``sys.argv == ['foo']``.
"""
for script_dir in self.env.script_dirs:
script_path = script_dir / args[0]
if WINDOWS:
script_path = script_path.with_suffix(".cmd")
if script_path.exists():
args = [str(script_path), *args[1:]]
break

if isinstance(script, dict):
script = script["callable"]

Expand Down
37 changes: 37 additions & 0 deletions tests/console/commands/test_run.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,3 +146,40 @@ def test_run_script_exit_code(
)
assert tester.execute("exit-code") == 42
assert tester.execute("return-code") == 42


@pytest.mark.parametrize(
"installed_script", [False, True], ids=["not installed", "installed"]
)
def test_run_script_sys_argv0(
installed_script: bool,
poetry_with_scripts: Poetry,
command_tester_factory: CommandTesterFactory,
tmp_venv: VirtualEnv,
mocker: MockerFixture,
) -> None:
"""
If RunCommand calls an installed script defined in pyproject.toml,
sys.argv[0] must be set to the full path of the script.
"""
mocker.patch("poetry.utils.env.EnvManager.get", return_value=tmp_venv)
mocker.patch(
"os.execvpe",
lambda file, args, env: subprocess.call([file] + args[1:], env=env),
)

install_tester = command_tester_factory(
"install",
poetry=poetry_with_scripts,
environment=tmp_venv,
)
assert install_tester.execute() == 0
if not installed_script:
for path in tmp_venv.script_dirs[0].glob("check-argv0*"):
path.unlink()

tester = command_tester_factory(
"run", poetry=poetry_with_scripts, environment=tmp_venv
)
argv1 = "absolute" if installed_script else "relative"
assert tester.execute(f"check-argv0 {argv1}") == 0
1 change: 1 addition & 0 deletions tests/fixtures/scripts/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ readme = "README.md"
python = "^3.7"

[tool.poetry.scripts]
check-argv0 = "scripts.check_argv0:main"
exit-code = "scripts.exit_code:main"
return-code = "scripts.return_code:main"

Expand Down
23 changes: 23 additions & 0 deletions tests/fixtures/scripts/scripts/check_argv0.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
from __future__ import annotations

import sys

from pathlib import Path


def main() -> int:
path = Path(sys.argv[0])
if sys.argv[1] == "absolute":
if not path.is_absolute():
raise RuntimeError(f"sys.argv[0] is not an absolute path: {path}")
if not path.exists():
raise RuntimeError(f"sys.argv[0] does not exist: {path}")
else:
if path.is_absolute():
raise RuntimeError(f"sys.argv[0] is an absolute path: {path}")

return 0


if __name__ == "__main__":
raise sys.exit(main())

0 comments on commit 33242c2

Please sign in to comment.