etisserant@79: #!/usr/bin/env python etisserant@79: # -*- coding: utf-8 -*- etisserant@79: andrej@1571: # This file is part of Beremiz, a Integrated Development Environment for andrej@1571: # programming IEC 61131-3 automates supporting plcopen standard and CanFestival. andrej@1571: # andrej@1571: # Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD andrej@1571: # andrej@1571: # See COPYING file for copyrights details. andrej@1571: # andrej@1571: # This program is free software; you can redistribute it and/or andrej@1571: # modify it under the terms of the GNU General Public License andrej@1571: # as published by the Free Software Foundation; either version 2 andrej@1571: # of the License, or (at your option) any later version. andrej@1571: # andrej@1571: # This program is distributed in the hope that it will be useful, andrej@1571: # but WITHOUT ANY WARRANTY; without even the implied warranty of andrej@1571: # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the andrej@1571: # GNU General Public License for more details. andrej@1571: # andrej@1571: # You should have received a copy of the GNU General Public License andrej@1571: # along with this program; if not, write to the Free Software andrej@1571: # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. etisserant@79: andrej@1881: andrej@1732: import os andrej@1732: import sys andrej@1732: import subprocess andrej@1732: import ctypes edouard@2730: import time edouard@3892: import shlex edouard@2730: from threading import Timer, Lock, Thread, Semaphore, Condition Edouard@1919: import signal etisserant@79: Edouard@2671: _debug = os.path.exists("BEREMIZ_DEBUG") Edouard@1407: Edouard@704: class outputThread(Thread): etisserant@110: """ etisserant@110: Thread is used to print the output of a command to the stdout etisserant@110: """ etisserant@110: def __init__(self, Proc, fd, callback=None, endcallback=None): Edouard@704: Thread.__init__(self) etisserant@110: self.killed = False etisserant@110: self.finished = False etisserant@110: self.retval = None etisserant@110: self.Proc = Proc etisserant@110: self.callback = callback etisserant@110: self.endcallback = endcallback etisserant@110: self.fd = fd edouard@3722: self.daemon = True etisserant@79: etisserant@110: def run(self): etisserant@162: outchunk = None etisserant@149: self.retval = None andrej@1739: while self.retval is None and not self.killed: andrej@1507: if self.endcallback: andrej@1507: self.retval = self.Proc.poll() andrej@1507: else: andrej@1507: self.retval = self.Proc.returncode andrej@1730: etisserant@110: outchunk = self.fd.readline() andrej@1756: if self.callback: andrej@1756: self.callback(outchunk) andrej@1739: while outchunk != '' and not self.killed: etisserant@162: outchunk = self.fd.readline() andrej@1756: if self.callback: andrej@1756: self.callback(outchunk) etisserant@110: if self.endcallback: greg@130: try: greg@232: err = self.Proc.wait() andrej@1780: except Exception: etisserant@149: err = self.retval etisserant@110: self.finished = True laurent@421: self.endcallback(self.Proc.pid, err) Edouard@1407: andrej@1736: andrej@1831: class ProcessLogger(object): andrej@1744: def __init__(self, logger, Command, finish_callback=None, andrej@1744: no_stdout=False, no_stderr=False, no_gui=True, andrej@1744: timeout=None, outlimit=None, errlimit=None, andrej@1744: endlog=None, keyword=None, kill_it=False, cwd=None, kinsamanka@3786: encoding=None, output_encoding=None, env=None): etisserant@110: self.logger = logger greg@424: if not isinstance(Command, list): greg@424: self.Command_str = Command edouard@3892: self.Command = shlex.split(Command) edouard@3892: else: edouard@3892: self.Command = Command greg@424: self.Command_str = subprocess.list2cmdline(self.Command) Edouard@1407: Edouard@1415: fsencoding = sys.getfilesystemencoding() surkovsv93@1883: self.output_encoding = output_encoding Edouard@1415: Edouard@1415: if encoding is None: Edouard@1415: encoding = fsencoding kinsamanka@3750: self.Command = [self.Command[0].encode(fsencoding)]+[x.encode(encoding) for x in self.Command[1:]] Edouard@1407: etisserant@110: self.finish_callback = finish_callback etisserant@110: self.no_stdout = no_stdout etisserant@110: self.no_stderr = no_stderr greg@111: self.startupinfo = None etisserant@110: self.errlen = 0 etisserant@110: self.outlen = 0 Edouard@704: self.errlimit = errlimit Edouard@704: self.outlimit = outlimit etisserant@110: self.exitcode = None Edouard@704: self.outdata = [] Edouard@704: self.errdata = [] Edouard@704: self.keyword = keyword Edouard@704: self.kill_it = kill_it andrej@1730: self.startsem = Semaphore(0) Edouard@704: self.finishsem = Semaphore(0) Edouard@704: self.endlock = Lock() Edouard@1407: andrej@1742: popenargs = { andrej@1878: "cwd": os.getcwd() if cwd is None else cwd, andrej@1878: "stdin": subprocess.PIPE, andrej@1878: "stdout": subprocess.PIPE, kinsamanka@3786: "stderr": subprocess.PIPE, kinsamanka@3786: "env": env andrej@1740: } Edouard@1407: Edouard@1919: if no_gui and os.name in ("nt", "ce"): greg@111: self.startupinfo = subprocess.STARTUPINFO() greg@111: self.startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW greg@128: popenargs["startupinfo"] = self.startupinfo Edouard@1919: elif os.name == 'posix': Edouard@704: popenargs["shell"] = False Edouard@1407: andrej@1506: if timeout: andrej@1740: self.timeout = Timer(timeout, self.endlog) andrej@1506: self.timeout.start() andrej@1506: else: andrej@1506: self.timeout = None andrej@1730: edouard@3483: if _debug and self.logger: edouard@3297: self.logger.write("(DEBUG) launching:\n" + self.Command_str + "\n") edouard@3297: kinsamanka@3755: self.Proc = subprocess.Popen(self.Command, encoding="utf-8", **popenargs) etisserant@112: etisserant@110: self.outt = outputThread( andrej@1878: self.Proc, andrej@1878: self.Proc.stdout, andrej@1878: self.output, andrej@1878: self.finish) etisserant@110: self.outt.start() etisserant@79: etisserant@112: self.errt = outputThread( andrej@1878: self.Proc, andrej@1878: self.Proc.stderr, andrej@1878: self.errors) greg@232: self.errt.start() andrej@1519: self.startsem.release() etisserant@79: edouard@2730: self.spinwakeuplock = Lock() edouard@2730: self.spinwakeupcond = Condition(self.spinwakeuplock) edouard@2730: self.spinwakeuptimer = None edouard@2730: andrej@1740: def output(self, v): surkovsv93@1883: if v and self.output_encoding: surkovsv93@1883: v = v.decode(self.output_encoding) Edouard@704: self.outdata.append(v) etisserant@110: self.outlen += 1 Edouard@2712: if self.logger and not self.no_stdout: Edouard@686: self.logger.write(v) andrej@1742: if (self.keyword and v.find(self.keyword) != -1) or (self.outlimit and self.outlen > self.outlimit): Edouard@704: self.endlog() Edouard@1407: andrej@1740: def errors(self, v): surkovsv93@1883: if v and self.output_encoding: surkovsv93@1883: v = v.decode(self.output_encoding) Edouard@704: self.errdata.append(v) etisserant@110: self.errlen += 1 Edouard@2712: if self.logger and not self.no_stderr: Edouard@686: self.logger.write_warning(v) Edouard@704: if self.errlimit and self.errlen > self.errlimit: Edouard@704: self.endlog() etisserant@79: andrej@1740: def log_the_end(self, ecode, pid): Edouard@2671: if self.logger is not None: Edouard@2671: self.logger.write(self.Command_str + "\n") Edouard@2671: self.logger.write_warning(_("exited with status {a1} (pid {a2})\n").format(a1=str(ecode), a2=str(pid))) greg@232: andrej@1740: def finish(self, pid, ecode): andrej@1730: # avoid running function before start is finished andrej@1519: self.startsem.acquire() andrej@1882: self.startsem.release() andrej@1476: if self.timeout: andrej@1476: self.timeout.cancel() etisserant@110: self.exitcode = ecode edouard@3297: if self.exitcode != 0: andrej@1740: self.log_the_end(ecode, pid) etisserant@110: if self.finish_callback is not None: andrej@1740: self.finish_callback(self, ecode, pid) andrey@1516: self.errt.join() Edouard@704: self.finishsem.release() edouard@2730: self.spinwakeup() etisserant@79: andrej@1740: def kill(self, gently=True): andrej@1539: # avoid running kill before start is finished andrej@1539: self.startsem.acquire() andrej@1539: self.startsem.release() andrej@1730: etisserant@110: self.outt.killed = True etisserant@112: self.errt.killed = True Edouard@1919: if os.name in ("nt", "ce"): etisserant@110: PROCESS_TERMINATE = 1 etisserant@110: handle = ctypes.windll.kernel32.OpenProcess(PROCESS_TERMINATE, False, self.Proc.pid) etisserant@110: ctypes.windll.kernel32.TerminateProcess(handle, -1) etisserant@110: ctypes.windll.kernel32.CloseHandle(handle) etisserant@110: else: etisserant@235: if gently: Edouard@1919: sig = signal.SIGTERM etisserant@235: else: Edouard@1919: sig = signal.SIGKILL etisserant@154: try: etisserant@235: os.kill(self.Proc.pid, sig) andrej@1780: except Exception: etisserant@154: pass greg@232: self.outt.join() greg@232: self.errt.join() etisserant@79: Edouard@704: def endlog(self): Edouard@704: if self.endlock.acquire(False): Edouard@704: if not self.outt.finished and self.kill_it: andrej@1757: self.kill() andrey@1516: self.finishsem.release() edouard@2730: self.spinwakeup() edouard@2730: edouard@2730: def spinwakeup(self): edouard@2730: with self.spinwakeuplock: edouard@2730: if self.spinwakeuptimer is not None: edouard@2730: self.spinwakeuptimer.cancel() edouard@2730: self.spinwakeuptimer = None edouard@2730: self.spinwakeupcond.notify() etisserant@89: Edouard@704: def spin(self): edouard@2730: start = time.time() edouard@2733: if self.logger: edouard@2733: while not self.finishsem.acquire(0): edouard@2733: with self.spinwakeuplock: edouard@2733: self.spinwakeuptimer = Timer(0.1, self.spinwakeup) edouard@2733: self.spinwakeuptimer.start() edouard@2733: self.spinwakeupcond.wait() edouard@2733: self.logger.progress("%.3fs"%(time.time() - start)) edouard@2733: else: edouard@2733: self.finishsem.acquire() edouard@2730: Edouard@704: return [self.exitcode, "".join(self.outdata), "".join(self.errdata)]