Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 60 |
|
0.00% |
0 / 13 |
CRAP | |
0.00% |
0 / 1 |
TaskRunner | |
0.00% |
0 / 60 |
|
0.00% |
0 / 13 |
992 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
2 | |||
execute | |
0.00% |
0 / 12 |
|
0.00% |
0 / 1 |
42 | |||
runNamedTask | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
20 | |||
loadExtensions | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
12 | |||
findNamedTask | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
20 | |||
isSkipped | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
6 | |||
isComplete | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
runTask | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
12 | |||
addTaskStartListener | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
addTaskEndListener | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
setSkippedTasks | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getCurrentTaskName | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
dumpTaskList | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
12 |
1 | <?php |
2 | |
3 | namespace MediaWiki\Installer\Task; |
4 | |
5 | use MediaWiki\Status\Status; |
6 | |
7 | /** |
8 | * @internal For use by the installer |
9 | */ |
10 | class TaskRunner { |
11 | /** @var TaskList|Task[] */ |
12 | private $tasks; |
13 | /** @var TaskFactory */ |
14 | private $taskFactory; |
15 | /** @var string */ |
16 | private $profile; |
17 | /** @var callable[] */ |
18 | private $taskStartListeners; |
19 | /** @var callable[] */ |
20 | private $taskEndListeners; |
21 | /** @var array<string,bool> */ |
22 | private $skippedTasks = []; |
23 | /** @var array<string,bool> */ |
24 | private $completedTasks = []; |
25 | /** @var string|null */ |
26 | private $currentTaskName; |
27 | |
28 | public function __construct( TaskList $tasks, TaskFactory $taskFactory, string $profile ) { |
29 | $this->tasks = $tasks; |
30 | $this->taskFactory = $taskFactory; |
31 | $this->profile = $profile; |
32 | } |
33 | |
34 | /** |
35 | * Run all non-skipped tasks and return a merged Status |
36 | * |
37 | * @return Status |
38 | */ |
39 | public function execute() { |
40 | $overallStatus = Status::newGood(); |
41 | $overallStatus->merge( $this->loadExtensions() ); |
42 | if ( !$overallStatus->isOK() ) { |
43 | return $overallStatus; |
44 | } |
45 | |
46 | /** @var Task $task */ |
47 | foreach ( $this->tasks as $task ) { |
48 | if ( $this->isSkipped( $task ) || $this->isComplete( $task ) ) { |
49 | continue; |
50 | } |
51 | |
52 | $status = $this->runTask( $task ); |
53 | $overallStatus->merge( $status ); |
54 | |
55 | // If we've hit some sort of fatal, we need to bail. |
56 | // Callback already had a chance to do output above. |
57 | if ( !$status->isOK() ) { |
58 | break; |
59 | } |
60 | } |
61 | return $overallStatus; |
62 | } |
63 | |
64 | /** |
65 | * Run a single specified task (and its scheduled providers) |
66 | * |
67 | * @param string $name |
68 | * @return Status |
69 | */ |
70 | public function runNamedTask( string $name ) { |
71 | $mainTask = $this->findNamedTask( $name ); |
72 | if ( !$mainTask ) { |
73 | throw new \RuntimeException( "Can't find task named \"$name\"" ); |
74 | } |
75 | $deps = (array)$mainTask->getDependencies(); |
76 | |
77 | $status = Status::newGood(); |
78 | foreach ( $this->tasks as $subTask ) { |
79 | if ( array_intersect( (array)$subTask->getProvidedNames(), $deps ) ) { |
80 | $status->merge( $this->runTask( $subTask ) ); |
81 | } |
82 | } |
83 | $status->merge( $this->runTask( $mainTask ) ); |
84 | |
85 | return $status; |
86 | } |
87 | |
88 | /** |
89 | * Run the extensions provider (if it is registered) and load any extension tasks. |
90 | * |
91 | * @return Status |
92 | */ |
93 | public function loadExtensions() { |
94 | $task = $this->findNamedTask( 'extensions' ); |
95 | if ( $task ) { |
96 | $status = $this->runTask( $task ); |
97 | if ( $status->isOK() ) { |
98 | $this->taskFactory->registerExtensionTasks( $this->tasks, $this->profile ); |
99 | } |
100 | } else { |
101 | $status = Status::newGood(); |
102 | } |
103 | return $status; |
104 | } |
105 | |
106 | /** |
107 | * @param string $name |
108 | * @return Task|null |
109 | */ |
110 | private function findNamedTask( string $name ): ?Task { |
111 | foreach ( $this->tasks as $task ) { |
112 | if ( $this->isSkipped( $task ) ) { |
113 | continue; |
114 | } |
115 | |
116 | if ( $name === $task->getName() ) { |
117 | return $task; |
118 | } |
119 | } |
120 | return null; |
121 | } |
122 | |
123 | /** |
124 | * Determine whether a task is skipped |
125 | * |
126 | * @param Task $task |
127 | * @return bool |
128 | */ |
129 | private function isSkipped( Task $task ) { |
130 | return $task->isSkipped() || isset( $this->skippedTasks[$task->getName()] ); |
131 | } |
132 | |
133 | /** |
134 | * Determine whether a task has already completed |
135 | * |
136 | * @param Task $task |
137 | * @return bool |
138 | */ |
139 | private function isComplete( Task $task ) { |
140 | return isset( $this->completedTasks[$task->getName()] ); |
141 | } |
142 | |
143 | /** |
144 | * Run a task and call the listeners |
145 | * |
146 | * @param Task $task |
147 | * @return Status |
148 | */ |
149 | private function runTask( Task $task ) { |
150 | $this->currentTaskName = $task->getName(); |
151 | foreach ( $this->taskStartListeners as $listener ) { |
152 | $listener( $task ); |
153 | } |
154 | |
155 | $status = $task->execute(); |
156 | |
157 | $this->completedTasks[$task->getName()] = true; |
158 | foreach ( $this->taskEndListeners as $listener ) { |
159 | $listener( $task, $status ); |
160 | } |
161 | return $status; |
162 | } |
163 | |
164 | /** |
165 | * Add a callback to be called before each task is executed. The callback |
166 | * takes one parameter: the task object. |
167 | * |
168 | * @param callable $listener |
169 | */ |
170 | public function addTaskStartListener( callable $listener ) { |
171 | $this->taskStartListeners[] = $listener; |
172 | } |
173 | |
174 | /** |
175 | * Add a callback to be called after each task completes. The callback |
176 | * takes two parameters: the task object and the Status returned by the |
177 | * task. |
178 | * |
179 | * @param callable $listener |
180 | */ |
181 | public function addTaskEndListener( callable $listener ) { |
182 | $this->taskEndListeners[] = $listener; |
183 | } |
184 | |
185 | /** |
186 | * Set a list of task names to be skipped |
187 | * |
188 | * @param string[] $taskNames |
189 | */ |
190 | public function setSkippedTasks( array $taskNames ) { |
191 | $this->skippedTasks = array_fill_keys( $taskNames, true ); |
192 | } |
193 | |
194 | /** |
195 | * Get the name of the last task to start execution. This is valid during |
196 | * callbacks and after execute() returns. |
197 | * |
198 | * @return string|null |
199 | */ |
200 | public function getCurrentTaskName(): ?string { |
201 | return $this->currentTaskName; |
202 | } |
203 | |
204 | /** |
205 | * Provide a summary of the tasks to be executed, for debugging. |
206 | * |
207 | * @return string |
208 | */ |
209 | public function dumpTaskList(): string { |
210 | $ret = ''; |
211 | $i = 0; |
212 | foreach ( $this->tasks as $task ) { |
213 | $ret .= ( ++$i ) . '. ' . $task->getName(); |
214 | if ( $task->isSkipped() ) { |
215 | $ret .= ' [SKIPPED]'; |
216 | } |
217 | $ret .= "\n"; |
218 | } |
219 | return $ret; |
220 | } |
221 | } |