diff --git a/includes/drupal.inc b/includes/drupal.inc index dfa846b899..c2b05c322c 100644 --- a/includes/drupal.inc +++ b/includes/drupal.inc @@ -13,14 +13,17 @@ use Drush\Drush; function drush_drupal_load_autoloader($drupal_root) { static $autoloader = FALSE; - if (!$autoloader) { - $autoloader = require $drupal_root .'/autoload.php'; - if ($autoloader === TRUE) { - // The autoloader was already require(). Assume that Drush and Drupal share an autoloader per - // "Point autoload.php to the proper vendor directory" - https://www.drupal.org/node/2404989 - $autoloader = drush_get_context('DRUSH_CLASSLOADER'); - } + $autoloadFilePath = $drupal_root .'/autoload.php'; + if (!$autoloader && file_exists($autoloadFilePath)) { + $autoloader = require $autoloadFilePath; + } + + if ($autoloader === TRUE) { + // The autoloader was already required. Assume that Drush and Drupal share an autoloader per + // "Point autoload.php to the proper vendor directory" - https://www.drupal.org/node/2404989 + $autoloader = drush_get_context('DRUSH_CLASSLOADER'); } + return $autoloader; } diff --git a/includes/preflight.inc b/includes/preflight.inc index b0e6d9bb03..b0700bea3d 100644 --- a/includes/preflight.inc +++ b/includes/preflight.inc @@ -497,19 +497,9 @@ function drush_preflight() { drush_preflight_site(); // Check to see if anything changed during the 'site' preflight - // that might allow us to find our alias record now + // that might allow us to find our alias record now. if (empty($alias_record)) { $alias_record = _drush_sitealias_set_context_by_name($target_alias); - - // If the site alias settings changed late in the preflight, - // then run the preflight for the root and site contexts again. - if (!empty($alias_record)) { - $remote_host = drush_get_option('remote-host'); - if (!isset($remote_host)) { - drush_preflight_root(); - drush_preflight_site(); - } - } } // Fail if we could not find the selected site alias. @@ -544,6 +534,11 @@ function drush_preflight() { function drush_preflight_root() { $root = drush_get_option('root'); Drush::bootstrapManager()->locateRoot($root); + $root = Drush::bootstrapManager()->getRoot(); + + // Load the autoload file and provide it to the bootstrap manager. + $siteAutoloader = drush_drupal_load_autoloader($root); + Drush::bootstrapManager()->setAutoloader($siteAutoloader); // Load the config options from Drupal's /drush, ../drush, and sites/all/drush directories, // even prior to bootstrapping the root. @@ -799,14 +794,21 @@ function drush_preflight_command_dispatch() { $args = drush_get_arguments(); $command_name = array_shift($args); $root = Drush::bootstrapManager()->getRoot(); - $local_drush = drush_get_option('drush-script'); $is_local = drush_get_option('local'); $values = NULL; - if (!empty($root) && !empty($local_drush) && empty($is_local)) { + + $local_drush = drush_get_option('drush-script'); + if (!empty($local_drush)) { if (!drush_is_absolute_path($local_drush)) { $local_drush = $root . DIRECTORY_SEPARATOR . $local_drush; } $local_drush = realpath($local_drush); + } + + $drupal_root_from_alias = drush_get_option('root', $root, 'alias'); + $shouldRedispatch = (!empty($root)) && ($root != $drupal_root_from_alias); + + if (!empty($root) && !empty($local_drush) && empty($is_local)) { $this_drush = drush_find_drush(); // If there is a local Drush selected, and it is not the // same Drush that is currently running, redispatch to it. @@ -817,18 +819,23 @@ function drush_preflight_command_dispatch() { // infinite loop. if (file_exists($local_drush) && !drush_is_nested_directory(dirname($root), $this_drush)) { $uri = drush_get_context('DRUSH_SELECTED_URI'); - $aditional_options = array( - 'root' => $root, - ); - if (!empty($uri)) { - $aditional_options['uri'] = $uri; - } - // We need to chdir to the Drupal root here, for the - // benefit of the Drush wrapper. - chdir($root); - $values = drush_do_command_redispatch(is_array($command) ? $command : $command_name, $args, NULL, NULL, $local_drush, TRUE, $aditional_options); + $shouldRedispatch = true; } } + + if ($shouldRedispatch) { + $aditional_options = [ + 'root' => $drupal_root_from_alias, + ]; + if (!empty($uri)) { + $aditional_options['uri'] = $uri; + } + // We need to chdir to the Drupal root here, for the + // benefit of the Drush wrapper. + chdir($root); + $values = drush_do_command_redispatch(is_array($command) ? $command : $command_name, $args, NULL, NULL, $local_drush, TRUE, $aditional_options); + } + // If the command sets the 'handle-remote-commands' flag, then we will short-circuit // remote command dispatching and site-list command dispatching, and always let // the command handler run on the local machine. diff --git a/includes/sitealias.inc b/includes/sitealias.inc index 3078a369c0..f01bb5616c 100644 --- a/includes/sitealias.inc +++ b/includes/sitealias.inc @@ -1772,8 +1772,6 @@ function _drush_sitealias_set_context_by_name($alias, $prefix = '') { } _drush_sitealias_cache_alias('@self', $site_alias_settings); - // Change the selected site to match the new --root and --uri, if any were set. - _drush_preflight_root_uri(); } return $site_alias_settings; } diff --git a/src/Boot/AutoloaderAwareInterface.php b/src/Boot/AutoloaderAwareInterface.php new file mode 100644 index 0000000000..d01fe879ad --- /dev/null +++ b/src/Boot/AutoloaderAwareInterface.php @@ -0,0 +1,11 @@ +loader = $loader; + } + + public function autoloader() + { + return $this->loader; + } + + public function hasAutoloader() + { + return isset($this->loader); + } +} diff --git a/src/Boot/BootstrapManager.php b/src/Boot/BootstrapManager.php index 41144e44f3..16f1d5599e 100644 --- a/src/Boot/BootstrapManager.php +++ b/src/Boot/BootstrapManager.php @@ -8,9 +8,10 @@ use Psr\Log\LoggerAwareInterface; use Psr\Log\LoggerAwareTrait; -class BootstrapManager implements LoggerAwareInterface +class BootstrapManager implements LoggerAwareInterface, AutoloaderAwareInterface { use LoggerAwareTrait; + use AutoloaderAwareTrait; /** * @var DrupalFinder @@ -137,6 +138,9 @@ public function bootstrapObjectForRoot($path) { foreach ($this->bootstrapCandidates as $candidate) { if ($candidate->validRoot($path)) { + if ($candidate instanceof AutoloaderAwareInterface) { + $candidate->setAutoloader($this->autoloader()); + } return $candidate; } } diff --git a/src/Boot/DrupalBoot8.php b/src/Boot/DrupalBoot8.php index 7a3dd15775..bf1eb99833 100644 --- a/src/Boot/DrupalBoot8.php +++ b/src/Boot/DrupalBoot8.php @@ -11,8 +11,9 @@ use Drush\Log\LogLevel; -class DrupalBoot8 extends DrupalBoot +class DrupalBoot8 extends DrupalBoot implements AutoloaderAwareInterface { + use AutoloaderAwareTrait; /** * @var \Drupal\Core\DrupalKernelInterface @@ -40,8 +41,10 @@ public function validRoot($path) public function getVersion($drupal_root) { - // Load the autoloader so we can access the class constants. - drush_drupal_load_autoloader($drupal_root); + // Are the class constants available? + if (!$this->hasAutoloader()) { + throw new \Exception('Cannot access Drupal 8 class constants - Drupal autoloader not loaded yet.'); + } // Drush depends on bootstrap being loaded at this point. require_once $drupal_root .'/core/includes/bootstrap.inc'; if (defined('\Drupal::VERSION')) { @@ -129,7 +132,7 @@ public function bootstrapDrupalDatabase() public function bootstrapDrupalConfiguration() { $this->request = Request::createFromGlobals(); - $classloader = drush_drupal_load_autoloader(DRUPAL_ROOT); + $classloader = $this->autoloader(); // @todo - use Request::create() and then no need to set PHP superglobals $kernelClass = new \ReflectionClass('\Drupal\Core\DrupalKernel'); if ($kernelClass->hasMethod('addServiceModifier')) { diff --git a/tests/siteAliasTest.php b/tests/siteAliasTest.php index 33fb18d7a3..0419cb66d5 100644 --- a/tests/siteAliasTest.php +++ b/tests/siteAliasTest.php @@ -8,6 +8,69 @@ * @group base */ class saCase extends CommandUnishTestCase { + /** + * Covers the following responsibilities: + * - Dispatching a Drush command via an alias that is defined in a + * site-local alias file. The target alias points to a different + * Drupal site at a different docroot on the same system. + */ + function testSiteLocalAliasDispatch() { + $sites = $this->setUpDrupal(2, TRUE); + + // Make a separate copy of the stage site so that we can test + // to see if we can switch to a separate site via an site-local alias. + $dev_root = $sites['dev']['root']; + $drush_sut = dirname($dev_root); + $other_sut = dirname($drush_sut) . '/drush-other-sut'; + $other_root = $other_sut . '/web'; + @mkdir($other_sut); + self::recursive_copy($dev_root, $other_root); + + if (!file_exists($drush_sut . '/composer.json') || !file_exists($drush_sut . '/composer.lock')) { + $this->markTestSkipped('This test does not run in the highest / lowest configurations.'); + } + + copy($drush_sut . '/composer.json', $other_sut . '/composer.json'); + copy($drush_sut . '/composer.lock', $other_sut . '/composer.lock'); + + // Hopefully this will run quickly from the cache. + passthru("composer --working-dir=$other_sut install"); + + $aliasPath = $dev_root . '/drush'; + $aliasFile = "$aliasPath/aliases.drushrc.php"; + $aliasContents = << '$other_root', + 'uri' => 'stage', +); +EOD; + if (!is_dir($aliasPath)) { + mkdir($aliasPath); + } + file_put_contents($aliasFile, $aliasContents); + + // Ensure that we can access the 'other' alias from the context + // of the 'dev' site, and that it has the right drupal root. + $options = [ + ]; + $this->drush('sa', array('@other'), $options, '@dev'); + $output = $this->getOutput(); + $this->assertContains("root: $other_root", $output); + + // Ensure that we can get status on the 'other' alias when the + // root of the dev site is provided (to allow Drush to find the 'other' alias) + $options = [ + 'root' => $dev_root, + 'format' => 'yaml', + ]; + $this->drush('core-status', [], $options, '@other'); + $output = $this->getOutput(); + $this->assertContains("root: $other_root", $output); + } + /** * Covers the following responsibilities. * - Dispatching a Drush command that uses strict option handling