configFileVariant = $configFileVariant; $this->config = new DrushConfig(); // Add placeholders to establish priority. We add // contexts from lowest to highest priority. $this->config->addPlaceholder(self::DRUSH_CONTEXT); if (!empty($envPrefix)) { $envConfig = new EnvConfig($envPrefix); $this->config->addContext(self::ENV_CONTEXT, $envConfig); } $this->config->addPlaceholder(self::USER_CONTEXT); $this->config->addPlaceholder(self::DRUPAL_CONTEXT); $this->config->addPlaceholder(self::SITE_CONTEXT); // not implemented yet (multisite) $this->config->addPlaceholder(self::ALIAS_CONTEXT); $this->config->addPlaceholder(self::PREFLIGHT_CONTEXT); $this->config->addPlaceholder(self::ENVIRONMENT_CONTEXT); $this->isLocal = false; $this->configFilePaths = []; } /** * Put the config locator into 'local 'mode. * * @param bool $isLocal */ public function setLocal($isLocal) { $this->isLocal = $isLocal; } /** * Keep track of the source that every config item originally came from. * Potentially useful in debugging. If collectSources(true) is called, * then the sources will be accumulated as config files are loaded. Otherwise, * this information will not be saved. * * @param bool $collect * @return $this */ public function collectSources($collect = true) { $this->sources = $collect ? [] : false; return $this; } /** * Return all of the sources for every configuration item. The key * is the address of the configuration item, and the value is the * configuration file it was loaded from. Note that this method will * return just an empty array unless collectSources(true) is called * prior to loading configuration files. * * @return array */ public function sources() { return $this->sources; } /** * Return a list of all configuration files that were loaded. * * @return string[] */ public function configFilePaths() { return $this->configFilePaths; } /** * Accumulate the sources provided by the configuration loader. */ protected function addToSources(array $sources) { if (!is_array($this->sources)) { return; } $this->sources = array_merge_recursive($this->sources, $sources); } /** * Return the configuration object. Create it and load it with * all identified configuration if necessary. * * @return Config */ public function config() { return $this->config; } /** * Exports all of the information stored in the environment, and adds * it to the configuration. The Environment object itself is only * available during preflight; the information exported here may be * obtained by commands et. al. as needed. @see Environment::exportConfigData() * * @param Environment $environent * @return $this */ public function addEnvironment(Environment $environment) { $this->config->getContext(self::ENVIRONMENT_CONTEXT)->import($environment->exportConfigData()); return $this; } /** * Add config paths defined in preflight configuration. * * @param array $paths * @return $this */ public function addPreflightConfigFiles($filepaths) { $this->addConfigPaths(self::PREFLIGHT_CONTEXT, (array) $filepaths); return $this; } /** * Take any configuration from the active alias record, and add it * to our configuratino. * @return $this */ public function addAliasConfig($aliasConfig) { $this->config->addContext(self::ALIAS_CONTEXT, $aliasConfig); return $this; } /** * Given the path provided via --config and the user's home directory, * add all of the user configuration paths. * * In 'local' mode, only the --config location is used. * @return $this */ public function addUserConfig($configPaths, $systemConfigPath, $userConfigDir) { $paths = $configPaths; if (!$this->isLocal) { $paths = array_merge($paths, [ $systemConfigPath, $userConfigDir ]); } $this->addConfigPaths(self::USER_CONTEXT, $paths); return $this; } /** * Add the Drush project directory as a configuration search location. * * @param $drushProjectDir path to the drush project directory * @return $this */ public function addDrushConfig($drushProjectDir) { if (!$this->isLocal) { $this->addConfigPaths(self::DRUSH_CONTEXT, [ $drushProjectDir ]); } return $this; } /** * Add any configuration files found around the Drupal root of the * selected site. * * @param Path to the selected Drupal site * @return $this */ public function addSitewideConfig($siteRoot) { // There might not be a site. if (!is_dir($siteRoot)) { return; } // We might have already processed this root. $siteRoot = realpath($siteRoot); if (in_array($siteRoot, $this->siteRoots)) { return; } // Remember that we've seen this location. $this->siteRoots[] = $siteRoot; $this->addConfigPaths(self::DRUPAL_CONTEXT, [ dirname($siteRoot) . '/drush', "$siteRoot/drush", "$siteRoot/sites/all/drush" ]); return $this; } /** * Add any configuration file found at any of the provided paths. Both the * provided location, and the directory `config` inside each provided location * is searched for a drush.yml file. * * @param string $contextName Which context to put all configuration files in. * @param string[] $paths List of paths to search for configuration. * @return $this */ public function addConfigPaths($contextName, $paths) { $loader = new YamlConfigLoader(); // Make all of the config values parsed so far available in evaluations. $reference = $this->config()->export(); $processor = new ConfigProcessor(); $processor->useMergeStrategyForKeys(['drush.paths.include', 'drush.paths.alias-path']); $context = $this->config->getContext($contextName); $processor->add($context->export()); $candidates = [ 'drush.yml', ]; if ($this->configFileVariant) { $candidates[] = "drush{$this->configFileVariant}.yml"; } $candidates = $this->expandCandidates($candidates, 'config/'); $config_files = $this->findConfigFiles($paths, $candidates); $this->addConfigFiles($processor, $loader, $config_files); // Complete config import. $this->addToSources($processor->sources()); $context->import($processor->export($reference)); $this->config->addContext($contextName, $context); $this->processedConfigPaths = array_merge($this->processedConfigPaths, $paths); // Recursive case. if ($context->has('drush.paths.config')) { $new_config_paths = array_diff((array) $context->get('drush.paths.config'), $this->processedConfigPaths); if ($new_config_paths) { $this->addConfigPaths($contextName, $new_config_paths); } } return $this; } /** * Adds $configFiles config files. * * @param ConfigProcessor $processor * @param ConfigLoaderInterface $loader * @param array $configFiles */ protected function addConfigFiles(ConfigProcessor $processor, ConfigLoaderInterface $loader, array $configFiles) { foreach ($configFiles as $configFile) { $processor->extend($loader->load($configFile)); $this->configFilePaths[] = $configFile; } } /** * Given a list of paths, and candidates that might exist at each path, * return all of the candidates that can be found. Candidates may be * either directories or files. * * @param string[] $paths * @param string[] $candidates * @return string[] paths */ protected function identifyCandidates($paths, $candidates) { $configFiles = []; foreach ($paths as $path) { $configFiles = array_merge($configFiles, $this->identifyCandidatesAtPath($path, $candidates)); } return $configFiles; } /** * Search for all matching candidate locations at a single path. * Candidate locations may be either directories or files. * * @param string $path * @param string[] $candidates * @return string[] */ protected function identifyCandidatesAtPath($path, $candidates) { if (!is_dir($path)) { return []; } $result = []; foreach ($candidates as $candidate) { $configFile = empty($candidate) ? $path : "$path/$candidate"; if (file_exists($configFile)) { $result[] = $configFile; } } return $result; } /** * Get the site aliases according to preflight arguments and environment. * * @param $preflightArgs * @param Environment $environment * * @return array */ public function getSiteAliasPaths($paths, Environment $environment) { // In addition to the paths passed in to us (from --alias-paths // commandline options), add some site-local locations. $base_dirs = array_filter(array_merge($this->siteRoots, [$this->composerRoot])); $site_local_paths = array_map( function ($item) { return "$item/drush/sites"; }, $base_dirs ); $paths = array_merge($paths, $site_local_paths); return $paths; } /** * Get the commandfile paths according to preflight arguments. * * @param $preflightArgs * * @return array */ public function getCommandFilePaths($commandPaths, $root) { $builtin = $this->getBuiltinCommandFilePaths(); $included = $this->getIncludedCommandFilePaths($commandPaths); $site = $this->getSiteCommandFilePaths($root); return array_merge( $builtin, $included, $site ); } /** * Return all of the built-in commandfile locations */ protected function getBuiltinCommandFilePaths() { return [ dirname(__DIR__), ]; } /** * Return all of the commandfile locations specified via * an 'include' option. */ protected function getIncludedCommandFilePaths($commandPaths) { $searchpath = []; // Commands specified by 'include' option foreach ($commandPaths as $commandPath) { if (is_dir($commandPath)) { $searchpath[] = $commandPath; } } return $searchpath; } /** * Return all of the commandfile paths in any '$root/drush' or * 'dirname($root)/drush' directory that contains a composer.json * file or a 'Commands' or 'src/Commands' directory. */ protected function getSiteCommandFilePaths($root) { $directories = ["$root/drush", dirname($root) . '/drush', "$root/sites/all/drush"]; return array_filter($directories, 'is_dir'); } /** * Sets the composer root. * * @param $selectedComposerRoot */ public function setComposerRoot($selectedComposerRoot) { $this->composerRoot = $selectedComposerRoot; } /** * Double the candidates, adding '$prefix' before each existing one. */ public function expandCandidates($candidates, $prefix) { $additional = array_map( function ($item) use ($prefix) { return $prefix . $item; }, $candidates ); return array_merge($candidates, $additional); } /** * Given an array of paths, separates files and directories. * * @param array $paths * An array of config paths. These may be config files or paths to dirs * containing config files. * @param array $candidates * An array filenames that are considered config files. * * @return array * An array. The first row is an array of files, the second row is an * array of dirs. */ protected function findConfigFiles($paths, $candidates) { $files = []; $dirs = []; foreach ($paths as $path) { if (file_exists($path)) { if (is_dir($path)) { $dirs[] = realpath($path); } else { $files[] = realpath($path); } } } // Search directories for config file candidates. $discovered_config_files = $this->identifyCandidates($dirs, $candidates); // Merge discoverd candidates with explicitly specified config files. $config_files = array_merge($discovered_config_files, $files); return $config_files; } }