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 === true) {
63 // The autoloader was already required. Assume that Drush and Drupal share an autoloader per
64 // "Point autoload.php to the proper vendor directory" - https://www.drupal.org/node/2404989
65 $this->siteLoader = $this->loader;
68 // Ensure that the site's autoloader has highest priority. Usually,
69 // the first classloader registered gets the first shot at loading classes.
70 // We want Drupal's classloader to be used first when a class is loaded,
71 // and have Drush's classloader only be called as a fallback measure.
72 $this->siteLoader->unregister();
73 $this->siteLoader->register(true);
75 return $this->siteLoader;
79 * Return the name of the user running drush.
83 protected function getUsername()
86 if (!$name = getenv("username")) { // Windows
87 if (!$name = getenv("USER")) {
88 // If USER not defined, use posix
89 if (function_exists('posix_getpwuid')) {
90 $processUser = posix_getpwuid(posix_geteuid());
91 $name = $processUser['name'];
98 protected function getTmp()
102 // Get user specific and operating system temp folders from system environment variables.
103 // See http://www.microsoft.com/resources/documentation/windows/xp/all/proddocs/en-us/ntcmds_shelloverview.mspx?mfr=true
104 $tempdir = getenv('TEMP');
105 if (!empty($tempdir)) {
106 $directories[] = $tempdir;
108 $tmpdir = getenv('TMP');
109 if (!empty($tmpdir)) {
110 $directories[] = $tmpdir;
112 // Operating system specific dirs.
113 if (self::isWindows()) {
114 $windir = getenv('WINDIR');
115 if (isset($windir)) {
116 // WINDIR itself is not writable, but it always contains a /Temp dir,
117 // which is the system-wide temporary directory on older versions. Newer
118 // versions only allow system processes to use it.
119 $directories[] = Path::join($windir, 'Temp');
122 $directories[] = Path::canonicalize('/tmp');
124 $directories[] = Path::canonicalize(sys_get_temp_dir());
126 foreach ($directories as $directory) {
127 if (is_dir($directory) && is_writable($directory)) {
128 $temporary_directory = $directory;
133 if (empty($temporary_directory)) {
134 // If no directory has been found, create one in cwd.
135 $temporary_directory = Path::join(Drush::config()->cwd(), 'tmp');
136 drush_mkdir($temporary_directory, true);
137 if (!is_dir($temporary_directory)) {
138 throw new \Exception(dt("Unable to create a temporary directory."));
140 // Function not available yet - this is not likely to get reached anyway.
141 // drush_register_file_for_deletion($temporary_directory);
143 return $temporary_directory;
147 * Convert the environment object into an exported configuration
150 * @see PreflightArgs::applyToConfig(), which also exports information to config.
152 * @return array Nested associative array that is overlayed on configuration.
154 public function exportConfigData()
157 // Information about the environment presented to Drush
159 'cwd' => $this->cwd(),
160 'home' => $this->homeDir(),
161 'user' => $this->getUsername(),
162 'is-windows' => $this->isWindows(),
163 'tmp' => $this->getTmp(),
165 // These values are available as global options, and
166 // will be passed in to the FormatterOptions et. al.
168 'width' => $this->calculateColumns(),
170 // Information about the directories where Drush found assets, etc.
172 'base-dir' => $this->drushBasePath,
173 'vendor-dir' => $this->vendorPath(),
174 'docs-dir' => $this->docsPath(),
175 'user-dir' => $this->userConfigPath(),
176 'system-dir' => $this->systemConfigPath(),
177 'system-command-dir' => $this->systemCommandFilePath(),
180 'site-file-previous' => $this->getSiteSetAliasFilePath('drush-drupal-prev-site-'),
181 'site-file-current' => $this->getSiteSetAliasFilePath(),
187 * The base directory of the Drush application itself
188 * (where composer.json et.al. are found)
192 public function drushBasePath()
194 return $this->drushBasePath;
198 * Get the site:set alias from the current site:set file path.
200 * @return bool|string
202 public function getSiteSetAliasName()
204 $site_filename = $this->getSiteSetAliasFilePath();
205 if (file_exists($site_filename)) {
206 $site = file_get_contents($site_filename);
215 * User's home directory
219 public function homeDir()
221 return $this->homeDir;
225 * The user's Drush configuration directory, ~/.drush
229 public function userConfigPath()
231 return $this->homeDir() . '/.drush';
234 public function setConfigFileVariant($variant)
236 $this->configFileVariant = $variant;
240 * Get the config file variant -- defined to be
241 * the Drush major version number. This is for
242 * loading drush.yml and drush9.yml, etc.
244 public function getConfigFileVariant()
246 return $this->configFileVariant;
250 * The original working directory
254 public function cwd()
256 return $this->originalCwd;
260 * Return the path to Drush's vendor directory
264 public function vendorPath()
266 return $this->vendorDir;
270 * The class loader returned when the autoload.php file is included.
272 * @return \Composer\Autoload\ClassLoader
274 public function loader()
276 return $this->loader;
280 * Set the class loader from the autload.php file, if available.
282 * @param \Composer\Autoload\ClassLoader $loader
284 public function setLoader(ClassLoader $loader)
286 $this->loader = $loader;
290 * Alter our default locations based on the value of environment variables
294 public function applyEnvironment()
296 // Copy ETC_PREFIX and SHARE_PREFIX from environment variables if available.
297 // This alters where we check for server-wide config and alias files.
298 // Used by unit test suite to provide a clean environment.
299 $this->setEtcPrefix(getenv('ETC_PREFIX'));
300 $this->setSharePrefix(getenv('SHARE_PREFIX'));
306 * Set the directory prefix to locate the directory that Drush will
307 * use as /etc (e.g. during the functional tests)
309 * @param string $etcPrefix
312 public function setEtcPrefix($etcPrefix)
314 if (isset($etcPrefix)) {
315 $this->etcPrefix = $etcPrefix;
321 * Set the directory prefix to locate the directory that Drush will
322 * use as /user/share (e.g. during the functional tests)
323 * @param string $sharePrefix
326 public function setSharePrefix($sharePrefix)
328 if (isset($sharePrefix)) {
329 $this->sharePrefix = $sharePrefix;
330 $this->docPrefix = null;
336 * Return the directory where Drush's documentation is stored. Usually
337 * this is within the Drush application, but some Drush RPM distributions
338 * & c. for Linux platforms slice-and-dice the contents and put the docs
343 public function docsPath()
345 if (!$this->docPrefix) {
346 $this->docPrefix = $this->findDocsPath($this->drushBasePath);
348 return $this->docPrefix;
352 * Locate the Drush documentation. This is recalculated whenever the
353 * share prefix is changed.
355 * @param string $drushBasePath
358 protected function findDocsPath($drushBasePath)
361 "$drushBasePath/README.md",
362 static::systemPathPrefix($this->sharePrefix, '/usr') . '/share/docs/drush/README.md',
364 return $this->findFromCandidates($candidates);
368 * Check a list of directories and return the first one that exists.
370 * @param array $candidates
371 * @return string|boolean
373 protected function findFromCandidates($candidates)
375 foreach ($candidates as $candidate) {
376 if (file_exists($candidate)) {
377 return dirname($candidate);
384 * Return the appropriate system path prefix, unless an override is provided.
385 * @param string $override
386 * @param string $defaultPrefix
389 protected static function systemPathPrefix($override = '', $defaultPrefix = '')
394 return static::isWindows() ? getenv('ALLUSERSPROFILE') . '/Drush' : $defaultPrefix;
398 * Return the system configuration path (default: /etc/drush)
402 public function systemConfigPath()
404 return static::systemPathPrefix($this->etcPrefix, '') . '/etc/drush';
408 * Return the system shared commandfile path (default: /usr/share/drush/commands)
412 public function systemCommandFilePath()
414 return static::systemPathPrefix($this->sharePrefix, '/usr') . '/share/drush/commands';
418 * Determine whether current OS is a Windows variant.
422 public static function isWindows($os = null)
424 return strtoupper(substr($os ?: PHP_OS, 0, 3)) === 'WIN';
428 * Verify that we are running PHP through the command line interface.
431 * A boolean value that is true when PHP is being run through the command line,
432 * and false if being run through cgi or mod_php.
434 public function verifyCLI()
436 return (php_sapi_name() == 'cli' || (is_numeric($_SERVER['argc']) && $_SERVER['argc'] > 0));
440 * Calculate the terminal width used for wrapping table output.
441 * Normally this is exported using tput in the drush script.
442 * If this is not present we do an additional check using stty here.
443 * On Windows in CMD and PowerShell is this exported using mode con.
447 public function calculateColumns()
449 if ($columns = getenv('COLUMNS')) {
453 // Trying to export the columns using stty.
454 exec('stty size 2>&1', $columns_output, $columns_status);
455 if (!$columns_status) {
456 $columns = preg_replace('/\d+\s(\d+)/', '$1', $columns_output[0], -1, $columns_count);
459 // If stty fails and Drush us running on Windows are we trying with mode con.
460 if (($columns_status || !$columns_count) && static::isWindows()) {
461 $columns_output = [];
462 exec('mode con', $columns_output, $columns_status);
463 if (!$columns_status && is_array($columns_output)) {
464 $columns = (int)preg_replace('/\D/', '', $columns_output[4], -1, $columns_count);
466 // TODO: else { 'Drush could not detect the console window width. Set a Windows Environment Variable of COLUMNS to the desired width.'
469 // Failling back to default columns value
470 if (empty($columns)) {
474 // TODO: should we deal with reserve-margin here, or adjust it later?
479 * Returns the filename for the file that stores the DRUPAL_SITE variable.
481 * @param string $filename_prefix
482 * An arbitrary string to prefix the filename with.
484 * @return string|false
485 * Returns the full path to temp file if possible, or FALSE if not.
487 protected function getSiteSetAliasFilePath($filename_prefix = 'drush-drupal-site-')
489 $shell_pid = getenv('DRUSH_SHELL_PID');
490 if (!$shell_pid && function_exists('posix_getppid')) {
491 $shell_pid = posix_getppid();
497 // The env variables below must match the variables in example.prompt.sh
498 $tmp = getenv('TMPDIR') ? getenv('TMPDIR') : '/tmp';
499 $username = $this->getUsername();
501 return "{$tmp}/drush-env-{$username}/{$filename_prefix}" . $shell_pid;