From 559903f00ebd1a90948c8bb10d9ee7268aa52149 Mon Sep 17 00:00:00 2001 From: Tzu-ping Chung Date: Fri, 10 Aug 2018 13:03:34 +0800 Subject: [PATCH 1/2] Improve "run" behavior on Windows Some Windows users are used to launch files without specifying a command, e.g.:: > my-script.py This works in the shell because Windows will automatically choose an command based on file association, and with newer Python versions, the Py Launcher (py.exe) automatically chooses the correct Python based on shebang-parsing. A similar syntax, unfortunately, does not currently work in Pipenv:: > pipenv run my-script.py Since my-script.py will be treated as a real application by the subprocess module. This patch catch Windows error 193 during subprocess initialization, and fall back to use COMSPEC (shell=True) when it happens, to provide better support for this use case. --- news/2718.behavior | 1 + pipenv/core.py | 25 ++++++++++++++++++++----- 2 files changed, 21 insertions(+), 5 deletions(-) create mode 100644 news/2718.behavior diff --git a/news/2718.behavior b/news/2718.behavior new file mode 100644 index 00000000..c00b1d46 --- /dev/null +++ b/news/2718.behavior @@ -0,0 +1 @@ +Fallback to shell mode if `run` fails with Windows error 193 to handle non-executable commands. This should improve usability on Windows, where some users run non-executable files without specifying a command, relying on Windows file association to choose the current command. diff --git a/pipenv/core.py b/pipenv/core.py index db60ee94..9a74bd9a 100644 --- a/pipenv/core.py +++ b/pipenv/core.py @@ -2087,15 +2087,30 @@ def inline_activate_virtual_environment(): os.environ["VIRTUAL_ENV"] = root -def do_run_nt(script): +def _launch_windows_subprocess(script): import subprocess command = system_which(script.command) options = {"universal_newlines": True} - if command: # Try to use CreateProcess directly if possible. - p = subprocess.Popen([command] + script.args, **options) - else: # Command not found, maybe this is a shell built-in? - p = subprocess.Popen(script.cmdify(), shell=True, **options) + + # Command not found, maybe this is a shell built-in? + if not command: + return subprocess.Popen(script.cmdify(), shell=True, **options) + + # Try to use CreateProcess directly if possible. + try: + return subprocess.Popen([command] + script.args, **options) + except WindowsError as e: + if e.winerror != 193: + raise + + # Windows error 193 "Command is not a valid Win32 application". + # Try shell mode to use Windows's file association for file launch. + return subprocess.Popen(script.cmdify(), shell=True, **options) + + +def do_run_nt(script): + p = _launch_windows_subprocess(script) p.communicate() sys.exit(p.returncode) From 87ab5e8a9388bf0a5dfeba3d95810a51b07eb39c Mon Sep 17 00:00:00 2001 From: Tzu-ping Chung Date: Fri, 10 Aug 2018 13:15:33 +0800 Subject: [PATCH 2/2] Better comment --- pipenv/core.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pipenv/core.py b/pipenv/core.py index 9a74bd9a..86e22711 100644 --- a/pipenv/core.py +++ b/pipenv/core.py @@ -2097,14 +2097,15 @@ def _launch_windows_subprocess(script): if not command: return subprocess.Popen(script.cmdify(), shell=True, **options) - # Try to use CreateProcess directly if possible. + # Try to use CreateProcess directly if possible. Specifically catch + # Windows error 193 "Command is not a valid Win32 application" to handle + # a "command" that is non-executable. See pypa/pipenv#2727. try: return subprocess.Popen([command] + script.args, **options) except WindowsError as e: if e.winerror != 193: raise - # Windows error 193 "Command is not a valid Win32 application". # Try shell mode to use Windows's file association for file launch. return subprocess.Popen(script.cmdify(), shell=True, **options)