MediaWiki master
checkDependencies.php
Go to the documentation of this file.
1<?php
26
27// @codeCoverageIgnoreStart
28require_once __DIR__ . '/Maintenance.php';
29// @codeCoverageIgnoreEnd
30
37
39 private $checkDev;
40
41 public function __construct() {
42 parent::__construct();
43 $this->addDescription( 'Check dependencies for extensions' );
44 $this->addOption( 'extensions', 'Comma separated list of extensions to check', false, true );
45 $this->addOption( 'skins', 'Comma separated list of skins to check', false, true );
46 $this->addOption( 'json', 'Output in JSON' );
47 $this->addOption( 'dev', 'Check development dependencies too' );
48 }
49
50 public function execute() {
51 $this->checkDev = $this->hasOption( 'dev' );
52 $extensions = $this->hasOption( 'extensions' )
53 ? explode( ',', $this->getOption( 'extensions' ) )
54 : [];
55 $skins = $this->hasOption( 'skins' )
56 ? explode( ',', $this->getOption( 'skins' ) )
57 : [];
58
59 $dependencies = [];
60 // Note that we can only use the main registry if we are
61 // not checking development dependencies.
62 $registry = ExtensionRegistry::getInstance();
63 foreach ( $extensions as $extension ) {
64 if ( !$this->checkDev && $registry->isLoaded( $extension ) ) {
65 // If it's already loaded, we know all the dependencies resolved.
66 $this->addToDependencies( $dependencies, [ $extension ], [] );
67 continue;
68 }
69 $this->loadThing( $dependencies, $extension, [ $extension ], [] );
70 }
71
72 foreach ( $skins as $skin ) {
73 if ( !$this->checkDev && $registry->isLoaded( $skin ) ) {
74 // If it's already loaded, we know all the dependencies resolved.
75 $this->addToDependencies( $dependencies, [], [ $skin ] );
76 continue;
77 }
78 $this->loadThing( $dependencies, $skin, [], [ $skin ] );
79 }
80
81 if ( $this->hasOption( 'json' ) ) {
82 $this->output( json_encode( $dependencies ) . "\n" );
83 } else {
84 $this->output( $this->formatForHumans( $dependencies ) . "\n" );
85 }
86 }
87
88 private function loadThing( &$dependencies, $name, $extensions, $skins ) {
89 $extDir = $this->getConfig()->get( MainConfigNames::ExtensionDirectory );
90 $styleDir = $this->getConfig()->get( MainConfigNames::StyleDirectory );
91 $queue = [];
92 $missing = false;
93 foreach ( $extensions as $extension ) {
94 $path = "$extDir/$extension/extension.json";
95 if ( file_exists( $path ) ) {
96 // 1 is ignored
97 $queue[$path] = 1;
98 $this->addToDependencies( $dependencies, [ $extension ], [], $name );
99 } else {
100 $missing = true;
101 $this->addToDependencies( $dependencies, [ $extension ], [], $name, 'missing' );
102 }
103 }
104
105 foreach ( $skins as $skin ) {
106 $path = "$styleDir/$skin/skin.json";
107 if ( file_exists( $path ) ) {
108 $queue[$path] = 1;
109 $this->addToDependencies( $dependencies, [], [ $skin ], $name );
110 } else {
111 $missing = true;
112 $this->addToDependencies( $dependencies, [], [ $skin ], $name, 'missing' );
113 }
114 }
115
116 if ( $missing ) {
117 // Stuff is missing, give up
118 return;
119 }
120
121 $registry = new ExtensionRegistry();
122 $registry->setCheckDevRequires( $this->checkDev );
123 try {
124 $registry->readFromQueue( $queue );
125 } catch ( ExtensionDependencyError $e ) {
126 $reason = false;
127 if ( $e->incompatibleCore ) {
128 $reason = 'incompatible-core';
129 } elseif ( $e->incompatibleSkins ) {
130 $reason = 'incompatible-skins';
131 } elseif ( $e->incompatibleExtensions ) {
132 $reason = 'incompatible-extensions';
133 } elseif ( $e->missingExtensions || $e->missingSkins ) {
134 // There's an extension missing in the dependency tree,
135 // so add those to the dependency list and try again
136 $this->loadThing(
137 $dependencies,
138 $name,
139 array_merge( $extensions, $e->missingExtensions ),
140 array_merge( $skins, $e->missingSkins )
141 );
142 return;
143 } else {
144 // missing-phpExtension
145 // missing-ability
146 // XXX: ???
147 $this->fatalError( $e->getMessage() );
148 }
149
150 $this->addToDependencies( $dependencies, $extensions, $skins, $name, $reason, $e->getMessage() );
151 }
152
153 $this->addToDependencies( $dependencies, $extensions, $skins, $name );
154 }
155
156 private function addToDependencies( &$dependencies, $extensions, $skins,
157 $why = null, $status = null, $message = null
158 ) {
159 $mainRegistry = ExtensionRegistry::getInstance();
160 $iter = [ 'extensions' => $extensions, 'skins' => $skins ];
161 foreach ( $iter as $type => $things ) {
162 foreach ( $things as $thing ) {
163 $preStatus = $dependencies[$type][$thing]['status'] ?? false;
164 if ( $preStatus !== 'loaded' ) {
165 // OK to overwrite
166 if ( $status ) {
167 $tStatus = $status;
168 } else {
169 $tStatus = $mainRegistry->isLoaded( $thing ) ? 'loaded' : 'present';
170 }
171 $dependencies[$type][$thing]['status'] = $tStatus;
172 }
173 if ( $why !== null ) {
174 $dependencies[$type][$thing]['why'][] = $why;
175 // XXX: this is a bit messy
176 $dependencies[$type][$thing]['why'] = array_unique(
177 $dependencies[$type][$thing]['why'] );
178 }
179
180 if ( $message !== null ) {
181 $dependencies[$type][$thing]['message'] = trim( $message );
182 }
183
184 }
185 }
186 }
187
188 private function formatForHumans( $dependencies ) {
189 $text = '';
190 foreach ( $dependencies as $type => $things ) {
191 $text .= ucfirst( $type ) . "\n" . str_repeat( '=', strlen( $type ) ) . "\n";
192 foreach ( $things as $thing => $info ) {
193 $why = $info['why'] ?? [];
194 if ( $why ) {
195 $whyText = '(because: ' . implode( ',', $why ) . ') ';
196 } else {
197 $whyText = '';
198 }
199 $msg = isset( $info['message'] ) ? ", {$info['message']}" : '';
200
201 $text .= "$thing: {$info['status']}{$msg} $whyText\n";
202 }
203 $text .= "\n";
204 }
205
206 return trim( $text );
207 }
208}
209
210// @codeCoverageIgnoreStart
211$maintClass = CheckDependencies::class;
212require_once RUN_MAINTENANCE_IF_MAIN;
213// @codeCoverageIgnoreEnd
Checks dependencies for extensions, mostly without loading them.
__construct()
Default constructor.
execute()
Do the actual work.
Abstract maintenance class for quickly writing and churning out maintenance scripts with minimal effo...
output( $out, $channel=null)
Throw some output to the user.
hasOption( $name)
Checks to see if a particular option was set.
addDescription( $text)
Set the description text.
addOption( $name, $description, $required=false, $withArg=false, $shortName=false, $multiOccurrence=false)
Add a parameter to the script.
getOption( $name, $default=null)
Get an option, or return the default.
fatalError( $msg, $exitCode=1)
Output a message and terminate the current script.
A class containing constants representing the names of configuration variables.
Load JSON files, and uses a Processor to extract information.