| """ |
| exec_command |
| |
| Implements exec_command function that is (almost) equivalent to |
| commands.getstatusoutput function but on NT, DOS systems the |
| returned status is actually correct (though, the returned status |
| values may be different by a factor). In addition, exec_command |
| takes keyword arguments for (re-)defining environment variables. |
| |
| Provides functions: |
| |
| exec_command --- execute command in a specified directory and |
| in the modified environment. |
| find_executable --- locate a command using info from environment |
| variable PATH. Equivalent to posix `which` |
| command. |
| |
| Author: Pearu Peterson <pearu@cens.ioc.ee> |
| Created: 11 January 2003 |
| |
| Requires: Python 2.x |
| |
| Successfully tested on: |
| |
| ======== ============ ================================================= |
| os.name sys.platform comments |
| ======== ============ ================================================= |
| posix linux2 Debian (sid) Linux, Python 2.1.3+, 2.2.3+, 2.3.3 |
| PyCrust 0.9.3, Idle 1.0.2 |
| posix linux2 Red Hat 9 Linux, Python 2.1.3, 2.2.2, 2.3.2 |
| posix sunos5 SunOS 5.9, Python 2.2, 2.3.2 |
| posix darwin Darwin 7.2.0, Python 2.3 |
| nt win32 Windows Me |
| Python 2.3(EE), Idle 1.0, PyCrust 0.7.2 |
| Python 2.1.1 Idle 0.8 |
| nt win32 Windows 98, Python 2.1.1. Idle 0.8 |
| nt win32 Cygwin 98-4.10, Python 2.1.1(MSC) - echo tests |
| fail i.e. redefining environment variables may |
| not work. FIXED: don't use cygwin echo! |
| Comment: also `cmd /c echo` will not work |
| but redefining environment variables do work. |
| posix cygwin Cygwin 98-4.10, Python 2.3.3(cygming special) |
| nt win32 Windows XP, Python 2.3.3 |
| ======== ============ ================================================= |
| |
| Known bugs: |
| |
| * Tests, that send messages to stderr, fail when executed from MSYS prompt |
| because the messages are lost at some point. |
| |
| """ |
| __all__ = ['exec_command', 'find_executable'] |
| |
| import os |
| import sys |
| import subprocess |
| import locale |
| import warnings |
| |
| from numpy.distutils.misc_util import is_sequence, make_temp_file |
| from numpy.distutils import log |
| |
| def filepath_from_subprocess_output(output): |
| """ |
| Convert `bytes` in the encoding used by a subprocess into a filesystem-appropriate `str`. |
| |
| Inherited from `exec_command`, and possibly incorrect. |
| """ |
| mylocale = locale.getpreferredencoding(False) |
| if mylocale is None: |
| mylocale = 'ascii' |
| output = output.decode(mylocale, errors='replace') |
| output = output.replace('\r\n', '\n') |
| # Another historical oddity |
| if output[-1:] == '\n': |
| output = output[:-1] |
| return output |
| |
| |
| def forward_bytes_to_stdout(val): |
| """ |
| Forward bytes from a subprocess call to the console, without attempting to |
| decode them. |
| |
| The assumption is that the subprocess call already returned bytes in |
| a suitable encoding. |
| """ |
| if hasattr(sys.stdout, 'buffer'): |
| # use the underlying binary output if there is one |
| sys.stdout.buffer.write(val) |
| elif hasattr(sys.stdout, 'encoding'): |
| # round-trip the encoding if necessary |
| sys.stdout.write(val.decode(sys.stdout.encoding)) |
| else: |
| # make a best-guess at the encoding |
| sys.stdout.write(val.decode('utf8', errors='replace')) |
| |
| |
| def temp_file_name(): |
| # 2019-01-30, 1.17 |
| warnings.warn('temp_file_name is deprecated since NumPy v1.17, use ' |
| 'tempfile.mkstemp instead', DeprecationWarning, stacklevel=1) |
| fo, name = make_temp_file() |
| fo.close() |
| return name |
| |
| def get_pythonexe(): |
| pythonexe = sys.executable |
| if os.name in ['nt', 'dos']: |
| fdir, fn = os.path.split(pythonexe) |
| fn = fn.upper().replace('PYTHONW', 'PYTHON') |
| pythonexe = os.path.join(fdir, fn) |
| assert os.path.isfile(pythonexe), '%r is not a file' % (pythonexe,) |
| return pythonexe |
| |
| def find_executable(exe, path=None, _cache={}): |
| """Return full path of a executable or None. |
| |
| Symbolic links are not followed. |
| """ |
| key = exe, path |
| try: |
| return _cache[key] |
| except KeyError: |
| pass |
| log.debug('find_executable(%r)' % exe) |
| orig_exe = exe |
| |
| if path is None: |
| path = os.environ.get('PATH', os.defpath) |
| if os.name=='posix': |
| realpath = os.path.realpath |
| else: |
| realpath = lambda a:a |
| |
| if exe.startswith('"'): |
| exe = exe[1:-1] |
| |
| suffixes = [''] |
| if os.name in ['nt', 'dos', 'os2']: |
| fn, ext = os.path.splitext(exe) |
| extra_suffixes = ['.exe', '.com', '.bat'] |
| if ext.lower() not in extra_suffixes: |
| suffixes = extra_suffixes |
| |
| if os.path.isabs(exe): |
| paths = [''] |
| else: |
| paths = [ os.path.abspath(p) for p in path.split(os.pathsep) ] |
| |
| for path in paths: |
| fn = os.path.join(path, exe) |
| for s in suffixes: |
| f_ext = fn+s |
| if not os.path.islink(f_ext): |
| f_ext = realpath(f_ext) |
| if os.path.isfile(f_ext) and os.access(f_ext, os.X_OK): |
| log.info('Found executable %s' % f_ext) |
| _cache[key] = f_ext |
| return f_ext |
| |
| log.warn('Could not locate executable %s' % orig_exe) |
| return None |
| |
| ############################################################ |
| |
| def _preserve_environment( names ): |
| log.debug('_preserve_environment(%r)' % (names)) |
| env = {name: os.environ.get(name) for name in names} |
| return env |
| |
| def _update_environment( **env ): |
| log.debug('_update_environment(...)') |
| for name, value in env.items(): |
| os.environ[name] = value or '' |
| |
| def exec_command(command, execute_in='', use_shell=None, use_tee=None, |
| _with_python = 1, **env ): |
| """ |
| Return (status,output) of executed command. |
| |
| .. deprecated:: 1.17 |
| Use subprocess.Popen instead |
| |
| Parameters |
| ---------- |
| command : str |
| A concatenated string of executable and arguments. |
| execute_in : str |
| Before running command ``cd execute_in`` and after ``cd -``. |
| use_shell : {bool, None}, optional |
| If True, execute ``sh -c command``. Default None (True) |
| use_tee : {bool, None}, optional |
| If True use tee. Default None (True) |
| |
| |
| Returns |
| ------- |
| res : str |
| Both stdout and stderr messages. |
| |
| Notes |
| ----- |
| On NT, DOS systems the returned status is correct for external commands. |
| Wild cards will not work for non-posix systems or when use_shell=0. |
| |
| """ |
| # 2019-01-30, 1.17 |
| warnings.warn('exec_command is deprecated since NumPy v1.17, use ' |
| 'subprocess.Popen instead', DeprecationWarning, stacklevel=1) |
| log.debug('exec_command(%r,%s)' % (command, |
| ','.join(['%s=%r'%kv for kv in env.items()]))) |
| |
| if use_tee is None: |
| use_tee = os.name=='posix' |
| if use_shell is None: |
| use_shell = os.name=='posix' |
| execute_in = os.path.abspath(execute_in) |
| oldcwd = os.path.abspath(os.getcwd()) |
| |
| if __name__[-12:] == 'exec_command': |
| exec_dir = os.path.dirname(os.path.abspath(__file__)) |
| elif os.path.isfile('exec_command.py'): |
| exec_dir = os.path.abspath('.') |
| else: |
| exec_dir = os.path.abspath(sys.argv[0]) |
| if os.path.isfile(exec_dir): |
| exec_dir = os.path.dirname(exec_dir) |
| |
| if oldcwd!=execute_in: |
| os.chdir(execute_in) |
| log.debug('New cwd: %s' % execute_in) |
| else: |
| log.debug('Retaining cwd: %s' % oldcwd) |
| |
| oldenv = _preserve_environment( list(env.keys()) ) |
| _update_environment( **env ) |
| |
| try: |
| st = _exec_command(command, |
| use_shell=use_shell, |
| use_tee=use_tee, |
| **env) |
| finally: |
| if oldcwd!=execute_in: |
| os.chdir(oldcwd) |
| log.debug('Restored cwd to %s' % oldcwd) |
| _update_environment(**oldenv) |
| |
| return st |
| |
| |
| def _exec_command(command, use_shell=None, use_tee = None, **env): |
| """ |
| Internal workhorse for exec_command(). |
| """ |
| if use_shell is None: |
| use_shell = os.name=='posix' |
| if use_tee is None: |
| use_tee = os.name=='posix' |
| |
| if os.name == 'posix' and use_shell: |
| # On POSIX, subprocess always uses /bin/sh, override |
| sh = os.environ.get('SHELL', '/bin/sh') |
| if is_sequence(command): |
| command = [sh, '-c', ' '.join(command)] |
| else: |
| command = [sh, '-c', command] |
| use_shell = False |
| |
| elif os.name == 'nt' and is_sequence(command): |
| # On Windows, join the string for CreateProcess() ourselves as |
| # subprocess does it a bit differently |
| command = ' '.join(_quote_arg(arg) for arg in command) |
| |
| # Inherit environment by default |
| env = env or None |
| try: |
| # text is set to False so that communicate() |
| # will return bytes. We need to decode the output ourselves |
| # so that Python will not raise a UnicodeDecodeError when |
| # it encounters an invalid character; rather, we simply replace it |
| proc = subprocess.Popen(command, shell=use_shell, env=env, text=False, |
| stdout=subprocess.PIPE, |
| stderr=subprocess.STDOUT) |
| except OSError: |
| # Return 127, as os.spawn*() and /bin/sh do |
| return 127, '' |
| |
| text, err = proc.communicate() |
| mylocale = locale.getpreferredencoding(False) |
| if mylocale is None: |
| mylocale = 'ascii' |
| text = text.decode(mylocale, errors='replace') |
| text = text.replace('\r\n', '\n') |
| # Another historical oddity |
| if text[-1:] == '\n': |
| text = text[:-1] |
| |
| if use_tee and text: |
| print(text) |
| return proc.returncode, text |
| |
| |
| def _quote_arg(arg): |
| """ |
| Quote the argument for safe use in a shell command line. |
| """ |
| # If there is a quote in the string, assume relevants parts of the |
| # string are already quoted (e.g. '-I"C:\\Program Files\\..."') |
| if '"' not in arg and ' ' in arg: |
| return '"%s"' % arg |
| return arg |
| |
| ############################################################ |