Source code for pymyinstall.installhelper.run_cmd

# -*- coding: utf-8 -*-
"""
Implements function :func:`run_cmd <pymyinstall.installhelper.install_cmd_helper.run_cmd>`.

.. versionadded:: 1.1


:githublink:`%|py|8`
"""
from __future__ import print_function
import sys
import os
import time
import subprocess
import threading
import warnings
import shlex
if sys.version_info[0] == 2:
    import Queue as queue
else:
    import queue


[docs]class RunCmdException(Exception): """ raised by function :func:`run_cmd <pymyinstall.installhelper.install_cmd_helper.run_cmd>` :githublink:`%|py|25` """ pass
[docs]def get_interpreter_path(): """ return the interpreter path :githublink:`%|py|32` """ if sys.platform.startswith("win"): return sys.executable.replace("pythonw.exe", "python.exe") else: return sys.executable
[docs]def split_cmp_command(cmd, remove_quotes=True): """ splits a command line :param cmd: command line :param remove_quotes: True by default :return: list :githublink:`%|py|45` """ if isinstance(cmd, str # unicode# ): return shlex.split(cmd) else: return cmd
[docs]def decode_outerr(outerr, encoding, encerror, msg): """ decode the output or the error after running a command line instructions :param outerr: output or error :param encoding: encoding (if None, it is replaced by ascii) :param encerror: how to handle errors :param msg: to add to the exception message :return: converted string .. versionchanged:: 1.4 If *encoding* is None, it is replaced by ``'ascii'``. :githublink:`%|py|65` """ if encoding is None: encoding = "ascii" typstr = str # unicode# if not isinstance(outerr, bytes): raise TypeError( "only able to decode bytes, not " + typstr(type(outerr))) try: out = outerr.decode(encoding, errors=encerror) return out except UnicodeDecodeError as exu: try: out = outerr.decode( "utf8" if encoding != "utf8" else "latin-1", errors=encerror) return out except Exception as e: out = outerr.decode(encoding, errors='ignore') raise Exception("issue with cmd (" + encoding + "):" + typstr(msg) + "\n" + typstr(exu) + "\n-----\n" + out) from e raise Exception("complete issue with cmd:" + typstr(msg))
[docs]def skip_run_cmd(cmd, sin="", shell=True, wait=False, log_error=True, stop_running_if=None, encerror="ignore", encoding="utf8", change_path=None, communicate=True, preprocess=True, timeout=None, catch_exit=False, fLOG=None, timeout_listen=None, tell_if_no_output=None): """ has the same signature as :func:`run_cmd <pymyinstall.installhelper.install_cmd_helper.run_cmd>` but does nothing .. versionadded:: 1.0 :githublink:`%|py|96` """ return "", ""
[docs]def run_cmd_private(cmd, sin="", shell=True, wait=False, log_error=True, stop_running_if=None, encerror="ignore", encoding="utf8", change_path=None, communicate=True, preprocess=True, timeout=None, catch_exit=False, fLOG=None, tell_if_no_output=None): """ run a command line and wait for the result :param cmd: command line :param sin: sin: what must be written on the standard input :param shell: if True, cmd is a shell command (and no command window is opened) :param wait: call ``proc.wait`` :param log_error: if log_error, call fLOG (error) :param stop_running_if: the function stops waiting if some condition is fulfilled. The function received the last line from the logs. Signature: ``stop_waiting_if(last_out, last_err) -> bool``. The function must return True to stop waiting. This function can also be used to intercept the standard output and the standard error while running. :param encerror: encoding errors (ignore by default) while converting the output into a string :param encoding: encoding of the output :param change_path: change the current path if not None (put it back after the execution) :param communicate: use method `communicate <https://docs.python.org/3/library/subprocess.html#subprocess.Popen.communicate>`_ which is supposed to be safer, parameter ``wait`` must be True :param preprocess: preprocess the command line if necessary (not available on Windows) (False to disable that option) :param timeout: when data is sent to stdin (``sin``), a timeout is needed to avoid waiting for ever (*timeout* is in seconds) :param catch_exit: catch *SystemExit* exception :param fLOG: logging function (if not None, bypass others parameters) :param tell_if_no_output: tells if there is no output every *tell_if_no_output* seconds :return: content of stdout, stdres (only if wait is True) .. exref:: :title: Run a program using the command line :: from pyquickhelper.loghelper import run_cmd out,err = run_cmd( "python setup.py install", wait=True) If you are using this function to run git function, parameter ``shell`` must be True. .. versionadded:: 1.1 :githublink:`%|py|142` """ if fLOG is not None: fLOG("execute", cmd) if sys.platform.startswith("win"): cmdl = cmd else: cmdl = split_cmp_command(cmd) if preprocess else cmd if catch_exit: try: pproc = subprocess.Popen(cmdl, shell=shell, stdin=subprocess.PIPE if ( sin and len(sin) > 0) else None, stdout=subprocess.PIPE if wait else None, stderr=subprocess.PIPE if wait else None, cwd=change_path) except SystemExit as e: raise RunCmdException("SystemExit raised (1)") from e else: pproc = subprocess.Popen(cmdl, shell=shell, stdin=subprocess.PIPE if ( sin and len(sin) > 0) else None, stdout=subprocess.PIPE if wait else None, stderr=subprocess.PIPE if wait else None, cwd=change_path) if isinstance(cmd, list): cmd = " ".join(cmd) if wait: skip_out_err = False out = [] err = [] err_read = False skip_waiting = False if communicate: # communicate is True if tell_if_no_output is not None: raise NotImplementedError( "tell_if_no_output is not implemented when communicate is True") if stop_running_if is not None: raise NotImplementedError( "stop_running_if is not implemented when communicate is True") input = None if (sin is None or len(sin) > 0) else sin.encode() if input is not None and len(input) > 0: if fLOG is not None: fLOG("input", [input]) if catch_exit: try: if sys.version_info[0] == 2: if timeout is not None: raise NotImplementedError( "timeout is only available with Python 3") stdoutdata, stderrdata = pproc.communicate(input=input) else: stdoutdata, stderrdata = pproc.communicate( input=input, timeout=timeout) except SystemExit as e: raise RunCmdException("SystemExit raised (2)") from e else: if sys.version_info[0] == 2: if timeout is not None: warnings.warn( "[run_cmd_private] timeout is only available with Python 3") stdoutdata, stderrdata = pproc.communicate(input=input) else: stdoutdata, stderrdata = pproc.communicate( input=input, timeout=timeout) out = decode_outerr(stdoutdata, encoding, encerror, cmd) err = decode_outerr(stderrdata, encoding, encerror, cmd) else: # communicate is False: use of threads if sin is not None and len(sin) > 0: raise Exception( "communicate should be True to send something on stdin") stdout, stderr = pproc.stdout, pproc.stderr begin = time.perf_counter() last_update = begin # with threads (stdoutReader, stdoutQueue) = _AsyncLineReader.getForFd( stdout, catch_exit=catch_exit) (stderrReader, stderrQueue) = _AsyncLineReader.getForFd( stderr, catch_exit=catch_exit) runloop = True while (not stdoutReader.eof() or not stderrReader.eof()) and runloop: while not stdoutQueue.empty(): line = stdoutQueue.get() decol = decode_outerr( line, encoding, encerror, cmd) if fLOG is not None: fLOG(decol.strip("\n\r")) out.append(decol.strip("\n\r")) last_update = time.perf_counter() if stop_running_if is not None and stop_running_if(decol, None): runloop = False break while not stderrQueue.empty(): line = stderrQueue.get() decol = decode_outerr( line, encoding, encerror, cmd) if fLOG is not None: fLOG(decol.strip("\n\r")) err.append(decol.strip("\n\r")) last_update = time.perf_counter() if stop_running_if is not None and stop_running_if(None, decol): runloop = False break time.sleep(0.05) delta = time.perf_counter() - last_update if tell_if_no_output is not None and delta >= tell_if_no_output: if fLOG is not None: fLOG("[run_cmd] No update in {0} seconds for cmd: {1}".format( "%5.1f" % (last_update - begin), cmd)) last_update = time.perf_counter() full_delta = time.perf_counter() - begin if timeout is not None and full_delta > timeout: runloop = False if fLOG is not None: fLOG("[run_cmd] Timeout after {0} seconds for cmd: {1}".format( "%5.1f" % full_delta, cmd)) break if runloop: # Waiting for async readers to finish... stdoutReader.join() stderrReader.join() # Waiting for process to exit... returnCode = pproc.wait() err_read = True if returnCode != 0: try: # we try to close the ressources stdout.close() stderr.close() except Exception as e: warnings.warn("Unable to close stdout and sterr.") if catch_exit: raise RunCmdException("SystemExit raised with error code {0}\nOUT:\n{1}\nERR-0:\n{2}".format( returnCode, "\n".join(out), "\n".join(err))) raise subprocess.CalledProcessError(returnCode, cmd) if not skip_waiting: pproc.wait() else: out.append("[run_cmd] killing process.") if fLOG is not None: fLOG( "[run_cmd] killing process because stop_running_if returned True.") pproc.kill() err_read = True if fLOG is not None: fLOG("[run_cmd] process killed.") skip_out_err = True out = "\n".join(out) if skip_out_err: err = "Process killed." else: if err_read: err = "\n".join(err) else: temp = err = stderr.read() try: err = decode_outerr(temp, encoding, encerror, cmd) except Exception: err = decode_outerr(temp, encoding, "ignore", cmd) stdout.close() stderr.close() # same path for whether communicate is False or True err = err.replace("\r\n", "\n") if fLOG is not None: fLOG("end of execution", cmd) if len(err) > 0 and log_error and fLOG is not None: fLOG("error (log)\n%s" % err) if sys.platform.startswith("win"): return out.replace("\r\n", "\n"), err.replace("\r\n", "\n") else: return out, err else: return "", ""
[docs]class _AsyncLineReader(threading.Thread):
[docs] def __init__(self, fd, outputQueue, catch_exit): threading.Thread.__init__(self) assert isinstance(outputQueue, queue.Queue) assert callable(fd.readline) self.fd = fd self.catch_exit = catch_exit self.outputQueue = outputQueue
[docs] def run(self): if self.catch_exit: try: for _ in map(self.outputQueue.put, iter(self.fd.readline, b'')): pass except SystemExit as e: self.outputQueue.put(str(e)) raise RunCmdException("SystemExit raised (3)") from e else: for _ in map(self.outputQueue.put, iter(self.fd.readline, b'')): pass
def eof(self): return not self.is_alive() and self.outputQueue.empty() @classmethod def getForFd(cls, fd, start=True, catch_exit=False): q = queue.Queue() reader = cls(fd, q, catch_exit) if start: reader.start() return reader, q
[docs]def run_cmd_old(cmd, sin="", shell=False, wait=False, log_error=True, secure=None, stop_waiting_if=None, do_not_log=False, encerror="ignore", encoding="utf8", cwd=None, fLOG=print): """ run a command line and wait for the result :param cmd: command line :param sin: sin, what must be written on the standard input :param shell: if True, cmd is a shell command (and no command window is opened) :param wait: call proc.wait :param log_error: if log_error, call fLOG (error) :param secure: if secure is a string (a valid filename), the function stores the output in a file and reads it continuously :param stop_waiting_if: the function stops waiting if some condition is fulfilled. The function received the last line from the logs. :param do_not_log: do not log the output :param encerror: encoding errors (ignore by default) while converting the output into a string :param encoding: encoding of the output :param cwd: current folder :param fLOG: logging function :return: content of stdout, stderr (only if wait is True) .. faqref:: :title: Exception when installing a module This error can occur when a module is installed on a virtual environment created before *pip* was updated on the main distribution. The solution consists in removing the virtual environment and create it again. :: c:\\Python34_x64vir\\install\\Scripts\\python -u setup.py install running install running bdist_egg running egg_info Traceback (most recent call last): File "c:\\Python34_x64vir\\install\\lib\\site-packages\\pkg_resources\\__init__.py", line 2785, in _dep_map return self.__dep_map File "c:\\Python34_x64vir\\install\\lib\\site-packages\\pkg_resources\\__init__.py", line 2642, in __getattr__ raise AttributeError(attr) AttributeError: _DistInfoDistribution__dep_map During handling of the above exception, another exception occurred: Traceback (most recent call last): File "c:\\Python34_x64vir\\install\\lib\\site-packages\\pkg_resources\\__init__.py", line 2776, in _parsed_pkg_info return self._pkg_info File "c:\\Python34_x64vir\\install\\lib\\site-packages\\pkg_resources\\__init__.py", line 2642, in __getattr__ raise AttributeError(attr) AttributeError: _pkg_info During handling of the above exception, another exception occurred: Traceback (most recent call last): File "setup.py", line 169, in <module> package_data=package_data, File "C:\\Python34_x64\\Lib\\distutils\\core.py", line 148, in setup dist.run_commands() File "C:\\Python34_x64\\Lib\\distutils\\dist.py", line 955, in run_commands self.run_command(cmd) File "C:\\Python34_x64\\Lib\\distutils\\dist.py", line 974, in run_command cmd_obj.run() File "c:\\Python34_x64vir\\install\\lib\\site-packages\\setuptools\\command\\install.py", line 67, in run self.do_egg_install() File "c:\\Python34_x64vir\\install\\lib\\site-packages\\setuptools\\command\\install.py", line 109, in do_egg_install self.run_command('bdist_egg') File "C:\\Python34_x64\\Lib\\distutils\\cmd.py", line 313, in run_command self.distribution.run_command(command) File "C:\\Python34_x64\\Lib\\distutils\\dist.py", line 974, in run_command cmd_obj.run() File "c:\\Python34_x64vir\\install\\lib\\site-packages\\setuptools\\command\\bdist_egg.py", line 151, in run self.run_command("egg_info") File "C:\\Python34_x64\\Lib\\distutils\\cmd.py", line 313, in run_command self.distribution.run_command(command) File "C:\\Python34_x64\\Lib\\distutils\\dist.py", line 974, in run_command cmd_obj.run() File "c:\\Python34_x64vir\\install\\lib\\site-packages\\setuptools\\command\\egg_info.py", line 171, in run ep.require(installer=installer) File "c:\\Python34_x64vir\\install\\lib\\site-packages\\pkg_resources\\__init__.py", line 2355, in require items = working_set.resolve(reqs, env, installer) File "c:\\Python34_x64vir\\install\\lib\\site-packages\\pkg_resources\\__init__.py", line 835, in resolve new_requirements = dist.requires(req.extras)[::-1] File "c:\\Python34_x64vir\\install\\lib\\site-packages\\pkg_resources\\__init__.py", line 2586, in requires dm = self._dep_map File "c:\\Python34_x64vir\\install\\lib\\site-packages\\pkg_resources\\__init__.py", line 2787, in _dep_map self.__dep_map = self._compute_dependencies() File "c:\\Python34_x64vir\\install\\lib\\site-packages\\pkg_resources\\__init__.py", line 2809, in _compute_dependencies for req in self._parsed_pkg_info.get_all('Requires-Dist') or []: File "c:\\Python34_x64vir\\install\\lib\\site-packages\\pkg_resources\\__init__.py", line 2778, in _parsed_pkg_info metadata = self.get_metadata(self.PKG_INFO) File "c:\\Python34_x64vir\\install\\lib\\site-packages\\pkg_resources\\__init__.py", line 1993, in get_metadata raise KeyError("No metadata except PKG-INFO is available") KeyError: 'No metadata except PKG-INFO is available' :githublink:`%|py|472` """ if sin is not None and sin != "": raise NotImplementedError("sin is not used") if secure is not None: if not do_not_log: fLOG("secure=", secure) with open(secure, "w") as f: f.write("") add = ">%s" % secure if isinstance(cmd, str): cmd += " " + add else: cmd.append(add) if not do_not_log and fLOG is not None: fLOG("execute ", cmd) if sys.platform.startswith("win"): startupinfo = subprocess.STARTUPINFO() startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW try: proc = subprocess.Popen(cmd, shell=shell, stdout=subprocess.PIPE, stderr=subprocess.PIPE, startupinfo=startupinfo, cwd=cwd) except FileNotFoundError as e: raise RunCmdException("unable to run CMD:\n{0}".format(cmd)) from e else: try: proc = subprocess.Popen(split_cmp_command(cmd), shell=shell, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=cwd) except FileNotFoundError as e: raise RunCmdException("unable to run CMD:\n{0}".format(cmd)) from e if wait: out = [] skip_waiting = False if secure is None: for line in proc.stdout: if not do_not_log and fLOG is not None: fLOG(line.decode(encoding, errors=encerror).strip("\n")) try: out.append( line.decode( encoding, errors=encerror).strip("\n")) except UnicodeDecodeError as exu: raise RunCmdException( "issue with cmd:" + str(cmd) + "\n" + str(exu)) if proc.stdout.closed: break if stop_waiting_if is not None and stop_waiting_if( line.decode("utf8", errors=encerror)): skip_waiting = True break else: last = [] while proc.poll() is None: if os.path.exists(secure): with open(secure, "r") as f: lines = f.readlines() if len(lines) > len(last): for line in lines[len(last):]: if not do_not_log and fLOG is not None: fLOG(line.strip("\n")) out.append(line.strip("\n")) last = lines if stop_waiting_if is not None and len( last) > 0 and stop_waiting_if(last[-1]): skip_waiting = True break time.sleep(0.1) if not skip_waiting: proc.wait() out = "\n".join(out) err = proc.stderr.read().decode(encoding, errors=encerror) if not do_not_log and fLOG is not None: fLOG("end of execution ", cmd) if len(err) > 0 and log_error and not do_not_log and fLOG is not None: fLOG("error (log)\n%s" % err) # return bytes.decode (out, errors="ignore"), bytes.decode(err, # errors="ignore") proc.stdout.close() proc.stderr.close() return out, err else: return "", ""