summaryrefslogtreecommitdiff
path: root/configure
diff options
context:
space:
mode:
Diffstat (limited to 'configure')
-rwxr-xr-xconfigure330
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)