1from collections import OrderedDict 2 3from .control import TaskControl 4from .cmd_base import DoitCmdBase 5from .cmd_base import check_tasks_exist 6 7 8opt_clean_dryrun = { 9 'name': 'dryrun', 10 'short': 'n', # like make dry-run 11 'long': 'dry-run', 12 'type': bool, 13 'default': False, 14 'help': 'print actions without really executing them', 15 } 16 17opt_clean_cleandep = { 18 'name': 'cleandep', 19 'short': 'c', # clean 20 'long': 'clean-dep', 21 'type': bool, 22 'default': False, 23 'help': 'clean task dependencies too', 24 } 25 26opt_clean_cleanall = { 27 'name': 'cleanall', 28 'short': 'a', # all 29 'long': 'clean-all', 30 'type': bool, 31 'default': False, 32 'help': 'clean all task', 33 } 34 35opt_clean_forget = { 36 'name': 'cleanforget', 37 'long': 'forget', 38 'type': bool, 39 'default': False, 40 'help': 'also forget tasks after cleaning', 41 } 42 43class Clean(DoitCmdBase): 44 doc_purpose = "clean action / remove targets" 45 doc_usage = "[TASK ...]" 46 doc_description = ("If no task is specified clean default tasks and " 47 "set --clean-dep automatically.") 48 49 cmd_options = (opt_clean_cleandep, opt_clean_cleanall, 50 opt_clean_dryrun, opt_clean_forget) 51 52 53 def clean_tasks(self, tasks, dryrun, cleanforget): 54 """ensure task clean-action is executed only once""" 55 cleaned = set() 56 forget_tasks = cleanforget and not dryrun 57 for task in tasks: 58 if task.name not in cleaned: 59 cleaned.add(task.name) 60 task.clean(self.outstream, dryrun) 61 if forget_tasks: 62 self.dep_manager.remove(task.name) 63 64 self.dep_manager.close() 65 66 def _execute(self, dryrun, cleandep, cleanall, cleanforget, 67 pos_args=None): 68 """Clean tasks 69 @param task_list (list - L{Task}): list of all tasks from dodo file 70 @ivar dryrun (bool): if True clean tasks are not executed 71 (just print out what would be executed) 72 @param cleandep (bool): execute clean from task_dep 73 @param cleanall (bool): clean all tasks 74 @param cleanforget (bool): forget cleaned tasks 75 @var default_tasks (list - string): list of default tasks 76 @var selected_tasks (list - string): list of tasks selected 77 from cmd-line 78 """ 79 tasks = TaskControl(self.task_list).tasks 80 # behavior of cleandep is different if selected_tasks comes from 81 # command line or DOIT_CONFIG.default_tasks 82 selected_tasks = pos_args 83 check_tasks_exist(tasks, selected_tasks) 84 85 # get base list of tasks to be cleaned 86 if selected_tasks and not cleanall: # from command line 87 clean_list = selected_tasks 88 else: 89 # if not cleaning specific task enable clean_dep automatically 90 cleandep = True 91 if self.sel_tasks is not None: 92 clean_list = self.sel_tasks # default tasks from config 93 else: 94 clean_list = [t.name for t in self.task_list] 95 # note: reversing is not required, but helps reversing 96 # execution order even if there are no restrictions about order. 97 clean_list.reverse() 98 99 tree = CleanDepTree() 100 # include dependencies in list 101 if cleandep: 102 for name in clean_list: 103 tree.build_nodes_with_deps(tasks, name) 104 # include only subtasks in list 105 else: 106 tree.build_nodes(tasks, clean_list) 107 108 to_clean = [tasks[x] for x in tree.flat()] 109 self.clean_tasks(to_clean, dryrun, cleanforget) 110 111 112class CleanDepTree: 113 """Create node structure where each node is a task and its children 114 are tasks that has the node as a task_dep/setup_task. 115 This creates an upside-down tree where leaf nodes should be 116 the first ones to be "cleaned". 117 """ 118 def __init__(self): 119 self.nodes = OrderedDict() 120 self._processed = set() # task names that were already built 121 122 def build_nodes_with_deps(self, tasks, task_name): 123 """build node including task_dep's""" 124 if task_name in self._processed: 125 return 126 else: 127 self._processed.add(task_name) 128 129 # add node itself if not in list of nodes 130 self.nodes.setdefault(task_name, []) 131 task = tasks[task_name] 132 # reversing not required 133 for dep_name in reversed(task.setup_tasks + task.task_dep): 134 rev_dep = self.nodes.setdefault(dep_name, []) 135 rev_dep.append(task_name) 136 self.build_nodes_with_deps(tasks, dep_name) 137 138 def build_nodes(self, tasks, clean_list): 139 """build nodes with sub-tasks but no other task_dep""" 140 for name in clean_list: 141 # add node itself if not in list of nodes 142 self.nodes.setdefault(name, []) 143 task = tasks[name] 144 # reversing not required 145 for dep_name in reversed(task.task_dep): 146 if tasks[dep_name].subtask_of == name: 147 rev_dep = self.nodes.setdefault(dep_name, []) 148 rev_dep.append(name) 149 150 def flat(self): 151 """return list of tasks in the order they should be `clean` """ 152 to_clean = [] 153 while self.nodes: 154 head, children = self.nodes.popitem(0) 155 to_clean.extend([x for x in self._get_leafs(head, children)]) 156 return to_clean 157 158 def _get_leafs(self, name, children): 159 for child_name in children: 160 if child_name in self.nodes: 161 grand = self.nodes.pop(child_name) 162 yield from self._get_leafs(child_name, grand) 163 yield name 164