diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -192,6 +192,7 @@ 'PhutilExcessiveServiceCallsDaemon' => 'daemon/torture/PhutilExcessiveServiceCallsDaemon.php', 'PhutilExecChannel' => 'channel/PhutilExecChannel.php', 'PhutilExecPassthru' => 'future/exec/PhutilExecPassthru.php', + 'PhutilExecutableFuture' => 'future/exec/PhutilExecutableFuture.php', 'PhutilExecutionEnvironment' => 'utils/PhutilExecutionEnvironment.php', 'PhutilExtensionsTestCase' => 'moduleutils/__tests__/PhutilExtensionsTestCase.php', 'PhutilFacebookAuthAdapter' => 'auth/PhutilFacebookAuthAdapter.php', @@ -576,7 +577,7 @@ 'ConduitClientException' => 'Exception', 'ConduitClientTestCase' => 'PhutilTestCase', 'ConduitFuture' => 'FutureProxy', - 'ExecFuture' => 'Future', + 'ExecFuture' => 'PhutilExecutableFuture', 'ExecFutureTestCase' => 'PhutilTestCase', 'ExecPassthruTestCase' => 'PhutilTestCase', 'FileFinder' => 'Phobject', @@ -736,7 +737,8 @@ 'PhutilExampleBufferedIterator' => 'PhutilBufferedIterator', 'PhutilExcessiveServiceCallsDaemon' => 'PhutilTortureTestDaemon', 'PhutilExecChannel' => 'PhutilChannel', - 'PhutilExecPassthru' => 'Phobject', + 'PhutilExecPassthru' => 'PhutilExecutableFuture', + 'PhutilExecutableFuture' => 'Future', 'PhutilExecutionEnvironment' => 'Phobject', 'PhutilExtensionsTestCase' => 'PhutilTestCase', 'PhutilFacebookAuthAdapter' => 'PhutilOAuthAuthAdapter', diff --git a/src/future/exec/ExecFuture.php b/src/future/exec/ExecFuture.php --- a/src/future/exec/ExecFuture.php +++ b/src/future/exec/ExecFuture.php @@ -19,7 +19,7 @@ * @task interact Interacting With Commands * @task internal Internals */ -final class ExecFuture extends Future { +final class ExecFuture extends PhutilExecutableFuture { private $pipes = array(); private $proc = null; @@ -35,8 +35,6 @@ private $stdoutPos = 0; private $stderrPos = 0; private $command = null; - private $env = null; - private $cwd; private $readBufferSize; private $stdoutSizeLimit = PHP_INT_MAX; @@ -181,59 +179,6 @@ /** - * Set the current working directory to use when executing the command. - * - * @param string Directory to set as CWD before executing the command. - * @return this - * @task config - */ - public function setCWD($cwd) { - $this->cwd = $cwd; - return $this; - } - - - /** - * Set the environment variables to use when executing the command. - * - * @param array Environment variables to use when executing the command. - * @return this - * @task config - */ - public function setEnv($env, $wipe_process_env = false) { - if ($wipe_process_env) { - $this->env = $env; - } else { - $this->env = $env + $_ENV; - } - return $this; - } - - - /** - * Set the value of a specific environmental variable for this command. - * - * @param string Environmental variable name. - * @param string|null New value, or null to remove this variable. - * @return this - * @task config - */ - public function updateEnv($key, $value) { - if (!is_array($this->env)) { - $this->env = $_ENV; - } - - if ($value === null) { - unset($this->env[$key]); - } else { - $this->env[$key] = $value; - } - - return $this; - } - - - /** * Set whether to use non-blocking streams on Windows. * * @param bool Whether to use non-blocking streams. @@ -659,26 +604,14 @@ } } - - // NOTE: Convert all the environmental variables we're going to pass - // into strings before we install PhutilErrorTrap. If something in here - // is really an object which is going to throw when we try to turn it - // into a string, we want the exception to escape here -- not after we - // start trapping errors. - $env = $this->env; - if ($env !== null) { - foreach ($env as $key => $value) { - $env[$key] = (string)$value; - } - } - - // Same for the working directory. - if ($this->cwd === null) { - $cwd = null; + if ($this->hasEnv()) { + $env = $this->getEnv(); } else { - $cwd = (string)$this->cwd; + $env = null; } + $cwd = $this->getCWD(); + // NOTE: See note above about Phage. if (class_exists('PhutilErrorTrap')) { $trap = new PhutilErrorTrap(); diff --git a/src/future/exec/PhutilExecPassthru.php b/src/future/exec/PhutilExecPassthru.php --- a/src/future/exec/PhutilExecPassthru.php +++ b/src/future/exec/PhutilExecPassthru.php @@ -17,13 +17,12 @@ * @{method:setCWD}, and set the environment with @{method:setEnv}. * * @task command Executing Passthru Commands - * @task config Configuring Passthru Commands */ -final class PhutilExecPassthru extends Phobject { +final class PhutilExecPassthru extends PhutilExecutableFuture { + private $command; - private $env; - private $cwd; + private $passthruResult; /* -( Executing Passthru Commands )---------------------------------------- */ @@ -72,8 +71,13 @@ $unmasked_command = $command; } - $env = $this->env; - $cwd = $this->cwd; + if ($this->hasEnv()) { + $env = $this->getEnv(); + } else { + $env = null; + } + + $cwd = $this->getCWD(); $options = array(); if (phutil_is_windows()) { @@ -115,50 +119,23 @@ } -/* -( Configuring Passthru Commands )-------------------------------------- */ +/* -( Future )------------------------------------------------------------- */ - /** - * Set environmental variables for the subprocess. - * - * By default, variables are added to the environment of this process. You - * can optionally wipe the environment and pass only the specified values. - * - * // Env will have "X" and current env ("PATH", etc.) - * $exec->setEnv(array('X' => 'y')); - * - * // Env will have ONLY "X". - * $exec->setEnv(array('X' => 'y'), $wipe_process_env = true); - * - * @param dict Dictionary of environmental variables. - * @param bool Optionally, pass true to wipe the existing environment clean. - * @return this - * - * @task config - */ - public function setEnv(array $env, $wipe_process_env = false) { - if ($wipe_process_env) { - $this->env = $env; - } else { - $this->env = $env + $_ENV; + public function isReady() { + // This isn't really a future because it executes synchronously and has + // full control of the console. We're just implementing the interfaces to + // make it easier to share code with ExecFuture. + + if ($this->passthruResult === null) { + $this->passthruResult = $this->execute(); } - return $this->env; - } + return true; + } - /** - * Set the current working directory for the subprocess (that is, set where - * the subprocess will execute). If not set, the default value is the parent's - * current working directory. - * - * @param string Directory to execute the subprocess in. - * @return this - * - * @task config - */ - public function setCWD($cwd) { - $this->cwd = $cwd; - return $this; + protected function getResult() { + return $this->passthruResult; } } diff --git a/src/future/exec/PhutilExecutableFuture.php b/src/future/exec/PhutilExecutableFuture.php new file mode 100644 --- /dev/null +++ b/src/future/exec/PhutilExecutableFuture.php @@ -0,0 +1,122 @@ +setEnv(array('X' => 'y')); + * + * // Env will have ONLY "X". + * $exec->setEnv(array('X' => 'y'), $wipe_process_env = true); + * + * @param map Dictionary of environmental variables. + * @param bool Optionally, pass `true` to replace the existing environment. + * @return this + * + * @task config + */ + final public function setEnv(array $env, $wipe_process_env = false) { + // Force values to strings here. The underlying PHP commands get upset if + // they are handed non-string values as environmental variables. + foreach ($env as $key => $value) { + $env[$key] = (string)$value; + } + + if (!$wipe_process_env) { + $env = $env + $this->getEnv(); + } + + $this->env = $env; + + return $this; + } + + + /** + * Set the value of a specific environmental variable for this command. + * + * @param string Environmental variable name. + * @param string|null New value, or null to remove this variable. + * @return this + * @task config + */ + final public function updateEnv($key, $value) { + $env = $this->getEnv(); + + if ($value === null) { + unset($env[$key]); + } else { + $env[$key] = (string)$value; + } + + $this->env = $env; + + return $this; + } + + + /** + * Returns `true` if this command has a configured environment. + * + * @return bool True if this command has an environment. + * @task config + */ + final public function hasEnv() { + return ($this->env !== null); + } + + + /** + * Get the configured environment. + * + * @return map Effective environment for this command. + * @task config + */ + final public function getEnv() { + if (!$this->hasEnv()) { + $this->setEnv($_ENV, $wipe_process_env = true); + } + + return $this->env; + } + + + /** + * Set the current working directory for the subprocess (that is, set where + * the subprocess will execute). If not set, the default value is the parent's + * current working directory. + * + * @param string Directory to execute the subprocess in. + * @return this + * @task config + */ + final public function setCWD($cwd) { + $this->cwd = (string)$cwd; + return $this; + } + + + /** + * Get the command's current working directory. + * + * @return string Working directory. + * @task config + */ + final public function getCWD() { + return $this->cwd; + } + +} diff --git a/src/phage/bootloader/PhagePHPAgentBootloader.php b/src/phage/bootloader/PhagePHPAgentBootloader.php --- a/src/phage/bootloader/PhagePHPAgentBootloader.php +++ b/src/phage/bootloader/PhagePHPAgentBootloader.php @@ -47,6 +47,7 @@ 'xsprintf/PhutilCommandString.php', 'future/Future.php', 'future/FutureIterator.php', + 'future/exec/PhutilExecutableFuture.php', 'future/exec/ExecFuture.php', 'future/exec/CommandException.php', 'channel/PhutilChannel.php',