Prettify error messages for invalid component names
[matrix.git] / matrix / matrix.py
index f534f20..4bf796a 100644 (file)
@@ -4,35 +4,37 @@
 #          Kalle Vahlman <kalle.vahlman@movial.fi>
 #          Tuomas Kulve <tuomas.kulve@movial.fi>
 
+import optparse
 import os
 import sys
 from sets import Set as set
 
 import cache
 import components
-import config
 import git
 import log
+from config import config
+from config import parse as config_parse
 from build import build, build_only
 
 Error = RuntimeError
 
-def main():
-       config.parse('config.local')
+def merge_config(name, value):
+       if isinstance(value, list):
+               value = getattr(config, name, []) + value
+       setattr(config, name, value)
 
-       command, params, targets = parse_args(sys.argv)
+def main(version):
+       command, targets, options = parse_args(version)
 
-       config.parse('config')
-       config.parse('boards')
-       config.parse('components')
+       for name in ('debug', 'initial_config_dirs', 'pull_config_dirs'):
+               if name in options:
+                       merge_config(name, options[name])
 
-       if 'global-cache' in config.flags:
-               config.cache_dir = config.global_cache_dir
+       config_parse('main', True)
 
-       if not os.path.exists(config.cache_dir):
-               os.makedirs(config.cache_dir)
-
-       all_targets = components.init()
+       for name in options:
+               merge_config(name, options[name])
 
        for i in xrange(len(targets)):
                name = targets[i]
@@ -40,116 +42,151 @@ def main():
                        name = name.split(os.path.sep)[1]
                        targets[i] = name
 
-               if name not in config.components:
-                       raise Error('Component "%s" does not exist' % name)
+       cache.init()
+       components.init(targets)
 
        if not targets:
-               targets = all_targets
-
-       if command == 'install':
-               build(targets)
-       elif command == 'install-only':
-               build_only(targets)
-       elif command == 'clone':
-               clone(targets)
-       elif command == 'clean':
-               clean(targets)
-       elif command == 'rebase':
-               rebase(targets)
-       elif command == 'pull':
-               pull(targets)
-       elif command == 'changes':
-               changes(targets)
-       elif command == 'source-dist':
-               source_dist(targets)
-       else:
-               raise Error('Invalid command: ' + command)
-
-def help(file, args):
+               targets = components.by_name.keys()
+               targets.sort()
+
+       command_funcs = {
+               'install':      build,
+               'install-only': build_only,
+               'meta':         meta,
+               'clone':        clone,
+               'clean':        clean,
+               'update':       update,
+               'rebase':       rebase,
+               'update-rebase':update_rebase,
+               'pull':         pull,
+               'changes':      changes,
+               'source-dist':  source_dist,
+       }
+
+       func = command_funcs.get(command)
+       if not func:
+               raise Error('Invalid command: "%s"' % command)
+
+       func(targets)
+
+def print_help(file, parser):
+       parser.print_help(file=file)
        print >>file, '''
-Copyright (C) 2006-2008 Movial Oy
-
-Usage: %(progname)s [<options>] <command> [<params>] [<components>]
-
-If no components are specified, all of them will be targeted.  All component
-metadata will be downloaded regardless of the specified command and components.
-
-Options:
-       -v              Verbose output.
-       -d              Debug output.
-       -r URL          Specify the root for component git repos to clone from.
-                       If this option is not given, the roots specified in the
-                       components file will be used.  This option may be
-                       specified multiple times.
-       -f              Build (or be quiet about) components with dirty files.
-       -k              Build as much as possible after an error.
-       -j N            Execute N build jobs in parallel.
-       -mj N           Run component builds with make -j N.
-       -h, --help      Print this help text.
-
-Commands and parameters:
-       clone           Download the components' git repositories.
-       install         Download, build and install the components.
-       install-only    Download, build and install the specified components
-                       without dependencies.
-       clean           Remove all non-tracked files from the component git
-                       repository directories.
-       rebase          Update repositories from server by rebasing.
-       pull            Update repositories from server by merging.
-       changes         Show commits which are not on the server.
-       source-dist     Download and package the component sources.
-''' % {'progname': args[0]}
-
-def parse_args(args):
-       def parse_jobs(arg):
-               jobs = int(arg)
-               if jobs <= 0:
-                       raise Error('Please specify a valid number of jobs')
-               return jobs
-
-       command = None
-       params = {}
-       targets = []
-
-       i = 1
-       while i < len(args):
-               if args[i].startswith('-'):
-                       if not command:
-                               if args[i] == '-v':
-                                       config.verbose = True
-                               elif args[i] == '-d':
-                                       config.debug = True
-                               elif args[i] == '-r':
-                                       i += 1
-                                       config.roots.append(args[i])
-                               elif args[i] == '-f':
-                                       config.force = True
-                               elif args[i] == '-k':
-                                       config.keep_going = True
-                               elif args[i] == '-j':
-                                       i += 1
-                                       config.jobs = parse_jobs(args[i])
-                               elif args[i] == '-mj':
-                                       i += 1
-                                       config.make_jobs = parse_jobs(args[i])
-                               elif args[i] in ('-h', '--help'):
-                                       help(sys.stdout, args)
-                                       sys.exit(0)
-                               else:
-                                       raise Error('Bad option: ' + args[i])
-                       else:
-                               raise Error('Command takes no parameters')
-               elif not command:
-                       command = args[i]
-               else:
-                       targets.append(args[i])
-               i += 1
-
-       if not command:
-               help(sys.stderr, args)
+commands:
+  meta          download the components' meta repositories
+  clone         download the components' git repositories
+  install       download, build and install the components
+  install-only  download, build and install the specified components without
+                dependencies
+  clean         remove all non-tracked files from the component git repository
+                directories
+  update        download remote repositories from server
+  rebase        rebase local repositories against remote repositories (remotes
+                won't be updated first)
+  update-rebase update remote repositories and rebase local repositories
+  pull          update remote repositories and merge with local repositories
+  changes       show commits which are not on the server
+  source-dist   download and package the component sources
+'''
+
+def parse_args(matrix_version):
+       options = {}
+
+       def help(option, opt, value, parser):
+               print_help(sys.stdout, parser)
+               sys.exit(0)
+
+       def version(option, opt, value, parser):
+               print matrix_version
+               sys.exit(0)
+
+       def set_flag(option, opt, value, parser):
+               options[option.dest] = True
+
+       def set_jobs(option, opt, value, parser):
+               if value < 1:
+                       raise optparse.OptionValueError(
+                               'option %s: invalid job count: %d' % \
+                               (opt, value))
+               options[option.dest] = value
+
+       def append(option, opt, value, parser):
+               l = options.get(option.dest, [])
+               if not l:
+                       options[option.dest] = l
+               l.append(value)
+
+       parser = optparse.OptionParser(
+               usage='%prog [<options>] <command> [<components>]',
+               add_help_option=False)
+
+       parser.add_option(
+               '-h', '--help',
+               help='show this help message and exit',
+               action='callback', callback=help)
+
+       parser.add_option(
+               '--version',
+               help='print Matrix version',
+               action='callback', callback=version)
+
+       parser.add_option(
+               '-v', '--verbose',
+               help='show build output',
+               dest='verbose', action='callback', callback=set_flag)
+
+       parser.add_option(
+               '-d', '--debug',
+               help='debug output',
+               dest='debug', action='callback', callback=set_flag)
+
+       parser.add_option(
+               '-c', '--config-dir', metavar='DIR',
+               help='add a directory for finding config files (may be set ' \
+                    'multiple times)',
+               dest='initial_config_dirs', action='callback', callback=append,
+               type='string')
+
+       parser.add_option(
+               '-p', '--pull-config-dirs',
+               help='git-pull config dirs which are backed by Git repositories',
+               dest='pull_config_dirs', action='callback', callback=set_flag)
+
+       parser.add_option(
+               '-r', '--root', metavar='URL',
+               help='add a location for finding git repos when cloning ' \
+                    '(may be set multiple times)',
+               dest='roots', action='callback', callback=append,
+               type='string')
+
+       parser.add_option(
+               '-f', '--force',
+               help='build (or be quiet about) components with dirty files',
+               dest='force', action='callback', callback=set_flag)
+
+       parser.add_option(
+               '-k', '--keep-going',
+               help='build as much as possible after an error',
+               dest='keep_going', action='callback', callback=set_flag)
+
+       parser.add_option(
+               '-j', '--jobs',
+               help='execute up to N build jobs in parallel', metavar='N',
+               dest='jobs', action='callback', callback=set_jobs,
+               type='int')
+
+       parser.add_option(
+               '-J', '--make-jobs',
+               help='run component builds with make -j N', metavar='N',
+               dest='make_jobs', action='callback', callback=set_jobs,
+               type='int')
+
+       values, args = parser.parse_args(values=None)
+       if not args:
+               print_help(sys.stderr, parser)
                sys.exit(1)
 
-       return command, params, targets
+       return args[0], args[1:], options
 
 def execute(args):
        if config.debug:
@@ -161,9 +198,21 @@ def execute(args):
 def remove_tree(path):
        execute(['rm', '-rf', path])
 
+def find_component(name):
+       try:
+               return components.by_name[name]
+       except KeyError:
+               raise Error("Component " + name + " not found");
+
+def meta(targets):
+       for name in targets:
+               c = find_component(name)
+               if not c.meta.exists():
+                       c.meta.clone()
+       
 def clone(targets):
        for name in targets:
-               c = config.components[name]
+               c = find_component(name)
                clone_component(c)
 
 def clone_component(c, overwrite=False):
@@ -189,7 +238,7 @@ def clean(targets):
        changed = False
 
        for name in targets:
-               c = config.components[name]
+               c = find_component(name)
                c.source.clean()
                cache.remove(c)
 
@@ -203,7 +252,7 @@ def for_each_repository(func, targets=None):
        ret = None
 
        for name in targets:
-               c = config.components[name]
+               c = find_component(name)
                if c.source.exists():
                        value = func(c.source)
                        if value:
@@ -214,9 +263,19 @@ def for_each_repository(func, targets=None):
 
        return ret
 
+def update(targets):
+       for_each_repository(lambda repo: repo.update(), targets)
+
 def rebase(targets):
        for_each_repository(lambda repo: repo.rebase(), targets)
 
+def update_rebase(targets):
+       def action(repo):
+               repo.update()
+               repo.rebase()
+
+       for_each_repository(action, targets)
+
 def pull(targets):
        for_each_repository(lambda repo: repo.pull(), targets)
 
@@ -224,7 +283,7 @@ def changes(targets):
        changed = False
 
        for name in targets:
-               c = config.components[name]
+               c = find_component(name)
 
                if c.source.exists():
                        if check_changes(c.source):
@@ -257,7 +316,7 @@ def source_dist(targets):
                os.makedirs(location)
 
        for name in targets:
-               c = config.components[name]
+               c = find_component(name)
                dist_changes(c, location)
                dist_sources(c, location)