Prettify error messages for invalid component names
[matrix.git] / matrix / matrix.py
index 5ef7b3f..4bf796a 100644 (file)
 #          Kalle Vahlman <kalle.vahlman@movial.fi>
 #          Tuomas Kulve <tuomas.kulve@movial.fi>
 
-import glob
+import optparse
 import os
-import re
-import signal
 import sys
-import tarfile
 from sets import Set as set
 
 import cache
-import config
+import components
 import git
-from rootfs import RootFS
+import log
+from config import config
+from config import parse as config_parse
+from build import build, build_only
 
 Error = RuntimeError
 
-error_log = []
+def merge_config(name, value):
+       if isinstance(value, list):
+               value = getattr(config, name, []) + value
+       setattr(config, name, value)
 
-def log_error(msg):
-       error_log.append(msg)
+def main(version):
+       command, targets, options = parse_args(version)
 
-def main():
-       try:
-               try:
-                       logged_main()
-               except:
-                       if not error_log:
-                               print >>sys.stderr
-                       raise
-       finally:
-               if error_log:
-                       print >>sys.stderr
-                       for msg in error_log:
-                               print >>sys.stderr, msg
-
-def logged_main():
-       parse_config('config.local')
-
-       command, params, targets = parse_args(sys.argv)
+       for name in ('debug', 'initial_config_dirs', 'pull_config_dirs'):
+               if name in options:
+                       merge_config(name, options[name])
 
-       parse_config('config')
-       parse_config('boards')
-       parse_config('components')
+       config_parse('main', True)
 
-       if 'global-cache' in config.flags:
-               config.cache_dir = config.global_cache_dir
+       for name in options:
+               merge_config(name, options[name])
 
-       if not os.path.exists(config.cache_dir):
-               os.makedirs(config.cache_dir)
+       for i in xrange(len(targets)):
+               name = targets[i]
+               if name.startswith('src' + os.path.sep):
+                       name = name.split(os.path.sep)[1]
+                       targets[i] = name
 
-       all_targets = update_components()
-
-       for name in targets:
-               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_components(targets, **params)
-       elif command == 'clone':
-               clone_components(targets)
-       elif command == 'clean':
-               clean_components(targets)
-       elif command == 'rebase':
-               rebase_components(targets)
-       elif command == 'pull':
-               pull_components(targets)
-       elif command == 'source-dist':
-               source_dist_components(targets)
-       elif command == 'rootfs':
-               build_rootfs(**params)
-       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 components with dirty files.
-       -h, --help      Print this help text.
-
-Commands and parameters:
-       clone           Download the components' git repositories.
-       install         Download, build and install the components.
-               -j N            Execute N build jobs in parallel.
-               -mj N           Run component builds with make -j N.
-       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.
-       source-dist     Download and package the component sources.
-       rootfs          Build a rootfs from the current target installation.
-               -n              Do not clean env.faked and stripped directory.
-               -r              Only generate a stripped rootfs.
-               -j              Only generate a jffs2 image
-               -d              Only generate a rootstrap (development rootfs).
-''' % {'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] in ('-h', '--help'):
-                                       help(sys.stdout, args)
-                                       sys.exit(0)
-                               else:
-                                       raise Error('Bad option: ' + args[i])
-                       elif command == 'install':
-                               if args[i] == '-j':
-                                       i += 1
-                                       params['build_jobs'] = parse_jobs(args[i])
-                               elif args[i] == '-mj':
-                                       i += 1
-                                       params['make_jobs'] = parse_jobs(args[i])
-                               else:
-                                       raise Error('Bad parameter: ' + args[i])
-                       elif command == 'rootfs':
-                               if args[i] == '-n':
-                                       i += 1
-                                       params['clean'] = False
-                               elif args[i] == '-r':
-                                       i += 1
-                                       params['rootfs_only'] = True
-                               elif args[i] == '-j':
-                                       i += 1
-                                       params['jffs2_only'] = True
-                               elif args[i] == '-d':
-                                       i += 1
-                                       params['devrootfs_only'] = True
-                       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
-
-def parse_config(path):
-       if os.path.isabs(path):
-               path = os.path.join(config.top_dir, path)
-
-       if os.path.exists(path):
-               print 'Reading', path
-               execfile(path, config.__dict__, config.__dict__)
-
-def update_components():
-       targets = []
-       packages = {}
-
-       for c in config.components.itervalues():
-               update_component_url(c)
-               clone_metadata(c)
-               update_component_packages(c, targets, packages)
-               c.active_depends = []
-
-       update_component_depends(packages)
-
-       return targets
-
-def update_component_url(c):
-       update_repository_url(c.repo)
-       update_repository_url(c.meta)
-
-def update_repository_url(repo):
-       if git.contains_database(repo.path):
-               url = git.getvar(repo.path, 'remote.origin.url')
-               if url:
-                       repo.active_url = url
-                       if config.debug:
-                               print 'Using', repo.active_url
-                       return
-
-       for root in config.roots:
-               for suffix in ('.git', ''):
-                       url = '%s/%s%s' % (root, repo.name, suffix)
-
-                       if config.debug:
-                               print 'Trying', url
-
-                       if not git.peek_remote(url, quiet=True):
-                               continue
-
-                       repo.active_url = url
-                       if config.debug:
-                               print 'Found', repo.active_url
-                       return
-
-       raise Error('Failed to locate repository: ' + repo.name)
-
-def update_component_depends(packages):
-       for pkg in packages.itervalues():
-               for spec in pkg.depends.split():
-                       depname = Depend(spec).name
-                       deppkg = packages.get(depname)
-
-                       if not deppkg:
-                               log_error('Package %s depends on ' \
-                                         'non-existent package %s', \
-                                         pkg.name, depname)
-                               continue
-
-                       if deppkg.component == pkg.component:
-                               continue
-
-                       pkg.component.active_depends.append(deppkg.component)
-
-       fail = False
-       for pkg in packages.itervalues():
-               for spec in pkg.depends.split():
-                       if not Depend(spec).check(packages):
-                               fail = True
-                               log_error('Dependency %s failed for %s' % \
-                                         (spec, pkg.name))
-
-               for spec in pkg.conflicts.split():
-                       if Depend(spec).check(packages):
-                               fail = True
-                               log_error('Package %s conflicts with %s' % \
-                                         (pkg.name, spec))
-
-       if fail:
-               raise Error('Invalid component tree')
-
-class Depend(object):
-       regex = re.compile(r'([@]?)([^\s:]+)[:]?([<>=]*)([^\s:]*)[:]?(.*)')
-
-       def __init__(self, spec):
-               match = self.regex.match(spec)
-               if not match:
-                       raise Error('Bad dependency specification: ' + spec)
-
-               self.build, self.name, self.tag_op, self.tag, flags \
-                       = match.groups()
-               self.flags = flags.split()
-
-       def check(self, packages):
-               # TODO: check version and flags
-               return self.name in packages
+       return args[0], args[1:], options
 
 def execute(args):
        if config.debug:
@@ -292,449 +198,142 @@ def execute(args):
 def remove_tree(path):
        execute(['rm', '-rf', path])
 
-def clone_components(targets):
-       if not targets:
-               targets = config.components.keys()
+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 = config.components[name]
+               c = find_component(name)
+               if not c.meta.exists():
+                       c.meta.clone()
+       
+def clone(targets):
+       for name in targets:
+               c = find_component(name)
                clone_component(c)
 
 def clone_component(c, overwrite=False):
-       have_repo = git.contains_database(c.repo.path)
-       have_meta = git.contains_database(c.meta.path)
+       have_source = c.source.exists()
+       have_meta = c.meta.exists()
 
-       if not overwrite and have_repo and have_meta:
+       if not overwrite and have_source and have_meta:
                return
 
-       if overwrite and os.path.exists(c.repo.path):
-               print 'Removing', c.repo.path
+       if overwrite and os.path.exists(c.source.path):
+               print 'Removing', c
 
-               remove_tree(c.repo.path)
-               have_repo = False
+               remove_tree(c.source.path)
+               have_source = False
                have_meta = False
 
-       if not have_repo:
-               clone_repository(c.repo)
-               git.exclude(c.repo.path, 'meta')
-
+       if not have_source:
+               c.source.clone()
        if not have_meta:
-               clone_repository(c.meta)
-
-def clone_metadata(c):
-       if not git.contains_database(c.meta.path):
-               clone_repository(c.meta)
-
-def clone_repository(repo):
-       print 'Cloning', repo.path
-
-       if os.path.exists(repo.path):
-               tmp = os.path.join(repo.path, 'tmp')
-               git.clone(tmp, repo.active_url, checkout=False)
-               try:
-                       tmpdb = os.path.join(tmp, '.git')
-                       repodb = os.path.join(repo.path, '.git')
-                       if config.debug:
-                               print 'Renaming "%s" as "%s"' % (tmpdb, repodb)
-                       os.rename(tmpdb, repodb)
-               finally:
-                       os.rmdir(tmp)
-               git.checkout(repo.path)
-       else:
-               git.clone(repo.path, repo.active_url, checkout=True)
-
-def update_component_packages(c, targets, packages):
-       c.active_packages = {}
-
-       for path in glob.glob(os.path.join(c.meta.path, '*.package')):
-               name = os.path.basename(path)[:-8]
-
-               pkg = parse_package(name, c, path)
-               if not pkg:
-                       continue
-
-               c.active_packages[name] = pkg
-               packages[name] = pkg
-
-               if config.debug:
-                       print 'Component', c.name, 'provides', name
-
-       if c.active_packages:
-               targets.append(c.name)
-       elif config.debug:
-               print 'Component', c.name, 'does not provide any packages'
-
-class Package(object):
-       def __init__(self, name, component):
-               self.name = name
-               self.component = component
-
-               self.depends = []
-               self.conflicts = []
-               self.architectures = None
-
-def parse_package(name, component, path):
-       pkg = Package(name, component)
-       execfile(path, pkg.__dict__, pkg.__dict__)
-
-       if pkg.architectures:
-               arch = config.boards[config.board].arch
-               if arch not in pkg.architectures:
-                       return None
-
-       return pkg
-
-def build_components(targets, build_jobs=1, make_jobs=1):
-       selected = set([config.components[i] for i in targets])
-
-       depends = None
-       while depends is None or depends:
-               depends = set()
-
-               for c in selected:
-                       for dep in c.active_depends:
-                               if dep not in selected:
-                                       depends.add(dep)
-
-               selected |= depends
-
-       components = list(selected)
-       components.sort()
-
-       if config.debug:
-               print 'Building components:'
-               for c in components:
-                       print '\t' + c.name
-
-       builder = Builder(components, build_jobs, make_jobs)
-       builder.run()
-
-class Builder(object):
-       def __init__(self, components, max_jobs, max_make_jobs):
-               self.max_jobs = max_jobs
-               self.max_make_jobs = max_make_jobs
-
-               self.jobs = {}
-
-               self.wait_build = set(components)
-               self.wait_install = []
-               self.in_install = None
-
-               self.progress_now = 0
-               self.progress_total = len(components) * 2
-
-               self.error = False
-
-       def run(self):
-               try:
-                       while self.run_iteration():
-                               pass
-               except:
-                       for pid in self.jobs:
-                               try:
-                                       os.kill(pid, signal.SIGTERM)
-                               except OSError, e:
-                                       log_error('kill: %s' % e)
-
-                               c = self.jobs[pid]
-                               c.active_state = 'killed'
-
-                       while self.jobs:
-                               self.wait()
-
-                       raise
-
-               if self.error:
-                       raise Error()
-
-       def run_iteration(self):
-               if self.can_start_install():
-                       c = self.wait_install.pop(0)
-                       self.start_install(c)
-                       self.in_install = c
-
-               while self.can_start_build():
-                       found = False
-
-                       for c in self.wait_build:
-                               if self.is_buildable(c):
-                                       found = True
+               c.meta.clone()
 
-                                       if not self.try_cache(c):
-                                               self.start_build(c)
+def clean(targets):
+       changed = False
 
-                                       self.wait_build.remove(c)
-                                       break
-
-                       if not found:
-                               break
-
-               if self.jobs:
-                       self.wait()
-                       return True
-               else:
-                       if self.wait_build and not self.error:
-                               raise Error('Circular dependencies?')
-                       return False
-
-       def progress(self, increment=1):
-               self.progress_now += increment
-
-       def message(self, arg1, arg2=None):
-               if arg2 is None:
-                       message = arg1
-               else:
-                       message = '%-10s %s' % (arg1, arg2)
-
-               progress = '%3d%%' % \
-                          (self.progress_now *100 / self.progress_total)
-
-               print progress, message
-
-       def is_buildable(self, c):
-               if c.active_state:
-                       if config.debug:
-                               self.message('Component %s is in state: %s' % \
-                                       (c.name, c.active_state))
-                       return False
-
-               for dep in c.active_depends:
-                       if config.components[dep.name].active_state != 'done':
-                               if config.debug:
-                                       self.message('Component %s depends ' \
-                                               'on uninstalled %s' % \
-                                               (c.name, dep.name))
-                               return False
-
-               return True
-
-       def try_cache(self, c):
-               if not cache.contains(c):
-                       return False
-
-               c.active_state = 'done'
-               self.progress(2)
-
-               if config.debug:
-                       self.message('Cached', c.repo.path)
-
-               return True
-
-       def can_start_build(self):
-               return self.can_start_job()
-
-       def can_start_install(self):
-               return not self.in_install and self.wait_install and \
-                      self.can_start_job()
-
-       def can_start_job(self):
-               return not self.error and len(self.jobs) < self.max_jobs
-
-       def start_build(self, c):
-               self.message('Building', c.repo.path)
-               self.start_job(c, 'build_matrix_component', 'in-build')
-
-       def start_install(self, c):
-               self.message('Installing', c.repo.path)
-               self.start_job(c, 'install_matrix_component', 'in-install')
-
-       def start_job(self, c, make_target, state):
-               pid = spawn_make(c, self.max_make_jobs, make_target)
-               self.jobs[pid] = c
-               c.active_state = state
-
-       def wait(self):
-               pid, status = os.wait()
-
-               if status:
-                       self.error = True
-
-               c = self.jobs[pid]
-               del self.jobs[pid]
-
-               if c.active_state == 'in-build':
-                       if status:
-                               log_error('Failed to build %s' % c.name)
-                               return
-
-                       self.progress()
-                       if config.debug:
-                               self.message('Built', c.repo.path)
+       for name in targets:
+               c = find_component(name)
+               c.source.clean()
+               cache.remove(c)
 
-                       c.active_state = 'wait-install'
-                       self.wait_install.append(c)
+               if not config.force:
+                       changed = check_dirty(c)
 
-               elif c.active_state == 'in-install':
-                       assert c == self.in_install
-
-                       if status:
-                               log_error('Failed to install %s' % c.name)
-                               return
-
-                       self.progress()
-                       self.message('Finished', c.repo.path)
+       if changed:
+               raise Error()
 
-                       c.active_state = 'done'
-                       self.in_install = None
-                       cache.update(c)
+def for_each_repository(func, targets=None):
+       ret = None
 
-               elif c.active_state != 'killed':
-                       raise Error('Unexpected state: %s' % c.active_state)
+       for name in targets:
+               c = find_component(name)
+               if c.source.exists():
+                       value = func(c.source)
+                       if value:
+                               ret = value
+               value = func(c.meta)
+               if value:
+                       ret = value
 
-def spawn_make(c, jobs, target):
-       board = config.boards[config.board]
+       return ret
 
-       make = os.getenv('MAKE', 'make')
-       makefile = os.path.join(config.script_dir, 'matrix.mak')
-       workdir = os.path.join(config.top_dir, 'src', c.name)
-       args = [make, '--no-print-directory', '-f', makefile, '-C', workdir,
-               '-j', str(jobs), target,
-               'MATRIX_TOPDIR='    + os.path.abspath(config.top_dir),
-               'MATRIX_SCRIPTDIR=' + os.path.abspath(config.script_dir),
-               'MATRIX_COMPONENT=' + c.name,
-               'MATRIX_ARCH='      + board.arch,
-               'MATRIX_GCC_MARCH=' + board.gcc_march,
-               'MATRIX_GCC_MCPU='  + board.gcc_mcpu,
-               'MATRIX_GCC_MFPU='  + board.gcc_mfpu,
-               'MATRIX_GCC_OPTIONS=' + board.gcc_options,
-               'MATRIX_GNU_HOST='  + board.gnu_host,
-               'MATRIX_LIBC='      + config.libc]
+def update(targets):
+       for_each_repository(lambda repo: repo.update(), targets)
 
-       for flag in config.flags:
-               args.append(flag + '=1')
+def rebase(targets):
+       for_each_repository(lambda repo: repo.rebase(), targets)
 
-       if config.verbose:
-               args.append('MATRIX_VERBOSE=1')
+def update_rebase(targets):
+       def action(repo):
+               repo.update()
+               repo.rebase()
 
-       if config.debug:
-               print 'Executing:', ' '.join(args)
+       for_each_repository(action, targets)
 
-       return os.spawnvp(os.P_NOWAIT, args[0], args)
+def pull(targets):
+       for_each_repository(lambda repo: repo.pull(), targets)
 
-def clean_components(targets):
-       if not targets:
-               targets = config.components.keys()
+def changes(targets):
+       changed = False
 
        for name in targets:
-               c = config.components[name]
-               print 'Cleaning', c.repo.path
-
-               cache.remove(c)
-
-               files = git.ls_files(c.repo.path, ['-o'])
-               paths = [os.path.join(c.repo.path, i) for i in files]
-               paths.sort()
-               paths.reverse()
-
-               for path in paths:
-                       if git.contains_database(path):
-                               continue
-
-                       if config.debug:
-                               print 'Removing', path
+               c = find_component(name)
 
-                       if os.path.islink(path) or not os.path.isdir(path):
-                               os.remove(path)
-                       else:
-                               remove_tree(path)
+               if c.source.exists():
+                       if check_changes(c.source):
+                               changed = True
 
-               for repo in (c.repo, c.meta):
-                       files = git.ls_files(repo.path, ['-m', '-d'])
-                       if files:
-                               log_error('Dirty files left in %s' % repo.path)
+               if check_changes(c.meta):
+                       changed = True
 
-def for_each_repository(func, targets=None):
-       if not targets:
-               targets = config.components.keys()
-
-       for name in targets:
-               c = config.components[name]
-               if git.contains_database(c.repo.path):
-                       func(c.repo)
-               func(c.meta)
-
-def rebase_components(targets):
-       for_each_repository(rebase_repository, targets)
+               if not config.force and check_dirty(c):
+                       changed = True
 
-def pull_components(targets):
-       for_each_repository(pull_repository, targets)
+       if changed:
+               raise Error()
 
-def rebase_repository(repo):
-       print 'Rebasing', repo.path
-       git.remote_update(repo.path)
-       git.rebase(repo.path)
+def check_changes(repo):
+       ret = repo.changes()
+       if ret:
+               log.message('Changes in %s' % repo)
+       return ret
 
-def pull_repository(repo):
-       print 'Pulling', repo.path
-       git.pull(repo.path)
+def check_dirty(c):
+       ret = c.is_dirty()
+       if ret:
+               log.message('Dirty files in %s' % c.name)
+       return ret
 
-def source_dist_components(targets):
-       if not targets:
-               targets = config.components.keys()
+def source_dist(targets):
+       location = 'dist'
+       if not os.path.exists(location):
+               os.makedirs(location)
 
        for name in targets:
-               c = config.components[name]
-               generate_component_changes(c, 'dist')
-               package_component_sources(c, 'dist')
-
-def generate_component_changes(c, location):
-       print 'Generating change log for', c.repo.path
+               c = find_component(name)
+               dist_changes(c, location)
+               dist_sources(c, location)
 
+def dist_changes(c, location):
        path = os.path.join(location, c.name) + '.changes'
+       c.source.dump_log(path)
 
-       pathdir = os.path.dirname(path)
-       if not os.path.exists(pathdir):
-               os.makedirs(pathdir)
-
-       fd = os.open(path, os.O_WRONLY | os.O_CREAT, 0644)
-       git.log(c.repo.path, [c.get_active_tag()], fd=fd)
-       os.close(fd)
-
-def package_component_sources(c, location):
-       print 'Archiving', c.repo.path
-
-       rev = git.describe(c.repo.path)
+def dist_sources(c, location):
+       rev = c.source.describe()
        if rev:
                rev = '_' + rev
        else:
                rev = ''
 
-       path = os.path.join(location, c.name) + rev + '.tar.bz2'
+       name = c.name + rev
+       path = os.path.join(location, name) + '.tar.bz2'
        if os.path.exists(path):
                os.remove(path)
 
-       pathdir = os.path.dirname(path)
-       if not os.path.exists(pathdir):
-               os.makedirs(pathdir)
-
-       git.archive(c.repo.path, path,
-                   prefix=os.path.basename(c.name) + '/',
-                   branch=c.get_active_tag())
-
-def build_rootfs(clean=True, rootfs_only=False, jffs2_only=False, devrootfs_only=False):
-       parse_config('rootfs')
-
-       f = os.popen('sb-conf cu')
-       target = f.read()
-       f.close()
-
-       rootfs = RootFS(target.strip())
-       rootfs.include_paths(config.include_paths)
-       rootfs.include_files(config.include_files)
-       rootfs.filter_paths(config.exclude_paths)
-       rootfs.filter_files(config.exclude_files)
-       rootfs.filter_expressions(config.exclude_expressions)
-       rootfs.add_paths(config.created_paths)
-       rootfs.set_devices(config.devices)
-       rootfs.set_change_owner(config.change_owner)
-       rootfs.set_erase_size(config.boards[config.board].flash_erase_size)
-       rootfs.set_pad_size(config.boards[config.board].flash_pad_size)
-
-       if rootfs_only:
-               rootfs.generate(clean, build_target="rootfs")
-       elif jffs2_only:
-               rootfs.generate(clean, build_target="jffs2")
-       elif devrootfs_only:
-               rootfs.generate(clean, build_target="devrootfs")
-       else:
-               rootfs.generate(clean, build_target="all")
+       c.source.archive(name, path)