From 30c90febf10981e2d5c2048f27976f9d85dd6e84 Mon Sep 17 00:00:00 2001 From: Paul O'Leary McCann Date: Wed, 28 Sep 2022 19:26:27 +0900 Subject: [PATCH] Attempt to handle command rewriting on Windows This handles command rewriting on Windows to ensure the same Python executable is re-used by using the `executable` argument to subprocess.run. This has the advantage that it avoids any need to escape the path of sys.executable. Still needs testing. --- spacy/cli/project/run.py | 24 ++++++++++++++++++++---- spacy/util.py | 4 ++++ 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/spacy/cli/project/run.py b/spacy/cli/project/run.py index e197662c9..e1f0e3701 100644 --- a/spacy/cli/project/run.py +++ b/spacy/cli/project/run.py @@ -167,12 +167,28 @@ def run_commands( """ for c in commands: command: Union[str, List[str]] + exe: Optional[str] = None if is_windows: - # On Windows we don't rewrite the command because there's no - # reliable way to split and reassemble it command = c + + # Correct Windows splitting is hard, so this only checks for simple + # cases. It will work in ordinary cases, but will miss cases where + # the command is like: + # "C:\My Programs\python.exe" script.py + head, _, tail = c.partition(" ") + + # This doesn't rewrite the command to include sys.executable + # because sys.executable might include spaces, quotes, or + # something, and need quoting itself. Instead the exe param is used + # to directly specify the binary to call. + if head in ("python", "python3"): + exe = sys.executable + if head in ("pip", "pip3"): + exe = sys.executable + command = "python -m pip " + tail + if not silent: - print(f"Running command: {c}") + print(f"Running command: {command}") else: command = shlex.split(c, posix=True) # Not sure if this is needed or a good idea. Motivation: users may often @@ -190,7 +206,7 @@ def run_commands( print(f"Running command: {c}") if not dry: - run_command(command, capture=capture) + run_command(command, capture=capture, exe=exe) def validate_subcommand( diff --git a/spacy/util.py b/spacy/util.py index 14b600aa1..34d470e79 100644 --- a/spacy/util.py +++ b/spacy/util.py @@ -941,6 +941,7 @@ def run_command( *, stdin: Optional[Any] = None, capture: bool = False, + exe: str = None, ) -> subprocess.CompletedProcess: """Run a command on the command line as a subprocess. If the subprocess returns a non-zero exit code, a system exit is performed. @@ -952,6 +953,8 @@ def run_command( sys.exit will be called with the return code. You should use capture=False when you want to turn over execution to the command, and capture=True when you want to run the command more like a function. + exe (Optional[str]): Path to Python executable to use. Passed separately + from the command on Windows to avoid quoting issues. RETURNS (Optional[CompletedProcess]): The process object. """ @@ -990,6 +993,7 @@ def run_command( input=stdin, encoding="utf8", check=False, + executable=exe, stdout=subprocess.PIPE if capture else None, stderr=subprocess.STDOUT if capture else None, )