6 from stgit
.compat
import environ_get
, fsencode_utf8
7 from stgit
.exception
import StgException
8 from stgit
.out
import MessagePrinter
, out
11 Copyright (C) 2007, Karl Hasselström <kha@treskal.com>
13 This program is free software; you can redistribute it and/or modify
14 it under the terms of the GNU General Public License version 2 as
15 published by the Free Software Foundation.
17 This program is distributed in the hope that it will be useful,
18 but WITHOUT ANY WARRANTY; without even the implied warranty of
19 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 GNU General Public License for more details.
22 You should have received a copy of the GNU General Public License
23 along with this program; if not, see http://www.gnu.org/licenses/.
27 class RunException(StgException
):
28 """Exception raised for subprocess failures."""
31 def get_log_mode(spec
):
34 (log_mode
, outfile
) = spec
.split(':', 1)
35 all_log_modes
= ['debug', 'profile']
36 if log_mode
and log_mode
not in all_log_modes
:
38 ('Unknown log mode "%s" specified in $STGIT_SUBPROCESS_LOG.' % log_mode
),
39 'Valid values are: %s' % ', '.join(all_log_modes
),
42 f
= MessagePrinter(io
.open(outfile
, 'a', encoding
='utf-8'))
48 _log_mode
, _logfile
= get_log_mode(environ_get('STGIT_SUBPROCESS_LOG', ''))
49 if _log_mode
== 'profile':
50 _log_starttime
= datetime
.datetime
.now()
51 _log_subproctime
= 0.0
56 return 86400 * d
.days
+ d
.seconds
+ 1e-6 * d
.microseconds
60 if _log_mode
!= 'profile':
62 ttime
= duration(_log_starttime
, datetime
.datetime
.now())
63 rtime
= ttime
- _log_subproctime
65 'Total time: %1.3f s' % ttime
,
66 'Time spent in subprocess calls: %1.3f s (%1.1f%%)'
67 % (_log_subproctime
, 100 * _log_subproctime
/ ttime
),
68 'Remaining time: %1.3f s (%1.1f%%)' % (rtime
, 100 * rtime
/ ttime
),
75 def __init__(self
, *cmd
):
77 self
._good
_retvals
= [0]
78 self
._env
= self
._cwd
= None
80 self
._in
_encoding
= 'utf-8'
81 self
._out
_encoding
= 'utf-8'
82 self
._discard
_stderr
= False
85 return [fsencode_utf8(c
) for c
in self
._cmd
]
88 # Windows requires a dict of strings as env parameter, so don't encode for Windows
89 if self
._env
and os
.name
!= 'nt':
90 return {fsencode_utf8(k
): fsencode_utf8(v
) for k
, v
in self
._env
.items()}
95 if _log_mode
== 'debug':
96 _logfile
.start('Running subprocess %s' % self
._cmd
)
97 if self
._cwd
is not None:
98 _logfile
.info('cwd: %s' % self
._cwd
)
99 if self
._env
is not None:
100 for k
in sorted(self
._env
):
102 if v
is None or v
!= self
._env
[k
]:
103 _logfile
.info('%s: %s' % (k
, self
._env
[k
]))
104 elif _log_mode
== 'profile':
105 _logfile
.start('Running subprocess %s' % self
._cmd
)
106 self
._starttime
= datetime
.datetime
.now()
108 def _log_end(self
, retcode
):
109 global _log_subproctime
, _log_starttime
110 if _log_mode
== 'debug':
111 _logfile
.done('return code: %d' % retcode
)
112 elif _log_mode
== 'profile':
113 n
= datetime
.datetime
.now()
114 d
= duration(self
._starttime
, n
)
115 _logfile
.done('%1.3f s' % d
)
116 _log_subproctime
+= d
118 'Time since program start: %1.3f s' % duration(_log_starttime
, n
)
121 def _check_exitcode(self
):
122 if self
._good
_retvals
is None:
124 if self
.exitcode
not in self
._good
_retvals
:
125 raise self
.exc('%s failed with code %d' % (self
._cmd
[0], self
.exitcode
))
128 """Run with captured IO."""
131 p
= subprocess
.Popen(
133 env
=self
._prep
_env
(),
135 stdin
=subprocess
.PIPE
,
136 stdout
=subprocess
.PIPE
,
137 stderr
=subprocess
.PIPE
,
139 outdata
, errdata
= p
.communicate(self
._indata
)
140 self
.exitcode
= p
.returncode
142 raise self
.exc('%s failed: %s' % (self
._cmd
[0], e
))
143 if errdata
and not self
._discard
_stderr
:
144 out
.err_bytes(errdata
)
145 self
._log
_end
(self
.exitcode
)
146 self
._check
_exitcode
()
147 if self
._out
_encoding
:
148 return outdata
.decode(self
._out
_encoding
)
153 """Run without captured IO."""
154 assert self
._indata
is None
157 p
= subprocess
.Popen(
159 env
=self
._prep
_env
(),
162 self
.exitcode
= p
.wait()
164 raise self
.exc('%s failed: %s' % (self
._cmd
[0], e
))
165 self
._log
_end
(self
.exitcode
)
166 self
._check
_exitcode
()
168 def run_background(self
):
169 """Run as a background process."""
170 assert self
._indata
is None
171 assert self
._in
_encoding
is None
172 assert self
._out
_encoding
is None
174 return subprocess
.Popen(
176 env
=self
._prep
_env
(),
178 stdin
=subprocess
.PIPE
,
179 stdout
=subprocess
.PIPE
,
180 stderr
=subprocess
.PIPE
,
183 raise self
.exc('%s failed: %s' % (self
._cmd
[0], e
))
185 def returns(self
, retvals
):
186 self
._good
_retvals
= retvals
189 def discard_exitcode(self
):
190 self
._good
_retvals
= None
193 def discard_stderr(self
, discard
=True):
194 self
._discard
_stderr
= discard
198 self
._env
= os
.environ
.copy()
199 self
._env
.update(env
)
206 def encoding(self
, encoding
):
207 self
._in
_encoding
= encoding
210 def decoding(self
, encoding
):
211 self
._out
_encoding
= encoding
214 def raw_input(self
, indata
):
215 if self
._in
_encoding
:
216 self
._indata
= indata
.encode(self
._in
_encoding
)
218 self
._indata
= indata
221 def input_nulterm(self
, lines
):
222 return self
.raw_input('\0'.join(lines
))
225 outdata
= self
._run
_io
()
227 raise self
.exc('%s produced output' % self
._cmd
[0])
229 def discard_output(self
):
232 def raw_output(self
):
233 return self
._run
_io
()
235 def output_lines(self
, sep
='\n'):
236 outdata
= self
._run
_io
()
237 if outdata
.endswith(sep
):
238 outdata
= outdata
[:-1]
240 return outdata
.split(sep
)
244 def output_one_line(self
, sep
='\n'):
245 outlines
= self
.output_lines(sep
)
246 if len(outlines
) == 1:
250 '%s produced %d lines, expected 1' % (self
._cmd
[0], len(outlines
))
254 """Just run, with no IO redirection."""