Plain is headed towards 1.0! Subscribe for development updates →

 1import datetime
 2import os
 3import signal
 4import subprocess
 5
 6from .compat import ON_WINDOWS
 7from .printer import Message
 8
 9
10class Process:
11    """
12    A simple utility wrapper around a subprocess.Popen that stores
13    a number of attributes needed by Poncho and supports forwarding process
14    lifecycle events and output to a queue.
15    """
16
17    def __init__(self, cmd, name=None, color=None, quiet=False, env=None, cwd=None):
18        self.cmd = cmd
19        self.color = color
20        self.quiet = quiet
21        self.name = name
22        self.env = os.environ.copy() if env is None else env
23        self.cwd = cwd
24
25        self._clock = datetime.datetime
26        self._child = None
27        self._child_ctor = Popen
28
29    def run(self, events=None, ignore_signals=False):
30        self._events = events
31        self._child = self._child_ctor(self.cmd, env=self.env, cwd=self.cwd)
32        self._send_message({"pid": self._child.pid}, type="start")
33
34        # Don't pay attention to SIGINT/SIGTERM. The process itself is
35        # considered unkillable, and will only exit when its child (the shell
36        # running the Procfile process) exits.
37        if ignore_signals:
38            signal.signal(signal.SIGINT, signal.SIG_IGN)
39            signal.signal(signal.SIGTERM, signal.SIG_IGN)
40
41        for line in iter(self._child.stdout.readline, b""):
42            if not self.quiet:
43                self._send_message(line)
44        self._child.stdout.close()
45        self._child.wait()
46
47        self._send_message({"returncode": self._child.returncode}, type="stop")
48
49    def _send_message(self, data, type="line"):
50        if self._events is not None:
51            self._events.put(
52                Message(
53                    type=type,
54                    data=data,
55                    time=self._clock.now(),
56                    name=self.name,
57                    color=self.color,
58                )
59            )
60
61
62class Popen(subprocess.Popen):
63    def __init__(self, cmd, **kwargs):
64        start_new_session = kwargs.pop("start_new_session", True)
65        options = {
66            "stdout": subprocess.PIPE,
67            "stderr": subprocess.STDOUT,
68            "shell": True,
69            "close_fds": not ON_WINDOWS,
70        }
71        options.update(**kwargs)
72
73        if ON_WINDOWS:
74            # MSDN reference:
75            #   http://msdn.microsoft.com/en-us/library/windows/desktop/ms684863%28v=vs.85%29.aspx
76            create_no_window = 0x08000000
77            options.update(creationflags=create_no_window)
78        elif start_new_session:
79            options.update(start_new_session=True)
80
81        super().__init__(cmd, **options)