1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
/* |
4
|
|
|
* Composer plugin for bower/npm assets |
5
|
|
|
* |
6
|
|
|
* @link https://github.com/hiqdev/composer-asset-plugin |
7
|
|
|
* @package composer-asset-plugin |
8
|
|
|
* @license BSD-3-Clause |
9
|
|
|
* @copyright Copyright (c) 2015-2016, HiQDev (http://hiqdev.com/) |
10
|
|
|
*/ |
11
|
|
|
|
12
|
|
|
namespace hiqdev\composerassetplugin; |
13
|
|
|
|
14
|
|
|
use Composer\Json\JsonFile; |
15
|
|
|
use Composer\Package\PackageInterface; |
16
|
|
|
|
17
|
|
|
/** |
18
|
|
|
* Abstract package manager class. |
19
|
|
|
* |
20
|
|
|
* @author Andrii Vasyliev <[email protected]> |
21
|
|
|
*/ |
22
|
|
|
abstract class PackageManager |
23
|
|
|
{ |
24
|
|
|
/** |
25
|
|
|
* @var Plugin the plugin instance |
26
|
|
|
*/ |
27
|
|
|
protected $plugin; |
28
|
|
|
|
29
|
|
|
/** |
30
|
|
|
* @var string Package manager name: `bower` or `npm` |
31
|
|
|
*/ |
32
|
|
|
protected $name; |
33
|
|
|
|
34
|
|
|
/** |
35
|
|
|
* @var string Package config file name: `bower.json` or `package.json` |
36
|
|
|
*/ |
37
|
|
|
public $file; |
38
|
|
|
|
39
|
|
|
/** |
40
|
|
|
* @var string Package RC config file name: `.bowerrc` or `.npmrc` |
41
|
|
|
*/ |
42
|
|
|
public $rcfile; |
43
|
|
|
|
44
|
|
|
/** |
45
|
|
|
* @var string Path to package manager binary |
46
|
|
|
*/ |
47
|
|
|
public $bin; |
48
|
|
|
|
49
|
|
|
/** |
50
|
|
|
* @var string Package name of the PHP version of the package manager |
51
|
|
|
*/ |
52
|
|
|
public $phpPackage; |
53
|
|
|
|
54
|
|
|
/** |
55
|
|
|
* @var string Binary name of PHP version of package manager |
56
|
|
|
*/ |
57
|
|
|
protected $phpBin; |
58
|
|
|
|
59
|
|
|
/** |
60
|
|
|
* @var array Package config. Initially holds default config |
61
|
|
|
*/ |
62
|
|
|
protected $config = []; |
63
|
|
|
|
64
|
|
|
/** |
65
|
|
|
* @var array RC config: .bowerrc or .npmrc |
66
|
|
|
*/ |
67
|
|
|
protected $rc = []; |
68
|
|
|
|
69
|
|
|
/** |
70
|
|
|
* @var array List of keys holding dependencies |
71
|
|
|
*/ |
72
|
|
|
protected $dependencies = ['dependencies', 'devDependencies']; |
73
|
|
|
|
74
|
|
|
/** |
75
|
|
|
* array known deps collected from requirements. |
76
|
|
|
*/ |
77
|
|
|
protected $knownDeps = []; |
78
|
|
|
|
79
|
|
|
/** |
80
|
|
|
* Reads config file or dist config if exists, merges with default config. |
81
|
|
|
* @param Plugin $plugin |
82
|
|
|
* @void |
83
|
|
|
*/ |
84
|
3 |
|
public function __construct(Plugin $plugin) |
85
|
|
|
{ |
86
|
3 |
|
$this->plugin = $plugin; |
87
|
|
|
//$dist = $this->file . '.dist'; |
|
|
|
|
88
|
3 |
|
$this->config = array_merge( |
89
|
3 |
|
$this->config, |
90
|
3 |
|
$this->readConfig($this->file) |
91
|
|
|
//$this->readConfig(file_exists($dist) ? $dist : $this->file) |
|
|
|
|
92
|
3 |
|
); |
93
|
3 |
|
} |
94
|
|
|
|
95
|
|
|
public function getName() |
96
|
|
|
{ |
97
|
|
|
return $this->name; |
98
|
|
|
} |
99
|
|
|
|
100
|
|
|
public function packageFullName($package) |
101
|
|
|
{ |
102
|
|
|
return $package->getName() . ':' . $package->getVersion(); |
103
|
|
|
} |
104
|
|
|
|
105
|
|
|
public function setKnownDeps(PackageInterface $package, $type, $name, $constraint) |
106
|
|
|
{ |
107
|
|
|
$res = $this->getKnownDeps($package); |
108
|
|
|
if (!isset($res[$type])) { |
109
|
|
|
$res[$type] = []; |
110
|
|
|
} |
111
|
|
|
$res[$type][$name] = $constraint; |
112
|
|
|
$this->knownDeps[$this->packageFullName($package)] = $res; |
113
|
|
|
} |
114
|
|
|
|
115
|
|
|
public function getKnownDeps(PackageInterface $package) |
116
|
|
|
{ |
117
|
|
|
$full = $this->packageFullName($package); |
118
|
|
|
return isset($this->knownDeps[$full]) ? $this->knownDeps[$full] : []; |
119
|
|
|
} |
120
|
|
|
|
121
|
|
|
public function getConfig() |
122
|
|
|
{ |
123
|
|
|
return $this->config; |
124
|
|
|
} |
125
|
|
|
|
126
|
|
|
abstract public function setDestination($dir); |
127
|
|
|
/** |
128
|
|
|
* Reads the JSON config from the $path. |
129
|
|
|
* |
130
|
|
|
* @param string $path path to the Json file |
131
|
|
|
* @return array|mixed |
132
|
|
|
*/ |
133
|
3 |
|
public function readConfig($path) |
134
|
|
|
{ |
135
|
3 |
|
$jsonFile = new JsonFile($path); |
136
|
3 |
|
$config = $jsonFile->exists() ? $jsonFile->read() : []; |
137
|
3 |
|
foreach ($this->dependencies as $key) { |
138
|
3 |
|
if (!isset($config[$key])) { |
139
|
3 |
|
$config[$key] = []; |
140
|
3 |
|
} |
141
|
3 |
|
} |
142
|
3 |
|
return $config; |
143
|
|
|
} |
144
|
|
|
|
145
|
|
|
/** |
146
|
|
|
* Saves JSON config to the given path. |
147
|
|
|
* |
148
|
|
|
* @param string $path |
149
|
|
|
* @param array $config |
150
|
|
|
* @throws \Exception |
151
|
|
|
*/ |
152
|
|
|
public function writeJson($path, array $config) |
153
|
|
|
{ |
154
|
|
|
$jsonFile = new JsonFile($path); |
155
|
|
|
$jsonFile->write($this->prepareConfig($config)); |
156
|
|
|
} |
157
|
|
|
|
158
|
|
|
public function prepareConfig(array $config) |
159
|
|
|
{ |
160
|
|
|
foreach ($this->dependencies as $key) { |
161
|
|
|
if (!isset($config[$key])) { |
162
|
|
|
continue; |
163
|
|
|
} |
164
|
|
|
if (!$config[$key]) { |
165
|
|
|
unset($config[$key]); |
166
|
|
|
continue; |
167
|
|
|
} |
168
|
|
|
foreach ($config[$key] as $name => &$constraint) { |
169
|
|
|
$constraint = $this->fixConstraint($constraint); |
170
|
|
|
} |
171
|
|
|
} |
172
|
|
|
|
173
|
|
|
return $config; |
174
|
|
|
} |
175
|
|
|
|
176
|
|
|
/** |
177
|
|
|
* Fixes constraint for the package manager. |
178
|
|
|
* Does nothing for NPM. Redefined in Bower. |
179
|
|
|
* @param string $constraint |
180
|
|
|
* @return string |
181
|
|
|
*/ |
182
|
|
|
public function fixConstraint($constraint) |
183
|
|
|
{ |
184
|
|
|
return $constraint; |
185
|
|
|
} |
186
|
|
|
|
187
|
|
|
/** |
188
|
|
|
* Scans the $package and extracts dependencies to the [[config]]. |
189
|
|
|
* @param PackageInterface $package |
190
|
|
|
*/ |
191
|
|
|
public function scanPackage(PackageInterface $package) |
192
|
|
|
{ |
193
|
|
|
$extra = $package->getExtra(); |
194
|
|
|
$extra_deps = []; |
195
|
|
|
foreach ($this->dependencies as $key) { |
196
|
|
|
$name = $this->name . '-' . $key; |
197
|
|
|
if (isset($extra[$name])) { |
198
|
|
|
$extra_deps[$key] = $extra[$name]; |
199
|
|
|
} |
200
|
|
|
} |
201
|
|
|
$known_deps = $this->getKnownDeps($package); |
202
|
|
|
foreach ([$known_deps, $extra_deps] as $deps) { |
203
|
|
|
if (!empty($deps)) { |
204
|
|
|
$this->mergeConfig($deps); |
205
|
|
|
} |
206
|
|
|
} |
207
|
|
|
} |
208
|
|
|
|
209
|
|
|
/** |
210
|
|
|
* Merges the $config over the [[config]]. |
211
|
|
|
* @param array $config |
212
|
|
|
*/ |
213
|
|
|
protected function mergeConfig(array $config) |
214
|
|
|
{ |
215
|
|
|
foreach ($config as $type => $packages) { |
216
|
|
|
foreach ($packages as $name => $constraint) { |
217
|
|
|
$this->addDependency($type, $name, $constraint); |
218
|
|
|
} |
219
|
|
|
} |
220
|
|
|
} |
221
|
|
|
|
222
|
|
|
public function addDependency($type, $name, $constraint) |
223
|
|
|
{ |
224
|
|
|
if (isset($this->config[$type][$name])) { |
225
|
|
|
$this->config[$type][$name] = Constraint::merge($this->config[$type][$name], $constraint); |
226
|
|
|
} else { |
227
|
|
|
$this->config[$type][$name] = $constraint; |
228
|
|
|
} |
229
|
|
|
} |
230
|
|
|
|
231
|
|
|
/** |
232
|
|
|
* Set config. |
233
|
|
|
* @param array $config |
234
|
|
|
*/ |
235
|
|
|
public function setConfig(array $config) |
236
|
|
|
{ |
237
|
|
|
$this->config = $config; |
238
|
|
|
} |
239
|
|
|
|
240
|
|
|
/** |
241
|
|
|
* Returns if the package manager has nonempty dependency list. |
242
|
|
|
* @return bool |
243
|
|
|
*/ |
244
|
1 |
|
public function hasDependencies() |
245
|
|
|
{ |
246
|
1 |
|
foreach ($this->dependencies as $key) { |
247
|
1 |
|
if (isset($this->config[$key]) && $this->config[$key]) { |
248
|
|
|
return true; |
249
|
|
|
} |
250
|
1 |
|
} |
251
|
|
|
|
252
|
1 |
|
return false; |
253
|
|
|
} |
254
|
|
|
|
255
|
|
|
/** |
256
|
|
|
* Run the given action: show notice, write config and run `perform`. |
257
|
|
|
* @param string $action the action name |
258
|
|
|
*/ |
259
|
|
|
public function runAction($action) |
260
|
|
|
{ |
261
|
|
|
$doing = ucfirst(trim($action, 'e')) . 'ing'; |
262
|
|
|
$this->plugin->io->writeError('<info>' . $doing . ' ' . $this->name . ' dependencies...</info>'); |
263
|
|
|
$this->saveConfigs(); |
264
|
|
|
$this->perform($action); |
265
|
|
|
} |
266
|
|
|
|
267
|
|
|
public function saveConfigs() |
268
|
|
|
{ |
269
|
|
|
if ($this->rc) { |
|
|
|
|
270
|
|
|
$this->writeRc($this->rcfile, $this->rc); |
271
|
|
|
} else { |
272
|
|
|
unlink($this->rcfile); |
273
|
|
|
} |
274
|
|
|
$this->writeJson($this->file, $this->config); |
275
|
|
|
} |
276
|
|
|
|
277
|
|
|
abstract public function writeRc($path, $data); |
278
|
|
|
|
279
|
|
|
/** |
280
|
|
|
* Run installation. Specific for every package manager. |
281
|
|
|
* @param string $action the action name |
282
|
|
|
* @void |
283
|
|
|
*/ |
284
|
|
|
protected function perform($action) |
285
|
|
|
{ |
286
|
|
|
$this->plugin->io->writeError('running ' . $this->getBin()); |
287
|
|
|
if ($this->passthru([$action])) { |
|
|
|
|
288
|
|
|
$this->plugin->io->writeError('<error>failed ' . $this->name . ' ' . $action . '</error>'); |
289
|
|
|
} |
290
|
|
|
} |
291
|
|
|
|
292
|
|
|
/** |
293
|
|
|
* Prepares arguments and runs the command with [[passthru()]]. |
294
|
|
|
* @param array $arguments |
295
|
|
|
* @return integer the exit code |
296
|
|
|
*/ |
297
|
|
|
public function passthru(array $arguments = []) |
298
|
|
|
{ |
299
|
|
|
passthru($this->getBin() . $this->prepareCommand($arguments), $exitCode); |
300
|
|
|
return $exitCode; |
301
|
|
|
} |
302
|
|
|
|
303
|
|
|
/** |
304
|
|
|
* Prepares given command arguments. |
305
|
|
|
* @param array $arguments |
306
|
|
|
* @return string |
307
|
|
|
*/ |
308
|
|
|
public function prepareCommand(array $arguments = []) |
309
|
|
|
{ |
310
|
|
|
$result = ''; |
311
|
|
|
foreach ($arguments as $a) { |
312
|
|
|
$result .= ' ' . escapeshellarg($a); |
313
|
|
|
} |
314
|
|
|
|
315
|
|
|
return $result; |
316
|
|
|
} |
317
|
|
|
|
318
|
|
|
/** |
319
|
|
|
* Set path to binary executable file. |
320
|
|
|
* @param $bin |
321
|
|
|
* @internal param string $value |
322
|
|
|
*/ |
323
|
|
|
public function setBin($bin) |
324
|
|
|
{ |
325
|
|
|
$this->bin = $bin; |
326
|
|
|
} |
327
|
|
|
|
328
|
|
|
/** |
329
|
|
|
* Get path to the binary executable file. |
330
|
|
|
* @return string |
331
|
|
|
*/ |
332
|
|
|
public function getBin() |
333
|
|
|
{ |
334
|
|
|
if ($this->bin === null) { |
335
|
|
|
$this->bin = $this->detectBin(); |
336
|
|
|
} |
337
|
|
|
|
338
|
|
|
return $this->bin; |
339
|
|
|
} |
340
|
|
|
|
341
|
|
|
/** |
342
|
|
|
* Find path to the binary. |
343
|
|
|
* @return string |
344
|
|
|
*/ |
345
|
|
|
public function detectBin() |
346
|
|
|
{ |
347
|
|
|
$pathes = [ |
348
|
|
|
static::buildPath([$this->plugin->getVendorDir(), 'bin', $this->phpBin]), |
349
|
|
|
static::buildPath([$this->plugin->getComposer()->getConfig()->get('home'), 'vendor', 'bin', $this->phpBin]), |
350
|
|
|
]; |
351
|
|
|
foreach ($pathes as $path) { |
352
|
|
|
if (file_exists($path)) { |
353
|
|
|
return $path; |
354
|
|
|
} |
355
|
|
|
} |
356
|
|
|
|
357
|
|
|
return $this->name; |
358
|
|
|
} |
359
|
|
|
|
360
|
|
|
public static function buildPath($parts) |
361
|
|
|
{ |
362
|
|
|
return implode(DIRECTORY_SEPARATOR, array_filter($parts)); |
363
|
|
|
} |
364
|
|
|
} |
365
|
|
|
|
Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.
The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.
This check looks for comments that seem to be mostly valid code and reports them.