diff options
Diffstat (limited to 'configure')
-rwxr-xr-x | configure | 330 |
1 files changed, 330 insertions, 0 deletions
diff --git a/configure b/configure new file mode 100755 index 00000000..e1d3e176 --- /dev/null +++ b/configure @@ -0,0 +1,330 @@ +#!/usr/bin/env python +# Copyright (C) 2016 Codethink Limited +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program. If not, see <http://www.gnu.org/licenses/>. + +'''Baserock Definitions configuration script. + +This generates various .morph files from corresponding .morph.in files. The +.morph.in files should use Jinja2 syntax to use the configuration parameters. + +There are predefined configurations you can use, for example 'x86_64-generic': + + ./configure x86_64-generic + +Run `./configure --list-configs` for a full list of these. + +You can also define your own configuration, but the name must not clash with +any of the built-in ones. For example: + + ./configure jetson-be --arch=armv7b + +The generated .morph files are named like `name.{{CONFIG}}.morph`; for example +`build-system.x86_64-generic.morph`. This means you can have as many +configurations set up in parallel as you want, as long as the names don't +clash. + +The 'configure' tool saves your configurations to a hidden file, and will +point out when you change an existing configuration. + +For information on the Jinja2 templating language, see: + + <http://jinja.pocoo.org/docs/dev/templates/> + +To add a new config setting, add it to the PARAMETERS dictionary, and possibly +add a new named configuration that uses it in the NAMED_CONFIGS list. + +''' + +import jinja2 + +import argparse +import json +import os +import sys +import tempfile + + +DEFAULT_ARCH = os.uname()[-1] + + +# All known configuration parameters. Names should be uppercase with _ to +# separate words. Each one is converted to a commandline option, these will +# be lowercase and use - to separate words. Properties are the same as for +# the 'argparse' module. +PARAMETERS = { + 'ARCH': dict( + type=str, default=DEFAULT_ARCH, + help="set architecture (default: %(default)s)", + ), + 'BSP': dict( + type=str, default=os.uname()[-1], + help="set 'board support' stratum (default: %(default)s)", + ), + 'ENABLE_VAGRANT_BASEBOX': dict( + type=bool, default=False, + help="configure systems that support it to work as Vagrant baseboxes", + ), +} + + +def compare_configs(a, b): + '''Compare two configuration dicts. + + Unset values are filled in from the defaults in PARAMETERS. + + ''' + for key in set(a.keys()).union(set(b.keys())): + default = PARAMETERS[key].get('default') + if a.get(key, default) != b.get(key, default): + return False + return True + + +# A list of predefined configurations. Users aren't allowed to use these names +# for other configurations. The idea of this is that users can report a failure +# in a specific config ('jetson', for example) and we will know exactly what +# the generated .morph file they are building should contain. +# +# Bear in mind that the Baserock reference systems are examples. Configurations +# listed here probably worked at some point, but they may not work now without +NAMED_CONFIGS = { + 'armv7b-highbank': dict( + ARCH='armv7b', BSP='armv7b-highbank'), + 'armv7b-vexpress-tc2': dict( + ARCH='armv7b', BSP='armv7b-vexpress-tc2'), + 'armv7l-altera-socfpga-devkit': dict( + ARCH='armv7l', BSP='armv7l-altera-socfpga-devkit'), + 'armv7l-versatile': dict( + ARCH='armv7l', BSP='armv7-versatile'), + 'armv7lhf-chroot': dict( + ARCH='armv7lhf', BSP=None), + 'armv7lhf-highbank': dict( + ARCH='armv7lhf', BSP='armv7-highbank'), + 'armv8b64-generic': dict( + ARCH='armv8b64', BSP='armv8b64-generic'), + 'armv8l64-generic': dict( + ARCH='armv8l64', BSP='armv8l64-generic'), + 'jetson': dict( + ARCH='armv7lhf', BSP='jetson'), + 'openbmc-aspeed': dict( + ARCH='armv5l', BSP='armv5l-openbmc-aspeed'), + 'ppc64-chroot': dict( + ARCH='ppc64', BSP=None), + 'ppc64-generic': dict( + ARCH='ppc64', BSP='ppc64-generic'), + 'wandboard': dict( + ARCH='armv7lhf', BSP='wandboard'), + 'x86_32-chroot': dict( + ARCH='x86_32', BSP=None), + 'x86_32-generic': dict( + ARCH='x86_32', BSP='x86_32-generic'), + 'x86_64-chroot': dict( + ARCH='x86_64', BSP=None), + 'x86_64-generic': dict( + ARCH='x86_64', BSP='x86_64-generic'), + 'x86_64-vagrant': dict( + ARCH='x86_64', BSP='x86_64-vagrant', ENABLE_VAGRANT_BASEBOX=True), +} + + +def argument_parser(): + '''Generate a commandline argument parser for `configure`. + + The values from PARAMETERS are turned into proper commandline arguments. + + ''' + parser = argparse.ArgumentParser( + description="Baserock Definitions configure script") + + parser.add_argument( + 'name', nargs='?', + help="name of this configuration") + + class list_all_configs_and_exit(argparse.Action): + def __call__(*args, **kwargs): + sys.stdout.write('Built-in configurations:\n ') + sys.stdout.write('\n '.join(sorted(name for name in NAMED_CONFIGS))) + sys.stdout.write('\n') + sys.exit(0) + + parser.add_argument( + '--list-configs', '-l', nargs=0, action=list_all_configs_and_exit, + help="list all named configurations") + + parser.add_argument( + '--output-dir', '-o', + help="directory to output generated files to (default: current dir)", + default='.', metavar='DIR') + + def config_var_to_arg(name): + return '--' + name.lower().replace('_', '-') + + for name, settings in PARAMETERS.iteritems(): + if settings['type'] == bool: + # argparse handles true/false parameters in a 'special' way. + argparse_settings = settings.copy() + del argparse_settings['type'] + if settings['default'] == True: + argparse_settings['action'] = 'store_false' + elif settings['default'] == False: + argparse_settings['action'] = 'store_true' + parser.add_argument( + config_var_to_arg(name), **argparse_settings) + else: + parser.add_argument( + config_var_to_arg(name), **settings) + + return parser + + +class SaveFile(file): + '''Save files with a temporary name and rename when they're ready.''' + + def __init__(self, filename, *args, **kwargs): + self.real_filename = filename + dirname = os.path.dirname(filename) + fd, self._savefile_tempname = tempfile.mkstemp(dir=dirname) + os.close(fd) + file.__init__(self, self._savefile_tempname, *args, **kwargs) + + def abort(self): + os.remove(self._savefile_tempname) + return file.close(self) + + def close(self): + ret = file.close(self) + os.rename(self._savefile_tempname, self.real_filename) + return ret + + +def status(message, *args): + print(message % args) + + +def get_jinja_vars(args): + '''Work out the actual configuration values we should be using. + + If a named configuration was specified, we use that. + + ''' + if args.name and args.name in NAMED_CONFIGS: + jinja_vars = NAMED_CONFIGS[args.name] + + # We don't allow customising these configurations. + for param_name, param_settings in PARAMETERS.items(): + value = getattr(args, param_name.lower()) + expected_value = param_settings.get('default') + if value != expected_value: + raise RuntimeError( + "Please specify a custom name for this configuration, or " + "use the expected value for %s: %s" % ( + param_name, NAMED_CONFIGS[args.name][param_name])) + if param_name not in jinja_vars: + jinja_vars[param_name] = expected_value + + jinja_vars['CONFIG'] = args.name + else: + jinja_vars = {} + + for param_name, param_settings in PARAMETERS.items(): + value = getattr(args, param_name.lower()) + jinja_vars[param_name] = value + + if args.name: + jinja_vars['CONFIG'] = args.name + else: + # If this config matches a built-in one, use that name + for name, config in NAMED_CONFIGS.items(): + if jinja_vars == config: + jinja_vars['CONFIG'] = name + break + else: + raise RuntimeError( + "Please specify a name for this configuration, or use one of " + "the built-in configurations (use `-l` to get a list).") + + return jinja_vars + + +def generate(environment, input_path, output_dir, jinja_vars): + '''Generate a .morph file from a .morph.in file, using Jinja2.''' + base_name = os.path.basename(input_path[:-len('.morph.in')]) + output_name = base_name + '.' + jinja_vars['CONFIG'] + '.morph' + output_path = os.path.join(output_dir, output_name) + + try: + input_file = environment.get_template(input_path) + except jinja2.exceptions.TemplateSyntaxError as e: + raise RuntimeError("%s:%i: %s" % (e.filename, e.lineno, e)) + + with SaveFile(output_path, 'w') as f: + f.write('# Generated from %s\n' % input_path) + f.write(input_file.render(**jinja_vars) + '\n') + f.write('# Generated from %s\n' % input_path) + + +def main(): + args = argument_parser().parse_args() + + output_dir = args.output_dir or '.' + + jinja_vars = get_jinja_vars(args) + config_name = jinja_vars['CONFIG'] + + if not os.path.isdir(output_dir): + os.mkdir(output_dir) + + configs_file = os.path.join(output_dir, '.configurations') + try: + with open(configs_file, 'r') as f: + existing_configs = json.load(f) + except (IOError, ValueError) as e: + existing_configs = dict() + + loader = jinja2.FileSystemLoader('.') + environment = jinja2.Environment(loader=loader) + + if config_name in existing_configs: + status("Regenerating '%s' configuration:" % config_name) + existing_config = existing_configs[config_name] + else: + status("Generating '%s' configuration:" % config_name) + existing_configs = None + + for var, value in jinja_vars.items(): + if var != 'CONFIG': + if var in existing_config and existing_config[var] != value: + status(" %s: %s (was: %s)" % (var, value, + existing_config[var])) + else: + status(" %s: %s" % (var, value)) + + for dirpath, dirnames, filenames in os.walk('.'): + for f in filenames: + if f.endswith('.morph.in'): + input_path = os.path.join(dirpath, f) + + generate (environment, input_path, + os.path.join(output_dir, dirpath), jinja_vars) + + existing_configs[config_name] = jinja_vars + with SaveFile(configs_file, 'w') as f: + json.dump(existing_configs, f) + + +try: + main() +except RuntimeError as e: + sys.stderr.write("%s\n" % e) + sys.exit(1) |