123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683 |
- #!/usr/bin/python3
- # Build many configurations of glibc.
- # Copyright (C) 2016-2019 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:
- 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,
- full_gcc, action):
- """Initialize the context."""
- self.topdir = topdir
- self.parallelism = parallelism
- self.keep = keep
- self.replace_sources = replace_sources
- self.strip = strip
- self.full_gcc = full_gcc
- 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='csky',
- os_name='linux-gnuabiv2',
- variant='soft',
- gcc_cfg=['--disable-multilib'])
- self.add_config(arch='csky',
- os_name='linux-gnuabiv2',
- gcc_cfg=['--with-float=hard', '--disable-multilib'])
- 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': 'enable-obsolete',
- 'cfg': ['--enable-obsolete-rpc',
- '--enable-obsolete-nsl']},
- {'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']},
- {'variant': 'enable-obsolete',
- 'arch': 'i686',
- 'ccopts': '-m32 -march=i686',
- 'cfg': ['--enable-obsolete-rpc',
- '--enable-obsolete-nsl']},
- {'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.32',
- 'gcc': 'vcs-8',
- 'glibc': 'vcs-mainline',
- 'gmp': '6.1.2',
- 'linux': '4.20',
- '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')
- # These files have Makefile dependencies to regenerate them in
- # the source tree that may be active during a normal build.
- # Some other files have such dependencies but do not need to
- # be touched because nothing in a build depends on the files
- # in question.
- for f in ('sysdeps/gnu/errlist.c',
- 'sysdeps/mach/hurd/bits/errno.h',
- 'sysdeps/sparc/sparc32/rem.S',
- 'sysdeps/sparc/sparc32/sdiv.S',
- 'sysdeps/sparc/sparc32/udiv.S',
- 'sysdeps/sparc/sparc32/urem.S'):
- to_touch = os.path.join(srcdir, f)
- subprocess.run(['touch', '-c', to_touch], check=True)
- 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]
- if self.full_gcc:
- cmd.append('--full-gcc')
- 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',
- 'csky': 'csky',
- '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."""
- # 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-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',
- '--disable-libsanitizer',
- '--without-headers', '--with-newlib',
- '--with-glibc-version=%s' % self.ctx.glibc_version
- ]
- cfg_opts += self.first_gcc_cfg
- else:
- tool_build = 'gcc'
- # libsanitizer commonly breaks because of glibc header
- # changes, or on unusual targets.
- if not self.ctx.full_gcc:
- cfg_opts += ['--disable-libsanitizer']
- langs = 'all' if self.ctx.full_gcc else 'c,c++'
- cfg_opts += ['--enable-languages=%s' % langs,
- '--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
- else:
- builddir = self.ctx.component_builddir('glibcs', self.name,
- 'glibc')
- installdir = self.ctx.glibc_installdir(self.name)
- cmdlist.create_use_dir(builddir)
- use_usr = self.os != 'gnu'
- prefix = '/usr' if use_usr else ''
- cfg_cmd = [os.path.join(srcdir, '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()
- 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 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('--full-gcc', action='store_true',
- help='Build GCC with all languages and libsanitizer')
- 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.full_gcc, opts.action)
- ctx.run_builds(opts.action, opts.configs)
- if __name__ == '__main__':
- main(sys.argv[1:])
|