disable status icon on win32
[jhbuild.git] / jhbuild / frontends / terminal.py
blobc6e62e42493eac388a6fed3f2ffd24ccac58d2e1
1 # jhbuild - a build script for GNOME 1.x and 2.x
2 # Copyright (C) 2001-2006 James Henstridge
3 # Copyright (C) 2003-2004 Seth Nickell
5 # terminal.py: build logic for a terminal interface
7 # This program is free software; you can redistribute it and/or modify
8 # it under the terms of the GNU General Public License as published by
9 # the Free Software Foundation; either version 2 of the License, or
10 # (at your option) any later version.
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
17 # You should have received a copy of the GNU General Public License
18 # along with this program; if not, write to the Free Software
19 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21 import sys
22 import os
23 import signal
24 import subprocess
25 import locale
27 from jhbuild.frontends import buildscript
28 from jhbuild.utils import cmds
29 from jhbuild.utils import trayicon
30 from jhbuild.utils import notify
31 from jhbuild.errors import CommandError
33 term = os.environ.get('TERM', '')
34 is_xterm = term.find('xterm') >= 0 or term == 'rxvt'
35 del term
37 try: t_bold = cmds.get_output(['tput', 'bold'])
38 except: t_bold = ''
39 try: t_reset = cmds.get_output(['tput', 'sgr0'])
40 except: t_reset = ''
41 t_colour = [''] * 16
42 try:
43 for i in range(8):
44 t_colour[i] = cmds.get_output(['tput', 'setf', '%d' % i])
45 t_colour[i+8] = t_bold + t_colour[i]
46 except: pass
49 user_shell = os.environ.get('SHELL', '/bin/sh')
51 try:
52 import curses
53 except ImportError:
54 curses = None
55 else:
56 try:
57 curses.setupterm()
58 except:
59 pass
61 # tray icon stuff ...
62 if DATADIR:
63 icondir = os.path.join(DATADIR, 'jhbuild')
64 else:
65 icondir = os.path.join(os.path.dirname(__file__), 'icons')
66 phase_map = {
67 'checkout': 'checkout.png',
68 'force_checkout': 'checkout.png',
69 'download': 'checkout.png',
70 'unpack': 'checkout.png',
71 'patch': 'checkout.png',
72 'configure': 'configure.png',
73 #'clean': 'clean.png',
74 'build': 'build.png',
75 'check': 'check.png',
76 'install': 'install.png',
79 class TerminalBuildScript(buildscript.BuildScript):
80 triedcheckout = None
81 is_end_of_build = False
83 def __init__(self, config, module_list):
84 buildscript.BuildScript.__init__(self, config, module_list)
85 self.trayicon = trayicon.TrayIcon(config)
86 self.notify = notify.Notify(config)
88 def message(self, msg, module_num=-1):
89 '''Display a message to the user'''
91 if module_num == -1:
92 module_num = self.module_num
93 if module_num > 0:
94 progress = ' [%d/%d]' % (module_num, len(self.modulelist))
95 else:
96 progress = ''
98 if not (self.config.quiet_mode and self.config.progress_bar):
99 uprint('%s*** %s ***%s%s' % (t_bold, msg, progress, t_reset))
100 else:
101 progress_percent = 1.0 * (module_num-1) / len(self.modulelist)
102 self.display_status_line(progress_percent, module_num, msg)
104 if is_xterm:
105 sys.stdout.write('\033]0;jhbuild:%s%s\007' % (uencode(msg), progress))
106 sys.stdout.flush()
107 self.trayicon.set_tooltip('%s%s' % (msg, progress))
109 def set_action(self, action, module, module_num=-1, action_target=None):
110 if module_num == -1:
111 module_num = self.module_num
112 if not action_target:
113 action_target = module.name
114 self.message('%s %s' % (action, action_target), module_num)
116 def display_status_line(self, progress, module_num, message):
117 if self.is_end_of_build:
118 # hardcode progress to 100% at the end of the build
119 progress = 1
121 columns = curses.tigetnum('cols')
122 width = columns / 2
123 num_hashes = int(round(progress * width))
124 progress_bar = '[' + (num_hashes * '=') + ((width - num_hashes) * '-') + ']'
126 module_no_digits = len(str(len(self.modulelist)))
127 format_str = '%%%dd' % module_no_digits
128 module_pos = '[' + format_str % module_num + '/' + format_str % len(self.modulelist) + ']'
130 output = '%s %s %s%s%s' % (progress_bar, module_pos, t_bold, message, t_reset)
131 if len(output) > columns:
132 output = output[:columns]
133 else:
134 output += ' ' * (columns-len(output))
136 sys.stdout.write(output + '\r')
137 if self.is_end_of_build:
138 sys.stdout.write('\n')
139 sys.stdout.flush()
142 def execute(self, command, hint=None, cwd=None, extra_env=None):
143 if not command:
144 raise CommandError(_('No command given'))
146 kws = {
147 'close_fds': True
149 if isinstance(command, (str, unicode)):
150 kws['shell'] = True
151 pretty_command = command
152 else:
153 pretty_command = ' '.join(command)
155 if not self.config.quiet_mode:
156 print pretty_command
158 kws['stdin'] = subprocess.PIPE
159 if hint in ('cvs', 'svn', 'hg-update.py'):
160 kws['stdout'] = subprocess.PIPE
161 kws['stderr'] = subprocess.STDOUT
162 else:
163 kws['stdout'] = None
164 kws['stderr'] = None
166 if self.config.quiet_mode:
167 kws['stdout'] = subprocess.PIPE
168 kws['stderr'] = subprocess.STDOUT
170 if cwd is not None:
171 kws['cwd'] = cwd
173 if extra_env is not None:
174 kws['env'] = os.environ.copy()
175 kws['env'].update(extra_env)
177 try:
178 p = subprocess.Popen(command, **kws)
179 except OSError, e:
180 raise CommandError(str(e))
182 output = []
183 if hint in ('cvs', 'svn', 'hg-update.py'):
184 conflicts = []
185 def format_line(line, error_output, conflicts = conflicts, output = output):
186 if line.startswith('C '):
187 conflicts.append(line)
189 if self.config.quiet_mode:
190 output.append(line)
191 return
193 if line[-1] == '\n': line = line[:-1]
194 if not self.config.pretty_print:
195 hint = None
196 print line
197 return
199 if line.startswith('C '):
200 print '%s%s%s' % (t_colour[12], line, t_reset)
201 elif line.startswith('M '):
202 print '%s%s%s' % (t_colour[10], line, t_reset)
203 elif line.startswith('? '):
204 print '%s%s%s' % (t_colour[8], line, t_reset)
205 else:
206 print line
208 cmds.pprint_output(p, format_line)
209 if conflicts:
210 uprint(_('\nConflicts during checkout:\n'))
211 for line in conflicts:
212 sys.stdout.write('%s %s%s\n'
213 % (t_colour[12], line, t_reset))
214 # make sure conflicts fail
215 if p.returncode == 0 and hint == 'cvs': p.returncode = 1
216 elif self.config.quiet_mode:
217 def format_line(line, error_output, output = output):
218 output.append(line)
219 cmds.pprint_output(p, format_line)
220 else:
221 try:
222 p.communicate()
223 except KeyboardInterrupt:
224 try:
225 os.kill(p.pid, signal.SIGINT)
226 except OSError:
227 # process might already be dead.
228 pass
229 try:
230 if p.wait() != 0:
231 if self.config.quiet_mode:
232 print ''.join(output)
233 raise CommandError(_('########## Error running %s') % pretty_command)
234 except OSError:
235 # it could happen on a really badly-timed ctrl-c (see bug 551641)
236 raise CommandError(_('########## Error running %s') % pretty_command)
238 def start_phase(self, module, phase):
239 self.trayicon.set_icon(os.path.join(icondir,
240 phase_map.get(phase, 'build.png')))
242 def end_build(self, failures):
243 self.is_end_of_build = True
244 if len(failures) == 0:
245 self.message(_('success'))
246 else:
247 self.message(_('the following modules were not built'))
248 for module in failures:
249 print module,
250 print
252 def handle_error(self, module, phase, nextphase, error, altphases):
253 '''handle error during build'''
254 summary = _('error during phase %(phase)s of %(module)s') % {
255 'phase': phase, 'module':module.name}
256 try:
257 error_message = error.args[0]
258 self.message('%s: %s' % (summary, error_message))
259 except:
260 error_message = None
261 self.message(summary)
262 self.trayicon.set_icon(os.path.join(icondir, 'error.png'))
263 self.notify.notify(summary = summary, body = error_message,
264 icon = 'dialog-error', expire = 20)
266 if self.config.trycheckout:
267 if self.triedcheckout is None and altphases.count('configure'):
268 self.triedcheckout = 'configure'
269 self.message(_('automatically retrying configure'))
270 return 'configure'
271 elif self.triedcheckout == 'configure' and altphases.count('force_checkout'):
272 self.triedcheckout = 'done'
273 self.message(_('automatically forcing a fresh checkout'))
274 return 'force_checkout'
275 self.triedcheckout = None
277 if not self.config.interact:
278 return 'fail'
279 while True:
280 print
281 uprint(_(' [1] rerun phase %s') % phase)
282 if nextphase:
283 uprint(_(' [2] ignore error and continue to %s') % nextphase)
284 else:
285 uprint(_(' [2] ignore error and continue to next module'))
286 uprint(_(' [3] give up on module'))
287 uprint(_(' [4] start shell'))
288 uprint(_(' [5] reload configuration'))
289 nb_options = i = 6
290 for altphase in (altphases or []):
291 uprint(_(' [%d] go to phase %s') % (i, altphase))
292 i = i + 1
293 val = raw_input(uencode(_('choice: ')))
294 val = val.strip()
295 if val == '1':
296 return phase
297 elif val == '2':
298 return nextphase
299 elif val == '3':
300 return 'fail'
301 elif val == '4':
302 cwd = os.getcwd()
303 try:
304 os.chdir(module.get_builddir(self))
305 except OSError:
306 os.chdir(self.config.checkoutroot)
307 uprint(_('exit shell to continue with build'))
308 os.system(user_shell)
309 os.chdir(cwd) # restor working directory
310 elif val == '5':
311 self.config.reload()
312 else:
313 try:
314 val = int(val)
315 return altphases[val - nb_options]
316 except:
317 uprint(_('invalid choice'))
318 assert False, 'not reached'
320 BUILD_SCRIPT = TerminalBuildScript