diff options
Diffstat (limited to 'scripts')
-rwxr-xr-x | scripts/build-many-glibcs.py | 170 |
1 files changed, 161 insertions, 9 deletions
diff --git a/scripts/build-many-glibcs.py b/scripts/build-many-glibcs.py index d5355d8..658a22e 100755 --- a/scripts/build-many-glibcs.py +++ b/scripts/build-many-glibcs.py @@ -22,21 +22,26 @@ This script takes as arguments a directory name (containing a src subdirectory with sources of the relevant toolchain components) and a description of what to do: 'checkout', to check out sources into that -directory, 'host-libraries', to build libraries required by the -toolchain, 'compilers', to build cross-compilers for various -configurations, or 'glibcs', to build glibc for various configurations -and run the compilation parts of the testsuite. Subsequent arguments -name the versions of components to check out (<component>-<version), -for 'checkout', or, for actions other than 'checkout', name -configurations for which compilers or glibc are to be built. +directory, 'bot-cycle', to run a series of checkout and build steps, +'host-libraries', to build libraries required by the toolchain, +'compilers', to build cross-compilers for various configurations, or +'glibcs', to build glibc for various configurations and run the +compilation parts of the testsuite. Subsequent arguments name the +versions of components to check out (<component>-<version), for +'checkout', or, for actions other than 'checkout' and 'bot-cycle', +name configurations for which compilers or glibc are to be built. + """ import argparse import datetime +import email.mime.text +import email.utils import json import os import re import shutil +import smtplib import stat import subprocess import sys @@ -55,6 +60,7 @@ class Context(object): self.srcdir = os.path.join(topdir, 'src') self.versions_json = os.path.join(self.srcdir, 'versions.json') self.build_state_json = os.path.join(topdir, 'build-state.json') + self.bot_config_json = os.path.join(topdir, 'bot-config.json') self.installdir = os.path.join(topdir, 'install') self.host_libraries_installdir = os.path.join(self.installdir, 'host-libraries') @@ -392,6 +398,12 @@ class Context(object): if action == 'checkout': self.checkout(configs) return + if action == 'bot-cycle': + if configs: + print('error: configurations specified for bot-cycle') + exit(1) + self.bot_cycle() + return if action == 'host-libraries' and configs: print('error: configurations specified for host-libraries') exit(1) @@ -860,6 +872,146 @@ class Context(object): new_passes) self.store_build_state_json() + def load_bot_config_json(self): + """Load bot configuration.""" + with open(self.bot_config_json, 'r') as f: + self.bot_config = json.load(f) + + def part_build_old(self, action, delay): + """Return whether the last build for a given action was at least a + given number of seconds ago, or does not have a time recorded.""" + old_time_str = self.build_state[action]['build-time'] + if not old_time_str: + return True + old_time = datetime.datetime.strptime(old_time_str, + '%Y-%m-%d %H:%M:%S') + new_time = datetime.datetime.utcnow() + delta = new_time - old_time + return delta.total_seconds() >= delay + + def bot_cycle(self): + """Run a single round of checkout and builds.""" + print('Bot cycle starting %s.' % str(datetime.datetime.utcnow())) + self.load_bot_config_json() + actions = ('host-libraries', 'compilers', 'glibcs') + self.bot_run_self(['--replace-sources'], 'checkout') + self.load_versions_json() + if self.get_script_text() != self.script_text: + print('Script changed, re-execing.') + # On script change, all parts of the build should be rerun. + for a in actions: + self.clear_last_build_state(a) + self.exec_self() + check_components = {'host-libraries': ('gmp', 'mpfr', 'mpc'), + 'compilers': ('binutils', 'gcc', 'glibc', 'linux'), + 'glibcs': ('glibc',)} + must_build = {} + for a in actions: + build_vers = self.build_state[a]['build-versions'] + must_build[a] = False + if not self.build_state[a]['build-time']: + must_build[a] = True + old_vers = {} + new_vers = {} + for c in check_components[a]: + if c in build_vers: + old_vers[c] = build_vers[c] + new_vers[c] = {'version': self.versions[c]['version'], + 'revision': self.versions[c]['revision']} + if new_vers == old_vers: + print('Versions for %s unchanged.' % a) + else: + print('Versions changed or rebuild forced for %s.' % a) + if a == 'compilers' and not self.part_build_old( + a, self.bot_config['compilers-rebuild-delay']): + print('Not requiring rebuild of compilers this soon.') + else: + must_build[a] = True + if must_build['host-libraries']: + must_build['compilers'] = True + if must_build['compilers']: + must_build['glibcs'] = True + for a in actions: + if must_build[a]: + print('Must rebuild %s.' % a) + self.clear_last_build_state(a) + else: + print('No need to rebuild %s.' % a) + for a in actions: + if must_build[a]: + build_time = datetime.datetime.utcnow() + print('Rebuilding %s at %s.' % (a, str(build_time))) + self.bot_run_self([], a) + self.load_build_state_json() + self.bot_build_mail(a, build_time) + print('Bot cycle done at %s.' % str(datetime.datetime.utcnow())) + + def bot_build_mail(self, action, build_time): + """Send email with the results of a build.""" + build_time = build_time.replace(microsecond=0) + subject = (self.bot_config['email-subject'] % + {'action': action, + 'build-time': str(build_time)}) + results = self.build_state[action]['build-results'] + changes = self.build_state[action]['result-changes'] + ever_passed = set(self.build_state[action]['ever-passed']) + versions = self.build_state[action]['build-versions'] + new_regressions = {k for k in changes if changes[k] == 'PASS -> FAIL'} + all_regressions = {k for k in ever_passed if results[k] == 'FAIL'} + all_fails = {k for k in results if results[k] == 'FAIL'} + if new_regressions: + new_reg_list = sorted(['FAIL: %s' % k for k in new_regressions]) + new_reg_text = ('New regressions:\n\n%s\n\n' % + '\n'.join(new_reg_list)) + else: + new_reg_text = '' + if all_regressions: + all_reg_list = sorted(['FAIL: %s' % k for k in all_regressions]) + all_reg_text = ('All regressions:\n\n%s\n\n' % + '\n'.join(all_reg_list)) + else: + all_reg_text = '' + if all_fails: + all_fail_list = sorted(['FAIL: %s' % k for k in all_fails]) + all_fail_text = ('All failures:\n\n%s\n\n' % + '\n'.join(all_fail_list)) + else: + all_fail_text = '' + if changes: + changes_list = sorted(changes.keys()) + changes_list = ['%s: %s' % (changes[k], k) for k in changes_list] + changes_text = ('All changed results:\n\n%s\n\n' % + '\n'.join(changes_list)) + else: + changes_text = '' + results_text = (new_reg_text + all_reg_text + all_fail_text + + changes_text) + if not results_text: + results_text = 'Clean build with unchanged results.\n\n' + versions_list = sorted(versions.keys()) + versions_list = ['%s: %s (%s)' % (k, versions[k]['version'], + versions[k]['revision']) + for k in versions_list] + versions_text = ('Component versions for this build:\n\n%s\n' % + '\n'.join(versions_list)) + body_text = results_text + versions_text + msg = email.mime.text.MIMEText(body_text) + msg['Subject'] = subject + msg['From'] = self.bot_config['email-from'] + msg['To'] = self.bot_config['email-to'] + msg['Message-ID'] = email.utils.make_msgid() + msg['Date'] = email.utils.format_datetime(datetime.datetime.utcnow()) + with smtplib.SMTP(self.bot_config['email-server']) as s: + s.send_message(msg) + + def bot_run_self(self, opts, action): + """Run a copy of this script with given options.""" + cmd = [sys.executable, sys.argv[0], '--keep=none', + '-j%d' % self.parallelism] + cmd.extend(opts) + cmd.extend([self.topdir, action]) + subprocess.run(cmd, check=True) + class Config(object): """A configuration for building a compiler and associated libraries.""" @@ -1312,8 +1464,8 @@ def get_parser(): help='Toplevel working directory') parser.add_argument('action', help='What to do', - choices=('checkout', 'host-libraries', 'compilers', - 'glibcs')) + choices=('checkout', 'bot-cycle', 'host-libraries', + 'compilers', 'glibcs')) parser.add_argument('configs', help='Versions to check out or configurations to build', nargs='*') |