2 namespace Drush\Config;
4 use Composer\Autoload\ClassLoader;
7 use Drush\Utils\FsUtils;
8 use Webmozart\PathUtil\Path;
11 * Store information about the environment
16 protected $originalCwd;
18 protected $sharePrefix;
19 protected $drushBasePath;
23 protected $configFileVariant;
26 protected $siteLoader;
29 * Environment constructor
30 * @param string $homeDir User home directory.
31 * @param string $cwd The current working directory at the time Drush was called.
32 * @param string $autoloadFile Path to the autoload.php file.
34 public function __construct($homeDir, $cwd, $autoloadFile)
36 $this->homeDir = $homeDir;
37 $this->originalCwd = Path::canonicalize($cwd);
38 $this->etcPrefix = '';
39 $this->sharePrefix = '';
40 $this->drushBasePath = dirname(dirname(__DIR__));
41 $this->vendorDir = FsUtils::realpath(dirname($autoloadFile));
45 * Load the autoloader for the selected Drupal site
50 public function loadSiteAutoloader($root)
52 $autloadFilePath = "$root/autoload.php";
53 if (!file_exists($autloadFilePath)) {
57 if ($this->siteLoader) {
58 return $this->siteLoader;
61 $this->siteLoader = require $autloadFilePath;
62 if ($this->siteLoader === false) {
63 // Nothing more to do. See https://github.com/drush-ops/drush/issues/3741.
66 if ($this->siteLoader === true) {
67 // The autoloader was already required. Assume that Drush and Drupal share an autoloader per
68 // "Point autoload.php to the proper vendor directory" - https://www.drupal.org/node/2404989
69 $this->siteLoader = $this->loader;
72 // Ensure that the site's autoloader has highest priority. Usually,
73 // the first classloader registered gets the first shot at loading classes.
74 // We want Drupal's classloader to be used first when a class is loaded,
75 // and have Drush's classloader only be called as a fallback measure.
76 $this->siteLoader->unregister();
77 $this->siteLoader->register(true);
79 return $this->siteLoader;
83 * Return the name of the user running drush.
87 protected function getUsername()
90 if (!$name = getenv("username")) { // Windows
91 if (!$name = getenv("USER")) {
92 // If USER not defined, use posix
93 if (function_exists('posix_getpwuid')) {
94 $processUser = posix_getpwuid(posix_geteuid());
95 $name = $processUser['name'];
102 protected function getTmp()
106 // Get user specific and operating system temp folders from system environment variables.
107 // See http://www.microsoft.com/resources/documentation/windows/xp/all/proddocs/en-us/ntcmds_shelloverview.mspx?mfr=true
108 $tempdir = getenv('TEMP');
109 if (!empty($tempdir)) {
110 $directories[] = $tempdir;
112 $tmpdir = getenv('TMP');
113 if (!empty($tmpdir)) {
114 $directories[] = $tmpdir;
116 // Operating system specific dirs.
117 if (self::isWindows()) {
118 $windir = getenv('WINDIR');
119 if (isset($windir)) {
120 // WINDIR itself is not writable, but it always contains a /Temp dir,
121 // which is the system-wide temporary directory on older versions. Newer
122 // versions only allow system processes to use it.
123 $directories[] = Path::join($windir, 'Temp');
126 $directories[] = Path::canonicalize('/tmp');
128 $directories[] = Path::canonicalize(sys_get_temp_dir());
130 foreach ($directories as $directory) {
131 if (is_dir($directory) && is_writable($directory)) {
132 $temporary_directory = $directory;
137 if (empty($temporary_directory)) {
138 // If no directory has been found, create one in cwd.
139 $temporary_directory = Path::join(Drush::config()->cwd(), 'tmp');
140 drush_mkdir($temporary_directory, true);
141 if (!is_dir($temporary_directory)) {
142 throw new \Exception(dt("Unable to create a temporary directory."));
144 // Function not available yet - this is not likely to get reached anyway.
145 // drush_register_file_for_deletion($temporary_directory);
147 return $temporary_directory;
151 * Convert the environment object into an exported configuration
154 * @see PreflightArgs::applyToConfig(), which also exports information to config.
156 * @return array Nested associative array that is overlayed on configuration.
158 public function exportConfigData()
161 // Information about the environment presented to Drush
163 'cwd' => $this->cwd(),
164 'home' => $this->homeDir(),
165 'user' => $this->getUsername(),
166 'is-windows' => $this->isWindows(),
167 'tmp' => $this->getTmp(),
169 // These values are available as global options, and
170 // will be passed in to the FormatterOptions et. al.
172 'width' => $this->calculateColumns(),
174 // Information about the directories where Drush found assets, etc.
176 'base-dir' => $this->drushBasePath,
177 'vendor-dir' => $this->vendorPath(),
178 'docs-dir' => $this->docsPath(),
179 'user-dir' => $this->userConfigPath(),
180 'system-dir' => $this->systemConfigPath(),
181 'system-command-dir' => $this->systemCommandFilePath(),
184 'site-file-previous' => $this->getSiteSetAliasFilePath('drush-drupal-prev-site-'),
185 'site-file-current' => $this->getSiteSetAliasFilePath(),
191 * The base directory of the Drush application itself
192 * (where composer.json et.al. are found)
196 public function drushBasePath()
198 return $this->drushBasePath;
202 * Get the site:set alias from the current site:set file path.
204 * @return bool|string
206 public function getSiteSetAliasName()
208 $site_filename = $this->getSiteSetAliasFilePath();
209 if (file_exists($site_filename)) {
210 $site = file_get_contents($site_filename);
219 * User's home directory
223 public function homeDir()
225 return $this->homeDir;
229 * The user's Drush configuration directory, ~/.drush
233 public function userConfigPath()
235 return $this->homeDir() . '/.drush';
238 public function setConfigFileVariant($variant)
240 $this->configFileVariant = $variant;
244 * Get the config file variant -- defined to be
245 * the Drush major version number. This is for
246 * loading drush.yml and drush9.yml, etc.
248 public function getConfigFileVariant()
250 return $this->configFileVariant;
254 * The original working directory
258 public function cwd()
260 return $this->originalCwd;
264 * Return the path to Drush's vendor directory
268 public function vendorPath()
270 return $this->vendorDir;
274 * The class loader returned when the autoload.php file is included.
276 * @return \Composer\Autoload\ClassLoader
278 public function loader()
280 return $this->loader;
284 * Set the class loader from the autload.php file, if available.
286 * @param \Composer\Autoload\ClassLoader $loader
288 public function setLoader(ClassLoader $loader)
290 $this->loader = $loader;
294 * Alter our default locations based on the value of environment variables
298 public function applyEnvironment()
300 // Copy ETC_PREFIX and SHARE_PREFIX from environment variables if available.
301 // This alters where we check for server-wide config and alias files.
302 // Used by unit test suite to provide a clean environment.
303 $this->setEtcPrefix(getenv('ETC_PREFIX'));
304 $this->setSharePrefix(getenv('SHARE_PREFIX'));
310 * Set the directory prefix to locate the directory that Drush will
311 * use as /etc (e.g. during the functional tests)
313 * @param string $etcPrefix
316 public function setEtcPrefix($etcPrefix)
318 if (isset($etcPrefix)) {
319 $this->etcPrefix = $etcPrefix;
325 * Set the directory prefix to locate the directory that Drush will
326 * use as /user/share (e.g. during the functional tests)
327 * @param string $sharePrefix
330 public function setSharePrefix($sharePrefix)
332 if (isset($sharePrefix)) {
333 $this->sharePrefix = $sharePrefix;
334 $this->docPrefix = null;
340 * Return the directory where Drush's documentation is stored. Usually
341 * this is within the Drush application, but some Drush RPM distributions
342 * & c. for Linux platforms slice-and-dice the contents and put the docs
347 public function docsPath()
349 if (!$this->docPrefix) {
350 $this->docPrefix = $this->findDocsPath($this->drushBasePath);
352 return $this->docPrefix;
356 * Locate the Drush documentation. This is recalculated whenever the
357 * share prefix is changed.
359 * @param string $drushBasePath
362 protected function findDocsPath($drushBasePath)
365 "$drushBasePath/README.md",
366 static::systemPathPrefix($this->sharePrefix, '/usr') . '/share/docs/drush/README.md',
368 return $this->findFromCandidates($candidates);
372 * Check a list of directories and return the first one that exists.
374 * @param array $candidates
375 * @return string|boolean
377 protected function findFromCandidates($candidates)
379 foreach ($candidates as $candidate) {
380 if (file_exists($candidate)) {
381 return dirname($candidate);
388 * Return the appropriate system path prefix, unless an override is provided.
389 * @param string $override
390 * @param string $defaultPrefix
393 protected static function systemPathPrefix($override = '', $defaultPrefix = '')
398 return static::isWindows() ? getenv('ALLUSERSPROFILE') . '/Drush' : $defaultPrefix;
402 * Return the system configuration path (default: /etc/drush)
406 public function systemConfigPath()
408 return static::systemPathPrefix($this->etcPrefix, '') . '/etc/drush';
412 * Return the system shared commandfile path (default: /usr/share/drush/commands)
416 public function systemCommandFilePath()
418 return static::systemPathPrefix($this->sharePrefix, '/usr') . '/share/drush/commands';
422 * Determine whether current OS is a Windows variant.
426 public static function isWindows($os = null)
428 return strtoupper(substr($os ?: PHP_OS, 0, 3)) === 'WIN';
432 * Verify that we are running PHP through the command line interface.
435 * A boolean value that is true when PHP is being run through the command line,
436 * and false if being run through cgi or mod_php.
438 public function verifyCLI()
440 return (php_sapi_name() == 'cli' || (is_numeric($_SERVER['argc']) && $_SERVER['argc'] > 0));
444 * Calculate the terminal width used for wrapping table output.
445 * Normally this is exported using tput in the drush script.
446 * If this is not present we do an additional check using stty here.
447 * On Windows in CMD and PowerShell is this exported using mode con.
451 public function calculateColumns()
453 if ($columns = getenv('COLUMNS')) {
457 // Trying to export the columns using stty.
458 exec('stty size 2>&1', $columns_output, $columns_status);
459 if (!$columns_status) {
460 $columns = preg_replace('/\d+\s(\d+)/', '$1', $columns_output[0], -1, $columns_count);
463 // If stty fails and Drush us running on Windows are we trying with mode con.
464 if (($columns_status || !$columns_count) && static::isWindows()) {
465 $columns_output = [];
466 exec('mode con', $columns_output, $columns_status);
467 if (!$columns_status && is_array($columns_output)) {
468 $columns = (int)preg_replace('/\D/', '', $columns_output[4], -1, $columns_count);
470 // TODO: else { 'Drush could not detect the console window width. Set a Windows Environment Variable of COLUMNS to the desired width.'
473 // Failling back to default columns value
474 if (empty($columns)) {
478 // TODO: should we deal with reserve-margin here, or adjust it later?
483 * Returns the filename for the file that stores the DRUPAL_SITE variable.
485 * @param string $filename_prefix
486 * An arbitrary string to prefix the filename with.
488 * @return string|false
489 * Returns the full path to temp file if possible, or FALSE if not.
491 protected function getSiteSetAliasFilePath($filename_prefix = 'drush-drupal-site-')
493 $shell_pid = getenv('DRUSH_SHELL_PID');
494 if (!$shell_pid && function_exists('posix_getppid')) {
495 $shell_pid = posix_getppid();
501 // The env variables below must match the variables in example.prompt.sh
502 $tmp = getenv('TMPDIR') ? getenv('TMPDIR') : '/tmp';
503 $username = $this->getUsername();
505 return "{$tmp}/drush-env-{$username}/{$filename_prefix}" . $shell_pid;