2 # Copyright 2020 Google, Inc.
4 # Redistribution and use in source and binary forms, with or without
5 # modification, are permitted provided that the following conditions are
6 # met: redistributions of source code must retain the above copyright
7 # notice, this list of conditions and the following disclaimer;
8 # redistributions in binary form must reproduce the above copyright
9 # notice, this list of conditions and the following disclaimer in the
10 # documentation and/or other materials provided with the distribution;
11 # neither the name of the copyright holders nor the names of its
12 # contributors may be used to endorse or promote products derived from
13 # this software without specific prior written permission.
15 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
16 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
17 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
18 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
19 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
21 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 import multiprocessing
39 SETTINGS_FILE
= '.build_cross_gcc.settings'
40 LOG_FILE
= 'build_cross_gcc.log'
45 description_paragraphs
= [
47 This script helps automate building a gcc based cross compiler.
48 The process is broken down into a series of steps which can be
49 executed one at a time or in arbtitrary sequences. It's assumed that
50 you've already downloaded the following sources into the current
56 '''4. linux kernel''',
60 The entire process can be configured with a series of settings
61 which are stored in a config file called {settings_file}. These
62 settings can generally also be set from the command line, and at run
63 time using step 0 of the process. Many will set themselves to
64 reasonable defaults if no value was loaded from a previous
65 configuration or a saved settings file.''',
68 Prebaked config options can be loaded in from an external file to
69 make it easier to build particular cross compilers without having to
70 mess with a lot of options.'''
73 When settings are listed, any setting which has a value which has
74 failed validation or which hasn't been set and doesn't have a
75 reasonable default will be marked with a X in the far left hand
76 column. Settings will generally refuse to be set to invalid values,
77 unless they were like that by default and the user refused to correct
80 '''This script is based on the excellent how-to here:''',
81 '''https://preshing.com/20141119/how-to-build-a-gcc-cross-compiler/''',
84 Please view that webpage for a detailed explanation of what this
88 def help_text_wrapper(text
):
89 width
= shutil
.get_terminal_size().columns
90 text
= textwrap
.dedent(text
)
92 return textwrap
.fill(text
, width
=width
)
94 description
= '\n'.join(list(map(help_text_wrapper
, description_paragraphs
)))
96 argparser
= argparse
.ArgumentParser(
97 formatter_class
=argparse
.RawDescriptionHelpFormatter
,
98 description
=description
)
102 # Some helper utilities.
107 yn
= input('{} (N/y): '.format(prompt
))
110 if yn
.lower() in ('y', 'Yes'):
112 elif yn
.lower() in ('n', 'No'):
116 def setup_build_dir(subdir
):
117 build_dir_base
= BuildDirBase
.setting()
118 target
= Target
.setting()
119 if not (build_dir_base
.valid
and target
.valid
):
121 target_build_dir
= os
.path
.join(build_dir_base
.get(), target
.get())
122 build_dir
= os
.path
.join(target_build_dir
, 'build-{}'.format(subdir
))
123 if not os
.path
.isdir(build_dir
):
124 os
.makedirs(build_dir
)
127 def run_commands(working_dir
, *cmds
):
128 with
open(LOG_FILE
, 'a') as log
:
129 print('In working directory {:s} (log in {:s}):'.format(
130 working_dir
, LOG_FILE
))
132 print(textwrap
.fill(cmd
, initial_indent
=' ',
133 subsequent_indent
=' ',
134 width
=shutil
.get_terminal_size().columns
))
138 if subprocess
.call(cmd
, shell
=True, cwd
=working_dir
,
139 stdout
=log
, stderr
=subprocess
.STDOUT
) != 0:
148 class MetaSetting(type):
149 def __new__(mcls
, name
, bases
, d
):
150 cls
= super(MetaSetting
, mcls
).__new
__(mcls
, name
, bases
, d
)
151 key
= d
.get('key', None)
153 assert('default' in d
)
155 instance
.value
= None
156 instance
.valid
= False
157 all_settings
[key
] = instance
160 @six.add_metaclass(MetaSetting
)
161 @six.add_metaclass(abc
.ABCMeta
)
162 class Setting(object):
166 def set(self
, value
):
167 'Validate and set the setting to "value", and return if successful.'
172 def set_default(self
):
173 'Set this setting to its default value, and return if successful.'
174 return self
.set(self
.default
)
176 def set_arg(self
, value
):
177 'Set this setting to value if not None, and return if successful.'
179 return self
.set(value
)
181 # Nothing happened, so nothing failed.
185 'Return the value of this setting.'
190 'Return a string describing this setting.'
194 def add_to_argparser(self
, argparser
):
195 'Add command line options associated with this setting.'
198 def set_from_args(self
, args
):
199 'Set this setting from the command line arguments, if requested.'
204 s
= all_settings
[cls
.key
]
206 print('"{}" is not valid.'.format(s
.key
))
209 class DirectorySetting(Setting
):
210 def set(self
, value
):
211 if not os
.path
.exists(value
):
212 print('Path "{:s}" does not exist.'.format(value
))
213 elif not os
.path
.isdir(value
):
214 print('Path "{:s}" is not a directory.'.format(value
))
220 def set_default(self
):
221 if not self
.set(self
.default
):
222 if not os
.path
.exists(self
.default
):
223 if confirm('Create?'):
226 assert(self
.set(self
.default
))
228 print('Failed to make directory')
232 self
.value
= self
.default
236 class Prefix(DirectorySetting
):
237 default
= os
.path
.join(os
.environ
['HOME'], 'cross')
241 return 'Path prefix to install to.'
243 def add_to_argparser(self
, parser
):
244 parser
.add_argument('--prefix', help=self
.describe())
246 def set_from_args(self
, args
):
247 return self
.set_arg(args
.prefix
)
249 class BuildDirBase(DirectorySetting
):
250 default
= os
.getcwd()
251 key
= 'BUILD_DIR_BASE'
254 return 'Path prefix for build directory(ies).'
256 def add_to_argparser(self
, parser
):
257 parser
.add_argument('--build-dir-base', help=self
.describe())
259 def set_from_args(self
, args
):
260 return self
.set_arg(args
.build_dir_base
)
262 class Target(Setting
):
266 def set_default(self
):
267 self
.value
= '(not set)'
272 return 'Tuple for the target architecture.'
274 def add_to_argparser(self
, parser
):
275 parser
.add_argument('--target', help=self
.describe())
277 def set_from_args(self
, args
):
278 return self
.set_arg(args
.target
)
280 class LinuxArch(Setting
):
284 def set_default(self
):
285 self
.value
= '(not set)'
290 return 'The arch directory for Linux headers.'
292 def add_to_argparser(self
, parser
):
293 parser
.add_argument('--linux-arch', help=self
.describe())
295 def set_from_args(self
, args
):
296 return self
.set_arg(args
.linux_arch
)
298 class SourceDirSetting(Setting
):
299 def set(self
, value
):
300 if os
.path
.isdir(value
):
305 def set_default(self
):
306 matches
= list(filter(os
.path
.isdir
, glob
.glob(self
.pattern
)))
307 if len(matches
) == 0:
313 print('Multple options for "{:s}":'.format(self
.key
))
314 choices
= list(enumerate(matches
))
315 for number
, value
in choices
:
316 print('{:>5}: {:s}'.format(number
, value
))
317 choice
= input('Which one? ')
319 choice
= choices
[int(choice
)][1]
321 print('Don\'t know what to do with "{:s}".'.format(choice
))
323 return self
.set(choice
)
324 return self
.set(matches
[0])
327 return 'Directory with the extracted {} source.'.format(self
.project
)
329 class BinutilsSourceDir(SourceDirSetting
):
330 key
= 'BINUTILS_SRC_DIR'
332 pattern
= 'binutils-*'
335 def add_to_argparser(self
, parser
):
336 parser
.add_argument('--binutils-src', help=self
.describe())
338 def set_from_args(self
, args
):
339 return self
.set_arg(args
.binutils_src
)
341 class GccSourceDir(SourceDirSetting
):
347 def add_to_argparser(self
, parser
):
348 parser
.add_argument('--gcc-src', help=self
.describe())
350 def set_from_args(self
, args
):
351 return self
.set_arg(args
.gcc_src
)
353 class GlibcSourceDir(SourceDirSetting
):
354 key
= 'GLIBC_SRC_DIR'
359 def add_to_argparser(self
, parser
):
360 parser
.add_argument('--glibc-src', help=self
.describe())
362 def set_from_args(self
, args
):
363 return self
.set_arg(args
.glibc_src
)
365 class LinuxSourceDir(SourceDirSetting
):
366 key
= 'LINUX_SRC_DIR'
371 def add_to_argparser(self
, parser
):
372 parser
.add_argument('--linux-src', help=self
.describe())
374 def set_from_args(self
, args
):
375 return self
.set_arg(args
.linux_src
)
377 class GdbSourceDir(SourceDirSetting
):
383 def add_to_argparser(self
, parser
):
384 parser
.add_argument('--gdb-src', help=self
.describe())
386 def set_from_args(self
, args
):
387 return self
.set_arg(args
.gdb_src
)
389 class Parallelism(Setting
):
393 def set(self
, value
):
397 print('Can\'t convert "{:s}" into an integer.'.format(value
))
399 print('Parallelism can\'t be negative.')
405 def set_default(self
):
406 self
.set(multiprocessing
.cpu_count())
409 return 'The level of parellism to request from "make".'
411 def add_to_argparser(self
, parser
):
412 parser
.add_argument('-j', help=self
.describe())
414 def set_from_args(self
, args
):
415 return self
.set_arg(args
.j
)
420 # Steps of the build process.
423 class MetaStep(type):
424 def __new__(mcls
, name
, bases
, d
):
425 cls
= super(MetaStep
, mcls
).__new
__(mcls
, name
, bases
, d
)
426 number
= d
.get('number', None)
427 if number
is not None:
428 all_steps
[number
] = cls()
431 @six.add_metaclass(MetaStep
)
432 @six.add_metaclass(abc
.ABCMeta
)
434 'Steps to set up a cross compiling gcc.'
444 'Return a string describing this step.'
448 class Configure(Step
):
452 return 'Adjust settings.'
454 def get_setting(self
):
455 settings
= list(enumerate(all_settings
.items()))
456 all_keys
= list(all_settings
.keys())
457 max_key_length
= max([len(key
) for key
in all_keys
])
459 for number
, (key
, setting
) in settings
:
460 print('{}{:>4}: {:{key_len}s} - {:s}'.format(
461 ' ' if setting
.valid
else 'X',
462 number
, key
, setting
.describe(), key_len
=max_key_length
))
463 print(' {}'.format(setting
.value
))
465 key
= input('Value to modify, or "done": ')
469 if key
not in all_keys
:
471 key
= settings
[int(key
)][1][0]
473 print('Don\'t know what to do with "{:s}."'.format(key
))
475 return all_settings
[key
]
479 setting
= self
.get_setting()
483 new_value
= input('New value ({:s}): '.format(setting
.get()))
485 setting
.set(new_value
)
491 class BuildBinutils(Step
):
495 return 'Build binutils.'
498 prefix
= Prefix
.setting()
499 target
= Target
.setting()
500 j
= Parallelism
.setting()
501 source_dir
= BinutilsSourceDir
.setting()
502 build_dir
= setup_build_dir('binutils')
504 if not all((prefix
, target
, j
, source_dir
, build_dir
)):
507 prefix
= prefix
.get()
508 target
= target
.get()
510 build_dir
= os
.path
.abspath(build_dir
)
511 source_dir
= os
.path
.abspath(source_dir
.get())
513 return run_commands(build_dir
,
514 '{configure} --prefix={prefix} --target={target} '
515 '--disable-multilib'.format(
516 configure
=os
.path
.join(source_dir
, 'configure'),
517 prefix
=prefix
, target
=target
),
518 'make -j{j}'.format(j
=j
),
522 class InstallLinuxHeaders(Step
):
526 return 'Install Linux headers.'
529 source_dir
= LinuxSourceDir
.setting()
530 linux_arch
= LinuxArch
.setting()
531 prefix
= Prefix
.setting()
532 target
= Target
.setting()
534 if not all((source_dir
, linux_arch
, prefix
, target
)):
537 source_dir
= os
.path
.abspath(source_dir
.get())
538 linux_arch
= linux_arch
.get()
539 prefix
= os
.path
.abspath(prefix
.get())
540 target
= target
.get()
542 hdr_path
= os
.path
.join(prefix
, target
)
544 return run_commands(source_dir
,
545 'make ARCH={arch} INSTALL_HDR_PATH={hdr_path} '
546 'headers_install'.format(arch
=linux_arch
, hdr_path
=hdr_path
))
548 class Compilers(Step
):
552 return 'Build C and C++ compilers.'
555 prefix
= Prefix
.setting()
556 target
= Target
.setting()
557 j
= Parallelism
.setting()
558 source_dir
= GccSourceDir
.setting()
559 build_dir
= setup_build_dir('gcc')
561 if not all((prefix
, target
, j
, source_dir
, build_dir
)):
564 prefix
= prefix
.get()
565 target
= target
.get()
567 build_dir
= os
.path
.abspath(build_dir
)
568 source_dir
= os
.path
.abspath(source_dir
.get())
570 return run_commands(build_dir
,
571 '{configure} --prefix={prefix} --target={target} '
572 '--enable-languages=c,c++ --disable-multilib'.format(
573 configure
=os
.path
.join(source_dir
, 'configure'),
574 prefix
=prefix
, target
=target
),
575 'make -j{j} all-gcc LIMITS_H_TEST=true'.format(j
=j
),
579 class CHeaders(Step
):
583 return 'Standard C library headers and startup files.'
586 prefix
= Prefix
.setting()
587 target
= Target
.setting()
588 j
= Parallelism
.setting()
589 source_dir
= GlibcSourceDir
.setting()
590 build_dir
= setup_build_dir('glibc')
592 if not all((prefix
, target
, j
, source_dir
, build_dir
)):
595 prefix
= prefix
.get()
596 target
= target
.get()
598 source_dir
= os
.path
.abspath(source_dir
.get())
599 build_dir
= os
.path
.abspath(build_dir
)
601 return run_commands(build_dir
,
602 '{configure} --prefix={prefix} --build=$MACHTYPE '
603 '--host={host} --target={target} --with-headers={hdr_path} '
604 '--disable-multilib libc_cv_forced_unwind=yes'.format(
605 configure
=os
.path
.join(source_dir
, 'configure'),
606 prefix
=os
.path
.join(prefix
, target
),
607 host
=target
, target
=target
,
608 hdr_path
=os
.path
.join(prefix
, target
, 'include')),
609 'make install-bootstrap-headers=yes install-headers',
610 'make -j{j} csu/subdir_lib'.format(j
=j
),
611 'install csu/crt1.o csu/crti.o csu/crtn.o {lib_path}'.format(
612 lib_path
=os
.path
.join(prefix
, target
, 'lib')),
613 '{target}-gcc -nostdlib -nostartfiles -shared -x c /dev/null '
614 '-o {libc_so}'.format(target
=target
,
615 libc_so
=os
.path
.join(prefix
, target
, 'lib', 'libc.so')),
616 'touch {stubs_h}'.format(stubs_h
=os
.path
.join(
617 prefix
, target
, 'include', 'gnu', 'stubs.h'))
620 class CompilerSupportLib(Step
):
624 return 'Build the compiler support library.'
627 j
= Parallelism
.setting()
628 build_dir
= setup_build_dir('gcc')
630 if not all((j
, build_dir
)):
634 build_dir
= os
.path
.abspath(build_dir
)
636 return run_commands(build_dir
,
637 'make -j{j} all-target-libgcc'.format(j
=j
),
638 'make install-target-libgcc'
641 class StandardCLib(Step
):
645 return 'Install the standard C library.'
648 j
= Parallelism
.setting()
649 build_dir
= setup_build_dir('glibc')
651 if not all((j
, build_dir
)):
655 build_dir
= os
.path
.abspath(build_dir
)
657 return run_commands(build_dir
,
658 'make -j{j}'.format(j
=j
),
662 class BuildGdb(Step
):
669 prefix
= Prefix
.setting()
670 target
= Target
.setting()
671 j
= Parallelism
.setting()
672 source_dir
= GdbSourceDir
.setting()
673 build_dir
= setup_build_dir('gdb')
675 if not all((prefix
, target
, j
, source_dir
, build_dir
)):
678 prefix
= prefix
.get()
679 target
= target
.get()
681 source_dir
= os
.path
.abspath(source_dir
.get())
682 build_dir
= os
.path
.abspath(build_dir
)
684 return run_commands(build_dir
,
685 '{configure} --prefix={prefix} --target={target} '
686 '$MACHTYPE'.format(prefix
=prefix
, target
=target
,
687 configure
=os
.path
.join(source_dir
, 'configure')),
688 'make -j{j}'.format(j
=j
),
692 class StandardCxxLib(Step
):
696 return 'Install the standard C++ library.'
699 j
= Parallelism
.setting()
700 build_dir
= setup_build_dir('gcc')
702 if not all((j
, build_dir
)):
706 build_dir
= os
.path
.abspath(build_dir
)
708 return run_commands(build_dir
,
709 'make -j{j}'.format(j
=j
),
715 # The engine that makes it all go.
722 for _
, step
in sorted(all_steps
.items()):
723 print('{:>5} {:s}'.format(
724 '{:d}:'.format(step
.number
), step
.describe()))
726 steps
= input('Comma separated list of steps, or '
727 '"exit", or "all" (all): ')
733 keys
= list([str(key
) for key
in all_steps
.keys()])
734 steps
= ','.join(keys
)
736 return list([all_steps
[int(i
)] for i
in steps
.split(",")])
738 print('Don\'t know what to do with "{:s}"'.format(steps
))
740 def print_settings():
743 for setting
in all_settings
.values():
744 print('{} {} = {}'.format(
745 ' ' if setting
.valid
else 'X', setting
.key
, setting
.value
))
749 for setting
in all_settings
.values():
751 settings
[setting
.key
] = setting
.get()
752 with
open(SETTINGS_FILE
, 'wb') as settings_file
:
753 pickle
.dump(settings
, settings_file
)
756 if os
.path
.exists(SETTINGS_FILE
):
757 with
open(SETTINGS_FILE
, 'rb') as settings_file
:
758 settings
= pickle
.load(settings_file
)
762 for setting
in all_settings
.values():
763 if setting
.key
in settings
:
764 setting
.set(settings
[setting
.key
])
766 def load_settings_file(path
):
767 with
open(path
, 'r') as settings
:
768 for line
in settings
.readlines():
772 key
, val
= line
.split('=')
774 print('Malformated line "{}" in settings file "{}".'.format(
779 if key
not in all_settings
:
780 print('Unknown setting "{}" found in settings '
781 'file "{}".'.format(key
, path
))
783 setting
= all_settings
[key
]
784 if not setting
.set(val
):
785 print('Failed to set "{}" to "{}" from '
786 'settings file "{}".'.format(key
, val
, path
))
792 argparser
.add_argument('--settings-file',
793 help='A file with name=value settings to load.')
796 # Install command line options for each setting.
797 for setting
in all_settings
.values():
798 setting
.add_to_argparser(argparser
)
800 args
= argparser
.parse_args()
802 # Load settings from the last time we ran. Lowest priority.
805 # If requested, read in a settings file. Medium priority.
806 if args
.settings_file
:
807 if not load_settings_file(args
.settings_file
):
810 # Set settings based on command line options. Highest priority.
811 for setting
in all_settings
.values():
812 setting
.set_from_args(args
)
814 # If a setting is still not valid, try setting it to its default.
815 for setting
in all_settings
.values():
816 if not setting
.valid
:
817 setting
.set_default()
819 # Print out the resulting settings.
828 print('Step {:d}: {:s}'.format(step
.number
, step
.describe()))
832 print('Step failed, aborting.')
835 if __name__
== "__main__":