Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 98
0.00% covered (danger)
0.00%
0 / 5
CRAP
0.00% covered (danger)
0.00%
0 / 1
CheckDependencies
0.00% covered (danger)
0.00%
0 / 98
0.00% covered (danger)
0.00%
0 / 5
1332
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
2
 execute
0.00% covered (danger)
0.00%
0 / 22
0.00% covered (danger)
0.00%
0 / 1
110
 loadThing
0.00% covered (danger)
0.00%
0 / 42
0.00% covered (danger)
0.00%
0 / 1
156
 addToDependencies
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 1
72
 formatForHumans
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
30
1<?php
2/**
3 * (C) 2019 Kunal Mehta <legoktm@debian.org>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 * http://www.gnu.org/copyleft/gpl.html
19 *
20 * @file
21 */
22
23use MediaWiki\MainConfigNames;
24use MediaWiki\Maintenance\Maintenance;
25use MediaWiki\Registration\ExtensionDependencyError;
26use MediaWiki\Registration\ExtensionRegistry;
27
28// @codeCoverageIgnoreStart
29require_once __DIR__ . '/Maintenance.php';
30// @codeCoverageIgnoreEnd
31
32/**
33 * Checks dependencies for extensions, mostly without loading them
34 *
35 * @since 1.34
36 */
37class CheckDependencies extends Maintenance {
38
39    /** @var bool */
40    private $checkDev;
41
42    public function __construct() {
43        parent::__construct();
44        $this->addDescription( 'Check dependencies for extensions' );
45        $this->addOption( 'extensions', 'Comma separated list of extensions to check', false, true );
46        $this->addOption( 'skins', 'Comma separated list of skins to check', false, true );
47        $this->addOption( 'json', 'Output in JSON' );
48        $this->addOption( 'dev', 'Check development dependencies too' );
49    }
50
51    public function execute() {
52        $this->checkDev = $this->hasOption( 'dev' );
53        $extensions = $this->hasOption( 'extensions' )
54            ? explode( ',', $this->getOption( 'extensions' ) )
55            : [];
56        $skins = $this->hasOption( 'skins' )
57            ? explode( ',', $this->getOption( 'skins' ) )
58            : [];
59
60        $dependencies = [];
61        // Note that we can only use the main registry if we are
62        // not checking development dependencies.
63        $registry = ExtensionRegistry::getInstance();
64        foreach ( $extensions as $extension ) {
65            if ( !$this->checkDev && $registry->isLoaded( $extension ) ) {
66                // If it's already loaded, we know all the dependencies resolved.
67                $this->addToDependencies( $dependencies, [ $extension ], [] );
68                continue;
69            }
70            $this->loadThing( $dependencies, $extension, [ $extension ], [] );
71        }
72
73        foreach ( $skins as $skin ) {
74            if ( !$this->checkDev && $registry->isLoaded( $skin ) ) {
75                // If it's already loaded, we know all the dependencies resolved.
76                $this->addToDependencies( $dependencies, [], [ $skin ] );
77                continue;
78            }
79            $this->loadThing( $dependencies, $skin, [], [ $skin ] );
80        }
81
82        if ( $this->hasOption( 'json' ) ) {
83            $this->output( json_encode( $dependencies ) . "\n" );
84        } else {
85            $this->output( $this->formatForHumans( $dependencies ) . "\n" );
86        }
87    }
88
89    private function loadThing( &$dependencies, $name, $extensions, $skins ) {
90        $extDir = $this->getConfig()->get( MainConfigNames::ExtensionDirectory );
91        $styleDir = $this->getConfig()->get( MainConfigNames::StyleDirectory );
92        $queue = [];
93        $missing = false;
94        foreach ( $extensions as $extension ) {
95            $path = "$extDir/$extension/extension.json";
96            if ( file_exists( $path ) ) {
97                // 1 is ignored
98                $queue[$path] = 1;
99                $this->addToDependencies( $dependencies, [ $extension ], [], $name );
100            } else {
101                $missing = true;
102                $this->addToDependencies( $dependencies, [ $extension ], [], $name, 'missing' );
103            }
104        }
105
106        foreach ( $skins as $skin ) {
107            $path = "$styleDir/$skin/skin.json";
108            if ( file_exists( $path ) ) {
109                $queue[$path] = 1;
110                $this->addToDependencies( $dependencies, [], [ $skin ], $name );
111            } else {
112                $missing = true;
113                $this->addToDependencies( $dependencies, [], [ $skin ], $name, 'missing' );
114            }
115        }
116
117        if ( $missing ) {
118            // Stuff is missing, give up
119            return;
120        }
121
122        $registry = new ExtensionRegistry();
123        $registry->setCheckDevRequires( $this->checkDev );
124        try {
125            $registry->readFromQueue( $queue );
126        } catch ( ExtensionDependencyError $e ) {
127            $reason = false;
128            if ( $e->incompatibleCore ) {
129                $reason = 'incompatible-core';
130            } elseif ( $e->incompatibleSkins ) {
131                $reason = 'incompatible-skins';
132            } elseif ( $e->incompatibleExtensions ) {
133                $reason = 'incompatible-extensions';
134            } elseif ( $e->missingExtensions || $e->missingSkins ) {
135                // There's an extension missing in the dependency tree,
136                // so add those to the dependency list and try again
137                $this->loadThing(
138                    $dependencies,
139                    $name,
140                    array_merge( $extensions, $e->missingExtensions ),
141                    array_merge( $skins, $e->missingSkins )
142                );
143                return;
144            } else {
145                // missing-phpExtension
146                // missing-ability
147                // XXX: ???
148                $this->fatalError( $e->getMessage() );
149            }
150
151            $this->addToDependencies( $dependencies, $extensions, $skins, $name, $reason, $e->getMessage() );
152        }
153
154        $this->addToDependencies( $dependencies, $extensions, $skins, $name );
155    }
156
157    private function addToDependencies( &$dependencies, $extensions, $skins,
158        $why = null, $status = null, $message = null
159    ) {
160        $mainRegistry = ExtensionRegistry::getInstance();
161        $iter = [ 'extensions' => $extensions, 'skins' => $skins ];
162        foreach ( $iter as $type => $things ) {
163            foreach ( $things as $thing ) {
164                $preStatus = $dependencies[$type][$thing]['status'] ?? false;
165                if ( $preStatus !== 'loaded' ) {
166                    // OK to overwrite
167                    if ( $status ) {
168                        $tStatus = $status;
169                    } else {
170                        $tStatus = $mainRegistry->isLoaded( $thing ) ? 'loaded' : 'present';
171                    }
172                    $dependencies[$type][$thing]['status'] = $tStatus;
173                }
174                if ( $why !== null ) {
175                    $dependencies[$type][$thing]['why'][] = $why;
176                    // XXX: this is a bit messy
177                    $dependencies[$type][$thing]['why'] = array_unique(
178                        $dependencies[$type][$thing]['why'] );
179                }
180
181                if ( $message !== null ) {
182                    $dependencies[$type][$thing]['message'] = trim( $message );
183                }
184
185            }
186        }
187    }
188
189    private function formatForHumans( $dependencies ) {
190        $text = '';
191        foreach ( $dependencies as $type => $things ) {
192            $text .= ucfirst( $type ) . "\n" . str_repeat( '=', strlen( $type ) ) . "\n";
193            foreach ( $things as $thing => $info ) {
194                $why = $info['why'] ?? [];
195                if ( $why ) {
196                    $whyText = '(because: ' . implode( ',', $why ) . ') ';
197                } else {
198                    $whyText = '';
199                }
200                $msg = isset( $info['message'] ) ? "{$info['message']}" : '';
201
202                $text .= "$thing{$info['status']}{$msg} $whyText\n";
203            }
204            $text .= "\n";
205        }
206
207        return trim( $text );
208    }
209}
210
211// @codeCoverageIgnoreStart
212$maintClass = CheckDependencies::class;
213require_once RUN_MAINTENANCE_IF_MAIN;
214// @codeCoverageIgnoreEnd