This repository has been archived by the owner on Jun 7, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 103
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Summary:Needed to extend it to support environment variables. Fix some annoyances with the existing API: - only able to get exit code if the exception is thrown (`git diff` often exits with 1 in non-error situations) - almost always specifying null stdin - almost always specifying DONT_VERBOSE - output was only printed at the end of a command (eg the slow git submodule command in HHVM looked like a hang) Existing API is now a wrapper around the new one. refs #12 refs #9 refs #5 Reviewed By: JoelMarcey Differential Revision: D3186167 fb-gh-sync-id: d64a852facbc6e8ed167fe462ff51cd1e4bde621 fbshipit-source-id: d64a852facbc6e8ed167fe462ff51cd1e4bde621
- Loading branch information
1 parent
7137ed4
commit f0c3dbd
Showing
5 changed files
with
313 additions
and
94 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,146 @@ | ||
<?hh | ||
/** | ||
* Copyright (c) 2016-present, Facebook, Inc. | ||
* All rights reserved. | ||
* | ||
* This source code is licensed under the BSD-style license found in the | ||
* LICENSE file in the root directory of this source tree. An additional grant | ||
* of patent rights can be found in the PATENTS file in the same directory. | ||
*/ | ||
namespace Facebook\ShipIt; | ||
|
||
class ShipItShellCommand { | ||
private ImmVector<string> $command; | ||
|
||
private Map<string, string> $environmentVariables = Map {}; | ||
private bool $throwForNonZeroExit = true; | ||
private ?string $stdin = null; | ||
private bool $outputToScreen = false; | ||
|
||
public function __construct( | ||
private string $path, | ||
...$command | ||
) { | ||
$this->command = new ImmVector($command); | ||
} | ||
|
||
public function setStdIn(string $input): this { | ||
$this->stdin = $input; | ||
return $this; | ||
} | ||
|
||
public function setOutputToScreen(): this { | ||
$this->outputToScreen = true; | ||
return $this; | ||
} | ||
|
||
public function setEnvironmentVariables( | ||
ImmMap<string, string> $vars, | ||
): this { | ||
$this->environmentVariables->setAll($vars); | ||
return $this; | ||
} | ||
|
||
public function setNoExceptions(): this { | ||
$this->throwForNonZeroExit = false; | ||
return $this; | ||
} | ||
|
||
public function runSynchronously(): ShipItShellCommandResult { | ||
$command = implode(' ', $this->command->map($str ==> escapeshellarg($str))); | ||
|
||
$fds = array( | ||
0 => array('pipe', 'r'), | ||
1 => array('pipe', 'w'), | ||
2 => array('pipe', 'w'), | ||
); | ||
$stdin = $this->stdin; | ||
if ($stdin === null) { | ||
unset($fds[0]); | ||
} | ||
|
||
$pipes = null; | ||
$fp = proc_open( | ||
$command, | ||
$fds, | ||
$pipes, | ||
$this->path, | ||
$this->environmentVariables->toArray(), | ||
); | ||
if (!$fp || !is_array($pipes)) { | ||
throw new \Exception("Failed executing $command"); | ||
} | ||
if ($stdin !== null) { | ||
while (strlen($stdin)) { | ||
$written = fwrite($pipes[0], $stdin); | ||
$stdin = substr($stdin, $written); | ||
} | ||
fclose($pipes[0]); | ||
} | ||
|
||
$stdout_stream = $pipes[1]; | ||
$stderr_stream = $pipes[2]; | ||
stream_set_blocking($stdout_stream, false); | ||
stream_set_blocking($stderr_stream, false); | ||
$stdout = ''; | ||
$stderr = ''; | ||
while (true) { | ||
$ready_streams = [$stdout_stream, $stderr_stream]; | ||
$null_byref = null; | ||
$result = stream_select( | ||
$ready_streams, | ||
/* write streams = */ $null_byref, | ||
/* exception streams = */ $null_byref, | ||
/* timeout = */ null, | ||
); | ||
if ($result === false) { | ||
break; | ||
} | ||
$all_empty = true; | ||
foreach ($ready_streams as $stream) { | ||
$out = fread($stream, 1024); | ||
if (strlen($out) === 0) { | ||
continue; | ||
} | ||
$all_empty = false; | ||
|
||
if ($stream === $stdout_stream) { | ||
$stdout .= $out; | ||
$this->maybeFwrite(STDOUT, $out); | ||
continue; | ||
} | ||
if ($stream === $stderr_stream) { | ||
$stderr .= $out; | ||
$this->maybeFwrite(STDERR, $out); | ||
continue; | ||
} | ||
|
||
invariant_violation('Unhandled stream!'); | ||
} | ||
|
||
if ($all_empty) { | ||
break; | ||
} | ||
} | ||
$exitcode = proc_close($fp); | ||
|
||
$result = new ShipItShellCommandResult( | ||
$exitcode, | ||
$stdout, | ||
$stderr, | ||
); | ||
|
||
if ($exitcode !== 0 && $this->throwForNonZeroExit) { | ||
throw new ShipItShellCommandException($command, $result); | ||
} | ||
|
||
return $result; | ||
} | ||
|
||
private function maybeFwrite(resource $stream, string $out): void { | ||
if (!$this->outputToScreen) { | ||
return; | ||
} | ||
fwrite($stream, $out); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
<?hh // strict | ||
/** | ||
* Copyright (c) 2016-present, Facebook, Inc. | ||
* All rights reserved. | ||
* | ||
* This source code is licensed under the BSD-style license found in the | ||
* LICENSE file in the root directory of this source tree. An additional grant | ||
* of patent rights can be found in the PATENTS file in the same directory. | ||
*/ | ||
namespace Facebook\ShipIt; | ||
|
||
class ShipItShellCommandException extends \Exception { | ||
public function __construct( | ||
private string $command, | ||
private ShipItShellCommandResult $result, | ||
) { | ||
$exitCode = $result->getExitCode(); | ||
$error = $result->getStdErr(); | ||
parent::__construct("$command returned exit code $exitCode: $error"); | ||
} | ||
|
||
public function getError(): string { | ||
return $this->result->getStdErr(); | ||
} | ||
|
||
public function getExitCode(): int { | ||
return $this->result->getExitCode(); | ||
} | ||
|
||
public function getOutput(): string { | ||
return $this->result->getStdOut(); | ||
} | ||
|
||
public function getResult(): ShipItShellCommandResult { | ||
return $this->result; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
<?hh // strict | ||
/** | ||
* Copyright (c) 2016-present, Facebook, Inc. | ||
* All rights reserved. | ||
* | ||
* This source code is licensed under the BSD-style license found in the | ||
* LICENSE file in the root directory of this source tree. An additional grant | ||
* of patent rights can be found in the PATENTS file in the same directory. | ||
*/ | ||
namespace Facebook\ShipIt; | ||
|
||
final class ShipItShellCommandResult { | ||
public function __construct( | ||
private int $exitCode, | ||
private string $stdout, | ||
private string $stderr, | ||
) { | ||
} | ||
|
||
public function getExitCode(): int { | ||
return $this->exitCode; | ||
} | ||
|
||
public function getStdOut(): string { | ||
return $this->stdout; | ||
} | ||
|
||
public function getStdErr(): string { | ||
return $this->stderr; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.