summaryrefslogtreecommitdiff
path: root/scripts/build-many-glibcs.py
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/build-many-glibcs.py')
-rwxr-xr-xscripts/build-many-glibcs.py1676
1 files changed, 1676 insertions, 0 deletions
diff --git a/scripts/build-many-glibcs.py b/scripts/build-many-glibcs.py
new file mode 100755
index 0000000000..8ef463f57a
--- /dev/null
+++ b/scripts/build-many-glibcs.py
@@ -0,0 +1,1676 @@
+#!/usr/bin/python3
+# Build many configurations of glibc.
+# Copyright (C) 2016-2018 Free Software Foundation, Inc.
+# This file is part of the GNU C Library.
+#
+# The GNU C Library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# The GNU C Library 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with the GNU C Library; if not, see
+# <http://www.gnu.org/licenses/>.
+
+"""Build many configurations of glibc.
+
+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, 'bot-cycle', to run a series of checkout and build steps,
+'bot', to run 'bot-cycle' repeatedly, '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
+import time
+import urllib.request
+
+try:
+ os.cpu_count
+except:
+ import multiprocessing
+ os.cpu_count = lambda: multiprocessing.cpu_count()
+
+try:
+ re.fullmatch
+except:
+ re.fullmatch = lambda p,s,f=0: re.match(p+"\\Z",s,f)
+
+try:
+ subprocess.run
+except:
+ class _CompletedProcess:
+ def __init__(self, args, returncode, stdout=None, stderr=None):
+ self.args = args
+ self.returncode = returncode
+ self.stdout = stdout
+ self.stderr = stderr
+
+ def _run(*popenargs, input=None, timeout=None, check=False, **kwargs):
+ assert(timeout is None)
+ with subprocess.Popen(*popenargs, **kwargs) as process:
+ try:
+ stdout, stderr = process.communicate(input)
+ except:
+ process.kill()
+ process.wait()
+ raise
+ returncode = process.poll()
+ if check and returncode:
+ raise subprocess.CalledProcessError(returncode, popenargs)
+ return _CompletedProcess(popenargs, returncode, stdout, stderr)
+
+ subprocess.run = _run
+
+
+class Context(object):
+ """The global state associated with builds in a given directory."""
+
+ def __init__(self, topdir, parallelism, keep, replace_sources, strip,
+ action):
+ """Initialize the context."""
+ self.topdir = topdir
+ self.parallelism = parallelism
+ self.keep = keep
+ self.replace_sources = replace_sources
+ self.strip = strip
+ 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')
+ self.builddir = os.path.join(topdir, 'build')
+ self.logsdir = os.path.join(topdir, 'logs')
+ self.logsdir_old = os.path.join(topdir, 'logs-old')
+ self.makefile = os.path.join(self.builddir, 'Makefile')
+ self.wrapper = os.path.join(self.builddir, 'wrapper')
+ self.save_logs = os.path.join(self.builddir, 'save-logs')
+ self.script_text = self.get_script_text()
+ if action != 'checkout':
+ self.build_triplet = self.get_build_triplet()
+ self.glibc_version = self.get_glibc_version()
+ self.configs = {}
+ self.glibc_configs = {}
+ self.makefile_pieces = ['.PHONY: all\n']
+ self.add_all_configs()
+ self.load_versions_json()
+ self.load_build_state_json()
+ self.status_log_list = []
+ self.email_warning = False
+
+ def get_script_text(self):
+ """Return the text of this script."""
+ with open(sys.argv[0], 'r') as f:
+ return f.read()
+
+ def exec_self(self):
+ """Re-execute this script with the same arguments."""
+ sys.stdout.flush()
+ os.execv(sys.executable, [sys.executable] + sys.argv)
+
+ def get_build_triplet(self):
+ """Determine the build triplet with config.guess."""
+ config_guess = os.path.join(self.component_srcdir('gcc'),
+ 'config.guess')
+ cg_out = subprocess.run([config_guess], stdout=subprocess.PIPE,
+ check=True, universal_newlines=True).stdout
+ return cg_out.rstrip()
+
+ def get_glibc_version(self):
+ """Determine the glibc version number (major.minor)."""
+ version_h = os.path.join(self.component_srcdir('glibc'), 'version.h')
+ with open(version_h, 'r') as f:
+ lines = f.readlines()
+ starttext = '#define VERSION "'
+ for l in lines:
+ if l.startswith(starttext):
+ l = l[len(starttext):]
+ l = l.rstrip('"\n')
+ m = re.fullmatch('([0-9]+)\.([0-9]+)[.0-9]*', l)
+ return '%s.%s' % m.group(1, 2)
+ print('error: could not determine glibc version')
+ exit(1)
+
+ def add_all_configs(self):
+ """Add all known glibc build configurations."""
+ self.add_config(arch='aarch64',
+ os_name='linux-gnu',
+ extra_glibcs=[{'variant': 'disable-multi-arch',
+ 'cfg': ['--disable-multi-arch']}])
+ self.add_config(arch='aarch64_be',
+ os_name='linux-gnu')
+ self.add_config(arch='alpha',
+ os_name='linux-gnu')
+ self.add_config(arch='arm',
+ os_name='linux-gnueabi')
+ self.add_config(arch='armeb',
+ os_name='linux-gnueabi')
+ self.add_config(arch='armeb',
+ os_name='linux-gnueabi',
+ variant='be8',
+ gcc_cfg=['--with-arch=armv7-a'])
+ self.add_config(arch='arm',
+ os_name='linux-gnueabihf',
+ gcc_cfg=['--with-float=hard', '--with-cpu=arm926ej-s'],
+ extra_glibcs=[{'variant': 'v7a',
+ 'ccopts': '-march=armv7-a -mfpu=vfpv3'},
+ {'variant': 'v7a-disable-multi-arch',
+ 'ccopts': '-march=armv7-a -mfpu=vfpv3',
+ 'cfg': ['--disable-multi-arch']}])
+ self.add_config(arch='armeb',
+ os_name='linux-gnueabihf',
+ gcc_cfg=['--with-float=hard', '--with-cpu=arm926ej-s'])
+ self.add_config(arch='armeb',
+ os_name='linux-gnueabihf',
+ variant='be8',
+ gcc_cfg=['--with-float=hard', '--with-arch=armv7-a',
+ '--with-fpu=vfpv3'])
+ self.add_config(arch='hppa',
+ os_name='linux-gnu')
+ self.add_config(arch='i686',
+ os_name='gnu')
+ self.add_config(arch='ia64',
+ os_name='linux-gnu',
+ first_gcc_cfg=['--with-system-libunwind'])
+ self.add_config(arch='m68k',
+ os_name='linux-gnu',
+ gcc_cfg=['--disable-multilib'])
+ self.add_config(arch='m68k',
+ os_name='linux-gnu',
+ variant='coldfire',
+ gcc_cfg=['--with-arch=cf', '--disable-multilib'])
+ self.add_config(arch='m68k',
+ os_name='linux-gnu',
+ variant='coldfire-soft',
+ gcc_cfg=['--with-arch=cf', '--with-cpu=54455',
+ '--disable-multilib'])
+ self.add_config(arch='microblaze',
+ os_name='linux-gnu',
+ gcc_cfg=['--disable-multilib'])
+ self.add_config(arch='microblazeel',
+ os_name='linux-gnu',
+ gcc_cfg=['--disable-multilib'])
+ self.add_config(arch='mips64',
+ os_name='linux-gnu',
+ gcc_cfg=['--with-mips-plt'],
+ glibcs=[{'variant': 'n32'},
+ {'arch': 'mips',
+ 'ccopts': '-mabi=32'},
+ {'variant': 'n64',
+ 'ccopts': '-mabi=64'}])
+ self.add_config(arch='mips64',
+ os_name='linux-gnu',
+ variant='soft',
+ gcc_cfg=['--with-mips-plt', '--with-float=soft'],
+ glibcs=[{'variant': 'n32-soft'},
+ {'variant': 'soft',
+ 'arch': 'mips',
+ 'ccopts': '-mabi=32'},
+ {'variant': 'n64-soft',
+ 'ccopts': '-mabi=64'}])
+ self.add_config(arch='mips64',
+ os_name='linux-gnu',
+ variant='nan2008',
+ gcc_cfg=['--with-mips-plt', '--with-nan=2008',
+ '--with-arch-64=mips64r2',
+ '--with-arch-32=mips32r2'],
+ glibcs=[{'variant': 'n32-nan2008'},
+ {'variant': 'nan2008',
+ 'arch': 'mips',
+ 'ccopts': '-mabi=32'},
+ {'variant': 'n64-nan2008',
+ 'ccopts': '-mabi=64'}])
+ self.add_config(arch='mips64',
+ os_name='linux-gnu',
+ variant='nan2008-soft',
+ gcc_cfg=['--with-mips-plt', '--with-nan=2008',
+ '--with-arch-64=mips64r2',
+ '--with-arch-32=mips32r2',
+ '--with-float=soft'],
+ glibcs=[{'variant': 'n32-nan2008-soft'},
+ {'variant': 'nan2008-soft',
+ 'arch': 'mips',
+ 'ccopts': '-mabi=32'},
+ {'variant': 'n64-nan2008-soft',
+ 'ccopts': '-mabi=64'}])
+ self.add_config(arch='mips64el',
+ os_name='linux-gnu',
+ gcc_cfg=['--with-mips-plt'],
+ glibcs=[{'variant': 'n32'},
+ {'arch': 'mipsel',
+ 'ccopts': '-mabi=32'},
+ {'variant': 'n64',
+ 'ccopts': '-mabi=64'}])
+ self.add_config(arch='mips64el',
+ os_name='linux-gnu',
+ variant='soft',
+ gcc_cfg=['--with-mips-plt', '--with-float=soft'],
+ glibcs=[{'variant': 'n32-soft'},
+ {'variant': 'soft',
+ 'arch': 'mipsel',
+ 'ccopts': '-mabi=32'},
+ {'variant': 'n64-soft',
+ 'ccopts': '-mabi=64'}])
+ self.add_config(arch='mips64el',
+ os_name='linux-gnu',
+ variant='nan2008',
+ gcc_cfg=['--with-mips-plt', '--with-nan=2008',
+ '--with-arch-64=mips64r2',
+ '--with-arch-32=mips32r2'],
+ glibcs=[{'variant': 'n32-nan2008'},
+ {'variant': 'nan2008',
+ 'arch': 'mipsel',
+ 'ccopts': '-mabi=32'},
+ {'variant': 'n64-nan2008',
+ 'ccopts': '-mabi=64'}])
+ self.add_config(arch='mips64el',
+ os_name='linux-gnu',
+ variant='nan2008-soft',
+ gcc_cfg=['--with-mips-plt', '--with-nan=2008',
+ '--with-arch-64=mips64r2',
+ '--with-arch-32=mips32r2',
+ '--with-float=soft'],
+ glibcs=[{'variant': 'n32-nan2008-soft'},
+ {'variant': 'nan2008-soft',
+ 'arch': 'mipsel',
+ 'ccopts': '-mabi=32'},
+ {'variant': 'n64-nan2008-soft',
+ 'ccopts': '-mabi=64'}])
+ self.add_config(arch='nios2',
+ os_name='linux-gnu')
+ self.add_config(arch='powerpc',
+ os_name='linux-gnu',
+ gcc_cfg=['--disable-multilib', '--enable-secureplt'],
+ extra_glibcs=[{'variant': 'power4',
+ 'ccopts': '-mcpu=power4',
+ 'cfg': ['--with-cpu=power4']}])
+ self.add_config(arch='powerpc',
+ os_name='linux-gnu',
+ variant='soft',
+ gcc_cfg=['--disable-multilib', '--with-float=soft',
+ '--enable-secureplt'])
+ self.add_config(arch='powerpc64',
+ os_name='linux-gnu',
+ gcc_cfg=['--disable-multilib', '--enable-secureplt'])
+ self.add_config(arch='powerpc64le',
+ os_name='linux-gnu',
+ gcc_cfg=['--disable-multilib', '--enable-secureplt'])
+ self.add_config(arch='powerpc',
+ os_name='linux-gnuspe',
+ gcc_cfg=['--disable-multilib', '--enable-secureplt',
+ '--enable-e500-double', '--enable-obsolete'])
+ self.add_config(arch='powerpc',
+ os_name='linux-gnuspe',
+ variant='e500v1',
+ gcc_cfg=['--disable-multilib', '--enable-secureplt',
+ '--enable-obsolete'])
+ self.add_config(arch='riscv64',
+ os_name='linux-gnu',
+ variant='rv64imac-lp64',
+ gcc_cfg=['--with-arch=rv64imac', '--with-abi=lp64',
+ '--disable-multilib'])
+ self.add_config(arch='riscv64',
+ os_name='linux-gnu',
+ variant='rv64imafdc-lp64',
+ gcc_cfg=['--with-arch=rv64imafdc', '--with-abi=lp64',
+ '--disable-multilib'])
+ self.add_config(arch='riscv64',
+ os_name='linux-gnu',
+ variant='rv64imafdc-lp64d',
+ gcc_cfg=['--with-arch=rv64imafdc', '--with-abi=lp64d',
+ '--disable-multilib'])
+ self.add_config(arch='s390x',
+ os_name='linux-gnu',
+ glibcs=[{},
+ {'arch': 's390', 'ccopts': '-m31'}])
+ self.add_config(arch='sh3',
+ os_name='linux-gnu')
+ self.add_config(arch='sh3eb',
+ os_name='linux-gnu')
+ self.add_config(arch='sh4',
+ os_name='linux-gnu')
+ self.add_config(arch='sh4eb',
+ os_name='linux-gnu')
+ self.add_config(arch='sh4',
+ os_name='linux-gnu',
+ variant='soft',
+ gcc_cfg=['--without-fp'])
+ self.add_config(arch='sh4eb',
+ os_name='linux-gnu',
+ variant='soft',
+ gcc_cfg=['--without-fp'])
+ self.add_config(arch='sparc64',
+ os_name='linux-gnu',
+ glibcs=[{},
+ {'arch': 'sparcv9',
+ 'ccopts': '-m32 -mlong-double-128'}],
+ extra_glibcs=[{'variant': 'disable-multi-arch',
+ 'cfg': ['--disable-multi-arch']},
+ {'variant': 'disable-multi-arch',
+ 'arch': 'sparcv9',
+ 'ccopts': '-m32 -mlong-double-128',
+ 'cfg': ['--disable-multi-arch']}])
+ self.add_config(arch='x86_64',
+ os_name='linux-gnu',
+ gcc_cfg=['--with-multilib-list=m64,m32,mx32'],
+ glibcs=[{},
+ {'variant': 'x32', 'ccopts': '-mx32'},
+ {'arch': 'i686', 'ccopts': '-m32 -march=i686'}],
+ extra_glibcs=[{'variant': 'disable-multi-arch',
+ 'cfg': ['--disable-multi-arch']},
+ {'variant': 'static-pie',
+ 'cfg': ['--enable-static-pie']},
+ {'variant': 'x32-static-pie',
+ 'ccopts': '-mx32',
+ 'cfg': ['--enable-static-pie']},
+ {'variant': 'static-pie',
+ 'arch': 'i686',
+ 'ccopts': '-m32 -march=i686',
+ 'cfg': ['--enable-static-pie']},
+ {'variant': 'disable-multi-arch',
+ 'arch': 'i686',
+ 'ccopts': '-m32 -march=i686',
+ 'cfg': ['--disable-multi-arch']},
+ {'arch': 'i486',
+ 'ccopts': '-m32 -march=i486'},
+ {'arch': 'i586',
+ 'ccopts': '-m32 -march=i586'}])
+
+ def add_config(self, **args):
+ """Add an individual build configuration."""
+ cfg = Config(self, **args)
+ if cfg.name in self.configs:
+ print('error: duplicate config %s' % cfg.name)
+ exit(1)
+ self.configs[cfg.name] = cfg
+ for c in cfg.all_glibcs:
+ if c.name in self.glibc_configs:
+ print('error: duplicate glibc config %s' % c.name)
+ exit(1)
+ self.glibc_configs[c.name] = c
+
+ def component_srcdir(self, component):
+ """Return the source directory for a given component, e.g. gcc."""
+ return os.path.join(self.srcdir, component)
+
+ def component_builddir(self, action, config, component, subconfig=None):
+ """Return the directory to use for a build."""
+ if config is None:
+ # Host libraries.
+ assert subconfig is None
+ return os.path.join(self.builddir, action, component)
+ if subconfig is None:
+ return os.path.join(self.builddir, action, config, component)
+ else:
+ # glibc build as part of compiler build.
+ return os.path.join(self.builddir, action, config, component,
+ subconfig)
+
+ def compiler_installdir(self, config):
+ """Return the directory in which to install a compiler."""
+ return os.path.join(self.installdir, 'compilers', config)
+
+ def compiler_bindir(self, config):
+ """Return the directory in which to find compiler binaries."""
+ return os.path.join(self.compiler_installdir(config), 'bin')
+
+ def compiler_sysroot(self, config):
+ """Return the sysroot directory for a compiler."""
+ return os.path.join(self.compiler_installdir(config), 'sysroot')
+
+ def glibc_installdir(self, config):
+ """Return the directory in which to install glibc."""
+ return os.path.join(self.installdir, 'glibcs', config)
+
+ def run_builds(self, action, configs):
+ """Run the requested builds."""
+ 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 == 'bot':
+ if configs:
+ print('error: configurations specified for bot')
+ exit(1)
+ self.bot()
+ return
+ if action == 'host-libraries' and configs:
+ print('error: configurations specified for host-libraries')
+ exit(1)
+ self.clear_last_build_state(action)
+ build_time = datetime.datetime.utcnow()
+ if action == 'host-libraries':
+ build_components = ('gmp', 'mpfr', 'mpc')
+ old_components = ()
+ old_versions = {}
+ self.build_host_libraries()
+ elif action == 'compilers':
+ build_components = ('binutils', 'gcc', 'glibc', 'linux', 'mig',
+ 'gnumach', 'hurd')
+ old_components = ('gmp', 'mpfr', 'mpc')
+ old_versions = self.build_state['host-libraries']['build-versions']
+ self.build_compilers(configs)
+ else:
+ build_components = ('glibc',)
+ old_components = ('gmp', 'mpfr', 'mpc', 'binutils', 'gcc', 'linux',
+ 'mig', 'gnumach', 'hurd')
+ old_versions = self.build_state['compilers']['build-versions']
+ self.build_glibcs(configs)
+ self.write_files()
+ self.do_build()
+ if configs:
+ # Partial build, do not update stored state.
+ return
+ build_versions = {}
+ for k in build_components:
+ if k in self.versions:
+ build_versions[k] = {'version': self.versions[k]['version'],
+ 'revision': self.versions[k]['revision']}
+ for k in old_components:
+ if k in old_versions:
+ build_versions[k] = {'version': old_versions[k]['version'],
+ 'revision': old_versions[k]['revision']}
+ self.update_build_state(action, build_time, build_versions)
+
+ @staticmethod
+ def remove_dirs(*args):
+ """Remove directories and their contents if they exist."""
+ for dir in args:
+ shutil.rmtree(dir, ignore_errors=True)
+
+ @staticmethod
+ def remove_recreate_dirs(*args):
+ """Remove directories if they exist, and create them as empty."""
+ Context.remove_dirs(*args)
+ for dir in args:
+ os.makedirs(dir, exist_ok=True)
+
+ def add_makefile_cmdlist(self, target, cmdlist, logsdir):
+ """Add makefile text for a list of commands."""
+ commands = cmdlist.makefile_commands(self.wrapper, logsdir)
+ self.makefile_pieces.append('all: %s\n.PHONY: %s\n%s:\n%s\n' %
+ (target, target, target, commands))
+ self.status_log_list.extend(cmdlist.status_logs(logsdir))
+
+ def write_files(self):
+ """Write out the Makefile and wrapper script."""
+ mftext = ''.join(self.makefile_pieces)
+ with open(self.makefile, 'w') as f:
+ f.write(mftext)
+ wrapper_text = (
+ '#!/bin/sh\n'
+ 'prev_base=$1\n'
+ 'this_base=$2\n'
+ 'desc=$3\n'
+ 'dir=$4\n'
+ 'path=$5\n'
+ 'shift 5\n'
+ 'prev_status=$prev_base-status.txt\n'
+ 'this_status=$this_base-status.txt\n'
+ 'this_log=$this_base-log.txt\n'
+ 'date > "$this_log"\n'
+ 'echo >> "$this_log"\n'
+ 'echo "Description: $desc" >> "$this_log"\n'
+ 'printf "%s" "Command:" >> "$this_log"\n'
+ 'for word in "$@"; do\n'
+ ' if expr "$word" : "[]+,./0-9@A-Z_a-z-]\\\\{1,\\\\}\\$" > /dev/null; then\n'
+ ' printf " %s" "$word"\n'
+ ' else\n'
+ ' printf " \'"\n'
+ ' printf "%s" "$word" | sed -e "s/\'/\'\\\\\\\\\'\'/"\n'
+ ' printf "\'"\n'
+ ' fi\n'
+ 'done >> "$this_log"\n'
+ 'echo >> "$this_log"\n'
+ 'echo "Directory: $dir" >> "$this_log"\n'
+ 'echo "Path addition: $path" >> "$this_log"\n'
+ 'echo >> "$this_log"\n'
+ 'record_status ()\n'
+ '{\n'
+ ' echo >> "$this_log"\n'
+ ' echo "$1: $desc" > "$this_status"\n'
+ ' echo "$1: $desc" >> "$this_log"\n'
+ ' echo >> "$this_log"\n'
+ ' date >> "$this_log"\n'
+ ' echo "$1: $desc"\n'
+ ' exit 0\n'
+ '}\n'
+ 'check_error ()\n'
+ '{\n'
+ ' if [ "$1" != "0" ]; then\n'
+ ' record_status FAIL\n'
+ ' fi\n'
+ '}\n'
+ 'if [ "$prev_base" ] && ! grep -q "^PASS" "$prev_status"; then\n'
+ ' record_status UNRESOLVED\n'
+ 'fi\n'
+ 'if [ "$dir" ]; then\n'
+ ' cd "$dir"\n'
+ ' check_error "$?"\n'
+ 'fi\n'
+ 'if [ "$path" ]; then\n'
+ ' PATH=$path:$PATH\n'
+ 'fi\n'
+ '"$@" < /dev/null >> "$this_log" 2>&1\n'
+ 'check_error "$?"\n'
+ 'record_status PASS\n')
+ with open(self.wrapper, 'w') as f:
+ f.write(wrapper_text)
+ # Mode 0o755.
+ mode_exec = (stat.S_IRWXU|stat.S_IRGRP|stat.S_IXGRP|
+ stat.S_IROTH|stat.S_IXOTH)
+ os.chmod(self.wrapper, mode_exec)
+ save_logs_text = (
+ '#!/bin/sh\n'
+ 'if ! [ -f tests.sum ]; then\n'
+ ' echo "No test summary available."\n'
+ ' exit 0\n'
+ 'fi\n'
+ 'save_file ()\n'
+ '{\n'
+ ' echo "Contents of $1:"\n'
+ ' echo\n'
+ ' cat "$1"\n'
+ ' echo\n'
+ ' echo "End of contents of $1."\n'
+ ' echo\n'
+ '}\n'
+ 'save_file tests.sum\n'
+ 'non_pass_tests=$(grep -v "^PASS: " tests.sum | sed -e "s/^PASS: //")\n'
+ 'for t in $non_pass_tests; do\n'
+ ' if [ -f "$t.out" ]; then\n'
+ ' save_file "$t.out"\n'
+ ' fi\n'
+ 'done\n')
+ with open(self.save_logs, 'w') as f:
+ f.write(save_logs_text)
+ os.chmod(self.save_logs, mode_exec)
+
+ def do_build(self):
+ """Do the actual build."""
+ cmd = ['make', '-j%d' % self.parallelism]
+ subprocess.run(cmd, cwd=self.builddir, check=True)
+
+ def build_host_libraries(self):
+ """Build the host libraries."""
+ installdir = self.host_libraries_installdir
+ builddir = os.path.join(self.builddir, 'host-libraries')
+ logsdir = os.path.join(self.logsdir, 'host-libraries')
+ self.remove_recreate_dirs(installdir, builddir, logsdir)
+ cmdlist = CommandList('host-libraries', self.keep)
+ self.build_host_library(cmdlist, 'gmp')
+ self.build_host_library(cmdlist, 'mpfr',
+ ['--with-gmp=%s' % installdir])
+ self.build_host_library(cmdlist, 'mpc',
+ ['--with-gmp=%s' % installdir,
+ '--with-mpfr=%s' % installdir])
+ cmdlist.add_command('done', ['touch', os.path.join(installdir, 'ok')])
+ self.add_makefile_cmdlist('host-libraries', cmdlist, logsdir)
+
+ def build_host_library(self, cmdlist, lib, extra_opts=None):
+ """Build one host library."""
+ srcdir = self.component_srcdir(lib)
+ builddir = self.component_builddir('host-libraries', None, lib)
+ installdir = self.host_libraries_installdir
+ cmdlist.push_subdesc(lib)
+ cmdlist.create_use_dir(builddir)
+ cfg_cmd = [os.path.join(srcdir, 'configure'),
+ '--prefix=%s' % installdir,
+ '--disable-shared']
+ if extra_opts:
+ cfg_cmd.extend (extra_opts)
+ cmdlist.add_command('configure', cfg_cmd)
+ cmdlist.add_command('build', ['make'])
+ cmdlist.add_command('check', ['make', 'check'])
+ cmdlist.add_command('install', ['make', 'install'])
+ cmdlist.cleanup_dir()
+ cmdlist.pop_subdesc()
+
+ def build_compilers(self, configs):
+ """Build the compilers."""
+ if not configs:
+ self.remove_dirs(os.path.join(self.builddir, 'compilers'))
+ self.remove_dirs(os.path.join(self.installdir, 'compilers'))
+ self.remove_dirs(os.path.join(self.logsdir, 'compilers'))
+ configs = sorted(self.configs.keys())
+ for c in configs:
+ self.configs[c].build()
+
+ def build_glibcs(self, configs):
+ """Build the glibcs."""
+ if not configs:
+ self.remove_dirs(os.path.join(self.builddir, 'glibcs'))
+ self.remove_dirs(os.path.join(self.installdir, 'glibcs'))
+ self.remove_dirs(os.path.join(self.logsdir, 'glibcs'))
+ configs = sorted(self.glibc_configs.keys())
+ for c in configs:
+ self.glibc_configs[c].build()
+
+ def load_versions_json(self):
+ """Load information about source directory versions."""
+ if not os.access(self.versions_json, os.F_OK):
+ self.versions = {}
+ return
+ with open(self.versions_json, 'r') as f:
+ self.versions = json.load(f)
+
+ def store_json(self, data, filename):
+ """Store information in a JSON file."""
+ filename_tmp = filename + '.tmp'
+ with open(filename_tmp, 'w') as f:
+ json.dump(data, f, indent=2, sort_keys=True)
+ os.rename(filename_tmp, filename)
+
+ def store_versions_json(self):
+ """Store information about source directory versions."""
+ self.store_json(self.versions, self.versions_json)
+
+ def set_component_version(self, component, version, explicit, revision):
+ """Set the version information for a component."""
+ self.versions[component] = {'version': version,
+ 'explicit': explicit,
+ 'revision': revision}
+ self.store_versions_json()
+
+ def checkout(self, versions):
+ """Check out the desired component versions."""
+ default_versions = {'binutils': 'vcs-2.31',
+ 'gcc': 'vcs-8',
+ 'glibc': 'vcs-mainline',
+ 'gmp': '6.1.2',
+ 'linux': '4.17',
+ 'mpc': '1.1.0',
+ 'mpfr': '4.0.1',
+ 'mig': 'vcs-mainline',
+ 'gnumach': 'vcs-mainline',
+ 'hurd': 'vcs-mainline'}
+ use_versions = {}
+ explicit_versions = {}
+ for v in versions:
+ found_v = False
+ for k in default_versions.keys():
+ kx = k + '-'
+ if v.startswith(kx):
+ vx = v[len(kx):]
+ if k in use_versions:
+ print('error: multiple versions for %s' % k)
+ exit(1)
+ use_versions[k] = vx
+ explicit_versions[k] = True
+ found_v = True
+ break
+ if not found_v:
+ print('error: unknown component in %s' % v)
+ exit(1)
+ for k in default_versions.keys():
+ if k not in use_versions:
+ if k in self.versions and self.versions[k]['explicit']:
+ use_versions[k] = self.versions[k]['version']
+ explicit_versions[k] = True
+ else:
+ use_versions[k] = default_versions[k]
+ explicit_versions[k] = False
+ os.makedirs(self.srcdir, exist_ok=True)
+ for k in sorted(default_versions.keys()):
+ update = os.access(self.component_srcdir(k), os.F_OK)
+ v = use_versions[k]
+ if (update and
+ k in self.versions and
+ v != self.versions[k]['version']):
+ if not self.replace_sources:
+ print('error: version of %s has changed from %s to %s, '
+ 'use --replace-sources to check out again' %
+ (k, self.versions[k]['version'], v))
+ exit(1)
+ shutil.rmtree(self.component_srcdir(k))
+ update = False
+ if v.startswith('vcs-'):
+ revision = self.checkout_vcs(k, v[4:], update)
+ else:
+ self.checkout_tar(k, v, update)
+ revision = v
+ self.set_component_version(k, v, explicit_versions[k], revision)
+ if self.get_script_text() != self.script_text:
+ # Rerun the checkout process in case the updated script
+ # uses different default versions or new components.
+ self.exec_self()
+
+ def checkout_vcs(self, component, version, update):
+ """Check out the given version of the given component from version
+ control. Return a revision identifier."""
+ if component == 'binutils':
+ git_url = 'git://sourceware.org/git/binutils-gdb.git'
+ if version == 'mainline':
+ git_branch = 'master'
+ else:
+ trans = str.maketrans({'.': '_'})
+ git_branch = 'binutils-%s-branch' % version.translate(trans)
+ return self.git_checkout(component, git_url, git_branch, update)
+ elif component == 'gcc':
+ if version == 'mainline':
+ branch = 'trunk'
+ else:
+ trans = str.maketrans({'.': '_'})
+ branch = 'branches/gcc-%s-branch' % version.translate(trans)
+ svn_url = 'svn://gcc.gnu.org/svn/gcc/%s' % branch
+ return self.gcc_checkout(svn_url, update)
+ elif component == 'glibc':
+ git_url = 'git://sourceware.org/git/glibc.git'
+ if version == 'mainline':
+ git_branch = 'master'
+ else:
+ git_branch = 'release/%s/master' % version
+ r = self.git_checkout(component, git_url, git_branch, update)
+ self.fix_glibc_timestamps()
+ return r
+ elif component == 'gnumach':
+ git_url = 'git://git.savannah.gnu.org/hurd/gnumach.git'
+ git_branch = 'master'
+ r = self.git_checkout(component, git_url, git_branch, update)
+ subprocess.run(['autoreconf', '-i'],
+ cwd=self.component_srcdir(component), check=True)
+ return r
+ elif component == 'mig':
+ git_url = 'git://git.savannah.gnu.org/hurd/mig.git'
+ git_branch = 'master'
+ r = self.git_checkout(component, git_url, git_branch, update)
+ subprocess.run(['autoreconf', '-i'],
+ cwd=self.component_srcdir(component), check=True)
+ return r
+ elif component == 'hurd':
+ git_url = 'git://git.savannah.gnu.org/hurd/hurd.git'
+ git_branch = 'master'
+ r = self.git_checkout(component, git_url, git_branch, update)
+ subprocess.run(['autoconf'],
+ cwd=self.component_srcdir(component), check=True)
+ return r
+ else:
+ print('error: component %s coming from VCS' % component)
+ exit(1)
+
+ def git_checkout(self, component, git_url, git_branch, update):
+ """Check out a component from git. Return a commit identifier."""
+ if update:
+ subprocess.run(['git', 'remote', 'prune', 'origin'],
+ cwd=self.component_srcdir(component), check=True)
+ if self.replace_sources:
+ subprocess.run(['git', 'clean', '-dxfq'],
+ cwd=self.component_srcdir(component), check=True)
+ subprocess.run(['git', 'pull', '-q'],
+ cwd=self.component_srcdir(component), check=True)
+ else:
+ subprocess.run(['git', 'clone', '-q', '-b', git_branch, git_url,
+ self.component_srcdir(component)], check=True)
+ r = subprocess.run(['git', 'rev-parse', 'HEAD'],
+ cwd=self.component_srcdir(component),
+ stdout=subprocess.PIPE,
+ check=True, universal_newlines=True).stdout
+ return r.rstrip()
+
+ def fix_glibc_timestamps(self):
+ """Fix timestamps in a glibc checkout."""
+ # Ensure that builds do not try to regenerate generated files
+ # in the source tree.
+ srcdir = self.component_srcdir('glibc')
+ for dirpath, dirnames, filenames in os.walk(srcdir):
+ for f in filenames:
+ if (f == 'configure' or
+ f == 'preconfigure' or
+ f.endswith('-kw.h')):
+ to_touch = os.path.join(dirpath, f)
+ subprocess.run(['touch', to_touch], check=True)
+
+ def gcc_checkout(self, svn_url, update):
+ """Check out GCC from SVN. Return the revision number."""
+ if not update:
+ subprocess.run(['svn', 'co', '-q', svn_url,
+ self.component_srcdir('gcc')], check=True)
+ subprocess.run(['contrib/gcc_update', '--silent'],
+ cwd=self.component_srcdir('gcc'), check=True)
+ r = subprocess.run(['svnversion', self.component_srcdir('gcc')],
+ stdout=subprocess.PIPE,
+ check=True, universal_newlines=True).stdout
+ return r.rstrip()
+
+ def checkout_tar(self, component, version, update):
+ """Check out the given version of the given component from a
+ tarball."""
+ if update:
+ return
+ url_map = {'binutils': 'https://ftp.gnu.org/gnu/binutils/binutils-%(version)s.tar.bz2',
+ 'gcc': 'https://ftp.gnu.org/gnu/gcc/gcc-%(version)s/gcc-%(version)s.tar.gz',
+ 'gmp': 'https://ftp.gnu.org/gnu/gmp/gmp-%(version)s.tar.xz',
+ 'linux': 'https://www.kernel.org/pub/linux/kernel/v4.x/linux-%(version)s.tar.xz',
+ 'mpc': 'https://ftp.gnu.org/gnu/mpc/mpc-%(version)s.tar.gz',
+ 'mpfr': 'https://ftp.gnu.org/gnu/mpfr/mpfr-%(version)s.tar.xz',
+ 'mig': 'https://ftp.gnu.org/gnu/mig/mig-%(version)s.tar.bz2',
+ 'gnumach': 'https://ftp.gnu.org/gnu/gnumach/gnumach-%(version)s.tar.bz2',
+ 'hurd': 'https://ftp.gnu.org/gnu/hurd/hurd-%(version)s.tar.bz2'}
+ if component not in url_map:
+ print('error: component %s coming from tarball' % component)
+ exit(1)
+ url = url_map[component] % {'version': version}
+ filename = os.path.join(self.srcdir, url.split('/')[-1])
+ response = urllib.request.urlopen(url)
+ data = response.read()
+ with open(filename, 'wb') as f:
+ f.write(data)
+ subprocess.run(['tar', '-C', self.srcdir, '-x', '-f', filename],
+ check=True)
+ os.rename(os.path.join(self.srcdir, '%s-%s' % (component, version)),
+ self.component_srcdir(component))
+ os.remove(filename)
+
+ def load_build_state_json(self):
+ """Load information about the state of previous builds."""
+ if os.access(self.build_state_json, os.F_OK):
+ with open(self.build_state_json, 'r') as f:
+ self.build_state = json.load(f)
+ else:
+ self.build_state = {}
+ for k in ('host-libraries', 'compilers', 'glibcs'):
+ if k not in self.build_state:
+ self.build_state[k] = {}
+ if 'build-time' not in self.build_state[k]:
+ self.build_state[k]['build-time'] = ''
+ if 'build-versions' not in self.build_state[k]:
+ self.build_state[k]['build-versions'] = {}
+ if 'build-results' not in self.build_state[k]:
+ self.build_state[k]['build-results'] = {}
+ if 'result-changes' not in self.build_state[k]:
+ self.build_state[k]['result-changes'] = {}
+ if 'ever-passed' not in self.build_state[k]:
+ self.build_state[k]['ever-passed'] = []
+
+ def store_build_state_json(self):
+ """Store information about the state of previous builds."""
+ self.store_json(self.build_state, self.build_state_json)
+
+ def clear_last_build_state(self, action):
+ """Clear information about the state of part of the build."""
+ # We clear the last build time and versions when starting a
+ # new build. The results of the last build are kept around,
+ # as comparison is still meaningful if this build is aborted
+ # and a new one started.
+ self.build_state[action]['build-time'] = ''
+ self.build_state[action]['build-versions'] = {}
+ self.store_build_state_json()
+
+ def update_build_state(self, action, build_time, build_versions):
+ """Update the build state after a build."""
+ build_time = build_time.replace(microsecond=0)
+ self.build_state[action]['build-time'] = str(build_time)
+ self.build_state[action]['build-versions'] = build_versions
+ build_results = {}
+ for log in self.status_log_list:
+ with open(log, 'r') as f:
+ log_text = f.read()
+ log_text = log_text.rstrip()
+ m = re.fullmatch('([A-Z]+): (.*)', log_text)
+ result = m.group(1)
+ test_name = m.group(2)
+ assert test_name not in build_results
+ build_results[test_name] = result
+ old_build_results = self.build_state[action]['build-results']
+ self.build_state[action]['build-results'] = build_results
+ result_changes = {}
+ all_tests = set(old_build_results.keys()) | set(build_results.keys())
+ for t in all_tests:
+ if t in old_build_results:
+ old_res = old_build_results[t]
+ else:
+ old_res = '(New test)'
+ if t in build_results:
+ new_res = build_results[t]
+ else:
+ new_res = '(Test removed)'
+ if old_res != new_res:
+ result_changes[t] = '%s -> %s' % (old_res, new_res)
+ self.build_state[action]['result-changes'] = result_changes
+ old_ever_passed = {t for t in self.build_state[action]['ever-passed']
+ if t in build_results}
+ new_passes = {t for t in build_results if build_results[t] == 'PASS'}
+ self.build_state[action]['ever-passed'] = sorted(old_ever_passed |
+ 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',
+ 'mig', 'gnumach', 'hurd'),
+ '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)
+ if os.access(self.logsdir, os.F_OK):
+ shutil.rmtree(self.logsdir_old, ignore_errors=True)
+ shutil.copytree(self.logsdir, self.logsdir_old)
+ 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."""
+ if not ('email-from' in self.bot_config and
+ 'email-server' in self.bot_config and
+ 'email-subject' in self.bot_config and
+ 'email-to' in self.bot_config):
+ if not self.email_warning:
+ print("Email not configured, not sending.")
+ self.email_warning = True
+ return
+
+ 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, check=True):
+ """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])
+ sys.stdout.flush()
+ subprocess.run(cmd, check=check)
+
+ def bot(self):
+ """Run repeated rounds of checkout and builds."""
+ while True:
+ self.load_bot_config_json()
+ if not self.bot_config['run']:
+ print('Bot exiting by request.')
+ exit(0)
+ self.bot_run_self([], 'bot-cycle', check=False)
+ self.load_bot_config_json()
+ if not self.bot_config['run']:
+ print('Bot exiting by request.')
+ exit(0)
+ time.sleep(self.bot_config['delay'])
+ if self.get_script_text() != self.script_text:
+ print('Script changed, bot re-execing.')
+ self.exec_self()
+
+
+class Config(object):
+ """A configuration for building a compiler and associated libraries."""
+
+ def __init__(self, ctx, arch, os_name, variant=None, gcc_cfg=None,
+ first_gcc_cfg=None, glibcs=None, extra_glibcs=None):
+ """Initialize a Config object."""
+ self.ctx = ctx
+ self.arch = arch
+ self.os = os_name
+ self.variant = variant
+ if variant is None:
+ self.name = '%s-%s' % (arch, os_name)
+ else:
+ self.name = '%s-%s-%s' % (arch, os_name, variant)
+ self.triplet = '%s-glibc-%s' % (arch, os_name)
+ if gcc_cfg is None:
+ self.gcc_cfg = []
+ else:
+ self.gcc_cfg = gcc_cfg
+ if first_gcc_cfg is None:
+ self.first_gcc_cfg = []
+ else:
+ self.first_gcc_cfg = first_gcc_cfg
+ if glibcs is None:
+ glibcs = [{'variant': variant}]
+ if extra_glibcs is None:
+ extra_glibcs = []
+ glibcs = [Glibc(self, **g) for g in glibcs]
+ extra_glibcs = [Glibc(self, **g) for g in extra_glibcs]
+ self.all_glibcs = glibcs + extra_glibcs
+ self.compiler_glibcs = glibcs
+ self.installdir = ctx.compiler_installdir(self.name)
+ self.bindir = ctx.compiler_bindir(self.name)
+ self.sysroot = ctx.compiler_sysroot(self.name)
+ self.builddir = os.path.join(ctx.builddir, 'compilers', self.name)
+ self.logsdir = os.path.join(ctx.logsdir, 'compilers', self.name)
+
+ def component_builddir(self, component):
+ """Return the directory to use for a (non-glibc) build."""
+ return self.ctx.component_builddir('compilers', self.name, component)
+
+ def build(self):
+ """Generate commands to build this compiler."""
+ self.ctx.remove_recreate_dirs(self.installdir, self.builddir,
+ self.logsdir)
+ cmdlist = CommandList('compilers-%s' % self.name, self.ctx.keep)
+ cmdlist.add_command('check-host-libraries',
+ ['test', '-f',
+ os.path.join(self.ctx.host_libraries_installdir,
+ 'ok')])
+ cmdlist.use_path(self.bindir)
+ self.build_cross_tool(cmdlist, 'binutils', 'binutils',
+ ['--disable-gdb',
+ '--disable-libdecnumber',
+ '--disable-readline',
+ '--disable-sim'])
+ if self.os.startswith('linux'):
+ self.install_linux_headers(cmdlist)
+ self.build_gcc(cmdlist, True)
+ if self.os == 'gnu':
+ self.install_gnumach_headers(cmdlist)
+ self.build_cross_tool(cmdlist, 'mig', 'mig')
+ self.install_hurd_headers(cmdlist)
+ for g in self.compiler_glibcs:
+ cmdlist.push_subdesc('glibc')
+ cmdlist.push_subdesc(g.name)
+ g.build_glibc(cmdlist, True)
+ cmdlist.pop_subdesc()
+ cmdlist.pop_subdesc()
+ self.build_gcc(cmdlist, False)
+ cmdlist.add_command('done', ['touch',
+ os.path.join(self.installdir, 'ok')])
+ self.ctx.add_makefile_cmdlist('compilers-%s' % self.name, cmdlist,
+ self.logsdir)
+
+ def build_cross_tool(self, cmdlist, tool_src, tool_build, extra_opts=None):
+ """Build one cross tool."""
+ srcdir = self.ctx.component_srcdir(tool_src)
+ builddir = self.component_builddir(tool_build)
+ cmdlist.push_subdesc(tool_build)
+ cmdlist.create_use_dir(builddir)
+ cfg_cmd = [os.path.join(srcdir, 'configure'),
+ '--prefix=%s' % self.installdir,
+ '--build=%s' % self.ctx.build_triplet,
+ '--host=%s' % self.ctx.build_triplet,
+ '--target=%s' % self.triplet,
+ '--with-sysroot=%s' % self.sysroot]
+ if extra_opts:
+ cfg_cmd.extend(extra_opts)
+ cmdlist.add_command('configure', cfg_cmd)
+ cmdlist.add_command('build', ['make'])
+ # Parallel "make install" for GCC has race conditions that can
+ # cause it to fail; see
+ # <https://gcc.gnu.org/bugzilla/show_bug.cgi?id=42980>. Such
+ # problems are not known for binutils, but doing the
+ # installation in parallel within a particular toolchain build
+ # (as opposed to installation of one toolchain from
+ # build-many-glibcs.py running in parallel to the installation
+ # of other toolchains being built) is not known to be
+ # significantly beneficial, so it is simplest just to disable
+ # parallel install for cross tools here.
+ cmdlist.add_command('install', ['make', '-j1', 'install'])
+ cmdlist.cleanup_dir()
+ cmdlist.pop_subdesc()
+
+ def install_linux_headers(self, cmdlist):
+ """Install Linux kernel headers."""
+ arch_map = {'aarch64': 'arm64',
+ 'alpha': 'alpha',
+ 'arm': 'arm',
+ 'hppa': 'parisc',
+ 'i486': 'x86',
+ 'i586': 'x86',
+ 'i686': 'x86',
+ 'i786': 'x86',
+ 'ia64': 'ia64',
+ 'm68k': 'm68k',
+ 'microblaze': 'microblaze',
+ 'mips': 'mips',
+ 'nios2': 'nios2',
+ 'powerpc': 'powerpc',
+ 's390': 's390',
+ 'riscv32': 'riscv',
+ 'riscv64': 'riscv',
+ 'sh': 'sh',
+ 'sparc': 'sparc',
+ 'x86_64': 'x86'}
+ linux_arch = None
+ for k in arch_map:
+ if self.arch.startswith(k):
+ linux_arch = arch_map[k]
+ break
+ assert linux_arch is not None
+ srcdir = self.ctx.component_srcdir('linux')
+ builddir = self.component_builddir('linux')
+ headers_dir = os.path.join(self.sysroot, 'usr')
+ cmdlist.push_subdesc('linux')
+ cmdlist.create_use_dir(builddir)
+ cmdlist.add_command('install-headers',
+ ['make', '-C', srcdir, 'O=%s' % builddir,
+ 'ARCH=%s' % linux_arch,
+ 'INSTALL_HDR_PATH=%s' % headers_dir,
+ 'headers_install'])
+ cmdlist.cleanup_dir()
+ cmdlist.pop_subdesc()
+
+ def install_gnumach_headers(self, cmdlist):
+ """Install GNU Mach headers."""
+ srcdir = self.ctx.component_srcdir('gnumach')
+ builddir = self.component_builddir('gnumach')
+ cmdlist.push_subdesc('gnumach')
+ cmdlist.create_use_dir(builddir)
+ cmdlist.add_command('configure',
+ [os.path.join(srcdir, 'configure'),
+ '--build=%s' % self.ctx.build_triplet,
+ '--host=%s' % self.triplet,
+ '--prefix=',
+ 'CC=%s-gcc -nostdlib' % self.triplet])
+ cmdlist.add_command('install', ['make', 'DESTDIR=%s' % self.sysroot,
+ 'install-data'])
+ cmdlist.cleanup_dir()
+ cmdlist.pop_subdesc()
+
+ def install_hurd_headers(self, cmdlist):
+ """Install Hurd headers."""
+ srcdir = self.ctx.component_srcdir('hurd')
+ builddir = self.component_builddir('hurd')
+ cmdlist.push_subdesc('hurd')
+ cmdlist.create_use_dir(builddir)
+ cmdlist.add_command('configure',
+ [os.path.join(srcdir, 'configure'),
+ '--build=%s' % self.ctx.build_triplet,
+ '--host=%s' % self.triplet,
+ '--prefix=',
+ '--disable-profile', '--without-parted',
+ 'CC=%s-gcc -nostdlib' % self.triplet])
+ cmdlist.add_command('install', ['make', 'prefix=%s' % self.sysroot,
+ 'no_deps=t', 'install-headers'])
+ cmdlist.cleanup_dir()
+ cmdlist.pop_subdesc()
+
+ def build_gcc(self, cmdlist, bootstrap):
+ """Build GCC."""
+ # libsanitizer commonly breaks because of glibc header
+ # changes, or on unusual targets. libssp is of little
+ # relevance with glibc's own stack checking support.
+ # libcilkrts does not support GNU/Hurd (and has been removed
+ # in GCC 8, so --disable-libcilkrts can be removed once glibc
+ # no longer supports building with older GCC versions).
+ cfg_opts = list(self.gcc_cfg)
+ cfg_opts += ['--disable-libsanitizer', '--disable-libssp',
+ '--disable-libcilkrts']
+ host_libs = self.ctx.host_libraries_installdir
+ cfg_opts += ['--with-gmp=%s' % host_libs,
+ '--with-mpfr=%s' % host_libs,
+ '--with-mpc=%s' % host_libs]
+ if bootstrap:
+ tool_build = 'gcc-first'
+ # Building a static-only, C-only compiler that is
+ # sufficient to build glibc. Various libraries and
+ # features that may require libc headers must be disabled.
+ # When configuring with a sysroot, --with-newlib is
+ # required to define inhibit_libc (to stop some parts of
+ # libgcc including libc headers); --without-headers is not
+ # sufficient.
+ cfg_opts += ['--enable-languages=c', '--disable-shared',
+ '--disable-threads',
+ '--disable-libatomic',
+ '--disable-decimal-float',
+ '--disable-libffi',
+ '--disable-libgomp',
+ '--disable-libitm',
+ '--disable-libmpx',
+ '--disable-libquadmath',
+ '--without-headers', '--with-newlib',
+ '--with-glibc-version=%s' % self.ctx.glibc_version
+ ]
+ cfg_opts += self.first_gcc_cfg
+ else:
+ tool_build = 'gcc'
+ cfg_opts += ['--enable-languages=c,c++', '--enable-shared',
+ '--enable-threads']
+ self.build_cross_tool(cmdlist, 'gcc', tool_build, cfg_opts)
+
+
+class Glibc(object):
+ """A configuration for building glibc."""
+
+ def __init__(self, compiler, arch=None, os_name=None, variant=None,
+ cfg=None, ccopts=None):
+ """Initialize a Glibc object."""
+ self.ctx = compiler.ctx
+ self.compiler = compiler
+ if arch is None:
+ self.arch = compiler.arch
+ else:
+ self.arch = arch
+ if os_name is None:
+ self.os = compiler.os
+ else:
+ self.os = os_name
+ self.variant = variant
+ if variant is None:
+ self.name = '%s-%s' % (self.arch, self.os)
+ else:
+ self.name = '%s-%s-%s' % (self.arch, self.os, variant)
+ self.triplet = '%s-glibc-%s' % (self.arch, self.os)
+ if cfg is None:
+ self.cfg = []
+ else:
+ self.cfg = cfg
+ self.ccopts = ccopts
+
+ def tool_name(self, tool):
+ """Return the name of a cross-compilation tool."""
+ ctool = '%s-%s' % (self.compiler.triplet, tool)
+ if self.ccopts and (tool == 'gcc' or tool == 'g++'):
+ ctool = '%s %s' % (ctool, self.ccopts)
+ return ctool
+
+ def build(self):
+ """Generate commands to build this glibc."""
+ builddir = self.ctx.component_builddir('glibcs', self.name, 'glibc')
+ installdir = self.ctx.glibc_installdir(self.name)
+ logsdir = os.path.join(self.ctx.logsdir, 'glibcs', self.name)
+ self.ctx.remove_recreate_dirs(installdir, builddir, logsdir)
+ cmdlist = CommandList('glibcs-%s' % self.name, self.ctx.keep)
+ cmdlist.add_command('check-compilers',
+ ['test', '-f',
+ os.path.join(self.compiler.installdir, 'ok')])
+ cmdlist.use_path(self.compiler.bindir)
+ self.build_glibc(cmdlist, False)
+ self.ctx.add_makefile_cmdlist('glibcs-%s' % self.name, cmdlist,
+ logsdir)
+
+ def build_glibc(self, cmdlist, for_compiler):
+ """Generate commands to build this glibc, either as part of a compiler
+ build or with the bootstrapped compiler (and in the latter case, run
+ tests as well)."""
+ srcdir = self.ctx.component_srcdir('glibc')
+ if for_compiler:
+ builddir = self.ctx.component_builddir('compilers',
+ self.compiler.name, 'glibc',
+ self.name)
+ installdir = self.compiler.sysroot
+ srcdir_copy = self.ctx.component_builddir('compilers',
+ self.compiler.name,
+ 'glibc-src',
+ self.name)
+ else:
+ builddir = self.ctx.component_builddir('glibcs', self.name,
+ 'glibc')
+ installdir = self.ctx.glibc_installdir(self.name)
+ srcdir_copy = self.ctx.component_builddir('glibcs', self.name,
+ 'glibc-src')
+ cmdlist.create_use_dir(builddir)
+ # glibc builds write into the source directory, and even if
+ # not intentionally there is a risk of bugs that involve
+ # writing into the working directory. To avoid possible
+ # concurrency issues, copy the source directory.
+ cmdlist.create_copy_dir(srcdir, srcdir_copy)
+ use_usr = self.os != 'gnu'
+ prefix = '/usr' if use_usr else ''
+ cfg_cmd = [os.path.join(srcdir_copy, 'configure'),
+ '--prefix=%s' % prefix,
+ '--enable-profile',
+ '--build=%s' % self.ctx.build_triplet,
+ '--host=%s' % self.triplet,
+ 'CC=%s' % self.tool_name('gcc'),
+ 'CXX=%s' % self.tool_name('g++'),
+ 'AR=%s' % self.tool_name('ar'),
+ 'AS=%s' % self.tool_name('as'),
+ 'LD=%s' % self.tool_name('ld'),
+ 'NM=%s' % self.tool_name('nm'),
+ 'OBJCOPY=%s' % self.tool_name('objcopy'),
+ 'OBJDUMP=%s' % self.tool_name('objdump'),
+ 'RANLIB=%s' % self.tool_name('ranlib'),
+ 'READELF=%s' % self.tool_name('readelf'),
+ 'STRIP=%s' % self.tool_name('strip')]
+ if self.os == 'gnu':
+ cfg_cmd += ['MIG=%s' % self.tool_name('mig')]
+ cfg_cmd += self.cfg
+ cmdlist.add_command('configure', cfg_cmd)
+ cmdlist.add_command('build', ['make'])
+ cmdlist.add_command('install', ['make', 'install',
+ 'install_root=%s' % installdir])
+ # GCC uses paths such as lib/../lib64, so make sure lib
+ # directories always exist.
+ mkdir_cmd = ['mkdir', '-p',
+ os.path.join(installdir, 'lib')]
+ if use_usr:
+ mkdir_cmd += [os.path.join(installdir, 'usr', 'lib')]
+ cmdlist.add_command('mkdir-lib', mkdir_cmd)
+ if not for_compiler:
+ if self.ctx.strip:
+ cmdlist.add_command('strip',
+ ['sh', '-c',
+ ('%s $(find %s/lib* -name "*.so")' %
+ (self.tool_name('strip'), installdir))])
+ cmdlist.add_command('check', ['make', 'check'])
+ cmdlist.add_command('save-logs', [self.ctx.save_logs],
+ always_run=True)
+ cmdlist.cleanup_dir('cleanup-src', srcdir_copy)
+ cmdlist.cleanup_dir()
+
+
+class Command(object):
+ """A command run in the build process."""
+
+ def __init__(self, desc, num, dir, path, command, always_run=False):
+ """Initialize a Command object."""
+ self.dir = dir
+ self.path = path
+ self.desc = desc
+ trans = str.maketrans({' ': '-'})
+ self.logbase = '%03d-%s' % (num, desc.translate(trans))
+ self.command = command
+ self.always_run = always_run
+
+ @staticmethod
+ def shell_make_quote_string(s):
+ """Given a string not containing a newline, quote it for use by the
+ shell and make."""
+ assert '\n' not in s
+ if re.fullmatch('[]+,./0-9@A-Z_a-z-]+', s):
+ return s
+ strans = str.maketrans({"'": "'\\''"})
+ s = "'%s'" % s.translate(strans)
+ mtrans = str.maketrans({'$': '$$'})
+ return s.translate(mtrans)
+
+ @staticmethod
+ def shell_make_quote_list(l, translate_make):
+ """Given a list of strings not containing newlines, quote them for use
+ by the shell and make, returning a single string. If translate_make
+ is true and the first string is 'make', change it to $(MAKE)."""
+ l = [Command.shell_make_quote_string(s) for s in l]
+ if translate_make and l[0] == 'make':
+ l[0] = '$(MAKE)'
+ return ' '.join(l)
+
+ def shell_make_quote(self):
+ """Return this command quoted for the shell and make."""
+ return self.shell_make_quote_list(self.command, True)
+
+
+class CommandList(object):
+ """A list of commands run in the build process."""
+
+ def __init__(self, desc, keep):
+ """Initialize a CommandList object."""
+ self.cmdlist = []
+ self.dir = None
+ self.path = None
+ self.desc = [desc]
+ self.keep = keep
+
+ def desc_txt(self, desc):
+ """Return the description to use for a command."""
+ return '%s %s' % (' '.join(self.desc), desc)
+
+ def use_dir(self, dir):
+ """Set the default directory for subsequent commands."""
+ self.dir = dir
+
+ def use_path(self, path):
+ """Set a directory to be prepended to the PATH for subsequent
+ commands."""
+ self.path = path
+
+ def push_subdesc(self, subdesc):
+ """Set the default subdescription for subsequent commands (e.g., the
+ name of a component being built, within the series of commands
+ building it)."""
+ self.desc.append(subdesc)
+
+ def pop_subdesc(self):
+ """Pop a subdescription from the list of descriptions."""
+ self.desc.pop()
+
+ def create_use_dir(self, dir):
+ """Remove and recreate a directory and use it for subsequent
+ commands."""
+ self.add_command_dir('rm', None, ['rm', '-rf', dir])
+ self.add_command_dir('mkdir', None, ['mkdir', '-p', dir])
+ self.use_dir(dir)
+
+ def create_copy_dir(self, src, dest):
+ """Remove a directory and recreate it as a copy from the given
+ source."""
+ self.add_command_dir('copy-rm', None, ['rm', '-rf', dest])
+ parent = os.path.dirname(dest)
+ self.add_command_dir('copy-mkdir', None, ['mkdir', '-p', parent])
+ self.add_command_dir('copy', None, ['cp', '-a', src, dest])
+
+ def add_command_dir(self, desc, dir, command, always_run=False):
+ """Add a command to run in a given directory."""
+ cmd = Command(self.desc_txt(desc), len(self.cmdlist), dir, self.path,
+ command, always_run)
+ self.cmdlist.append(cmd)
+
+ def add_command(self, desc, command, always_run=False):
+ """Add a command to run in the default directory."""
+ cmd = Command(self.desc_txt(desc), len(self.cmdlist), self.dir,
+ self.path, command, always_run)
+ self.cmdlist.append(cmd)
+
+ def cleanup_dir(self, desc='cleanup', dir=None):
+ """Clean up a build directory. If no directory is specified, the
+ default directory is cleaned up and ceases to be the default
+ directory."""
+ if dir is None:
+ dir = self.dir
+ self.use_dir(None)
+ if self.keep != 'all':
+ self.add_command_dir(desc, None, ['rm', '-rf', dir],
+ always_run=(self.keep == 'none'))
+
+ def makefile_commands(self, wrapper, logsdir):
+ """Return the sequence of commands in the form of text for a Makefile.
+ The given wrapper script takes arguments: base of logs for
+ previous command, or empty; base of logs for this command;
+ description; directory; PATH addition; the command itself."""
+ # prev_base is the base of the name for logs of the previous
+ # command that is not always-run (that is, a build command,
+ # whose failure should stop subsequent build commands from
+ # being run, as opposed to a cleanup command, which is run
+ # even if previous commands failed).
+ prev_base = ''
+ cmds = []
+ for c in self.cmdlist:
+ ctxt = c.shell_make_quote()
+ if prev_base and not c.always_run:
+ prev_log = os.path.join(logsdir, prev_base)
+ else:
+ prev_log = ''
+ this_log = os.path.join(logsdir, c.logbase)
+ if not c.always_run:
+ prev_base = c.logbase
+ if c.dir is None:
+ dir = ''
+ else:
+ dir = c.dir
+ if c.path is None:
+ path = ''
+ else:
+ path = c.path
+ prelims = [wrapper, prev_log, this_log, c.desc, dir, path]
+ prelim_txt = Command.shell_make_quote_list(prelims, False)
+ cmds.append('\t@%s %s' % (prelim_txt, ctxt))
+ return '\n'.join(cmds)
+
+ def status_logs(self, logsdir):
+ """Return the list of log files with command status."""
+ return [os.path.join(logsdir, '%s-status.txt' % c.logbase)
+ for c in self.cmdlist]
+
+
+def get_parser():
+ """Return an argument parser for this module."""
+ parser = argparse.ArgumentParser(description=__doc__)
+ parser.add_argument('-j', dest='parallelism',
+ help='Run this number of jobs in parallel',
+ type=int, default=os.cpu_count())
+ parser.add_argument('--keep', dest='keep',
+ help='Whether to keep all build directories, '
+ 'none or only those from failed builds',
+ default='none', choices=('none', 'all', 'failed'))
+ parser.add_argument('--replace-sources', action='store_true',
+ help='Remove and replace source directories '
+ 'with the wrong version of a component')
+ parser.add_argument('--strip', action='store_true',
+ help='Strip installed glibc libraries')
+ parser.add_argument('topdir',
+ help='Toplevel working directory')
+ parser.add_argument('action',
+ help='What to do',
+ choices=('checkout', 'bot-cycle', 'bot',
+ 'host-libraries', 'compilers', 'glibcs'))
+ parser.add_argument('configs',
+ help='Versions to check out or configurations to build',
+ nargs='*')
+ return parser
+
+
+def main(argv):
+ """The main entry point."""
+ parser = get_parser()
+ opts = parser.parse_args(argv)
+ topdir = os.path.abspath(opts.topdir)
+ ctx = Context(topdir, opts.parallelism, opts.keep, opts.replace_sources,
+ opts.strip, opts.action)
+ ctx.run_builds(opts.action, opts.configs)
+
+
+if __name__ == '__main__':
+ main(sys.argv[1:])