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.
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."""
50 self
.parallelism
= parallelism
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
,
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()
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'),
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
:
82 starttext
= '#define VERSION "'
84 if l
.startswith(starttext
):
85 l
= l
[len(starttext
):]
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')
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',
101 self
.add_config(arch
='aarch64_be',
103 self
.add_config(arch
='alpha',
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',
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',
120 gcc_cfg
=['--with-arch=armv7-a'])
121 self
.add_config(arch
='hppa',
123 self
.add_config(arch
='ia64',
125 first_gcc_cfg
=['--with-system-libunwind'])
126 self
.add_config(arch
='m68k',
128 gcc_cfg
=['--disable-multilib'])
129 self
.add_config(arch
='m68k',
132 gcc_cfg
=['--with-arch=cf', '--disable-multilib'])
133 self
.add_config(arch
='microblaze',
135 gcc_cfg
=['--disable-multilib'])
136 self
.add_config(arch
='microblazeel',
138 gcc_cfg
=['--disable-multilib'])
139 self
.add_config(arch
='mips64',
141 gcc_cfg
=['--with-mips-plt'],
142 glibcs
=[{'variant': 'n32'},
144 'ccopts': '-mabi=32'},
146 'ccopts': '-mabi=64'}])
147 self
.add_config(arch
='mips64',
150 gcc_cfg
=['--with-mips-plt', '--with-float=soft'],
151 glibcs
=[{'variant': 'n32-soft',
152 'cfg': ['--without-fp']},
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',
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',
169 'ccopts': '-mabi=32'},
170 {'variant': 'n64-nan2008',
171 'ccopts': '-mabi=64'}])
172 self
.add_config(arch
='mips64',
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',
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',
190 gcc_cfg
=['--with-mips-plt'],
191 glibcs
=[{'variant': 'n32'},
193 'ccopts': '-mabi=32'},
195 'ccopts': '-mabi=64'}])
196 self
.add_config(arch
='mips64el',
199 gcc_cfg
=['--with-mips-plt', '--with-float=soft'],
200 glibcs
=[{'variant': 'n32-soft',
201 'cfg': ['--without-fp']},
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',
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',
218 'ccopts': '-mabi=32'},
219 {'variant': 'n64-nan2008',
220 'ccopts': '-mabi=64'}])
221 self
.add_config(arch
='mips64el',
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',
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',
239 self
.add_config(arch
='powerpc',
241 gcc_cfg
=['--disable-multilib', '--enable-secureplt'])
242 self
.add_config(arch
='powerpc',
245 gcc_cfg
=['--disable-multilib', '--with-float=soft',
246 '--enable-secureplt'],
247 glibcs
=[{'variant': 'soft', 'cfg': ['--without-fp']}])
248 self
.add_config(arch
='powerpc64',
250 gcc_cfg
=['--disable-multilib', '--enable-secureplt'])
251 self
.add_config(arch
='powerpc64le',
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',
262 gcc_cfg
=['--disable-multilib', '--enable-secureplt'],
263 glibcs
=[{'variant': 'e500v1', 'cfg': ['--without-fp']}])
264 self
.add_config(arch
='s390x',
267 {'arch': 's390', 'ccopts': '-m31'}])
268 self
.add_config(arch
='sh3',
270 glibcs
=[{'ccopts': no_isolate
}])
271 self
.add_config(arch
='sh3eb',
273 glibcs
=[{'ccopts': no_isolate
}])
274 self
.add_config(arch
='sh4',
276 glibcs
=[{'ccopts': no_isolate
}])
277 self
.add_config(arch
='sh4eb',
279 glibcs
=[{'ccopts': no_isolate
}])
280 self
.add_config(arch
='sh4',
283 gcc_cfg
=['--without-fp'],
284 glibcs
=[{'variant': 'soft',
285 'cfg': ['--without-fp'],
286 'ccopts': no_isolate
}])
287 self
.add_config(arch
='sh4eb',
290 gcc_cfg
=['--without-fp'],
291 glibcs
=[{'variant': 'soft',
292 'cfg': ['--without-fp'],
293 'ccopts': no_isolate
}])
294 self
.add_config(arch
='sparc64',
298 'ccopts': '-m32 -mlong-double-128'}])
299 self
.add_config(arch
='tilegx',
302 {'variant': '32', 'ccopts': '-m32'}])
303 self
.add_config(arch
='tilegxbe',
306 {'variant': '32', 'ccopts': '-m32'}])
307 self
.add_config(arch
='tilepro',
309 self
.add_config(arch
='x86_64',
311 gcc_cfg
=['--with-multilib-list=m64,m32,mx32'],
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',
319 'ccopts': '-m32 -march=i686',
320 'cfg': ['--disable-multi-arch']},
322 'ccopts': '-m32 -march=i486'},
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
)
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
)
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."""
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
)
352 # glibc build as part of compiler build.
353 return os
.path
.join(self
.builddir
, action
, config
, component
,
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
)
377 elif action
== 'host-libraries':
379 print('error: configurations specified for host-libraries')
381 self
.build_host_libraries()
382 elif action
== 'compilers':
383 self
.build_compilers(configs
)
385 self
.build_glibcs(configs
)
390 def remove_dirs(*args
):
391 """Remove directories and their contents if they exist."""
393 shutil
.rmtree(dir, ignore_errors
=True)
396 def remove_recreate_dirs(*args
):
397 """Remove directories if they exist, and create them as empty."""
398 Context
.remove_dirs(*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
:
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'
433 ' printf "%s" "$word" | sed -e "s/\'/\'\\\\\\\\\'\'/"\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'
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'
453 ' if [ "$1" != "0" ]; then\n'
454 ' record_status FAIL\n'
457 'if [ "$prev_base" ] && ! grep -q "^PASS" "$prev_status"; then\n'
458 ' record_status UNRESOLVED\n'
460 'if [ "$dir" ]; then\n'
462 ' check_error "$?"\n'
464 'if [ "$path" ]; then\n'
465 ' PATH=$path:$PATH\n'
467 '"$@" < /dev/null >> "$this_log" 2>&1\n'
469 'record_status PASS\n')
470 with
open(self
.wrapper
, 'w') as f
:
471 f
.write(wrapper_text
)
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
)
478 'if ! [ -f tests.sum ]; then\n'
479 ' echo "No test summary available."\n'
484 ' echo "Contents of $1:"\n'
488 ' echo "End of contents of $1."\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'
498 with
open(self
.save_logs
, 'w') as f
:
499 f
.write(save_logs_text
)
500 os
.chmod(self
.save_logs
, mode_exec
)
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
,
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."""
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())
550 self
.configs
[c
].build()
552 def build_glibcs(self
, configs
):
553 """Build the glibcs."""
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())
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',
566 'glibc': 'vcs-mainline',
574 for k
in default_versions
.keys():
578 if k
in use_versions
:
579 print('error: multiple versions for %s' % k
)
585 print('error: unknown component in %s' % v
)
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
)
594 if v
.startswith('vcs-'):
595 self
.checkout_vcs(k
, v
[4:], update
)
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
602 if component
== 'binutils':
603 git_url
= 'git://sourceware.org/git/binutils-gdb.git'
604 if version
== 'mainline':
605 git_branch
= 'master'
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':
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'
623 git_branch
= 'release/%s/master' % version
624 self
.git_checkout(component
, git_url
, git_branch
, update
)
625 self
.fix_glibc_timestamps()
627 print('error: component %s coming from VCS' % component
)
630 def git_checkout(self
, component
, git_url
, git_branch
, update
):
631 """Check out a component from git."""
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)
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
):
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."""
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
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
)
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
:
682 subprocess
.run(['tar', '-C', self
.srcdir
, '-x', '-f', filename
],
684 os
.rename(os
.path
.join(self
.srcdir
, '%s-%s' % (component
, version
)),
685 self
.component_srcdir(component
))
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."""
698 self
.variant
= variant
700 self
.name
= '%s-%s' % (arch
, os_name
)
702 self
.name
= '%s-%s-%s' % (arch
, os_name
, variant
)
703 self
.triplet
= '%s-glibc-%s' % (arch
, os_name
)
707 self
.gcc_cfg
= gcc_cfg
708 if first_gcc_cfg
is None:
709 self
.first_gcc_cfg
= []
711 self
.first_gcc_cfg
= first_gcc_cfg
713 glibcs
= [{'variant': variant
}]
714 if extra_glibcs
is None:
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
)
731 """Generate commands to build this compiler."""
732 self
.ctx
.remove_recreate_dirs(self
.installdir
, self
.builddir
,
734 cmdlist
= CommandList('compilers-%s' % self
.name
, self
.ctx
.keep
)
735 cmdlist
.add_command('check-host-libraries',
737 os
.path
.join(self
.ctx
.host_libraries_installdir
,
739 cmdlist
.use_path(self
.bindir
)
740 self
.build_cross_tool(cmdlist
, 'binutils', 'binutils',
742 '--disable-libdecnumber',
743 '--disable-readline',
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
,
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
]
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',
792 'microblaze': 'microblaze',
795 'powerpc': 'powerpc',
803 if self
.arch
.startswith(k
):
804 linux_arch
= arch_map
[k
]
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
,
817 cmdlist
.cleanup_dir()
818 cmdlist
.pop_subdesc()
820 def build_gcc(self
, cmdlist
, bootstrap
):
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
]
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
840 cfg_opts
+= ['--enable-languages=c', '--disable-shared',
842 '--disable-libatomic',
843 '--disable-decimal-float',
848 '--disable-libquadmath',
849 '--without-headers', '--with-newlib',
850 '--with-glibc-version=%s' % self
.ctx
.glibc_version
852 cfg_opts
+= self
.first_gcc_cfg
855 cfg_opts
+= ['--enable-languages=c,c++', '--enable-shared',
857 self
.build_cross_tool(cmdlist
, 'gcc', tool_build
, cfg_opts
)
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
869 self
.arch
= compiler
.arch
873 self
.os
= compiler
.os
876 self
.variant
= variant
878 self
.name
= '%s-%s' % (self
.arch
, self
.os
)
880 self
.name
= '%s-%s-%s' % (self
.arch
, self
.os
, variant
)
881 self
.triplet
= '%s-glibc-%s' % (self
.arch
, self
.os
)
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
)
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',
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
,
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
914 srcdir
= self
.ctx
.component_srcdir('glibc')
916 builddir
= self
.ctx
.component_builddir('compilers',
917 self
.compiler
.name
, 'glibc',
919 installdir
= self
.compiler
.sysroot
920 srcdir_copy
= self
.ctx
.component_builddir('compilers',
925 builddir
= self
.ctx
.component_builddir('glibcs', self
.name
,
927 installdir
= self
.ctx
.glibc_installdir(self
.name
)
928 srcdir_copy
= self
.ctx
.component_builddir('glibcs', self
.name
,
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'),
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')]
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
,
964 cmdlist
.add_command('check', ['make', 'check'])
965 cmdlist
.add_command('save-logs', [self
.ctx
.save_logs
],
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."""
979 trans
= str.maketrans({' ': '-'})
980 self
.logbase
= '%03d-%s' % (num
, desc
.translate(trans
))
981 self
.command
= command
982 self
.always_run
= always_run
985 def shell_make_quote_string(s
):
986 """Given a string not containing a newline, quote it for use by the
989 if re
.fullmatch('[]+,./0-9@A-Z_a-z-]+', s
):
991 strans
= str.maketrans({"'": "'\\''"})
992 s
= "'%s'" % s
.translate(strans
)
993 mtrans
= str.maketrans({'$': '$$'})
994 return s
.translate(mtrans
)
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':
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."""
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."""
1030 def use_path(self
, path
):
1031 """Set a directory to be prepended to the PATH for subsequent
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
1039 self
.desc
.append(subdesc
)
1041 def pop_subdesc(self
):
1042 """Pop a subdescription from the list of descriptions."""
1045 def create_use_dir(self
, dir):
1046 """Remove and recreate a directory and use it for subsequent
1048 self
.add_command_dir('rm', None, ['rm', '-rf', dir])
1049 self
.add_command_dir('mkdir', None, ['mkdir', '-p', dir])
1052 def create_copy_dir(self
, src
, dest
):
1053 """Remove a directory and recreate it as a copy from the given
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
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).
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
)
1101 this_log
= os
.path
.join(logsdir
, c
.logbase
)
1102 if not c
.always_run
:
1103 prev_base
= c
.logbase
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
)
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',
1132 choices
=('checkout', 'host-libraries', 'compilers',
1134 parser
.add_argument('configs',
1135 help='Versions to check out or configurations to build',
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__':