Mercurial > lcfOS
diff python/ppci/tasks.py @ 377:9667d78ba79e
Switched to xml for project description
author | Windel Bouwman |
---|---|
date | Fri, 11 Apr 2014 15:47:50 +0200 |
parents | 3bb7dcfe5529 |
children | 6ae782a085e0 |
line wrap: on
line diff
--- a/python/ppci/tasks.py Tue Mar 25 19:36:51 2014 +0100 +++ b/python/ppci/tasks.py Fri Apr 11 15:47:50 2014 +0200 @@ -4,94 +4,195 @@ """ import logging +import re +import os +import glob + + +task_map = {} +def register_task(name): + """ Decorator that registers a task class """ + def f(cls): + task_map[name] = cls + return cls + return f + class TaskError(Exception): + """ When a task fails, this exception is raised """ def __init__(self, msg): self.msg = msg -class Task: - """ Task that can run, and depend on other tasks """ +class Project: + """ A project contains a set of named targets that can depend upon + eachother """ def __init__(self, name): self.name = name - self.completed = False + self.targets = {} + self.properties = {} + self.macro_regex = re.compile('\$\{([^\}]+)\}') + + def set_property(self, name, value): + self.properties[name] = value + + def get_property(self, name): + if name not in self.properties: + raise TaskError('Property "{}" not found'.format(name)) + return self.properties[name] + + def add_target(self, t): + if t.name in self.targets: + raise TaskError("Duplicate target '{}'".format(t.name)) + self.targets[t.name] = t + + def get_target(self, target_name): + if target_name not in self.targets: + raise TaskError('target "{}" not found'.format(target_name)) + return self.targets[target_name] + + def expand_macros(self, txt): + """ Replace all macros in txt with the correct properties """ + while True: + mo = self.macro_regex.search(txt) + if not mo: + break + propname = mo.group(1) + propval = self.get_property(propname) + txt = txt[:mo.start()] + propval + txt[mo.end():] + return txt + + def dfs(self, target_name, state): + state.add(target_name) + target = self.get_target(target_name) + for dep in target.dependencies: + if dep in state: + raise TaskError('Dependency loop detected {} -> {}'.format(target_name, dep)) + self.dfs(dep, state) + + def check_target(self, target_name): + state = set() + self.dfs(target_name, state) + + def dependencies(self, target_name): + assert type(target_name) is str + target = self.get_target(target_name) + cdst = list(self.dependencies(dep) for dep in target.dependencies) + cdst.append(target.dependencies) + return set.union(*cdst) + + +class Target: + """ Defines a target that has a name and a list of tasks to execute """ + def __init__(self, name, project): + self.name = name + self.project = project + self.tasks = [] self.dependencies = set() - self.duration = 1 + + def add_task(self, task): + self.tasks.append(task) + + def add_dependency(self, target_name): + """ Add another task as a dependency for this task """ + self.dependencies.add(target_name) + + def __gt__(self, other): + return other.name in self.project.dependencies(self.name) + + def __repr__(self): + return 'Target "{}"'.format(self.name) + + +class Task: + """ Task that can run, and depend on other tasks """ + def __init__(self, target, kwargs, sub_elements=[]): + self.logger = logging.getLogger('task') + self.target = target + self.name = self.__class__.__name__ + self.arguments = kwargs + self.subs = sub_elements + + def get_argument(self, name): + if name not in self.arguments: + raise TaskError('attribute "{}" not specified'.format(name)) + return self.arguments[name] + + def get_property(self, name): + return self.target.project.get_property(name) + + def relpath(self, filename): + basedir = self.get_property('basedir') + return os.path.join(basedir, filename) + + def open_file_set(self, s): + """ Creates a list of open file handles. s can be one of these: + - A string like "a.c3" + - A string like "*.c3" + - A string like "a.c3;src/*.c3" + """ + assert type(s) is str + fns = [] + for part in s.split(';'): + fns += glob.glob(self.relpath(part)) + return fns def run(self): raise NotImplementedError("Implement this abstract method!") - def fire(self): - """ Wrapper around run that marks the task as done """ - assert all(t.completed for t in self.dependencies) - self.run() - self.completed = True - - def add_dependency(self, task): - """ Add another task as a dependency for this task """ - if task is self: - raise TaskError('Can not add dependency on task itself!') - if self in task.down_stream_tasks: - raise TaskError('Can not introduce circular task') - self.dependencies.add(task) - return task - - @property - def down_stream_tasks(self): - """ Return a set of all tasks that follow this task """ - # TODO: is this upstream or downstream??? - cdst = list(dep.down_stream_tasks for dep in self.dependencies) - cdst.append(self.dependencies) - return set.union(*cdst) - - def __gt__(self, other): - return other in self.down_stream_tasks - def __repr__(self): return 'Task "{}"'.format(self.name) -class EmptyTask(Task): - """ Basic task that does nothing """ - def run(self): - pass - - class TaskRunner: """ Basic task runner that can run some tasks in sequence """ def __init__(self): self.logger = logging.getLogger('taskrunner') - self.task_list = [] - def add_task(self, task): - self.task_list.append(task) + def run(self, project, targets=[]): + """ Try to run a project """ + # Determine what targets to run: + if targets: + target_list = targets + else: + if project.default: + target_list = [project.default] + else: + target_list = [] - @property - def total_duration(self): - return sum(t.duration for t in self.task_list) + try: + if not target_list: + self.logger.info('Done!') + return 0 + + # Check for loops: + for target in target_list: + project.check_target(target) - def run_tasks(self): - # First sort tasks: - self.task_list.sort() + # Calculate all dependencies: + target_list = set.union(*[project.dependencies(t) for t in target_list]).union(set(target_list)) + # Lookup actual targets: + target_list = [project.get_target(target_name) for target_name in target_list] + target_list.sort() + + self.logger.info('Target sequence: {}'.format(target_list)) - # Run tasks: - passed_time = 0.0 - total_time = self.total_duration - try: - for t in self.task_list: - self.report_progress(passed_time / total_time, t.name) - t.fire() - passed_time += t.duration + # Run tasks: + for target in target_list: + self.logger.info('Target {}'.format(target.name)) + for task in target.tasks: + if type(task) is tuple: + tname, props = task + for arg in props: + props[arg] = project.expand_macros(props[arg]) + task = task_map[tname](target, props) + self.logger.info('Running {}'.format(task)) + task.run() + else: + raise Exception() + self.logger.info('Done!') except TaskError as e: self.logger.error(str(e.msg)) return 1 - self.report_progress(1, 'OK') return 0 - def display(self): - """ Display task how they would be run """ - for task in self.task_list: - print(task) - - def report_progress(self, percentage, text): - self.logger.info('[{:3.1%}] {}'.format(percentage, text))