diff options
author | Eric Wieser <wieser.eric@gmail.com> | 2019-02-23 21:15:54 -0800 |
---|---|---|
committer | Eric Wieser <wieser.eric@gmail.com> | 2019-02-23 22:35:10 -0800 |
commit | 77aee9c3069851e53a772d5b1f24392932d0801f (patch) | |
tree | 9f2f2ded6c26d5e63bcb1c8b66db5911cf18c451 /numpy/distutils/_shell_utils.py | |
parent | 490b8542c8d042f712cfe4671ebfc5a2afe8f8ae (diff) | |
download | numpy-77aee9c3069851e53a772d5b1f24392932d0801f.tar.gz |
MAINT: Add functions to parse shell-strings in the platform-native way
There are places in distutils where we accept a single string from the user, and interpret it as a set of command line arguments.
Previously, these were passed on as a string unmodified to exec_command, and interpreted by subprocess in a platform-specific way.
Recent changes to distutils now pass a list of arguments to subprocess, meaning we have to split the strings ourselves.
While `shlex.split` is perfect on posix systems, it is not a good approximation of either the old or the expected behavior on windows.
Provides the building blocks needed to fix gh-12979
Diffstat (limited to 'numpy/distutils/_shell_utils.py')
-rw-r--r-- | numpy/distutils/_shell_utils.py | 91 |
1 files changed, 91 insertions, 0 deletions
diff --git a/numpy/distutils/_shell_utils.py b/numpy/distutils/_shell_utils.py new file mode 100644 index 000000000..5ef874900 --- /dev/null +++ b/numpy/distutils/_shell_utils.py @@ -0,0 +1,91 @@ +""" +Helper functions for interacting with the shell, and consuming shell-style +parameters provided in config files. +""" +import os +import shlex +import subprocess +try: + from shlex import quote +except ImportError: + from pipes import quote + +__all__ = ['WindowsParser', 'PosixParser', 'NativeParser'] + + +class CommandLineParser: + """ + An object that knows how to split and join command-line arguments. + + It must be true that ``argv == split(join(argv))`` for all ``argv``. + The reverse neednt be true - `join(split(cmd))` may result in the addition + or removal of unnecessary escaping. + """ + @staticmethod + def join(argv): + """ Join a list of arguments into a command line string """ + raise NotImplemented + + @staticmethod + def split(cmd): + """ Split a command line string into a list of arguments """ + raise NotImplemented + + +class WindowsParser: + """ + The parsing behavior used by `subprocess.call("string")` on Windows, which + matches the Microsoft C/C++ runtime. + + Note that this is _not_ the behavior of cmd. + """ + @staticmethod + def join(argv): + # note that list2cmdline is specific to the windows syntax + return subprocess.list2cmdline(argv) + + @staticmethod + def split(cmd): + import ctypes # guarded import for systems without ctypes + try: + ctypes.windll + except AttributeError: + raise NotImplementedError + + # Windows has special parsing rules for the executable (no quotes), + # that we do not care about - insert a dummy element + if not cmd: + return [] + cmd = 'dummy ' + cmd + + CommandLineToArgvW = ctypes.windll.shell32.CommandLineToArgvW + CommandLineToArgvW.restype = ctypes.POINTER(ctypes.c_wchar_p) + CommandLineToArgvW.argtypes = (ctypes.c_wchar_p, ctypes.POINTER(ctypes.c_int)) + + nargs = ctypes.c_int() + lpargs = CommandLineToArgvW(cmd, ctypes.byref(nargs)) + args = [lpargs[i] for i in range(nargs.value)] + assert not ctypes.windll.kernel32.LocalFree(lpargs) + + # strip the element we inserted + assert args[0] == "dummy" + return args[1:] + + +class PosixParser: + """ + The parsing behavior used by `subprocess.call("string", shell=True)` on Posix. + """ + @staticmethod + def join(argv): + return ' '.join(quote(arg) for arg in argv) + + @staticmethod + def split(cmd): + return shlex.split(cmd, posix=True) + + +if os.name == 'nt': + NativeParser = WindowsParser +elif os.name == 'posix': + NativeParser = PosixParser |