2 # Build many configurations of glibc.
3 # Copyright (C) 2016-2020 Free Software Foundation, Inc.
4 # This file is part of the GNU C Library.
6 # The GNU C Library is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU Lesser General Public
8 # License as published by the Free Software Foundation; either
9 # version 2.1 of the License, or (at your option) any later version.
11 # The GNU C Library is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 # Lesser General Public License for more details.
16 # You should have received a copy of the GNU Lesser General Public
17 # License along with the GNU C Library; if not, see
18 # <https://www.gnu.org/licenses/>.
20 """Build many configurations of glibc.
22 This script takes as arguments a directory name (containing a src
23 subdirectory with sources of the relevant toolchain components) and a
24 description of what to do: 'checkout', to check out sources into that
25 directory, 'bot-cycle', to run a series of checkout and build steps,
26 'bot', to run 'bot-cycle' repeatedly, 'host-libraries', to build
27 libraries required by the toolchain, 'compilers', to build
28 cross-compilers for various configurations, or 'glibcs', to build
29 glibc for various configurations and run the compilation parts of the
30 testsuite. Subsequent arguments name the versions of components to
31 check out (<component>-<version), for 'checkout', or, for actions
32 other than 'checkout' and 'bot-cycle', name configurations for which
33 compilers or glibc are to be built.
39 import email
.mime
.text
55 class _CompletedProcess
:
56 def __init__(self
, args
, returncode
, stdout
=None, stderr
=None):
58 self
.returncode
= returncode
62 def _run(*popenargs
, input=None, timeout
=None, check
=False, **kwargs
):
63 assert(timeout
is None)
64 with subprocess
.Popen(*popenargs
, **kwargs
) as process
:
66 stdout
, stderr
= process
.communicate(input)
71 returncode
= process
.poll()
72 if check
and returncode
:
73 raise subprocess
.CalledProcessError(returncode
, popenargs
)
74 return _CompletedProcess(popenargs
, returncode
, stdout
, stderr
)
79 class Context(object):
80 """The global state associated with builds in a given directory."""
82 def __init__(self
, topdir
, parallelism
, keep
, replace_sources
, strip
,
84 """Initialize the context."""
86 self
.parallelism
= parallelism
88 self
.replace_sources
= replace_sources
90 self
.full_gcc
= full_gcc
91 self
.srcdir
= os
.path
.join(topdir
, 'src')
92 self
.versions_json
= os
.path
.join(self
.srcdir
, 'versions.json')
93 self
.build_state_json
= os
.path
.join(topdir
, 'build-state.json')
94 self
.bot_config_json
= os
.path
.join(topdir
, 'bot-config.json')
95 self
.installdir
= os
.path
.join(topdir
, 'install')
96 self
.host_libraries_installdir
= os
.path
.join(self
.installdir
,
98 self
.builddir
= os
.path
.join(topdir
, 'build')
99 self
.logsdir
= os
.path
.join(topdir
, 'logs')
100 self
.logsdir_old
= os
.path
.join(topdir
, 'logs-old')
101 self
.makefile
= os
.path
.join(self
.builddir
, 'Makefile')
102 self
.wrapper
= os
.path
.join(self
.builddir
, 'wrapper')
103 self
.save_logs
= os
.path
.join(self
.builddir
, 'save-logs')
104 self
.script_text
= self
.get_script_text()
105 if action
!= 'checkout':
106 self
.build_triplet
= self
.get_build_triplet()
107 self
.glibc_version
= self
.get_glibc_version()
109 self
.glibc_configs
= {}
110 self
.makefile_pieces
= ['.PHONY: all\n']
111 self
.add_all_configs()
112 self
.load_versions_json()
113 self
.load_build_state_json()
114 self
.status_log_list
= []
115 self
.email_warning
= False
117 def get_script_text(self
):
118 """Return the text of this script."""
119 with
open(sys
.argv
[0], 'r') as f
:
123 """Re-execute this script with the same arguments."""
125 os
.execv(sys
.executable
, [sys
.executable
] + sys
.argv
)
127 def get_build_triplet(self
):
128 """Determine the build triplet with config.guess."""
129 config_guess
= os
.path
.join(self
.component_srcdir('gcc'),
131 cg_out
= subprocess
.run([config_guess
], stdout
=subprocess
.PIPE
,
132 check
=True, universal_newlines
=True).stdout
133 return cg_out
.rstrip()
135 def get_glibc_version(self
):
136 """Determine the glibc version number (major.minor)."""
137 version_h
= os
.path
.join(self
.component_srcdir('glibc'), 'version.h')
138 with
open(version_h
, 'r') as f
:
139 lines
= f
.readlines()
140 starttext
= '#define VERSION "'
142 if l
.startswith(starttext
):
143 l
= l
[len(starttext
):]
145 m
= re
.fullmatch('([0-9]+)\.([0-9]+)[.0-9]*', l
)
146 return '%s.%s' % m
.group(1, 2)
147 print('error: could not determine glibc version')
150 def add_all_configs(self
):
151 """Add all known glibc build configurations."""
152 self
.add_config(arch
='aarch64',
154 extra_glibcs
=[{'variant': 'disable-multi-arch',
155 'cfg': ['--disable-multi-arch']}])
156 self
.add_config(arch
='aarch64_be',
158 self
.add_config(arch
='alpha',
160 self
.add_config(arch
='arm',
161 os_name
='linux-gnueabi',
162 extra_glibcs
=[{'variant': 'v4t',
163 'ccopts': '-march=armv4t'}])
164 self
.add_config(arch
='armeb',
165 os_name
='linux-gnueabi')
166 self
.add_config(arch
='armeb',
167 os_name
='linux-gnueabi',
169 gcc_cfg
=['--with-arch=armv7-a'])
170 self
.add_config(arch
='arm',
171 os_name
='linux-gnueabihf',
172 gcc_cfg
=['--with-float=hard', '--with-cpu=arm926ej-s'],
173 extra_glibcs
=[{'variant': 'v7a',
174 'ccopts': '-march=armv7-a -mfpu=vfpv3'},
175 {'variant': 'v7a-disable-multi-arch',
176 'ccopts': '-march=armv7-a -mfpu=vfpv3',
177 'cfg': ['--disable-multi-arch']}])
178 self
.add_config(arch
='armeb',
179 os_name
='linux-gnueabihf',
180 gcc_cfg
=['--with-float=hard', '--with-cpu=arm926ej-s'])
181 self
.add_config(arch
='armeb',
182 os_name
='linux-gnueabihf',
184 gcc_cfg
=['--with-float=hard', '--with-arch=armv7-a',
186 self
.add_config(arch
='csky',
187 os_name
='linux-gnuabiv2',
189 gcc_cfg
=['--disable-multilib'])
190 self
.add_config(arch
='csky',
191 os_name
='linux-gnuabiv2',
192 gcc_cfg
=['--with-float=hard', '--disable-multilib'])
193 self
.add_config(arch
='hppa',
195 self
.add_config(arch
='i686',
197 self
.add_config(arch
='ia64',
199 first_gcc_cfg
=['--with-system-libunwind'])
200 self
.add_config(arch
='m68k',
202 gcc_cfg
=['--disable-multilib'])
203 self
.add_config(arch
='m68k',
206 gcc_cfg
=['--with-arch=cf', '--disable-multilib'])
207 self
.add_config(arch
='m68k',
209 variant
='coldfire-soft',
210 gcc_cfg
=['--with-arch=cf', '--with-cpu=54455',
211 '--disable-multilib'])
212 self
.add_config(arch
='microblaze',
214 gcc_cfg
=['--disable-multilib'])
215 self
.add_config(arch
='microblazeel',
217 gcc_cfg
=['--disable-multilib'])
218 self
.add_config(arch
='mips64',
220 gcc_cfg
=['--with-mips-plt'],
221 glibcs
=[{'variant': 'n32'},
223 'ccopts': '-mabi=32'},
225 'ccopts': '-mabi=64'}])
226 self
.add_config(arch
='mips64',
229 gcc_cfg
=['--with-mips-plt', '--with-float=soft'],
230 glibcs
=[{'variant': 'n32-soft'},
233 'ccopts': '-mabi=32'},
234 {'variant': 'n64-soft',
235 'ccopts': '-mabi=64'}])
236 self
.add_config(arch
='mips64',
239 gcc_cfg
=['--with-mips-plt', '--with-nan=2008',
240 '--with-arch-64=mips64r2',
241 '--with-arch-32=mips32r2'],
242 glibcs
=[{'variant': 'n32-nan2008'},
243 {'variant': 'nan2008',
245 'ccopts': '-mabi=32'},
246 {'variant': 'n64-nan2008',
247 'ccopts': '-mabi=64'}])
248 self
.add_config(arch
='mips64',
250 variant
='nan2008-soft',
251 gcc_cfg
=['--with-mips-plt', '--with-nan=2008',
252 '--with-arch-64=mips64r2',
253 '--with-arch-32=mips32r2',
254 '--with-float=soft'],
255 glibcs
=[{'variant': 'n32-nan2008-soft'},
256 {'variant': 'nan2008-soft',
258 'ccopts': '-mabi=32'},
259 {'variant': 'n64-nan2008-soft',
260 'ccopts': '-mabi=64'}])
261 self
.add_config(arch
='mips64el',
263 gcc_cfg
=['--with-mips-plt'],
264 glibcs
=[{'variant': 'n32'},
266 'ccopts': '-mabi=32'},
268 'ccopts': '-mabi=64'}])
269 self
.add_config(arch
='mips64el',
272 gcc_cfg
=['--with-mips-plt', '--with-float=soft'],
273 glibcs
=[{'variant': 'n32-soft'},
276 'ccopts': '-mabi=32'},
277 {'variant': 'n64-soft',
278 'ccopts': '-mabi=64'}])
279 self
.add_config(arch
='mips64el',
282 gcc_cfg
=['--with-mips-plt', '--with-nan=2008',
283 '--with-arch-64=mips64r2',
284 '--with-arch-32=mips32r2'],
285 glibcs
=[{'variant': 'n32-nan2008'},
286 {'variant': 'nan2008',
288 'ccopts': '-mabi=32'},
289 {'variant': 'n64-nan2008',
290 'ccopts': '-mabi=64'}])
291 self
.add_config(arch
='mips64el',
293 variant
='nan2008-soft',
294 gcc_cfg
=['--with-mips-plt', '--with-nan=2008',
295 '--with-arch-64=mips64r2',
296 '--with-arch-32=mips32r2',
297 '--with-float=soft'],
298 glibcs
=[{'variant': 'n32-nan2008-soft'},
299 {'variant': 'nan2008-soft',
301 'ccopts': '-mabi=32'},
302 {'variant': 'n64-nan2008-soft',
303 'ccopts': '-mabi=64'}])
304 self
.add_config(arch
='mipsisa64r6el',
306 gcc_cfg
=['--with-mips-plt', '--with-nan=2008',
307 '--with-arch-64=mips64r6',
308 '--with-arch-32=mips32r6',
309 '--with-float=hard'],
310 glibcs
=[{'variant': 'n32'},
311 {'arch': 'mipsisa32r6el',
312 'ccopts': '-mabi=32'},
314 'ccopts': '-mabi=64'}])
315 self
.add_config(arch
='nios2',
317 self
.add_config(arch
='powerpc',
319 gcc_cfg
=['--disable-multilib', '--enable-secureplt'],
320 extra_glibcs
=[{'variant': 'power4',
321 'ccopts': '-mcpu=power4',
322 'cfg': ['--with-cpu=power4']}])
323 self
.add_config(arch
='powerpc',
326 gcc_cfg
=['--disable-multilib', '--with-float=soft',
327 '--enable-secureplt'])
328 self
.add_config(arch
='powerpc64',
330 gcc_cfg
=['--disable-multilib', '--enable-secureplt'])
331 self
.add_config(arch
='powerpc64le',
333 gcc_cfg
=['--disable-multilib', '--enable-secureplt'])
334 self
.add_config(arch
='riscv64',
336 variant
='rv64imac-lp64',
337 gcc_cfg
=['--with-arch=rv64imac', '--with-abi=lp64',
338 '--disable-multilib'])
339 self
.add_config(arch
='riscv64',
341 variant
='rv64imafdc-lp64',
342 gcc_cfg
=['--with-arch=rv64imafdc', '--with-abi=lp64',
343 '--disable-multilib'])
344 self
.add_config(arch
='riscv64',
346 variant
='rv64imafdc-lp64d',
347 gcc_cfg
=['--with-arch=rv64imafdc', '--with-abi=lp64d',
348 '--disable-multilib'])
349 self
.add_config(arch
='s390x',
352 {'arch': 's390', 'ccopts': '-m31'}])
353 self
.add_config(arch
='sh3',
355 self
.add_config(arch
='sh3eb',
357 self
.add_config(arch
='sh4',
359 self
.add_config(arch
='sh4eb',
361 self
.add_config(arch
='sh4',
364 gcc_cfg
=['--without-fp'])
365 self
.add_config(arch
='sh4eb',
368 gcc_cfg
=['--without-fp'])
369 self
.add_config(arch
='sparc64',
373 'ccopts': '-m32 -mlong-double-128 -mcpu=v9'}],
374 extra_glibcs
=[{'variant': 'leon3',
376 'ccopts' : '-m32 -mlong-double-128 -mcpu=leon3'},
377 {'variant': 'disable-multi-arch',
378 'cfg': ['--disable-multi-arch']},
379 {'variant': 'disable-multi-arch',
381 'ccopts': '-m32 -mlong-double-128 -mcpu=v9',
382 'cfg': ['--disable-multi-arch']}])
383 self
.add_config(arch
='x86_64',
385 gcc_cfg
=['--with-multilib-list=m64,m32,mx32'],
387 {'variant': 'x32', 'ccopts': '-mx32'},
388 {'arch': 'i686', 'ccopts': '-m32 -march=i686'}],
389 extra_glibcs
=[{'variant': 'disable-multi-arch',
390 'cfg': ['--disable-multi-arch']},
391 {'variant': 'enable-obsolete',
392 'cfg': ['--enable-obsolete-rpc',
393 '--enable-obsolete-nsl']},
394 {'variant': 'static-pie',
395 'cfg': ['--enable-static-pie']},
396 {'variant': 'x32-static-pie',
398 'cfg': ['--enable-static-pie']},
399 {'variant': 'static-pie',
401 'ccopts': '-m32 -march=i686',
402 'cfg': ['--enable-static-pie']},
403 {'variant': 'disable-multi-arch',
405 'ccopts': '-m32 -march=i686',
406 'cfg': ['--disable-multi-arch']},
407 {'variant': 'enable-obsolete',
409 'ccopts': '-m32 -march=i686',
410 'cfg': ['--enable-obsolete-rpc',
411 '--enable-obsolete-nsl']},
413 'ccopts': '-m32 -march=i486'},
415 'ccopts': '-m32 -march=i586'}])
417 def add_config(self
, **args
):
418 """Add an individual build configuration."""
419 cfg
= Config(self
, **args
)
420 if cfg
.name
in self
.configs
:
421 print('error: duplicate config %s' % cfg
.name
)
423 self
.configs
[cfg
.name
] = cfg
424 for c
in cfg
.all_glibcs
:
425 if c
.name
in self
.glibc_configs
:
426 print('error: duplicate glibc config %s' % c
.name
)
428 self
.glibc_configs
[c
.name
] = c
430 def component_srcdir(self
, component
):
431 """Return the source directory for a given component, e.g. gcc."""
432 return os
.path
.join(self
.srcdir
, component
)
434 def component_builddir(self
, action
, config
, component
, subconfig
=None):
435 """Return the directory to use for a build."""
438 assert subconfig
is None
439 return os
.path
.join(self
.builddir
, action
, component
)
440 if subconfig
is None:
441 return os
.path
.join(self
.builddir
, action
, config
, component
)
443 # glibc build as part of compiler build.
444 return os
.path
.join(self
.builddir
, action
, config
, component
,
447 def compiler_installdir(self
, config
):
448 """Return the directory in which to install a compiler."""
449 return os
.path
.join(self
.installdir
, 'compilers', config
)
451 def compiler_bindir(self
, config
):
452 """Return the directory in which to find compiler binaries."""
453 return os
.path
.join(self
.compiler_installdir(config
), 'bin')
455 def compiler_sysroot(self
, config
):
456 """Return the sysroot directory for a compiler."""
457 return os
.path
.join(self
.compiler_installdir(config
), 'sysroot')
459 def glibc_installdir(self
, config
):
460 """Return the directory in which to install glibc."""
461 return os
.path
.join(self
.installdir
, 'glibcs', config
)
463 def run_builds(self
, action
, configs
):
464 """Run the requested builds."""
465 if action
== 'checkout':
466 self
.checkout(configs
)
468 if action
== 'bot-cycle':
470 print('error: configurations specified for bot-cycle')
476 print('error: configurations specified for bot')
480 if action
== 'host-libraries' and configs
:
481 print('error: configurations specified for host-libraries')
483 self
.clear_last_build_state(action
)
484 build_time
= datetime
.datetime
.utcnow()
485 if action
== 'host-libraries':
486 build_components
= ('gmp', 'mpfr', 'mpc')
489 self
.build_host_libraries()
490 elif action
== 'compilers':
491 build_components
= ('binutils', 'gcc', 'glibc', 'linux', 'mig',
493 old_components
= ('gmp', 'mpfr', 'mpc')
494 old_versions
= self
.build_state
['host-libraries']['build-versions']
495 self
.build_compilers(configs
)
497 build_components
= ('glibc',)
498 old_components
= ('gmp', 'mpfr', 'mpc', 'binutils', 'gcc', 'linux',
499 'mig', 'gnumach', 'hurd')
500 old_versions
= self
.build_state
['compilers']['build-versions']
501 if action
== 'update-syscalls':
502 self
.update_syscalls(configs
)
504 self
.build_glibcs(configs
)
508 # Partial build, do not update stored state.
511 for k
in build_components
:
512 if k
in self
.versions
:
513 build_versions
[k
] = {'version': self
.versions
[k
]['version'],
514 'revision': self
.versions
[k
]['revision']}
515 for k
in old_components
:
516 if k
in old_versions
:
517 build_versions
[k
] = {'version': old_versions
[k
]['version'],
518 'revision': old_versions
[k
]['revision']}
519 self
.update_build_state(action
, build_time
, build_versions
)
522 def remove_dirs(*args
):
523 """Remove directories and their contents if they exist."""
525 shutil
.rmtree(dir, ignore_errors
=True)
528 def remove_recreate_dirs(*args
):
529 """Remove directories if they exist, and create them as empty."""
530 Context
.remove_dirs(*args
)
532 os
.makedirs(dir, exist_ok
=True)
534 def add_makefile_cmdlist(self
, target
, cmdlist
, logsdir
):
535 """Add makefile text for a list of commands."""
536 commands
= cmdlist
.makefile_commands(self
.wrapper
, logsdir
)
537 self
.makefile_pieces
.append('all: %s\n.PHONY: %s\n%s:\n%s\n' %
538 (target
, target
, target
, commands
))
539 self
.status_log_list
.extend(cmdlist
.status_logs(logsdir
))
541 def write_files(self
):
542 """Write out the Makefile and wrapper script."""
543 mftext
= ''.join(self
.makefile_pieces
)
544 with
open(self
.makefile
, 'w') as f
:
554 'prev_status=$prev_base-status.txt\n'
555 'this_status=$this_base-status.txt\n'
556 'this_log=$this_base-log.txt\n'
557 'date > "$this_log"\n'
558 'echo >> "$this_log"\n'
559 'echo "Description: $desc" >> "$this_log"\n'
560 'printf "%s" "Command:" >> "$this_log"\n'
561 'for word in "$@"; do\n'
562 ' if expr "$word" : "[]+,./0-9@A-Z_a-z-]\\\\{1,\\\\}\\$" > /dev/null; then\n'
563 ' printf " %s" "$word"\n'
566 ' printf "%s" "$word" | sed -e "s/\'/\'\\\\\\\\\'\'/"\n'
569 'done >> "$this_log"\n'
570 'echo >> "$this_log"\n'
571 'echo "Directory: $dir" >> "$this_log"\n'
572 'echo "Path addition: $path" >> "$this_log"\n'
573 'echo >> "$this_log"\n'
576 ' echo >> "$this_log"\n'
577 ' echo "$1: $desc" > "$this_status"\n'
578 ' echo "$1: $desc" >> "$this_log"\n'
579 ' echo >> "$this_log"\n'
580 ' date >> "$this_log"\n'
581 ' echo "$1: $desc"\n'
586 ' if [ "$1" != "0" ]; then\n'
587 ' record_status FAIL\n'
590 'if [ "$prev_base" ] && ! grep -q "^PASS" "$prev_status"; then\n'
591 ' record_status UNRESOLVED\n'
593 'if [ "$dir" ]; then\n'
595 ' check_error "$?"\n'
597 'if [ "$path" ]; then\n'
598 ' PATH=$path:$PATH\n'
600 '"$@" < /dev/null >> "$this_log" 2>&1\n'
602 'record_status PASS\n')
603 with
open(self
.wrapper
, 'w') as f
:
604 f
.write(wrapper_text
)
606 mode_exec
= (stat
.S_IRWXU|stat
.S_IRGRP|stat
.S_IXGRP|
607 stat
.S_IROTH|stat
.S_IXOTH
)
608 os
.chmod(self
.wrapper
, mode_exec
)
611 'if ! [ -f tests.sum ]; then\n'
612 ' echo "No test summary available."\n'
617 ' echo "Contents of $1:"\n'
621 ' echo "End of contents of $1."\n'
624 'save_file tests.sum\n'
625 'non_pass_tests=$(grep -v "^PASS: " tests.sum | sed -e "s/^PASS: //")\n'
626 'for t in $non_pass_tests; do\n'
627 ' if [ -f "$t.out" ]; then\n'
628 ' save_file "$t.out"\n'
631 with
open(self
.save_logs
, 'w') as f
:
632 f
.write(save_logs_text
)
633 os
.chmod(self
.save_logs
, mode_exec
)
636 """Do the actual build."""
637 cmd
= ['make', '-j%d' % self
.parallelism
]
638 subprocess
.run(cmd
, cwd
=self
.builddir
, check
=True)
640 def build_host_libraries(self
):
641 """Build the host libraries."""
642 installdir
= self
.host_libraries_installdir
643 builddir
= os
.path
.join(self
.builddir
, 'host-libraries')
644 logsdir
= os
.path
.join(self
.logsdir
, 'host-libraries')
645 self
.remove_recreate_dirs(installdir
, builddir
, logsdir
)
646 cmdlist
= CommandList('host-libraries', self
.keep
)
647 self
.build_host_library(cmdlist
, 'gmp')
648 self
.build_host_library(cmdlist
, 'mpfr',
649 ['--with-gmp=%s' % installdir
])
650 self
.build_host_library(cmdlist
, 'mpc',
651 ['--with-gmp=%s' % installdir
,
652 '--with-mpfr=%s' % installdir
])
653 cmdlist
.add_command('done', ['touch', os
.path
.join(installdir
, 'ok')])
654 self
.add_makefile_cmdlist('host-libraries', cmdlist
, logsdir
)
656 def build_host_library(self
, cmdlist
, lib
, extra_opts
=None):
657 """Build one host library."""
658 srcdir
= self
.component_srcdir(lib
)
659 builddir
= self
.component_builddir('host-libraries', None, lib
)
660 installdir
= self
.host_libraries_installdir
661 cmdlist
.push_subdesc(lib
)
662 cmdlist
.create_use_dir(builddir
)
663 cfg_cmd
= [os
.path
.join(srcdir
, 'configure'),
664 '--prefix=%s' % installdir
,
667 cfg_cmd
.extend (extra_opts
)
668 cmdlist
.add_command('configure', cfg_cmd
)
669 cmdlist
.add_command('build', ['make'])
670 cmdlist
.add_command('check', ['make', 'check'])
671 cmdlist
.add_command('install', ['make', 'install'])
672 cmdlist
.cleanup_dir()
673 cmdlist
.pop_subdesc()
675 def build_compilers(self
, configs
):
676 """Build the compilers."""
678 self
.remove_dirs(os
.path
.join(self
.builddir
, 'compilers'))
679 self
.remove_dirs(os
.path
.join(self
.installdir
, 'compilers'))
680 self
.remove_dirs(os
.path
.join(self
.logsdir
, 'compilers'))
681 configs
= sorted(self
.configs
.keys())
683 self
.configs
[c
].build()
685 def build_glibcs(self
, configs
):
686 """Build the glibcs."""
688 self
.remove_dirs(os
.path
.join(self
.builddir
, 'glibcs'))
689 self
.remove_dirs(os
.path
.join(self
.installdir
, 'glibcs'))
690 self
.remove_dirs(os
.path
.join(self
.logsdir
, 'glibcs'))
691 configs
= sorted(self
.glibc_configs
.keys())
693 self
.glibc_configs
[c
].build()
695 def update_syscalls(self
, configs
):
696 """Update the glibc syscall lists."""
698 self
.remove_dirs(os
.path
.join(self
.builddir
, 'update-syscalls'))
699 self
.remove_dirs(os
.path
.join(self
.logsdir
, 'update-syscalls'))
700 configs
= sorted(self
.glibc_configs
.keys())
702 self
.glibc_configs
[c
].update_syscalls()
704 def load_versions_json(self
):
705 """Load information about source directory versions."""
706 if not os
.access(self
.versions_json
, os
.F_OK
):
709 with
open(self
.versions_json
, 'r') as f
:
710 self
.versions
= json
.load(f
)
712 def store_json(self
, data
, filename
):
713 """Store information in a JSON file."""
714 filename_tmp
= filename
+ '.tmp'
715 with
open(filename_tmp
, 'w') as f
:
716 json
.dump(data
, f
, indent
=2, sort_keys
=True)
717 os
.rename(filename_tmp
, filename
)
719 def store_versions_json(self
):
720 """Store information about source directory versions."""
721 self
.store_json(self
.versions
, self
.versions_json
)
723 def set_component_version(self
, component
, version
, explicit
, revision
):
724 """Set the version information for a component."""
725 self
.versions
[component
] = {'version': version
,
726 'explicit': explicit
,
727 'revision': revision
}
728 self
.store_versions_json()
730 def checkout(self
, versions
):
731 """Check out the desired component versions."""
732 default_versions
= {'binutils': 'vcs-2.33',
734 'glibc': 'vcs-mainline',
739 'mig': 'vcs-mainline',
740 'gnumach': 'vcs-mainline',
741 'hurd': 'vcs-mainline'}
743 explicit_versions
= {}
746 for k
in default_versions
.keys():
750 if k
in use_versions
:
751 print('error: multiple versions for %s' % k
)
754 explicit_versions
[k
] = True
758 print('error: unknown component in %s' % v
)
760 for k
in default_versions
.keys():
761 if k
not in use_versions
:
762 if k
in self
.versions
and self
.versions
[k
]['explicit']:
763 use_versions
[k
] = self
.versions
[k
]['version']
764 explicit_versions
[k
] = True
766 use_versions
[k
] = default_versions
[k
]
767 explicit_versions
[k
] = False
768 os
.makedirs(self
.srcdir
, exist_ok
=True)
769 for k
in sorted(default_versions
.keys()):
770 update
= os
.access(self
.component_srcdir(k
), os
.F_OK
)
773 k
in self
.versions
and
774 v
!= self
.versions
[k
]['version']):
775 if not self
.replace_sources
:
776 print('error: version of %s has changed from %s to %s, '
777 'use --replace-sources to check out again' %
778 (k
, self
.versions
[k
]['version'], v
))
780 shutil
.rmtree(self
.component_srcdir(k
))
782 if v
.startswith('vcs-'):
783 revision
= self
.checkout_vcs(k
, v
[4:], update
)
785 self
.checkout_tar(k
, v
, update
)
787 self
.set_component_version(k
, v
, explicit_versions
[k
], revision
)
788 if self
.get_script_text() != self
.script_text
:
789 # Rerun the checkout process in case the updated script
790 # uses different default versions or new components.
793 def checkout_vcs(self
, component
, version
, update
):
794 """Check out the given version of the given component from version
795 control. Return a revision identifier."""
796 if component
== 'binutils':
797 git_url
= 'git://sourceware.org/git/binutils-gdb.git'
798 if version
== 'mainline':
799 git_branch
= 'master'
801 trans
= str.maketrans({'.': '_'})
802 git_branch
= 'binutils-%s-branch' % version
.translate(trans
)
803 return self
.git_checkout(component
, git_url
, git_branch
, update
)
804 elif component
== 'gcc':
805 if version
== 'mainline':
808 branch
= 'releases/gcc-%s' % version
809 return self
.gcc_checkout(branch
, update
)
810 elif component
== 'glibc':
811 git_url
= 'git://sourceware.org/git/glibc.git'
812 if version
== 'mainline':
813 git_branch
= 'master'
815 git_branch
= 'release/%s/master' % version
816 r
= self
.git_checkout(component
, git_url
, git_branch
, update
)
817 self
.fix_glibc_timestamps()
819 elif component
== 'gnumach':
820 git_url
= 'git://git.savannah.gnu.org/hurd/gnumach.git'
821 git_branch
= 'master'
822 r
= self
.git_checkout(component
, git_url
, git_branch
, update
)
823 subprocess
.run(['autoreconf', '-i'],
824 cwd
=self
.component_srcdir(component
), check
=True)
826 elif component
== 'mig':
827 git_url
= 'git://git.savannah.gnu.org/hurd/mig.git'
828 git_branch
= 'master'
829 r
= self
.git_checkout(component
, git_url
, git_branch
, update
)
830 subprocess
.run(['autoreconf', '-i'],
831 cwd
=self
.component_srcdir(component
), check
=True)
833 elif component
== 'hurd':
834 git_url
= 'git://git.savannah.gnu.org/hurd/hurd.git'
835 git_branch
= 'master'
836 r
= self
.git_checkout(component
, git_url
, git_branch
, update
)
837 subprocess
.run(['autoconf'],
838 cwd
=self
.component_srcdir(component
), check
=True)
841 print('error: component %s coming from VCS' % component
)
844 def git_checkout(self
, component
, git_url
, git_branch
, update
):
845 """Check out a component from git. Return a commit identifier."""
847 subprocess
.run(['git', 'remote', 'prune', 'origin'],
848 cwd
=self
.component_srcdir(component
), check
=True)
849 if self
.replace_sources
:
850 subprocess
.run(['git', 'clean', '-dxfq'],
851 cwd
=self
.component_srcdir(component
), check
=True)
852 subprocess
.run(['git', 'pull', '-q'],
853 cwd
=self
.component_srcdir(component
), check
=True)
855 subprocess
.run(['git', 'clone', '-q', '-b', git_branch
, git_url
,
856 self
.component_srcdir(component
)], check
=True)
857 r
= subprocess
.run(['git', 'rev-parse', 'HEAD'],
858 cwd
=self
.component_srcdir(component
),
859 stdout
=subprocess
.PIPE
,
860 check
=True, universal_newlines
=True).stdout
863 def fix_glibc_timestamps(self
):
864 """Fix timestamps in a glibc checkout."""
865 # Ensure that builds do not try to regenerate generated files
866 # in the source tree.
867 srcdir
= self
.component_srcdir('glibc')
868 # These files have Makefile dependencies to regenerate them in
869 # the source tree that may be active during a normal build.
870 # Some other files have such dependencies but do not need to
871 # be touched because nothing in a build depends on the files
873 for f
in ('sysdeps/gnu/errlist.c',
874 'sysdeps/mach/hurd/bits/errno.h'):
875 to_touch
= os
.path
.join(srcdir
, f
)
876 subprocess
.run(['touch', '-c', to_touch
], check
=True)
877 for dirpath
, dirnames
, filenames
in os
.walk(srcdir
):
879 if (f
== 'configure' or
880 f
== 'preconfigure' or
881 f
.endswith('-kw.h')):
882 to_touch
= os
.path
.join(dirpath
, f
)
883 subprocess
.run(['touch', to_touch
], check
=True)
885 def gcc_checkout(self
, branch
, update
):
886 """Check out GCC from git. Return the commit identifier."""
887 if os
.access(os
.path
.join(self
.component_srcdir('gcc'), '.svn'),
889 if not self
.replace_sources
:
890 print('error: GCC has moved from SVN to git, use '
891 '--replace-sources to check out again')
893 shutil
.rmtree(self
.component_srcdir('gcc'))
896 self
.git_checkout('gcc', 'git://gcc.gnu.org/git/gcc.git',
898 subprocess
.run(['contrib/gcc_update', '--silent'],
899 cwd
=self
.component_srcdir('gcc'), check
=True)
900 r
= subprocess
.run(['git', 'rev-parse', 'HEAD'],
901 cwd
=self
.component_srcdir('gcc'),
902 stdout
=subprocess
.PIPE
,
903 check
=True, universal_newlines
=True).stdout
906 def checkout_tar(self
, component
, version
, update
):
907 """Check out the given version of the given component from a
911 url_map
= {'binutils': 'https://ftp.gnu.org/gnu/binutils/binutils-%(version)s.tar.bz2',
912 'gcc': 'https://ftp.gnu.org/gnu/gcc/gcc-%(version)s/gcc-%(version)s.tar.gz',
913 'gmp': 'https://ftp.gnu.org/gnu/gmp/gmp-%(version)s.tar.xz',
914 'linux': 'https://www.kernel.org/pub/linux/kernel/v%(major)s.x/linux-%(version)s.tar.xz',
915 'mpc': 'https://ftp.gnu.org/gnu/mpc/mpc-%(version)s.tar.gz',
916 'mpfr': 'https://ftp.gnu.org/gnu/mpfr/mpfr-%(version)s.tar.xz',
917 'mig': 'https://ftp.gnu.org/gnu/mig/mig-%(version)s.tar.bz2',
918 'gnumach': 'https://ftp.gnu.org/gnu/gnumach/gnumach-%(version)s.tar.bz2',
919 'hurd': 'https://ftp.gnu.org/gnu/hurd/hurd-%(version)s.tar.bz2'}
920 if component
not in url_map
:
921 print('error: component %s coming from tarball' % component
)
923 version_major
= version
.split('.')[0]
924 url
= url_map
[component
] % {'version': version
, 'major': version_major
}
925 filename
= os
.path
.join(self
.srcdir
, url
.split('/')[-1])
926 response
= urllib
.request
.urlopen(url
)
927 data
= response
.read()
928 with
open(filename
, 'wb') as f
:
930 subprocess
.run(['tar', '-C', self
.srcdir
, '-x', '-f', filename
],
932 os
.rename(os
.path
.join(self
.srcdir
, '%s-%s' % (component
, version
)),
933 self
.component_srcdir(component
))
936 def load_build_state_json(self
):
937 """Load information about the state of previous builds."""
938 if os
.access(self
.build_state_json
, os
.F_OK
):
939 with
open(self
.build_state_json
, 'r') as f
:
940 self
.build_state
= json
.load(f
)
942 self
.build_state
= {}
943 for k
in ('host-libraries', 'compilers', 'glibcs', 'update-syscalls'):
944 if k
not in self
.build_state
:
945 self
.build_state
[k
] = {}
946 if 'build-time' not in self
.build_state
[k
]:
947 self
.build_state
[k
]['build-time'] = ''
948 if 'build-versions' not in self
.build_state
[k
]:
949 self
.build_state
[k
]['build-versions'] = {}
950 if 'build-results' not in self
.build_state
[k
]:
951 self
.build_state
[k
]['build-results'] = {}
952 if 'result-changes' not in self
.build_state
[k
]:
953 self
.build_state
[k
]['result-changes'] = {}
954 if 'ever-passed' not in self
.build_state
[k
]:
955 self
.build_state
[k
]['ever-passed'] = []
957 def store_build_state_json(self
):
958 """Store information about the state of previous builds."""
959 self
.store_json(self
.build_state
, self
.build_state_json
)
961 def clear_last_build_state(self
, action
):
962 """Clear information about the state of part of the build."""
963 # We clear the last build time and versions when starting a
964 # new build. The results of the last build are kept around,
965 # as comparison is still meaningful if this build is aborted
966 # and a new one started.
967 self
.build_state
[action
]['build-time'] = ''
968 self
.build_state
[action
]['build-versions'] = {}
969 self
.store_build_state_json()
971 def update_build_state(self
, action
, build_time
, build_versions
):
972 """Update the build state after a build."""
973 build_time
= build_time
.replace(microsecond
=0)
974 self
.build_state
[action
]['build-time'] = str(build_time
)
975 self
.build_state
[action
]['build-versions'] = build_versions
977 for log
in self
.status_log_list
:
978 with
open(log
, 'r') as f
:
980 log_text
= log_text
.rstrip()
981 m
= re
.fullmatch('([A-Z]+): (.*)', log_text
)
983 test_name
= m
.group(2)
984 assert test_name
not in build_results
985 build_results
[test_name
] = result
986 old_build_results
= self
.build_state
[action
]['build-results']
987 self
.build_state
[action
]['build-results'] = build_results
989 all_tests
= set(old_build_results
.keys()) |
set(build_results
.keys())
991 if t
in old_build_results
:
992 old_res
= old_build_results
[t
]
994 old_res
= '(New test)'
995 if t
in build_results
:
996 new_res
= build_results
[t
]
998 new_res
= '(Test removed)'
999 if old_res
!= new_res
:
1000 result_changes
[t
] = '%s -> %s' % (old_res
, new_res
)
1001 self
.build_state
[action
]['result-changes'] = result_changes
1002 old_ever_passed
= {t
for t
in self
.build_state
[action
]['ever-passed']
1003 if t
in build_results
}
1004 new_passes
= {t
for t
in build_results
if build_results
[t
] == 'PASS'}
1005 self
.build_state
[action
]['ever-passed'] = sorted(old_ever_passed |
1007 self
.store_build_state_json()
1009 def load_bot_config_json(self
):
1010 """Load bot configuration."""
1011 with
open(self
.bot_config_json
, 'r') as f
:
1012 self
.bot_config
= json
.load(f
)
1014 def part_build_old(self
, action
, delay
):
1015 """Return whether the last build for a given action was at least a
1016 given number of seconds ago, or does not have a time recorded."""
1017 old_time_str
= self
.build_state
[action
]['build-time']
1018 if not old_time_str
:
1020 old_time
= datetime
.datetime
.strptime(old_time_str
,
1021 '%Y-%m-%d %H:%M:%S')
1022 new_time
= datetime
.datetime
.utcnow()
1023 delta
= new_time
- old_time
1024 return delta
.total_seconds() >= delay
1026 def bot_cycle(self
):
1027 """Run a single round of checkout and builds."""
1028 print('Bot cycle starting %s.' % str(datetime
.datetime
.utcnow()))
1029 self
.load_bot_config_json()
1030 actions
= ('host-libraries', 'compilers', 'glibcs')
1031 self
.bot_run_self(['--replace-sources'], 'checkout')
1032 self
.load_versions_json()
1033 if self
.get_script_text() != self
.script_text
:
1034 print('Script changed, re-execing.')
1035 # On script change, all parts of the build should be rerun.
1037 self
.clear_last_build_state(a
)
1039 check_components
= {'host-libraries': ('gmp', 'mpfr', 'mpc'),
1040 'compilers': ('binutils', 'gcc', 'glibc', 'linux',
1041 'mig', 'gnumach', 'hurd'),
1042 'glibcs': ('glibc',)}
1045 build_vers
= self
.build_state
[a
]['build-versions']
1046 must_build
[a
] = False
1047 if not self
.build_state
[a
]['build-time']:
1048 must_build
[a
] = True
1051 for c
in check_components
[a
]:
1053 old_vers
[c
] = build_vers
[c
]
1054 new_vers
[c
] = {'version': self
.versions
[c
]['version'],
1055 'revision': self
.versions
[c
]['revision']}
1056 if new_vers
== old_vers
:
1057 print('Versions for %s unchanged.' % a
)
1059 print('Versions changed or rebuild forced for %s.' % a
)
1060 if a
== 'compilers' and not self
.part_build_old(
1061 a
, self
.bot_config
['compilers-rebuild-delay']):
1062 print('Not requiring rebuild of compilers this soon.')
1064 must_build
[a
] = True
1065 if must_build
['host-libraries']:
1066 must_build
['compilers'] = True
1067 if must_build
['compilers']:
1068 must_build
['glibcs'] = True
1071 print('Must rebuild %s.' % a
)
1072 self
.clear_last_build_state(a
)
1074 print('No need to rebuild %s.' % a
)
1075 if os
.access(self
.logsdir
, os
.F_OK
):
1076 shutil
.rmtree(self
.logsdir_old
, ignore_errors
=True)
1077 shutil
.copytree(self
.logsdir
, self
.logsdir_old
)
1080 build_time
= datetime
.datetime
.utcnow()
1081 print('Rebuilding %s at %s.' % (a
, str(build_time
)))
1082 self
.bot_run_self([], a
)
1083 self
.load_build_state_json()
1084 self
.bot_build_mail(a
, build_time
)
1085 print('Bot cycle done at %s.' % str(datetime
.datetime
.utcnow()))
1087 def bot_build_mail(self
, action
, build_time
):
1088 """Send email with the results of a build."""
1089 if not ('email-from' in self
.bot_config
and
1090 'email-server' in self
.bot_config
and
1091 'email-subject' in self
.bot_config
and
1092 'email-to' in self
.bot_config
):
1093 if not self
.email_warning
:
1094 print("Email not configured, not sending.")
1095 self
.email_warning
= True
1098 build_time
= build_time
.replace(microsecond
=0)
1099 subject
= (self
.bot_config
['email-subject'] %
1101 'build-time': str(build_time
)})
1102 results
= self
.build_state
[action
]['build-results']
1103 changes
= self
.build_state
[action
]['result-changes']
1104 ever_passed
= set(self
.build_state
[action
]['ever-passed'])
1105 versions
= self
.build_state
[action
]['build-versions']
1106 new_regressions
= {k
for k
in changes
if changes
[k
] == 'PASS -> FAIL'}
1107 all_regressions
= {k
for k
in ever_passed
if results
[k
] == 'FAIL'}
1108 all_fails
= {k
for k
in results
if results
[k
] == 'FAIL'}
1110 new_reg_list
= sorted(['FAIL: %s' % k
for k
in new_regressions
])
1111 new_reg_text
= ('New regressions:\n\n%s\n\n' %
1112 '\n'.join(new_reg_list
))
1116 all_reg_list
= sorted(['FAIL: %s' % k
for k
in all_regressions
])
1117 all_reg_text
= ('All regressions:\n\n%s\n\n' %
1118 '\n'.join(all_reg_list
))
1122 all_fail_list
= sorted(['FAIL: %s' % k
for k
in all_fails
])
1123 all_fail_text
= ('All failures:\n\n%s\n\n' %
1124 '\n'.join(all_fail_list
))
1128 changes_list
= sorted(changes
.keys())
1129 changes_list
= ['%s: %s' % (changes
[k
], k
) for k
in changes_list
]
1130 changes_text
= ('All changed results:\n\n%s\n\n' %
1131 '\n'.join(changes_list
))
1134 results_text
= (new_reg_text
+ all_reg_text
+ all_fail_text
+
1136 if not results_text
:
1137 results_text
= 'Clean build with unchanged results.\n\n'
1138 versions_list
= sorted(versions
.keys())
1139 versions_list
= ['%s: %s (%s)' % (k
, versions
[k
]['version'],
1140 versions
[k
]['revision'])
1141 for k
in versions_list
]
1142 versions_text
= ('Component versions for this build:\n\n%s\n' %
1143 '\n'.join(versions_list
))
1144 body_text
= results_text
+ versions_text
1145 msg
= email
.mime
.text
.MIMEText(body_text
)
1146 msg
['Subject'] = subject
1147 msg
['From'] = self
.bot_config
['email-from']
1148 msg
['To'] = self
.bot_config
['email-to']
1149 msg
['Message-ID'] = email
.utils
.make_msgid()
1150 msg
['Date'] = email
.utils
.format_datetime(datetime
.datetime
.utcnow())
1151 with smtplib
.SMTP(self
.bot_config
['email-server']) as s
:
1154 def bot_run_self(self
, opts
, action
, check
=True):
1155 """Run a copy of this script with given options."""
1156 cmd
= [sys
.executable
, sys
.argv
[0], '--keep=none',
1157 '-j%d' % self
.parallelism
]
1159 cmd
.append('--full-gcc')
1161 cmd
.extend([self
.topdir
, action
])
1163 subprocess
.run(cmd
, check
=check
)
1166 """Run repeated rounds of checkout and builds."""
1168 self
.load_bot_config_json()
1169 if not self
.bot_config
['run']:
1170 print('Bot exiting by request.')
1172 self
.bot_run_self([], 'bot-cycle', check
=False)
1173 self
.load_bot_config_json()
1174 if not self
.bot_config
['run']:
1175 print('Bot exiting by request.')
1177 time
.sleep(self
.bot_config
['delay'])
1178 if self
.get_script_text() != self
.script_text
:
1179 print('Script changed, bot re-execing.')
1182 class LinuxHeadersPolicyForBuild(object):
1183 """Names and directories for installing Linux headers. Build variant."""
1185 def __init__(self
, config
):
1186 self
.arch
= config
.arch
1187 self
.srcdir
= config
.ctx
.component_srcdir('linux')
1188 self
.builddir
= config
.component_builddir('linux')
1189 self
.headers_dir
= os
.path
.join(config
.sysroot
, 'usr')
1191 class LinuxHeadersPolicyForUpdateSyscalls(object):
1192 """Names and directories for Linux headers. update-syscalls variant."""
1194 def __init__(self
, glibc
, headers_dir
):
1195 self
.arch
= glibc
.compiler
.arch
1196 self
.srcdir
= glibc
.compiler
.ctx
.component_srcdir('linux')
1197 self
.builddir
= glibc
.ctx
.component_builddir(
1198 'update-syscalls', glibc
.name
, 'build-linux')
1199 self
.headers_dir
= headers_dir
1201 def install_linux_headers(policy
, cmdlist
):
1202 """Install Linux kernel headers."""
1203 arch_map
= {'aarch64': 'arm64',
1214 'microblaze': 'microblaze',
1217 'powerpc': 'powerpc',
1226 if policy
.arch
.startswith(k
):
1227 linux_arch
= arch_map
[k
]
1229 assert linux_arch
is not None
1230 cmdlist
.push_subdesc('linux')
1231 cmdlist
.create_use_dir(policy
.builddir
)
1232 cmdlist
.add_command('install-headers',
1233 ['make', '-C', policy
.srcdir
, 'O=%s' % policy
.builddir
,
1234 'ARCH=%s' % linux_arch
,
1235 'INSTALL_HDR_PATH=%s' % policy
.headers_dir
,
1237 cmdlist
.cleanup_dir()
1238 cmdlist
.pop_subdesc()
1240 class Config(object):
1241 """A configuration for building a compiler and associated libraries."""
1243 def __init__(self
, ctx
, arch
, os_name
, variant
=None, gcc_cfg
=None,
1244 first_gcc_cfg
=None, glibcs
=None, extra_glibcs
=None):
1245 """Initialize a Config object."""
1249 self
.variant
= variant
1251 self
.name
= '%s-%s' % (arch
, os_name
)
1253 self
.name
= '%s-%s-%s' % (arch
, os_name
, variant
)
1254 self
.triplet
= '%s-glibc-%s' % (arch
, os_name
)
1258 self
.gcc_cfg
= gcc_cfg
1259 if first_gcc_cfg
is None:
1260 self
.first_gcc_cfg
= []
1262 self
.first_gcc_cfg
= first_gcc_cfg
1264 glibcs
= [{'variant': variant
}]
1265 if extra_glibcs
is None:
1267 glibcs
= [Glibc(self
, **g
) for g
in glibcs
]
1268 extra_glibcs
= [Glibc(self
, **g
) for g
in extra_glibcs
]
1269 self
.all_glibcs
= glibcs
+ extra_glibcs
1270 self
.compiler_glibcs
= glibcs
1271 self
.installdir
= ctx
.compiler_installdir(self
.name
)
1272 self
.bindir
= ctx
.compiler_bindir(self
.name
)
1273 self
.sysroot
= ctx
.compiler_sysroot(self
.name
)
1274 self
.builddir
= os
.path
.join(ctx
.builddir
, 'compilers', self
.name
)
1275 self
.logsdir
= os
.path
.join(ctx
.logsdir
, 'compilers', self
.name
)
1277 def component_builddir(self
, component
):
1278 """Return the directory to use for a (non-glibc) build."""
1279 return self
.ctx
.component_builddir('compilers', self
.name
, component
)
1282 """Generate commands to build this compiler."""
1283 self
.ctx
.remove_recreate_dirs(self
.installdir
, self
.builddir
,
1285 cmdlist
= CommandList('compilers-%s' % self
.name
, self
.ctx
.keep
)
1286 cmdlist
.add_command('check-host-libraries',
1288 os
.path
.join(self
.ctx
.host_libraries_installdir
,
1290 cmdlist
.use_path(self
.bindir
)
1291 self
.build_cross_tool(cmdlist
, 'binutils', 'binutils',
1293 '--disable-libdecnumber',
1294 '--disable-readline',
1296 if self
.os
.startswith('linux'):
1297 install_linux_headers(LinuxHeadersPolicyForBuild(self
), cmdlist
)
1298 self
.build_gcc(cmdlist
, True)
1299 if self
.os
== 'gnu':
1300 self
.install_gnumach_headers(cmdlist
)
1301 self
.build_cross_tool(cmdlist
, 'mig', 'mig')
1302 self
.install_hurd_headers(cmdlist
)
1303 for g
in self
.compiler_glibcs
:
1304 cmdlist
.push_subdesc('glibc')
1305 cmdlist
.push_subdesc(g
.name
)
1306 g
.build_glibc(cmdlist
, GlibcPolicyForCompiler(g
))
1307 cmdlist
.pop_subdesc()
1308 cmdlist
.pop_subdesc()
1309 self
.build_gcc(cmdlist
, False)
1310 cmdlist
.add_command('done', ['touch',
1311 os
.path
.join(self
.installdir
, 'ok')])
1312 self
.ctx
.add_makefile_cmdlist('compilers-%s' % self
.name
, cmdlist
,
1315 def build_cross_tool(self
, cmdlist
, tool_src
, tool_build
, extra_opts
=None):
1316 """Build one cross tool."""
1317 srcdir
= self
.ctx
.component_srcdir(tool_src
)
1318 builddir
= self
.component_builddir(tool_build
)
1319 cmdlist
.push_subdesc(tool_build
)
1320 cmdlist
.create_use_dir(builddir
)
1321 cfg_cmd
= [os
.path
.join(srcdir
, 'configure'),
1322 '--prefix=%s' % self
.installdir
,
1323 '--build=%s' % self
.ctx
.build_triplet
,
1324 '--host=%s' % self
.ctx
.build_triplet
,
1325 '--target=%s' % self
.triplet
,
1326 '--with-sysroot=%s' % self
.sysroot
]
1328 cfg_cmd
.extend(extra_opts
)
1329 cmdlist
.add_command('configure', cfg_cmd
)
1330 cmdlist
.add_command('build', ['make'])
1331 # Parallel "make install" for GCC has race conditions that can
1332 # cause it to fail; see
1333 # <https://gcc.gnu.org/bugzilla/show_bug.cgi?id=42980>. Such
1334 # problems are not known for binutils, but doing the
1335 # installation in parallel within a particular toolchain build
1336 # (as opposed to installation of one toolchain from
1337 # build-many-glibcs.py running in parallel to the installation
1338 # of other toolchains being built) is not known to be
1339 # significantly beneficial, so it is simplest just to disable
1340 # parallel install for cross tools here.
1341 cmdlist
.add_command('install', ['make', '-j1', 'install'])
1342 cmdlist
.cleanup_dir()
1343 cmdlist
.pop_subdesc()
1345 def install_gnumach_headers(self
, cmdlist
):
1346 """Install GNU Mach headers."""
1347 srcdir
= self
.ctx
.component_srcdir('gnumach')
1348 builddir
= self
.component_builddir('gnumach')
1349 cmdlist
.push_subdesc('gnumach')
1350 cmdlist
.create_use_dir(builddir
)
1351 cmdlist
.add_command('configure',
1352 [os
.path
.join(srcdir
, 'configure'),
1353 '--build=%s' % self
.ctx
.build_triplet
,
1354 '--host=%s' % self
.triplet
,
1356 'CC=%s-gcc -nostdlib' % self
.triplet
])
1357 cmdlist
.add_command('install', ['make', 'DESTDIR=%s' % self
.sysroot
,
1359 cmdlist
.cleanup_dir()
1360 cmdlist
.pop_subdesc()
1362 def install_hurd_headers(self
, cmdlist
):
1363 """Install Hurd headers."""
1364 srcdir
= self
.ctx
.component_srcdir('hurd')
1365 builddir
= self
.component_builddir('hurd')
1366 cmdlist
.push_subdesc('hurd')
1367 cmdlist
.create_use_dir(builddir
)
1368 cmdlist
.add_command('configure',
1369 [os
.path
.join(srcdir
, 'configure'),
1370 '--build=%s' % self
.ctx
.build_triplet
,
1371 '--host=%s' % self
.triplet
,
1373 '--disable-profile', '--without-parted',
1374 'CC=%s-gcc -nostdlib' % self
.triplet
])
1375 cmdlist
.add_command('install', ['make', 'prefix=%s' % self
.sysroot
,
1376 'no_deps=t', 'install-headers'])
1377 cmdlist
.cleanup_dir()
1378 cmdlist
.pop_subdesc()
1380 def build_gcc(self
, cmdlist
, bootstrap
):
1382 # libssp is of little relevance with glibc's own stack
1383 # checking support. libcilkrts does not support GNU/Hurd (and
1384 # has been removed in GCC 8, so --disable-libcilkrts can be
1385 # removed once glibc no longer supports building with older
1387 cfg_opts
= list(self
.gcc_cfg
)
1388 cfg_opts
+= ['--disable-libssp', '--disable-libcilkrts']
1389 host_libs
= self
.ctx
.host_libraries_installdir
1390 cfg_opts
+= ['--with-gmp=%s' % host_libs
,
1391 '--with-mpfr=%s' % host_libs
,
1392 '--with-mpc=%s' % host_libs
]
1394 tool_build
= 'gcc-first'
1395 # Building a static-only, C-only compiler that is
1396 # sufficient to build glibc. Various libraries and
1397 # features that may require libc headers must be disabled.
1398 # When configuring with a sysroot, --with-newlib is
1399 # required to define inhibit_libc (to stop some parts of
1400 # libgcc including libc headers); --without-headers is not
1402 cfg_opts
+= ['--enable-languages=c', '--disable-shared',
1403 '--disable-threads',
1404 '--disable-libatomic',
1405 '--disable-decimal-float',
1407 '--disable-libgomp',
1410 '--disable-libquadmath',
1411 '--disable-libsanitizer',
1412 '--without-headers', '--with-newlib',
1413 '--with-glibc-version=%s' % self
.ctx
.glibc_version
1415 cfg_opts
+= self
.first_gcc_cfg
1418 # libsanitizer commonly breaks because of glibc header
1419 # changes, or on unusual targets. C++ pre-compiled
1420 # headers are not used during the glibc build and are
1421 # expensive to create.
1422 if not self
.ctx
.full_gcc
:
1423 cfg_opts
+= ['--disable-libsanitizer',
1424 '--disable-libstdcxx-pch']
1425 langs
= 'all' if self
.ctx
.full_gcc
else 'c,c++'
1426 cfg_opts
+= ['--enable-languages=%s' % langs
,
1427 '--enable-shared', '--enable-threads']
1428 self
.build_cross_tool(cmdlist
, 'gcc', tool_build
, cfg_opts
)
1430 class GlibcPolicyDefault(object):
1431 """Build policy for glibc: common defaults."""
1433 def __init__(self
, glibc
):
1434 self
.srcdir
= glibc
.ctx
.component_srcdir('glibc')
1435 self
.use_usr
= glibc
.os
!= 'gnu'
1436 self
.prefix
= '/usr' if self
.use_usr
else ''
1437 self
.configure_args
= [
1438 '--prefix=%s' % self
.prefix
,
1440 '--build=%s' % glibc
.ctx
.build_triplet
,
1441 '--host=%s' % glibc
.triplet
,
1442 'CC=%s' % glibc
.tool_name('gcc'),
1443 'CXX=%s' % glibc
.tool_name('g++'),
1444 'AR=%s' % glibc
.tool_name('ar'),
1445 'AS=%s' % glibc
.tool_name('as'),
1446 'LD=%s' % glibc
.tool_name('ld'),
1447 'NM=%s' % glibc
.tool_name('nm'),
1448 'OBJCOPY=%s' % glibc
.tool_name('objcopy'),
1449 'OBJDUMP=%s' % glibc
.tool_name('objdump'),
1450 'RANLIB=%s' % glibc
.tool_name('ranlib'),
1451 'READELF=%s' % glibc
.tool_name('readelf'),
1452 'STRIP=%s' % glibc
.tool_name('strip'),
1454 if glibc
.os
== 'gnu':
1455 self
.configure_args
.append('MIG=%s' % glibc
.tool_name('mig'))
1456 self
.configure_args
+= glibc
.cfg
1458 def configure(self
, cmdlist
):
1459 """Invoked to add the configure command to the command list."""
1460 cmdlist
.add_command('configure',
1461 [os
.path
.join(self
.srcdir
, 'configure'),
1462 *self
.configure_args
])
1464 def extra_commands(self
, cmdlist
):
1465 """Invoked to inject additional commands (make check) after build."""
1468 class GlibcPolicyForCompiler(GlibcPolicyDefault
):
1469 """Build policy for glibc during the compilers stage."""
1471 def __init__(self
, glibc
):
1472 super().__init
__(glibc
)
1473 self
.builddir
= glibc
.ctx
.component_builddir(
1474 'compilers', glibc
.compiler
.name
, 'glibc', glibc
.name
)
1475 self
.installdir
= glibc
.compiler
.sysroot
1477 class GlibcPolicyForBuild(GlibcPolicyDefault
):
1478 """Build policy for glibc during the glibcs stage."""
1480 def __init__(self
, glibc
):
1481 super().__init
__(glibc
)
1482 self
.builddir
= glibc
.ctx
.component_builddir(
1483 'glibcs', glibc
.name
, 'glibc')
1484 self
.installdir
= glibc
.ctx
.glibc_installdir(glibc
.name
)
1486 self
.strip
= glibc
.tool_name('strip')
1489 self
.save_logs
= glibc
.ctx
.save_logs
1491 def extra_commands(self
, cmdlist
):
1493 # Avoid picking up libc.so and libpthread.so, which are
1494 # linker scripts stored in /lib on Hurd. libc and
1495 # libpthread are still stripped via their libc-X.YY.so
1496 # implementation files.
1497 find_command
= (('find %s/lib* -name "*.so"'
1498 + r
' \! -name libc.so \! -name libpthread.so')
1500 cmdlist
.add_command('strip', ['sh', '-c', ('%s $(%s)' %
1501 (self
.strip
, find_command
))])
1502 cmdlist
.add_command('check', ['make', 'check'])
1503 cmdlist
.add_command('save-logs', [self
.save_logs
], always_run
=True)
1505 class GlibcPolicyForUpdateSyscalls(GlibcPolicyDefault
):
1506 """Build policy for glibc during update-syscalls."""
1508 def __init__(self
, glibc
):
1509 super().__init
__(glibc
)
1510 self
.builddir
= glibc
.ctx
.component_builddir(
1511 'update-syscalls', glibc
.name
, 'glibc')
1512 self
.linuxdir
= glibc
.ctx
.component_builddir(
1513 'update-syscalls', glibc
.name
, 'linux')
1514 self
.linux_policy
= LinuxHeadersPolicyForUpdateSyscalls(
1515 glibc
, self
.linuxdir
)
1516 self
.configure_args
.insert(
1517 0, '--with-headers=%s' % os
.path
.join(self
.linuxdir
, 'include'))
1518 # self.installdir not set because installation is not supported
1520 class Glibc(object):
1521 """A configuration for building glibc."""
1523 def __init__(self
, compiler
, arch
=None, os_name
=None, variant
=None,
1524 cfg
=None, ccopts
=None):
1525 """Initialize a Glibc object."""
1526 self
.ctx
= compiler
.ctx
1527 self
.compiler
= compiler
1529 self
.arch
= compiler
.arch
1533 self
.os
= compiler
.os
1536 self
.variant
= variant
1538 self
.name
= '%s-%s' % (self
.arch
, self
.os
)
1540 self
.name
= '%s-%s-%s' % (self
.arch
, self
.os
, variant
)
1541 self
.triplet
= '%s-glibc-%s' % (self
.arch
, self
.os
)
1546 self
.ccopts
= ccopts
1548 def tool_name(self
, tool
):
1549 """Return the name of a cross-compilation tool."""
1550 ctool
= '%s-%s' % (self
.compiler
.triplet
, tool
)
1551 if self
.ccopts
and (tool
== 'gcc' or tool
== 'g++'):
1552 ctool
= '%s %s' % (ctool
, self
.ccopts
)
1556 """Generate commands to build this glibc."""
1557 builddir
= self
.ctx
.component_builddir('glibcs', self
.name
, 'glibc')
1558 installdir
= self
.ctx
.glibc_installdir(self
.name
)
1559 logsdir
= os
.path
.join(self
.ctx
.logsdir
, 'glibcs', self
.name
)
1560 self
.ctx
.remove_recreate_dirs(installdir
, builddir
, logsdir
)
1561 cmdlist
= CommandList('glibcs-%s' % self
.name
, self
.ctx
.keep
)
1562 cmdlist
.add_command('check-compilers',
1564 os
.path
.join(self
.compiler
.installdir
, 'ok')])
1565 cmdlist
.use_path(self
.compiler
.bindir
)
1566 self
.build_glibc(cmdlist
, GlibcPolicyForBuild(self
))
1567 self
.ctx
.add_makefile_cmdlist('glibcs-%s' % self
.name
, cmdlist
,
1570 def build_glibc(self
, cmdlist
, policy
):
1571 """Generate commands to build this glibc, either as part of a compiler
1572 build or with the bootstrapped compiler (and in the latter case, run
1574 cmdlist
.create_use_dir(policy
.builddir
)
1575 policy
.configure(cmdlist
)
1576 cmdlist
.add_command('build', ['make'])
1577 cmdlist
.add_command('install', ['make', 'install',
1578 'install_root=%s' % policy
.installdir
])
1579 # GCC uses paths such as lib/../lib64, so make sure lib
1580 # directories always exist.
1581 mkdir_cmd
= ['mkdir', '-p',
1582 os
.path
.join(policy
.installdir
, 'lib')]
1584 mkdir_cmd
+= [os
.path
.join(policy
.installdir
, 'usr', 'lib')]
1585 cmdlist
.add_command('mkdir-lib', mkdir_cmd
)
1586 policy
.extra_commands(cmdlist
)
1587 cmdlist
.cleanup_dir()
1589 def update_syscalls(self
):
1590 if self
.os
== 'gnu':
1591 # Hurd does not have system call tables that need updating.
1594 policy
= GlibcPolicyForUpdateSyscalls(self
)
1595 logsdir
= os
.path
.join(self
.ctx
.logsdir
, 'update-syscalls', self
.name
)
1596 self
.ctx
.remove_recreate_dirs(policy
.builddir
, logsdir
)
1597 cmdlist
= CommandList('update-syscalls-%s' % self
.name
, self
.ctx
.keep
)
1598 cmdlist
.add_command('check-compilers',
1600 os
.path
.join(self
.compiler
.installdir
, 'ok')])
1601 cmdlist
.use_path(self
.compiler
.bindir
)
1603 install_linux_headers(policy
.linux_policy
, cmdlist
)
1605 cmdlist
.create_use_dir(policy
.builddir
)
1606 policy
.configure(cmdlist
)
1607 cmdlist
.add_command('build', ['make', 'update-syscall-lists'])
1608 cmdlist
.cleanup_dir()
1609 self
.ctx
.add_makefile_cmdlist('update-syscalls-%s' % self
.name
,
1612 class Command(object):
1613 """A command run in the build process."""
1615 def __init__(self
, desc
, num
, dir, path
, command
, always_run
=False):
1616 """Initialize a Command object."""
1620 trans
= str.maketrans({' ': '-'})
1621 self
.logbase
= '%03d-%s' % (num
, desc
.translate(trans
))
1622 self
.command
= command
1623 self
.always_run
= always_run
1626 def shell_make_quote_string(s
):
1627 """Given a string not containing a newline, quote it for use by the
1629 assert '\n' not in s
1630 if re
.fullmatch('[]+,./0-9@A-Z_a-z-]+', s
):
1632 strans
= str.maketrans({"'": "'\\''"})
1633 s
= "'%s'" % s
.translate(strans
)
1634 mtrans
= str.maketrans({'$': '$$'})
1635 return s
.translate(mtrans
)
1638 def shell_make_quote_list(l
, translate_make
):
1639 """Given a list of strings not containing newlines, quote them for use
1640 by the shell and make, returning a single string. If translate_make
1641 is true and the first string is 'make', change it to $(MAKE)."""
1642 l
= [Command
.shell_make_quote_string(s
) for s
in l
]
1643 if translate_make
and l
[0] == 'make':
1647 def shell_make_quote(self
):
1648 """Return this command quoted for the shell and make."""
1649 return self
.shell_make_quote_list(self
.command
, True)
1652 class CommandList(object):
1653 """A list of commands run in the build process."""
1655 def __init__(self
, desc
, keep
):
1656 """Initialize a CommandList object."""
1663 def desc_txt(self
, desc
):
1664 """Return the description to use for a command."""
1665 return '%s %s' % (' '.join(self
.desc
), desc
)
1667 def use_dir(self
, dir):
1668 """Set the default directory for subsequent commands."""
1671 def use_path(self
, path
):
1672 """Set a directory to be prepended to the PATH for subsequent
1676 def push_subdesc(self
, subdesc
):
1677 """Set the default subdescription for subsequent commands (e.g., the
1678 name of a component being built, within the series of commands
1680 self
.desc
.append(subdesc
)
1682 def pop_subdesc(self
):
1683 """Pop a subdescription from the list of descriptions."""
1686 def create_use_dir(self
, dir):
1687 """Remove and recreate a directory and use it for subsequent
1689 self
.add_command_dir('rm', None, ['rm', '-rf', dir])
1690 self
.add_command_dir('mkdir', None, ['mkdir', '-p', dir])
1693 def add_command_dir(self
, desc
, dir, command
, always_run
=False):
1694 """Add a command to run in a given directory."""
1695 cmd
= Command(self
.desc_txt(desc
), len(self
.cmdlist
), dir, self
.path
,
1696 command
, always_run
)
1697 self
.cmdlist
.append(cmd
)
1699 def add_command(self
, desc
, command
, always_run
=False):
1700 """Add a command to run in the default directory."""
1701 cmd
= Command(self
.desc_txt(desc
), len(self
.cmdlist
), self
.dir,
1702 self
.path
, command
, always_run
)
1703 self
.cmdlist
.append(cmd
)
1705 def cleanup_dir(self
, desc
='cleanup', dir=None):
1706 """Clean up a build directory. If no directory is specified, the
1707 default directory is cleaned up and ceases to be the default
1712 if self
.keep
!= 'all':
1713 self
.add_command_dir(desc
, None, ['rm', '-rf', dir],
1714 always_run
=(self
.keep
== 'none'))
1716 def makefile_commands(self
, wrapper
, logsdir
):
1717 """Return the sequence of commands in the form of text for a Makefile.
1718 The given wrapper script takes arguments: base of logs for
1719 previous command, or empty; base of logs for this command;
1720 description; directory; PATH addition; the command itself."""
1721 # prev_base is the base of the name for logs of the previous
1722 # command that is not always-run (that is, a build command,
1723 # whose failure should stop subsequent build commands from
1724 # being run, as opposed to a cleanup command, which is run
1725 # even if previous commands failed).
1728 for c
in self
.cmdlist
:
1729 ctxt
= c
.shell_make_quote()
1730 if prev_base
and not c
.always_run
:
1731 prev_log
= os
.path
.join(logsdir
, prev_base
)
1734 this_log
= os
.path
.join(logsdir
, c
.logbase
)
1735 if not c
.always_run
:
1736 prev_base
= c
.logbase
1745 prelims
= [wrapper
, prev_log
, this_log
, c
.desc
, dir, path
]
1746 prelim_txt
= Command
.shell_make_quote_list(prelims
, False)
1747 cmds
.append('\t@%s %s' % (prelim_txt
, ctxt
))
1748 return '\n'.join(cmds
)
1750 def status_logs(self
, logsdir
):
1751 """Return the list of log files with command status."""
1752 return [os
.path
.join(logsdir
, '%s-status.txt' % c
.logbase
)
1753 for c
in self
.cmdlist
]
1757 """Return an argument parser for this module."""
1758 parser
= argparse
.ArgumentParser(description
=__doc__
)
1759 parser
.add_argument('-j', dest
='parallelism',
1760 help='Run this number of jobs in parallel',
1761 type=int, default
=os
.cpu_count())
1762 parser
.add_argument('--keep', dest
='keep',
1763 help='Whether to keep all build directories, '
1764 'none or only those from failed builds',
1765 default
='none', choices
=('none', 'all', 'failed'))
1766 parser
.add_argument('--replace-sources', action
='store_true',
1767 help='Remove and replace source directories '
1768 'with the wrong version of a component')
1769 parser
.add_argument('--strip', action
='store_true',
1770 help='Strip installed glibc libraries')
1771 parser
.add_argument('--full-gcc', action
='store_true',
1772 help='Build GCC with all languages and libsanitizer')
1773 parser
.add_argument('topdir',
1774 help='Toplevel working directory')
1775 parser
.add_argument('action',
1777 choices
=('checkout', 'bot-cycle', 'bot',
1778 'host-libraries', 'compilers', 'glibcs',
1780 parser
.add_argument('configs',
1781 help='Versions to check out or configurations to build',
1787 """The main entry point."""
1788 parser
= get_parser()
1789 opts
= parser
.parse_args(argv
)
1790 topdir
= os
.path
.abspath(opts
.topdir
)
1791 ctx
= Context(topdir
, opts
.parallelism
, opts
.keep
, opts
.replace_sources
,
1792 opts
.strip
, opts
.full_gcc
, opts
.action
)
1793 ctx
.run_builds(opts
.action
, opts
.configs
)
1796 if __name__
== '__main__':