diff --git a/examples/example.aliases.drushrc.php b/examples/example.aliases.drushrc.php index 076697c..534523a 100644 --- a/examples/example.aliases.drushrc.php +++ b/examples/example.aliases.drushrc.php @@ -156,6 +156,9 @@ * '%files': Path to 'files' directory. This will be looked up if not specified. * '%root': A reference to the Drupal root defined in the 'root' item * in the site alias record. + * - 'php': path to custom php interpreter, defaults tu /usr/bin/php + * - 'php-options': commandline options for php interpreter, you may + * want to set this to '-d error_reporting="E_ALL^E_DEPRECATED"' * - 'command-specific': These options will only be set if the alias * is used with the specified command. In the example below, the option * `--no-cache` will be selected whenever the @stage alias diff --git a/includes/backend.inc b/includes/backend.inc index f26743a..2223f12 100644 --- a/includes/backend.inc +++ b/includes/backend.inc @@ -60,12 +60,14 @@ * Identify the JSON encoded output from a command. */ define('DRUSH_BACKEND_OUTPUT_DELIMITER', 'DRUSH_BACKEND_OUTPUT_START>>>%s<< "\\0")); + $data['output'] = preg_replace("/$packet_regex/s", '', drush_backend_output_collect(NULL)); + + if (drush_get_context('DRUSH_QUIET', FALSE)) { + ob_end_clean(); + } $result_object = drush_backend_get_result(); if (isset($result_object)) { @@ -138,9 +145,52 @@ function drush_backend_output() { // Return the options that were set at the end of the process. $data['context'] = drush_get_merged_options(); - if (!drush_get_context('DRUSH_QUIET')) { - printf(DRUSH_BACKEND_OUTPUT_DELIMITER, json_encode($data)); + printf(DRUSH_BACKEND_OUTPUT_DELIMITER, json_encode($data)); +} + +/** + * Callback to collect backend command output. + */ +function drush_backend_output_collect($string) { + static $output = ''; + if (is_null($string)) { + return $output; + } + + $output .= $string; + return $string; +} + +/** + * Output buffer functions that discards all output but backend packets. + */ +function drush_backend_output_discard($string) { + $packet_regex = strtr(sprintf(DRUSH_BACKEND_PACKET_PATTERN, "(.*)"), array("\0" => "\\0")); + if (preg_match_all("/$packet_regex/s", $string, $matches)) { + return implode('', $matches[0]); + } +} + +/** + * Output a backend packet if we're running as backend. + * + * @param packet + * The packet to send. + * @param data + * Data for the command. + * + * @return + * A boolean indicating whether the command was output. + */ +function drush_backend_packet($packet, $data) { + if (drush_get_context('DRUSH_BACKEND')) { + $data['packet'] = $packet; + $data = json_encode($data); + printf(DRUSH_BACKEND_PACKET_PATTERN, $data); + return TRUE; } + + return FALSE; } /** @@ -150,12 +200,14 @@ function drush_backend_output() { * The output of a drush command * @param integrate * Integrate the errors and log messages from the command into the current process. + * @param outputted + * Whether output has already been handled. * * @return * An associative array containing the data from the external command, or the string parameter if it * could not be parsed successfully. */ -function drush_backend_parse_output($string, $integrate = TRUE) { +function drush_backend_parse_output($string, $backend_options = array(), $outputted = FALSE) { $regex = sprintf(DRUSH_BACKEND_OUTPUT_DELIMITER, '(.*)'); preg_match("/$regex/s", $string, $match); @@ -170,9 +222,7 @@ function drush_backend_parse_output($string, $integrate = TRUE) { if (!empty($output)) { $data = json_decode($output, TRUE); if (is_array($data)) { - if ($integrate) { - _drush_backend_integrate($data); - } + _drush_backend_integrate($data, $backend_options, $outputted); return $data; } } @@ -180,19 +230,23 @@ function drush_backend_parse_output($string, $integrate = TRUE) { } /** - * Integrate log messages and error statuses into the current process. + * Integrate log messages and error statuses into the current + * process. * - * Output produced by the called script will be printed, errors will be set - * and log messages will be logged locally. + * Output produced by the called script will be printed if we didn't print it + * on the fly, errors will be set, and log messages will be logged locally, if + * not already logged. * * @param data * The associative array returned from the external command. + * @param outputted + * Whether output has already been handled. */ -function _drush_backend_integrate($data) { - if (is_array($data['log'])) { +function _drush_backend_integrate($data, $backend_options, $outputted) { + if (is_array($data['log']) && $backend_options['log'] && !$outputted) { foreach($data['log'] as $log) { $message = is_array($log['message']) ? implode("\n", $log['message']) : $log['message']; - if (!is_null($log['error'])) { + if (!is_null($log['error']) && $backend_options['integrate']) { drush_set_error($log['error'], $message); } else { @@ -204,10 +258,9 @@ function _drush_backend_integrate($data) { if (drush_cmp_error('DRUSH_APPLICATION_ERROR') && !empty($data['output'])) { drush_set_error("DRUSH_APPLICATION_ERROR", dt("Output from failed command :\n !output", array('!output' => $data['output']))); } - else { + elseif (!$outputted && $backend_options['output']) { print ($data['output']); } - } /** @@ -224,7 +277,8 @@ function _drush_backend_integrate($data) { * If it executed successfully, it returns an associative array containing the command * called, the output of the command, and the error code of the command. */ -function _drush_proc_open($cmd, $data = NULL, $context = NULL) { +function _drush_proc_open($cmd, $command_options = NULL, $context = NULL, $backend_options = array()) { + $descriptorspec = array( 0 => array("pipe", "r"), // stdin is a pipe that the child will read from 1 => array("pipe", "w"), // stdout is a pipe that the child will write to @@ -232,17 +286,44 @@ function _drush_proc_open($cmd, $data = NULL, $context = NULL) { ); $process = proc_open($cmd, $descriptorspec, $pipes, null, null, array('context' => $context)); if (is_resource($process)) { - if ($data) { - fwrite($pipes[0], json_encode($data)); // pass the data array in a JSON encoded string + if ($command_options) { + fwrite($pipes[0], json_encode($command_options)); // pass the data array in a JSON encoded string } fclose($pipes[0]); $info = stream_get_meta_data($pipes[1]); stream_set_blocking($pipes[1], TRUE); stream_set_timeout($pipes[1], 1); - $string = ''; + $output = ''; + $end_of_output = FALSE; + $outputted = FALSE; + $output_label = array_key_exists('output-label', $backend_options) ? $backend_options['output-label'] : FALSE; while (!feof($pipes[1]) && !$info['timed_out']) { - $string .= fgets($pipes[1], 4096); + $string = fgets($pipes[1], 4096); + if (preg_match('/DRUSH_BACKEND_OUTPUT_START/', $string)) { + $end_of_output = TRUE; + } + if (!$end_of_output) { + drush_backend_parse_packets($string, $backend_options); + // Pass output through. + if ($backend_options['output'] && !empty($string)) { + if ($output_label) { + foreach (explode("\n", $string) as $line) { + if (empty($line)) { + fwrite(STDOUT, "\n"); + } + else { + fwrite(STDOUT, $output_label . $line); + } + } + } + else { + fwrite(STDOUT, $string); + } + } + $outputted = TRUE; + } + $output .= $string; $info = stream_get_meta_data($pipes[1]); flush(); }; @@ -251,7 +332,9 @@ function _drush_proc_open($cmd, $data = NULL, $context = NULL) { stream_set_blocking($pipes[2], TRUE); stream_set_timeout($pipes[2], 1); while (!feof($pipes[2]) && !$info['timed_out']) { - $string .= fgets($pipes[2], 4096); + $string = fgets($pipes[2], 4096); + $output .= $string; + fwrite(STDERR, $string); $info = stream_get_meta_data($pipes[2]); flush(); }; @@ -259,40 +342,154 @@ function _drush_proc_open($cmd, $data = NULL, $context = NULL) { fclose($pipes[1]); fclose($pipes[2]); $code = proc_close($process); - return array('cmd' => $cmd, 'output' => $string, 'code' => $code); + return array('cmd' => $cmd, 'output' => $output, 'code' => $code, 'outputted' => $outputted); } return FALSE; } /** + * Parse out and remove backend packet from the supplied string and + * invoke the commands. + */ +function drush_backend_parse_packets(&$string, $backend_options) { + $packet_regex = strtr(sprintf(DRUSH_BACKEND_PACKET_PATTERN, "(.*)"), array("\0" => "\\0")); + if (preg_match("/$packet_regex/s", $string, $match)) { + $entry = (array) json_decode($match[1]); + if (is_array($entry) && isset($entry['packet'])) { + if (function_exists($function = 'drush_backend_packet_' . $entry['packet'])) { + $function($entry, $backend_options); + } + else { + drush_log(dt("Unknown backend packet @packet", array('@packet' => $entry['packet'])), 'notice'); + } + } + else { + drush_log(dt("Malformed backend packet"), 'error'); + drush_log(dt("Bad packet: @packet", array('@packet' => print_r($entry, TRUE))), 'debug'); + } + + $string = preg_replace("/$packet_regex/s", '', $string); + } +} + +/** + * Converts old parameter based options to backend_invoke commands to + * the new option array format. + * + * @param args + * func_get_args() from the original command. + * @param offset + * The offset of the $data parameter. + * + * @return + * A option array. + */ +function _drush_backend_oldstyle_options($args, $offset) { + $options = array(); + list( + $data, + $options['method'], + $options['integrate'], + $options['drush-script'], + $options['remote-host'], + $options['remote-user'] + ) = array_slice($args, $offset); + $options['integrate'] = array_key_exists('#integrate', $data) ? $data['#integrate'] : $options['integrate']; + $options['output'] = $options['log'] = TRUE; + return $options; +} + +/** + * Default options for backend_invoke commands. + */ +function _drush_backend_default_options($command, $command_options, $backend_options) { + return array( + 'method' => 'GET', + 'output' => TRUE, + 'log' => TRUE, + 'integrate' => TRUE, + 'prefix' => $command . ': ', + ); +} + +/** * Invoke a drush backend command. * * @param command + * A defined drush command such as 'cron', 'status' or any of the available + * ones such as 'drush pm'. + * @param command_options + * Optional. An array containing options to pass to the call. Common + * options would be 'uri' if you want to call a command on a different + * site, or 'root', if you want to call a command using a different Drupal + * installation. Array items with a numeric key are treated as optional + * arguments to the command. + * @param backend_options + * Optional. An array of options for the invocation. + * 'method' + * Optional. Defaults to 'GET'. + * If this parameter is set to 'POST', the $data array will be passed + * to the script being called as a JSON encoded string over the STDIN + * pipe of that process. This is preferable if you have to pass + * sensitive data such as passwords and the like. + * For any other value, the $data array will be collapsed down into a + * set of command line options to the script. + * 'integrate' + * Optional. Defaults to TRUE. + * If TRUE, any error statuses will be integrated into the current + * process. This might not be what you want, if you are writing a + * command that operates on multiple sites. + * 'log' + * Optional. Defaults to TRUE. + * If TRUE, any log messages will be integrated into the current + * process. + * 'output' + * Optional. Defaults to TRUE. + * If TRUE, output from the command will be synchronously printed to + * stdout. + * 'drush-script' + * Optional. Defaults to the current drush.php file on the local + * machine, and to simply 'drush' (the drush script in the current + * PATH) on remote servers. You may also specify a different drush.php + * script explicitly. You will need to set this when calling drush on + * a remote server if 'drush' is not in the PATH on that machine. + * 'remote-host' + * Optional. A remote host to execute the drush command on. + * 'remote-user' + * Optional. Defaults to the current user. If you specify this, you can + * choose which module to send. + * 'ssh-options' + * Optional. Defaults to "-o PasswordAuthentication=no" + * + * Deprecated arguments: drush_backend_invoke_args can also be called + * with the following deprecated function arguments: + * + * param command * A defined drush command such as 'cron', 'status' or so on. - * @param args + * param args * An array of command arguments. - * @param data + * param data * Optional. An array containing options to pass to the call. Common options would be 'uri' if you want to call a command * on a different site, or 'root', if you want to call a command using a different Drupal installation. * Array items with a numeric key are treated as optional arguments to the command. - * @param method + * param method * Optional. Defaults to 'GET'. * If this parameter is set to 'POST', the $data array will be passed to the script being called as a JSON encoded string over * the STDIN pipe of that process. This is preferable if you have to pass sensitive data such as passwords and the like. * For any other value, the $data array will be collapsed down into a set of command line options to the script. - * @param integrate + * param integrate * Optional. Defaults to TRUE. * If TRUE, any error statuses or log messages will be integrated into the current process. This might not be what you want, * if you are writing a command that operates on multiple sites. - * @param drush_path + * param drush_path * Optional. Defaults to the current drush.php file on the local machine, and * to simply 'drush' (the drush script in the current PATH) on remote servers. * You may also specify a different drush.php script explicitly. You will need * to set this when calling drush on a remote server if 'drush' is not in the * PATH on that machine. - * @param hostname + * param hostname * Optional. A remote host to execute the drush command on. - * @param username + * param username * Optional. Defaults to the current user. If you specify this, you can choose which module to send. * * @deprecated Prefer wrapper function @see drush_invoke_process when possible. @@ -302,9 +499,17 @@ function _drush_proc_open($cmd, $data = NULL, $context = NULL) { * If the command could not be completed successfully, FALSE. * If the command was completed, this will return an associative array containing the data from drush_backend_output(). */ -function drush_backend_invoke_args($command, $args = array(), $data = array(), $method = 'GET', $integrate = TRUE, $drush_path = NULL, $hostname = NULL, $username = NULL, $ssh_options = NULL) { - $cmd = _drush_backend_generate_command($command, $args, $data, $method, $drush_path, $hostname, $username, $ssh_options); - return _drush_backend_invoke($cmd, $data, array_key_exists('#integrate', $data) ? $data['#integrate'] : $integrate); +function drush_backend_invoke_args($command, $args, $command_options = array(), $backend_options = array()) { + // If called with the deprecated arguments list, then the fourth argument will be + // a method string, 'PUT' or 'GET' rather than the backend options array. If this + // is the case, then convert the old-style options to an array. + if (!is_array($backend_options)) { + $backend_options = _drush_backend_oldstyle_options(func_get_args(), 2); + } + $backend_options += _drush_backend_default_options($command, $command_options, $backend_options); + $site_alias = drush_backend_generate_sitealias($backend_options); + $cmd = _drush_backend_generate_command_sitealias($site_alias, $command, $args, $command_options, $backend_options); + return _drush_backend_invoke($cmd, $command_options, $backend_options); } /** @@ -330,47 +535,28 @@ function drush_backend_invoke_args($command, $args = array(), $data = array(), $ * A defined drush command such as 'cron', 'status' or any of the available ones such as 'drush pm'. * @param args * An array of arguments for the command. - * @param data + * @param command_options * Optional. An array containing options to pass to the remote script. - * Array items with a numeric key are treated as optional arguments to the command. - * This parameter is a reference, as any options that have been represented as either an option, or an argument will be removed. - * This allows you to pass the left over options as a JSON encoded string, without duplicating data. - * Parameters that begin with a '#' are not passed on, but are used to affect - * the operation of backend invoke. Available options include: - * '#integrate' - * Print the output and merge the logs and error codes into - * the data structures for the running drush command. This causes - * the command to act as if it were called directly, without using - * backend invoke, while still running it in a separate process. - * Function results are still available. - * '#interactive' - * The output is displayed immediately, as it is produced, and it is - * possible for the user to send keyboard input to the command being - * executed. If interactive mode is used, then the command output, - * logs, etc. are NOT returned to the caller. - * '#override-simulated' - * Backend invoke will run the command even if DRUSH_SIMULATE is set. - * This is useful to run backend commands that fetch data that will - * be used by the simulated command. For example, sql-sync looks up - * the database options via backend invoke of sql-conf with override - * simulated set so that the sql-sync operation can be simulated using - * actual database setting values. - * @param method - * Optional. Defaults to 'GET'. - * If this parameter is set to 'POST', the $data array will be passed to the script being called as a JSON encoded string over - * the STDIN pipe of that process. This is preferable if you have to pass sensitive data such as passwords and the like. - * For any other value, the $data array will be collapsed down into a set of command line options to the script. - * @param integrate - * Optional. Defaults to TRUE. - * If TRUE, any error statuses or log messages will be integrated into the current process. This might not be what you want, - * if you are writing a command that operates on multiple sites. + * Array items with a numeric key are treated as optional arguments to the + * command. This parameter is a reference, as any options that have been + * represented as either an option, or an argument will be removed. This + * allows you to pass the left over options as a JSON encoded string, + * without duplicating data. + * @param backend_options + * Optional. An array of options for the invocation. + * @see drush_backend_invoke_args for documentation. * * @return * A text string representing a fully escaped command. */ -function drush_backend_invoke_sitealias($site_record, $command, $args, $data = array(), $method = 'GET', $integrate = TRUE) { - $cmd = _drush_backend_generate_command_sitealias($site_record, $command, $args, $data, $method); - return _drush_backend_invoke($cmd, $data, array_key_exists('#integrate', $data) ? $data['#integrate'] : $integrate); +function drush_backend_invoke_sitealias($site_record, $command, $args, $command_options = array(), $backend_options = array()) { + if (!is_array($backend_options)) { + $backend_options = _drush_backend_oldstyle_options(func_get_args(), 3); + } + $backend_options += _drush_backend_default_options($command, $command_options, $backend_options); + + $cmd = _drush_backend_generate_command_sitealias($site_record, $command, $args, $command_options, $backend_options); + return _drush_backend_invoke($cmd, $command_options, $backend_options); } /** @@ -385,34 +571,34 @@ function drush_backend_invoke_sitealias($site_record, $command, $args, $data = a * * @param cmd * The complete command line call to use. - * @param data + * @param command_options * An associative array to pass to the remote script. - * @param integrate - * Integrate data from remote script with local process. + * @param backend_options + * Options for the invocation. * * @return * If the command could not be completed successfully, FALSE. * If the command was completed, this will return an associative array containing the data from drush_backend_output(). */ -function _drush_backend_invoke($cmd, $data = null, $integrate = TRUE) { - if (drush_get_context('DRUSH_SIMULATE') && !array_key_exists('#override-simulated', $data)) { +function _drush_backend_invoke($cmd, $command_options = NULL, $backend_options = array()) { + if (drush_get_context('DRUSH_SIMULATE') && !array_key_exists('#override-simulated', $command_options)) { drush_print(dt('Simulating backend invoke: !cmd', array('!cmd' => $cmd))); return FALSE; } drush_log(dt('Backend invoke: !cmd', array('!cmd' => $cmd)), 'command'); - if (array_key_exists('#interactive', $data)) { + if (array_key_exists('#interactive', $command_options)) { drush_log(dt("executing !cmd", array('!cmd' => $cmd))); return drush_op_system($cmd); } else { - $proc = _drush_proc_open($cmd, $data); + $proc = _drush_proc_open($cmd, $command_options, NULL, $backend_options); - if (($proc['code'] == DRUSH_APPLICATION_ERROR) && $integrate) { + if (($proc['code'] == DRUSH_APPLICATION_ERROR) && $backend_options['integrate']) { drush_set_error('DRUSH_APPLICATION_ERROR', dt("The external command could not be executed due to an application error.")); } if ($proc['output']) { - $values = drush_backend_parse_output($proc['output'], $integrate); + $values = drush_backend_parse_output($proc['output'], $backend_options, $proc['outputted']); if (is_array($values)) { return $values; } @@ -428,36 +614,27 @@ function _drush_backend_invoke($cmd, $data = null, $integrate = TRUE) { * Generate a command to execute. * * @param command - * A defined drush command such as 'cron', 'status' or any of the available ones such as 'drush pm'. + * A defined drush command such as 'cron', 'status' or any of the available + * ones such as 'drush pm'. * @param args * An array of arguments for the command. * @param data * Optional. An array containing options to pass to the remote script. - * Array items with a numeric key are treated as optional arguments to the command. - * This parameter is a reference, as any options that have been represented as either an option, or an argument will be removed. - * This allows you to pass the left over options as a JSON encoded string, without duplicating data. - * @param method - * Optional. Defaults to 'GET'. - * If this parameter is set to 'POST', the $data array will be passed to the script being called as a JSON encoded string over - * the STDIN pipe of that process. This is preferable if you have to pass sensitive data such as passwords and the like. - * For any other value, the $data array will be collapsed down into a set of command line options to the script. - * @param drush_path - * Optional. Defaults to the current drush.php file on the local machine, and - * to simply 'drush' (the drush script in the current PATH) on remote servers. - * You may also specify a different drush.php script explicitly. You will need - * to set this when calling drush on a remote server if 'drush' is not in the - * PATH on that machine. - * @param hostname - * Optional. A remote host to execute the drush command on. - * @param username - * Optional. Defaults to the current user. If you specify this, you can choose which module to send. + * Array items with a numeric key are treated as optional arguments to the + * command. This parameter is a reference, as any options that have been + * represented as either an option, or an argument will be removed. This + * allows you to pass the left over options as a JSON encoded string, + * without duplicating data. + * @param backend_options + * Optional. An array of options for the invocation. + * @see drush_backend_invoke for documentation. * * @return * A text string representing a fully escaped command. * * @deprecated Is not as flexible as recommended command. @see _drush_backend_generate_command_sitealias(). */ -function _drush_backend_generate_command($command, $args, &$data, $method = 'GET', $drush_path = null, $hostname = null, $username = null, $ssh_options = NULL) { +function _drush_backend_generate_command($command, $args, &$command_options, $backend_options) { return _drush_backend_generate_command_sitealias( array( 'remote-host' => $hostname, @@ -466,7 +643,29 @@ function _drush_backend_generate_command($command, $args, &$data, $method = 'GET 'path-aliases' => array( '%drush-script' => $drush_path, ), - ), $command, $args, $data, $method); + ), $command, $args, $command_options, $backend_options); +} + +/** + * Helper function that generates an anonymous site alias specification for + * the given parameters. + */ +function drush_backend_generate_sitealias($backend_options) { + // Ensure default values. + $backend_options += array( + 'remote-host' => NULL, + 'remote-user' => NULL, + 'ssh-options' => NULL, + 'drush-script' => NULL, + ); + return array( + 'remote-host' => $backend_options['remote-host'], + 'remote-user' => $backend_options['remote-user'], + 'ssh-options' => $backend_options['ssh-options'], + 'path-aliases' => array( + '%drush-script' => $backend_options['drush-script'], + ), + ); } /** @@ -492,35 +691,39 @@ function _drush_backend_generate_command($command, $args, &$data, $method = 'GET * A defined drush command such as 'cron', 'status' or any of the available ones such as 'drush pm'. * @param args * An array of arguments for the command. - * @param data + * @param command_options * Optional. An array containing options to pass to the remote script. - * Array items with a numeric key are treated as optional arguments to the command. - * This parameter is a reference, as any options that have been represented as either an option, or an argument will be removed. - * This allows you to pass the left over options as a JSON encoded string, without duplicating data. - * @param method - * Optional. Defaults to 'GET'. - * If this parameter is set to 'POST', the $data array will be passed to the script being called as a JSON encoded string over - * the STDIN pipe of that process. This is preferable if you have to pass sensitive data such as passwords and the like. - * For any other value, the $data array will be collapsed down into a set of command line options to the script. + * Array items with a numeric key are treated as optional arguments to the + * command. This parameter is a reference, as any options that have been + * represented as either an option, or an argument will be removed. This + * allows you to pass the left over options as a JSON encoded string, + * without duplicating data. + * @param backend_options + * Optional. An array of options for the invocation. + * @see drush_backend_invoke for documentation. * * @return * A text string representing a fully escaped command. */ -function _drush_backend_generate_command_sitealias($site_record, $command, $args, &$data, $method = 'GET') { +function _drush_backend_generate_command_sitealias($site_record, $command, $args, &$command_options, $backend_options = array()) { $drush_path = null; $php = ''; - $hostname = array_key_exists('remote-host', $site_record) ? $site_record['remote-host'] : null; - $username = array_key_exists('remote-user', $site_record) ? $site_record['remote-user'] : null; - $ssh_options = array_key_exists('ssh-options', $site_record) ? $site_record['ssh-options'] : null; - $os = drush_os($site_record); + $site_record += array( + 'remote-host' => NULL, + 'remote-user' => NULL, + 'ssh-options' => NULL, + 'path-aliases' => array(), + ); + $site_record['path-aliases'] += array( + '%drush-script' => NULL, + ); - $drush_path = NULL; - if (array_key_exists('path-aliases', $site_record)) { - if (array_key_exists('%drush-script', $site_record['path-aliases'])) { - $drush_path = $site_record['path-aliases']['%drush-script']; - } - } + $hostname = $site_record['remote-host']; + $username = $site_record['remote-user']; + $ssh_options = $site_record['ssh-options']; + $drush_path = $site_record['path-aliases']['%drush-script']; + $os = drush_os($site_record); if (drush_is_local_host($hostname)) { $hostname = null; @@ -531,22 +734,22 @@ function _drush_backend_generate_command_sitealias($site_record, $command, $args // machine, we will use DRUSH_COMMAND, which is the path to the drush.php // that is running right now. For remote commands, we will run a wrapper // script instead of drush.php -- drush.bat on Windows, or drush on Linux. - $drush_command = drush_build_drush_command($drush_path, array_key_exists('php', $data) ? $data['php'] : NULL, $os, !empty($hostname)); + $drush_command = drush_build_drush_command($drush_path, array_key_exists('php', $command_options) ? $command_options['php'] : NULL, $os, !empty($hostname)); - $data['root'] = array_key_exists('root', $data) ? $data['root'] : drush_get_context('DRUSH_DRUPAL_ROOT'); - $data['uri'] = array_key_exists('uri', $data) ? $data['uri'] : drush_get_context('DRUSH_URI'); + $command_options['root'] = array_key_exists('root', $command_options) ? $command_options['root'] : drush_get_context('DRUSH_DRUPAL_ROOT'); + $command_options['uri'] = array_key_exists('uri', $command_options) ? $command_options['uri'] : drush_get_context('DRUSH_URI'); - $option_str = _drush_backend_argument_string($data, $method, $os); - foreach ($data as $key => $arg) { + $option_str = _drush_backend_argument_string($command_options, $backend_options['method']); + foreach ($command_options as $key => $arg) { if (is_numeric($key)) { $args[] = $arg; - unset($data[$key]); + unset($command_options[$key]); } } foreach ($args as $arg) { $command .= ' ' . drush_escapeshellarg($arg, $os); } - $cmd = $drush_command . " " . $option_str . " " . $command . (empty($data['#interactive']) ? " --backend" : ""); + $cmd = $drush_command . " " . $option_str . " " . $command . (empty($command_options['#interactive']) ? " --backend=2" : ""); if (!is_null($hostname)) { $username = (!is_null($username)) ? drush_escapeshellarg($username, "LOCAL") . "@" : ''; $ssh_options = (!is_null($ssh_options)) ? $ssh_options : drush_get_option('ssh-options', "-o PasswordAuthentication=no"); @@ -554,7 +757,7 @@ function _drush_backend_generate_command_sitealias($site_record, $command, $args } else { // TODO: `tty` is not usable on Windows. Is this necessary at all, and if so, is there a better way to do it? - $interactive = ' ' . ((drush_is_windows() || empty($data['#interactive'])) ? '' : ' > `tty`') . ' 2>&1'; + $interactive = ' ' . ((drush_is_windows() || empty($command_options['#interactive'])) ? '' : ' > `tty`') . ' 2>&1'; $cmd .= $interactive; } @@ -570,11 +773,20 @@ function _drush_backend_generate_command_sitealias($site_record, $command, $args * * Use this if you don't care what the return value of the command may be. */ -function drush_backend_fork($command, $data, $drush_path = null, $hostname = null, $username = null) { - $data['quiet'] = TRUE; +function drush_backend_fork($command, $command_options, $backend_options = array()) { + if (!is_array($backend_options)) { + $backend_options = array(); + // Not quite the same as the other commands, so we can't use + // _drush_backend_oldstyle_options. + list(,,$backend_options['drush-script'], $backend_options['remote-host'], $backend_options['remote-user']) = func_get_args(); + } +; + $command_options['quiet'] = TRUE; $args = explode(" ", $command); $command = array_shift($args); - $cmd = "(" . _drush_backend_generate_command($command, $args, $data, 'GET', $drush_path, $hostname, $username) . ' &) > /dev/null'; + $backend_options += _drush_backend_default_options($command, $command_options); + $site_record = drush_backend_generate_sitealias($backend_options); + $cmd = "(" . _drush_backend_generate_command_sitealias($site_record, $command, $args, $command_options, $backend_options) . ' &) > /dev/null'; drush_op_system($cmd); } @@ -604,6 +816,9 @@ function _drush_backend_argument_string(&$data, $method = 'GET', $os = NULL) { if (is_numeric($key)) { $args[$key] = $value; } + elseif ($key == 'quiet') { + $options['backend-quiet'] = $value; + } elseif (substr($key,0,1) != '#') { $options[$key] = $value; } diff --git a/includes/command.inc b/includes/command.inc index b21d51b..d30b9be 100644 --- a/includes/command.inc +++ b/includes/command.inc @@ -315,7 +315,13 @@ function drush_invoke_sitealias_args($site_alias_record, $command_name, $command function drush_redispatch_get_options() { // Start off by taking everything from the site alias and command line // ('cli' context) - $options = array_merge(drush_get_context('alias'), drush_get_context('cli')); + $cli_context = drush_get_context('cli'); + // local php settings should not override sitealias settings + unset($cli_context['php']); + unset($cli_context['php-options']); + // cli overrides sitealias + $options = $cli_context + drush_get_context('alias'); + $options = array_diff_key($options, array_flip(drush_sitealias_site_selection_keys())); unset($options['command-specific']); unset($options['path-aliases']); @@ -332,10 +338,6 @@ function drush_redispatch_get_options() { } } } - // 'php', if needed, will be included in DRUSH_COMMAND. If DRUSH_COMMAND - // is not used (e.g. when calling a remote instance of drush), then --php - // should not be passed along. - unset($options['php']); // If --bootstrap-to-first-arg is specified, do not // pass it along to remote commands. unset($options['bootstrap-to-first-arg']); diff --git a/includes/drush.inc b/includes/drush.inc index 8f61989..9f8b5f0 100644 --- a/includes/drush.inc +++ b/includes/drush.inc @@ -782,6 +782,7 @@ function drush_remote_command() { } $command = array_shift($args); $multi_options = drush_get_context('cli'); + $backend_options = array(); if (!drush_get_option('no-label', FALSE) && !$interactive) { $label_separator = ' >> '; @@ -792,14 +793,20 @@ function drush_remote_command() { } } $multi_options['reserve-margin'] = $max_name_length + strlen($label_separator); + $backend_options['output-label'] = str_pad($alias_name, $max_name_length, " ") . $label_separator; + $multi_options['quiet'] = FALSE; foreach ($site_list as $alias_name => $alias_record) { - $values = drush_do_site_command($alias_record, $command, $args, $multi_options); - foreach (explode("\n", $values['output']) as $line) { - if (empty($line)) { - drush_print(); - } - else { - drush_print(str_pad($alias_name, $max_name_length, " ") . $label_separator . $line); + $values = drush_do_site_command($alias_record, $command, $args, $multi_options, FALSE, $backend_options); + // This output code is only for talking to drush-4.x and earlier; + // if we are talking to drush-5.x or later, output has already been displayed + if (FALSE) { + foreach (explode("\n", $values['output']) as $line) { + if (empty($line)) { + drush_print(); + } + else { + drush_print(str_pad($alias_name, $max_name_length, " ") . $label_separator . $line); + } } } } @@ -887,7 +894,7 @@ function drush_do_multiple_command($command, $source_record, $destination_record * drush_invoke_sitealias_args. Please call the standard function * unless you need to set $integrate = TRUE. */ -function drush_do_site_command($site_record, $command, $args = array(), $data = array(), $integrate = FALSE) { +function drush_do_site_command($site_record, $command, $args = array(), $data = array(), $integrate = FALSE, $backend_options = array()) { $values = NULL; if (!empty($site_record)) { foreach ($site_record as $key => $value) { @@ -895,7 +902,11 @@ function drush_do_site_command($site_record, $command, $args = array(), $data = $data[$key] = $site_record[$key]; } } - $values = drush_backend_invoke_sitealias($site_record, $command, $args, $data, 'GET', $integrate); + if (($integrate === FALSE) && (!array_key_exists('quiet', $data))) { + $data['quiet'] = TRUE; + } + $backend_options['integrate'] = $integrate; + $values = drush_backend_invoke_sitealias($site_record, $command, $args, $data, $backend_options); } return $values; } @@ -975,6 +986,28 @@ function drush_log($message, $type = 'notice', $error = null) { ); $entry['error'] = $error; $log[] = $entry; + drush_backend_packet('log', $entry); + + return $callback($entry); +} + +/** + * Backend command callback. Add a log message to the log history. + * + * @param entry + * The log entry. + */ +function drush_backend_packet_log($entry, $backend_options) { + if (!$backend_options['log']) { + return; + } + $log =& drush_get_context('DRUSH_LOG', array()); + $callback = drush_get_context('DRUSH_LOG_CALLBACK', '_drush_print_log'); + $log[] = $entry; + // Yes, this looks odd, but we might in fact be a backend command + // that ran another backend command. + drush_backend_packet('log', $entry); + return $callback($entry); } @@ -1255,12 +1288,24 @@ function drush_set_error($error, $message = null) { } $error_log[$error][] = $message; - drush_log(($message) ? $message : $error, 'error', $error); + if (!drush_backend_packet('set_error', array('error' => $error, 'message' => $message))) { + drush_log(($message) ? $message : $error, 'error', $error); + } return FALSE; } /** + * Backend command for setting errors. + */ +function drush_backend_packet_set_error($data, $backend_options) { + if (!$backend_options['integrate']) { + return; + } + drush_set_error($data['error'], $data['message']); +} + +/** * Return the current error handling status * * @return diff --git a/includes/environment.inc b/includes/environment.inc index 94bcbca..3b7d5d6 100644 --- a/includes/environment.inc +++ b/includes/environment.inc @@ -642,22 +642,33 @@ function _drush_bootstrap_drush() { $backend = drush_set_context('DRUSH_BACKEND', drush_get_option(array('b', 'backend'))); + // Pipe implies quiet. + $quiet = drush_set_context('DRUSH_QUIET', drush_get_option(array('q', 'quiet', 'backend-quiet', 'p', 'pipe'))); + + drush_set_context('DRUSH_PIPE', drush_get_option(array('p', 'pipe'))); + if ($backend) { // Load options passed as a JSON encoded string through STDIN. $stdin_options = _drush_backend_get_stdin(); if (is_array($stdin_options)) { drush_set_context('stdin', $stdin_options); } + // Add an output buffer handler to collect output/pass through backend + // packets. Using a chunksize of 2 ensures that each line is flushed + // strait away. + if ($quiet) { + // Pass through of backend packets, discard regular output. + ob_start('drush_backend_output_discard', 2); + } + else { + // Collect output. + ob_start('drush_backend_output_collect', 2); + } } - // Pipe implies quiet. - $quiet = drush_set_context('DRUSH_QUIET', drush_get_option(array('q', 'quiet', 'p', 'pipe'))); - - drush_set_context('DRUSH_PIPE', drush_get_option(array('p', 'pipe'))); - - // When running in backend mode, all output is buffered, and returned - // as a property of a JSON encoded associative array. - if ($backend || $quiet) { + // In non-backend quiet mode we start buffering and discards it on command + // completion. + if ($quiet && !$backend) { ob_start(); } @@ -965,10 +976,13 @@ function _drush_bootstrap_drupal_database() { * Attempt to load the full Drupal system. */ function _drush_bootstrap_drupal_full() { - ob_start(); + if (!drush_get_context('DRUSH_QUIET', FALSE)) { + ob_start(); + } drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL); - ob_end_clean(); - + if (!drush_get_context('DRUSH_QUIET', FALSE)) { + ob_end_clean(); + } // If needed, prod module_implements() to recognize our system_watchdog() implementation. $dogs = module_implements('watchdog'); if (!in_array('system', $dogs)) { diff --git a/includes/sitealias.inc b/includes/sitealias.inc index f526df4..fdc704c 100644 --- a/includes/sitealias.inc +++ b/includes/sitealias.inc @@ -1367,9 +1367,18 @@ function drush_sitealias_set_alias_context($site_alias_settings, $prefix = '') { // There are some items that we should just skip $skip_list = drush_get_special_keys(); - // Also skip 'remote-host' and 'remote-user' if 'remote-host' is actually - // the local machine - if (array_key_exists('remote-host', $site_alias_settings) && drush_is_local_host($site_alias_settings['remote-host'])) { + // If 'php-options' are set in the alias, then we will force drush + // to redispatch via the remote dispatch mechanism even if the target is localhost. + if (array_key_exists('php-options', $site_alias_settings) || drush_get_context('DRUSH_BACKEND', FALSE)) { + if (!array_key_exists('remote-host', $site_alias_settings)) { + $site_alias_settings['remote-host'] = 'localhost'; + } + } + // If 'php-options' are not set in the alias, then skip 'remote-host' + // and 'remote-user' if 'remote-host' is actually the local machine. + // This prevents drush from using the remote dispatch mechanism (the command + // is just run directly on the local machine, bootstrapping to the specified alias) + elseif (array_key_exists('remote-host', $site_alias_settings) && drush_is_local_host($site_alias_settings['remote-host'])) { $skip_list[] = 'remote-host'; $skip_list[] = 'remote-user'; }