# jhbuild - a build script for GNOME 1.x and 2.x # Copyright (C) 2001-2006 James Henstridge # # tinderbox.py: build logic for a non-interactive reporting build # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA import os import time import subprocess import locale from jhbuild.utils import cmds from jhbuild.errors import CommandError import buildscript index_header = ''' JHBuild Results

JHBuild Results

Platform

%(buildplatform)s

Summary

''' index_footer = '''
Time Module Phases Status
%(failures)s ''' buildlog_header = ''' %(module)s Build Log

%(module)s Build Log

''' buildlog_footer = ''' ''' def get_distro(): # try using the lsb_release tool to get the distro info try: distro = cmds.get_output(['lsb_release', '--short', '--id']).strip() release = cmds.get_output(['lsb_release', '--short', '--release']).strip() codename = cmds.get_output(['lsb_release', '--short', '--codename']).strip() if codename: return '%s %s (%s)' % (distro, release, codename) else: return '%s %s' % (distro, release) except (CommandError, IOError): pass # otherwise, look for a /etc/*-release file release_files = ['/etc/redhat-release', '/etc/debian_version' ] release_files.extend([ os.path.join('/etc', fname) for fname in os.listdir('/etc') if fname.endswith('release') \ and fname != 'lsb-release' ]) for filename in release_files: if os.path.exists(filename): return open(filename, 'r').readline().strip() # else: return None def escape(string): return string.replace('&','&').replace('<','<').replace( '>','>').replace('\n','
').replace( '\t','    ') class TinderboxBuildScript(buildscript.BuildScript): def __init__(self, config, module_list): buildscript.BuildScript.__init__(self, config, module_list) self.indexfp = None self.modulefp = None self.outputdir = os.path.abspath(config.tinderbox_outputdir) if not os.path.exists(self.outputdir): os.makedirs(self.outputdir) os.environ['TERM'] = 'dumb' charset = locale.getpreferredencoding() if charset: self.charset = ';charset=%s' % charset else: self.charset = '' def timestamp(self): tm = time.time() s = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(tm)) msecs = max(int((tm - int(tm)) * 1000), 0) return '%s.%03d' % (s, msecs) def message(self, msg, module_num=-1): '''Display a message to the user''' if self.modulefp: self.modulefp.write('
%s ' '%s
\n' % (escape(msg), self.timestamp())) else: # do something with messages outside of builds of module builds pass def set_action(self, action, module, module_num=-1, action_target=None): if module_num == -1: module_num = self.module_num if not action_target: action_target = module.name self.message('%s %s' % (action, action_target), module_num) def execute(self, command, hint=None, cwd=None, extra_env=None): assert self.modulefp, 'not currently building a module' kws = { 'close_fds': True } self.modulefp.write('
')
        if isinstance(command, (str, unicode)):
            self.modulefp.write('%s\n'
                                % escape(command))
            kws['shell'] = True
        else:
            self.modulefp.write('%s\n'
                                % escape(' '.join(command)))
        kws['stdin'] = subprocess.PIPE
        kws['stdout'] = subprocess.PIPE
        kws['stderr'] = subprocess.PIPE
        if hint == 'cvs':
            def format_line(line, error_output, fp=self.modulefp):
                if line[-1] == '\n': line = line[:-1]
                if line.startswith('C '):
                    fp.write('%s\n'
                                        % escape(line))
                else:
                    fp.write('%s\n' % escape(line))
            kws['stderr'] = subprocess.STDOUT
        else:
            def format_line(line, error_output, fp=self.modulefp):
                if line[-1] == '\n': line = line[:-1]
                if error_output:
                    fp.write('%s\n'
                                        % escape(line))
                else:
                    fp.write('%s\n' % escape(line))

        if cwd is not None:
            kws['cwd'] = cwd

        if extra_env is not None:
            kws['env'] = os.environ.copy()
            kws['env'].update(extra_env)

        try:
            p = subprocess.Popen(command, **kws)
        except OSError, e:
            self.modulefp.write('Error: %s\n'
                                % escape(str(e)))
            raise CommandError(str(e))
        cmds.pprint_output(p, format_line)
        self.modulefp.write('
\n') self.modulefp.flush() if p.returncode != 0: raise CommandError('Error running %s' % command, p.returncode) def start_build(self): assert self.outputdir # close stdin os.close(0) info = [] import socket un = os.uname() info.append(('Build Host', socket.gethostname())) info.append(('Architecture', '%s %s (%s)' % (un[0], un[2], un[4]))) distro = get_distro() if distro: info.append(('Distribution', distro)) info.append(('Module Set', self.config.moduleset)) info.append(('Start Time', self.timestamp())) buildplatform = '\n' for (key, val) in info: buildplatform += '\n' \ % (key, val) buildplatform += '
%s%s
\n' self.indexfp = open(os.path.join(self.outputdir, 'index.html'), 'w') self.indexfp.write(index_header % { 'buildplatform': buildplatform, 'charset': self.charset }) self.indexfp.flush() def end_build(self, failures): self.indexfp.write('' '%s' 'activities' '\n' % self.timestamp()) self.modulefilename='activities.html' self.modulefp = open(os.path.join(self.outputdir, self.modulefilename), 'w') self.modulefp.write(buildlog_header % { 'module': 'Activities', 'charset': self.charset }) sub = subprocess.Popen(["./sugar-jhbuild", "run"]) time.sleep(25) if not sub.poll() == None: raise CommandError, "sugar-jhbuild failed." # Now, list all activities. activities = os.listdir('build/share/activities') self.modulefp.write("Activities: ") self.modulefp.write(" ".join(activities)) runfailures = [] # Launch each activity for activity in activities: self.modulefp.write("

%s: " % activity) infopath = "build/share/activities/%s/activity/activity.info" % activity info = open(infopath) for line in info.readlines(): if line.startswith("name = "): activityname = line if not activityname: raise CommandError, "%s has no name in activity.info!" % activity info.close() activityname = activityname.replace("name = ", "") activityname = activityname.rstrip() if activityname == "Journal": p1 = subprocess.Popen(['ps', 'ax'], stdout=subprocess.PIPE) p2 = subprocess.Popen(['grep', '-c', 'Journal.activity'], stdin=p1.stdout, stdout=subprocess.PIPE) numproc = p2.stdout.read() if int(numproc) > 1: self.modulefp.write("A journal is already running.") continue subtwo = subprocess.Popen(["sugar-activity", "%s" % activityname]) time.sleep(20) if not subtwo.poll() == None: runfailures.append(activityname) log = open("/home/cjb/.sugar/default/logs/%s.log" % activityname) logtext = log.readlines() self.modulefp.write("Failed:

%s
" % "".join(logtext)) else: self.modulefp.write("Succeeded.") self.indexfp.write('' '%s' 'finish' '\n' % self.timestamp()) if failures: info = '

The following modules failed to build

\n' info += '
\n' info += ', '.join(failures) info += '
\n' else: info = '' self.indexfp.write(index_footer % { 'failures': info }) if runfailures: info = '

The following activities failed to run

\n' info += '
\n' info += ', '.join(runfailures) info += '
\n' else: info = '' self.indexfp.write(index_footer % { 'failures': info }) self.indexfp.close() self.indexfp = None def start_module(self, module): self.modulefilename='%s.html' % module.replace('/','_') self.indexfp.write('' '%s' '%s' '\n' % (self.timestamp(), self.modulefilename, module)) self.modulefp = open(os.path.join(self.outputdir, self.modulefilename), 'w') self.modulefp.write(buildlog_header % { 'module': module, 'charset': self.charset }) def end_module(self, module, failed): if failed: self.message('Failed') else: self.message('Succeeded') self.modulefp.write(buildlog_footer) self.modulefp.close() self.modulefp = None self.indexfp.write('\n') if failed: self.indexfp.write('failed\n') else: self.indexfp.write('ok\n') self.indexfp.write('\n\n') self.indexfp.flush() def start_phase(self, module, state): self.modulefp.write('\n' % state) def end_phase(self, module, state, error): if error: self.indexfp.write('%s\n' % (error, self.modulefilename, state, state)) else: self.indexfp.write('%s\n' % (self.modulefilename, state, state)) self.indexfp.flush() def handle_error(self, module, state, nextstate, error, altstates): '''handle error during build''' self.message('error during stage %s of %s: %s' % (state, module.name, error)) return 'fail' BUILD_SCRIPT = TinderboxBuildScript