5 * Functions used when Drush is starting up.
7 * This file is included and used early in the Drush
8 * startup process, before any other Drush APIs are
9 * available, and before the autoloader has been included.
13 * Get the current enviornment.
15 function drush_env() {
16 // Fetch the current environment. To ensure that
17 // $_ENV is correctly populated, make sure that
18 // the value of 'variables-order' in your php.ini
19 // contains "E" ("Environment"). See:
20 // http://us.php.net/manual/en/ini.core.php#ini.variables-order
23 // If PHP is not configured correctly, $_ENV will be
24 // empty. Drush counts on the fact that environment
25 // variables will always be available, though, so we
26 // need to repair this situation. We can always access
27 // individual environmnet values via getenv(); however,
28 // there is no PHP API that will tell us all of the
29 // available values, so we will get the environment
30 // variable values using 'printenv'.
32 exec('printenv', $env_items);
33 foreach ($env_items as $item) {
34 // Each $item is 'key=value' or just 'key'.
35 // If $item has no value, then explode will return
36 // a single array, [0 => 'key']. We add a default
37 // value of [1 => 'value'] to cover this case. If
38 // explode returns two items, the default value is ignored.
39 list($key, $value) = explode('=', $item, 2) + array(1 => '');
48 * Checks the provided location and return the appropriate
49 * Drush wrapper or Drush launcher script, if found.
51 * If the provided location looks like it might be a web
52 * root (i.e., it contains an index.php), then we will search
53 * in a number of locations in the general vicinity of the
54 * web root for a Drush executable.
56 * For other locations, we will look only in that specific
57 * directory, or in vendor/bin.
59 function find_wrapper_or_launcher($location) {
60 if (file_exists($location. DIRECTORY_SEPARATOR. 'index.php')) {
61 return find_wrapper_or_launcher_in_vicinity($location);
63 return find_wrapper_or_launcher_in_specific_locations($location, ["", 'vendor'. DIRECTORY_SEPARATOR. 'bin']);
67 * We look for a "Drush wrapper" script that might
68 * be stored in the root of a site. If there is
69 * no wrapper script, then we look for the
70 * drush.launcher script in vendor/bin. We try just a
71 * few of the most common locations; if the user relocates
72 * their vendor directory anywhere else, then they must
73 * use a wrapper script to locate it. See the comment in
74 * 'examples/drush' for details.
76 function find_wrapper_or_launcher_in_vicinity($location) {
77 $sep = DIRECTORY_SEPARATOR;
81 "..{$sep}vendor{$sep}bin{$sep}",
82 "sites{$sep}all{$sep}vendor{$sep}bin{$sep}",
83 "sites{$sep}all{$sep}vendor{$sep}drush{$sep}drush{$sep}",
84 "sites{$sep}all{$sep}drush{$sep}drush{$sep}",
85 "drush{$sep}drush{$sep}",
88 return find_wrapper_or_launcher_in_specific_locations($location, $drush_locations);
91 function find_wrapper_or_launcher_in_specific_locations($location, $drush_locations) {
92 $sep = DIRECTORY_SEPARATOR;
93 foreach ($drush_locations as $d) {
94 $found_script = find_wrapper_or_launcher_at_location("$location$sep$d");
95 if (!empty($found_script)) {
103 * We are somewhat "loose" about whether we are looking
104 * for "drush" or "drush.launcher", because in old versions
105 * of Drush, the "drush launcher" was named "drush".
106 * Otherwise, there wouldn't be any point in looking for
107 * "drush.launcher" at the root, or "drush" in a vendor directory.
108 * We also allow users to rename their drush wrapper to
109 * 'drush.wrapper' to avoid conflicting with a directory named
110 * 'drush' at the site root.
112 function find_wrapper_or_launcher_at_location($location) {
113 $sep = DIRECTORY_SEPARATOR;
114 // Sanity-check: empty $location means that we should search
115 // at the cwd, not at the root of the filesystem.
116 if (empty($location)) {
119 foreach (array('.launcher', '.wrapper', '') as $suffix) {
120 $check_location = "$location{$sep}drush$suffix";
121 if (is_file($check_location)) {
122 return $check_location;
129 * Determine whether current OS is a Windows variant.
131 function drush_is_windows($os = NULL) {
132 // The _drush_get_os() function may not be available, so resolve "LOCAL"
133 if (!$os || $os == "LOCAL") {
136 return strtoupper(substr($os, 0, 3)) === 'WIN';
139 function drush_escapeshellarg($arg, $os = NULL, $raw = FALSE) {
140 // Short-circuit escaping for simple params (keep stuff readable)
141 if (preg_match('|^[a-zA-Z0-9.:/_-]*$|', $arg)) {
144 elseif (drush_is_windows($os)) {
145 return _drush_escapeshellarg_windows($arg, $raw);
148 return _drush_escapeshellarg_linux($arg, $raw);
153 * Linux version of escapeshellarg().
155 * This is intended to work the same way that escapeshellarg() does on
156 * Linux. If we need to escape a string that will be used remotely on
157 * a Linux system, then we need our own implementation of escapeshellarg,
158 * because the Windows version behaves differently.
160 function _drush_escapeshellarg_linux($arg, $raw = FALSE) {
161 // For single quotes existing in the string, we will "exit"
162 // single-quote mode, add a \' and then "re-enter"
163 // single-quote mode. The result of this is that
164 // 'quote' becomes '\''quote'\''
165 $arg = preg_replace('/\'/', '\'\\\'\'', $arg);
167 // Replace "\t", "\n", "\r", "\0", "\x0B" with a whitespace.
168 // Note that this replacement makes Drush's escapeshellarg work differently
169 // than the built-in escapeshellarg in PHP on Linux, as these characters
170 // usually are NOT replaced. However, this was done deliberately to be more
171 // conservative when running _drush_escapeshellarg_linux on Windows
172 // (this can happen when generating a command to run on a remote Linux server.)
173 $arg = str_replace(array("\t", "\n", "\r", "\0", "\x0B"), ' ', $arg);
175 // Only wrap with quotes when needed.
177 // Add surrounding quotes.
178 $arg = "'" . $arg . "'";
185 * Windows version of escapeshellarg().
187 function _drush_escapeshellarg_windows($arg, $raw = FALSE) {
188 // Double up existing backslashes
189 $arg = preg_replace('/\\\/', '\\\\\\\\', $arg);
191 // Double up double quotes
192 $arg = preg_replace('/"/', '""', $arg);
194 // Double up percents.
195 // $arg = preg_replace('/%/', '%%', $arg);
197 // Only wrap with quotes when needed.
199 // Add surrounding quotes.
200 $arg = '"' . $arg . '"';
207 * drush_startup is called once, by the Drush "finder"
208 * script -- the "drush" script at the Drush root.
209 * It finds the correct Drush "wrapper" or "launcher"
210 * script to use, and executes it with process replacement.
212 function drush_startup($argv) {
213 $sep = DIRECTORY_SEPARATOR;
216 $home = getenv("HOME");
217 $use_dir = "$home{$sep}.drush{$sep}use";
219 // Get the arguments for the command. Shift off argv[0],
220 // which contains the name of this script.
222 array_shift($arguments);
224 // We need to do at least a partial parsing of the options,
225 // so that we can find --root / -r and so on.
233 foreach ($arguments as $arg) {
234 // If a variable to set was indicated on the
235 // previous iteration, then set the value of
236 // the named variable (e.g. "ROOT") to "$arg".
261 if (!$COMMAND && !$ALIAS && ($arg[0] == '@')) {
264 elseif (!$COMMAND && ($arg[0] != '-')) {
267 if (substr($arg, 0, 7) == "--root=") {
268 $ROOT = substr($arg, 7);
273 $NONE=($ALIAS == "@none");
275 // If we have found the site-local Drush script, then
276 // do not search for it again; use the environment value
278 $found_script = getenv('DRUSH_FINDER_SCRIPT');
280 // If the @none alias is used, then we skip the Drush wrapper,
281 // and call the Drush launcher directly.
283 // In this instance, we are assuming that the 'drush' that is being
286 // a) The global 'drush', or
287 // b) A site-local 'drush' in a vendor/bin directory.
289 // In either event, the appropriate 'drush.launcher' should be right next
290 // to this script (stored in the same directory).
291 if (empty($found_script) && $NONE) {
292 if (is_file(dirname(__DIR__) . "{$sep}drush.launcher")) {
293 $found_script = dirname(__DIR__) . "{$sep}drush.launcher";
296 fwrite(STDERR, "Could not find drush.launcher in " . dirname(__DIR__) . ". Check your installation.\n");
301 // Check for a root option:
303 // drush --root=/path
305 // If the site root is specified via a commandline option, then we
306 // should always use the Drush stored at this root, if there is one.
307 // We will first check for a "wrapper" script at the root, and then
308 // we will look for a "launcher" script in vendor/bin.
309 if (empty($found_script) && !empty($ROOT)) {
310 $found_script = find_wrapper_or_launcher($ROOT);
311 if (!empty($found_script)) {
316 // If there is a .drush-use file, then its contents will
317 // contain the path to the Drush to use.
318 if (empty($found_script)) {
319 if (is_file(".drush-use")) {
320 $found_script = trim(file_get_contents(".drush-use"));
324 // Look for a 'drush' wrapper or launcher at the cwd,
325 // and in each of the directories above the cwd. If
326 // we find one, use it.
327 if (empty($found_script)) {
329 // Windows can give us lots of different strings to represent the root
330 // directory as it often includes the drive letter. If we get the same
331 // result from dirname() twice in a row, then we know we're at the root.
333 while (!empty($c) && ($c != $last)) {
334 $found_script = find_wrapper_or_launcher($c);
344 if (!empty($found_script)) {
345 $found_script = realpath($found_script);
347 // Guard against errors: if we have found a "drush" script
348 // (that is, theoretically a drush wrapper script), and
349 // there is a "drush.launcher" script in the same directory,
350 // then we will skip the "drush" script and use the drush launcher
351 // instead. This is because drush "wrapper" scripts should
352 // only ever exist at the root of a site, and there should
353 // never be a drush "launcher" at the root of a site.
354 // Therefore, if we find a "drush.launcher" next to a script
355 // called "drush", we have probably found a Drush install directory,
356 // not a site root. Adjust appropriately. Note that this
357 // also catches the case where a drush "finder" script finds itself.
358 if (is_file(dirname($found_script) . "{$sep}drush.launcher")) {
359 $found_script = dirname($found_script) . "{$sep}drush.launcher";
363 // Didn't find any site-local Drush, or @use'd Drush.
364 // Skip the Bash niceties of the launcher and proceed to drush_main() in either case:
365 // - No script was found and we are running a Phar
366 // - The found script *is* the Phar https://github.com/drush-ops/drush/pull/2246.
367 $phar_path = class_exists('Phar') ? Phar::running(FALSE) : '';
368 if ((empty($found_script) && $phar_path) || !empty($found_script) && $found_script == $phar_path) {
369 drush_run_main($DEBUG, $sep, "Phar detected. Proceeding to drush_main().");
372 // Didn't find any site-local Drush, or @use'd Drush, or Phar.
373 // There should be a drush.launcher in same directory as this script.
374 if (empty($found_script)) {
375 $found_script = dirname(__DIR__) . "{$sep}drush.launcher";
378 if (drush_is_windows()) {
379 // Sometimes we found launcher in /bin, and sometimes not. Adjust accordingly.
380 if (strpos($found_script, 'bin')) {
381 $found_script = dirname($found_script). $sep. 'drush.php.bat';
384 array_unshift($arguments, dirname($found_script). $sep. 'drush.php');
385 $found_script = 'php';
389 // Always use pcntl_exec if it exists.
390 $use_pcntl_exec = function_exists("pcntl_exec") && (strpos(ini_get('disable_functions'), 'pcntl_exec') === FALSE);
392 // If we have posix_getppid, then pass in the shell pid so
393 // that 'site-set' et. al. can work correctly.
394 if (function_exists('posix_getppid')) {
395 putenv("DRUSH_SHELL_PID=" . posix_getppid());
398 // Set an environment variable indicating which script
399 // the Drush finder found. If we end up re-entrantly calling
400 // another Drush finder, then we will skip searching for
401 // a site-local Drush, and always use the drush.launcher
402 // found previously. This environment variable typically should
403 // not be set by clients.
404 putenv("DRUSH_FINDER_SCRIPT=$found_script");
406 // Emit a message in debug mode advertising the location of the
409 $launch_method = $use_pcntl_exec ? 'pcntl_exec' : 'proc_open';
410 fwrite(STDERR, "Using the Drush script found at $found_script using $launch_method\n");
413 if ($use_pcntl_exec) {
414 // Get the current environment for pnctl_exec.
417 // Launch the new script in the same process.
418 // If the launch succeeds, then it will not return.
419 $error = pcntl_exec($found_script, $arguments, $env);
421 $errno = pcntl_get_last_error();
422 $strerror = pcntl_strerror($errno);
423 fwrite(STDERR, "Error has occurred executing the Drush script found at $found_script\n");
424 fwrite(STDERR, "(errno {$errno}) $strerror\n");
429 $escaped_args = array_map(function($item) { return drush_escapeshellarg($item); }, $arguments);
430 // Double quotes around $found_script as it can contain spaces.
431 $cmd = drush_escapeshellarg($found_script). ' '. implode(' ', $escaped_args);
432 if (drush_is_windows()) {
433 // Windows requires double quotes around whole command.
434 // @see https://bugs.php.net/bug.php?id=49139
435 // @see https://bugs.php.net/bug.php?id=60181
436 $cmd = '"'. $cmd. '"';
438 $process = proc_open($cmd, array(0 => STDIN, 1 => STDOUT, 2 => STDERR), $pipes, $cwd);
439 $proc_status = proc_get_status($process);
440 $exit_code = proc_close($process);
441 exit($proc_status["running"] ? $exit_code : $proc_status["exitcode"] );
446 * Run drush_main() and then exit. Used when we cannot hand over execution to
450 * Are we in debug mode
452 * Directory separator
454 * Debug message to log before running drush_main()
456 function drush_run_main($DEBUG, $sep, $msg) {
457 // Emit a message in debug mode advertising how we proceeded.
459 fwrite(STDERR, $msg. "\n");
461 require __DIR__ . "{$sep}preflight.inc";