| #! /usr/bin/env python |
| # Copyright 2020 Google, Inc. |
| # |
| # Redistribution and use in source and binary forms, with or without |
| # modification, are permitted provided that the following conditions are |
| # met: redistributions of source code must retain the above copyright |
| # notice, this list of conditions and the following disclaimer; |
| # redistributions in binary form must reproduce the above copyright |
| # notice, this list of conditions and the following disclaimer in the |
| # documentation and/or other materials provided with the distribution; |
| # neither the name of the copyright holders nor the names of its |
| # contributors may be used to endorse or promote products derived from |
| # this software without specific prior written permission. |
| # |
| # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| |
| import abc |
| import argparse |
| import glob |
| import multiprocessing |
| import os |
| import os.path |
| import pickle |
| import shutil |
| import six |
| import subprocess |
| import textwrap |
| |
| SETTINGS_FILE = '.build_cross_gcc.settings' |
| LOG_FILE = 'build_cross_gcc.log' |
| |
| all_settings = {} |
| all_steps = {} |
| |
| description_paragraphs = [ |
| ''' |
| This script helps automate building a gcc based cross compiler. |
| The process is broken down into a series of steps which can be |
| executed one at a time or in arbtitrary sequences. It's assumed that |
| you've already downloaded the following sources into the current |
| directory:''', |
| '', |
| '''1. binutils''', |
| '''2. gcc''', |
| '''3. glibc''', |
| '''4. linux kernel''', |
| '''5. gdb''', |
| '', |
| ''' |
| The entire process can be configured with a series of settings |
| which are stored in a config file called {settings_file}. These |
| settings can generally also be set from the command line, and at run |
| time using step 0 of the process. Many will set themselves to |
| reasonable defaults if no value was loaded from a previous |
| configuration or a saved settings file.''', |
| '', |
| ''' |
| Prebaked config options can be loaded in from an external file to |
| make it easier to build particular cross compilers without having to |
| mess with a lot of options.''' |
| '', |
| ''' |
| When settings are listed, any setting which has a value which has |
| failed validation or which hasn't been set and doesn't have a |
| reasonable default will be marked with a X in the far left hand |
| column. Settings will generally refuse to be set to invalid values, |
| unless they were like that by default and the user refused to correct |
| them.''', |
| '', |
| '''This script is based on the excellent how-to here:''', |
| '''https://preshing.com/20141119/how-to-build-a-gcc-cross-compiler/''', |
| '', |
| ''' |
| Please view that webpage for a detailed explanation of what this |
| script does.''' |
| ] |
| |
| def help_text_wrapper(text): |
| width = shutil.get_terminal_size().columns |
| text = textwrap.dedent(text) |
| text = text.strip() |
| return textwrap.fill(text, width=width) |
| |
| description = '\n'.join(list(map(help_text_wrapper, description_paragraphs))) |
| |
| argparser = argparse.ArgumentParser( |
| formatter_class=argparse.RawDescriptionHelpFormatter, |
| description=description) |
| |
| |
| # |
| # Some helper utilities. |
| # |
| |
| def confirm(prompt): |
| while True: |
| yn = input('{} (N/y): '.format(prompt)) |
| if yn == '': |
| yn = 'n' |
| if yn.lower() in ('y', 'Yes'): |
| return True |
| elif yn.lower() in ('n', 'No'): |
| return False |
| |
| |
| def setup_build_dir(subdir): |
| build_dir_base = BuildDirBase.setting() |
| target = Target.setting() |
| if not (build_dir_base.valid and target.valid): |
| return False |
| target_build_dir = os.path.join(build_dir_base.get(), target.get()) |
| build_dir = os.path.join(target_build_dir, 'build-{}'.format(subdir)) |
| if not os.path.isdir(build_dir): |
| os.makedirs(build_dir) |
| return build_dir |
| |
| def run_commands(working_dir, *cmds): |
| with open(LOG_FILE, 'a') as log: |
| print('In working directory {:s} (log in {:s}):'.format( |
| working_dir, LOG_FILE)) |
| for cmd in cmds: |
| print(textwrap.fill(cmd, initial_indent=' ', |
| subsequent_indent=' ', |
| width=shutil.get_terminal_size().columns)) |
| print('', file=log) |
| print(cmd, file=log) |
| print('', file=log) |
| if subprocess.call(cmd, shell=True, cwd=working_dir, |
| stdout=log, stderr=subprocess.STDOUT) != 0: |
| return False |
| return True |
| |
| |
| # |
| # Settings. |
| # |
| |
| class MetaSetting(type): |
| def __new__(mcls, name, bases, d): |
| cls = super(MetaSetting, mcls).__new__(mcls, name, bases, d) |
| key = d.get('key', None) |
| if key is not None: |
| assert('default' in d) |
| instance = cls() |
| instance.value = None |
| instance.valid = False |
| all_settings[key] = instance |
| return cls |
| |
| @six.add_metaclass(MetaSetting) |
| @six.add_metaclass(abc.ABCMeta) |
| class Setting(object): |
| key = None |
| |
| @abc.abstractmethod |
| def set(self, value): |
| 'Validate and set the setting to "value", and return if successful.' |
| self.value = value |
| self.valid = True |
| return True |
| |
| def set_default(self): |
| 'Set this setting to its default value, and return if successful.' |
| return self.set(self.default) |
| |
| def set_arg(self, value): |
| 'Set this setting to value if not None, and return if successful.' |
| if value: |
| return self.set(value) |
| else: |
| # Nothing happened, so nothing failed. |
| return True |
| |
| def get(self): |
| 'Return the value of this setting.' |
| return self.value |
| |
| @abc.abstractmethod |
| def describe(self): |
| 'Return a string describing this setting.' |
| return '' |
| |
| @abc.abstractmethod |
| def add_to_argparser(self, argparser): |
| 'Add command line options associated with this setting.' |
| |
| @abc.abstractmethod |
| def set_from_args(self, args): |
| 'Set this setting from the command line arguments, if requested.' |
| return True |
| |
| @classmethod |
| def setting(cls): |
| s = all_settings[cls.key] |
| if not s.valid: |
| print('"{}" is not valid.'.format(s.key)) |
| return s |
| |
| class DirectorySetting(Setting): |
| def set(self, value): |
| if not os.path.exists(value): |
| print('Path "{:s}" does not exist.'.format(value)) |
| elif not os.path.isdir(value): |
| print('Path "{:s}" is not a directory.'.format(value)) |
| else: |
| self.value = value |
| self.valid = True |
| return self.valid |
| |
| def set_default(self): |
| if not self.set(self.default): |
| if not os.path.exists(self.default): |
| if confirm('Create?'): |
| try: |
| os.mkdirs(value) |
| assert(self.set(self.default)) |
| except: |
| print('Failed to make directory') |
| self.valid = False |
| return False |
| else: |
| self.value = self.default |
| self.valid = False |
| return False |
| |
| class Prefix(DirectorySetting): |
| default = os.path.join(os.environ['HOME'], 'cross') |
| key = 'PREFIX' |
| |
| def describe(self): |
| return 'Path prefix to install to.' |
| |
| def add_to_argparser(self, parser): |
| parser.add_argument('--prefix', help=self.describe()) |
| |
| def set_from_args(self, args): |
| return self.set_arg(args.prefix) |
| |
| class BuildDirBase(DirectorySetting): |
| default = os.getcwd() |
| key = 'BUILD_DIR_BASE' |
| |
| def describe(self): |
| return 'Path prefix for build directory(ies).' |
| |
| def add_to_argparser(self, parser): |
| parser.add_argument('--build-dir-base', help=self.describe()) |
| |
| def set_from_args(self, args): |
| return self.set_arg(args.build_dir_base) |
| |
| class Target(Setting): |
| key = 'TARGET' |
| default = None |
| |
| def set_default(self): |
| self.value = '(not set)' |
| self.valid = False |
| return False |
| |
| def describe(self): |
| return 'Tuple for the target architecture.' |
| |
| def add_to_argparser(self, parser): |
| parser.add_argument('--target', help=self.describe()) |
| |
| def set_from_args(self, args): |
| return self.set_arg(args.target) |
| |
| class LinuxArch(Setting): |
| key = 'LINUX_ARCH' |
| default = None |
| |
| def set_default(self): |
| self.value = '(not set)' |
| self.valid = False |
| return False |
| |
| def describe(self): |
| return 'The arch directory for Linux headers.' |
| |
| def add_to_argparser(self, parser): |
| parser.add_argument('--linux-arch', help=self.describe()) |
| |
| def set_from_args(self, args): |
| return self.set_arg(args.linux_arch) |
| |
| class SourceDirSetting(Setting): |
| def set(self, value): |
| if os.path.isdir(value): |
| self.value = value |
| self.valid = True |
| return self.valid |
| |
| def set_default(self): |
| matches = list(filter(os.path.isdir, glob.glob(self.pattern))) |
| if len(matches) == 0: |
| self.valid = False |
| return False |
| if len(matches) > 1: |
| while True: |
| print() |
| print('Multple options for "{:s}":'.format(self.key)) |
| choices = list(enumerate(matches)) |
| for number, value in choices: |
| print('{:>5}: {:s}'.format(number, value)) |
| choice = input('Which one? ') |
| try: |
| choice = choices[int(choice)][1] |
| except: |
| print('Don\'t know what to do with "{:s}".'.format(choice)) |
| continue |
| return self.set(choice) |
| return self.set(matches[0]) |
| |
| def describe(self): |
| return 'Directory with the extracted {} source.'.format(self.project) |
| |
| class BinutilsSourceDir(SourceDirSetting): |
| key = 'BINUTILS_SRC_DIR' |
| default = None |
| pattern = 'binutils-*' |
| project = 'binutils' |
| |
| def add_to_argparser(self, parser): |
| parser.add_argument('--binutils-src', help=self.describe()) |
| |
| def set_from_args(self, args): |
| return self.set_arg(args.binutils_src) |
| |
| class GccSourceDir(SourceDirSetting): |
| key = 'GCC_SRC_DIR' |
| default = None |
| pattern = 'gcc-*' |
| project = 'gcc' |
| |
| def add_to_argparser(self, parser): |
| parser.add_argument('--gcc-src', help=self.describe()) |
| |
| def set_from_args(self, args): |
| return self.set_arg(args.gcc_src) |
| |
| class GlibcSourceDir(SourceDirSetting): |
| key = 'GLIBC_SRC_DIR' |
| default = None |
| pattern = 'glibc-*' |
| project = 'glibc' |
| |
| def add_to_argparser(self, parser): |
| parser.add_argument('--glibc-src', help=self.describe()) |
| |
| def set_from_args(self, args): |
| return self.set_arg(args.glibc_src) |
| |
| class LinuxSourceDir(SourceDirSetting): |
| key = 'LINUX_SRC_DIR' |
| default = None |
| pattern = 'linux-*' |
| project = 'linux' |
| |
| def add_to_argparser(self, parser): |
| parser.add_argument('--linux-src', help=self.describe()) |
| |
| def set_from_args(self, args): |
| return self.set_arg(args.linux_src) |
| |
| class GdbSourceDir(SourceDirSetting): |
| key = 'GDB_SRC_DIR' |
| default = None |
| pattern = 'gdb-*' |
| project = 'gdb' |
| |
| def add_to_argparser(self, parser): |
| parser.add_argument('--gdb-src', help=self.describe()) |
| |
| def set_from_args(self, args): |
| return self.set_arg(args.gdb_src) |
| |
| class Parallelism(Setting): |
| key = 'J' |
| default = None |
| |
| def set(self, value): |
| try: |
| value = int(value) |
| except: |
| print('Can\'t convert "{:s}" into an integer.'.format(value)) |
| if value < 0: |
| print('Parallelism can\'t be negative.') |
| return False |
| self.value = value |
| self.valid = True |
| return self.valid |
| |
| def set_default(self): |
| self.set(multiprocessing.cpu_count()) |
| |
| def describe(self): |
| return 'The level of parellism to request from "make".' |
| |
| def add_to_argparser(self, parser): |
| parser.add_argument('-j', help=self.describe()) |
| |
| def set_from_args(self, args): |
| return self.set_arg(args.j) |
| |
| |
| |
| # |
| # Steps of the build process. |
| # |
| |
| class MetaStep(type): |
| def __new__(mcls, name, bases, d): |
| cls = super(MetaStep, mcls).__new__(mcls, name, bases, d) |
| number = d.get('number', None) |
| if number is not None: |
| all_steps[number] = cls() |
| return cls |
| |
| @six.add_metaclass(MetaStep) |
| @six.add_metaclass(abc.ABCMeta) |
| class Step(object): |
| 'Steps to set up a cross compiling gcc.' |
| number = None |
| |
| @abc.abstractmethod |
| def run(self): |
| 'Execute this step.' |
| pass |
| |
| @abc.abstractmethod |
| def describe(self): |
| 'Return a string describing this step.' |
| return '' |
| |
| |
| class Configure(Step): |
| number = 0 |
| |
| def describe(self): |
| return 'Adjust settings.' |
| |
| def get_setting(self): |
| settings = list(enumerate(all_settings.items())) |
| all_keys = list(all_settings.keys()) |
| max_key_length = max([len(key) for key in all_keys]) |
| while True: |
| for number, (key, setting) in settings: |
| print('{}{:>4}: {:{key_len}s} - {:s}'.format( |
| ' ' if setting.valid else 'X', |
| number, key, setting.describe(), key_len=max_key_length)) |
| print(' {}'.format(setting.value)) |
| print() |
| key = input('Value to modify, or "done": ') |
| if key == "done": |
| save_settings() |
| return None |
| if key not in all_keys: |
| try: |
| key = settings[int(key)][1][0] |
| except: |
| print('Don\'t know what to do with "{:s}."'.format(key)) |
| continue |
| return all_settings[key] |
| |
| def run(self): |
| while True: |
| setting = self.get_setting() |
| if not setting: |
| return True |
| |
| new_value = input('New value ({:s}): '.format(setting.get())) |
| if new_value: |
| setting.set(new_value) |
| save_settings() |
| |
| print_settings() |
| return True |
| |
| class BuildBinutils(Step): |
| number = 1 |
| |
| def describe(self): |
| return 'Build binutils.' |
| |
| def run(self): |
| prefix = Prefix.setting() |
| target = Target.setting() |
| j = Parallelism.setting() |
| source_dir = BinutilsSourceDir.setting() |
| build_dir = setup_build_dir('binutils') |
| |
| if not all((prefix, target, j, source_dir, build_dir)): |
| return False |
| |
| prefix = prefix.get() |
| target = target.get() |
| j = j.get() |
| build_dir = os.path.abspath(build_dir) |
| source_dir = os.path.abspath(source_dir.get()) |
| |
| return run_commands(build_dir, |
| '{configure} --prefix={prefix} --target={target} ' |
| '--disable-multilib'.format( |
| configure=os.path.join(source_dir, 'configure'), |
| prefix=prefix, target=target), |
| 'make -j{j}'.format(j=j), |
| 'make install' |
| ) |
| |
| class InstallLinuxHeaders(Step): |
| number = 2 |
| |
| def describe(self): |
| return 'Install Linux headers.' |
| |
| def run(self): |
| source_dir = LinuxSourceDir.setting() |
| linux_arch = LinuxArch.setting() |
| prefix = Prefix.setting() |
| target = Target.setting() |
| |
| if not all((source_dir, linux_arch, prefix, target)): |
| return False |
| |
| source_dir = os.path.abspath(source_dir.get()) |
| linux_arch = linux_arch.get() |
| prefix = os.path.abspath(prefix.get()) |
| target = target.get() |
| |
| hdr_path = os.path.join(prefix, target) |
| |
| return run_commands(source_dir, |
| 'make ARCH={arch} INSTALL_HDR_PATH={hdr_path} ' |
| 'headers_install'.format(arch=linux_arch, hdr_path=hdr_path)) |
| |
| class Compilers(Step): |
| number = 3 |
| |
| def describe(self): |
| return 'Build C and C++ compilers.' |
| |
| def run(self): |
| prefix = Prefix.setting() |
| target = Target.setting() |
| j = Parallelism.setting() |
| source_dir = GccSourceDir.setting() |
| build_dir = setup_build_dir('gcc') |
| |
| if not all((prefix, target, j, source_dir, build_dir)): |
| return False |
| |
| prefix = prefix.get() |
| target = target.get() |
| j = j.get() |
| build_dir = os.path.abspath(build_dir) |
| source_dir = os.path.abspath(source_dir.get()) |
| |
| return run_commands(build_dir, |
| '{configure} --prefix={prefix} --target={target} ' |
| '--enable-languages=c,c++ --disable-multilib'.format( |
| configure=os.path.join(source_dir, 'configure'), |
| prefix=prefix, target=target), |
| 'make -j{j} all-gcc LIMITS_H_TEST=true'.format(j=j), |
| 'make install-gcc' |
| ) |
| |
| class CHeaders(Step): |
| number = 4 |
| |
| def describe(self): |
| return 'Standard C library headers and startup files.' |
| |
| def run(self): |
| prefix = Prefix.setting() |
| target = Target.setting() |
| j = Parallelism.setting() |
| source_dir = GlibcSourceDir.setting() |
| build_dir = setup_build_dir('glibc') |
| |
| if not all((prefix, target, j, source_dir, build_dir)): |
| return False |
| |
| prefix = prefix.get() |
| target = target.get() |
| j = j.get() |
| source_dir = os.path.abspath(source_dir.get()) |
| build_dir = os.path.abspath(build_dir) |
| |
| return run_commands(build_dir, |
| '{configure} --prefix={prefix} --build=$MACHTYPE ' |
| '--host={host} --target={target} --with-headers={hdr_path} ' |
| '--disable-multilib libc_cv_forced_unwind=yes'.format( |
| configure=os.path.join(source_dir, 'configure'), |
| prefix=os.path.join(prefix, target), |
| host=target, target=target, |
| hdr_path=os.path.join(prefix, target, 'include')), |
| 'make install-bootstrap-headers=yes install-headers', |
| 'make -j{j} csu/subdir_lib'.format(j=j), |
| 'install csu/crt1.o csu/crti.o csu/crtn.o {lib_path}'.format( |
| lib_path=os.path.join(prefix, target, 'lib')), |
| '{target}-gcc -nostdlib -nostartfiles -shared -x c /dev/null ' |
| '-o {libc_so}'.format(target=target, |
| libc_so=os.path.join(prefix, target, 'lib', 'libc.so')), |
| 'touch {stubs_h}'.format(stubs_h=os.path.join( |
| prefix, target, 'include', 'gnu', 'stubs.h')) |
| ) |
| |
| class CompilerSupportLib(Step): |
| number = 5 |
| |
| def describe(self): |
| return 'Build the compiler support library.' |
| |
| def run(self): |
| j = Parallelism.setting() |
| build_dir = setup_build_dir('gcc') |
| |
| if not all((j, build_dir)): |
| return False |
| |
| j = j.get() |
| build_dir = os.path.abspath(build_dir) |
| |
| return run_commands(build_dir, |
| 'make -j{j} all-target-libgcc'.format(j=j), |
| 'make install-target-libgcc' |
| ) |
| |
| class StandardCLib(Step): |
| number = 6 |
| |
| def describe(self): |
| return 'Install the standard C library.' |
| |
| def run(self): |
| j = Parallelism.setting() |
| build_dir = setup_build_dir('glibc') |
| |
| if not all((j, build_dir)): |
| return False |
| |
| j = j.get() |
| build_dir = os.path.abspath(build_dir) |
| |
| return run_commands(build_dir, |
| 'make -j{j}'.format(j=j), |
| 'make install', |
| ) |
| |
| class BuildGdb(Step): |
| number = 7 |
| |
| def describe(self): |
| return 'Build GDB.' |
| |
| def run(self): |
| prefix = Prefix.setting() |
| target = Target.setting() |
| j = Parallelism.setting() |
| source_dir = GdbSourceDir.setting() |
| build_dir = setup_build_dir('gdb') |
| |
| if not all((prefix, target, j, source_dir, build_dir)): |
| return False |
| |
| prefix = prefix.get() |
| target = target.get() |
| j = j.get() |
| source_dir = os.path.abspath(source_dir.get()) |
| build_dir = os.path.abspath(build_dir) |
| |
| return run_commands(build_dir, |
| '{configure} --prefix={prefix} --target={target} ' |
| '$MACHTYPE'.format(prefix=prefix, target=target, |
| configure=os.path.join(source_dir, 'configure')), |
| 'make -j{j}'.format(j=j), |
| 'make install' |
| ) |
| |
| class StandardCxxLib(Step): |
| number = 8 |
| |
| def describe(self): |
| return 'Install the standard C++ library.' |
| |
| def run(self): |
| j = Parallelism.setting() |
| build_dir = setup_build_dir('gcc') |
| |
| if not all((j, build_dir)): |
| return False |
| |
| j = j.get() |
| build_dir = os.path.abspath(build_dir) |
| |
| return run_commands(build_dir, |
| 'make -j{j}'.format(j=j), |
| 'make install' |
| ) |
| |
| |
| # |
| # The engine that makes it all go. |
| # |
| |
| def get_steps(): |
| while True: |
| print() |
| print('Steps:') |
| for _, step in sorted(all_steps.items()): |
| print('{:>5} {:s}'.format( |
| '{:d}:'.format(step.number), step.describe())) |
| print() |
| steps = input('Comma separated list of steps, or ' |
| '"exit", or "all" (all): ') |
| if not steps: |
| steps = 'all' |
| if steps == 'exit': |
| return [] |
| if steps == 'all': |
| keys = list([str(key) for key in all_steps.keys()]) |
| steps = ','.join(keys) |
| try: |
| return list([all_steps[int(i)] for i in steps.split(",")]) |
| except: |
| print('Don\'t know what to do with "{:s}"'.format(steps)) |
| |
| def print_settings(): |
| print() |
| print('Settings:') |
| for setting in all_settings.values(): |
| print('{} {} = {}'.format( |
| ' ' if setting.valid else 'X', setting.key, setting.value)) |
| |
| def save_settings(): |
| settings = {} |
| for setting in all_settings.values(): |
| if setting.valid: |
| settings[setting.key] = setting.get() |
| with open(SETTINGS_FILE, 'wb') as settings_file: |
| pickle.dump(settings, settings_file) |
| |
| def load_settings(): |
| if os.path.exists(SETTINGS_FILE): |
| with open(SETTINGS_FILE, 'rb') as settings_file: |
| settings = pickle.load(settings_file) |
| else: |
| settings = {} |
| |
| for setting in all_settings.values(): |
| if setting.key in settings: |
| setting.set(settings[setting.key]) |
| |
| def load_settings_file(path): |
| with open(path, 'r') as settings: |
| for line in settings.readlines(): |
| if not line: |
| continue |
| try: |
| key, val = line.split('=') |
| except: |
| print('Malformated line "{}" in settings file "{}".'.format( |
| line, path)) |
| return False |
| key = key.strip() |
| val = val.strip() |
| if key not in all_settings: |
| print('Unknown setting "{}" found in settings ' |
| 'file "{}".'.format(key, path)) |
| return False |
| setting = all_settings[key] |
| if not setting.set(val): |
| print('Failed to set "{}" to "{}" from ' |
| 'settings file "{}".'.format(key, val, path)) |
| return False |
| return True |
| |
| |
| |
| argparser.add_argument('--settings-file', |
| help='A file with name=value settings to load.') |
| |
| def main(): |
| # Install command line options for each setting. |
| for setting in all_settings.values(): |
| setting.add_to_argparser(argparser) |
| |
| args = argparser.parse_args() |
| |
| # Load settings from the last time we ran. Lowest priority. |
| load_settings() |
| |
| # If requested, read in a settings file. Medium priority. |
| if args.settings_file: |
| if not load_settings_file(args.settings_file): |
| return |
| |
| # Set settings based on command line options. Highest priority. |
| for setting in all_settings.values(): |
| setting.set_from_args(args) |
| |
| # If a setting is still not valid, try setting it to its default. |
| for setting in all_settings.values(): |
| if not setting.valid: |
| setting.set_default() |
| |
| # Print out the resulting settings. |
| print_settings() |
| |
| while True: |
| steps = get_steps() |
| if not steps: |
| return |
| for step in steps: |
| print() |
| print('Step {:d}: {:s}'.format(step.number, step.describe())) |
| print() |
| if not step.run(): |
| print() |
| print('Step failed, aborting.') |
| break |
| |
| if __name__ == "__main__": |
| main() |