2 # Build many configurations of glibc.
3 # Copyright (C) 2016-2019 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 # <http://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 self
.add_config(arch
='armeb',
163 os_name
='linux-gnueabi')
164 self
.add_config(arch
='armeb',
165 os_name
='linux-gnueabi',
167 gcc_cfg
=['--with-arch=armv7-a'])
168 self
.add_config(arch
='arm',
169 os_name
='linux-gnueabihf',
170 gcc_cfg
=['--with-float=hard', '--with-cpu=arm926ej-s'],
171 extra_glibcs
=[{'variant': 'v7a',
172 'ccopts': '-march=armv7-a -mfpu=vfpv3'},
173 {'variant': 'v7a-disable-multi-arch',
174 'ccopts': '-march=armv7-a -mfpu=vfpv3',
175 'cfg': ['--disable-multi-arch']}])
176 self
.add_config(arch
='armeb',
177 os_name
='linux-gnueabihf',
178 gcc_cfg
=['--with-float=hard', '--with-cpu=arm926ej-s'])
179 self
.add_config(arch
='armeb',
180 os_name
='linux-gnueabihf',
182 gcc_cfg
=['--with-float=hard', '--with-arch=armv7-a',
184 self
.add_config(arch
='csky',
185 os_name
='linux-gnuabiv2',
187 gcc_cfg
=['--disable-multilib'])
188 self
.add_config(arch
='csky',
189 os_name
='linux-gnuabiv2',
190 gcc_cfg
=['--with-float=hard', '--disable-multilib'])
191 self
.add_config(arch
='hppa',
193 self
.add_config(arch
='i686',
195 self
.add_config(arch
='ia64',
197 first_gcc_cfg
=['--with-system-libunwind'])
198 self
.add_config(arch
='m68k',
200 gcc_cfg
=['--disable-multilib'])
201 self
.add_config(arch
='m68k',
204 gcc_cfg
=['--with-arch=cf', '--disable-multilib'])
205 self
.add_config(arch
='m68k',
207 variant
='coldfire-soft',
208 gcc_cfg
=['--with-arch=cf', '--with-cpu=54455',
209 '--disable-multilib'])
210 self
.add_config(arch
='microblaze',
212 gcc_cfg
=['--disable-multilib'])
213 self
.add_config(arch
='microblazeel',
215 gcc_cfg
=['--disable-multilib'])
216 self
.add_config(arch
='mips64',
218 gcc_cfg
=['--with-mips-plt'],
219 glibcs
=[{'variant': 'n32'},
221 'ccopts': '-mabi=32'},
223 'ccopts': '-mabi=64'}])
224 self
.add_config(arch
='mips64',
227 gcc_cfg
=['--with-mips-plt', '--with-float=soft'],
228 glibcs
=[{'variant': 'n32-soft'},
231 'ccopts': '-mabi=32'},
232 {'variant': 'n64-soft',
233 'ccopts': '-mabi=64'}])
234 self
.add_config(arch
='mips64',
237 gcc_cfg
=['--with-mips-plt', '--with-nan=2008',
238 '--with-arch-64=mips64r2',
239 '--with-arch-32=mips32r2'],
240 glibcs
=[{'variant': 'n32-nan2008'},
241 {'variant': 'nan2008',
243 'ccopts': '-mabi=32'},
244 {'variant': 'n64-nan2008',
245 'ccopts': '-mabi=64'}])
246 self
.add_config(arch
='mips64',
248 variant
='nan2008-soft',
249 gcc_cfg
=['--with-mips-plt', '--with-nan=2008',
250 '--with-arch-64=mips64r2',
251 '--with-arch-32=mips32r2',
252 '--with-float=soft'],
253 glibcs
=[{'variant': 'n32-nan2008-soft'},
254 {'variant': 'nan2008-soft',
256 'ccopts': '-mabi=32'},
257 {'variant': 'n64-nan2008-soft',
258 'ccopts': '-mabi=64'}])
259 self
.add_config(arch
='mips64el',
261 gcc_cfg
=['--with-mips-plt'],
262 glibcs
=[{'variant': 'n32'},
264 'ccopts': '-mabi=32'},
266 'ccopts': '-mabi=64'}])
267 self
.add_config(arch
='mips64el',
270 gcc_cfg
=['--with-mips-plt', '--with-float=soft'],
271 glibcs
=[{'variant': 'n32-soft'},
274 'ccopts': '-mabi=32'},
275 {'variant': 'n64-soft',
276 'ccopts': '-mabi=64'}])
277 self
.add_config(arch
='mips64el',
280 gcc_cfg
=['--with-mips-plt', '--with-nan=2008',
281 '--with-arch-64=mips64r2',
282 '--with-arch-32=mips32r2'],
283 glibcs
=[{'variant': 'n32-nan2008'},
284 {'variant': 'nan2008',
286 'ccopts': '-mabi=32'},
287 {'variant': 'n64-nan2008',
288 'ccopts': '-mabi=64'}])
289 self
.add_config(arch
='mips64el',
291 variant
='nan2008-soft',
292 gcc_cfg
=['--with-mips-plt', '--with-nan=2008',
293 '--with-arch-64=mips64r2',
294 '--with-arch-32=mips32r2',
295 '--with-float=soft'],
296 glibcs
=[{'variant': 'n32-nan2008-soft'},
297 {'variant': 'nan2008-soft',
299 'ccopts': '-mabi=32'},
300 {'variant': 'n64-nan2008-soft',
301 'ccopts': '-mabi=64'}])
302 self
.add_config(arch
='nios2',
304 self
.add_config(arch
='powerpc',
306 gcc_cfg
=['--disable-multilib', '--enable-secureplt'],
307 extra_glibcs
=[{'variant': 'power4',
308 'ccopts': '-mcpu=power4',
309 'cfg': ['--with-cpu=power4']}])
310 self
.add_config(arch
='powerpc',
313 gcc_cfg
=['--disable-multilib', '--with-float=soft',
314 '--enable-secureplt'])
315 self
.add_config(arch
='powerpc64',
317 gcc_cfg
=['--disable-multilib', '--enable-secureplt'])
318 self
.add_config(arch
='powerpc64le',
320 gcc_cfg
=['--disable-multilib', '--enable-secureplt'])
321 self
.add_config(arch
='powerpc',
322 os_name
='linux-gnuspe',
323 gcc_cfg
=['--disable-multilib', '--enable-secureplt',
324 '--enable-e500-double', '--enable-obsolete'])
325 self
.add_config(arch
='powerpc',
326 os_name
='linux-gnuspe',
328 gcc_cfg
=['--disable-multilib', '--enable-secureplt',
329 '--enable-obsolete'])
330 self
.add_config(arch
='riscv64',
332 variant
='rv64imac-lp64',
333 gcc_cfg
=['--with-arch=rv64imac', '--with-abi=lp64',
334 '--disable-multilib'])
335 self
.add_config(arch
='riscv64',
337 variant
='rv64imafdc-lp64',
338 gcc_cfg
=['--with-arch=rv64imafdc', '--with-abi=lp64',
339 '--disable-multilib'])
340 self
.add_config(arch
='riscv64',
342 variant
='rv64imafdc-lp64d',
343 gcc_cfg
=['--with-arch=rv64imafdc', '--with-abi=lp64d',
344 '--disable-multilib'])
345 self
.add_config(arch
='s390x',
348 {'arch': 's390', 'ccopts': '-m31'}])
349 self
.add_config(arch
='sh3',
351 self
.add_config(arch
='sh3eb',
353 self
.add_config(arch
='sh4',
355 self
.add_config(arch
='sh4eb',
357 self
.add_config(arch
='sh4',
360 gcc_cfg
=['--without-fp'])
361 self
.add_config(arch
='sh4eb',
364 gcc_cfg
=['--without-fp'])
365 self
.add_config(arch
='sparc64',
369 'ccopts': '-m32 -mlong-double-128'}],
370 extra_glibcs
=[{'variant': 'disable-multi-arch',
371 'cfg': ['--disable-multi-arch']},
372 {'variant': 'disable-multi-arch',
374 'ccopts': '-m32 -mlong-double-128',
375 'cfg': ['--disable-multi-arch']}])
376 self
.add_config(arch
='x86_64',
378 gcc_cfg
=['--with-multilib-list=m64,m32,mx32'],
380 {'variant': 'x32', 'ccopts': '-mx32'},
381 {'arch': 'i686', 'ccopts': '-m32 -march=i686'}],
382 extra_glibcs
=[{'variant': 'disable-multi-arch',
383 'cfg': ['--disable-multi-arch']},
384 {'variant': 'enable-obsolete',
385 'cfg': ['--enable-obsolete-rpc',
386 '--enable-obsolete-nsl']},
387 {'variant': 'static-pie',
388 'cfg': ['--enable-static-pie']},
389 {'variant': 'x32-static-pie',
391 'cfg': ['--enable-static-pie']},
392 {'variant': 'static-pie',
394 'ccopts': '-m32 -march=i686',
395 'cfg': ['--enable-static-pie']},
396 {'variant': 'disable-multi-arch',
398 'ccopts': '-m32 -march=i686',
399 'cfg': ['--disable-multi-arch']},
400 {'variant': 'enable-obsolete',
402 'ccopts': '-m32 -march=i686',
403 'cfg': ['--enable-obsolete-rpc',
404 '--enable-obsolete-nsl']},
406 'ccopts': '-m32 -march=i486'},
408 'ccopts': '-m32 -march=i586'}])
410 def add_config(self
, **args
):
411 """Add an individual build configuration."""
412 cfg
= Config(self
, **args
)
413 if cfg
.name
in self
.configs
:
414 print('error: duplicate config %s' % cfg
.name
)
416 self
.configs
[cfg
.name
] = cfg
417 for c
in cfg
.all_glibcs
:
418 if c
.name
in self
.glibc_configs
:
419 print('error: duplicate glibc config %s' % c
.name
)
421 self
.glibc_configs
[c
.name
] = c
423 def component_srcdir(self
, component
):
424 """Return the source directory for a given component, e.g. gcc."""
425 return os
.path
.join(self
.srcdir
, component
)
427 def component_builddir(self
, action
, config
, component
, subconfig
=None):
428 """Return the directory to use for a build."""
431 assert subconfig
is None
432 return os
.path
.join(self
.builddir
, action
, component
)
433 if subconfig
is None:
434 return os
.path
.join(self
.builddir
, action
, config
, component
)
436 # glibc build as part of compiler build.
437 return os
.path
.join(self
.builddir
, action
, config
, component
,
440 def compiler_installdir(self
, config
):
441 """Return the directory in which to install a compiler."""
442 return os
.path
.join(self
.installdir
, 'compilers', config
)
444 def compiler_bindir(self
, config
):
445 """Return the directory in which to find compiler binaries."""
446 return os
.path
.join(self
.compiler_installdir(config
), 'bin')
448 def compiler_sysroot(self
, config
):
449 """Return the sysroot directory for a compiler."""
450 return os
.path
.join(self
.compiler_installdir(config
), 'sysroot')
452 def glibc_installdir(self
, config
):
453 """Return the directory in which to install glibc."""
454 return os
.path
.join(self
.installdir
, 'glibcs', config
)
456 def run_builds(self
, action
, configs
):
457 """Run the requested builds."""
458 if action
== 'checkout':
459 self
.checkout(configs
)
461 if action
== 'bot-cycle':
463 print('error: configurations specified for bot-cycle')
469 print('error: configurations specified for bot')
473 if action
== 'host-libraries' and configs
:
474 print('error: configurations specified for host-libraries')
476 self
.clear_last_build_state(action
)
477 build_time
= datetime
.datetime
.utcnow()
478 if action
== 'host-libraries':
479 build_components
= ('gmp', 'mpfr', 'mpc')
482 self
.build_host_libraries()
483 elif action
== 'compilers':
484 build_components
= ('binutils', 'gcc', 'glibc', 'linux', 'mig',
486 old_components
= ('gmp', 'mpfr', 'mpc')
487 old_versions
= self
.build_state
['host-libraries']['build-versions']
488 self
.build_compilers(configs
)
490 build_components
= ('glibc',)
491 old_components
= ('gmp', 'mpfr', 'mpc', 'binutils', 'gcc', 'linux',
492 'mig', 'gnumach', 'hurd')
493 old_versions
= self
.build_state
['compilers']['build-versions']
494 self
.build_glibcs(configs
)
498 # Partial build, do not update stored state.
501 for k
in build_components
:
502 if k
in self
.versions
:
503 build_versions
[k
] = {'version': self
.versions
[k
]['version'],
504 'revision': self
.versions
[k
]['revision']}
505 for k
in old_components
:
506 if k
in old_versions
:
507 build_versions
[k
] = {'version': old_versions
[k
]['version'],
508 'revision': old_versions
[k
]['revision']}
509 self
.update_build_state(action
, build_time
, build_versions
)
512 def remove_dirs(*args
):
513 """Remove directories and their contents if they exist."""
515 shutil
.rmtree(dir, ignore_errors
=True)
518 def remove_recreate_dirs(*args
):
519 """Remove directories if they exist, and create them as empty."""
520 Context
.remove_dirs(*args
)
522 os
.makedirs(dir, exist_ok
=True)
524 def add_makefile_cmdlist(self
, target
, cmdlist
, logsdir
):
525 """Add makefile text for a list of commands."""
526 commands
= cmdlist
.makefile_commands(self
.wrapper
, logsdir
)
527 self
.makefile_pieces
.append('all: %s\n.PHONY: %s\n%s:\n%s\n' %
528 (target
, target
, target
, commands
))
529 self
.status_log_list
.extend(cmdlist
.status_logs(logsdir
))
531 def write_files(self
):
532 """Write out the Makefile and wrapper script."""
533 mftext
= ''.join(self
.makefile_pieces
)
534 with
open(self
.makefile
, 'w') as f
:
544 'prev_status=$prev_base-status.txt\n'
545 'this_status=$this_base-status.txt\n'
546 'this_log=$this_base-log.txt\n'
547 'date > "$this_log"\n'
548 'echo >> "$this_log"\n'
549 'echo "Description: $desc" >> "$this_log"\n'
550 'printf "%s" "Command:" >> "$this_log"\n'
551 'for word in "$@"; do\n'
552 ' if expr "$word" : "[]+,./0-9@A-Z_a-z-]\\\\{1,\\\\}\\$" > /dev/null; then\n'
553 ' printf " %s" "$word"\n'
556 ' printf "%s" "$word" | sed -e "s/\'/\'\\\\\\\\\'\'/"\n'
559 'done >> "$this_log"\n'
560 'echo >> "$this_log"\n'
561 'echo "Directory: $dir" >> "$this_log"\n'
562 'echo "Path addition: $path" >> "$this_log"\n'
563 'echo >> "$this_log"\n'
566 ' echo >> "$this_log"\n'
567 ' echo "$1: $desc" > "$this_status"\n'
568 ' echo "$1: $desc" >> "$this_log"\n'
569 ' echo >> "$this_log"\n'
570 ' date >> "$this_log"\n'
571 ' echo "$1: $desc"\n'
576 ' if [ "$1" != "0" ]; then\n'
577 ' record_status FAIL\n'
580 'if [ "$prev_base" ] && ! grep -q "^PASS" "$prev_status"; then\n'
581 ' record_status UNRESOLVED\n'
583 'if [ "$dir" ]; then\n'
585 ' check_error "$?"\n'
587 'if [ "$path" ]; then\n'
588 ' PATH=$path:$PATH\n'
590 '"$@" < /dev/null >> "$this_log" 2>&1\n'
592 'record_status PASS\n')
593 with
open(self
.wrapper
, 'w') as f
:
594 f
.write(wrapper_text
)
596 mode_exec
= (stat
.S_IRWXU|stat
.S_IRGRP|stat
.S_IXGRP|
597 stat
.S_IROTH|stat
.S_IXOTH
)
598 os
.chmod(self
.wrapper
, mode_exec
)
601 'if ! [ -f tests.sum ]; then\n'
602 ' echo "No test summary available."\n'
607 ' echo "Contents of $1:"\n'
611 ' echo "End of contents of $1."\n'
614 'save_file tests.sum\n'
615 'non_pass_tests=$(grep -v "^PASS: " tests.sum | sed -e "s/^PASS: //")\n'
616 'for t in $non_pass_tests; do\n'
617 ' if [ -f "$t.out" ]; then\n'
618 ' save_file "$t.out"\n'
621 with
open(self
.save_logs
, 'w') as f
:
622 f
.write(save_logs_text
)
623 os
.chmod(self
.save_logs
, mode_exec
)
626 """Do the actual build."""
627 cmd
= ['make', '-j%d' % self
.parallelism
]
628 subprocess
.run(cmd
, cwd
=self
.builddir
, check
=True)
630 def build_host_libraries(self
):
631 """Build the host libraries."""
632 installdir
= self
.host_libraries_installdir
633 builddir
= os
.path
.join(self
.builddir
, 'host-libraries')
634 logsdir
= os
.path
.join(self
.logsdir
, 'host-libraries')
635 self
.remove_recreate_dirs(installdir
, builddir
, logsdir
)
636 cmdlist
= CommandList('host-libraries', self
.keep
)
637 self
.build_host_library(cmdlist
, 'gmp')
638 self
.build_host_library(cmdlist
, 'mpfr',
639 ['--with-gmp=%s' % installdir
])
640 self
.build_host_library(cmdlist
, 'mpc',
641 ['--with-gmp=%s' % installdir
,
642 '--with-mpfr=%s' % installdir
])
643 cmdlist
.add_command('done', ['touch', os
.path
.join(installdir
, 'ok')])
644 self
.add_makefile_cmdlist('host-libraries', cmdlist
, logsdir
)
646 def build_host_library(self
, cmdlist
, lib
, extra_opts
=None):
647 """Build one host library."""
648 srcdir
= self
.component_srcdir(lib
)
649 builddir
= self
.component_builddir('host-libraries', None, lib
)
650 installdir
= self
.host_libraries_installdir
651 cmdlist
.push_subdesc(lib
)
652 cmdlist
.create_use_dir(builddir
)
653 cfg_cmd
= [os
.path
.join(srcdir
, 'configure'),
654 '--prefix=%s' % installdir
,
657 cfg_cmd
.extend (extra_opts
)
658 cmdlist
.add_command('configure', cfg_cmd
)
659 cmdlist
.add_command('build', ['make'])
660 cmdlist
.add_command('check', ['make', 'check'])
661 cmdlist
.add_command('install', ['make', 'install'])
662 cmdlist
.cleanup_dir()
663 cmdlist
.pop_subdesc()
665 def build_compilers(self
, configs
):
666 """Build the compilers."""
668 self
.remove_dirs(os
.path
.join(self
.builddir
, 'compilers'))
669 self
.remove_dirs(os
.path
.join(self
.installdir
, 'compilers'))
670 self
.remove_dirs(os
.path
.join(self
.logsdir
, 'compilers'))
671 configs
= sorted(self
.configs
.keys())
673 self
.configs
[c
].build()
675 def build_glibcs(self
, configs
):
676 """Build the glibcs."""
678 self
.remove_dirs(os
.path
.join(self
.builddir
, 'glibcs'))
679 self
.remove_dirs(os
.path
.join(self
.installdir
, 'glibcs'))
680 self
.remove_dirs(os
.path
.join(self
.logsdir
, 'glibcs'))
681 configs
= sorted(self
.glibc_configs
.keys())
683 self
.glibc_configs
[c
].build()
685 def load_versions_json(self
):
686 """Load information about source directory versions."""
687 if not os
.access(self
.versions_json
, os
.F_OK
):
690 with
open(self
.versions_json
, 'r') as f
:
691 self
.versions
= json
.load(f
)
693 def store_json(self
, data
, filename
):
694 """Store information in a JSON file."""
695 filename_tmp
= filename
+ '.tmp'
696 with
open(filename_tmp
, 'w') as f
:
697 json
.dump(data
, f
, indent
=2, sort_keys
=True)
698 os
.rename(filename_tmp
, filename
)
700 def store_versions_json(self
):
701 """Store information about source directory versions."""
702 self
.store_json(self
.versions
, self
.versions_json
)
704 def set_component_version(self
, component
, version
, explicit
, revision
):
705 """Set the version information for a component."""
706 self
.versions
[component
] = {'version': version
,
707 'explicit': explicit
,
708 'revision': revision
}
709 self
.store_versions_json()
711 def checkout(self
, versions
):
712 """Check out the desired component versions."""
713 default_versions
= {'binutils': 'vcs-2.32',
715 'glibc': 'vcs-mainline',
720 'mig': 'vcs-mainline',
721 'gnumach': 'vcs-mainline',
722 'hurd': 'vcs-mainline'}
724 explicit_versions
= {}
727 for k
in default_versions
.keys():
731 if k
in use_versions
:
732 print('error: multiple versions for %s' % k
)
735 explicit_versions
[k
] = True
739 print('error: unknown component in %s' % v
)
741 for k
in default_versions
.keys():
742 if k
not in use_versions
:
743 if k
in self
.versions
and self
.versions
[k
]['explicit']:
744 use_versions
[k
] = self
.versions
[k
]['version']
745 explicit_versions
[k
] = True
747 use_versions
[k
] = default_versions
[k
]
748 explicit_versions
[k
] = False
749 os
.makedirs(self
.srcdir
, exist_ok
=True)
750 for k
in sorted(default_versions
.keys()):
751 update
= os
.access(self
.component_srcdir(k
), os
.F_OK
)
754 k
in self
.versions
and
755 v
!= self
.versions
[k
]['version']):
756 if not self
.replace_sources
:
757 print('error: version of %s has changed from %s to %s, '
758 'use --replace-sources to check out again' %
759 (k
, self
.versions
[k
]['version'], v
))
761 shutil
.rmtree(self
.component_srcdir(k
))
763 if v
.startswith('vcs-'):
764 revision
= self
.checkout_vcs(k
, v
[4:], update
)
766 self
.checkout_tar(k
, v
, update
)
768 self
.set_component_version(k
, v
, explicit_versions
[k
], revision
)
769 if self
.get_script_text() != self
.script_text
:
770 # Rerun the checkout process in case the updated script
771 # uses different default versions or new components.
774 def checkout_vcs(self
, component
, version
, update
):
775 """Check out the given version of the given component from version
776 control. Return a revision identifier."""
777 if component
== 'binutils':
778 git_url
= 'git://sourceware.org/git/binutils-gdb.git'
779 if version
== 'mainline':
780 git_branch
= 'master'
782 trans
= str.maketrans({'.': '_'})
783 git_branch
= 'binutils-%s-branch' % version
.translate(trans
)
784 return self
.git_checkout(component
, git_url
, git_branch
, update
)
785 elif component
== 'gcc':
786 if version
== 'mainline':
789 trans
= str.maketrans({'.': '_'})
790 branch
= 'branches/gcc-%s-branch' % version
.translate(trans
)
791 svn_url
= 'svn://gcc.gnu.org/svn/gcc/%s' % branch
792 return self
.gcc_checkout(svn_url
, update
)
793 elif component
== 'glibc':
794 git_url
= 'git://sourceware.org/git/glibc.git'
795 if version
== 'mainline':
796 git_branch
= 'master'
798 git_branch
= 'release/%s/master' % version
799 r
= self
.git_checkout(component
, git_url
, git_branch
, update
)
800 self
.fix_glibc_timestamps()
802 elif component
== 'gnumach':
803 git_url
= 'git://git.savannah.gnu.org/hurd/gnumach.git'
804 git_branch
= 'master'
805 r
= self
.git_checkout(component
, git_url
, git_branch
, update
)
806 subprocess
.run(['autoreconf', '-i'],
807 cwd
=self
.component_srcdir(component
), check
=True)
809 elif component
== 'mig':
810 git_url
= 'git://git.savannah.gnu.org/hurd/mig.git'
811 git_branch
= 'master'
812 r
= self
.git_checkout(component
, git_url
, git_branch
, update
)
813 subprocess
.run(['autoreconf', '-i'],
814 cwd
=self
.component_srcdir(component
), check
=True)
816 elif component
== 'hurd':
817 git_url
= 'git://git.savannah.gnu.org/hurd/hurd.git'
818 git_branch
= 'master'
819 r
= self
.git_checkout(component
, git_url
, git_branch
, update
)
820 subprocess
.run(['autoconf'],
821 cwd
=self
.component_srcdir(component
), check
=True)
824 print('error: component %s coming from VCS' % component
)
827 def git_checkout(self
, component
, git_url
, git_branch
, update
):
828 """Check out a component from git. Return a commit identifier."""
830 subprocess
.run(['git', 'remote', 'prune', 'origin'],
831 cwd
=self
.component_srcdir(component
), check
=True)
832 if self
.replace_sources
:
833 subprocess
.run(['git', 'clean', '-dxfq'],
834 cwd
=self
.component_srcdir(component
), check
=True)
835 subprocess
.run(['git', 'pull', '-q'],
836 cwd
=self
.component_srcdir(component
), check
=True)
838 subprocess
.run(['git', 'clone', '-q', '-b', git_branch
, git_url
,
839 self
.component_srcdir(component
)], check
=True)
840 r
= subprocess
.run(['git', 'rev-parse', 'HEAD'],
841 cwd
=self
.component_srcdir(component
),
842 stdout
=subprocess
.PIPE
,
843 check
=True, universal_newlines
=True).stdout
846 def fix_glibc_timestamps(self
):
847 """Fix timestamps in a glibc checkout."""
848 # Ensure that builds do not try to regenerate generated files
849 # in the source tree.
850 srcdir
= self
.component_srcdir('glibc')
851 # These files have Makefile dependencies to regenerate them in
852 # the source tree that may be active during a normal build.
853 # Some other files have such dependencies but do not need to
854 # be touched because nothing in a build depends on the files
856 for f
in ('sysdeps/gnu/errlist.c',
857 'sysdeps/mach/hurd/bits/errno.h',
858 'sysdeps/sparc/sparc32/rem.S',
859 'sysdeps/sparc/sparc32/sdiv.S',
860 'sysdeps/sparc/sparc32/udiv.S',
861 'sysdeps/sparc/sparc32/urem.S'):
862 to_touch
= os
.path
.join(srcdir
, f
)
863 subprocess
.run(['touch', '-c', to_touch
], check
=True)
864 for dirpath
, dirnames
, filenames
in os
.walk(srcdir
):
866 if (f
== 'configure' or
867 f
== 'preconfigure' or
868 f
.endswith('-kw.h')):
869 to_touch
= os
.path
.join(dirpath
, f
)
870 subprocess
.run(['touch', to_touch
], check
=True)
872 def gcc_checkout(self
, svn_url
, update
):
873 """Check out GCC from SVN. Return the revision number."""
875 subprocess
.run(['svn', 'co', '-q', svn_url
,
876 self
.component_srcdir('gcc')], check
=True)
877 subprocess
.run(['contrib/gcc_update', '--silent'],
878 cwd
=self
.component_srcdir('gcc'), check
=True)
879 r
= subprocess
.run(['svnversion', self
.component_srcdir('gcc')],
880 stdout
=subprocess
.PIPE
,
881 check
=True, universal_newlines
=True).stdout
884 def checkout_tar(self
, component
, version
, update
):
885 """Check out the given version of the given component from a
889 url_map
= {'binutils': 'https://ftp.gnu.org/gnu/binutils/binutils-%(version)s.tar.bz2',
890 'gcc': 'https://ftp.gnu.org/gnu/gcc/gcc-%(version)s/gcc-%(version)s.tar.gz',
891 'gmp': 'https://ftp.gnu.org/gnu/gmp/gmp-%(version)s.tar.xz',
892 'linux': 'https://www.kernel.org/pub/linux/kernel/v%(major)s.x/linux-%(version)s.tar.xz',
893 'mpc': 'https://ftp.gnu.org/gnu/mpc/mpc-%(version)s.tar.gz',
894 'mpfr': 'https://ftp.gnu.org/gnu/mpfr/mpfr-%(version)s.tar.xz',
895 'mig': 'https://ftp.gnu.org/gnu/mig/mig-%(version)s.tar.bz2',
896 'gnumach': 'https://ftp.gnu.org/gnu/gnumach/gnumach-%(version)s.tar.bz2',
897 'hurd': 'https://ftp.gnu.org/gnu/hurd/hurd-%(version)s.tar.bz2'}
898 if component
not in url_map
:
899 print('error: component %s coming from tarball' % component
)
901 version_major
= version
.split('.')[0]
902 url
= url_map
[component
] % {'version': version
, 'major': version_major
}
903 filename
= os
.path
.join(self
.srcdir
, url
.split('/')[-1])
904 response
= urllib
.request
.urlopen(url
)
905 data
= response
.read()
906 with
open(filename
, 'wb') as f
:
908 subprocess
.run(['tar', '-C', self
.srcdir
, '-x', '-f', filename
],
910 os
.rename(os
.path
.join(self
.srcdir
, '%s-%s' % (component
, version
)),
911 self
.component_srcdir(component
))
914 def load_build_state_json(self
):
915 """Load information about the state of previous builds."""
916 if os
.access(self
.build_state_json
, os
.F_OK
):
917 with
open(self
.build_state_json
, 'r') as f
:
918 self
.build_state
= json
.load(f
)
920 self
.build_state
= {}
921 for k
in ('host-libraries', 'compilers', 'glibcs'):
922 if k
not in self
.build_state
:
923 self
.build_state
[k
] = {}
924 if 'build-time' not in self
.build_state
[k
]:
925 self
.build_state
[k
]['build-time'] = ''
926 if 'build-versions' not in self
.build_state
[k
]:
927 self
.build_state
[k
]['build-versions'] = {}
928 if 'build-results' not in self
.build_state
[k
]:
929 self
.build_state
[k
]['build-results'] = {}
930 if 'result-changes' not in self
.build_state
[k
]:
931 self
.build_state
[k
]['result-changes'] = {}
932 if 'ever-passed' not in self
.build_state
[k
]:
933 self
.build_state
[k
]['ever-passed'] = []
935 def store_build_state_json(self
):
936 """Store information about the state of previous builds."""
937 self
.store_json(self
.build_state
, self
.build_state_json
)
939 def clear_last_build_state(self
, action
):
940 """Clear information about the state of part of the build."""
941 # We clear the last build time and versions when starting a
942 # new build. The results of the last build are kept around,
943 # as comparison is still meaningful if this build is aborted
944 # and a new one started.
945 self
.build_state
[action
]['build-time'] = ''
946 self
.build_state
[action
]['build-versions'] = {}
947 self
.store_build_state_json()
949 def update_build_state(self
, action
, build_time
, build_versions
):
950 """Update the build state after a build."""
951 build_time
= build_time
.replace(microsecond
=0)
952 self
.build_state
[action
]['build-time'] = str(build_time
)
953 self
.build_state
[action
]['build-versions'] = build_versions
955 for log
in self
.status_log_list
:
956 with
open(log
, 'r') as f
:
958 log_text
= log_text
.rstrip()
959 m
= re
.fullmatch('([A-Z]+): (.*)', log_text
)
961 test_name
= m
.group(2)
962 assert test_name
not in build_results
963 build_results
[test_name
] = result
964 old_build_results
= self
.build_state
[action
]['build-results']
965 self
.build_state
[action
]['build-results'] = build_results
967 all_tests
= set(old_build_results
.keys()) |
set(build_results
.keys())
969 if t
in old_build_results
:
970 old_res
= old_build_results
[t
]
972 old_res
= '(New test)'
973 if t
in build_results
:
974 new_res
= build_results
[t
]
976 new_res
= '(Test removed)'
977 if old_res
!= new_res
:
978 result_changes
[t
] = '%s -> %s' % (old_res
, new_res
)
979 self
.build_state
[action
]['result-changes'] = result_changes
980 old_ever_passed
= {t
for t
in self
.build_state
[action
]['ever-passed']
981 if t
in build_results
}
982 new_passes
= {t
for t
in build_results
if build_results
[t
] == 'PASS'}
983 self
.build_state
[action
]['ever-passed'] = sorted(old_ever_passed |
985 self
.store_build_state_json()
987 def load_bot_config_json(self
):
988 """Load bot configuration."""
989 with
open(self
.bot_config_json
, 'r') as f
:
990 self
.bot_config
= json
.load(f
)
992 def part_build_old(self
, action
, delay
):
993 """Return whether the last build for a given action was at least a
994 given number of seconds ago, or does not have a time recorded."""
995 old_time_str
= self
.build_state
[action
]['build-time']
998 old_time
= datetime
.datetime
.strptime(old_time_str
,
1000 new_time
= datetime
.datetime
.utcnow()
1001 delta
= new_time
- old_time
1002 return delta
.total_seconds() >= delay
1004 def bot_cycle(self
):
1005 """Run a single round of checkout and builds."""
1006 print('Bot cycle starting %s.' % str(datetime
.datetime
.utcnow()))
1007 self
.load_bot_config_json()
1008 actions
= ('host-libraries', 'compilers', 'glibcs')
1009 self
.bot_run_self(['--replace-sources'], 'checkout')
1010 self
.load_versions_json()
1011 if self
.get_script_text() != self
.script_text
:
1012 print('Script changed, re-execing.')
1013 # On script change, all parts of the build should be rerun.
1015 self
.clear_last_build_state(a
)
1017 check_components
= {'host-libraries': ('gmp', 'mpfr', 'mpc'),
1018 'compilers': ('binutils', 'gcc', 'glibc', 'linux',
1019 'mig', 'gnumach', 'hurd'),
1020 'glibcs': ('glibc',)}
1023 build_vers
= self
.build_state
[a
]['build-versions']
1024 must_build
[a
] = False
1025 if not self
.build_state
[a
]['build-time']:
1026 must_build
[a
] = True
1029 for c
in check_components
[a
]:
1031 old_vers
[c
] = build_vers
[c
]
1032 new_vers
[c
] = {'version': self
.versions
[c
]['version'],
1033 'revision': self
.versions
[c
]['revision']}
1034 if new_vers
== old_vers
:
1035 print('Versions for %s unchanged.' % a
)
1037 print('Versions changed or rebuild forced for %s.' % a
)
1038 if a
== 'compilers' and not self
.part_build_old(
1039 a
, self
.bot_config
['compilers-rebuild-delay']):
1040 print('Not requiring rebuild of compilers this soon.')
1042 must_build
[a
] = True
1043 if must_build
['host-libraries']:
1044 must_build
['compilers'] = True
1045 if must_build
['compilers']:
1046 must_build
['glibcs'] = True
1049 print('Must rebuild %s.' % a
)
1050 self
.clear_last_build_state(a
)
1052 print('No need to rebuild %s.' % a
)
1053 if os
.access(self
.logsdir
, os
.F_OK
):
1054 shutil
.rmtree(self
.logsdir_old
, ignore_errors
=True)
1055 shutil
.copytree(self
.logsdir
, self
.logsdir_old
)
1058 build_time
= datetime
.datetime
.utcnow()
1059 print('Rebuilding %s at %s.' % (a
, str(build_time
)))
1060 self
.bot_run_self([], a
)
1061 self
.load_build_state_json()
1062 self
.bot_build_mail(a
, build_time
)
1063 print('Bot cycle done at %s.' % str(datetime
.datetime
.utcnow()))
1065 def bot_build_mail(self
, action
, build_time
):
1066 """Send email with the results of a build."""
1067 if not ('email-from' in self
.bot_config
and
1068 'email-server' in self
.bot_config
and
1069 'email-subject' in self
.bot_config
and
1070 'email-to' in self
.bot_config
):
1071 if not self
.email_warning
:
1072 print("Email not configured, not sending.")
1073 self
.email_warning
= True
1076 build_time
= build_time
.replace(microsecond
=0)
1077 subject
= (self
.bot_config
['email-subject'] %
1079 'build-time': str(build_time
)})
1080 results
= self
.build_state
[action
]['build-results']
1081 changes
= self
.build_state
[action
]['result-changes']
1082 ever_passed
= set(self
.build_state
[action
]['ever-passed'])
1083 versions
= self
.build_state
[action
]['build-versions']
1084 new_regressions
= {k
for k
in changes
if changes
[k
] == 'PASS -> FAIL'}
1085 all_regressions
= {k
for k
in ever_passed
if results
[k
] == 'FAIL'}
1086 all_fails
= {k
for k
in results
if results
[k
] == 'FAIL'}
1088 new_reg_list
= sorted(['FAIL: %s' % k
for k
in new_regressions
])
1089 new_reg_text
= ('New regressions:\n\n%s\n\n' %
1090 '\n'.join(new_reg_list
))
1094 all_reg_list
= sorted(['FAIL: %s' % k
for k
in all_regressions
])
1095 all_reg_text
= ('All regressions:\n\n%s\n\n' %
1096 '\n'.join(all_reg_list
))
1100 all_fail_list
= sorted(['FAIL: %s' % k
for k
in all_fails
])
1101 all_fail_text
= ('All failures:\n\n%s\n\n' %
1102 '\n'.join(all_fail_list
))
1106 changes_list
= sorted(changes
.keys())
1107 changes_list
= ['%s: %s' % (changes
[k
], k
) for k
in changes_list
]
1108 changes_text
= ('All changed results:\n\n%s\n\n' %
1109 '\n'.join(changes_list
))
1112 results_text
= (new_reg_text
+ all_reg_text
+ all_fail_text
+
1114 if not results_text
:
1115 results_text
= 'Clean build with unchanged results.\n\n'
1116 versions_list
= sorted(versions
.keys())
1117 versions_list
= ['%s: %s (%s)' % (k
, versions
[k
]['version'],
1118 versions
[k
]['revision'])
1119 for k
in versions_list
]
1120 versions_text
= ('Component versions for this build:\n\n%s\n' %
1121 '\n'.join(versions_list
))
1122 body_text
= results_text
+ versions_text
1123 msg
= email
.mime
.text
.MIMEText(body_text
)
1124 msg
['Subject'] = subject
1125 msg
['From'] = self
.bot_config
['email-from']
1126 msg
['To'] = self
.bot_config
['email-to']
1127 msg
['Message-ID'] = email
.utils
.make_msgid()
1128 msg
['Date'] = email
.utils
.format_datetime(datetime
.datetime
.utcnow())
1129 with smtplib
.SMTP(self
.bot_config
['email-server']) as s
:
1132 def bot_run_self(self
, opts
, action
, check
=True):
1133 """Run a copy of this script with given options."""
1134 cmd
= [sys
.executable
, sys
.argv
[0], '--keep=none',
1135 '-j%d' % self
.parallelism
]
1137 cmd
.append('--full-gcc')
1139 cmd
.extend([self
.topdir
, action
])
1141 subprocess
.run(cmd
, check
=check
)
1144 """Run repeated rounds of checkout and builds."""
1146 self
.load_bot_config_json()
1147 if not self
.bot_config
['run']:
1148 print('Bot exiting by request.')
1150 self
.bot_run_self([], 'bot-cycle', check
=False)
1151 self
.load_bot_config_json()
1152 if not self
.bot_config
['run']:
1153 print('Bot exiting by request.')
1155 time
.sleep(self
.bot_config
['delay'])
1156 if self
.get_script_text() != self
.script_text
:
1157 print('Script changed, bot re-execing.')
1161 class Config(object):
1162 """A configuration for building a compiler and associated libraries."""
1164 def __init__(self
, ctx
, arch
, os_name
, variant
=None, gcc_cfg
=None,
1165 first_gcc_cfg
=None, glibcs
=None, extra_glibcs
=None):
1166 """Initialize a Config object."""
1170 self
.variant
= variant
1172 self
.name
= '%s-%s' % (arch
, os_name
)
1174 self
.name
= '%s-%s-%s' % (arch
, os_name
, variant
)
1175 self
.triplet
= '%s-glibc-%s' % (arch
, os_name
)
1179 self
.gcc_cfg
= gcc_cfg
1180 if first_gcc_cfg
is None:
1181 self
.first_gcc_cfg
= []
1183 self
.first_gcc_cfg
= first_gcc_cfg
1185 glibcs
= [{'variant': variant
}]
1186 if extra_glibcs
is None:
1188 glibcs
= [Glibc(self
, **g
) for g
in glibcs
]
1189 extra_glibcs
= [Glibc(self
, **g
) for g
in extra_glibcs
]
1190 self
.all_glibcs
= glibcs
+ extra_glibcs
1191 self
.compiler_glibcs
= glibcs
1192 self
.installdir
= ctx
.compiler_installdir(self
.name
)
1193 self
.bindir
= ctx
.compiler_bindir(self
.name
)
1194 self
.sysroot
= ctx
.compiler_sysroot(self
.name
)
1195 self
.builddir
= os
.path
.join(ctx
.builddir
, 'compilers', self
.name
)
1196 self
.logsdir
= os
.path
.join(ctx
.logsdir
, 'compilers', self
.name
)
1198 def component_builddir(self
, component
):
1199 """Return the directory to use for a (non-glibc) build."""
1200 return self
.ctx
.component_builddir('compilers', self
.name
, component
)
1203 """Generate commands to build this compiler."""
1204 self
.ctx
.remove_recreate_dirs(self
.installdir
, self
.builddir
,
1206 cmdlist
= CommandList('compilers-%s' % self
.name
, self
.ctx
.keep
)
1207 cmdlist
.add_command('check-host-libraries',
1209 os
.path
.join(self
.ctx
.host_libraries_installdir
,
1211 cmdlist
.use_path(self
.bindir
)
1212 self
.build_cross_tool(cmdlist
, 'binutils', 'binutils',
1214 '--disable-libdecnumber',
1215 '--disable-readline',
1217 if self
.os
.startswith('linux'):
1218 self
.install_linux_headers(cmdlist
)
1219 self
.build_gcc(cmdlist
, True)
1220 if self
.os
== 'gnu':
1221 self
.install_gnumach_headers(cmdlist
)
1222 self
.build_cross_tool(cmdlist
, 'mig', 'mig')
1223 self
.install_hurd_headers(cmdlist
)
1224 for g
in self
.compiler_glibcs
:
1225 cmdlist
.push_subdesc('glibc')
1226 cmdlist
.push_subdesc(g
.name
)
1227 g
.build_glibc(cmdlist
, True)
1228 cmdlist
.pop_subdesc()
1229 cmdlist
.pop_subdesc()
1230 self
.build_gcc(cmdlist
, False)
1231 cmdlist
.add_command('done', ['touch',
1232 os
.path
.join(self
.installdir
, 'ok')])
1233 self
.ctx
.add_makefile_cmdlist('compilers-%s' % self
.name
, cmdlist
,
1236 def build_cross_tool(self
, cmdlist
, tool_src
, tool_build
, extra_opts
=None):
1237 """Build one cross tool."""
1238 srcdir
= self
.ctx
.component_srcdir(tool_src
)
1239 builddir
= self
.component_builddir(tool_build
)
1240 cmdlist
.push_subdesc(tool_build
)
1241 cmdlist
.create_use_dir(builddir
)
1242 cfg_cmd
= [os
.path
.join(srcdir
, 'configure'),
1243 '--prefix=%s' % self
.installdir
,
1244 '--build=%s' % self
.ctx
.build_triplet
,
1245 '--host=%s' % self
.ctx
.build_triplet
,
1246 '--target=%s' % self
.triplet
,
1247 '--with-sysroot=%s' % self
.sysroot
]
1249 cfg_cmd
.extend(extra_opts
)
1250 cmdlist
.add_command('configure', cfg_cmd
)
1251 cmdlist
.add_command('build', ['make'])
1252 # Parallel "make install" for GCC has race conditions that can
1253 # cause it to fail; see
1254 # <https://gcc.gnu.org/bugzilla/show_bug.cgi?id=42980>. Such
1255 # problems are not known for binutils, but doing the
1256 # installation in parallel within a particular toolchain build
1257 # (as opposed to installation of one toolchain from
1258 # build-many-glibcs.py running in parallel to the installation
1259 # of other toolchains being built) is not known to be
1260 # significantly beneficial, so it is simplest just to disable
1261 # parallel install for cross tools here.
1262 cmdlist
.add_command('install', ['make', '-j1', 'install'])
1263 cmdlist
.cleanup_dir()
1264 cmdlist
.pop_subdesc()
1266 def install_linux_headers(self
, cmdlist
):
1267 """Install Linux kernel headers."""
1268 arch_map
= {'aarch64': 'arm64',
1279 'microblaze': 'microblaze',
1282 'powerpc': 'powerpc',
1291 if self
.arch
.startswith(k
):
1292 linux_arch
= arch_map
[k
]
1294 assert linux_arch
is not None
1295 srcdir
= self
.ctx
.component_srcdir('linux')
1296 builddir
= self
.component_builddir('linux')
1297 headers_dir
= os
.path
.join(self
.sysroot
, 'usr')
1298 cmdlist
.push_subdesc('linux')
1299 cmdlist
.create_use_dir(builddir
)
1300 cmdlist
.add_command('install-headers',
1301 ['make', '-C', srcdir
, 'O=%s' % builddir
,
1302 'ARCH=%s' % linux_arch
,
1303 'INSTALL_HDR_PATH=%s' % headers_dir
,
1305 cmdlist
.cleanup_dir()
1306 cmdlist
.pop_subdesc()
1308 def install_gnumach_headers(self
, cmdlist
):
1309 """Install GNU Mach headers."""
1310 srcdir
= self
.ctx
.component_srcdir('gnumach')
1311 builddir
= self
.component_builddir('gnumach')
1312 cmdlist
.push_subdesc('gnumach')
1313 cmdlist
.create_use_dir(builddir
)
1314 cmdlist
.add_command('configure',
1315 [os
.path
.join(srcdir
, 'configure'),
1316 '--build=%s' % self
.ctx
.build_triplet
,
1317 '--host=%s' % self
.triplet
,
1319 'CC=%s-gcc -nostdlib' % self
.triplet
])
1320 cmdlist
.add_command('install', ['make', 'DESTDIR=%s' % self
.sysroot
,
1322 cmdlist
.cleanup_dir()
1323 cmdlist
.pop_subdesc()
1325 def install_hurd_headers(self
, cmdlist
):
1326 """Install Hurd headers."""
1327 srcdir
= self
.ctx
.component_srcdir('hurd')
1328 builddir
= self
.component_builddir('hurd')
1329 cmdlist
.push_subdesc('hurd')
1330 cmdlist
.create_use_dir(builddir
)
1331 cmdlist
.add_command('configure',
1332 [os
.path
.join(srcdir
, 'configure'),
1333 '--build=%s' % self
.ctx
.build_triplet
,
1334 '--host=%s' % self
.triplet
,
1336 '--disable-profile', '--without-parted',
1337 'CC=%s-gcc -nostdlib' % self
.triplet
])
1338 cmdlist
.add_command('install', ['make', 'prefix=%s' % self
.sysroot
,
1339 'no_deps=t', 'install-headers'])
1340 cmdlist
.cleanup_dir()
1341 cmdlist
.pop_subdesc()
1343 def build_gcc(self
, cmdlist
, bootstrap
):
1345 # libssp is of little relevance with glibc's own stack
1346 # checking support. libcilkrts does not support GNU/Hurd (and
1347 # has been removed in GCC 8, so --disable-libcilkrts can be
1348 # removed once glibc no longer supports building with older
1350 cfg_opts
= list(self
.gcc_cfg
)
1351 cfg_opts
+= ['--disable-libssp', '--disable-libcilkrts']
1352 host_libs
= self
.ctx
.host_libraries_installdir
1353 cfg_opts
+= ['--with-gmp=%s' % host_libs
,
1354 '--with-mpfr=%s' % host_libs
,
1355 '--with-mpc=%s' % host_libs
]
1357 tool_build
= 'gcc-first'
1358 # Building a static-only, C-only compiler that is
1359 # sufficient to build glibc. Various libraries and
1360 # features that may require libc headers must be disabled.
1361 # When configuring with a sysroot, --with-newlib is
1362 # required to define inhibit_libc (to stop some parts of
1363 # libgcc including libc headers); --without-headers is not
1365 cfg_opts
+= ['--enable-languages=c', '--disable-shared',
1366 '--disable-threads',
1367 '--disable-libatomic',
1368 '--disable-decimal-float',
1370 '--disable-libgomp',
1373 '--disable-libquadmath',
1374 '--disable-libsanitizer',
1375 '--without-headers', '--with-newlib',
1376 '--with-glibc-version=%s' % self
.ctx
.glibc_version
1378 cfg_opts
+= self
.first_gcc_cfg
1381 # libsanitizer commonly breaks because of glibc header
1382 # changes, or on unusual targets.
1383 if not self
.ctx
.full_gcc
:
1384 cfg_opts
+= ['--disable-libsanitizer']
1385 langs
= 'all' if self
.ctx
.full_gcc
else 'c,c++'
1386 cfg_opts
+= ['--enable-languages=%s' % langs
,
1387 '--enable-shared', '--enable-threads']
1388 self
.build_cross_tool(cmdlist
, 'gcc', tool_build
, cfg_opts
)
1391 class Glibc(object):
1392 """A configuration for building glibc."""
1394 def __init__(self
, compiler
, arch
=None, os_name
=None, variant
=None,
1395 cfg
=None, ccopts
=None):
1396 """Initialize a Glibc object."""
1397 self
.ctx
= compiler
.ctx
1398 self
.compiler
= compiler
1400 self
.arch
= compiler
.arch
1404 self
.os
= compiler
.os
1407 self
.variant
= variant
1409 self
.name
= '%s-%s' % (self
.arch
, self
.os
)
1411 self
.name
= '%s-%s-%s' % (self
.arch
, self
.os
, variant
)
1412 self
.triplet
= '%s-glibc-%s' % (self
.arch
, self
.os
)
1417 self
.ccopts
= ccopts
1419 def tool_name(self
, tool
):
1420 """Return the name of a cross-compilation tool."""
1421 ctool
= '%s-%s' % (self
.compiler
.triplet
, tool
)
1422 if self
.ccopts
and (tool
== 'gcc' or tool
== 'g++'):
1423 ctool
= '%s %s' % (ctool
, self
.ccopts
)
1427 """Generate commands to build this glibc."""
1428 builddir
= self
.ctx
.component_builddir('glibcs', self
.name
, 'glibc')
1429 installdir
= self
.ctx
.glibc_installdir(self
.name
)
1430 logsdir
= os
.path
.join(self
.ctx
.logsdir
, 'glibcs', self
.name
)
1431 self
.ctx
.remove_recreate_dirs(installdir
, builddir
, logsdir
)
1432 cmdlist
= CommandList('glibcs-%s' % self
.name
, self
.ctx
.keep
)
1433 cmdlist
.add_command('check-compilers',
1435 os
.path
.join(self
.compiler
.installdir
, 'ok')])
1436 cmdlist
.use_path(self
.compiler
.bindir
)
1437 self
.build_glibc(cmdlist
, False)
1438 self
.ctx
.add_makefile_cmdlist('glibcs-%s' % self
.name
, cmdlist
,
1441 def build_glibc(self
, cmdlist
, for_compiler
):
1442 """Generate commands to build this glibc, either as part of a compiler
1443 build or with the bootstrapped compiler (and in the latter case, run
1445 srcdir
= self
.ctx
.component_srcdir('glibc')
1447 builddir
= self
.ctx
.component_builddir('compilers',
1448 self
.compiler
.name
, 'glibc',
1450 installdir
= self
.compiler
.sysroot
1452 builddir
= self
.ctx
.component_builddir('glibcs', self
.name
,
1454 installdir
= self
.ctx
.glibc_installdir(self
.name
)
1455 cmdlist
.create_use_dir(builddir
)
1456 use_usr
= self
.os
!= 'gnu'
1457 prefix
= '/usr' if use_usr
else ''
1458 cfg_cmd
= [os
.path
.join(srcdir
, 'configure'),
1459 '--prefix=%s' % prefix
,
1461 '--build=%s' % self
.ctx
.build_triplet
,
1462 '--host=%s' % self
.triplet
,
1463 'CC=%s' % self
.tool_name('gcc'),
1464 'CXX=%s' % self
.tool_name('g++'),
1465 'AR=%s' % self
.tool_name('ar'),
1466 'AS=%s' % self
.tool_name('as'),
1467 'LD=%s' % self
.tool_name('ld'),
1468 'NM=%s' % self
.tool_name('nm'),
1469 'OBJCOPY=%s' % self
.tool_name('objcopy'),
1470 'OBJDUMP=%s' % self
.tool_name('objdump'),
1471 'RANLIB=%s' % self
.tool_name('ranlib'),
1472 'READELF=%s' % self
.tool_name('readelf'),
1473 'STRIP=%s' % self
.tool_name('strip')]
1474 if self
.os
== 'gnu':
1475 cfg_cmd
+= ['MIG=%s' % self
.tool_name('mig')]
1477 cmdlist
.add_command('configure', cfg_cmd
)
1478 cmdlist
.add_command('build', ['make'])
1479 cmdlist
.add_command('install', ['make', 'install',
1480 'install_root=%s' % installdir
])
1481 # GCC uses paths such as lib/../lib64, so make sure lib
1482 # directories always exist.
1483 mkdir_cmd
= ['mkdir', '-p',
1484 os
.path
.join(installdir
, 'lib')]
1486 mkdir_cmd
+= [os
.path
.join(installdir
, 'usr', 'lib')]
1487 cmdlist
.add_command('mkdir-lib', mkdir_cmd
)
1488 if not for_compiler
:
1490 cmdlist
.add_command('strip',
1492 ('%s $(find %s/lib* -name "*.so")' %
1493 (self
.tool_name('strip'), installdir
))])
1494 cmdlist
.add_command('check', ['make', 'check'])
1495 cmdlist
.add_command('save-logs', [self
.ctx
.save_logs
],
1497 cmdlist
.cleanup_dir()
1500 class Command(object):
1501 """A command run in the build process."""
1503 def __init__(self
, desc
, num
, dir, path
, command
, always_run
=False):
1504 """Initialize a Command object."""
1508 trans
= str.maketrans({' ': '-'})
1509 self
.logbase
= '%03d-%s' % (num
, desc
.translate(trans
))
1510 self
.command
= command
1511 self
.always_run
= always_run
1514 def shell_make_quote_string(s
):
1515 """Given a string not containing a newline, quote it for use by the
1517 assert '\n' not in s
1518 if re
.fullmatch('[]+,./0-9@A-Z_a-z-]+', s
):
1520 strans
= str.maketrans({"'": "'\\''"})
1521 s
= "'%s'" % s
.translate(strans
)
1522 mtrans
= str.maketrans({'$': '$$'})
1523 return s
.translate(mtrans
)
1526 def shell_make_quote_list(l
, translate_make
):
1527 """Given a list of strings not containing newlines, quote them for use
1528 by the shell and make, returning a single string. If translate_make
1529 is true and the first string is 'make', change it to $(MAKE)."""
1530 l
= [Command
.shell_make_quote_string(s
) for s
in l
]
1531 if translate_make
and l
[0] == 'make':
1535 def shell_make_quote(self
):
1536 """Return this command quoted for the shell and make."""
1537 return self
.shell_make_quote_list(self
.command
, True)
1540 class CommandList(object):
1541 """A list of commands run in the build process."""
1543 def __init__(self
, desc
, keep
):
1544 """Initialize a CommandList object."""
1551 def desc_txt(self
, desc
):
1552 """Return the description to use for a command."""
1553 return '%s %s' % (' '.join(self
.desc
), desc
)
1555 def use_dir(self
, dir):
1556 """Set the default directory for subsequent commands."""
1559 def use_path(self
, path
):
1560 """Set a directory to be prepended to the PATH for subsequent
1564 def push_subdesc(self
, subdesc
):
1565 """Set the default subdescription for subsequent commands (e.g., the
1566 name of a component being built, within the series of commands
1568 self
.desc
.append(subdesc
)
1570 def pop_subdesc(self
):
1571 """Pop a subdescription from the list of descriptions."""
1574 def create_use_dir(self
, dir):
1575 """Remove and recreate a directory and use it for subsequent
1577 self
.add_command_dir('rm', None, ['rm', '-rf', dir])
1578 self
.add_command_dir('mkdir', None, ['mkdir', '-p', dir])
1581 def add_command_dir(self
, desc
, dir, command
, always_run
=False):
1582 """Add a command to run in a given directory."""
1583 cmd
= Command(self
.desc_txt(desc
), len(self
.cmdlist
), dir, self
.path
,
1584 command
, always_run
)
1585 self
.cmdlist
.append(cmd
)
1587 def add_command(self
, desc
, command
, always_run
=False):
1588 """Add a command to run in the default directory."""
1589 cmd
= Command(self
.desc_txt(desc
), len(self
.cmdlist
), self
.dir,
1590 self
.path
, command
, always_run
)
1591 self
.cmdlist
.append(cmd
)
1593 def cleanup_dir(self
, desc
='cleanup', dir=None):
1594 """Clean up a build directory. If no directory is specified, the
1595 default directory is cleaned up and ceases to be the default
1600 if self
.keep
!= 'all':
1601 self
.add_command_dir(desc
, None, ['rm', '-rf', dir],
1602 always_run
=(self
.keep
== 'none'))
1604 def makefile_commands(self
, wrapper
, logsdir
):
1605 """Return the sequence of commands in the form of text for a Makefile.
1606 The given wrapper script takes arguments: base of logs for
1607 previous command, or empty; base of logs for this command;
1608 description; directory; PATH addition; the command itself."""
1609 # prev_base is the base of the name for logs of the previous
1610 # command that is not always-run (that is, a build command,
1611 # whose failure should stop subsequent build commands from
1612 # being run, as opposed to a cleanup command, which is run
1613 # even if previous commands failed).
1616 for c
in self
.cmdlist
:
1617 ctxt
= c
.shell_make_quote()
1618 if prev_base
and not c
.always_run
:
1619 prev_log
= os
.path
.join(logsdir
, prev_base
)
1622 this_log
= os
.path
.join(logsdir
, c
.logbase
)
1623 if not c
.always_run
:
1624 prev_base
= c
.logbase
1633 prelims
= [wrapper
, prev_log
, this_log
, c
.desc
, dir, path
]
1634 prelim_txt
= Command
.shell_make_quote_list(prelims
, False)
1635 cmds
.append('\t@%s %s' % (prelim_txt
, ctxt
))
1636 return '\n'.join(cmds
)
1638 def status_logs(self
, logsdir
):
1639 """Return the list of log files with command status."""
1640 return [os
.path
.join(logsdir
, '%s-status.txt' % c
.logbase
)
1641 for c
in self
.cmdlist
]
1645 """Return an argument parser for this module."""
1646 parser
= argparse
.ArgumentParser(description
=__doc__
)
1647 parser
.add_argument('-j', dest
='parallelism',
1648 help='Run this number of jobs in parallel',
1649 type=int, default
=os
.cpu_count())
1650 parser
.add_argument('--keep', dest
='keep',
1651 help='Whether to keep all build directories, '
1652 'none or only those from failed builds',
1653 default
='none', choices
=('none', 'all', 'failed'))
1654 parser
.add_argument('--replace-sources', action
='store_true',
1655 help='Remove and replace source directories '
1656 'with the wrong version of a component')
1657 parser
.add_argument('--strip', action
='store_true',
1658 help='Strip installed glibc libraries')
1659 parser
.add_argument('--full-gcc', action
='store_true',
1660 help='Build GCC with all languages and libsanitizer')
1661 parser
.add_argument('topdir',
1662 help='Toplevel working directory')
1663 parser
.add_argument('action',
1665 choices
=('checkout', 'bot-cycle', 'bot',
1666 'host-libraries', 'compilers', 'glibcs'))
1667 parser
.add_argument('configs',
1668 help='Versions to check out or configurations to build',
1674 """The main entry point."""
1675 parser
= get_parser()
1676 opts
= parser
.parse_args(argv
)
1677 topdir
= os
.path
.abspath(opts
.topdir
)
1678 ctx
= Context(topdir
, opts
.parallelism
, opts
.keep
, opts
.replace_sources
,
1679 opts
.strip
, opts
.full_gcc
, opts
.action
)
1680 ctx
.run_builds(opts
.action
, opts
.configs
)
1683 if __name__
== '__main__':