From 7ebd5fa5bca50abfc3d91fcec7a5ad807efd2fc1 Mon Sep 17 00:00:00 2001 From: Moshe Weitzman Date: Thu, 11 May 2017 16:33:07 -0400 Subject: [PATCH] Remove Finder+Launcher. Use DrupalFinder (#2749) --- composer.json | 6 +- docs/examples.md | 1 - drush | 113 +------- drush.launcher | 132 ---------- drush.php | 1 - examples/drush.wrapper | 27 -- includes/backend.inc | 5 +- includes/drush.inc | 2 +- includes/environment.inc | 108 ++++++-- includes/preflight.inc | 9 +- includes/startup.inc | 459 --------------------------------- sut | 29 +++ tests/CommandUnishTestCase.php | 3 - tests/UnishTestCase.php | 17 +- tests/completeTest.php | 4 +- tests/contextTest.php | 3 +- tests/drushScriptTest.php | 148 ----------- 17 files changed, 133 insertions(+), 934 deletions(-) delete mode 100755 drush.launcher delete mode 100755 examples/drush.wrapper delete mode 100644 includes/startup.inc create mode 100755 sut delete mode 100644 tests/drushScriptTest.php diff --git a/composer.json b/composer.json index 83c9c75dac..158ac9e1c8 100644 --- a/composer.json +++ b/composer.json @@ -22,10 +22,7 @@ "irc": "irc://irc.freenode.org/drush" }, "bin": [ - "drush", - "drush.launcher", - "drush.php", - "drush.complete.sh" + "drush" ], "require": { "php": ">=5.6.0", @@ -45,6 +42,7 @@ "symfony/finder": "~2.7|^3", "pear/console_table": "~1.3.0", "phpdocumentor/reflection-docblock": "^2.0", + "webflo/drupal-finder": "^0", "webmozart/path-util": "~2", "sebastian/version": "~1" }, diff --git a/docs/examples.md b/docs/examples.md index ca927f432f..ac88f73db7 100644 --- a/docs/examples.md +++ b/docs/examples.md @@ -1,6 +1,5 @@ The _examples_ folder contains excellent example files which you may copy and edit as needed. Read the documentation right in the file. If you see an opportunity to improve the file, please submit a pull request. -* [drush.wrapper](https://raw.githubusercontent.com/drush-ops/drush/master/examples/drush.wrapper). A handy launcher script which calls the Drush located in vendor/bin/drush and can add options like --local, --root, etc. * [example.aliases.drushrc.php](https://raw.githubusercontent.com/drush-ops/drush/master/examples/example.aliases.drushrc.php). Example site alias definitions. * [example.bashrc](https://raw.githubusercontent.com/drush-ops/drush/master/examples/example.bashrc). Enhance your shell with lots of Drush niceties including bash completion. * [example.drush.ini](https://raw.githubusercontent.com/drush-ops/drush/master/examples/example.drush.ini). Configure your PHP just for Drush requests. diff --git a/drush b/drush index 902e66c6bf..5318cb3973 100755 --- a/drush +++ b/drush @@ -1,115 +1,4 @@ #!/usr/bin/env php "drush wrapper" -> "drush launcher". - * - * Brief description of each: - * - * - Drush finder: Finds the right Drush script and calls it. - * - Drush wrapper: Contains user customizations to options. - * - Drush launcher: Excutes drush.php. - * - * A full explanation of each script follows. - * - * - * DRUSH FINDER - * - * - The "drush" script on the user's global $PATH - * - It's goal is to find the correct site-local Drush to run. - * - * The Drush finder will locate the correct site-local Drush to use - * by examining: - * - * a) The --root option - * b) The site set via `drush site set` in the current terminal - * c) The cwd - * - * If no site-local Drush is found, then the global Drush will be - * used. The Drush finder assumes that the global Drush is the - * "Drush launcher" found in the same directory as the Drush finder itself. - * - * If a site-local Drush is found, then the Drush finder will call - * either the "Drush wrapper", if it exists, or the "Drush launcher" if - * there is no wrapper script. - * - * - * DRUSH WRAPPER - * - * - The drush.wrapper script that the user optionally copies and edits. - * - Its goal is to allow the user to add options when --local is in use - * - * The Drush "wrapper" is found at examples/drush.wrapper, and may optionally - * be copied to __ROOT__ by the user. It may be named either - * "drush" or "drush.wrapper". It will call the "Drush launcher" - * for the same site that it is located in. It adds the --local flag; the - * user is encouraged to add other options to the "Drush wrapper", e.g. to set - * the location where aliases and global commandfiles can be found. - * The Drush "finder" script always calls the "Drush wrapper" if it exists; - * however, if the user does not want to customize the early options of - * the site-local Drush (site-alias locations, etc.), then the wrapper does not - * need to be used. - * - * - * DRUSH LAUNCHER - * - * - The "drush.launcher" script in vendor/bin - * - The bash script formerly called "drush" - * - * The "Drush launcher" is the traditional script that identifies PHP and - * sets up to call drush.php. It is called by the "Drush wrapper", or - * directly by the "Drush launcher" if there is no "Drush wrapper" in use. - * - * - * LOCATIONS FOR THESE SCRIPTS - * - * "Drush finder" : __ROOT__/vendor/bin/drush (composer install) - * __DRUSH__/drush (source) - * - * "Drush wrapper" : __ROOT__/drush (copied by user) - * __DRUSH__/examples/drush.wrapper (source) - * - * "Drush launcher" : __ROOT__/vendor/bin/drush.launcher (composer install) - * __DRUSH__/drush.launcher (source) - * - * - * BACKEND CALL DISPATCHING - * - * Backend calls are typically set up to call the "drush" script in the $PATH, - * or perhaps some might call __ROOT__/vendor/bin/drush directly, by way - * of the "drush-script" element in a site alias. In either event, this is - * the "drush finder" script. - * - * The backend call will always set --root. The "Drush finder" script - * always favors the site-local Drush stored with the site indicated by the - * --root option, if it exists. If there is no site-local Drush, then the - * "Drush finder" will behave as usual (i.e., it will end up calling the - * "Drush launcher" located next to it). - * - * This should always get you the correct "Drush" for local and remote calls. - * Note that it is also okay for aliases to specify a path directly to - * drush.launcher, in instances where it is known that a recent version of - * Drush is installed on the remote end. - */ - -if (!function_exists("drush_startup")) { - include __DIR__ . '/includes/startup.inc'; -} -drush_startup($argv); +require __DIR__ . '/drush.php'; diff --git a/drush.launcher b/drush.launcher deleted file mode 100755 index 0136c0df90..0000000000 --- a/drush.launcher +++ /dev/null @@ -1,132 +0,0 @@ -#!/usr/bin/env sh -# -# This script is a simple launcher that will run Drush with the most appropriate -# php executable it can find. In most cases, the 'drush' script should be -# called first; it will in turn launch this script. -# -# Solaris users: Add /usr/xpg4/bin to the head of your PATH -# - -# Get the absolute path of this executable -SELF_DIRNAME="`dirname -- "$0"`" -SELF_PATH="`cd -P -- "$SELF_DIRNAME" && pwd -P`/`basename -- "$0"`" - -# Decide if we are running a Unix shell on Windows -if `which uname > /dev/null 2>&1`; then - case "`uname -a`" in - CYGWIN*) - CYGWIN=1 ;; - MINGW*) - MINGW=1 ;; - esac -fi - -# Resolve symlinks - this is the equivalent of "readlink -f", but also works with non-standard OS X readlink. -while [ -h "$SELF_PATH" ]; do - # 1) cd to directory of the symlink - # 2) cd to the directory of where the symlink points - # 3) Get the pwd - # 4) Append the basename - DIR="`dirname -- "$SELF_PATH"`" - SYM="`readlink "$SELF_PATH"`" - SYM_DIRNAME="`dirname -- "$SYM"`" - SELF_PATH="`cd "$DIR" && cd "$SYM_DIRNAME" && pwd`/`basename -- "$SYM"`" -done - -# If not exported, try to determine and export the number of columns. -# We do not want to run `tput cols` if $TERM is empty, "unknown", or "dumb", because -# if we do, tput will output an undesirable error message to stderr. If -# we redirect stderr in any way, e.g. `tput cols 2>/dev/null`, then the -# error message is suppressed, but tput cols becomes confused about the -# terminal and prints out the default value (80). -if [ -z $COLUMNS ] && [ -n "$TERM" ] && [ "$TERM" != dumb ] && [ "$TERM" != unknown ] && [ -n "`which tput`" ] ; then - # Note to cygwin/mingw/msys users: install the ncurses package to get tput command. - # Note to mingw/msys users: there is no precompiled ncurses package. - if COLUMNS="`tput cols`"; then - export COLUMNS - fi -fi - -if [ -n "$DRUSH_PHP" ] ; then - # Use the DRUSH_PHP environment variable if it is available. - php="$DRUSH_PHP" -else - # On MSYSGIT, we need to use "php", not the full path to php - if [ -n "$MINGW" ] ; then - php="php" - else - # Default to using the php that we find on the PATH. - # We check for a command line (cli) version of php, and if found use that. - # Note that we need the full path to php here for Dreamhost, which behaves oddly. See http://drupal.org/node/662926 - php="`which php-cli 2>/dev/null`" - - if [ ! -x "$php" ]; then - php="`which php 2>/dev/null`" - fi - - if [ ! -x "$php" ]; then - echo "ERROR: can't find php."; exit 1 - fi - fi -fi - -# Build the path to drush.php. -SCRIPT_PATH="`dirname "$SELF_PATH"`/drush.php" -if [ -n "$CYGWIN" ] ; then - # try to determine if we are running cygwin port php or Windows native php: - if [ -n "`"$php" -i | grep -E '^System => Windows'`" ]; then - SCRIPT_PATH="`cygpath -w -a -- "$SCRIPT_PATH"`" - else - SCRIPT_PATH="`cygpath -u -a -- "$SCRIPT_PATH"`" - fi -fi - -# Check to see if the user has provided a php.ini file or drush.ini file in any conf dir -# Last found wins, so search in reverse priority order -for conf_dir in "`dirname "$SELF_PATH"`" /etc/drush "$HOME/.drush" ; do - if [ ! -d "$conf_dir" ] ; then - continue - fi - # Handle paths that don't start with a drive letter on MinGW shell. Equivalent to cygpath on Cygwin. - if [ -n "$MINGW" ] ; then - conf_dir=`sh -c "cd \"$conf_dir\"; pwd -W"` - fi - if [ -f "$conf_dir/php.ini" ] ; then - drush_php_ini="$conf_dir/php.ini" - fi - if [ -f "$conf_dir/drush.ini" ] ; then - drush_php_override="$conf_dir/drush.ini" - fi -done -# If the PHP_INI environment variable is specified, then tell -# php to use the php.ini file that it specifies. -if [ -n "$PHP_INI" ] ; then - drush_php_ini="$PHP_INI" -fi -# If the DRUSH_INI environment variable is specified, then -# extract all ini variable assignments from it and convert -# them into php '-d' options. These will override similarly-named -# options in the php.ini file -if [ -n "$DRUSH_INI" ] ; then - drush_php_override="$DRUSH_INI" -fi - -# Add in the php file location and/or the php override variables as appropriate -if [ -n "$drush_php_ini" ] ; then - php_options="--php-ini $drush_php_ini" -fi -if [ -n "$drush_php_override" ] ; then - php_options=`grep '^[a-z_A-Z0-9.]\+ *=' $drush_php_override | sed -e 's|\([^ =]*\) *= *\(.*\)|\1="\2"|' -e 's| ||g' -e 's|^|-d |' | tr '\n\r' ' '` -fi -# If the PHP_OPTIONS environment variable is specified, then -# its contents will be passed to php on the command line as -# additional options to use. -if [ -n "$PHP_OPTIONS" ] ; then - php_options="$php_options $PHP_OPTIONS" -fi - -# Pass in the path to php so that drush knows which one to use if it -# re-launches itself to run subcommands. We will also pass in the php options. -# Important note: Any options added here must be removed when Drush processes -# a #! (shebang) script. @see drush_adjust_args_if_shebang_script() -exec "$php" $php_options "$SCRIPT_PATH" --php="$php" --php-options="$php_options" "$@" diff --git a/drush.php b/drush.php index 326c93216a..73254291df 100755 --- a/drush.php +++ b/drush.php @@ -1,4 +1,3 @@ -#!/usr/bin/env php 'ALIAS_PATH', 'local-context-only' => TRUE, 'merge-pathlist' => TRUE, 'propagate-cli-value' => TRUE, 'description' => "Specifies the list of paths where drush will search for alias files.", 'example-value' => '/path/alias1:/path/alias2'); $options['confirm-rollback'] = array('description' => 'Wait for confirmation before doing a rollback when something goes wrong.'); $options['complete-debug'] = array('hidden' => TRUE, 'description' => "Turn on debug mode forf completion code"); - $options['php-options'] = array('description' => "Options to pass to `php` when running drush. Only effective when using the drush.launcher script.", 'never-propagate' => TRUE, 'example-value' => '-d error_reporting="E_ALL"'); + $options['php-options'] = array('hidden' => TRUE, 'description' => "Options to pass to `php` when running drush. Only effective when specified in a site alias definition.", 'never-propagate' => TRUE, 'example-value' => '-d error_reporting="E_ALL"'); $options['halt-on-error'] = array('propagate-cli-value' => TRUE, 'description' => "Manage recoverable errors. Values: 1=Execution halted. 0=Execution continues."); $options['deferred-sanitization'] = array('hidden' => TRUE, 'description' => "Defer calculating the sanitization operations until after the DB is copied. This is the default if the source DB is remote."); $options['remote-host'] = array('hidden' => TRUE, 'description' => 'Remote site to execute drush command on. Managed by site alias.', 'example-value' => 'http://example.com'); diff --git a/includes/environment.inc b/includes/environment.inc index 0320e09ff0..009835048d 100644 --- a/includes/environment.inc +++ b/includes/environment.inc @@ -10,6 +10,7 @@ * @see includes/bootstrap.inc */ +use DrupalFinder\DrupalFinder; use Drush\Log\LogLevel; use Webmozart\PathUtil\Path; @@ -328,31 +329,18 @@ function drush_locate_root($start_path = NULL) { $drupal_root = FALSE; $start_path = empty($start_path) ? drush_cwd() : $start_path; - foreach (array(TRUE, FALSE) as $follow_symlinks) { - $path = $start_path; - if ($follow_symlinks && is_link($path)) { - $path = realpath($path); - } - // Check the start path. - if (drush_valid_root($path)) { - $drupal_root = $path; - break; - } - else { - // Move up dir by dir and check each. - while ($path = _drush_shift_path_up($path)) { - if ($follow_symlinks && is_link($path)) { - $path = realpath($path); - } - if (drush_valid_root($path)) { - $drupal_root = $path; - break 2; - } - } - } + + $drupalFinder = new DrupalFinder(); + if (!$drupalFinder->locateRoot($start_path)) { +// echo ' Drush must be executed within a Drupal site.'. PHP_EOL; +// exit(1); } - return $drupal_root; + // $composerRoot = $drupalFinder->getComposerRoot(); + $drupalRoot = $drupalFinder->getDrupalRoot(); + // chdir($drupalRoot); + + return $drupalRoot; } /** @@ -702,3 +690,77 @@ function drush_set_environment_vars(array $site_record) { } } } + +/** + * Determine whether current OS is a Windows variant. + */ +function drush_is_windows($os = NULL) { + return strtoupper(substr($os ?: PHP_OS, 0, 3)) === 'WIN'; +} + +function drush_escapeshellarg($arg, $os = NULL, $raw = FALSE) { + // Short-circuit escaping for simple params (keep stuff readable) + if (preg_match('|^[a-zA-Z0-9.:/_-]*$|', $arg)) { + return $arg; + } + elseif (drush_is_windows($os)) { + return _drush_escapeshellarg_windows($arg, $raw); + } + else { + return _drush_escapeshellarg_linux($arg, $raw); + } +} + +/** + * Linux version of escapeshellarg(). + * + * This is intended to work the same way that escapeshellarg() does on + * Linux. If we need to escape a string that will be used remotely on + * a Linux system, then we need our own implementation of escapeshellarg, + * because the Windows version behaves differently. + */ +function _drush_escapeshellarg_linux($arg, $raw = FALSE) { + // For single quotes existing in the string, we will "exit" + // single-quote mode, add a \' and then "re-enter" + // single-quote mode. The result of this is that + // 'quote' becomes '\''quote'\'' + $arg = preg_replace('/\'/', '\'\\\'\'', $arg); + + // Replace "\t", "\n", "\r", "\0", "\x0B" with a whitespace. + // Note that this replacement makes Drush's escapeshellarg work differently + // than the built-in escapeshellarg in PHP on Linux, as these characters + // usually are NOT replaced. However, this was done deliberately to be more + // conservative when running _drush_escapeshellarg_linux on Windows + // (this can happen when generating a command to run on a remote Linux server.) + $arg = str_replace(array("\t", "\n", "\r", "\0", "\x0B"), ' ', $arg); + + // Only wrap with quotes when needed. + if(!$raw) { + // Add surrounding quotes. + $arg = "'" . $arg . "'"; + } + + return $arg; +} + +/** + * Windows version of escapeshellarg(). + */ +function _drush_escapeshellarg_windows($arg, $raw = FALSE) { + // Double up existing backslashes + $arg = preg_replace('/\\\/', '\\\\\\\\', $arg); + + // Double up double quotes + $arg = preg_replace('/"/', '""', $arg); + + // Double up percents. + // $arg = preg_replace('/%/', '%%', $arg); + + // Only wrap with quotes when needed. + if(!$raw) { + // Add surrounding quotes. + $arg = '"' . $arg . '"'; + } + + return $arg; +} diff --git a/includes/preflight.inc b/includes/preflight.inc index 75bb32997e..68d9a04b1d 100644 --- a/includes/preflight.inc +++ b/includes/preflight.inc @@ -142,6 +142,7 @@ function drush_symfony_input() { */ function drush_preflight_prepare() { define('DRUSH_BASE_PATH', dirname(dirname(__FILE__))); + // Local means that autoload.php is inside of Drush. That is, Drush is its own Composer project. // Global means autoload.php is outside of Drush. That is, Drush is a dependency of a bigger project. $local_vendor_path = DRUSH_BASE_PATH . '/vendor/autoload.php'; @@ -158,13 +159,12 @@ function drush_preflight_prepare() { $vendor_path = $global_vendor_path; } else { - $msg = "Unable to load autoload.php. Run composer install to fetch dependencies and write this file (http://docs.drush.org/en/master/install-alternative/). Or if you prefer, use the drush.phar which already has dependencies included (http://docs.drush.org/en/master/install).\n"; + $msg = "Unable to load autoload.php. Run composer install to fetch dependencies and write autoload.php (http://docs.drush.org/en/master/install/).\n"; fwrite(STDERR, $msg); return FALSE; } $classloader = require $vendor_path; - require_once DRUSH_BASE_PATH . '/includes/startup.inc'; require_once DRUSH_BASE_PATH . '/includes/bootstrap.inc'; require_once DRUSH_BASE_PATH . '/includes/environment.inc'; require_once DRUSH_BASE_PATH . '/includes/annotationcommand_adapter.inc'; @@ -820,10 +820,7 @@ 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'); - if (empty($local_drush) && !empty($root)) { - $local_drush = find_wrapper_or_launcher($root); - } + $local_drush = drush_get_option('drush-script', Path::join(dirname(__DIR__), 'drush')); $is_local = drush_get_option('local'); $values = NULL; if (!empty($root) && !empty($local_drush) && empty($is_local)) { diff --git a/includes/startup.inc b/includes/startup.inc deleted file mode 100644 index ea36e1cc12..0000000000 --- a/includes/startup.inc +++ /dev/null @@ -1,459 +0,0 @@ - 'key']. We add a default - // value of [1 => 'value'] to cover this case. If - // explode returns two items, the default value is ignored. - list($key, $value) = explode('=', $item, 2) + array(1 => ''); - $env[$key] = $value; - } - } - - return $env; -} - -/** - * Checks the provided location and return the appropriate - * Drush wrapper or Drush launcher script, if found. - * - * If the provided location looks like it might be a web - * root (i.e., it contains an index.php), then we will search - * in a number of locations in the general vicinity of the - * web root for a Drush executable. - * - * For other locations, we will look only in that specific - * directory, or in vendor/bin. - */ -function find_wrapper_or_launcher($location) { - if (file_exists($location. DIRECTORY_SEPARATOR. 'index.php')) { - return find_wrapper_or_launcher_in_vicinity($location); - } - return find_wrapper_or_launcher_in_specific_locations($location, ["", 'vendor'. DIRECTORY_SEPARATOR. 'bin']); -} - -/** - * We look for a "Drush wrapper" script that might - * be stored in the root of a site. If there is - * no wrapper script, then we look for the - * drush.launcher script in vendor/bin. We try just a - * few of the most common locations; if the user relocates - * their vendor directory anywhere else, then they must - * use a wrapper script to locate it. See the comment in - * 'examples/drush.wrapper' for details. - */ -function find_wrapper_or_launcher_in_vicinity($location) { - $sep = DIRECTORY_SEPARATOR; - $drush_locations = [ - "", - "vendor{$sep}bin/", - "..{$sep}vendor{$sep}bin{$sep}", - "sites{$sep}all{$sep}vendor{$sep}bin{$sep}", - "sites{$sep}all{$sep}vendor{$sep}drush{$sep}drush{$sep}", - "sites{$sep}all{$sep}drush{$sep}drush{$sep}", - "drush{$sep}drush{$sep}", - ]; - - return find_wrapper_or_launcher_in_specific_locations($location, $drush_locations); -} - -function find_wrapper_or_launcher_in_specific_locations($location, $drush_locations) { - $sep = DIRECTORY_SEPARATOR; - foreach ($drush_locations as $d) { - $found_script = find_wrapper_or_launcher_at_location("$location$sep$d"); - if (!empty($found_script)) { - return $found_script; - } - } - return ""; -} - -/** - * We are somewhat "loose" about whether we are looking - * for "drush" or "drush.launcher", because in old versions - * of Drush, the "drush launcher" was named "drush". - * Otherwise, there wouldn't be any point in looking for - * "drush.launcher" at the root, or "drush" in a vendor directory. - * We also allow users name their wrapper 'drush' or 'drush.wrapper' - * to avoid conflicting with a directory named - * 'drush' at the site root. - */ -function find_wrapper_or_launcher_at_location($location) { - $sep = DIRECTORY_SEPARATOR; - // Sanity-check: empty $location means that we should search - // at the cwd, not at the root of the filesystem. - if (empty($location)) { - $location = "."; - } - foreach (array('.launcher', '.wrapper', '') as $suffix) { - $check_location = "$location{$sep}drush$suffix"; - if (is_file($check_location)) { - return $check_location; - } - } - return ""; -} - -/** - * Determine whether current OS is a Windows variant. - */ -function drush_is_windows($os = NULL) { - return strtoupper(substr($os ?: PHP_OS, 0, 3)) === 'WIN'; -} - -function drush_escapeshellarg($arg, $os = NULL, $raw = FALSE) { - // Short-circuit escaping for simple params (keep stuff readable) - if (preg_match('|^[a-zA-Z0-9.:/_-]*$|', $arg)) { - return $arg; - } - elseif (drush_is_windows($os)) { - return _drush_escapeshellarg_windows($arg, $raw); - } - else { - return _drush_escapeshellarg_linux($arg, $raw); - } -} - -/** - * Linux version of escapeshellarg(). - * - * This is intended to work the same way that escapeshellarg() does on - * Linux. If we need to escape a string that will be used remotely on - * a Linux system, then we need our own implementation of escapeshellarg, - * because the Windows version behaves differently. - */ -function _drush_escapeshellarg_linux($arg, $raw = FALSE) { - // For single quotes existing in the string, we will "exit" - // single-quote mode, add a \' and then "re-enter" - // single-quote mode. The result of this is that - // 'quote' becomes '\''quote'\'' - $arg = preg_replace('/\'/', '\'\\\'\'', $arg); - - // Replace "\t", "\n", "\r", "\0", "\x0B" with a whitespace. - // Note that this replacement makes Drush's escapeshellarg work differently - // than the built-in escapeshellarg in PHP on Linux, as these characters - // usually are NOT replaced. However, this was done deliberately to be more - // conservative when running _drush_escapeshellarg_linux on Windows - // (this can happen when generating a command to run on a remote Linux server.) - $arg = str_replace(array("\t", "\n", "\r", "\0", "\x0B"), ' ', $arg); - - // Only wrap with quotes when needed. - if(!$raw) { - // Add surrounding quotes. - $arg = "'" . $arg . "'"; - } - - return $arg; -} - -/** - * Windows version of escapeshellarg(). - */ -function _drush_escapeshellarg_windows($arg, $raw = FALSE) { - // Double up existing backslashes - $arg = preg_replace('/\\\/', '\\\\\\\\', $arg); - - // Double up double quotes - $arg = preg_replace('/"/', '""', $arg); - - // Double up percents. - // $arg = preg_replace('/%/', '%%', $arg); - - // Only wrap with quotes when needed. - if(!$raw) { - // Add surrounding quotes. - $arg = '"' . $arg . '"'; - } - - return $arg; -} - -/** - * drush_startup is called once, by the Drush "finder" - * script -- the "drush" script at the Drush root. - * It finds the correct Drush "wrapper" or "launcher" - * script to use, and executes it with process replacement. - */ -function drush_startup($argv) { - $sep = DIRECTORY_SEPARATOR; - $found_script = ""; - $cwd = getcwd(); - $home = getenv("HOME"); - $use_dir = "$home{$sep}.drush{$sep}use"; - - // Get the arguments for the command. Shift off argv[0], - // which contains the name of this script. - $arguments = $argv; - array_shift($arguments); - - // We need to do at least a partial parsing of the options, - // so that we can find --root / -r and so on. - $VERBOSE=FALSE; - $DEBUG=FALSE; - $ROOT=FALSE; - $COMMAND=FALSE; - $ALIAS=FALSE; - $VAR=FALSE; - - foreach ($arguments as $arg) { - // If a variable to set was indicated on the - // previous iteration, then set the value of - // the named variable (e.g. "ROOT") to "$arg". - if ($VAR) { - $$VAR = "$arg"; - $VAR = FALSE; - } - else { - switch ($arg) { - case "-r": - $VAR = "ROOT"; - break; - - case "-dv": - case "-vd": - case "--debug": - case "-d": - $DEBUG = TRUE; - break; - - case "-dv": - case "-vd": - case "--verbose": - case "-v": - $VERBOSE = TRUE; - break; - } - if (!$COMMAND && !$ALIAS && ($arg[0] == '@')) { - $ALIAS = $arg; - } - elseif (!$COMMAND && ($arg[0] != '-')) { - $COMMAND = $arg; - } - if (substr($arg, 0, 7) == "--root=") { - $ROOT = substr($arg, 7); - } - } - } - - $NONE=($ALIAS == "@none"); - - // If we have found the site-local Drush script, then - // do not search for it again; use the environment value - // we set last time. - $found_script = getenv('DRUSH_FINDER_SCRIPT'); - - // If the @none alias is used, then we skip the Drush wrapper, - // and call the Drush launcher directly. - // - // In this instance, we are assuming that the 'drush' that is being - // called is: - // - // a) The global 'drush', or - // b) A site-local 'drush' in a vendor/bin directory. - // - // In either event, the appropriate 'drush.launcher' should be right next - // to this script (stored in the same directory). - if (empty($found_script) && $NONE) { - if (is_file(dirname(__DIR__) . "{$sep}drush.launcher")) { - $found_script = dirname(__DIR__) . "{$sep}drush.launcher"; - } - else { - fwrite(STDERR, "Could not find drush.launcher in " . dirname(__DIR__) . ". Check your installation.\n"); - exit(1); - } - } - - // Check for a root option: - // - // drush --root=/path - // - // If the site root is specified via a commandline option, then we - // should always use the Drush stored at this root, if there is one. - // We will first check for a "wrapper" script at the root, and then - // we will look for a "launcher" script in vendor/bin. - if (empty($found_script) && !empty($ROOT)) { - $found_script = find_wrapper_or_launcher($ROOT); - if (!empty($found_script)) { - chdir($ROOT); - } - } - - // If there is a .drush-use file, then its contents will - // contain the path to the Drush to use. - if (empty($found_script)) { - if (is_file(".drush-use")) { - $found_script = trim(file_get_contents(".drush-use")); - } - } - - // Look for a 'drush' wrapper or launcher at the cwd, - // and in each of the directories above the cwd. If - // we find one, use it. - if (empty($found_script)) { - $c = getcwd(); - // Windows can give us lots of different strings to represent the root - // directory as it often includes the drive letter. If we get the same - // result from dirname() twice in a row, then we know we're at the root. - $last = ''; - while (!empty($c) && ($c != $last)) { - $found_script = find_wrapper_or_launcher($c); - if ($found_script) { - chdir($c); - break; - } - $last = $c; - $c = dirname($c); - } - } - - if (!empty($found_script)) { - $found_script = realpath($found_script); - - // Guard against errors: if we have found a "drush" script - // (that is, theoretically a drush wrapper script), and - // there is a "drush.launcher" script in the same directory, - // then we will skip the "drush" script and use the drush launcher - // instead. This is because drush "wrapper" scripts should - // only ever exist at the root of a site, and there should - // never be a drush "launcher" at the root of a site. - // Therefore, if we find a "drush.launcher" next to a script - // called "drush", we have probably found a Drush install directory, - // not a site root. Adjust appropriately. Note that this - // also catches the case where a drush "finder" script finds itself. - if (is_file(dirname($found_script) . "{$sep}drush.launcher")) { - $found_script = dirname($found_script) . "{$sep}drush.launcher"; - } - } - - // Didn't find any site-local Drush, or @use'd Drush. - // Skip the Bash niceties of the launcher and proceed to drush_main() in either case: - // - No script was found and we are running a Phar - // - The found script *is* the Phar https://github.com/drush-ops/drush/pull/2246. - $phar_path = class_exists('Phar') ? Phar::running(FALSE) : ''; - if ((empty($found_script) && $phar_path) || !empty($found_script) && $found_script == $phar_path) { - drush_run_main($DEBUG, $sep, "Phar detected. Proceeding to drush_main()."); - } - - // Didn't find any site-local Drush, or @use'd Drush, or Phar. - // There should be a drush.launcher in same directory as this script. - if (empty($found_script)) { - $found_script = dirname(__DIR__) . "{$sep}drush.launcher"; - } - - if (drush_is_windows()) { - // Sometimes we found launcher in /bin, and sometimes not. Adjust accordingly. - if (strpos($found_script, 'bin')) { - $found_script = dirname($found_script). $sep. 'drush.php.bat'; - } - else { - array_unshift($arguments, dirname($found_script). $sep. 'drush.php'); - $found_script = 'php'; - } - } - - // Always use pcntl_exec if it exists. - $use_pcntl_exec = function_exists("pcntl_exec"); - - // If we have posix_getppid, then pass in the shell pid so - // that 'site-set' et. al. can work correctly. - if (function_exists('posix_getppid')) { - putenv("DRUSH_SHELL_PID=" . posix_getppid()); - } - - // Set an environment variable indicating which script - // the Drush finder found. If we end up re-entrantly calling - // another Drush finder, then we will skip searching for - // a site-local Drush, and always use the drush.launcher - // found previously. This environment variable typically should - // not be set by clients. - putenv("DRUSH_FINDER_SCRIPT=$found_script"); - - // Emit a message in debug mode advertising the location of the - // script we found. - if ($DEBUG) { - $launch_method = $use_pcntl_exec ? 'pcntl_exec' : 'proc_open'; - fwrite(STDERR, "Using the Drush script found at $found_script using $launch_method\n"); - } - - if ($use_pcntl_exec) { - // Get the current environment for pnctl_exec. - $env = drush_env(); - - // Launch the new script in the same process. - // If the launch succeeds, then it will not return. - $error = pcntl_exec($found_script, $arguments, $env); - if (!$error) { - $errno = pcntl_get_last_error(); - $strerror = pcntl_strerror($errno); - fwrite(STDERR, "Error has occurred executing the Drush script found at $found_script\n"); - fwrite(STDERR, "(errno {$errno}) $strerror\n"); - } - exit(1); - } - else { - $escaped_args = array_map(function($item) { return drush_escapeshellarg($item); }, $arguments); - // Double quotes around $found_script as it can contain spaces. - $cmd = drush_escapeshellarg($found_script). ' '. implode(' ', $escaped_args); - if (drush_is_windows()) { - // Windows requires double quotes around whole command. - // @see https://bugs.php.net/bug.php?id=49139 - // @see https://bugs.php.net/bug.php?id=60181 - $cmd = '"'. $cmd. '"'; - } - $process = proc_open($cmd, array(0 => STDIN, 1 => STDOUT, 2 => STDERR), $pipes, $cwd); - $proc_status = proc_get_status($process); - $exit_code = proc_close($process); - exit($proc_status["running"] ? $exit_code : $proc_status["exitcode"] ); - } -} - -/** - * Run drush_main() and then exit. Used when we cannot hand over execution to - * the launcher. - * - * @param bool $DEBUG - * Are we in debug mode - * @param string $sep - * Directory separator - * @param string $msg - * Debug message to log before running drush_main() - */ -function drush_run_main($DEBUG, $sep, $msg) { -// Emit a message in debug mode advertising how we proceeded. - if ($DEBUG) { - fwrite(STDERR, $msg. "\n"); - } - require __DIR__ . "{$sep}preflight.inc"; - exit(drush_main()); -} diff --git a/sut b/sut new file mode 100755 index 0000000000..4ea6e05c31 --- /dev/null +++ b/sut @@ -0,0 +1,29 @@ +#!/usr/bin/env php +webroot(); - $major_version = substr($version_string, 0, 1); - - if (!isset($profile)) { - $profile = $major_version >= 7 ? 'testing' : 'default'; - } // Install (if needed). foreach ($sites_subdirs as $subdir) { @@ -468,7 +463,7 @@ function setUpDrupal($num_sites = 1, $install = FALSE, $version_string = UNISH_D } // Write an empty sites.php. Needed for multi-site on D8+. - if ($major_version >= 7 && !file_exists($root . '/sites/sites.php')) { + if (!file_exists($root . '/sites/sites.php')) { copy($root . '/sites/example.sites.php', $root . '/sites/sites.php'); } @@ -527,9 +522,9 @@ function unish_file_aliases($aliases) { } /** - * The sidewide directory for a Drupal 8 installation. + * The sidewide directory for Drupal extensions. */ - function drupalSitewideDirectory($major_version = NULL) { + function drupalSitewideDirectory() { return '/sites/all'; } } diff --git a/tests/completeTest.php b/tests/completeTest.php index 6166a76094..847a2c49a5 100644 --- a/tests/completeTest.php +++ b/tests/completeTest.php @@ -24,8 +24,8 @@ static function setUpBeforeClass() { public function testComplete() { - if ($this->is_windows()) { - $this->markTestSkipped('Complete tests not fully working nor needed on Windows.'); + if (TRUE) { + $this->markTestSkipped('Complete tests not needed.'); } // We copy our completetest commandfile into our path. diff --git a/tests/contextTest.php b/tests/contextTest.php index 2743dc49ce..4308737a1a 100644 --- a/tests/contextTest.php +++ b/tests/contextTest.php @@ -22,7 +22,7 @@ function setUpPaths() { $this->paths = array( 'custom' => self::getSandbox(), 'site' => $this->site, - 'drupal' => $this->webroot() . '/sites/all/drush', + 'drupal' => $this->webroot() . '/drush', 'drupal-parent' => dirname($this->webroot()) . '/drush', 'user' => $this->home, 'home.drush' => $this->home . '/.drush', @@ -189,6 +189,7 @@ function testContextHierarchy() { $output = $this->getOutput(); $actuals = json_decode(trim($output)); $this->assertEquals('alias1', $actuals->contextConfig); + unlink($this->webroot() . '/sites/' . $this->env . '/aliases.drushrc.php'); // Command specific wins over non-specific. If it did not, $expected would // be 'site'. Note we call unit-eval command in order not to purturb diff --git a/tests/drushScriptTest.php b/tests/drushScriptTest.php deleted file mode 100644 index b9fb19bed1..0000000000 --- a/tests/drushScriptTest.php +++ /dev/null @@ -1,148 +0,0 @@ -markTestSkipped('Environment variables not yet passed along to Process by execute().'); - - - // @todo: could probably run this test on mingw - if ($this->is_windows()) { - $this->markTestSkipped('Environment variable tests not currently functional on Windows.'); - } - - $options = array(); - $env = array('PHP_OPTIONS' => '-d default_mimetype="text/drush"'); - $this->drush('ev', array('print ini_get("default_mimetype");'), $options, NULL, NULL, self::EXIT_SUCCESS, NULL, $env); - $output = $this->getOutput(); - $this->assertEquals('text/drush', $output); - } - - public function testDrushFinder() { - $this->markTestSkipped('The Finder is not long for this world. Disabling this test.'); - - $globalDrushDotPhp = Path::join(self::getDrush(), '../drush.php'); - - // Control: test `drush --root ` ... with no site-local Drush - $drush_location = $this->getDrushLocation(); - $this->assertEquals($globalDrushDotPhp, $drush_location); - - // We will try copying a site-local Drush to - // all of the various locations the 'drush finder' - // might expect to find it. - $drush_locations = array( - "vendor", - "../vendor", - "sites/all/vendor", - "sites/all", - ); - - foreach ($drush_locations as $drush_base) { - $drush_root = $this->create_site_local_drush($drush_base); - - // Test `drush --root ` ... with a site-local Drush - $drush_location = $this->getDrushLocation(array('root' => $this->webroot())); - $this->assertEquals(realpath($drush_root . '/drush.php'), realpath($drush_location)); - // Ensure that --local was NOT added - $result = $this->drush('ev', array('return drush_get_option("local");'), array('root' => $this->webroot())); - $output = $this->getOutput(); - $this->assertEquals("", $output); - - // Run the `drush --root` test again, this time with - // a drush.wrapper script in place. - $this->createDrushWrapper($drush_base); - $drush_location = $this->getDrushLocation(array('root' => $this->webroot())); - $this->assertEquals(realpath($drush_root . '/drush.php'), realpath($drush_location)); - // Test to see if --local was added - $result = $this->drush('ev', array('var_export(drush_get_option("local"));'), array('root' => $this->webroot())); - $output = $this->getOutput(); - $this->assertEquals("true", $output); - - // Get rid of the symlink and site-local Drush we created - $this->remove_site_local_drush($drush_base); - } - - // Next, try again with a site-local Drush in a location - // that Drush does not search. - $mysterious_location = "path/drush/does/not/search"; - $drush_root = $this->create_site_local_drush($mysterious_location); - // We should not find the site-local Drush without a Drush wrapper. - $drush_location = $this->getDrushLocation(array('root' => $this->webroot())); - $this->assertEquals($globalDrushDotPhp, $drush_location); - $this->createDrushWrapper($mysterious_location); - // Now that there is a Drush wrapper, we should be able to find the site-local Drush. - $drush_location = $this->getDrushLocation(array('root' => $this->webroot())); - $this->assertEquals(realpath($drush_root . '/drush.php'), $drush_location); - } - - /** - * Copy UNISH_DRUSH into the specified site-local location. - */ - function create_site_local_drush($drush_base) { - $drush_root = $this->webroot() . '/' . $drush_base . '/drush/drush'; - $bin_dir = $this->webroot() . '/' . $drush_base . '/bin'; - - $this->mkdir(dirname($drush_root)); - $this->recursive_copy(dirname(UNISH_DRUSH), $drush_root); - @chmod($drush_root . '/drush', 0777); - @chmod($drush_root . '/drush.launcher', 0777); - $this->mkdir($bin_dir); - symlink($drush_root . '/drush', $bin_dir . '/drush'); - - return $drush_root; - } - - function remove_site_local_drush($drush_base) { - // Get rid of the symlink and site-local Drush we created - unish_file_delete_recursive($this->webroot() . '/' . $drush_base . '/drush/drush'); - unlink($this->webroot() . '/' . $drush_base . '/bin/drush'); - if (file_exists($this->webroot() . '/drush.wrapper')) { - unlink($this->webroot() . '/drush.wrapper'); - } - } - - /** - * TODO: Create a Drush wrapper script, and copy it to - * to the root of the fake Drupal site, and point it - * at the specified site-local Drush script. - */ - function createDrushWrapper($drush_base) { - $drush_launcher = $drush_base . '/drush/drush/drush.launcher'; - - $drush_wrapper_src = dirname(UNISH_DRUSH) . '/examples/drush.wrapper'; - $drush_wrapper_contents = file_get_contents($drush_wrapper_src); - $drush_wrapper_contents = preg_replace('#\.\./vendor/bin/drush.launcher#', $drush_launcher, $drush_wrapper_contents); - $drush_wrapper_target = $this->webroot() . '/drush.wrapper'; - - file_put_contents($drush_wrapper_target, $drush_wrapper_contents); - @chmod($drush_wrapper_target, 0777); - } - - /** - * Get the current location of the Drush script via - * `drush status 'Drush script' --format=yaml`. This - * will return results other than UNISH_DRUSH in the - * presence of a site-local Drush. - */ - function getDrushLocation($options = array()) { - $options += array( - 'format' => 'yaml', - 'verbose' => NULL, - 'fields' => 'drush-script', - ); - $result = $this->drush('status', [], $options); - - $output = $this->getOutput(); - list($key, $value) = explode(": ", $output); - return trim($value, "'"); - } -}