Skip to content

Commit 4fbc6ff

Browse files
authored
dotenv run: Use PATH on Windows to find the command to run (#742)
1 parent 555d78f commit 4fbc6ff

2 files changed

Lines changed: 73 additions & 3 deletions

File tree

‎src/dotenv/cli.py‎

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import json
22
import os
33
import shlex
4+
import shutil
45
import sys
56
from contextlib import contextmanager
67
from typing import IO, Any, Dict, Iterator, List, Optional
@@ -235,11 +236,18 @@ def run_command(command: List[str], env: Dict[str, str]) -> None:
235236
cmd_env.update(env)
236237

237238
if sys.platform == "win32":
238-
# execvpe on Windows returns control immediately
239-
# rather than once the command has finished.
239+
# Resolve the command as suggested in Python's docs
240+
# (https://docs.python.org/3/library/subprocess.html#popen-constructor).
241+
executable = shutil.which(command[0], path=cmd_env.get("PATH"))
240242
try:
243+
# `execvpe` on Windows returns control immediately.
241244
p = Popen(
242-
command, universal_newlines=True, bufsize=0, shell=False, env=cmd_env
245+
command,
246+
executable=executable,
247+
universal_newlines=True,
248+
bufsize=0,
249+
shell=False,
250+
env=cmd_env,
243251
)
244252
except FileNotFoundError:
245253
print(f"Command not found: {command[0]}", file=sys.stderr)

‎tests/test_cli.py‎

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -344,6 +344,68 @@ def test_file_option_expands_user(tmp_path, args, expected_stdout, expected_cont
344344
assert sorted(tmp_path.iterdir()) == [home]
345345

346346

347+
def make_command(directory: Path, name: str, output: str) -> None:
348+
"""
349+
Create a command named `name` in `directory` which prints `output`.
350+
"""
351+
352+
if sys.platform == "win32":
353+
(directory / f"{name}.cmd").write_text(f"@echo {output}\n")
354+
else:
355+
path = directory / name
356+
path.write_text(f"#!/bin/sh\necho {output}\n")
357+
path.chmod(0o755)
358+
359+
360+
def test_run_with_path_from_env_file(tmp_path):
361+
"""
362+
Check that the command is looked up in the `PATH` set by the .env file.
363+
"""
364+
365+
# We use two commands (one prints "x", the other "y").
366+
dotenv_bin_dir = tmp_path / "dotenv-bin"
367+
dotenv_bin_dir.mkdir()
368+
make_command(directory=dotenv_bin_dir, name="dotenv-test-command", output="x")
369+
start_bin_dir = tmp_path / "start-bin"
370+
start_bin_dir.mkdir()
371+
make_command(directory=start_bin_dir, name="dotenv-test-command", output="y")
372+
start_env = {
373+
**os.environ,
374+
"PATH": os.pathsep.join([str(start_bin_dir), os.environ["PATH"]]),
375+
}
376+
# Unquoted, so that a Windows `PATH` ending with `\` isn't read as an escape.
377+
(tmp_path / ".env").write_text(f"PATH={dotenv_bin_dir}{os.pathsep}${{PATH}}")
378+
379+
result = run_dotenv(["run", "dotenv-test-command"], cwd=tmp_path, env=start_env)
380+
381+
check_process(result, exit_code=0, stdout="x\n")
382+
383+
384+
@pytest.mark.skipif(sys.platform != "win32", reason="Windows-specific lookup")
385+
def test_run_python_uses_path_on_windows(dotenv_path, tmp_path):
386+
"""
387+
Check that `dotenv run python` runs the first `python` in `PATH`, as with an
388+
activated virtual environment (#373).
389+
390+
A fake `python` placed first in `PATH` prints "x", while the real interpreter
391+
would run the `-c` code and print "y" instead. Without resolving the command
392+
against `PATH`, Windows picks the real interpreter.
393+
"""
394+
395+
bin_dir = tmp_path / "bin"
396+
bin_dir.mkdir()
397+
make_command(directory=bin_dir, name="python", output="x") # Creates `python.cmd`.
398+
env = {**os.environ, "PATH": os.pathsep.join([str(bin_dir), os.environ["PATH"]])}
399+
400+
result = run_dotenv(
401+
["--file", str(dotenv_path), "run", "python", "-c", "print('y')"],
402+
cwd=tmp_path,
403+
env=env,
404+
)
405+
406+
check_process(result, exit_code=0, stdout="x\n")
407+
408+
347409
def test_run_without_cmd(tmp_path):
348410
result = run_dotenv(["run"], cwd=tmp_path)
349411

0 commit comments

Comments
 (0)