build-many-glibcs.py 90 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043
  1. #!/usr/bin/python3
  2. # Build many configurations of glibc.
  3. # Copyright (C) 2016-2026 Free Software Foundation, Inc.
  4. # Copyright The GNU Toolchain Authors.
  5. # This file is part of the GNU C Library.
  6. #
  7. # The GNU C Library is free software; you can redistribute it and/or
  8. # modify it under the terms of the GNU Lesser General Public
  9. # License as published by the Free Software Foundation; either
  10. # version 2.1 of the License, or (at your option) any later version.
  11. #
  12. # The GNU C Library is distributed in the hope that it will be useful,
  13. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  15. # Lesser General Public License for more details.
  16. #
  17. # You should have received a copy of the GNU Lesser General Public
  18. # License along with the GNU C Library; if not, see
  19. # <https://www.gnu.org/licenses/>.
  20. """Build many configurations of glibc.
  21. This script takes as arguments a directory name (containing a src
  22. subdirectory with sources of the relevant toolchain components) and a
  23. description of what to do: 'checkout', to check out sources into that
  24. directory, 'bot-cycle', to run a series of checkout and build steps,
  25. 'bot', to run 'bot-cycle' repeatedly, 'host-libraries', to build
  26. libraries required by the toolchain, 'compilers', to build
  27. cross-compilers for various configurations, or 'glibcs', to build
  28. glibc for various configurations and run the compilation parts of the
  29. testsuite. Subsequent arguments name the versions of components to
  30. check out (<component>-<version), for 'checkout', or, for actions
  31. other than 'checkout' and 'bot-cycle', name configurations for which
  32. compilers or glibc are to be built.
  33. It is possible to override the URL used to download tarballs during
  34. the checkout process using environment variable FTP_GNU_ORG_MIRROR
  35. that will replace default URL 'https://ftp.gnu.org'.
  36. It is also possible to use different Git URL for cloning sources
  37. from a Git repository using a <component>_GIT_MIRROR environment
  38. variable (<component> should be upper case, e.g. GLIBC).
  39. The 'list-compilers' command prints the name of each available
  40. compiler configuration, without building anything. The 'list-glibcs'
  41. command prints the name of each glibc compiler configuration, followed
  42. by the space, followed by the name of the compiler configuration used
  43. for building this glibc variant.
  44. """
  45. import argparse
  46. import datetime
  47. import email.mime.text
  48. import email.utils
  49. import json
  50. import os
  51. import re
  52. import shutil
  53. import smtplib
  54. import stat
  55. import subprocess
  56. import sys
  57. import time
  58. import urllib.request
  59. REQUIRED_TOOLS = {}
  60. # This is a list of system utilities that are expected to be available
  61. # to this script, and, if a non-zero version is included, the minimum
  62. # version required to work with this script.
  63. def get_list_of_required_tools():
  64. global REQUIRED_TOOLS
  65. REQUIRED_TOOLS = {
  66. 'awk' : (get_version_awk, (0,0,0)),
  67. 'bison' : (get_version, (0,0)),
  68. 'flex' : (get_version, (0,0,0)),
  69. 'git' : (get_version, (1,8,3)),
  70. 'make' : (get_version, (4,0)),
  71. 'makeinfo' : (get_version, (0,0)),
  72. 'patch' : (get_version, (0,0,0)),
  73. 'sed' : (get_version, (0,0)),
  74. 'tar' : (get_version, (0,0,0)),
  75. 'gzip' : (get_version, (0,0)),
  76. 'bzip2' : (get_version_bzip2, (0,0,0)),
  77. 'xz' : (get_version, (0,0,0)),
  78. }
  79. try:
  80. subprocess.run
  81. except:
  82. class _CompletedProcess:
  83. def __init__(self, args, returncode, stdout=None, stderr=None):
  84. self.args = args
  85. self.returncode = returncode
  86. self.stdout = stdout
  87. self.stderr = stderr
  88. def _run(*popenargs, input=None, timeout=None, check=False, **kwargs):
  89. assert(timeout is None)
  90. with subprocess.Popen(*popenargs, **kwargs) as process:
  91. try:
  92. stdout, stderr = process.communicate(input)
  93. except:
  94. process.kill()
  95. process.wait()
  96. raise
  97. returncode = process.poll()
  98. if check and returncode:
  99. raise subprocess.CalledProcessError(returncode, popenargs)
  100. return _CompletedProcess(popenargs, returncode, stdout, stderr)
  101. subprocess.run = _run
  102. def get_git_url(component):
  103. """Return Git URL for the given component. Allow overrides via env var."""
  104. git_urls = {
  105. 'binutils': 'https://sourceware.org/git/binutils-gdb.git',
  106. 'glibc': 'https://sourceware.org/git/glibc.git',
  107. 'gcc': 'https://gcc.gnu.org/git/gcc.git',
  108. 'gnumach': 'https://git.savannah.gnu.org/git/hurd/gnumach.git',
  109. 'mig': 'https://git.savannah.gnu.org/git/hurd/mig.git',
  110. 'hurd': 'https://git.savannah.gnu.org/git/hurd/hurd.git',
  111. }
  112. env_var = '%s_GIT_MIRROR' % component.upper()
  113. if env_var in os.environ:
  114. return os.environ[env_var]
  115. if component in git_urls:
  116. return git_urls[component]
  117. else:
  118. raise RuntimeError('unknown component')
  119. class Context(object):
  120. """The global state associated with builds in a given directory."""
  121. def __init__(self, topdir, parallelism, keep, replace_sources, strip,
  122. full_gcc, action, exclude, shallow=False):
  123. """Initialize the context."""
  124. self.bot_config = None
  125. self.build_state = None
  126. self.versions = None
  127. self.topdir = topdir
  128. self.parallelism = parallelism
  129. self.keep = keep
  130. self.replace_sources = replace_sources
  131. self.strip = strip
  132. self.full_gcc = full_gcc
  133. self.exclude = exclude
  134. self.shallow = shallow
  135. self.srcdir = os.path.join(topdir, 'src')
  136. self.versions_json = os.path.join(self.srcdir, 'versions.json')
  137. self.build_state_json = os.path.join(topdir, 'build-state.json')
  138. self.bot_config_json = os.path.join(topdir, 'bot-config.json')
  139. self.installdir = os.path.join(topdir, 'install')
  140. self.host_libraries_installdir = os.path.join(self.installdir,
  141. 'host-libraries')
  142. self.builddir = os.path.join(topdir, 'build')
  143. self.logsdir = os.path.join(topdir, 'logs')
  144. self.logsdir_old = os.path.join(topdir, 'logs-old')
  145. self.makefile = os.path.join(self.builddir, 'Makefile')
  146. self.wrapper = os.path.join(self.builddir, 'wrapper')
  147. self.save_logs = os.path.join(self.builddir, 'save-logs')
  148. self.script_text = self.get_script_text()
  149. if action not in ('checkout', 'list-compilers', 'list-glibcs'):
  150. self.build_triplet = self.get_build_triplet()
  151. self.glibc_version = self.get_glibc_version()
  152. self.configs = {}
  153. self.glibc_configs = {}
  154. self.makefile_pieces = ['.PHONY: all\n']
  155. self.add_all_configs()
  156. self.load_versions_json()
  157. self.load_build_state_json()
  158. self.status_log_list = []
  159. self.email_warning = False
  160. def get_script_text(self):
  161. """Return the text of this script."""
  162. with open(sys.argv[0], 'r') as f:
  163. return f.read()
  164. def exec_self(self):
  165. """Re-execute this script with the same arguments."""
  166. sys.stdout.flush()
  167. os.execv(sys.executable, [sys.executable] + sys.argv)
  168. def get_build_triplet(self):
  169. """Determine the build triplet with config.guess."""
  170. config_guess = os.path.join(self.component_srcdir('gcc'),
  171. 'config.guess')
  172. cg_out = subprocess.run([config_guess], stdout=subprocess.PIPE,
  173. check=True, universal_newlines=True).stdout
  174. return cg_out.rstrip()
  175. def get_glibc_version(self):
  176. """Determine the glibc version number (major.minor)."""
  177. version_h = os.path.join(self.component_srcdir('glibc'), 'version.h')
  178. with open(version_h, 'r') as f:
  179. lines = f.readlines()
  180. starttext = '#define VERSION "'
  181. for l in lines:
  182. if l.startswith(starttext):
  183. l = l[len(starttext):]
  184. l = l.rstrip('"\n')
  185. m = re.fullmatch(r'([0-9]+)\.([0-9]+)[.0-9]*', l)
  186. return '%s.%s' % m.group(1, 2)
  187. print('error: could not determine glibc version')
  188. exit(1)
  189. def add_all_configs(self):
  190. """Add all known glibc build configurations."""
  191. self.add_config(arch='aarch64',
  192. os_name='linux-gnu',
  193. extra_glibcs=[{'variant': 'disable-multi-arch',
  194. 'cfg': ['--disable-multi-arch']}])
  195. self.add_config(arch='aarch64_be',
  196. os_name='linux-gnu')
  197. self.add_config(arch='arc',
  198. os_name='linux-gnu',
  199. gcc_cfg=['--disable-multilib', '--with-cpu=hs38'])
  200. self.add_config(arch='arc',
  201. os_name='linux-gnuhf',
  202. gcc_cfg=['--disable-multilib', '--with-cpu=hs38_linux'])
  203. self.add_config(arch='alpha',
  204. os_name='linux-gnu')
  205. self.add_config(arch='arm',
  206. os_name='linux-gnueabi',
  207. extra_glibcs=[{'variant': 'v4t',
  208. 'ccopts': '-march=armv4t'}])
  209. self.add_config(arch='armeb',
  210. os_name='linux-gnueabi')
  211. self.add_config(arch='armeb',
  212. os_name='linux-gnueabi',
  213. variant='be8',
  214. gcc_cfg=['--with-arch=armv7-a'])
  215. self.add_config(arch='arm',
  216. os_name='linux-gnueabihf',
  217. gcc_cfg=['--with-float=hard', '--with-cpu=arm926ej-s'],
  218. extra_glibcs=[{'variant': 'v7a',
  219. 'ccopts': '-march=armv7-a -mfpu=vfpv3'},
  220. {'variant': 'thumb',
  221. 'ccopts':
  222. '-mthumb -march=armv7-a -mfpu=vfpv3'},
  223. {'variant': 'v7a-disable-multi-arch',
  224. 'ccopts': '-march=armv7-a -mfpu=vfpv3',
  225. 'cfg': ['--disable-multi-arch']}])
  226. self.add_config(arch='armeb',
  227. os_name='linux-gnueabihf',
  228. gcc_cfg=['--with-float=hard', '--with-cpu=arm926ej-s'])
  229. self.add_config(arch='armeb',
  230. os_name='linux-gnueabihf',
  231. variant='be8',
  232. gcc_cfg=['--with-float=hard', '--with-arch=armv7-a',
  233. '--with-fpu=vfpv3'])
  234. self.add_config(arch='csky',
  235. os_name='linux-gnuabiv2',
  236. variant='soft',
  237. gcc_cfg=['--disable-multilib'])
  238. self.add_config(arch='csky',
  239. os_name='linux-gnuabiv2',
  240. gcc_cfg=['--with-float=hard', '--disable-multilib'])
  241. self.add_config(arch='hppa',
  242. os_name='linux-gnu')
  243. self.add_config(arch='i686',
  244. os_name='gnu')
  245. self.add_config(arch='loongarch64',
  246. os_name='linux-gnuf64',
  247. gcc_cfg=['--disable-multilib'])
  248. self.add_config(arch='loongarch64',
  249. os_name='linux-gnusf',
  250. gcc_cfg=['--disable-multilib'])
  251. self.add_config(arch='m68k',
  252. os_name='linux-gnu',
  253. gcc_cfg=['--disable-multilib'])
  254. self.add_config(arch='m68k',
  255. os_name='linux-gnu',
  256. variant='coldfire',
  257. gcc_cfg=['--with-arch=cf', '--disable-multilib'])
  258. self.add_config(arch='m68k',
  259. os_name='linux-gnu',
  260. variant='coldfire-soft',
  261. gcc_cfg=['--with-arch=cf', '--with-cpu=54455',
  262. '--disable-multilib'])
  263. self.add_config(arch='microblaze',
  264. os_name='linux-gnu',
  265. gcc_cfg=['--disable-multilib'])
  266. self.add_config(arch='microblazeel',
  267. os_name='linux-gnu',
  268. gcc_cfg=['--disable-multilib'])
  269. self.add_config(arch='mips64',
  270. os_name='linux-gnu',
  271. gcc_cfg=['--with-mips-plt'],
  272. glibcs=[{'variant': 'n32'},
  273. {'arch': 'mips',
  274. 'ccopts': '-mabi=32'},
  275. {'variant': 'n64',
  276. 'ccopts': '-mabi=64'}])
  277. self.add_config(arch='mips64',
  278. os_name='linux-gnu',
  279. variant='soft',
  280. gcc_cfg=['--with-mips-plt', '--with-float=soft'],
  281. glibcs=[{'variant': 'n32-soft'},
  282. {'variant': 'soft',
  283. 'arch': 'mips',
  284. 'ccopts': '-mabi=32'},
  285. {'variant': 'n64-soft',
  286. 'ccopts': '-mabi=64'}])
  287. self.add_config(arch='mips64',
  288. os_name='linux-gnu',
  289. variant='nan2008',
  290. gcc_cfg=['--with-mips-plt', '--with-nan=2008',
  291. '--with-arch-64=mips64r2',
  292. '--with-arch-32=mips32r2'],
  293. glibcs=[{'variant': 'n32-nan2008'},
  294. {'variant': 'nan2008',
  295. 'arch': 'mips',
  296. 'ccopts': '-mabi=32'},
  297. {'variant': 'n64-nan2008',
  298. 'ccopts': '-mabi=64'}])
  299. self.add_config(arch='mips64',
  300. os_name='linux-gnu',
  301. variant='nan2008-soft',
  302. gcc_cfg=['--with-mips-plt', '--with-nan=2008',
  303. '--with-arch-64=mips64r2',
  304. '--with-arch-32=mips32r2',
  305. '--with-float=soft'],
  306. glibcs=[{'variant': 'n32-nan2008-soft'},
  307. {'variant': 'nan2008-soft',
  308. 'arch': 'mips',
  309. 'ccopts': '-mabi=32'},
  310. {'variant': 'n64-nan2008-soft',
  311. 'ccopts': '-mabi=64'}])
  312. self.add_config(arch='mips64el',
  313. os_name='linux-gnu',
  314. gcc_cfg=['--with-mips-plt'],
  315. glibcs=[{'variant': 'n32'},
  316. {'arch': 'mipsel',
  317. 'ccopts': '-mabi=32'},
  318. {'variant': 'n64',
  319. 'ccopts': '-mabi=64'}])
  320. self.add_config(arch='mips64el',
  321. os_name='linux-gnu',
  322. variant='soft',
  323. gcc_cfg=['--with-mips-plt', '--with-float=soft'],
  324. glibcs=[{'variant': 'n32-soft'},
  325. {'variant': 'soft',
  326. 'arch': 'mipsel',
  327. 'ccopts': '-mabi=32'},
  328. {'variant': 'n64-soft',
  329. 'ccopts': '-mabi=64'}])
  330. self.add_config(arch='mips64el',
  331. os_name='linux-gnu',
  332. variant='nan2008',
  333. gcc_cfg=['--with-mips-plt', '--with-nan=2008',
  334. '--with-arch-64=mips64r2',
  335. '--with-arch-32=mips32r2'],
  336. glibcs=[{'variant': 'n32-nan2008'},
  337. {'variant': 'nan2008',
  338. 'arch': 'mipsel',
  339. 'ccopts': '-mabi=32'},
  340. {'variant': 'n64-nan2008',
  341. 'ccopts': '-mabi=64'}])
  342. self.add_config(arch='mips64el',
  343. os_name='linux-gnu',
  344. variant='nan2008-soft',
  345. gcc_cfg=['--with-mips-plt', '--with-nan=2008',
  346. '--with-arch-64=mips64r2',
  347. '--with-arch-32=mips32r2',
  348. '--with-float=soft'],
  349. glibcs=[{'variant': 'n32-nan2008-soft'},
  350. {'variant': 'nan2008-soft',
  351. 'arch': 'mipsel',
  352. 'ccopts': '-mabi=32'},
  353. {'variant': 'n64-nan2008-soft',
  354. 'ccopts': '-mabi=64'}])
  355. self.add_config(arch='mipsisa64r6el',
  356. os_name='linux-gnu',
  357. gcc_cfg=['--with-mips-plt', '--with-nan=2008',
  358. '--with-arch-64=mips64r6',
  359. '--with-arch-32=mips32r6',
  360. '--with-float=hard'],
  361. glibcs=[{'variant': 'n32'},
  362. {'arch': 'mipsisa32r6el',
  363. 'ccopts': '-mabi=32'},
  364. {'variant': 'n64',
  365. 'ccopts': '-mabi=64'}])
  366. self.add_config(arch='or1k',
  367. os_name='linux-gnu',
  368. gcc_cfg=['--with-multilib-list=mcmov,mhard-float'],
  369. glibcs=[{'variant': 'soft'},
  370. {'variant': 'hard', 'ccopts': '-mhard-float'}])
  371. self.add_config(arch='powerpc',
  372. os_name='linux-gnu',
  373. gcc_cfg=['--disable-multilib', '--enable-secureplt'],
  374. extra_glibcs=[{'variant': 'power4',
  375. 'ccopts': '-mcpu=power4',
  376. 'cfg': ['--with-cpu=power4']}])
  377. self.add_config(arch='powerpc',
  378. os_name='linux-gnu',
  379. variant='soft',
  380. gcc_cfg=['--disable-multilib', '--with-float=soft',
  381. '--enable-secureplt'])
  382. self.add_config(arch='powerpc64',
  383. os_name='linux-gnu',
  384. gcc_cfg=['--disable-multilib', '--enable-secureplt'])
  385. self.add_config(arch='powerpc64le',
  386. os_name='linux-gnu',
  387. gcc_cfg=['--disable-multilib', '--enable-secureplt'],
  388. extra_glibcs=[{'variant': 'disable-multi-arch',
  389. 'cfg': ['--disable-multi-arch']}])
  390. self.add_config(arch='riscv32',
  391. os_name='linux-gnu',
  392. variant='rv32imac-ilp32',
  393. gcc_cfg=['--with-arch=rv32imac', '--with-abi=ilp32',
  394. '--disable-multilib'])
  395. self.add_config(arch='riscv32',
  396. os_name='linux-gnu',
  397. variant='rv32imafdc-ilp32',
  398. gcc_cfg=['--with-arch=rv32imafdc', '--with-abi=ilp32',
  399. '--disable-multilib'])
  400. self.add_config(arch='riscv32',
  401. os_name='linux-gnu',
  402. variant='rv32imafdc-ilp32d',
  403. gcc_cfg=['--with-arch=rv32imafdc', '--with-abi=ilp32d',
  404. '--disable-multilib'])
  405. self.add_config(arch='riscv64',
  406. os_name='linux-gnu',
  407. variant='rv64imac-lp64',
  408. gcc_cfg=['--with-arch=rv64imac', '--with-abi=lp64',
  409. '--disable-multilib'])
  410. self.add_config(arch='riscv64',
  411. os_name='linux-gnu',
  412. variant='rv64imafdc-lp64',
  413. gcc_cfg=['--with-arch=rv64imafdc', '--with-abi=lp64',
  414. '--disable-multilib'])
  415. self.add_config(arch='riscv64',
  416. os_name='linux-gnu',
  417. variant='rv64imafdc-lp64d',
  418. gcc_cfg=['--with-arch=rv64imafdc', '--with-abi=lp64d',
  419. '--disable-multilib'])
  420. self.add_config(arch='s390x',
  421. os_name='linux-gnu',
  422. gcc_cfg=['--disable-multilib'],
  423. extra_glibcs=[{'variant': 'O3',
  424. 'cflags': '-O3'},
  425. {'variant': 'zEC12',
  426. 'ccopts': '-march=zEC12'},
  427. {'variant': 'z13',
  428. 'ccopts': '-march=z13'},
  429. {'variant': 'z15',
  430. 'ccopts': '-march=z15'},
  431. {'variant': 'zEC12-disable-multi-arch',
  432. 'ccopts': '-march=zEC12',
  433. 'cfg': ['--disable-multi-arch']},
  434. {'variant': 'z13-disable-multi-arch',
  435. 'ccopts': '-march=z13',
  436. 'cfg': ['--disable-multi-arch']},
  437. {'variant': 'z15-disable-multi-arch',
  438. 'ccopts': '-march=z15',
  439. 'cfg': ['--disable-multi-arch']}])
  440. self.add_config(arch='sh3',
  441. os_name='linux-gnu')
  442. self.add_config(arch='sh3eb',
  443. os_name='linux-gnu')
  444. self.add_config(arch='sh4',
  445. os_name='linux-gnu')
  446. self.add_config(arch='sh4eb',
  447. os_name='linux-gnu')
  448. self.add_config(arch='sh4',
  449. os_name='linux-gnu',
  450. variant='soft',
  451. gcc_cfg=['--without-fp'])
  452. self.add_config(arch='sh4eb',
  453. os_name='linux-gnu',
  454. variant='soft',
  455. gcc_cfg=['--without-fp'])
  456. self.add_config(arch='sparc64',
  457. os_name='linux-gnu',
  458. glibcs=[{'cfg' : ['--disable-default-pie']},
  459. {'cfg' : ['--disable-default-pie'],
  460. 'arch': 'sparcv9',
  461. 'ccopts': '-m32 -mlong-double-128 -mcpu=v9'}],
  462. extra_glibcs=[{'variant': 'leon3',
  463. 'cfg' : ['--disable-default-pie'],
  464. 'arch' : 'sparcv8',
  465. 'ccopts' : '-m32 -mlong-double-128 -mcpu=leon3'},
  466. {'variant': 'disable-multi-arch',
  467. 'cfg': ['--disable-multi-arch', '--disable-default-pie']},
  468. {'variant': 'disable-multi-arch',
  469. 'arch': 'sparcv9',
  470. 'ccopts': '-m32 -mlong-double-128 -mcpu=v9',
  471. 'cfg': ['--disable-multi-arch', '--disable-default-pie']}])
  472. self.add_config(arch='x86_64',
  473. os_name='linux-gnu',
  474. gcc_cfg=['--with-multilib-list=m64,m32,mx32'],
  475. glibcs=[{},
  476. {'variant': 'x32', 'ccopts': '-mx32'},
  477. {'arch': 'i686', 'ccopts': '-m32 -march=i686'}],
  478. extra_glibcs=[{'variant': 'disable-multi-arch',
  479. 'cfg': ['--disable-multi-arch']},
  480. {'variant': 'minimal',
  481. 'cfg': ['--disable-multi-arch',
  482. '--disable-profile',
  483. '--disable-timezone-tools',
  484. '--disable-mathvec',
  485. '--disable-build-nscd',
  486. '--disable-nscd']},
  487. {'variant': 'no-pie',
  488. 'cfg': ['--disable-default-pie']},
  489. {'variant': 'x32-no-pie',
  490. 'ccopts': '-mx32',
  491. 'cfg': ['--disable-default-pie']},
  492. {'variant': 'no-pie',
  493. 'arch': 'i686',
  494. 'ccopts': '-m32 -march=i686',
  495. 'cfg': ['--disable-default-pie']},
  496. {'variant': 'disable-multi-arch',
  497. 'arch': 'i686',
  498. 'ccopts': '-m32 -march=i686',
  499. 'cfg': ['--disable-multi-arch']},
  500. {'arch': 'i486',
  501. 'ccopts': '-m32 -march=i486'},
  502. {'arch': 'i586',
  503. 'ccopts': '-m32 -march=i586'},
  504. {'variant': 'enable-fortify-source',
  505. 'cfg': ['--enable-fortify-source']}])
  506. self.add_config(arch='x86_64',
  507. os_name='gnu',
  508. gcc_cfg=['--disable-multilib'])
  509. def add_config(self, **args):
  510. """Add an individual build configuration."""
  511. cfg = Config(self, **args)
  512. if self.exclude and cfg.name in self.exclude:
  513. return
  514. if cfg.name in self.configs:
  515. print('error: duplicate config %s' % cfg.name)
  516. exit(1)
  517. self.configs[cfg.name] = cfg
  518. for c in cfg.all_glibcs:
  519. if c.name in self.glibc_configs:
  520. print('error: duplicate glibc config %s' % c.name)
  521. exit(1)
  522. self.glibc_configs[c.name] = c
  523. def component_srcdir(self, component):
  524. """Return the source directory for a given component, e.g. gcc."""
  525. return os.path.join(self.srcdir, component)
  526. def component_builddir(self, action, config, component, subconfig=None):
  527. """Return the directory to use for a build."""
  528. if config is None:
  529. # Host libraries.
  530. assert subconfig is None
  531. return os.path.join(self.builddir, action, component)
  532. if subconfig is None:
  533. return os.path.join(self.builddir, action, config, component)
  534. else:
  535. # glibc build as part of compiler build.
  536. return os.path.join(self.builddir, action, config, component,
  537. subconfig)
  538. def compiler_installdir(self, config):
  539. """Return the directory in which to install a compiler."""
  540. return os.path.join(self.installdir, 'compilers', config)
  541. def compiler_bindir(self, config):
  542. """Return the directory in which to find compiler binaries."""
  543. return os.path.join(self.compiler_installdir(config), 'bin')
  544. def compiler_sysroot(self, config):
  545. """Return the sysroot directory for a compiler."""
  546. return os.path.join(self.compiler_installdir(config), 'sysroot')
  547. def glibc_installdir(self, config):
  548. """Return the directory in which to install glibc."""
  549. return os.path.join(self.installdir, 'glibcs', config)
  550. def run_builds(self, action, configs):
  551. """Run the requested builds."""
  552. if action == 'checkout':
  553. self.checkout(configs)
  554. return
  555. if action == 'bot-cycle':
  556. if configs:
  557. print('error: configurations specified for bot-cycle')
  558. exit(1)
  559. self.bot_cycle()
  560. return
  561. if action == 'bot':
  562. if configs:
  563. print('error: configurations specified for bot')
  564. exit(1)
  565. self.bot()
  566. return
  567. if action in ('host-libraries', 'list-compilers',
  568. 'list-glibcs') and configs:
  569. print('error: configurations specified for ' + action)
  570. exit(1)
  571. if action == 'list-compilers':
  572. for name in sorted(self.configs.keys()):
  573. print(name)
  574. return
  575. if action == 'list-glibcs':
  576. for config in sorted(self.glibc_configs.values(),
  577. key=lambda c: c.name):
  578. print(config.name, config.compiler.name)
  579. return
  580. self.clear_last_build_state(action)
  581. build_time = datetime.datetime.now(datetime.timezone.utc)
  582. if action == 'host-libraries':
  583. build_components = ('gmp', 'mpfr', 'mpc')
  584. old_components = ()
  585. old_versions = {}
  586. self.build_host_libraries()
  587. elif action == 'compilers':
  588. build_components = ('binutils', 'gcc', 'glibc', 'linux', 'mig',
  589. 'gnumach', 'hurd')
  590. old_components = ('gmp', 'mpfr', 'mpc')
  591. old_versions = self.build_state['host-libraries']['build-versions']
  592. self.build_compilers(configs)
  593. else:
  594. build_components = ('glibc',)
  595. old_components = ('gmp', 'mpfr', 'mpc', 'binutils', 'gcc', 'linux',
  596. 'mig', 'gnumach', 'hurd')
  597. old_versions = self.build_state['compilers']['build-versions']
  598. if action == 'update-syscalls':
  599. self.update_syscalls(configs)
  600. else:
  601. self.build_glibcs(configs)
  602. self.write_files()
  603. self.do_build()
  604. if configs:
  605. # Partial build, do not update stored state.
  606. return
  607. build_versions = {}
  608. for k in build_components:
  609. if k in self.versions:
  610. build_versions[k] = {'version': self.versions[k]['version'],
  611. 'revision': self.versions[k]['revision']}
  612. for k in old_components:
  613. if k in old_versions:
  614. build_versions[k] = {'version': old_versions[k]['version'],
  615. 'revision': old_versions[k]['revision']}
  616. self.update_build_state(action, build_time, build_versions)
  617. @staticmethod
  618. def remove_dirs(*args):
  619. """Remove directories and their contents if they exist."""
  620. for dir in args:
  621. shutil.rmtree(dir, ignore_errors=True)
  622. @staticmethod
  623. def remove_recreate_dirs(*args):
  624. """Remove directories if they exist, and create them as empty."""
  625. Context.remove_dirs(*args)
  626. for dir in args:
  627. os.makedirs(dir, exist_ok=True)
  628. def add_makefile_cmdlist(self, target, cmdlist, logsdir):
  629. """Add makefile text for a list of commands."""
  630. commands = cmdlist.makefile_commands(self.wrapper, logsdir)
  631. self.makefile_pieces.append('all: %s\n.PHONY: %s\n%s:\n%s\n' %
  632. (target, target, target, commands))
  633. self.status_log_list.extend(cmdlist.status_logs(logsdir))
  634. def write_files(self):
  635. """Write out the Makefile and wrapper script."""
  636. mftext = ''.join(self.makefile_pieces)
  637. with open(self.makefile, 'w') as f:
  638. f.write(mftext)
  639. wrapper_text = (
  640. '#!/bin/sh\n'
  641. 'prev_base=$1\n'
  642. 'this_base=$2\n'
  643. 'desc=$3\n'
  644. 'dir=$4\n'
  645. 'path=$5\n'
  646. 'shift 5\n'
  647. 'prev_status=$prev_base-status.txt\n'
  648. 'this_status=$this_base-status.txt\n'
  649. 'this_log=$this_base-log.txt\n'
  650. 'date > "$this_log"\n'
  651. 'echo >> "$this_log"\n'
  652. 'echo "Description: $desc" >> "$this_log"\n'
  653. 'printf "%s" "Command:" >> "$this_log"\n'
  654. 'for word in "$@"; do\n'
  655. ' if expr "$word" : "[]+,./0-9@A-Z_a-z-]\\\\{1,\\\\}\\$" > /dev/null; then\n'
  656. ' printf " %s" "$word"\n'
  657. ' else\n'
  658. ' printf " \'"\n'
  659. ' printf "%s" "$word" | sed -e "s/\'/\'\\\\\\\\\'\'/"\n'
  660. ' printf "\'"\n'
  661. ' fi\n'
  662. 'done >> "$this_log"\n'
  663. 'echo >> "$this_log"\n'
  664. 'echo "Directory: $dir" >> "$this_log"\n'
  665. 'echo "Path addition: $path" >> "$this_log"\n'
  666. 'echo >> "$this_log"\n'
  667. 'record_status ()\n'
  668. '{\n'
  669. ' echo >> "$this_log"\n'
  670. ' echo "$1: $desc" > "$this_status"\n'
  671. ' echo "$1: $desc" >> "$this_log"\n'
  672. ' echo >> "$this_log"\n'
  673. ' date >> "$this_log"\n'
  674. ' echo "$1: $desc"\n'
  675. ' exit 0\n'
  676. '}\n'
  677. 'check_error ()\n'
  678. '{\n'
  679. ' if [ "$1" != "0" ]; then\n'
  680. ' record_status FAIL\n'
  681. ' fi\n'
  682. '}\n'
  683. 'if [ "$prev_base" ] && ! grep -q "^PASS" "$prev_status"; then\n'
  684. ' record_status UNRESOLVED\n'
  685. 'fi\n'
  686. 'if [ "$dir" ]; then\n'
  687. ' cd "$dir"\n'
  688. ' check_error "$?"\n'
  689. 'fi\n'
  690. 'if [ "$path" ]; then\n'
  691. ' PATH=$path:$PATH\n'
  692. 'fi\n'
  693. '"$@" < /dev/null >> "$this_log" 2>&1\n'
  694. 'check_error "$?"\n'
  695. 'record_status PASS\n')
  696. with open(self.wrapper, 'w') as f:
  697. f.write(wrapper_text)
  698. # Mode 0o755.
  699. mode_exec = (stat.S_IRWXU|stat.S_IRGRP|stat.S_IXGRP|
  700. stat.S_IROTH|stat.S_IXOTH)
  701. os.chmod(self.wrapper, mode_exec)
  702. save_logs_text = (
  703. '#!/bin/sh\n'
  704. 'if ! [ -f tests.sum ]; then\n'
  705. ' echo "No test summary available."\n'
  706. ' exit 0\n'
  707. 'fi\n'
  708. 'save_file ()\n'
  709. '{\n'
  710. ' echo "Contents of $1:"\n'
  711. ' echo\n'
  712. ' cat "$1"\n'
  713. ' echo\n'
  714. ' echo "End of contents of $1."\n'
  715. ' echo\n'
  716. '}\n'
  717. 'save_file tests.sum\n'
  718. 'non_pass_tests=$(grep -v "^PASS: " tests.sum | sed -e "s/^PASS: //")\n'
  719. 'for t in $non_pass_tests; do\n'
  720. ' if [ -f "$t.out" ]; then\n'
  721. ' save_file "$t.out"\n'
  722. ' fi\n'
  723. 'done\n')
  724. with open(self.save_logs, 'w') as f:
  725. f.write(save_logs_text)
  726. os.chmod(self.save_logs, mode_exec)
  727. def do_build(self):
  728. """Do the actual build."""
  729. cmd = ['make', '-O', '-j%d' % self.parallelism]
  730. subprocess.run(cmd, cwd=self.builddir, check=True)
  731. def build_host_libraries(self):
  732. """Build the host libraries."""
  733. installdir = self.host_libraries_installdir
  734. builddir = os.path.join(self.builddir, 'host-libraries')
  735. logsdir = os.path.join(self.logsdir, 'host-libraries')
  736. self.remove_recreate_dirs(installdir, builddir, logsdir)
  737. cmdlist = CommandList('host-libraries', self.keep)
  738. # This CFLAGS setting works around GMP 6.3.0's configure
  739. # script being incompatible with compilers defaulting to C23
  740. # and should be removed when this script is updated to use a
  741. # release of GMP from after that configure test was fixed in
  742. # Jan 2025.
  743. self.build_host_library(cmdlist, 'gmp',
  744. ['CFLAGS=-Wall -O2 -std=gnu17'])
  745. self.build_host_library(cmdlist, 'mpfr',
  746. ['--with-gmp=%s' % installdir])
  747. self.build_host_library(cmdlist, 'mpc',
  748. ['--with-gmp=%s' % installdir,
  749. '--with-mpfr=%s' % installdir])
  750. cmdlist.add_command('done', ['touch', os.path.join(installdir, 'ok')])
  751. self.add_makefile_cmdlist('host-libraries', cmdlist, logsdir)
  752. def build_host_library(self, cmdlist, lib, extra_opts=None):
  753. """Build one host library."""
  754. srcdir = self.component_srcdir(lib)
  755. builddir = self.component_builddir('host-libraries', None, lib)
  756. installdir = self.host_libraries_installdir
  757. cmdlist.push_subdesc(lib)
  758. cmdlist.create_use_dir(builddir)
  759. cfg_cmd = [os.path.join(srcdir, 'configure'),
  760. '--prefix=%s' % installdir,
  761. '--disable-shared']
  762. if extra_opts:
  763. cfg_cmd.extend(extra_opts)
  764. cmdlist.add_command('configure', cfg_cmd)
  765. cmdlist.add_command('build', ['make'])
  766. cmdlist.add_command('check', ['make', 'check'])
  767. cmdlist.add_command('install', ['make', 'install'])
  768. cmdlist.cleanup_dir()
  769. cmdlist.pop_subdesc()
  770. def build_compilers(self, configs):
  771. """Build the compilers."""
  772. if not configs:
  773. self.remove_dirs(os.path.join(self.builddir, 'compilers'))
  774. self.remove_dirs(os.path.join(self.installdir, 'compilers'))
  775. self.remove_dirs(os.path.join(self.logsdir, 'compilers'))
  776. configs = sorted(self.configs.keys())
  777. for c in configs:
  778. self.configs[c].build()
  779. def build_glibcs(self, configs):
  780. """Build the glibcs."""
  781. if not configs:
  782. self.remove_dirs(os.path.join(self.builddir, 'glibcs'))
  783. self.remove_dirs(os.path.join(self.installdir, 'glibcs'))
  784. self.remove_dirs(os.path.join(self.logsdir, 'glibcs'))
  785. configs = sorted(self.glibc_configs.keys())
  786. for c in configs:
  787. self.glibc_configs[c].build()
  788. def update_syscalls(self, configs):
  789. """Update the glibc syscall lists."""
  790. if not configs:
  791. self.remove_dirs(os.path.join(self.builddir, 'update-syscalls'))
  792. self.remove_dirs(os.path.join(self.logsdir, 'update-syscalls'))
  793. configs = sorted(self.glibc_configs.keys())
  794. for c in configs:
  795. self.glibc_configs[c].update_syscalls()
  796. def load_versions_json(self):
  797. """Load information about source directory versions."""
  798. if not os.access(self.versions_json, os.F_OK):
  799. self.versions = {}
  800. return
  801. with open(self.versions_json, 'r') as f:
  802. self.versions = json.load(f)
  803. def store_json(self, data, filename):
  804. """Store information in a JSON file."""
  805. filename_tmp = filename + '.tmp'
  806. with open(filename_tmp, 'w') as f:
  807. json.dump(data, f, indent=2, sort_keys=True)
  808. os.rename(filename_tmp, filename)
  809. def store_versions_json(self):
  810. """Store information about source directory versions."""
  811. self.store_json(self.versions, self.versions_json)
  812. def set_component_version(self, component, version, explicit, revision):
  813. """Set the version information for a component."""
  814. self.versions[component] = {'version': version,
  815. 'explicit': explicit,
  816. 'revision': revision}
  817. self.store_versions_json()
  818. def checkout(self, versions):
  819. """Check out the desired component versions."""
  820. default_versions = {'binutils': 'vcs-2.45',
  821. 'gcc': 'vcs-15',
  822. 'glibc': 'vcs-mainline',
  823. 'gmp': '6.3.0',
  824. 'linux': '6.18',
  825. 'mpc': '1.3.1',
  826. 'mpfr': '4.2.2',
  827. 'mig': 'vcs-mainline',
  828. 'gnumach': 'vcs-mainline',
  829. 'hurd': 'vcs-mainline'}
  830. use_versions = {}
  831. explicit_versions = {}
  832. for v in versions:
  833. found_v = False
  834. for k in default_versions.keys():
  835. kx = k + '-'
  836. if v.startswith(kx):
  837. vx = v[len(kx):]
  838. if k in use_versions:
  839. print('error: multiple versions for %s' % k)
  840. exit(1)
  841. use_versions[k] = vx
  842. explicit_versions[k] = True
  843. found_v = True
  844. break
  845. if not found_v:
  846. print('error: unknown component in %s' % v)
  847. exit(1)
  848. for k in default_versions.keys():
  849. if k not in use_versions:
  850. if k in self.versions and self.versions[k]['explicit']:
  851. use_versions[k] = self.versions[k]['version']
  852. explicit_versions[k] = True
  853. else:
  854. use_versions[k] = default_versions[k]
  855. explicit_versions[k] = False
  856. os.makedirs(self.srcdir, exist_ok=True)
  857. for k in sorted(default_versions.keys()):
  858. update = os.access(self.component_srcdir(k), os.F_OK)
  859. v = use_versions[k]
  860. if (update and
  861. k in self.versions and
  862. v != self.versions[k]['version']):
  863. if not self.replace_sources:
  864. print('error: version of %s has changed from %s to %s, '
  865. 'use --replace-sources to check out again' %
  866. (k, self.versions[k]['version'], v))
  867. exit(1)
  868. shutil.rmtree(self.component_srcdir(k))
  869. update = False
  870. if v.startswith('vcs-'):
  871. revision = self.checkout_vcs(k, v[4:], update)
  872. else:
  873. self.checkout_tar(k, v, update)
  874. revision = v
  875. self.set_component_version(k, v, explicit_versions[k], revision)
  876. if self.get_script_text() != self.script_text:
  877. # Rerun the checkout process in case the updated script
  878. # uses different default versions or new components.
  879. self.exec_self()
  880. def checkout_vcs(self, component, version, update):
  881. """Check out the given version of the given component from version
  882. control. Return a revision identifier."""
  883. if component == 'binutils':
  884. git_url = get_git_url(component)
  885. if version == 'mainline':
  886. git_branch = 'master'
  887. else:
  888. trans = str.maketrans({'.': '_'})
  889. git_branch = 'binutils-%s-branch' % version.translate(trans)
  890. return self.git_checkout(component, git_url, git_branch, update)
  891. elif component == 'gcc':
  892. if version == 'mainline':
  893. branch = 'master'
  894. else:
  895. branch = 'releases/gcc-%s' % version
  896. return self.gcc_checkout(branch, update)
  897. elif component == 'glibc':
  898. git_url = get_git_url(component)
  899. if version == 'mainline':
  900. git_branch = 'master'
  901. else:
  902. git_branch = 'release/%s/master' % version
  903. r = self.git_checkout(component, git_url, git_branch, update)
  904. self.fix_glibc_timestamps()
  905. return r
  906. elif component == 'gnumach':
  907. git_url = get_git_url(component)
  908. git_branch = 'master'
  909. r = self.git_checkout(component, git_url, git_branch, update)
  910. subprocess.run(['autoreconf', '-i'],
  911. cwd=self.component_srcdir(component), check=True)
  912. return r
  913. elif component == 'mig':
  914. git_url = get_git_url(component)
  915. git_branch = 'master'
  916. r = self.git_checkout(component, git_url, git_branch, update)
  917. subprocess.run(['autoreconf', '-i'],
  918. cwd=self.component_srcdir(component), check=True)
  919. return r
  920. elif component == 'hurd':
  921. git_url = get_git_url(component)
  922. git_branch = 'master'
  923. r = self.git_checkout(component, git_url, git_branch, update)
  924. subprocess.run(['autoconf'],
  925. cwd=self.component_srcdir(component), check=True)
  926. return r
  927. else:
  928. print('error: component %s coming from VCS' % component)
  929. exit(1)
  930. def git_checkout(self, component, git_url, git_branch, update):
  931. """Check out a component from git. Return a commit identifier."""
  932. if update:
  933. subprocess.run(['git', 'remote', 'prune', 'origin'],
  934. cwd=self.component_srcdir(component), check=True)
  935. if self.replace_sources:
  936. subprocess.run(['git', 'remote', 'set-url', 'origin', git_url],
  937. cwd=self.component_srcdir(component), check=True)
  938. subprocess.run(['git', 'clean', '-dxfq'],
  939. cwd=self.component_srcdir(component), check=True)
  940. else:
  941. r = subprocess.run(['git', 'remote', 'get-url', 'origin'],
  942. cwd=self.component_srcdir(component),
  943. stdout=subprocess.PIPE,
  944. check=True, universal_newlines=True).stdout
  945. if r.rstrip() != git_url:
  946. print('error: origin url has changed from %s to %s, '
  947. 'use --replace-sources to check out again' %
  948. (r.rstrip(), git_url))
  949. exit(1)
  950. subprocess.run(['git', 'pull', '-q'],
  951. cwd=self.component_srcdir(component), check=True)
  952. else:
  953. if self.shallow:
  954. depth_arg = ('--depth', '1')
  955. else:
  956. depth_arg = ()
  957. subprocess.run(['git', 'clone', '-q', '-b', git_branch,
  958. *depth_arg, git_url,
  959. self.component_srcdir(component)], check=True)
  960. r = subprocess.run(['git', 'rev-parse', 'HEAD'],
  961. cwd=self.component_srcdir(component),
  962. stdout=subprocess.PIPE,
  963. check=True, universal_newlines=True).stdout
  964. return r.rstrip()
  965. def fix_glibc_timestamps(self):
  966. """Fix timestamps in a glibc checkout."""
  967. # Ensure that builds do not try to regenerate generated files
  968. # in the source tree.
  969. srcdir = self.component_srcdir('glibc')
  970. # These files have Makefile dependencies to regenerate them in
  971. # the source tree that may be active during a normal build.
  972. # Some other files have such dependencies but do not need to
  973. # be touched because nothing in a build depends on the files
  974. # in question.
  975. for f in ('sysdeps/mach/hurd/bits/errno.h',):
  976. to_touch = os.path.join(srcdir, f)
  977. subprocess.run(['touch', '-c', to_touch], check=True)
  978. for dirpath, dirnames, filenames in os.walk(srcdir):
  979. for f in filenames:
  980. if (f == 'configure' or
  981. f == 'preconfigure' or
  982. f.endswith('-kw.h')):
  983. to_touch = os.path.join(dirpath, f)
  984. subprocess.run(['touch', to_touch], check=True)
  985. def gcc_checkout(self, branch, update):
  986. """Check out GCC from git. Return the commit identifier."""
  987. if os.access(os.path.join(self.component_srcdir('gcc'), '.svn'),
  988. os.F_OK):
  989. if not self.replace_sources:
  990. print('error: GCC has moved from SVN to git, use '
  991. '--replace-sources to check out again')
  992. exit(1)
  993. shutil.rmtree(self.component_srcdir('gcc'))
  994. update = False
  995. if not update:
  996. self.git_checkout('gcc', get_git_url('gcc'),
  997. branch, update)
  998. subprocess.run(['contrib/gcc_update', '--silent'],
  999. cwd=self.component_srcdir('gcc'), check=True)
  1000. r = subprocess.run(['git', 'rev-parse', 'HEAD'],
  1001. cwd=self.component_srcdir('gcc'),
  1002. stdout=subprocess.PIPE,
  1003. check=True, universal_newlines=True).stdout
  1004. return r.rstrip()
  1005. def checkout_tar(self, component, version, update):
  1006. """Check out the given version of the given component from a
  1007. tarball."""
  1008. if update:
  1009. return
  1010. url_map = {
  1011. 'binutils': '%(baseurl)s/gnu/binutils/binutils-%(version)s.tar.bz2',
  1012. 'gcc': '%(baseurl)s/gnu/gcc/gcc-%(version)s/gcc-%(version)s.tar.gz',
  1013. 'gmp': '%(baseurl)s/gnu/gmp/gmp-%(version)s.tar.xz',
  1014. 'linux': 'https://www.kernel.org/pub/linux/kernel/v%(major)s.x/linux-%(version)s.tar.xz',
  1015. 'mpc': '%(baseurl)s/gnu/mpc/mpc-%(version)s.tar.gz',
  1016. 'mpfr': '%(baseurl)s/gnu/mpfr/mpfr-%(version)s.tar.xz',
  1017. 'mig': '%(baseurl)s/gnu/mig/mig-%(version)s.tar.bz2',
  1018. 'gnumach': '%(baseurl)s/gnu/gnumach/gnumach-%(version)s.tar.bz2',
  1019. 'hurd': '%(baseurl)s/gnu/hurd/hurd-%(version)s.tar.bz2',
  1020. }
  1021. if component not in url_map:
  1022. print('error: component %s coming from tarball' % component)
  1023. exit(1)
  1024. version_major = version.split('.')[0]
  1025. baseurl = os.environ.get('FTP_GNU_ORG_MIRROR' , 'https://ftp.gnu.org').rstrip('/')
  1026. url = url_map[component] % {'version': version, 'major': version_major, 'baseurl': baseurl}
  1027. filename = os.path.join(self.srcdir, url.split('/')[-1])
  1028. try:
  1029. with urllib.request.urlopen(url) as response:
  1030. data = response.read()
  1031. except:
  1032. raise IOError('downloading ' + repr(url))
  1033. with open(filename, 'wb') as f:
  1034. f.write(data)
  1035. subprocess.run(['tar', '-C', self.srcdir, '-x', '-f', filename],
  1036. check=True)
  1037. os.rename(os.path.join(self.srcdir, '%s-%s' % (component, version)),
  1038. self.component_srcdir(component))
  1039. os.remove(filename)
  1040. def load_build_state_json(self):
  1041. """Load information about the state of previous builds."""
  1042. if os.access(self.build_state_json, os.F_OK):
  1043. with open(self.build_state_json, 'r') as f:
  1044. self.build_state = json.load(f)
  1045. else:
  1046. self.build_state = {}
  1047. for k in ('host-libraries', 'compilers', 'glibcs', 'update-syscalls'):
  1048. if k not in self.build_state:
  1049. self.build_state[k] = {}
  1050. if 'build-time' not in self.build_state[k]:
  1051. self.build_state[k]['build-time'] = ''
  1052. if 'build-versions' not in self.build_state[k]:
  1053. self.build_state[k]['build-versions'] = {}
  1054. if 'build-results' not in self.build_state[k]:
  1055. self.build_state[k]['build-results'] = {}
  1056. if 'result-changes' not in self.build_state[k]:
  1057. self.build_state[k]['result-changes'] = {}
  1058. if 'ever-passed' not in self.build_state[k]:
  1059. self.build_state[k]['ever-passed'] = []
  1060. def store_build_state_json(self):
  1061. """Store information about the state of previous builds."""
  1062. self.store_json(self.build_state, self.build_state_json)
  1063. def clear_last_build_state(self, action):
  1064. """Clear information about the state of part of the build."""
  1065. # We clear the last build time and versions when starting a
  1066. # new build. The results of the last build are kept around,
  1067. # as comparison is still meaningful if this build is aborted
  1068. # and a new one started.
  1069. self.build_state[action]['build-time'] = ''
  1070. self.build_state[action]['build-versions'] = {}
  1071. self.store_build_state_json()
  1072. def update_build_state(self, action, build_time, build_versions):
  1073. """Update the build state after a build."""
  1074. build_time = build_time.replace(microsecond=0)
  1075. self.build_state[action]['build-time'] = build_time.strftime(
  1076. '%Y-%m-%d %H:%M:%S')
  1077. self.build_state[action]['build-versions'] = build_versions
  1078. build_results = {}
  1079. for log in self.status_log_list:
  1080. with open(log, 'r') as f:
  1081. log_text = f.read()
  1082. log_text = log_text.rstrip()
  1083. m = re.fullmatch('([A-Z]+): (.*)', log_text)
  1084. result = m.group(1)
  1085. test_name = m.group(2)
  1086. assert test_name not in build_results
  1087. build_results[test_name] = result
  1088. old_build_results = self.build_state[action]['build-results']
  1089. self.build_state[action]['build-results'] = build_results
  1090. result_changes = {}
  1091. all_tests = set(old_build_results.keys()) | set(build_results.keys())
  1092. for t in all_tests:
  1093. if t in old_build_results:
  1094. old_res = old_build_results[t]
  1095. else:
  1096. old_res = '(New test)'
  1097. if t in build_results:
  1098. new_res = build_results[t]
  1099. else:
  1100. new_res = '(Test removed)'
  1101. if old_res != new_res:
  1102. result_changes[t] = '%s -> %s' % (old_res, new_res)
  1103. self.build_state[action]['result-changes'] = result_changes
  1104. old_ever_passed = {t for t in self.build_state[action]['ever-passed']
  1105. if t in build_results}
  1106. new_passes = {t for t in build_results if build_results[t] == 'PASS'}
  1107. self.build_state[action]['ever-passed'] = sorted(old_ever_passed |
  1108. new_passes)
  1109. self.store_build_state_json()
  1110. def load_bot_config_json(self):
  1111. """Load bot configuration."""
  1112. with open(self.bot_config_json, 'r') as f:
  1113. self.bot_config = json.load(f)
  1114. def part_build_old(self, action, delay):
  1115. """Return whether the last build for a given action was at least a
  1116. given number of seconds ago, or does not have a time recorded."""
  1117. old_time_str = self.build_state[action]['build-time']
  1118. if not old_time_str:
  1119. return True
  1120. old_time = datetime.datetime.strptime(
  1121. old_time_str, '%Y-%m-%d %H:%M:%S').replace(
  1122. tzinfo=datetime.timezone.utc)
  1123. new_time = datetime.datetime.now(datetime.timezone.utc)
  1124. delta = new_time - old_time
  1125. return delta.total_seconds() >= delay
  1126. def bot_cycle(self):
  1127. """Run a single round of checkout and builds."""
  1128. print('Bot cycle starting %s.'
  1129. % str(datetime.datetime.now(datetime.timezone.utc)))
  1130. self.load_bot_config_json()
  1131. actions = ('host-libraries', 'compilers', 'glibcs')
  1132. self.bot_run_self(['--replace-sources'], 'checkout')
  1133. self.load_versions_json()
  1134. if self.get_script_text() != self.script_text:
  1135. print('Script changed, re-execing.')
  1136. # On script change, all parts of the build should be rerun.
  1137. for a in actions:
  1138. self.clear_last_build_state(a)
  1139. self.exec_self()
  1140. check_components = {'host-libraries': ('gmp', 'mpfr', 'mpc'),
  1141. 'compilers': ('binutils', 'gcc', 'glibc', 'linux',
  1142. 'mig', 'gnumach', 'hurd'),
  1143. 'glibcs': ('glibc',)}
  1144. must_build = {}
  1145. for a in actions:
  1146. build_vers = self.build_state[a]['build-versions']
  1147. must_build[a] = False
  1148. if not self.build_state[a]['build-time']:
  1149. must_build[a] = True
  1150. old_vers = {}
  1151. new_vers = {}
  1152. for c in check_components[a]:
  1153. if c in build_vers:
  1154. old_vers[c] = build_vers[c]
  1155. new_vers[c] = {'version': self.versions[c]['version'],
  1156. 'revision': self.versions[c]['revision']}
  1157. if new_vers == old_vers:
  1158. print('Versions for %s unchanged.' % a)
  1159. else:
  1160. print('Versions changed or rebuild forced for %s.' % a)
  1161. if a == 'compilers' and not self.part_build_old(
  1162. a, self.bot_config['compilers-rebuild-delay']):
  1163. print('Not requiring rebuild of compilers this soon.')
  1164. else:
  1165. must_build[a] = True
  1166. if must_build['host-libraries']:
  1167. must_build['compilers'] = True
  1168. if must_build['compilers']:
  1169. must_build['glibcs'] = True
  1170. for a in actions:
  1171. if must_build[a]:
  1172. print('Must rebuild %s.' % a)
  1173. self.clear_last_build_state(a)
  1174. else:
  1175. print('No need to rebuild %s.' % a)
  1176. if os.access(self.logsdir, os.F_OK):
  1177. shutil.rmtree(self.logsdir_old, ignore_errors=True)
  1178. shutil.copytree(self.logsdir, self.logsdir_old)
  1179. for a in actions:
  1180. if must_build[a]:
  1181. build_time = datetime.datetime.now(datetime.timezone.utc)
  1182. print('Rebuilding %s at %s.' % (a, str(build_time)))
  1183. self.bot_run_self([], a)
  1184. self.load_build_state_json()
  1185. self.bot_build_mail(a, build_time)
  1186. print('Bot cycle done at %s.'
  1187. % str(datetime.datetime.now(datetime.timezone.utc)))
  1188. def bot_build_mail(self, action, build_time):
  1189. """Send email with the results of a build."""
  1190. if not ('email-from' in self.bot_config and
  1191. 'email-server' in self.bot_config and
  1192. 'email-subject' in self.bot_config and
  1193. 'email-to' in self.bot_config):
  1194. if not self.email_warning:
  1195. print("Email not configured, not sending.")
  1196. self.email_warning = True
  1197. return
  1198. build_time = build_time.replace(microsecond=0)
  1199. subject = (self.bot_config['email-subject'] %
  1200. {'action': action,
  1201. 'build-time': build_time.strftime('%Y-%m-%d %H:%M:%S')})
  1202. results = self.build_state[action]['build-results']
  1203. changes = self.build_state[action]['result-changes']
  1204. ever_passed = set(self.build_state[action]['ever-passed'])
  1205. versions = self.build_state[action]['build-versions']
  1206. new_regressions = {k for k in changes if changes[k] == 'PASS -> FAIL'}
  1207. all_regressions = {k for k in ever_passed if results[k] == 'FAIL'}
  1208. all_fails = {k for k in results if results[k] == 'FAIL'}
  1209. if new_regressions:
  1210. new_reg_list = sorted(['FAIL: %s' % k for k in new_regressions])
  1211. new_reg_text = ('New regressions:\n\n%s\n\n' %
  1212. '\n'.join(new_reg_list))
  1213. else:
  1214. new_reg_text = ''
  1215. if all_regressions:
  1216. all_reg_list = sorted(['FAIL: %s' % k for k in all_regressions])
  1217. all_reg_text = ('All regressions:\n\n%s\n\n' %
  1218. '\n'.join(all_reg_list))
  1219. else:
  1220. all_reg_text = ''
  1221. if all_fails:
  1222. all_fail_list = sorted(['FAIL: %s' % k for k in all_fails])
  1223. all_fail_text = ('All failures:\n\n%s\n\n' %
  1224. '\n'.join(all_fail_list))
  1225. else:
  1226. all_fail_text = ''
  1227. if changes:
  1228. changes_list = sorted(changes.keys())
  1229. changes_list = ['%s: %s' % (changes[k], k) for k in changes_list]
  1230. changes_text = ('All changed results:\n\n%s\n\n' %
  1231. '\n'.join(changes_list))
  1232. else:
  1233. changes_text = ''
  1234. results_text = (new_reg_text + all_reg_text + all_fail_text +
  1235. changes_text)
  1236. if not results_text:
  1237. results_text = 'Clean build with unchanged results.\n\n'
  1238. versions_list = sorted(versions.keys())
  1239. versions_list = ['%s: %s (%s)' % (k, versions[k]['version'],
  1240. versions[k]['revision'])
  1241. for k in versions_list]
  1242. versions_text = ('Component versions for this build:\n\n%s\n' %
  1243. '\n'.join(versions_list))
  1244. body_text = results_text + versions_text
  1245. msg = email.mime.text.MIMEText(body_text)
  1246. msg['Subject'] = subject
  1247. msg['From'] = self.bot_config['email-from']
  1248. msg['To'] = self.bot_config['email-to']
  1249. msg['Message-ID'] = email.utils.make_msgid()
  1250. msg['Date'] = email.utils.format_datetime(
  1251. datetime.datetime.now(datetime.timezone.utc))
  1252. with smtplib.SMTP(self.bot_config['email-server']) as s:
  1253. s.send_message(msg)
  1254. def bot_run_self(self, opts, action, check=True):
  1255. """Run a copy of this script with given options."""
  1256. cmd = [sys.executable, sys.argv[0], '--keep=none',
  1257. '-j%d' % self.parallelism]
  1258. if self.full_gcc:
  1259. cmd.append('--full-gcc')
  1260. cmd.extend(opts)
  1261. cmd.extend([self.topdir, action])
  1262. sys.stdout.flush()
  1263. subprocess.run(cmd, check=check)
  1264. def bot(self):
  1265. """Run repeated rounds of checkout and builds."""
  1266. while True:
  1267. self.load_bot_config_json()
  1268. if not self.bot_config['run']:
  1269. print('Bot exiting by request.')
  1270. exit(0)
  1271. self.bot_run_self([], 'bot-cycle', check=False)
  1272. self.load_bot_config_json()
  1273. if not self.bot_config['run']:
  1274. print('Bot exiting by request.')
  1275. exit(0)
  1276. time.sleep(self.bot_config['delay'])
  1277. if self.get_script_text() != self.script_text:
  1278. print('Script changed, bot re-execing.')
  1279. self.exec_self()
  1280. class LinuxHeadersPolicyForBuild(object):
  1281. """Names and directories for installing Linux headers. Build variant."""
  1282. def __init__(self, config):
  1283. self.arch = config.arch
  1284. self.srcdir = config.ctx.component_srcdir('linux')
  1285. self.builddir = config.component_builddir('linux')
  1286. self.headers_dir = os.path.join(config.sysroot, 'usr')
  1287. class LinuxHeadersPolicyForUpdateSyscalls(object):
  1288. """Names and directories for Linux headers. update-syscalls variant."""
  1289. def __init__(self, glibc, headers_dir):
  1290. self.arch = glibc.compiler.arch
  1291. self.srcdir = glibc.compiler.ctx.component_srcdir('linux')
  1292. self.builddir = glibc.ctx.component_builddir(
  1293. 'update-syscalls', glibc.name, 'build-linux')
  1294. self.headers_dir = headers_dir
  1295. def install_linux_headers(policy, cmdlist):
  1296. """Install Linux kernel headers."""
  1297. arch_map = {'aarch64': 'arm64',
  1298. 'alpha': 'alpha',
  1299. 'arc': 'arc',
  1300. 'arm': 'arm',
  1301. 'csky': 'csky',
  1302. 'hppa': 'parisc',
  1303. 'i486': 'x86',
  1304. 'i586': 'x86',
  1305. 'i686': 'x86',
  1306. 'i786': 'x86',
  1307. 'loongarch64': 'loongarch',
  1308. 'm68k': 'm68k',
  1309. 'microblaze': 'microblaze',
  1310. 'mips': 'mips',
  1311. 'or1k': 'openrisc',
  1312. 'powerpc': 'powerpc',
  1313. 's390': 's390',
  1314. 'riscv32': 'riscv',
  1315. 'riscv64': 'riscv',
  1316. 'sh': 'sh',
  1317. 'sparc': 'sparc',
  1318. 'x86_64': 'x86'}
  1319. linux_arch = None
  1320. for k in arch_map:
  1321. if policy.arch.startswith(k):
  1322. linux_arch = arch_map[k]
  1323. break
  1324. assert linux_arch is not None
  1325. cmdlist.push_subdesc('linux')
  1326. cmdlist.create_use_dir(policy.builddir)
  1327. cmdlist.add_command('install-headers',
  1328. ['make', '-C', policy.srcdir, 'O=%s' % policy.builddir,
  1329. 'ARCH=%s' % linux_arch,
  1330. 'INSTALL_HDR_PATH=%s' % policy.headers_dir,
  1331. 'headers_install'])
  1332. cmdlist.cleanup_dir()
  1333. cmdlist.pop_subdesc()
  1334. class Config(object):
  1335. """A configuration for building a compiler and associated libraries."""
  1336. def __init__(self, ctx, arch, os_name, variant=None, gcc_cfg=None,
  1337. first_gcc_cfg=None, binutils_cfg=None, glibcs=None,
  1338. extra_glibcs=None):
  1339. """Initialize a Config object."""
  1340. self.ctx = ctx
  1341. self.arch = arch
  1342. self.os = os_name
  1343. self.variant = variant
  1344. if variant is None:
  1345. self.name = '%s-%s' % (arch, os_name)
  1346. else:
  1347. self.name = '%s-%s-%s' % (arch, os_name, variant)
  1348. self.triplet = '%s-glibc-%s' % (arch, os_name)
  1349. if gcc_cfg is None:
  1350. self.gcc_cfg = []
  1351. else:
  1352. self.gcc_cfg = gcc_cfg
  1353. if first_gcc_cfg is None:
  1354. self.first_gcc_cfg = []
  1355. else:
  1356. self.first_gcc_cfg = first_gcc_cfg
  1357. if binutils_cfg is None:
  1358. self.binutils_cfg = []
  1359. else:
  1360. self.binutils_cfg = binutils_cfg
  1361. if glibcs is None:
  1362. glibcs = [{'variant': variant}]
  1363. if extra_glibcs is None:
  1364. extra_glibcs = []
  1365. glibcs = [Glibc(self, **g) for g in glibcs]
  1366. extra_glibcs = [Glibc(self, **g) for g in extra_glibcs]
  1367. self.all_glibcs = glibcs + extra_glibcs
  1368. self.compiler_glibcs = glibcs
  1369. self.installdir = ctx.compiler_installdir(self.name)
  1370. self.bindir = ctx.compiler_bindir(self.name)
  1371. self.sysroot = ctx.compiler_sysroot(self.name)
  1372. self.builddir = os.path.join(ctx.builddir, 'compilers', self.name)
  1373. self.logsdir = os.path.join(ctx.logsdir, 'compilers', self.name)
  1374. def component_builddir(self, component):
  1375. """Return the directory to use for a (non-glibc) build."""
  1376. return self.ctx.component_builddir('compilers', self.name, component)
  1377. def build(self):
  1378. """Generate commands to build this compiler."""
  1379. self.ctx.remove_recreate_dirs(self.installdir, self.builddir,
  1380. self.logsdir)
  1381. cmdlist = CommandList('compilers-%s' % self.name, self.ctx.keep)
  1382. cmdlist.add_command('check-host-libraries',
  1383. ['test', '-f',
  1384. os.path.join(self.ctx.host_libraries_installdir,
  1385. 'ok')])
  1386. cmdlist.use_path(self.bindir)
  1387. self.build_cross_tool(cmdlist, 'binutils', 'binutils',
  1388. ['--disable-gdb',
  1389. '--disable-gdbserver',
  1390. '--disable-libdecnumber',
  1391. '--disable-readline',
  1392. '--disable-sim'] + self.binutils_cfg)
  1393. if self.os.startswith('linux'):
  1394. install_linux_headers(LinuxHeadersPolicyForBuild(self), cmdlist)
  1395. self.build_gcc(cmdlist, True)
  1396. if self.os == 'gnu':
  1397. self.install_gnumach_headers(cmdlist)
  1398. self.build_cross_tool(cmdlist, 'mig', 'mig')
  1399. self.install_hurd_headers(cmdlist)
  1400. for g in self.compiler_glibcs:
  1401. cmdlist.push_subdesc('glibc')
  1402. cmdlist.push_subdesc(g.name)
  1403. g.build_glibc(cmdlist, GlibcPolicyForCompiler(g))
  1404. cmdlist.pop_subdesc()
  1405. cmdlist.pop_subdesc()
  1406. self.build_gcc(cmdlist, False)
  1407. cmdlist.add_command('done', ['touch',
  1408. os.path.join(self.installdir, 'ok')])
  1409. self.ctx.add_makefile_cmdlist('compilers-%s' % self.name, cmdlist,
  1410. self.logsdir)
  1411. def build_cross_tool(self, cmdlist, tool_src, tool_build, extra_opts=None):
  1412. """Build one cross tool."""
  1413. srcdir = self.ctx.component_srcdir(tool_src)
  1414. builddir = self.component_builddir(tool_build)
  1415. cmdlist.push_subdesc(tool_build)
  1416. cmdlist.create_use_dir(builddir)
  1417. cfg_cmd = [os.path.join(srcdir, 'configure'),
  1418. '--prefix=%s' % self.installdir,
  1419. '--build=%s' % self.ctx.build_triplet,
  1420. '--host=%s' % self.ctx.build_triplet,
  1421. '--target=%s' % self.triplet,
  1422. '--with-sysroot=%s' % self.sysroot]
  1423. if extra_opts:
  1424. cfg_cmd.extend(extra_opts)
  1425. cmdlist.add_command('configure', cfg_cmd)
  1426. cmdlist.add_command('build', ['make'])
  1427. # Parallel "make install" for GCC has race conditions that can
  1428. # cause it to fail; see
  1429. # <https://gcc.gnu.org/bugzilla/show_bug.cgi?id=42980>. Such
  1430. # problems are not known for binutils, but doing the
  1431. # installation in parallel within a particular toolchain build
  1432. # (as opposed to installation of one toolchain from
  1433. # build-many-glibcs.py running in parallel to the installation
  1434. # of other toolchains being built) is not known to be
  1435. # significantly beneficial, so it is simplest just to disable
  1436. # parallel install for cross tools here.
  1437. cmdlist.add_command('install', ['make', '-j1', 'install'])
  1438. cmdlist.cleanup_dir()
  1439. cmdlist.pop_subdesc()
  1440. def install_gnumach_headers(self, cmdlist):
  1441. """Install GNU Mach headers."""
  1442. srcdir = self.ctx.component_srcdir('gnumach')
  1443. builddir = self.component_builddir('gnumach')
  1444. cmdlist.push_subdesc('gnumach')
  1445. cmdlist.create_use_dir(builddir)
  1446. cmdlist.add_command('configure',
  1447. [os.path.join(srcdir, 'configure'),
  1448. '--build=%s' % self.ctx.build_triplet,
  1449. '--host=%s' % self.triplet,
  1450. '--prefix=',
  1451. '--disable-user32',
  1452. 'CC=%s-gcc -nostdlib' % self.triplet])
  1453. cmdlist.add_command('install', ['make', 'DESTDIR=%s' % self.sysroot,
  1454. 'install-data'])
  1455. cmdlist.cleanup_dir()
  1456. cmdlist.pop_subdesc()
  1457. def install_hurd_headers(self, cmdlist):
  1458. """Install Hurd headers."""
  1459. srcdir = self.ctx.component_srcdir('hurd')
  1460. builddir = self.component_builddir('hurd')
  1461. cmdlist.push_subdesc('hurd')
  1462. cmdlist.create_use_dir(builddir)
  1463. cmdlist.add_command('configure',
  1464. [os.path.join(srcdir, 'configure'),
  1465. '--build=%s' % self.ctx.build_triplet,
  1466. '--host=%s' % self.triplet,
  1467. '--prefix=',
  1468. '--disable-profile', '--without-parted',
  1469. 'CC=%s-gcc -nostdlib' % self.triplet])
  1470. cmdlist.add_command('install', ['make', 'prefix=%s' % self.sysroot,
  1471. 'no_deps=t', 'install-headers'])
  1472. cmdlist.cleanup_dir()
  1473. cmdlist.pop_subdesc()
  1474. def build_gcc(self, cmdlist, bootstrap):
  1475. """Build GCC."""
  1476. # libssp is of little relevance with glibc's own stack
  1477. # checking support. libcilkrts does not support GNU/Hurd (and
  1478. # has been removed in GCC 8, so --disable-libcilkrts can be
  1479. # removed once glibc no longer supports building with older
  1480. # GCC versions). --enable-initfini-array is enabled by default
  1481. # in GCC 12, which can be removed when GCC 12 becomes the
  1482. # minimum requirement.
  1483. cfg_opts = list(self.gcc_cfg)
  1484. cfg_opts += ['--enable-initfini-array']
  1485. cfg_opts += ['--disable-libssp', '--disable-libcilkrts']
  1486. host_libs = self.ctx.host_libraries_installdir
  1487. cfg_opts += ['--with-gmp=%s' % host_libs,
  1488. '--with-mpfr=%s' % host_libs,
  1489. '--with-mpc=%s' % host_libs]
  1490. if bootstrap:
  1491. tool_build = 'gcc-first'
  1492. # Building a static-only, C-only compiler that is
  1493. # sufficient to build glibc. Various libraries and
  1494. # features that may require libc headers must be disabled.
  1495. # When configuring with a sysroot, --with-newlib is
  1496. # required to define inhibit_libc (to stop some parts of
  1497. # libgcc including libc headers); --without-headers is not
  1498. # sufficient.
  1499. cfg_opts += ['--enable-languages=c', '--disable-shared',
  1500. '--disable-threads',
  1501. '--disable-libatomic',
  1502. '--disable-decimal-float',
  1503. '--disable-gcov',
  1504. '--disable-libffi',
  1505. '--disable-libgomp',
  1506. '--disable-libitm',
  1507. '--disable-libmpx',
  1508. '--disable-libquadmath',
  1509. '--disable-libsanitizer',
  1510. '--without-headers', '--with-newlib',
  1511. '--with-glibc-version=%s' % self.ctx.glibc_version
  1512. ]
  1513. cfg_opts += self.first_gcc_cfg
  1514. else:
  1515. tool_build = 'gcc'
  1516. # libsanitizer commonly breaks because of glibc header
  1517. # changes, or on unusual targets. C++ pre-compiled
  1518. # headers are not used during the glibc build and are
  1519. # expensive to create.
  1520. if not self.ctx.full_gcc:
  1521. cfg_opts += ['--disable-libsanitizer',
  1522. '--disable-libstdcxx-pch']
  1523. langs = 'all' if self.ctx.full_gcc else 'c,c++'
  1524. cfg_opts += ['--enable-languages=%s' % langs,
  1525. '--enable-shared', '--enable-threads']
  1526. self.build_cross_tool(cmdlist, 'gcc', tool_build, cfg_opts)
  1527. class GlibcPolicyDefault(object):
  1528. """Build policy for glibc: common defaults."""
  1529. def __init__(self, glibc):
  1530. self.srcdir = glibc.ctx.component_srcdir('glibc')
  1531. self.use_usr = glibc.os != 'gnu'
  1532. self.prefix = '/usr' if self.use_usr else ''
  1533. self.configure_args = [
  1534. '--prefix=%s' % self.prefix,
  1535. '--enable-profile',
  1536. '--build=%s' % glibc.ctx.build_triplet,
  1537. '--host=%s' % glibc.triplet,
  1538. 'CC=%s' % glibc.tool_name('gcc'),
  1539. 'CXX=%s' % glibc.tool_name('g++'),
  1540. ]
  1541. if glibc.os == 'gnu':
  1542. self.configure_args.append('MIG=%s' % glibc.tool_name('mig'))
  1543. if glibc.cflags:
  1544. self.configure_args.append('CFLAGS=%s' % glibc.cflags)
  1545. self.configure_args.append('CXXFLAGS=%s' % glibc.cflags)
  1546. self.configure_args += glibc.cfg
  1547. def configure(self, cmdlist):
  1548. """Invoked to add the configure command to the command list."""
  1549. cmdlist.add_command('configure',
  1550. [os.path.join(self.srcdir, 'configure'),
  1551. *self.configure_args])
  1552. def extra_commands(self, cmdlist):
  1553. """Invoked to inject additional commands (make check) after build."""
  1554. pass
  1555. class GlibcPolicyForCompiler(GlibcPolicyDefault):
  1556. """Build policy for glibc during the compilers stage."""
  1557. def __init__(self, glibc):
  1558. super().__init__(glibc)
  1559. self.builddir = glibc.ctx.component_builddir(
  1560. 'compilers', glibc.compiler.name, 'glibc', glibc.name)
  1561. self.installdir = glibc.compiler.sysroot
  1562. class GlibcPolicyForBuild(GlibcPolicyDefault):
  1563. """Build policy for glibc during the glibcs stage."""
  1564. def __init__(self, glibc):
  1565. super().__init__(glibc)
  1566. self.builddir = glibc.ctx.component_builddir(
  1567. 'glibcs', glibc.name, 'glibc')
  1568. self.installdir = glibc.ctx.glibc_installdir(glibc.name)
  1569. if glibc.ctx.strip:
  1570. self.strip = glibc.tool_name('strip')
  1571. else:
  1572. self.strip = None
  1573. self.save_logs = glibc.ctx.save_logs
  1574. def extra_commands(self, cmdlist):
  1575. if self.strip:
  1576. # Avoid stripping libc.so and libpthread.so, which are
  1577. # linker scripts stored in /lib on Hurd.
  1578. find_command = 'find %s/lib* -name "*.so*"' % self.installdir
  1579. cmdlist.add_command('strip', ['sh', '-c', (
  1580. 'set -e; for f in $(%s); do '
  1581. 'if ! head -c16 $f | grep -q "GNU ld script"; then %s $f; fi; '
  1582. 'done' % (find_command, self.strip))])
  1583. cmdlist.add_command('check', ['make', 'check'])
  1584. cmdlist.add_command('save-logs', [self.save_logs], always_run=True)
  1585. class GlibcPolicyForUpdateSyscalls(GlibcPolicyDefault):
  1586. """Build policy for glibc during update-syscalls."""
  1587. def __init__(self, glibc):
  1588. super().__init__(glibc)
  1589. self.builddir = glibc.ctx.component_builddir(
  1590. 'update-syscalls', glibc.name, 'glibc')
  1591. self.linuxdir = glibc.ctx.component_builddir(
  1592. 'update-syscalls', glibc.name, 'linux')
  1593. self.linux_policy = LinuxHeadersPolicyForUpdateSyscalls(
  1594. glibc, self.linuxdir)
  1595. self.configure_args.insert(
  1596. 0, '--with-headers=%s' % os.path.join(self.linuxdir, 'include'))
  1597. # self.installdir not set because installation is not supported
  1598. class Glibc(object):
  1599. """A configuration for building glibc."""
  1600. def __init__(self, compiler, arch=None, os_name=None, variant=None,
  1601. cfg=None, ccopts=None, cflags=None):
  1602. """Initialize a Glibc object."""
  1603. self.ctx = compiler.ctx
  1604. self.compiler = compiler
  1605. if arch is None:
  1606. self.arch = compiler.arch
  1607. else:
  1608. self.arch = arch
  1609. if os_name is None:
  1610. self.os = compiler.os
  1611. else:
  1612. self.os = os_name
  1613. self.variant = variant
  1614. if variant is None:
  1615. self.name = '%s-%s' % (self.arch, self.os)
  1616. else:
  1617. self.name = '%s-%s-%s' % (self.arch, self.os, variant)
  1618. self.triplet = '%s-glibc-%s' % (self.arch, self.os)
  1619. if cfg is None:
  1620. self.cfg = []
  1621. else:
  1622. self.cfg = cfg
  1623. # ccopts contain ABI options and are passed to configure as CC / CXX.
  1624. self.ccopts = ccopts
  1625. # cflags contain non-ABI options like -g or -O and are passed to
  1626. # configure as CFLAGS / CXXFLAGS.
  1627. self.cflags = cflags
  1628. def tool_name(self, tool):
  1629. """Return the name of a cross-compilation tool."""
  1630. ctool = '%s-%s' % (self.compiler.triplet, tool)
  1631. if self.ccopts and (tool == 'gcc' or tool == 'g++'):
  1632. ctool = '%s %s' % (ctool, self.ccopts)
  1633. return ctool
  1634. def build(self):
  1635. """Generate commands to build this glibc."""
  1636. builddir = self.ctx.component_builddir('glibcs', self.name, 'glibc')
  1637. installdir = self.ctx.glibc_installdir(self.name)
  1638. logsdir = os.path.join(self.ctx.logsdir, 'glibcs', self.name)
  1639. self.ctx.remove_recreate_dirs(installdir, builddir, logsdir)
  1640. cmdlist = CommandList('glibcs-%s' % self.name, self.ctx.keep)
  1641. cmdlist.add_command('check-compilers',
  1642. ['test', '-f',
  1643. os.path.join(self.compiler.installdir, 'ok')])
  1644. cmdlist.use_path(self.compiler.bindir)
  1645. self.build_glibc(cmdlist, GlibcPolicyForBuild(self))
  1646. self.ctx.add_makefile_cmdlist('glibcs-%s' % self.name, cmdlist,
  1647. logsdir)
  1648. def build_glibc(self, cmdlist, policy):
  1649. """Generate commands to build this glibc, either as part of a compiler
  1650. build or with the bootstrapped compiler (and in the latter case, run
  1651. tests as well)."""
  1652. cmdlist.create_use_dir(policy.builddir)
  1653. policy.configure(cmdlist)
  1654. cmdlist.add_command('build', ['make'])
  1655. cmdlist.add_command('install', ['make', 'install',
  1656. 'install_root=%s' % policy.installdir])
  1657. # GCC uses paths such as lib/../lib64, so make sure lib
  1658. # directories always exist.
  1659. mkdir_cmd = ['mkdir', '-p',
  1660. os.path.join(policy.installdir, 'lib')]
  1661. if policy.use_usr:
  1662. mkdir_cmd += [os.path.join(policy.installdir, 'usr', 'lib')]
  1663. cmdlist.add_command('mkdir-lib', mkdir_cmd)
  1664. policy.extra_commands(cmdlist)
  1665. cmdlist.cleanup_dir()
  1666. def update_syscalls(self):
  1667. if self.os == 'gnu':
  1668. # Hurd does not have system call tables that need updating.
  1669. return
  1670. policy = GlibcPolicyForUpdateSyscalls(self)
  1671. logsdir = os.path.join(self.ctx.logsdir, 'update-syscalls', self.name)
  1672. self.ctx.remove_recreate_dirs(policy.builddir, logsdir)
  1673. cmdlist = CommandList('update-syscalls-%s' % self.name, self.ctx.keep)
  1674. cmdlist.add_command('check-compilers',
  1675. ['test', '-f',
  1676. os.path.join(self.compiler.installdir, 'ok')])
  1677. cmdlist.use_path(self.compiler.bindir)
  1678. install_linux_headers(policy.linux_policy, cmdlist)
  1679. cmdlist.create_use_dir(policy.builddir)
  1680. policy.configure(cmdlist)
  1681. cmdlist.add_command('build', ['make', 'update-syscall-lists'])
  1682. cmdlist.cleanup_dir()
  1683. self.ctx.add_makefile_cmdlist('update-syscalls-%s' % self.name,
  1684. cmdlist, logsdir)
  1685. class Command(object):
  1686. """A command run in the build process."""
  1687. def __init__(self, desc, num, dir, path, command, always_run=False):
  1688. """Initialize a Command object."""
  1689. self.dir = dir
  1690. self.path = path
  1691. self.desc = desc
  1692. trans = str.maketrans({' ': '-'})
  1693. self.logbase = '%03d-%s' % (num, desc.translate(trans))
  1694. self.command = command
  1695. self.always_run = always_run
  1696. @staticmethod
  1697. def shell_make_quote_string(s):
  1698. """Given a string not containing a newline, quote it for use by the
  1699. shell and make."""
  1700. assert '\n' not in s
  1701. if re.fullmatch('[]+,./0-9@A-Z_a-z-]+', s):
  1702. return s
  1703. strans = str.maketrans({"'": "'\\''"})
  1704. s = "'%s'" % s.translate(strans)
  1705. mtrans = str.maketrans({'$': '$$'})
  1706. return s.translate(mtrans)
  1707. @staticmethod
  1708. def shell_make_quote_list(l, translate_make):
  1709. """Given a list of strings not containing newlines, quote them for use
  1710. by the shell and make, returning a single string. If translate_make
  1711. is true and the first string is 'make', change it to $(MAKE)."""
  1712. l = [Command.shell_make_quote_string(s) for s in l]
  1713. if translate_make and l[0] == 'make':
  1714. l[0] = '$(MAKE)'
  1715. return ' '.join(l)
  1716. def shell_make_quote(self):
  1717. """Return this command quoted for the shell and make."""
  1718. return self.shell_make_quote_list(self.command, True)
  1719. class CommandList(object):
  1720. """A list of commands run in the build process."""
  1721. def __init__(self, desc, keep):
  1722. """Initialize a CommandList object."""
  1723. self.cmdlist = []
  1724. self.dir = None
  1725. self.path = None
  1726. self.desc = [desc]
  1727. self.keep = keep
  1728. def desc_txt(self, desc):
  1729. """Return the description to use for a command."""
  1730. return '%s %s' % (' '.join(self.desc), desc)
  1731. def use_dir(self, dir):
  1732. """Set the default directory for subsequent commands."""
  1733. self.dir = dir
  1734. def use_path(self, path):
  1735. """Set a directory to be prepended to the PATH for subsequent
  1736. commands."""
  1737. self.path = path
  1738. def push_subdesc(self, subdesc):
  1739. """Set the default subdescription for subsequent commands (e.g., the
  1740. name of a component being built, within the series of commands
  1741. building it)."""
  1742. self.desc.append(subdesc)
  1743. def pop_subdesc(self):
  1744. """Pop a subdescription from the list of descriptions."""
  1745. self.desc.pop()
  1746. def create_use_dir(self, dir):
  1747. """Remove and recreate a directory and use it for subsequent
  1748. commands."""
  1749. self.add_command_dir('rm', None, ['rm', '-rf', dir])
  1750. self.add_command_dir('mkdir', None, ['mkdir', '-p', dir])
  1751. self.use_dir(dir)
  1752. def add_command_dir(self, desc, dir, command, always_run=False):
  1753. """Add a command to run in a given directory."""
  1754. cmd = Command(self.desc_txt(desc), len(self.cmdlist), dir, self.path,
  1755. command, always_run)
  1756. self.cmdlist.append(cmd)
  1757. def add_command(self, desc, command, always_run=False):
  1758. """Add a command to run in the default directory."""
  1759. cmd = Command(self.desc_txt(desc), len(self.cmdlist), self.dir,
  1760. self.path, command, always_run)
  1761. self.cmdlist.append(cmd)
  1762. def cleanup_dir(self, desc='cleanup', dir=None):
  1763. """Clean up a build directory. If no directory is specified, the
  1764. default directory is cleaned up and ceases to be the default
  1765. directory."""
  1766. if dir is None:
  1767. dir = self.dir
  1768. self.use_dir(None)
  1769. if self.keep != 'all':
  1770. self.add_command_dir(desc, None, ['rm', '-rf', dir],
  1771. always_run=(self.keep == 'none'))
  1772. def makefile_commands(self, wrapper, logsdir):
  1773. """Return the sequence of commands in the form of text for a Makefile.
  1774. The given wrapper script takes arguments: base of logs for
  1775. previous command, or empty; base of logs for this command;
  1776. description; directory; PATH addition; the command itself."""
  1777. # prev_base is the base of the name for logs of the previous
  1778. # command that is not always-run (that is, a build command,
  1779. # whose failure should stop subsequent build commands from
  1780. # being run, as opposed to a cleanup command, which is run
  1781. # even if previous commands failed).
  1782. prev_base = ''
  1783. cmds = []
  1784. for c in self.cmdlist:
  1785. ctxt = c.shell_make_quote()
  1786. if prev_base and not c.always_run:
  1787. prev_log = os.path.join(logsdir, prev_base)
  1788. else:
  1789. prev_log = ''
  1790. this_log = os.path.join(logsdir, c.logbase)
  1791. if not c.always_run:
  1792. prev_base = c.logbase
  1793. if c.dir is None:
  1794. dir = ''
  1795. else:
  1796. dir = c.dir
  1797. if c.path is None:
  1798. path = ''
  1799. else:
  1800. path = c.path
  1801. prelims = [wrapper, prev_log, this_log, c.desc, dir, path]
  1802. prelim_txt = Command.shell_make_quote_list(prelims, False)
  1803. cmds.append('\t@%s %s' % (prelim_txt, ctxt))
  1804. return '\n'.join(cmds)
  1805. def status_logs(self, logsdir):
  1806. """Return the list of log files with command status."""
  1807. return [os.path.join(logsdir, '%s-status.txt' % c.logbase)
  1808. for c in self.cmdlist]
  1809. def get_parser():
  1810. """Return an argument parser for this module."""
  1811. parser = argparse.ArgumentParser(description=__doc__)
  1812. parser.add_argument('-j', dest='parallelism',
  1813. help='Run this number of jobs in parallel',
  1814. type=int, default=os.cpu_count())
  1815. parser.add_argument('--keep', dest='keep',
  1816. help='Whether to keep all build directories, '
  1817. 'none or only those from failed builds',
  1818. default='none', choices=('none', 'all', 'failed'))
  1819. parser.add_argument('--replace-sources', action='store_true',
  1820. help='Remove and replace source directories '
  1821. 'with the wrong version of a component')
  1822. parser.add_argument('--strip', action='store_true',
  1823. help='Strip installed glibc libraries')
  1824. parser.add_argument('--full-gcc', action='store_true',
  1825. help='Build GCC with all languages and libsanitizer')
  1826. parser.add_argument('--shallow', action='store_true',
  1827. help='Do not download Git history during checkout')
  1828. parser.add_argument('--exclude', dest='exclude',
  1829. help='Targets to be excluded', nargs='*')
  1830. parser.add_argument('topdir',
  1831. help='Toplevel working directory')
  1832. parser.add_argument('action',
  1833. help='What to do',
  1834. choices=('checkout', 'bot-cycle', 'bot',
  1835. 'host-libraries', 'compilers', 'glibcs',
  1836. 'update-syscalls', 'list-compilers',
  1837. 'list-glibcs'))
  1838. parser.add_argument('configs',
  1839. help='Versions to check out or configurations to build',
  1840. nargs='*')
  1841. return parser
  1842. def get_version_common(progname,line,word,arg1):
  1843. try:
  1844. out = subprocess.run([progname, arg1],
  1845. stdout=subprocess.PIPE,
  1846. stderr=subprocess.DEVNULL,
  1847. stdin=subprocess.DEVNULL,
  1848. check=True, universal_newlines=True)
  1849. v = out.stdout.splitlines()[line].split()[word]
  1850. v = re.match(r'[0-9]+(.[0-9]+)*', v).group()
  1851. return [int(x) for x in v.split('.')]
  1852. except:
  1853. return 'missing'
  1854. def get_version_common_stderr(progname,line,word,arg1):
  1855. try:
  1856. out = subprocess.run([progname, arg1],
  1857. stdout=subprocess.DEVNULL,
  1858. stderr=subprocess.PIPE,
  1859. stdin=subprocess.DEVNULL,
  1860. check=True, universal_newlines=True)
  1861. v = out.stderr.splitlines()[line].split()[word]
  1862. v = re.match(r'[0-9]+(.[0-9]+)*', v).group()
  1863. return [int(x) for x in v.split('.')]
  1864. except:
  1865. return 'missing'
  1866. def get_version(progname):
  1867. return get_version_common(progname, 0, -1, '--version')
  1868. def get_version_awk(progname):
  1869. return get_version_common(progname, 0, 2, '--version')
  1870. def get_version_bzip2(progname):
  1871. return get_version_common_stderr(progname, 0, 6, '-h')
  1872. def check_version(ver, req):
  1873. for v, r in zip(ver, req):
  1874. if v > r:
  1875. return True
  1876. if v < r:
  1877. return False
  1878. return True
  1879. def version_str(ver):
  1880. return '.'.join([str(x) for x in ver])
  1881. def check_for_required_tools():
  1882. get_list_of_required_tools()
  1883. count_old_tools = 0
  1884. count_missing_tools = 0
  1885. for k, v in REQUIRED_TOOLS.items():
  1886. version = v[0](k)
  1887. if version == 'missing':
  1888. ok = 'missing'
  1889. else:
  1890. ok = 'ok' if check_version(version, v[1]) else 'old'
  1891. if ok == 'old':
  1892. if count_old_tools == 0:
  1893. print("One or more required tools are too old:")
  1894. count_old_tools = count_old_tools + 1
  1895. print('{:9}: {:3} (obtained=\"{}\" required=\"{}\")'.format(k, ok,
  1896. version_str(version), version_str(v[1])))
  1897. if ok == 'missing':
  1898. if count_missing_tools == 0:
  1899. print("One or more required tools are missing:")
  1900. count_missing_tools = count_missing_tools + 1
  1901. print('{:9}: {:3} (required=\"{}\")'.format(k, ok,
  1902. version_str(v[1])))
  1903. if count_old_tools > 0 or count_missing_tools > 0:
  1904. exit(1)
  1905. def main(argv):
  1906. """The main entry point."""
  1907. check_for_required_tools()
  1908. parser = get_parser()
  1909. opts = parser.parse_args(argv)
  1910. topdir = os.path.abspath(opts.topdir)
  1911. ctx = Context(topdir, opts.parallelism, opts.keep, opts.replace_sources,
  1912. opts.strip, opts.full_gcc, opts.action, opts.exclude,
  1913. shallow=opts.shallow)
  1914. ctx.run_builds(opts.action, opts.configs)
  1915. if __name__ == '__main__':
  1916. main(sys.argv[1:])