Skip to content

Commit 8d18c59

Browse files
Retain 'contrib' / 'custom' paths for site-wide Drush extensions (#3687)
1 parent 7a51a7c commit 8d18c59

File tree

12 files changed

+181
-38
lines changed

12 files changed

+181
-38
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ api
1111
.idea/
1212
# https://github.com/drush-ops/drush/issues/2688
1313
composer.lock
14-
/sut/
14+
sut/*
15+
!sut/drush
1516
/sandbox/
1617
.env

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
"ext-dom": "*",
3535
"chi-teck/drupal-code-generator": "^1.26.1",
3636
"composer/semver": "^1.4",
37-
"consolidation/annotated-command": "^2.8.1",
37+
"consolidation/annotated-command": "^2.9.1",
3838
"consolidation/config": "^1.1.0",
3939
"consolidation/output-formatters": "^3.1.12",
4040
"consolidation/robo": "^1.1.5",

docs/commands.md

Lines changed: 38 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -47,31 +47,60 @@ Altering Drush Command Info
4747

4848
Drush command info (annotations) can be altered from other modules. This is done by creating and registering 'command info alterers'. Alterers are class services that are able to intercept and manipulate an existing command annotation.
4949

50-
In order to alter an existing command info, follow the next steps:
50+
In order to alter an existing command info, follow the steps below:
5151

5252
1. In the module that wants to alter a command info, add a service class that implements the `\Consolidation\AnnotatedCommand\CommandInfoAltererInterface`.
5353
1. In the module `drush.services.yml` declare a service pointing to this class and tag the service with the `drush.command_info_alterer` tag.
54-
1. In the class implement the alteration logic the `alterCommandInfo()` method.
55-
1. Along with the alter code, it's strongly recommended to log a debug message explaining what exactly was altered. This would allow the easy debugging. Also it's a good practice to inject the the logger in the class constructor.
54+
1. In that class, implement the alteration logic in the `alterCommandInfo()` method.
55+
1. Along with the alter code, it's strongly recommended to log a debug message explaining what exactly was altered. This makes things easier on others who may need to debug the interaction of the alter code with other modules. Also it's a good practice to inject the the logger in the class constructor.
5656

5757
For an example, see the alterer class provided by the testing 'woot' module: `tests/resources/modules/d8/woot/src/WootCommandInfoAlterer.php`.
5858

59-
Global Drush Commands
59+
Site-Wide Drush Commands
6060
==============================
6161

62-
Commandfiles that don't ship inside Drupal modules are called 'global' commandfiles. See the [examples/Commands](https://github.com/drush-ops/drush/tree/master/examples/Commands) folder for examples. In general, it's better to use modules to carry your Drush commands. If you still prefer using a global commandfiles, here are two examples of valid commandfile names and namespaces:
62+
Commandfiles that are installed in a Drupal site and are not bundled inside a Drupal module are called 'site-wide' commandfiles. Site-wide commands may either be added directly to the Drupal site's repository (e.g. for site-specific policy files), or via `composer require`. See the [examples/Commands](/examples/Commands) folder for examples. In general, it's better to use modules to carry your Drush commands, as module-based commands may [participate in Drupal's dependency injection via the drush.services.yml](#specifying-the-services-file).
63+
64+
If you still prefer using site-wide commandfiles, here are some examples of valid commandfile names and namespaces:
6365

6466
1. Simple
6567
- Filename: $PROJECT_ROOT/drush/Commands/ExampleCommands.php
6668
- Namespace: Drush\Commands
67-
1. Nested (e.g. Commandfile is part of a Composer package)
68-
- Filename: $PROJECT_ROOT/drush/Commands/dev_modules/ExampleCommands.php
69+
1. Nested in a subdirectory committed to the site's repository
70+
- Filename: $PROJECT_ROOT/drush/Commands/example/ExampleCommands.php
71+
- Namespace: Drush\Commands\example
72+
1. Nested in a subdirectory installed via a Composer package
73+
- Filename: $PROJECT_ROOT/drush/Commands/contrib/dev_modules/ExampleCommands.php
6974
- Namespace: Drush\Commands\dev_modules
7075

76+
Installing commands as part of a Composer project requires that the project's type be `drupal-drush`, and that the `installer-paths` in the Drupal site's composer.json file contains `"drush/Commands/contrib/{$name}": ["type:drupal-drush"]`. It is also possible to commit projects with a similar layout using a directory named `custom` in place of `contrib`; if this is done, then the directory `custom` will not be considered to be part of the commandfile's namespace.
77+
78+
If a site-wide commandfile is added via a Composer package, then it may declare any dependencies that it may need in its composer.json file. Site-wide commandfiles that are committed directly to a site's repository only have access to the dependencies already available in the site. Site-wide commandfiles should declare their Drush version compatibility via a `conflict` directive. For example, a Composer-managed site-wide command that works with both Drush 8 and Drush 9 might contain something similar to the following in its composer.json file:
79+
```
80+
"conflict": {
81+
"drush/drush": "<8.1 || >=9.0 <9.5 || >=10.0",
82+
}
83+
```
84+
Using `require` in place of `conflict` is not recommended.
85+
86+
Global Drush Commands
87+
==============================
88+
89+
Commandfiles that are not part of any Drupal site are called 'global' commandfiles. Global commandfiles are not supported by default; in order to enable them, you must configure your `drush.yml` configuration file to add an `include` search location.
90+
91+
For example:
92+
93+
drush:
94+
paths:
95+
include:
96+
- '${env.home}/.drush/commands'
97+
98+
With this configuration in place, global commands may be placed as described in the Site-Wide Drush Commands section above. Global commandfiles may not declare any dependencies of their own; they may only use those dependencies already available via the autoloader.
99+
71100
##### Tips
72101
1. The filename must be have a name like Commands/ExampleCommands.php
73102
1. The prefix `Example` can be whatever string you want.
74103
1. The file must end in `Commands.php`
75-
1. The directory above Commands must be one of:
76-
1. A Folder listed in the 'include' option. include may be provided via config or via CLI.
104+
1. The directory above `Commands` must be one of:
105+
1. A Folder listed in the 'include' option. Include may be provided via [config](#global-drush-commands) or via CLI.
77106
1. ../drush, /drush or /sites/all/drush. These paths are relative to Drupal root.

scenarios/isolation-phpunit4/composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@
6060
"optimize-autoloader": true,
6161
"preferred-install": "dist",
6262
"sort-packages": true,
63+
"process-timeout": 2400,
6364
"platform": {
6465

6566
},

scenarios/isolation/composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@
6060
"optimize-autoloader": true,
6161
"preferred-install": "dist",
6262
"sort-packages": true,
63+
"process-timeout": 2400,
6364
"platform": {
6465

6566
},

src/Application.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,7 @@ public function configureAndRegisterCommands(InputInterface $input, OutputInterf
316316

317317
$discovery = $this->commandDiscovery();
318318
$commandClasses = $discovery->discover($commandfileSearchpath, '\Drush');
319+
319320
$this->loadCommandClasses($commandClasses);
320321

321322
// Uncomment the lines below to use Console's built in help and list commands.
@@ -350,6 +351,10 @@ protected function commandDiscovery()
350351
$discovery = new CommandFileDiscovery();
351352
$discovery
352353
->setIncludeFilesAtBase(true)
354+
->setSearchDepth(3)
355+
->ignoreNamespacePart('contrib', 'Commands')
356+
->ignoreNamespacePart('custom', 'Commands')
357+
->ignoreNamespacePart('src')
353358
->setSearchLocations(['Commands', 'Hooks', 'Generators'])
354359
->setSearchPattern('#.*(Command|Hook|Generator)s?.php$#');
355360
return $discovery;

src/Config/ConfigLocator.php

Lines changed: 4 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
use Drush\Config\Loader\YamlConfigLoader;
66
use Consolidation\Config\Loader\ConfigProcessor;
77
use Consolidation\Config\Util\EnvConfig;
8-
use Symfony\Component\Finder\Finder;
98

109
/**
1110
* Locate Drush configuration files and load them into the configuration
@@ -422,7 +421,7 @@ public function getCommandFilePaths($commandPaths, $root)
422421
{
423422
$builtin = $this->getBuiltinCommandFilePaths();
424423
$included = $this->getIncludedCommandFilePaths($commandPaths);
425-
$site = $this->getSiteCommandFilePaths(["$root/drush", dirname($root) . '/drush']);
424+
$site = $this->getSiteCommandFilePaths($root);
426425

427426
return array_merge(
428427
$builtin,
@@ -462,29 +461,11 @@ protected function getIncludedCommandFilePaths($commandPaths)
462461
* 'dirname($root)/drush' directory that contains a composer.json
463462
* file or a 'Commands' or 'src/Commands' directory.
464463
*/
465-
protected function getSiteCommandFilePaths($directories)
464+
protected function getSiteCommandFilePaths($root)
466465
{
467-
$result = [];
468-
469-
$directories = array_filter($directories, 'is_dir');
470-
471-
if (empty($directories)) {
472-
return $result;
473-
}
466+
$directories = ["$root/drush", dirname($root) . '/drush', "$root/sites/all/drush"];
474467

475-
// Find projects
476-
$finder = new Finder();
477-
$finder->directories()
478-
->ignoreUnreadableDirs()
479-
->path('#^src/Commands$|^Commands$#')
480-
->in($directories)
481-
->depth('<= 3');
482-
483-
foreach ($finder as $file) {
484-
$result[] = dirname($file->getRealPath());
485-
}
486-
487-
return $result;
468+
return array_filter($directories, 'is_dir');
488469
}
489470

490471
/**
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php
2+
namespace Drush\Commands;
3+
4+
use Consolidation\AnnotatedCommand\AnnotationData;
5+
use Consolidation\AnnotatedCommand\CommandData;
6+
use Consolidation\AnnotatedCommand\Events\CustomEventAwareInterface;
7+
use Consolidation\AnnotatedCommand\Events\CustomEventAwareTrait;
8+
use Consolidation\OutputFormatters\StructuredData\RowsOfFields;
9+
use Symfony\Component\Console\Input\InputInterface;
10+
use Symfony\Component\Console\Output\OutputInterface;
11+
use Symfony\Component\Console\Style\SymfonyStyle;
12+
use Drush\Commands\DrushCommands;
13+
14+
use Drush\Style\DrushStyle;
15+
use Drush\Utils\StringUtils;
16+
17+
/**
18+
* Site-wide commands for the System-Under-Test site
19+
*/
20+
21+
class SimpleSutCommands extends DrushCommands
22+
{
23+
/**
24+
* Show a fabulous picture.
25+
*
26+
* @command sut:simple
27+
*/
28+
public function example()
29+
{
30+
$this->logger()->notice(dt("This is an example site-wide command committed to the repository in the SUT inside of the 'drush/Commands' directory."));
31+
}
32+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php
2+
namespace Drush\Commands\example_site_wide_command;
3+
4+
use Consolidation\AnnotatedCommand\AnnotationData;
5+
use Consolidation\AnnotatedCommand\CommandData;
6+
use Consolidation\AnnotatedCommand\Events\CustomEventAwareInterface;
7+
use Consolidation\AnnotatedCommand\Events\CustomEventAwareTrait;
8+
use Consolidation\OutputFormatters\StructuredData\RowsOfFields;
9+
use Symfony\Component\Console\Input\InputInterface;
10+
use Symfony\Component\Console\Output\OutputInterface;
11+
use Symfony\Component\Console\Style\SymfonyStyle;
12+
use Drush\Commands\DrushCommands;
13+
14+
use Drush\Style\DrushStyle;
15+
use Drush\Utils\StringUtils;
16+
17+
/**
18+
* Site-wide commands for the System-Under-Test site
19+
*/
20+
21+
class NestedSutCommands extends DrushCommands
22+
{
23+
/**
24+
* Show a fabulous picture.
25+
*
26+
* @command sut:nested
27+
*/
28+
public function example()
29+
{
30+
$this->logger()->notice(dt("This is an example site-wide command committed to the repository in the SUT nested inside a custom/example-site-wide-command directory."));
31+
}
32+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php
2+
namespace Drush\Commands\example_site_wide_command;
3+
4+
use Consolidation\AnnotatedCommand\AnnotationData;
5+
use Consolidation\AnnotatedCommand\CommandData;
6+
use Consolidation\AnnotatedCommand\Events\CustomEventAwareInterface;
7+
use Consolidation\AnnotatedCommand\Events\CustomEventAwareTrait;
8+
use Consolidation\OutputFormatters\StructuredData\RowsOfFields;
9+
use Symfony\Component\Console\Input\InputInterface;
10+
use Symfony\Component\Console\Output\OutputInterface;
11+
use Symfony\Component\Console\Style\SymfonyStyle;
12+
use Drush\Commands\DrushCommands;
13+
14+
use Drush\Style\DrushStyle;
15+
use Drush\Utils\StringUtils;
16+
17+
/**
18+
* Site-wide commands for the System-Under-Test site
19+
*/
20+
21+
class NestedSrcSutCommands extends DrushCommands
22+
{
23+
/**
24+
* Show a fabulous picture.
25+
*
26+
* @command sut:nested-src
27+
*/
28+
public function example()
29+
{
30+
$this->logger()->notice(dt("This is an example site-wide command committed to the repository in the SUT nested inside a custom/example-site-wide-command/src directory."));
31+
}
32+
}

0 commit comments

Comments
 (0)