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.
45 class Context(object):
46 """The global state associated with builds in a given directory."""
48 def __init__(self
, topdir
, parallelism
, keep
, replace_sources
, action
):
49 """Initialize the context."""
51 self
.parallelism
= parallelism
53 self
.replace_sources
= replace_sources
54 self
.srcdir
= os
.path
.join(topdir
, 'src')
55 self
.versions_json
= os
.path
.join(self
.srcdir
, 'versions.json')
56 self
.installdir
= os
.path
.join(topdir
, 'install')
57 self
.host_libraries_installdir
= os
.path
.join(self
.installdir
,
59 self
.builddir
= os
.path
.join(topdir
, 'build')
60 self
.logsdir
= os
.path
.join(topdir
, 'logs')
61 self
.makefile
= os
.path
.join(self
.builddir
, 'Makefile')
62 self
.wrapper
= os
.path
.join(self
.builddir
, 'wrapper')
63 self
.save_logs
= os
.path
.join(self
.builddir
, 'save-logs')
64 self
.script_text
= self
.get_script_text()
65 if action
!= 'checkout':
66 self
.build_triplet
= self
.get_build_triplet()
67 self
.glibc_version
= self
.get_glibc_version()
69 self
.glibc_configs
= {}
70 self
.makefile_pieces
= ['.PHONY: all\n']
71 self
.add_all_configs()
72 self
.load_versions_json()
74 def get_script_text(self
):
75 """Return the text of this script."""
76 with
open(sys
.argv
[0], 'r') as f
:
80 """Re-execute this script with the same arguments."""
81 os
.execv(sys
.executable
, [sys
.executable
] + sys
.argv
)
83 def get_build_triplet(self
):
84 """Determine the build triplet with config.guess."""
85 config_guess
= os
.path
.join(self
.component_srcdir('gcc'),
87 cg_out
= subprocess
.run([config_guess
], stdout
=subprocess
.PIPE
,
88 check
=True, universal_newlines
=True).stdout
89 return cg_out
.rstrip()
91 def get_glibc_version(self
):
92 """Determine the glibc version number (major.minor)."""
93 version_h
= os
.path
.join(self
.component_srcdir('glibc'), 'version.h')
94 with
open(version_h
, 'r') as f
:
96 starttext
= '#define VERSION "'
98 if l
.startswith(starttext
):
99 l
= l
[len(starttext
):]
101 m
= re
.fullmatch('([0-9]+)\.([0-9]+)[.0-9]*', l
)
102 return '%s.%s' % m
.group(1, 2)
103 print('error: could not determine glibc version')
106 def add_all_configs(self
):
107 """Add all known glibc build configurations."""
108 # On architectures missing __builtin_trap support, these
109 # options may be needed as a workaround; see
110 # <https://gcc.gnu.org/bugzilla/show_bug.cgi?id=70216> for SH.
111 no_isolate
= ('-fno-isolate-erroneous-paths-dereference'
112 ' -fno-isolate-erroneous-paths-attribute')
113 self
.add_config(arch
='aarch64',
115 self
.add_config(arch
='aarch64_be',
117 self
.add_config(arch
='alpha',
119 self
.add_config(arch
='arm',
120 os_name
='linux-gnueabi')
121 self
.add_config(arch
='armeb',
122 os_name
='linux-gnueabi')
123 self
.add_config(arch
='armeb',
124 os_name
='linux-gnueabi',
126 gcc_cfg
=['--with-arch=armv7-a'])
127 self
.add_config(arch
='arm',
128 os_name
='linux-gnueabihf')
129 self
.add_config(arch
='armeb',
130 os_name
='linux-gnueabihf')
131 self
.add_config(arch
='armeb',
132 os_name
='linux-gnueabihf',
134 gcc_cfg
=['--with-arch=armv7-a'])
135 self
.add_config(arch
='hppa',
137 self
.add_config(arch
='ia64',
139 first_gcc_cfg
=['--with-system-libunwind'])
140 self
.add_config(arch
='m68k',
142 gcc_cfg
=['--disable-multilib'])
143 self
.add_config(arch
='m68k',
146 gcc_cfg
=['--with-arch=cf', '--disable-multilib'])
147 self
.add_config(arch
='microblaze',
149 gcc_cfg
=['--disable-multilib'])
150 self
.add_config(arch
='microblazeel',
152 gcc_cfg
=['--disable-multilib'])
153 self
.add_config(arch
='mips64',
155 gcc_cfg
=['--with-mips-plt'],
156 glibcs
=[{'variant': 'n32'},
158 'ccopts': '-mabi=32'},
160 'ccopts': '-mabi=64'}])
161 self
.add_config(arch
='mips64',
164 gcc_cfg
=['--with-mips-plt', '--with-float=soft'],
165 glibcs
=[{'variant': 'n32-soft',
166 'cfg': ['--without-fp']},
169 'ccopts': '-mabi=32',
170 'cfg': ['--without-fp']},
171 {'variant': 'n64-soft',
172 'ccopts': '-mabi=64',
173 'cfg': ['--without-fp']}])
174 self
.add_config(arch
='mips64',
177 gcc_cfg
=['--with-mips-plt', '--with-nan=2008',
178 '--with-arch-64=mips64r2',
179 '--with-arch-32=mips32r2'],
180 glibcs
=[{'variant': 'n32-nan2008'},
181 {'variant': 'nan2008',
183 'ccopts': '-mabi=32'},
184 {'variant': 'n64-nan2008',
185 'ccopts': '-mabi=64'}])
186 self
.add_config(arch
='mips64',
188 variant
='nan2008-soft',
189 gcc_cfg
=['--with-mips-plt', '--with-nan=2008',
190 '--with-arch-64=mips64r2',
191 '--with-arch-32=mips32r2',
192 '--with-float=soft'],
193 glibcs
=[{'variant': 'n32-nan2008-soft',
194 'cfg': ['--without-fp']},
195 {'variant': 'nan2008-soft',
197 'ccopts': '-mabi=32',
198 'cfg': ['--without-fp']},
199 {'variant': 'n64-nan2008-soft',
200 'ccopts': '-mabi=64',
201 'cfg': ['--without-fp']}])
202 self
.add_config(arch
='mips64el',
204 gcc_cfg
=['--with-mips-plt'],
205 glibcs
=[{'variant': 'n32'},
207 'ccopts': '-mabi=32'},
209 'ccopts': '-mabi=64'}])
210 self
.add_config(arch
='mips64el',
213 gcc_cfg
=['--with-mips-plt', '--with-float=soft'],
214 glibcs
=[{'variant': 'n32-soft',
215 'cfg': ['--without-fp']},
218 'ccopts': '-mabi=32',
219 'cfg': ['--without-fp']},
220 {'variant': 'n64-soft',
221 'ccopts': '-mabi=64',
222 'cfg': ['--without-fp']}])
223 self
.add_config(arch
='mips64el',
226 gcc_cfg
=['--with-mips-plt', '--with-nan=2008',
227 '--with-arch-64=mips64r2',
228 '--with-arch-32=mips32r2'],
229 glibcs
=[{'variant': 'n32-nan2008'},
230 {'variant': 'nan2008',
232 'ccopts': '-mabi=32'},
233 {'variant': 'n64-nan2008',
234 'ccopts': '-mabi=64'}])
235 self
.add_config(arch
='mips64el',
237 variant
='nan2008-soft',
238 gcc_cfg
=['--with-mips-plt', '--with-nan=2008',
239 '--with-arch-64=mips64r2',
240 '--with-arch-32=mips32r2',
241 '--with-float=soft'],
242 glibcs
=[{'variant': 'n32-nan2008-soft',
243 'cfg': ['--without-fp']},
244 {'variant': 'nan2008-soft',
246 'ccopts': '-mabi=32',
247 'cfg': ['--without-fp']},
248 {'variant': 'n64-nan2008-soft',
249 'ccopts': '-mabi=64',
250 'cfg': ['--without-fp']}])
251 self
.add_config(arch
='nios2',
253 self
.add_config(arch
='powerpc',
255 gcc_cfg
=['--disable-multilib', '--enable-secureplt'])
256 self
.add_config(arch
='powerpc',
259 gcc_cfg
=['--disable-multilib', '--with-float=soft',
260 '--enable-secureplt'],
261 glibcs
=[{'variant': 'soft', 'cfg': ['--without-fp']}])
262 self
.add_config(arch
='powerpc64',
264 gcc_cfg
=['--disable-multilib', '--enable-secureplt'])
265 self
.add_config(arch
='powerpc64le',
267 gcc_cfg
=['--disable-multilib', '--enable-secureplt'])
268 self
.add_config(arch
='powerpc',
269 os_name
='linux-gnuspe',
270 gcc_cfg
=['--disable-multilib', '--enable-secureplt',
271 '--enable-e500-double'],
272 glibcs
=[{'cfg': ['--without-fp']}])
273 self
.add_config(arch
='powerpc',
274 os_name
='linux-gnuspe',
276 gcc_cfg
=['--disable-multilib', '--enable-secureplt'],
277 glibcs
=[{'variant': 'e500v1', 'cfg': ['--without-fp']}])
278 self
.add_config(arch
='s390x',
281 {'arch': 's390', 'ccopts': '-m31'}])
282 self
.add_config(arch
='sh3',
284 glibcs
=[{'ccopts': no_isolate
}])
285 self
.add_config(arch
='sh3eb',
287 glibcs
=[{'ccopts': no_isolate
}])
288 self
.add_config(arch
='sh4',
290 glibcs
=[{'ccopts': no_isolate
}])
291 self
.add_config(arch
='sh4eb',
293 glibcs
=[{'ccopts': no_isolate
}])
294 self
.add_config(arch
='sh4',
297 gcc_cfg
=['--without-fp'],
298 glibcs
=[{'variant': 'soft',
299 'cfg': ['--without-fp'],
300 'ccopts': no_isolate
}])
301 self
.add_config(arch
='sh4eb',
304 gcc_cfg
=['--without-fp'],
305 glibcs
=[{'variant': 'soft',
306 'cfg': ['--without-fp'],
307 'ccopts': no_isolate
}])
308 self
.add_config(arch
='sparc64',
312 'ccopts': '-m32 -mlong-double-128'}])
313 self
.add_config(arch
='tilegx',
316 {'variant': '32', 'ccopts': '-m32'}])
317 self
.add_config(arch
='tilegxbe',
320 {'variant': '32', 'ccopts': '-m32'}])
321 self
.add_config(arch
='tilepro',
323 self
.add_config(arch
='x86_64',
325 gcc_cfg
=['--with-multilib-list=m64,m32,mx32'],
327 {'variant': 'x32', 'ccopts': '-mx32'},
328 {'arch': 'i686', 'ccopts': '-m32 -march=i686'}],
329 extra_glibcs
=[{'variant': 'disable-multi-arch',
330 'cfg': ['--disable-multi-arch']},
331 {'variant': 'disable-multi-arch',
333 'ccopts': '-m32 -march=i686',
334 'cfg': ['--disable-multi-arch']},
336 'ccopts': '-m32 -march=i486'},
338 'ccopts': '-m32 -march=i586'}])
340 def add_config(self
, **args
):
341 """Add an individual build configuration."""
342 cfg
= Config(self
, **args
)
343 if cfg
.name
in self
.configs
:
344 print('error: duplicate config %s' % cfg
.name
)
346 self
.configs
[cfg
.name
] = cfg
347 for c
in cfg
.all_glibcs
:
348 if c
.name
in self
.glibc_configs
:
349 print('error: duplicate glibc config %s' % c
.name
)
351 self
.glibc_configs
[c
.name
] = c
353 def component_srcdir(self
, component
):
354 """Return the source directory for a given component, e.g. gcc."""
355 return os
.path
.join(self
.srcdir
, component
)
357 def component_builddir(self
, action
, config
, component
, subconfig
=None):
358 """Return the directory to use for a build."""
361 assert subconfig
is None
362 return os
.path
.join(self
.builddir
, action
, component
)
363 if subconfig
is None:
364 return os
.path
.join(self
.builddir
, action
, config
, component
)
366 # glibc build as part of compiler build.
367 return os
.path
.join(self
.builddir
, action
, config
, component
,
370 def compiler_installdir(self
, config
):
371 """Return the directory in which to install a compiler."""
372 return os
.path
.join(self
.installdir
, 'compilers', config
)
374 def compiler_bindir(self
, config
):
375 """Return the directory in which to find compiler binaries."""
376 return os
.path
.join(self
.compiler_installdir(config
), 'bin')
378 def compiler_sysroot(self
, config
):
379 """Return the sysroot directory for a compiler."""
380 return os
.path
.join(self
.compiler_installdir(config
), 'sysroot')
382 def glibc_installdir(self
, config
):
383 """Return the directory in which to install glibc."""
384 return os
.path
.join(self
.installdir
, 'glibcs', config
)
386 def run_builds(self
, action
, configs
):
387 """Run the requested builds."""
388 if action
== 'checkout':
389 self
.checkout(configs
)
391 elif action
== 'host-libraries':
393 print('error: configurations specified for host-libraries')
395 self
.build_host_libraries()
396 elif action
== 'compilers':
397 self
.build_compilers(configs
)
399 self
.build_glibcs(configs
)
404 def remove_dirs(*args
):
405 """Remove directories and their contents if they exist."""
407 shutil
.rmtree(dir, ignore_errors
=True)
410 def remove_recreate_dirs(*args
):
411 """Remove directories if they exist, and create them as empty."""
412 Context
.remove_dirs(*args
)
414 os
.makedirs(dir, exist_ok
=True)
416 def add_makefile_cmdlist(self
, target
, cmdlist
, logsdir
):
417 """Add makefile text for a list of commands."""
418 commands
= cmdlist
.makefile_commands(self
.wrapper
, logsdir
)
419 self
.makefile_pieces
.append('all: %s\n.PHONY: %s\n%s:\n%s\n' %
420 (target
, target
, target
, commands
))
422 def write_files(self
):
423 """Write out the Makefile and wrapper script."""
424 mftext
= ''.join(self
.makefile_pieces
)
425 with
open(self
.makefile
, 'w') as f
:
435 'prev_status=$prev_base-status.txt\n'
436 'this_status=$this_base-status.txt\n'
437 'this_log=$this_base-log.txt\n'
438 'date > "$this_log"\n'
439 'echo >> "$this_log"\n'
440 'echo "Description: $desc" >> "$this_log"\n'
441 'printf "%s" "Command:" >> "$this_log"\n'
442 'for word in "$@"; do\n'
443 ' if expr "$word" : "[]+,./0-9@A-Z_a-z-]\\\\{1,\\\\}\\$" > /dev/null; then\n'
444 ' printf " %s" "$word"\n'
447 ' printf "%s" "$word" | sed -e "s/\'/\'\\\\\\\\\'\'/"\n'
450 'done >> "$this_log"\n'
451 'echo >> "$this_log"\n'
452 'echo "Directory: $dir" >> "$this_log"\n'
453 'echo "Path addition: $path" >> "$this_log"\n'
454 'echo >> "$this_log"\n'
457 ' echo >> "$this_log"\n'
458 ' echo "$1: $desc" > "$this_status"\n'
459 ' echo "$1: $desc" >> "$this_log"\n'
460 ' echo >> "$this_log"\n'
461 ' date >> "$this_log"\n'
462 ' echo "$1: $desc"\n'
467 ' if [ "$1" != "0" ]; then\n'
468 ' record_status FAIL\n'
471 'if [ "$prev_base" ] && ! grep -q "^PASS" "$prev_status"; then\n'
472 ' record_status UNRESOLVED\n'
474 'if [ "$dir" ]; then\n'
476 ' check_error "$?"\n'
478 'if [ "$path" ]; then\n'
479 ' PATH=$path:$PATH\n'
481 '"$@" < /dev/null >> "$this_log" 2>&1\n'
483 'record_status PASS\n')
484 with
open(self
.wrapper
, 'w') as f
:
485 f
.write(wrapper_text
)
487 mode_exec
= (stat
.S_IRWXU|stat
.S_IRGRP|stat
.S_IXGRP|
488 stat
.S_IROTH|stat
.S_IXOTH
)
489 os
.chmod(self
.wrapper
, mode_exec
)
492 'if ! [ -f tests.sum ]; then\n'
493 ' echo "No test summary available."\n'
498 ' echo "Contents of $1:"\n'
502 ' echo "End of contents of $1."\n'
505 'save_file tests.sum\n'
506 'non_pass_tests=$(grep -v "^PASS: " tests.sum | sed -e "s/^PASS: //")\n'
507 'for t in $non_pass_tests; do\n'
508 ' if [ -f "$t.out" ]; then\n'
509 ' save_file "$t.out"\n'
512 with
open(self
.save_logs
, 'w') as f
:
513 f
.write(save_logs_text
)
514 os
.chmod(self
.save_logs
, mode_exec
)
517 """Do the actual build."""
518 cmd
= ['make', '-j%d' % self
.parallelism
]
519 subprocess
.run(cmd
, cwd
=self
.builddir
, check
=True)
521 def build_host_libraries(self
):
522 """Build the host libraries."""
523 installdir
= self
.host_libraries_installdir
524 builddir
= os
.path
.join(self
.builddir
, 'host-libraries')
525 logsdir
= os
.path
.join(self
.logsdir
, 'host-libraries')
526 self
.remove_recreate_dirs(installdir
, builddir
, logsdir
)
527 cmdlist
= CommandList('host-libraries', self
.keep
)
528 self
.build_host_library(cmdlist
, 'gmp')
529 self
.build_host_library(cmdlist
, 'mpfr',
530 ['--with-gmp=%s' % installdir
])
531 self
.build_host_library(cmdlist
, 'mpc',
532 ['--with-gmp=%s' % installdir
,
533 '--with-mpfr=%s' % installdir
])
534 cmdlist
.add_command('done', ['touch', os
.path
.join(installdir
, 'ok')])
535 self
.add_makefile_cmdlist('host-libraries', cmdlist
, logsdir
)
537 def build_host_library(self
, cmdlist
, lib
, extra_opts
=None):
538 """Build one host library."""
539 srcdir
= self
.component_srcdir(lib
)
540 builddir
= self
.component_builddir('host-libraries', None, lib
)
541 installdir
= self
.host_libraries_installdir
542 cmdlist
.push_subdesc(lib
)
543 cmdlist
.create_use_dir(builddir
)
544 cfg_cmd
= [os
.path
.join(srcdir
, 'configure'),
545 '--prefix=%s' % installdir
,
548 cfg_cmd
.extend (extra_opts
)
549 cmdlist
.add_command('configure', cfg_cmd
)
550 cmdlist
.add_command('build', ['make'])
551 cmdlist
.add_command('check', ['make', 'check'])
552 cmdlist
.add_command('install', ['make', 'install'])
553 cmdlist
.cleanup_dir()
554 cmdlist
.pop_subdesc()
556 def build_compilers(self
, configs
):
557 """Build the compilers."""
559 self
.remove_dirs(os
.path
.join(self
.builddir
, 'compilers'))
560 self
.remove_dirs(os
.path
.join(self
.installdir
, 'compilers'))
561 self
.remove_dirs(os
.path
.join(self
.logsdir
, 'compilers'))
562 configs
= sorted(self
.configs
.keys())
564 self
.configs
[c
].build()
566 def build_glibcs(self
, configs
):
567 """Build the glibcs."""
569 self
.remove_dirs(os
.path
.join(self
.builddir
, 'glibcs'))
570 self
.remove_dirs(os
.path
.join(self
.installdir
, 'glibcs'))
571 self
.remove_dirs(os
.path
.join(self
.logsdir
, 'glibcs'))
572 configs
= sorted(self
.glibc_configs
.keys())
574 self
.glibc_configs
[c
].build()
576 def load_versions_json(self
):
577 """Load information about source directory versions."""
578 if not os
.access(self
.versions_json
, os
.F_OK
):
581 with
open(self
.versions_json
, 'r') as f
:
582 self
.versions
= json
.load(f
)
584 def store_json(self
, data
, filename
):
585 """Store information in a JSON file."""
586 filename_tmp
= filename
+ '.tmp'
587 with
open(filename_tmp
, 'w') as f
:
588 json
.dump(data
, f
, indent
=2, sort_keys
=True)
589 os
.rename(filename_tmp
, filename
)
591 def store_versions_json(self
):
592 """Store information about source directory versions."""
593 self
.store_json(self
.versions
, self
.versions_json
)
595 def set_component_version(self
, component
, version
, explicit
, revision
):
596 """Set the version information for a component."""
597 self
.versions
[component
] = {'version': version
,
598 'explicit': explicit
,
599 'revision': revision
}
600 self
.store_versions_json()
602 def checkout(self
, versions
):
603 """Check out the desired component versions."""
604 default_versions
= {'binutils': 'vcs-2.27',
606 'glibc': 'vcs-mainline',
612 explicit_versions
= {}
615 for k
in default_versions
.keys():
619 if k
in use_versions
:
620 print('error: multiple versions for %s' % k
)
623 explicit_versions
[k
] = True
627 print('error: unknown component in %s' % v
)
629 for k
in default_versions
.keys():
630 if k
not in use_versions
:
631 if k
in self
.versions
and self
.versions
[k
]['explicit']:
632 use_versions
[k
] = self
.versions
[k
]['version']
633 explicit_versions
[k
] = True
635 use_versions
[k
] = default_versions
[k
]
636 explicit_versions
[k
] = False
637 os
.makedirs(self
.srcdir
, exist_ok
=True)
638 for k
in sorted(default_versions
.keys()):
639 update
= os
.access(self
.component_srcdir(k
), os
.F_OK
)
642 k
in self
.versions
and
643 v
!= self
.versions
[k
]['version']):
644 if not self
.replace_sources
:
645 print('error: version of %s has changed from %s to %s, '
646 'use --replace-sources to check out again' %
647 (k
, self
.versions
[k
]['version'], v
))
649 shutil
.rmtree(self
.component_srcdir(k
))
651 if v
.startswith('vcs-'):
652 revision
= self
.checkout_vcs(k
, v
[4:], update
)
654 self
.checkout_tar(k
, v
, update
)
656 self
.set_component_version(k
, v
, explicit_versions
[k
], revision
)
657 if self
.get_script_text() != self
.script_text
:
658 # Rerun the checkout process in case the updated script
659 # uses different default versions or new components.
662 def checkout_vcs(self
, component
, version
, update
):
663 """Check out the given version of the given component from version
664 control. Return a revision identifier."""
665 if component
== 'binutils':
666 git_url
= 'git://sourceware.org/git/binutils-gdb.git'
667 if version
== 'mainline':
668 git_branch
= 'master'
670 trans
= str.maketrans({'.': '_'})
671 git_branch
= 'binutils-%s-branch' % version
.translate(trans
)
672 return self
.git_checkout(component
, git_url
, git_branch
, update
)
673 elif component
== 'gcc':
674 if version
== 'mainline':
677 trans
= str.maketrans({'.': '_'})
678 branch
= 'branches/gcc-%s-branch' % version
.translate(trans
)
679 svn_url
= 'svn://gcc.gnu.org/svn/gcc/%s' % branch
680 return self
.gcc_checkout(svn_url
, update
)
681 elif component
== 'glibc':
682 git_url
= 'git://sourceware.org/git/glibc.git'
683 if version
== 'mainline':
684 git_branch
= 'master'
686 git_branch
= 'release/%s/master' % version
687 r
= self
.git_checkout(component
, git_url
, git_branch
, update
)
688 self
.fix_glibc_timestamps()
691 print('error: component %s coming from VCS' % component
)
694 def git_checkout(self
, component
, git_url
, git_branch
, update
):
695 """Check out a component from git. Return a commit identifier."""
697 subprocess
.run(['git', 'remote', 'prune', 'origin'],
698 cwd
=self
.component_srcdir(component
), check
=True)
699 subprocess
.run(['git', 'pull', '-q'],
700 cwd
=self
.component_srcdir(component
), check
=True)
702 subprocess
.run(['git', 'clone', '-q', '-b', git_branch
, git_url
,
703 self
.component_srcdir(component
)], check
=True)
704 r
= subprocess
.run(['git', 'rev-parse', 'HEAD'],
705 cwd
=self
.component_srcdir(component
),
706 stdout
=subprocess
.PIPE
,
707 check
=True, universal_newlines
=True).stdout
710 def fix_glibc_timestamps(self
):
711 """Fix timestamps in a glibc checkout."""
712 # Ensure that builds do not try to regenerate generated files
713 # in the source tree.
714 srcdir
= self
.component_srcdir('glibc')
715 for dirpath
, dirnames
, filenames
in os
.walk(srcdir
):
717 if (f
== 'configure' or
718 f
== 'preconfigure' or
719 f
.endswith('-kw.h')):
720 to_touch
= os
.path
.join(dirpath
, f
)
721 subprocess
.run(['touch', to_touch
], check
=True)
723 def gcc_checkout(self
, svn_url
, update
):
724 """Check out GCC from SVN. Return the revision number."""
726 subprocess
.run(['svn', 'co', '-q', svn_url
,
727 self
.component_srcdir('gcc')], check
=True)
728 subprocess
.run(['contrib/gcc_update', '--silent'],
729 cwd
=self
.component_srcdir('gcc'), check
=True)
730 r
= subprocess
.run(['svnversion', self
.component_srcdir('gcc')],
731 stdout
=subprocess
.PIPE
,
732 check
=True, universal_newlines
=True).stdout
735 def checkout_tar(self
, component
, version
, update
):
736 """Check out the given version of the given component from a
740 url_map
= {'binutils': 'https://ftp.gnu.org/gnu/binutils/binutils-%(version)s.tar.bz2',
741 'gcc': 'https://ftp.gnu.org/gnu/gcc/gcc-%(version)s/gcc-%(version)s.tar.bz2',
742 'gmp': 'https://ftp.gnu.org/gnu/gmp/gmp-%(version)s.tar.xz',
743 'linux': 'https://www.kernel.org/pub/linux/kernel/v4.x/linux-%(version)s.tar.xz',
744 'mpc': 'https://ftp.gnu.org/gnu/mpc/mpc-%(version)s.tar.gz',
745 'mpfr': 'https://ftp.gnu.org/gnu/mpfr/mpfr-%(version)s.tar.xz'}
746 if component
not in url_map
:
747 print('error: component %s coming from tarball' % component
)
749 url
= url_map
[component
] % {'version': version
}
750 filename
= os
.path
.join(self
.srcdir
, url
.split('/')[-1])
751 response
= urllib
.request
.urlopen(url
)
752 data
= response
.read()
753 with
open(filename
, 'wb') as f
:
755 subprocess
.run(['tar', '-C', self
.srcdir
, '-x', '-f', filename
],
757 os
.rename(os
.path
.join(self
.srcdir
, '%s-%s' % (component
, version
)),
758 self
.component_srcdir(component
))
762 class Config(object):
763 """A configuration for building a compiler and associated libraries."""
765 def __init__(self
, ctx
, arch
, os_name
, variant
=None, gcc_cfg
=None,
766 first_gcc_cfg
=None, glibcs
=None, extra_glibcs
=None):
767 """Initialize a Config object."""
771 self
.variant
= variant
773 self
.name
= '%s-%s' % (arch
, os_name
)
775 self
.name
= '%s-%s-%s' % (arch
, os_name
, variant
)
776 self
.triplet
= '%s-glibc-%s' % (arch
, os_name
)
780 self
.gcc_cfg
= gcc_cfg
781 if first_gcc_cfg
is None:
782 self
.first_gcc_cfg
= []
784 self
.first_gcc_cfg
= first_gcc_cfg
786 glibcs
= [{'variant': variant
}]
787 if extra_glibcs
is None:
789 glibcs
= [Glibc(self
, **g
) for g
in glibcs
]
790 extra_glibcs
= [Glibc(self
, **g
) for g
in extra_glibcs
]
791 self
.all_glibcs
= glibcs
+ extra_glibcs
792 self
.compiler_glibcs
= glibcs
793 self
.installdir
= ctx
.compiler_installdir(self
.name
)
794 self
.bindir
= ctx
.compiler_bindir(self
.name
)
795 self
.sysroot
= ctx
.compiler_sysroot(self
.name
)
796 self
.builddir
= os
.path
.join(ctx
.builddir
, 'compilers', self
.name
)
797 self
.logsdir
= os
.path
.join(ctx
.logsdir
, 'compilers', self
.name
)
799 def component_builddir(self
, component
):
800 """Return the directory to use for a (non-glibc) build."""
801 return self
.ctx
.component_builddir('compilers', self
.name
, component
)
804 """Generate commands to build this compiler."""
805 self
.ctx
.remove_recreate_dirs(self
.installdir
, self
.builddir
,
807 cmdlist
= CommandList('compilers-%s' % self
.name
, self
.ctx
.keep
)
808 cmdlist
.add_command('check-host-libraries',
810 os
.path
.join(self
.ctx
.host_libraries_installdir
,
812 cmdlist
.use_path(self
.bindir
)
813 self
.build_cross_tool(cmdlist
, 'binutils', 'binutils',
815 '--disable-libdecnumber',
816 '--disable-readline',
818 if self
.os
.startswith('linux'):
819 self
.install_linux_headers(cmdlist
)
820 self
.build_gcc(cmdlist
, True)
821 for g
in self
.compiler_glibcs
:
822 cmdlist
.push_subdesc('glibc')
823 cmdlist
.push_subdesc(g
.name
)
824 g
.build_glibc(cmdlist
, True)
825 cmdlist
.pop_subdesc()
826 cmdlist
.pop_subdesc()
827 self
.build_gcc(cmdlist
, False)
828 cmdlist
.add_command('done', ['touch',
829 os
.path
.join(self
.installdir
, 'ok')])
830 self
.ctx
.add_makefile_cmdlist('compilers-%s' % self
.name
, cmdlist
,
833 def build_cross_tool(self
, cmdlist
, tool_src
, tool_build
, extra_opts
=None):
834 """Build one cross tool."""
835 srcdir
= self
.ctx
.component_srcdir(tool_src
)
836 builddir
= self
.component_builddir(tool_build
)
837 cmdlist
.push_subdesc(tool_build
)
838 cmdlist
.create_use_dir(builddir
)
839 cfg_cmd
= [os
.path
.join(srcdir
, 'configure'),
840 '--prefix=%s' % self
.installdir
,
841 '--build=%s' % self
.ctx
.build_triplet
,
842 '--host=%s' % self
.ctx
.build_triplet
,
843 '--target=%s' % self
.triplet
,
844 '--with-sysroot=%s' % self
.sysroot
]
846 cfg_cmd
.extend(extra_opts
)
847 cmdlist
.add_command('configure', cfg_cmd
)
848 cmdlist
.add_command('build', ['make'])
849 cmdlist
.add_command('install', ['make', 'install'])
850 cmdlist
.cleanup_dir()
851 cmdlist
.pop_subdesc()
853 def install_linux_headers(self
, cmdlist
):
854 """Install Linux kernel headers."""
855 arch_map
= {'aarch64': 'arm64',
865 'microblaze': 'microblaze',
868 'powerpc': 'powerpc',
876 if self
.arch
.startswith(k
):
877 linux_arch
= arch_map
[k
]
879 assert linux_arch
is not None
880 srcdir
= self
.ctx
.component_srcdir('linux')
881 builddir
= self
.component_builddir('linux')
882 headers_dir
= os
.path
.join(self
.sysroot
, 'usr')
883 cmdlist
.push_subdesc('linux')
884 cmdlist
.create_use_dir(builddir
)
885 cmdlist
.add_command('install-headers',
886 ['make', '-C', srcdir
, 'O=%s' % builddir
,
887 'ARCH=%s' % linux_arch
,
888 'INSTALL_HDR_PATH=%s' % headers_dir
,
890 cmdlist
.cleanup_dir()
891 cmdlist
.pop_subdesc()
893 def build_gcc(self
, cmdlist
, bootstrap
):
895 # libsanitizer commonly breaks because of glibc header
896 # changes, or on unusual targets. libssp is of little
897 # relevance with glibc's own stack checking support.
898 cfg_opts
= list(self
.gcc_cfg
)
899 cfg_opts
+= ['--disable-libsanitizer', '--disable-libssp']
900 host_libs
= self
.ctx
.host_libraries_installdir
901 cfg_opts
+= ['--with-gmp=%s' % host_libs
,
902 '--with-mpfr=%s' % host_libs
,
903 '--with-mpc=%s' % host_libs
]
905 tool_build
= 'gcc-first'
906 # Building a static-only, C-only compiler that is
907 # sufficient to build glibc. Various libraries and
908 # features that may require libc headers must be disabled.
909 # When configuring with a sysroot, --with-newlib is
910 # required to define inhibit_libc (to stop some parts of
911 # libgcc including libc headers); --without-headers is not
913 cfg_opts
+= ['--enable-languages=c', '--disable-shared',
915 '--disable-libatomic',
916 '--disable-decimal-float',
921 '--disable-libquadmath',
922 '--without-headers', '--with-newlib',
923 '--with-glibc-version=%s' % self
.ctx
.glibc_version
925 cfg_opts
+= self
.first_gcc_cfg
928 cfg_opts
+= ['--enable-languages=c,c++', '--enable-shared',
930 self
.build_cross_tool(cmdlist
, 'gcc', tool_build
, cfg_opts
)
934 """A configuration for building glibc."""
936 def __init__(self
, compiler
, arch
=None, os_name
=None, variant
=None,
937 cfg
=None, ccopts
=None):
938 """Initialize a Glibc object."""
939 self
.ctx
= compiler
.ctx
940 self
.compiler
= compiler
942 self
.arch
= compiler
.arch
946 self
.os
= compiler
.os
949 self
.variant
= variant
951 self
.name
= '%s-%s' % (self
.arch
, self
.os
)
953 self
.name
= '%s-%s-%s' % (self
.arch
, self
.os
, variant
)
954 self
.triplet
= '%s-glibc-%s' % (self
.arch
, self
.os
)
961 def tool_name(self
, tool
):
962 """Return the name of a cross-compilation tool."""
963 ctool
= '%s-%s' % (self
.compiler
.triplet
, tool
)
964 if self
.ccopts
and (tool
== 'gcc' or tool
== 'g++'):
965 ctool
= '%s %s' % (ctool
, self
.ccopts
)
969 """Generate commands to build this glibc."""
970 builddir
= self
.ctx
.component_builddir('glibcs', self
.name
, 'glibc')
971 installdir
= self
.ctx
.glibc_installdir(self
.name
)
972 logsdir
= os
.path
.join(self
.ctx
.logsdir
, 'glibcs', self
.name
)
973 self
.ctx
.remove_recreate_dirs(installdir
, builddir
, logsdir
)
974 cmdlist
= CommandList('glibcs-%s' % self
.name
, self
.ctx
.keep
)
975 cmdlist
.add_command('check-compilers',
977 os
.path
.join(self
.compiler
.installdir
, 'ok')])
978 cmdlist
.use_path(self
.compiler
.bindir
)
979 self
.build_glibc(cmdlist
, False)
980 self
.ctx
.add_makefile_cmdlist('glibcs-%s' % self
.name
, cmdlist
,
983 def build_glibc(self
, cmdlist
, for_compiler
):
984 """Generate commands to build this glibc, either as part of a compiler
985 build or with the bootstrapped compiler (and in the latter case, run
987 srcdir
= self
.ctx
.component_srcdir('glibc')
989 builddir
= self
.ctx
.component_builddir('compilers',
990 self
.compiler
.name
, 'glibc',
992 installdir
= self
.compiler
.sysroot
993 srcdir_copy
= self
.ctx
.component_builddir('compilers',
998 builddir
= self
.ctx
.component_builddir('glibcs', self
.name
,
1000 installdir
= self
.ctx
.glibc_installdir(self
.name
)
1001 srcdir_copy
= self
.ctx
.component_builddir('glibcs', self
.name
,
1003 cmdlist
.create_use_dir(builddir
)
1004 # glibc builds write into the source directory, and even if
1005 # not intentionally there is a risk of bugs that involve
1006 # writing into the working directory. To avoid possible
1007 # concurrency issues, copy the source directory.
1008 cmdlist
.create_copy_dir(srcdir
, srcdir_copy
)
1009 cfg_cmd
= [os
.path
.join(srcdir_copy
, 'configure'),
1012 '--build=%s' % self
.ctx
.build_triplet
,
1013 '--host=%s' % self
.triplet
,
1014 'CC=%s' % self
.tool_name('gcc'),
1015 'CXX=%s' % self
.tool_name('g++'),
1016 'AR=%s' % self
.tool_name('ar'),
1017 'AS=%s' % self
.tool_name('as'),
1018 'LD=%s' % self
.tool_name('ld'),
1019 'NM=%s' % self
.tool_name('nm'),
1020 'OBJCOPY=%s' % self
.tool_name('objcopy'),
1021 'OBJDUMP=%s' % self
.tool_name('objdump'),
1022 'RANLIB=%s' % self
.tool_name('ranlib'),
1023 'READELF=%s' % self
.tool_name('readelf'),
1024 'STRIP=%s' % self
.tool_name('strip')]
1026 cmdlist
.add_command('configure', cfg_cmd
)
1027 cmdlist
.add_command('build', ['make'])
1028 cmdlist
.add_command('install', ['make', 'install',
1029 'install_root=%s' % installdir
])
1030 # GCC uses paths such as lib/../lib64, so make sure lib
1031 # directories always exist.
1032 cmdlist
.add_command('mkdir-lib', ['mkdir', '-p',
1033 os
.path
.join(installdir
, 'lib'),
1034 os
.path
.join(installdir
,
1036 if not for_compiler
:
1037 cmdlist
.add_command('check', ['make', 'check'])
1038 cmdlist
.add_command('save-logs', [self
.ctx
.save_logs
],
1040 cmdlist
.cleanup_dir('cleanup-src', srcdir_copy
)
1041 cmdlist
.cleanup_dir()
1044 class Command(object):
1045 """A command run in the build process."""
1047 def __init__(self
, desc
, num
, dir, path
, command
, always_run
=False):
1048 """Initialize a Command object."""
1052 trans
= str.maketrans({' ': '-'})
1053 self
.logbase
= '%03d-%s' % (num
, desc
.translate(trans
))
1054 self
.command
= command
1055 self
.always_run
= always_run
1058 def shell_make_quote_string(s
):
1059 """Given a string not containing a newline, quote it for use by the
1061 assert '\n' not in s
1062 if re
.fullmatch('[]+,./0-9@A-Z_a-z-]+', s
):
1064 strans
= str.maketrans({"'": "'\\''"})
1065 s
= "'%s'" % s
.translate(strans
)
1066 mtrans
= str.maketrans({'$': '$$'})
1067 return s
.translate(mtrans
)
1070 def shell_make_quote_list(l
, translate_make
):
1071 """Given a list of strings not containing newlines, quote them for use
1072 by the shell and make, returning a single string. If translate_make
1073 is true and the first string is 'make', change it to $(MAKE)."""
1074 l
= [Command
.shell_make_quote_string(s
) for s
in l
]
1075 if translate_make
and l
[0] == 'make':
1079 def shell_make_quote(self
):
1080 """Return this command quoted for the shell and make."""
1081 return self
.shell_make_quote_list(self
.command
, True)
1084 class CommandList(object):
1085 """A list of commands run in the build process."""
1087 def __init__(self
, desc
, keep
):
1088 """Initialize a CommandList object."""
1095 def desc_txt(self
, desc
):
1096 """Return the description to use for a command."""
1097 return '%s %s' % (' '.join(self
.desc
), desc
)
1099 def use_dir(self
, dir):
1100 """Set the default directory for subsequent commands."""
1103 def use_path(self
, path
):
1104 """Set a directory to be prepended to the PATH for subsequent
1108 def push_subdesc(self
, subdesc
):
1109 """Set the default subdescription for subsequent commands (e.g., the
1110 name of a component being built, within the series of commands
1112 self
.desc
.append(subdesc
)
1114 def pop_subdesc(self
):
1115 """Pop a subdescription from the list of descriptions."""
1118 def create_use_dir(self
, dir):
1119 """Remove and recreate a directory and use it for subsequent
1121 self
.add_command_dir('rm', None, ['rm', '-rf', dir])
1122 self
.add_command_dir('mkdir', None, ['mkdir', '-p', dir])
1125 def create_copy_dir(self
, src
, dest
):
1126 """Remove a directory and recreate it as a copy from the given
1128 self
.add_command_dir('copy-rm', None, ['rm', '-rf', dest
])
1129 parent
= os
.path
.dirname(dest
)
1130 self
.add_command_dir('copy-mkdir', None, ['mkdir', '-p', parent
])
1131 self
.add_command_dir('copy', None, ['cp', '-a', src
, dest
])
1133 def add_command_dir(self
, desc
, dir, command
, always_run
=False):
1134 """Add a command to run in a given directory."""
1135 cmd
= Command(self
.desc_txt(desc
), len(self
.cmdlist
), dir, self
.path
,
1136 command
, always_run
)
1137 self
.cmdlist
.append(cmd
)
1139 def add_command(self
, desc
, command
, always_run
=False):
1140 """Add a command to run in the default directory."""
1141 cmd
= Command(self
.desc_txt(desc
), len(self
.cmdlist
), self
.dir,
1142 self
.path
, command
, always_run
)
1143 self
.cmdlist
.append(cmd
)
1145 def cleanup_dir(self
, desc
='cleanup', dir=None):
1146 """Clean up a build directory. If no directory is specified, the
1147 default directory is cleaned up and ceases to be the default
1152 if self
.keep
!= 'all':
1153 self
.add_command_dir(desc
, None, ['rm', '-rf', dir],
1154 always_run
=(self
.keep
== 'none'))
1156 def makefile_commands(self
, wrapper
, logsdir
):
1157 """Return the sequence of commands in the form of text for a Makefile.
1158 The given wrapper script takes arguments: base of logs for
1159 previous command, or empty; base of logs for this command;
1160 description; directory; PATH addition; the command itself."""
1161 # prev_base is the base of the name for logs of the previous
1162 # command that is not always-run (that is, a build command,
1163 # whose failure should stop subsequent build commands from
1164 # being run, as opposed to a cleanup command, which is run
1165 # even if previous commands failed).
1168 for c
in self
.cmdlist
:
1169 ctxt
= c
.shell_make_quote()
1170 if prev_base
and not c
.always_run
:
1171 prev_log
= os
.path
.join(logsdir
, prev_base
)
1174 this_log
= os
.path
.join(logsdir
, c
.logbase
)
1175 if not c
.always_run
:
1176 prev_base
= c
.logbase
1185 prelims
= [wrapper
, prev_log
, this_log
, c
.desc
, dir, path
]
1186 prelim_txt
= Command
.shell_make_quote_list(prelims
, False)
1187 cmds
.append('\t@%s %s' % (prelim_txt
, ctxt
))
1188 return '\n'.join(cmds
)
1192 """Return an argument parser for this module."""
1193 parser
= argparse
.ArgumentParser(description
=__doc__
)
1194 parser
.add_argument('-j', dest
='parallelism',
1195 help='Run this number of jobs in parallel',
1196 type=int, default
=os
.cpu_count())
1197 parser
.add_argument('--keep', dest
='keep',
1198 help='Whether to keep all build directories, '
1199 'none or only those from failed builds',
1200 default
='none', choices
=('none', 'all', 'failed'))
1201 parser
.add_argument('--replace-sources', action
='store_true',
1202 help='Remove and replace source directories '
1203 'with the wrong version of a component')
1204 parser
.add_argument('topdir',
1205 help='Toplevel working directory')
1206 parser
.add_argument('action',
1208 choices
=('checkout', 'host-libraries', 'compilers',
1210 parser
.add_argument('configs',
1211 help='Versions to check out or configurations to build',
1217 """The main entry point."""
1218 parser
= get_parser()
1219 opts
= parser
.parse_args(argv
)
1220 topdir
= os
.path
.abspath(opts
.topdir
)
1221 ctx
= Context(topdir
, opts
.parallelism
, opts
.keep
, opts
.replace_sources
,
1223 ctx
.run_builds(opts
.action
, opts
.configs
)
1226 if __name__
== '__main__':