build-many-glibcs: Revert -fno-isolate-erroneous-paths options for tilepro
[glibc.git] / scripts / build-many-glibcs.py
blob1447e22dceff1862d63b1aea86c6e80fc5997e63
1 #!/usr/bin/python3
2 # Build many configurations of glibc.
3 # Copyright (C) 2016 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, 'host-libraries', to build libraries required by the
26 toolchain, 'compilers', to build cross-compilers for various
27 configurations, or 'glibcs', to build glibc for various configurations
28 and run the compilation parts of the testsuite. Subsequent arguments
29 name the versions of components to check out (<component>-<version),
30 for 'checkout', or, for actions other than 'checkout', name
31 configurations for which compilers or glibc are to be built.
32 """
34 import argparse
35 import os
36 import re
37 import shutil
38 import stat
39 import subprocess
40 import sys
41 import urllib.request
44 class Context(object):
45 """The global state associated with builds in a given directory."""
47 def __init__(self, topdir, parallelism, keep, action):
48 """Initialize the context."""
49 self.topdir = topdir
50 self.parallelism = parallelism
51 self.keep = keep
52 self.srcdir = os.path.join(topdir, 'src')
53 self.installdir = os.path.join(topdir, 'install')
54 self.host_libraries_installdir = os.path.join(self.installdir,
55 'host-libraries')
56 self.builddir = os.path.join(topdir, 'build')
57 self.logsdir = os.path.join(topdir, 'logs')
58 self.makefile = os.path.join(self.builddir, 'Makefile')
59 self.wrapper = os.path.join(self.builddir, 'wrapper')
60 self.save_logs = os.path.join(self.builddir, 'save-logs')
61 if action != 'checkout':
62 self.build_triplet = self.get_build_triplet()
63 self.glibc_version = self.get_glibc_version()
64 self.configs = {}
65 self.glibc_configs = {}
66 self.makefile_pieces = ['.PHONY: all\n']
67 self.add_all_configs()
69 def get_build_triplet(self):
70 """Determine the build triplet with config.guess."""
71 config_guess = os.path.join(self.component_srcdir('gcc'),
72 'config.guess')
73 cg_out = subprocess.run([config_guess], stdout=subprocess.PIPE,
74 check=True, universal_newlines=True).stdout
75 return cg_out.rstrip()
77 def get_glibc_version(self):
78 """Determine the glibc version number (major.minor)."""
79 version_h = os.path.join(self.component_srcdir('glibc'), 'version.h')
80 with open(version_h, 'r') as f:
81 lines = f.readlines()
82 starttext = '#define VERSION "'
83 for l in lines:
84 if l.startswith(starttext):
85 l = l[len(starttext):]
86 l = l.rstrip('"\n')
87 m = re.fullmatch('([0-9]+)\.([0-9]+)[.0-9]*', l)
88 return '%s.%s' % m.group(1, 2)
89 print('error: could not determine glibc version')
90 exit(1)
92 def add_all_configs(self):
93 """Add all known glibc build configurations."""
94 # On architectures missing __builtin_trap support, these
95 # options may be needed as a workaround; see
96 # <https://gcc.gnu.org/bugzilla/show_bug.cgi?id=70216> for SH.
97 no_isolate = ('-fno-isolate-erroneous-paths-dereference'
98 ' -fno-isolate-erroneous-paths-attribute')
99 self.add_config(arch='aarch64',
100 os_name='linux-gnu')
101 self.add_config(arch='aarch64_be',
102 os_name='linux-gnu')
103 self.add_config(arch='alpha',
104 os_name='linux-gnu')
105 self.add_config(arch='arm',
106 os_name='linux-gnueabi')
107 self.add_config(arch='armeb',
108 os_name='linux-gnueabi')
109 self.add_config(arch='armeb',
110 os_name='linux-gnueabi',
111 variant='be8',
112 gcc_cfg=['--with-arch=armv7-a'])
113 self.add_config(arch='arm',
114 os_name='linux-gnueabihf')
115 self.add_config(arch='armeb',
116 os_name='linux-gnueabihf')
117 self.add_config(arch='armeb',
118 os_name='linux-gnueabihf',
119 variant='be8',
120 gcc_cfg=['--with-arch=armv7-a'])
121 self.add_config(arch='hppa',
122 os_name='linux-gnu')
123 self.add_config(arch='ia64',
124 os_name='linux-gnu',
125 first_gcc_cfg=['--with-system-libunwind'])
126 self.add_config(arch='m68k',
127 os_name='linux-gnu',
128 gcc_cfg=['--disable-multilib'])
129 self.add_config(arch='m68k',
130 os_name='linux-gnu',
131 variant='coldfire',
132 gcc_cfg=['--with-arch=cf', '--disable-multilib'])
133 self.add_config(arch='microblaze',
134 os_name='linux-gnu',
135 gcc_cfg=['--disable-multilib'])
136 self.add_config(arch='microblazeel',
137 os_name='linux-gnu',
138 gcc_cfg=['--disable-multilib'])
139 self.add_config(arch='mips64',
140 os_name='linux-gnu',
141 gcc_cfg=['--with-mips-plt'],
142 glibcs=[{'variant': 'n32'},
143 {'arch': 'mips',
144 'ccopts': '-mabi=32'},
145 {'variant': 'n64',
146 'ccopts': '-mabi=64'}])
147 self.add_config(arch='mips64',
148 os_name='linux-gnu',
149 variant='soft',
150 gcc_cfg=['--with-mips-plt', '--with-float=soft'],
151 glibcs=[{'variant': 'n32-soft',
152 'cfg': ['--without-fp']},
153 {'variant': 'soft',
154 'arch': 'mips',
155 'ccopts': '-mabi=32',
156 'cfg': ['--without-fp']},
157 {'variant': 'n64-soft',
158 'ccopts': '-mabi=64',
159 'cfg': ['--without-fp']}])
160 self.add_config(arch='mips64',
161 os_name='linux-gnu',
162 variant='nan2008',
163 gcc_cfg=['--with-mips-plt', '--with-nan=2008',
164 '--with-arch-64=mips64r2',
165 '--with-arch-32=mips32r2'],
166 glibcs=[{'variant': 'n32-nan2008'},
167 {'variant': 'nan2008',
168 'arch': 'mips',
169 'ccopts': '-mabi=32'},
170 {'variant': 'n64-nan2008',
171 'ccopts': '-mabi=64'}])
172 self.add_config(arch='mips64',
173 os_name='linux-gnu',
174 variant='nan2008-soft',
175 gcc_cfg=['--with-mips-plt', '--with-nan=2008',
176 '--with-arch-64=mips64r2',
177 '--with-arch-32=mips32r2',
178 '--with-float=soft'],
179 glibcs=[{'variant': 'n32-nan2008-soft',
180 'cfg': ['--without-fp']},
181 {'variant': 'nan2008-soft',
182 'arch': 'mips',
183 'ccopts': '-mabi=32',
184 'cfg': ['--without-fp']},
185 {'variant': 'n64-nan2008-soft',
186 'ccopts': '-mabi=64',
187 'cfg': ['--without-fp']}])
188 self.add_config(arch='mips64el',
189 os_name='linux-gnu',
190 gcc_cfg=['--with-mips-plt'],
191 glibcs=[{'variant': 'n32'},
192 {'arch': 'mipsel',
193 'ccopts': '-mabi=32'},
194 {'variant': 'n64',
195 'ccopts': '-mabi=64'}])
196 self.add_config(arch='mips64el',
197 os_name='linux-gnu',
198 variant='soft',
199 gcc_cfg=['--with-mips-plt', '--with-float=soft'],
200 glibcs=[{'variant': 'n32-soft',
201 'cfg': ['--without-fp']},
202 {'variant': 'soft',
203 'arch': 'mipsel',
204 'ccopts': '-mabi=32',
205 'cfg': ['--without-fp']},
206 {'variant': 'n64-soft',
207 'ccopts': '-mabi=64',
208 'cfg': ['--without-fp']}])
209 self.add_config(arch='mips64el',
210 os_name='linux-gnu',
211 variant='nan2008',
212 gcc_cfg=['--with-mips-plt', '--with-nan=2008',
213 '--with-arch-64=mips64r2',
214 '--with-arch-32=mips32r2'],
215 glibcs=[{'variant': 'n32-nan2008'},
216 {'variant': 'nan2008',
217 'arch': 'mipsel',
218 'ccopts': '-mabi=32'},
219 {'variant': 'n64-nan2008',
220 'ccopts': '-mabi=64'}])
221 self.add_config(arch='mips64el',
222 os_name='linux-gnu',
223 variant='nan2008-soft',
224 gcc_cfg=['--with-mips-plt', '--with-nan=2008',
225 '--with-arch-64=mips64r2',
226 '--with-arch-32=mips32r2',
227 '--with-float=soft'],
228 glibcs=[{'variant': 'n32-nan2008-soft',
229 'cfg': ['--without-fp']},
230 {'variant': 'nan2008-soft',
231 'arch': 'mipsel',
232 'ccopts': '-mabi=32',
233 'cfg': ['--without-fp']},
234 {'variant': 'n64-nan2008-soft',
235 'ccopts': '-mabi=64',
236 'cfg': ['--without-fp']}])
237 self.add_config(arch='nios2',
238 os_name='linux-gnu')
239 self.add_config(arch='powerpc',
240 os_name='linux-gnu',
241 gcc_cfg=['--disable-multilib', '--enable-secureplt'])
242 self.add_config(arch='powerpc',
243 os_name='linux-gnu',
244 variant='soft',
245 gcc_cfg=['--disable-multilib', '--with-float=soft',
246 '--enable-secureplt'],
247 glibcs=[{'variant': 'soft', 'cfg': ['--without-fp']}])
248 self.add_config(arch='powerpc64',
249 os_name='linux-gnu',
250 gcc_cfg=['--disable-multilib', '--enable-secureplt'])
251 self.add_config(arch='powerpc64le',
252 os_name='linux-gnu',
253 gcc_cfg=['--disable-multilib', '--enable-secureplt'])
254 self.add_config(arch='powerpc',
255 os_name='linux-gnuspe',
256 gcc_cfg=['--disable-multilib', '--enable-secureplt',
257 '--enable-e500-double'],
258 glibcs=[{'cfg': ['--without-fp']}])
259 self.add_config(arch='powerpc',
260 os_name='linux-gnuspe',
261 variant='e500v1',
262 gcc_cfg=['--disable-multilib', '--enable-secureplt'],
263 glibcs=[{'variant': 'e500v1', 'cfg': ['--without-fp']}])
264 self.add_config(arch='s390x',
265 os_name='linux-gnu',
266 glibcs=[{},
267 {'arch': 's390', 'ccopts': '-m31'}])
268 self.add_config(arch='sh3',
269 os_name='linux-gnu',
270 glibcs=[{'ccopts': no_isolate}])
271 self.add_config(arch='sh3eb',
272 os_name='linux-gnu',
273 glibcs=[{'ccopts': no_isolate}])
274 self.add_config(arch='sh4',
275 os_name='linux-gnu',
276 glibcs=[{'ccopts': no_isolate}])
277 self.add_config(arch='sh4eb',
278 os_name='linux-gnu',
279 glibcs=[{'ccopts': no_isolate}])
280 self.add_config(arch='sh4',
281 os_name='linux-gnu',
282 variant='soft',
283 gcc_cfg=['--without-fp'],
284 glibcs=[{'variant': 'soft',
285 'cfg': ['--without-fp'],
286 'ccopts': no_isolate}])
287 self.add_config(arch='sh4eb',
288 os_name='linux-gnu',
289 variant='soft',
290 gcc_cfg=['--without-fp'],
291 glibcs=[{'variant': 'soft',
292 'cfg': ['--without-fp'],
293 'ccopts': no_isolate}])
294 self.add_config(arch='sparc64',
295 os_name='linux-gnu',
296 glibcs=[{},
297 {'arch': 'sparcv9',
298 'ccopts': '-m32 -mlong-double-128'}])
299 self.add_config(arch='tilegx',
300 os_name='linux-gnu',
301 glibcs=[{},
302 {'variant': '32', 'ccopts': '-m32'}])
303 self.add_config(arch='tilegxbe',
304 os_name='linux-gnu',
305 glibcs=[{},
306 {'variant': '32', 'ccopts': '-m32'}])
307 self.add_config(arch='tilepro',
308 os_name='linux-gnu')
309 self.add_config(arch='x86_64',
310 os_name='linux-gnu',
311 gcc_cfg=['--with-multilib-list=m64,m32,mx32'],
312 glibcs=[{},
313 {'variant': 'x32', 'ccopts': '-mx32'},
314 {'arch': 'i686', 'ccopts': '-m32 -march=i686'}],
315 extra_glibcs=[{'variant': 'disable-multi-arch',
316 'cfg': ['--disable-multi-arch']},
317 {'variant': 'disable-multi-arch',
318 'arch': 'i686',
319 'ccopts': '-m32 -march=i686',
320 'cfg': ['--disable-multi-arch']},
321 {'arch': 'i486',
322 'ccopts': '-m32 -march=i486'},
323 {'arch': 'i586',
324 'ccopts': '-m32 -march=i586'}])
326 def add_config(self, **args):
327 """Add an individual build configuration."""
328 cfg = Config(self, **args)
329 if cfg.name in self.configs:
330 print('error: duplicate config %s' % cfg.name)
331 exit(1)
332 self.configs[cfg.name] = cfg
333 for c in cfg.all_glibcs:
334 if c.name in self.glibc_configs:
335 print('error: duplicate glibc config %s' % c.name)
336 exit(1)
337 self.glibc_configs[c.name] = c
339 def component_srcdir(self, component):
340 """Return the source directory for a given component, e.g. gcc."""
341 return os.path.join(self.srcdir, component)
343 def component_builddir(self, action, config, component, subconfig=None):
344 """Return the directory to use for a build."""
345 if config is None:
346 # Host libraries.
347 assert subconfig is None
348 return os.path.join(self.builddir, action, component)
349 if subconfig is None:
350 return os.path.join(self.builddir, action, config, component)
351 else:
352 # glibc build as part of compiler build.
353 return os.path.join(self.builddir, action, config, component,
354 subconfig)
356 def compiler_installdir(self, config):
357 """Return the directory in which to install a compiler."""
358 return os.path.join(self.installdir, 'compilers', config)
360 def compiler_bindir(self, config):
361 """Return the directory in which to find compiler binaries."""
362 return os.path.join(self.compiler_installdir(config), 'bin')
364 def compiler_sysroot(self, config):
365 """Return the sysroot directory for a compiler."""
366 return os.path.join(self.compiler_installdir(config), 'sysroot')
368 def glibc_installdir(self, config):
369 """Return the directory in which to install glibc."""
370 return os.path.join(self.installdir, 'glibcs', config)
372 def run_builds(self, action, configs):
373 """Run the requested builds."""
374 if action == 'checkout':
375 self.checkout(configs)
376 return
377 elif action == 'host-libraries':
378 if configs:
379 print('error: configurations specified for host-libraries')
380 exit(1)
381 self.build_host_libraries()
382 elif action == 'compilers':
383 self.build_compilers(configs)
384 else:
385 self.build_glibcs(configs)
386 self.write_files()
387 self.do_build()
389 @staticmethod
390 def remove_dirs(*args):
391 """Remove directories and their contents if they exist."""
392 for dir in args:
393 shutil.rmtree(dir, ignore_errors=True)
395 @staticmethod
396 def remove_recreate_dirs(*args):
397 """Remove directories if they exist, and create them as empty."""
398 Context.remove_dirs(*args)
399 for dir in args:
400 os.makedirs(dir, exist_ok=True)
402 def add_makefile_cmdlist(self, target, cmdlist, logsdir):
403 """Add makefile text for a list of commands."""
404 commands = cmdlist.makefile_commands(self.wrapper, logsdir)
405 self.makefile_pieces.append('all: %s\n.PHONY: %s\n%s:\n%s\n' %
406 (target, target, target, commands))
408 def write_files(self):
409 """Write out the Makefile and wrapper script."""
410 mftext = ''.join(self.makefile_pieces)
411 with open(self.makefile, 'w') as f:
412 f.write(mftext)
413 wrapper_text = (
414 '#!/bin/sh\n'
415 'prev_base=$1\n'
416 'this_base=$2\n'
417 'desc=$3\n'
418 'dir=$4\n'
419 'path=$5\n'
420 'shift 5\n'
421 'prev_status=$prev_base-status.txt\n'
422 'this_status=$this_base-status.txt\n'
423 'this_log=$this_base-log.txt\n'
424 'date > "$this_log"\n'
425 'echo >> "$this_log"\n'
426 'echo "Description: $desc" >> "$this_log"\n'
427 'printf "%s" "Command:" >> "$this_log"\n'
428 'for word in "$@"; do\n'
429 ' if expr "$word" : "[]+,./0-9@A-Z_a-z-]\\\\{1,\\\\}\\$" > /dev/null; then\n'
430 ' printf " %s" "$word"\n'
431 ' else\n'
432 ' printf " \'"\n'
433 ' printf "%s" "$word" | sed -e "s/\'/\'\\\\\\\\\'\'/"\n'
434 ' printf "\'"\n'
435 ' fi\n'
436 'done >> "$this_log"\n'
437 'echo >> "$this_log"\n'
438 'echo "Directory: $dir" >> "$this_log"\n'
439 'echo "Path addition: $path" >> "$this_log"\n'
440 'echo >> "$this_log"\n'
441 'record_status ()\n'
442 '{\n'
443 ' echo >> "$this_log"\n'
444 ' echo "$1: $desc" > "$this_status"\n'
445 ' echo "$1: $desc" >> "$this_log"\n'
446 ' echo >> "$this_log"\n'
447 ' date >> "$this_log"\n'
448 ' echo "$1: $desc"\n'
449 ' exit 0\n'
450 '}\n'
451 'check_error ()\n'
452 '{\n'
453 ' if [ "$1" != "0" ]; then\n'
454 ' record_status FAIL\n'
455 ' fi\n'
456 '}\n'
457 'if [ "$prev_base" ] && ! grep -q "^PASS" "$prev_status"; then\n'
458 ' record_status UNRESOLVED\n'
459 'fi\n'
460 'if [ "$dir" ]; then\n'
461 ' cd "$dir"\n'
462 ' check_error "$?"\n'
463 'fi\n'
464 'if [ "$path" ]; then\n'
465 ' PATH=$path:$PATH\n'
466 'fi\n'
467 '"$@" < /dev/null >> "$this_log" 2>&1\n'
468 'check_error "$?"\n'
469 'record_status PASS\n')
470 with open(self.wrapper, 'w') as f:
471 f.write(wrapper_text)
472 # Mode 0o755.
473 mode_exec = (stat.S_IRWXU|stat.S_IRGRP|stat.S_IXGRP|
474 stat.S_IROTH|stat.S_IXOTH)
475 os.chmod(self.wrapper, mode_exec)
476 save_logs_text = (
477 '#!/bin/sh\n'
478 'if ! [ -f tests.sum ]; then\n'
479 ' echo "No test summary available."\n'
480 ' exit 0\n'
481 'fi\n'
482 'save_file ()\n'
483 '{\n'
484 ' echo "Contents of $1:"\n'
485 ' echo\n'
486 ' cat "$1"\n'
487 ' echo\n'
488 ' echo "End of contents of $1."\n'
489 ' echo\n'
490 '}\n'
491 'save_file tests.sum\n'
492 'non_pass_tests=$(grep -v "^PASS: " tests.sum | sed -e "s/^PASS: //")\n'
493 'for t in $non_pass_tests; do\n'
494 ' if [ -f "$t.out" ]; then\n'
495 ' save_file "$t.out"\n'
496 ' fi\n'
497 'done\n')
498 with open(self.save_logs, 'w') as f:
499 f.write(save_logs_text)
500 os.chmod(self.save_logs, mode_exec)
502 def do_build(self):
503 """Do the actual build."""
504 cmd = ['make', '-j%d' % self.parallelism]
505 subprocess.run(cmd, cwd=self.builddir, check=True)
507 def build_host_libraries(self):
508 """Build the host libraries."""
509 installdir = self.host_libraries_installdir
510 builddir = os.path.join(self.builddir, 'host-libraries')
511 logsdir = os.path.join(self.logsdir, 'host-libraries')
512 self.remove_recreate_dirs(installdir, builddir, logsdir)
513 cmdlist = CommandList('host-libraries', self.keep)
514 self.build_host_library(cmdlist, 'gmp')
515 self.build_host_library(cmdlist, 'mpfr',
516 ['--with-gmp=%s' % installdir])
517 self.build_host_library(cmdlist, 'mpc',
518 ['--with-gmp=%s' % installdir,
519 '--with-mpfr=%s' % installdir])
520 cmdlist.add_command('done', ['touch', os.path.join(installdir, 'ok')])
521 self.add_makefile_cmdlist('host-libraries', cmdlist, logsdir)
523 def build_host_library(self, cmdlist, lib, extra_opts=None):
524 """Build one host library."""
525 srcdir = self.component_srcdir(lib)
526 builddir = self.component_builddir('host-libraries', None, lib)
527 installdir = self.host_libraries_installdir
528 cmdlist.push_subdesc(lib)
529 cmdlist.create_use_dir(builddir)
530 cfg_cmd = [os.path.join(srcdir, 'configure'),
531 '--prefix=%s' % installdir,
532 '--disable-shared']
533 if extra_opts:
534 cfg_cmd.extend (extra_opts)
535 cmdlist.add_command('configure', cfg_cmd)
536 cmdlist.add_command('build', ['make'])
537 cmdlist.add_command('check', ['make', 'check'])
538 cmdlist.add_command('install', ['make', 'install'])
539 cmdlist.cleanup_dir()
540 cmdlist.pop_subdesc()
542 def build_compilers(self, configs):
543 """Build the compilers."""
544 if not configs:
545 self.remove_dirs(os.path.join(self.builddir, 'compilers'))
546 self.remove_dirs(os.path.join(self.installdir, 'compilers'))
547 self.remove_dirs(os.path.join(self.logsdir, 'compilers'))
548 configs = sorted(self.configs.keys())
549 for c in configs:
550 self.configs[c].build()
552 def build_glibcs(self, configs):
553 """Build the glibcs."""
554 if not configs:
555 self.remove_dirs(os.path.join(self.builddir, 'glibcs'))
556 self.remove_dirs(os.path.join(self.installdir, 'glibcs'))
557 self.remove_dirs(os.path.join(self.logsdir, 'glibcs'))
558 configs = sorted(self.glibc_configs.keys())
559 for c in configs:
560 self.glibc_configs[c].build()
562 def checkout(self, versions):
563 """Check out the desired component versions."""
564 default_versions = {'binutils': 'vcs-2.27',
565 'gcc': 'vcs-6',
566 'glibc': 'vcs-mainline',
567 'gmp': '6.1.1',
568 'linux': '4.8.6',
569 'mpc': '1.0.3',
570 'mpfr': '3.1.5'}
571 use_versions = {}
572 for v in versions:
573 found_v = False
574 for k in default_versions.keys():
575 kx = k + '-'
576 if v.startswith(kx):
577 vx = v[len(kx):]
578 if k in use_versions:
579 print('error: multiple versions for %s' % k)
580 exit(1)
581 use_versions[k] = vx
582 found_v = True
583 break
584 if not found_v:
585 print('error: unknown component in %s' % v)
586 exit(1)
587 for k in default_versions.keys():
588 if k not in use_versions:
589 use_versions[k] = default_versions[k]
590 os.makedirs(self.srcdir, exist_ok=True)
591 for k in sorted(default_versions.keys()):
592 update = os.access(self.component_srcdir(k), os.F_OK)
593 v = use_versions[k]
594 if v.startswith('vcs-'):
595 self.checkout_vcs(k, v[4:], update)
596 else:
597 self.checkout_tar(k, v, update)
599 def checkout_vcs(self, component, version, update):
600 """Check out the given version of the given component from version
601 control."""
602 if component == 'binutils':
603 git_url = 'git://sourceware.org/git/binutils-gdb.git'
604 if version == 'mainline':
605 git_branch = 'master'
606 else:
607 trans = str.maketrans({'.': '_'})
608 git_branch = 'binutils-%s-branch' % version.translate(trans)
609 self.git_checkout(component, git_url, git_branch, update)
610 elif component == 'gcc':
611 if version == 'mainline':
612 branch = 'trunk'
613 else:
614 trans = str.maketrans({'.': '_'})
615 branch = 'branches/gcc-%s-branch' % version.translate(trans)
616 svn_url = 'svn://gcc.gnu.org/svn/gcc/%s' % branch
617 self.gcc_checkout(svn_url, update)
618 elif component == 'glibc':
619 git_url = 'git://sourceware.org/git/glibc.git'
620 if version == 'mainline':
621 git_branch = 'master'
622 else:
623 git_branch = 'release/%s/master' % version
624 self.git_checkout(component, git_url, git_branch, update)
625 self.fix_glibc_timestamps()
626 else:
627 print('error: component %s coming from VCS' % component)
628 exit(1)
630 def git_checkout(self, component, git_url, git_branch, update):
631 """Check out a component from git."""
632 if update:
633 subprocess.run(['git', 'remote', 'prune', 'origin'],
634 cwd=self.component_srcdir(component), check=True)
635 subprocess.run(['git', 'pull', '-q'],
636 cwd=self.component_srcdir(component), check=True)
637 else:
638 subprocess.run(['git', 'clone', '-q', '-b', git_branch, git_url,
639 self.component_srcdir(component)], check=True)
641 def fix_glibc_timestamps(self):
642 """Fix timestamps in a glibc checkout."""
643 # Ensure that builds do not try to regenerate generated files
644 # in the source tree.
645 srcdir = self.component_srcdir('glibc')
646 for dirpath, dirnames, filenames in os.walk(srcdir):
647 for f in filenames:
648 if (f == 'configure' or
649 f == 'preconfigure' or
650 f.endswith('-kw.h')):
651 to_touch = os.path.join(dirpath, f)
652 subprocess.run(['touch', to_touch], check=True)
654 def gcc_checkout(self, svn_url, update):
655 """Check out GCC from SVN."""
656 if not update:
657 subprocess.run(['svn', 'co', '-q', svn_url,
658 self.component_srcdir('gcc')], check=True)
659 subprocess.run(['contrib/gcc_update', '--silent'],
660 cwd=self.component_srcdir('gcc'), check=True)
662 def checkout_tar(self, component, version, update):
663 """Check out the given version of the given component from a
664 tarball."""
665 if update:
666 return
667 url_map = {'binutils': 'https://ftp.gnu.org/gnu/binutils/binutils-%(version)s.tar.bz2',
668 'gcc': 'https://ftp.gnu.org/gnu/gcc/gcc-%(version)s/gcc-%(version)s.tar.bz2',
669 'gmp': 'https://ftp.gnu.org/gnu/gmp/gmp-%(version)s.tar.xz',
670 'linux': 'https://www.kernel.org/pub/linux/kernel/v4.x/linux-%(version)s.tar.xz',
671 'mpc': 'https://ftp.gnu.org/gnu/mpc/mpc-%(version)s.tar.gz',
672 'mpfr': 'https://ftp.gnu.org/gnu/mpfr/mpfr-%(version)s.tar.xz'}
673 if component not in url_map:
674 print('error: component %s coming from tarball' % component)
675 exit(1)
676 url = url_map[component] % {'version': version}
677 filename = os.path.join(self.srcdir, url.split('/')[-1])
678 response = urllib.request.urlopen(url)
679 data = response.read()
680 with open(filename, 'wb') as f:
681 f.write(data)
682 subprocess.run(['tar', '-C', self.srcdir, '-x', '-f', filename],
683 check=True)
684 os.rename(os.path.join(self.srcdir, '%s-%s' % (component, version)),
685 self.component_srcdir(component))
686 os.remove(filename)
689 class Config(object):
690 """A configuration for building a compiler and associated libraries."""
692 def __init__(self, ctx, arch, os_name, variant=None, gcc_cfg=None,
693 first_gcc_cfg=None, glibcs=None, extra_glibcs=None):
694 """Initialize a Config object."""
695 self.ctx = ctx
696 self.arch = arch
697 self.os = os_name
698 self.variant = variant
699 if variant is None:
700 self.name = '%s-%s' % (arch, os_name)
701 else:
702 self.name = '%s-%s-%s' % (arch, os_name, variant)
703 self.triplet = '%s-glibc-%s' % (arch, os_name)
704 if gcc_cfg is None:
705 self.gcc_cfg = []
706 else:
707 self.gcc_cfg = gcc_cfg
708 if first_gcc_cfg is None:
709 self.first_gcc_cfg = []
710 else:
711 self.first_gcc_cfg = first_gcc_cfg
712 if glibcs is None:
713 glibcs = [{'variant': variant}]
714 if extra_glibcs is None:
715 extra_glibcs = []
716 glibcs = [Glibc(self, **g) for g in glibcs]
717 extra_glibcs = [Glibc(self, **g) for g in extra_glibcs]
718 self.all_glibcs = glibcs + extra_glibcs
719 self.compiler_glibcs = glibcs
720 self.installdir = ctx.compiler_installdir(self.name)
721 self.bindir = ctx.compiler_bindir(self.name)
722 self.sysroot = ctx.compiler_sysroot(self.name)
723 self.builddir = os.path.join(ctx.builddir, 'compilers', self.name)
724 self.logsdir = os.path.join(ctx.logsdir, 'compilers', self.name)
726 def component_builddir(self, component):
727 """Return the directory to use for a (non-glibc) build."""
728 return self.ctx.component_builddir('compilers', self.name, component)
730 def build(self):
731 """Generate commands to build this compiler."""
732 self.ctx.remove_recreate_dirs(self.installdir, self.builddir,
733 self.logsdir)
734 cmdlist = CommandList('compilers-%s' % self.name, self.ctx.keep)
735 cmdlist.add_command('check-host-libraries',
736 ['test', '-f',
737 os.path.join(self.ctx.host_libraries_installdir,
738 'ok')])
739 cmdlist.use_path(self.bindir)
740 self.build_cross_tool(cmdlist, 'binutils', 'binutils',
741 ['--disable-gdb',
742 '--disable-libdecnumber',
743 '--disable-readline',
744 '--disable-sim'])
745 if self.os.startswith('linux'):
746 self.install_linux_headers(cmdlist)
747 self.build_gcc(cmdlist, True)
748 for g in self.compiler_glibcs:
749 cmdlist.push_subdesc('glibc')
750 cmdlist.push_subdesc(g.name)
751 g.build_glibc(cmdlist, True)
752 cmdlist.pop_subdesc()
753 cmdlist.pop_subdesc()
754 self.build_gcc(cmdlist, False)
755 cmdlist.add_command('done', ['touch',
756 os.path.join(self.installdir, 'ok')])
757 self.ctx.add_makefile_cmdlist('compilers-%s' % self.name, cmdlist,
758 self.logsdir)
760 def build_cross_tool(self, cmdlist, tool_src, tool_build, extra_opts=None):
761 """Build one cross tool."""
762 srcdir = self.ctx.component_srcdir(tool_src)
763 builddir = self.component_builddir(tool_build)
764 cmdlist.push_subdesc(tool_build)
765 cmdlist.create_use_dir(builddir)
766 cfg_cmd = [os.path.join(srcdir, 'configure'),
767 '--prefix=%s' % self.installdir,
768 '--build=%s' % self.ctx.build_triplet,
769 '--host=%s' % self.ctx.build_triplet,
770 '--target=%s' % self.triplet,
771 '--with-sysroot=%s' % self.sysroot]
772 if extra_opts:
773 cfg_cmd.extend(extra_opts)
774 cmdlist.add_command('configure', cfg_cmd)
775 cmdlist.add_command('build', ['make'])
776 cmdlist.add_command('install', ['make', 'install'])
777 cmdlist.cleanup_dir()
778 cmdlist.pop_subdesc()
780 def install_linux_headers(self, cmdlist):
781 """Install Linux kernel headers."""
782 arch_map = {'aarch64': 'arm64',
783 'alpha': 'alpha',
784 'arm': 'arm',
785 'hppa': 'parisc',
786 'i486': 'x86',
787 'i586': 'x86',
788 'i686': 'x86',
789 'i786': 'x86',
790 'ia64': 'ia64',
791 'm68k': 'm68k',
792 'microblaze': 'microblaze',
793 'mips': 'mips',
794 'nios2': 'nios2',
795 'powerpc': 'powerpc',
796 's390': 's390',
797 'sh': 'sh',
798 'sparc': 'sparc',
799 'tile': 'tile',
800 'x86_64': 'x86'}
801 linux_arch = None
802 for k in arch_map:
803 if self.arch.startswith(k):
804 linux_arch = arch_map[k]
805 break
806 assert linux_arch is not None
807 srcdir = self.ctx.component_srcdir('linux')
808 builddir = self.component_builddir('linux')
809 headers_dir = os.path.join(self.sysroot, 'usr')
810 cmdlist.push_subdesc('linux')
811 cmdlist.create_use_dir(builddir)
812 cmdlist.add_command('install-headers',
813 ['make', '-C', srcdir, 'O=%s' % builddir,
814 'ARCH=%s' % linux_arch,
815 'INSTALL_HDR_PATH=%s' % headers_dir,
816 'headers_install'])
817 cmdlist.cleanup_dir()
818 cmdlist.pop_subdesc()
820 def build_gcc(self, cmdlist, bootstrap):
821 """Build GCC."""
822 # libsanitizer commonly breaks because of glibc header
823 # changes, or on unusual targets. libssp is of little
824 # relevance with glibc's own stack checking support.
825 cfg_opts = list(self.gcc_cfg)
826 cfg_opts += ['--disable-libsanitizer', '--disable-libssp']
827 host_libs = self.ctx.host_libraries_installdir
828 cfg_opts += ['--with-gmp=%s' % host_libs,
829 '--with-mpfr=%s' % host_libs,
830 '--with-mpc=%s' % host_libs]
831 if bootstrap:
832 tool_build = 'gcc-first'
833 # Building a static-only, C-only compiler that is
834 # sufficient to build glibc. Various libraries and
835 # features that may require libc headers must be disabled.
836 # When configuring with a sysroot, --with-newlib is
837 # required to define inhibit_libc (to stop some parts of
838 # libgcc including libc headers); --without-headers is not
839 # sufficient.
840 cfg_opts += ['--enable-languages=c', '--disable-shared',
841 '--disable-threads',
842 '--disable-libatomic',
843 '--disable-decimal-float',
844 '--disable-libffi',
845 '--disable-libgomp',
846 '--disable-libitm',
847 '--disable-libmpx',
848 '--disable-libquadmath',
849 '--without-headers', '--with-newlib',
850 '--with-glibc-version=%s' % self.ctx.glibc_version
852 cfg_opts += self.first_gcc_cfg
853 else:
854 tool_build = 'gcc'
855 cfg_opts += ['--enable-languages=c,c++', '--enable-shared',
856 '--enable-threads']
857 self.build_cross_tool(cmdlist, 'gcc', tool_build, cfg_opts)
860 class Glibc(object):
861 """A configuration for building glibc."""
863 def __init__(self, compiler, arch=None, os_name=None, variant=None,
864 cfg=None, ccopts=None):
865 """Initialize a Glibc object."""
866 self.ctx = compiler.ctx
867 self.compiler = compiler
868 if arch is None:
869 self.arch = compiler.arch
870 else:
871 self.arch = arch
872 if os_name is None:
873 self.os = compiler.os
874 else:
875 self.os = os_name
876 self.variant = variant
877 if variant is None:
878 self.name = '%s-%s' % (self.arch, self.os)
879 else:
880 self.name = '%s-%s-%s' % (self.arch, self.os, variant)
881 self.triplet = '%s-glibc-%s' % (self.arch, self.os)
882 if cfg is None:
883 self.cfg = []
884 else:
885 self.cfg = cfg
886 self.ccopts = ccopts
888 def tool_name(self, tool):
889 """Return the name of a cross-compilation tool."""
890 ctool = '%s-%s' % (self.compiler.triplet, tool)
891 if self.ccopts and (tool == 'gcc' or tool == 'g++'):
892 ctool = '%s %s' % (ctool, self.ccopts)
893 return ctool
895 def build(self):
896 """Generate commands to build this glibc."""
897 builddir = self.ctx.component_builddir('glibcs', self.name, 'glibc')
898 installdir = self.ctx.glibc_installdir(self.name)
899 logsdir = os.path.join(self.ctx.logsdir, 'glibcs', self.name)
900 self.ctx.remove_recreate_dirs(installdir, builddir, logsdir)
901 cmdlist = CommandList('glibcs-%s' % self.name, self.ctx.keep)
902 cmdlist.add_command('check-compilers',
903 ['test', '-f',
904 os.path.join(self.compiler.installdir, 'ok')])
905 cmdlist.use_path(self.compiler.bindir)
906 self.build_glibc(cmdlist, False)
907 self.ctx.add_makefile_cmdlist('glibcs-%s' % self.name, cmdlist,
908 logsdir)
910 def build_glibc(self, cmdlist, for_compiler):
911 """Generate commands to build this glibc, either as part of a compiler
912 build or with the bootstrapped compiler (and in the latter case, run
913 tests as well)."""
914 srcdir = self.ctx.component_srcdir('glibc')
915 if for_compiler:
916 builddir = self.ctx.component_builddir('compilers',
917 self.compiler.name, 'glibc',
918 self.name)
919 installdir = self.compiler.sysroot
920 srcdir_copy = self.ctx.component_builddir('compilers',
921 self.compiler.name,
922 'glibc-src',
923 self.name)
924 else:
925 builddir = self.ctx.component_builddir('glibcs', self.name,
926 'glibc')
927 installdir = self.ctx.glibc_installdir(self.name)
928 srcdir_copy = self.ctx.component_builddir('glibcs', self.name,
929 'glibc-src')
930 cmdlist.create_use_dir(builddir)
931 # glibc builds write into the source directory, and even if
932 # not intentionally there is a risk of bugs that involve
933 # writing into the working directory. To avoid possible
934 # concurrency issues, copy the source directory.
935 cmdlist.create_copy_dir(srcdir, srcdir_copy)
936 cfg_cmd = [os.path.join(srcdir_copy, 'configure'),
937 '--prefix=/usr',
938 '--enable-add-ons',
939 '--build=%s' % self.ctx.build_triplet,
940 '--host=%s' % self.triplet,
941 'CC=%s' % self.tool_name('gcc'),
942 'CXX=%s' % self.tool_name('g++'),
943 'AR=%s' % self.tool_name('ar'),
944 'AS=%s' % self.tool_name('as'),
945 'LD=%s' % self.tool_name('ld'),
946 'NM=%s' % self.tool_name('nm'),
947 'OBJCOPY=%s' % self.tool_name('objcopy'),
948 'OBJDUMP=%s' % self.tool_name('objdump'),
949 'RANLIB=%s' % self.tool_name('ranlib'),
950 'READELF=%s' % self.tool_name('readelf'),
951 'STRIP=%s' % self.tool_name('strip')]
952 cfg_cmd += self.cfg
953 cmdlist.add_command('configure', cfg_cmd)
954 cmdlist.add_command('build', ['make'])
955 cmdlist.add_command('install', ['make', 'install',
956 'install_root=%s' % installdir])
957 # GCC uses paths such as lib/../lib64, so make sure lib
958 # directories always exist.
959 cmdlist.add_command('mkdir-lib', ['mkdir', '-p',
960 os.path.join(installdir, 'lib'),
961 os.path.join(installdir,
962 'usr', 'lib')])
963 if not for_compiler:
964 cmdlist.add_command('check', ['make', 'check'])
965 cmdlist.add_command('save-logs', [self.ctx.save_logs],
966 always_run=True)
967 cmdlist.cleanup_dir('cleanup-src', srcdir_copy)
968 cmdlist.cleanup_dir()
971 class Command(object):
972 """A command run in the build process."""
974 def __init__(self, desc, num, dir, path, command, always_run=False):
975 """Initialize a Command object."""
976 self.dir = dir
977 self.path = path
978 self.desc = desc
979 trans = str.maketrans({' ': '-'})
980 self.logbase = '%03d-%s' % (num, desc.translate(trans))
981 self.command = command
982 self.always_run = always_run
984 @staticmethod
985 def shell_make_quote_string(s):
986 """Given a string not containing a newline, quote it for use by the
987 shell and make."""
988 assert '\n' not in s
989 if re.fullmatch('[]+,./0-9@A-Z_a-z-]+', s):
990 return s
991 strans = str.maketrans({"'": "'\\''"})
992 s = "'%s'" % s.translate(strans)
993 mtrans = str.maketrans({'$': '$$'})
994 return s.translate(mtrans)
996 @staticmethod
997 def shell_make_quote_list(l, translate_make):
998 """Given a list of strings not containing newlines, quote them for use
999 by the shell and make, returning a single string. If translate_make
1000 is true and the first string is 'make', change it to $(MAKE)."""
1001 l = [Command.shell_make_quote_string(s) for s in l]
1002 if translate_make and l[0] == 'make':
1003 l[0] = '$(MAKE)'
1004 return ' '.join(l)
1006 def shell_make_quote(self):
1007 """Return this command quoted for the shell and make."""
1008 return self.shell_make_quote_list(self.command, True)
1011 class CommandList(object):
1012 """A list of commands run in the build process."""
1014 def __init__(self, desc, keep):
1015 """Initialize a CommandList object."""
1016 self.cmdlist = []
1017 self.dir = None
1018 self.path = None
1019 self.desc = [desc]
1020 self.keep = keep
1022 def desc_txt(self, desc):
1023 """Return the description to use for a command."""
1024 return '%s %s' % (' '.join(self.desc), desc)
1026 def use_dir(self, dir):
1027 """Set the default directory for subsequent commands."""
1028 self.dir = dir
1030 def use_path(self, path):
1031 """Set a directory to be prepended to the PATH for subsequent
1032 commands."""
1033 self.path = path
1035 def push_subdesc(self, subdesc):
1036 """Set the default subdescription for subsequent commands (e.g., the
1037 name of a component being built, within the series of commands
1038 building it)."""
1039 self.desc.append(subdesc)
1041 def pop_subdesc(self):
1042 """Pop a subdescription from the list of descriptions."""
1043 self.desc.pop()
1045 def create_use_dir(self, dir):
1046 """Remove and recreate a directory and use it for subsequent
1047 commands."""
1048 self.add_command_dir('rm', None, ['rm', '-rf', dir])
1049 self.add_command_dir('mkdir', None, ['mkdir', '-p', dir])
1050 self.use_dir(dir)
1052 def create_copy_dir(self, src, dest):
1053 """Remove a directory and recreate it as a copy from the given
1054 source."""
1055 self.add_command_dir('copy-rm', None, ['rm', '-rf', dest])
1056 parent = os.path.dirname(dest)
1057 self.add_command_dir('copy-mkdir', None, ['mkdir', '-p', parent])
1058 self.add_command_dir('copy', None, ['cp', '-a', src, dest])
1060 def add_command_dir(self, desc, dir, command, always_run=False):
1061 """Add a command to run in a given directory."""
1062 cmd = Command(self.desc_txt(desc), len(self.cmdlist), dir, self.path,
1063 command, always_run)
1064 self.cmdlist.append(cmd)
1066 def add_command(self, desc, command, always_run=False):
1067 """Add a command to run in the default directory."""
1068 cmd = Command(self.desc_txt(desc), len(self.cmdlist), self.dir,
1069 self.path, command, always_run)
1070 self.cmdlist.append(cmd)
1072 def cleanup_dir(self, desc='cleanup', dir=None):
1073 """Clean up a build directory. If no directory is specified, the
1074 default directory is cleaned up and ceases to be the default
1075 directory."""
1076 if dir is None:
1077 dir = self.dir
1078 self.use_dir(None)
1079 if self.keep != 'all':
1080 self.add_command_dir(desc, None, ['rm', '-rf', dir],
1081 always_run=(self.keep == 'none'))
1083 def makefile_commands(self, wrapper, logsdir):
1084 """Return the sequence of commands in the form of text for a Makefile.
1085 The given wrapper script takes arguments: base of logs for
1086 previous command, or empty; base of logs for this command;
1087 description; directory; PATH addition; the command itself."""
1088 # prev_base is the base of the name for logs of the previous
1089 # command that is not always-run (that is, a build command,
1090 # whose failure should stop subsequent build commands from
1091 # being run, as opposed to a cleanup command, which is run
1092 # even if previous commands failed).
1093 prev_base = ''
1094 cmds = []
1095 for c in self.cmdlist:
1096 ctxt = c.shell_make_quote()
1097 if prev_base and not c.always_run:
1098 prev_log = os.path.join(logsdir, prev_base)
1099 else:
1100 prev_log = ''
1101 this_log = os.path.join(logsdir, c.logbase)
1102 if not c.always_run:
1103 prev_base = c.logbase
1104 if c.dir is None:
1105 dir = ''
1106 else:
1107 dir = c.dir
1108 if c.path is None:
1109 path = ''
1110 else:
1111 path = c.path
1112 prelims = [wrapper, prev_log, this_log, c.desc, dir, path]
1113 prelim_txt = Command.shell_make_quote_list(prelims, False)
1114 cmds.append('\t@%s %s' % (prelim_txt, ctxt))
1115 return '\n'.join(cmds)
1118 def get_parser():
1119 """Return an argument parser for this module."""
1120 parser = argparse.ArgumentParser(description=__doc__)
1121 parser.add_argument('-j', dest='parallelism',
1122 help='Run this number of jobs in parallel',
1123 type=int, default=os.cpu_count())
1124 parser.add_argument('--keep', dest='keep',
1125 help='Whether to keep all build directories, '
1126 'none or only those from failed builds',
1127 default='none', choices=('none', 'all', 'failed'))
1128 parser.add_argument('topdir',
1129 help='Toplevel working directory')
1130 parser.add_argument('action',
1131 help='What to do',
1132 choices=('checkout', 'host-libraries', 'compilers',
1133 'glibcs'))
1134 parser.add_argument('configs',
1135 help='Versions to check out or configurations to build',
1136 nargs='*')
1137 return parser
1140 def main(argv):
1141 """The main entry point."""
1142 parser = get_parser()
1143 opts = parser.parse_args(argv)
1144 topdir = os.path.abspath(opts.topdir)
1145 ctx = Context(topdir, opts.parallelism, opts.keep, opts.action)
1146 ctx.run_builds(opts.action, opts.configs)
1149 if __name__ == '__main__':
1150 main(sys.argv[1:])