Page MenuHomePhorge

No OneTemporary

diff --git a/src/init/lib/PhutilBootloader.php b/src/init/lib/PhutilBootloader.php
index 7a8759e8..ab6bd587 100644
--- a/src/init/lib/PhutilBootloader.php
+++ b/src/init/lib/PhutilBootloader.php
@@ -1,332 +1,336 @@
<?php
/**
* IMPORTANT: Do not call any libphutil functions in this class, including
* functions like @{function:id}, @{function:idx} and @{function:pht}. They
* may not have loaded yet.
*/
final class PhutilBootloader {
private static $instance;
private $registeredLibraries = array();
private $libraryMaps = array();
private $extensionMaps = array();
private $extendedMaps = array();
private $currentLibrary = null;
private $classTree = array();
private $inMemoryMaps = array();
public static function newLibrary($name, $path) {
self::getInstance()->registerLibrary($name, $path);
}
public static function getInstance() {
if (!self::$instance) {
self::$instance = new PhutilBootloader();
}
return self::$instance;
}
private function __construct() {
// This method intentionally left blank.
}
public function getClassTree() {
return $this->classTree;
}
public function registerInMemoryLibrary($name, $map) {
$this->registeredLibraries[$name] = "memory:$name";
$this->inMemoryMaps[$name] = $map;
$this->getLibraryMap($name);
}
public function registerLibrary($name, $path) {
// Detect attempts to load the same library multiple times from different
// locations. This might mean you're doing something silly like trying to
// include two different versions of something, or it might mean you're
// doing something subtle like running a different version of 'arc' on a
// working copy of Arcanist.
if (isset($this->registeredLibraries[$name])) {
$old_path = $this->registeredLibraries[$name];
if ($old_path != $path) {
throw new PhutilLibraryConflictException($name, $old_path, $path);
}
}
$this->registeredLibraries[$name] = $path;
// If we're loading libphutil itself, load the utility functions first so
// we can safely call functions like "id()" when handling errors. In
// particular, this improves error behavior when "utils.php" itself can
// not load.
if ($name === 'arcanist') {
$root = $this->getLibraryRoot('arcanist');
$this->executeInclude($root.'/utils/utils.php');
}
// For libphutil v2 libraries, load all functions when we load the library.
if (!class_exists('PhutilSymbolLoader', false)) {
$root = $this->getLibraryRoot('arcanist');
$this->executeInclude($root.'/symbols/PhutilSymbolLoader.php');
}
$loader = new PhutilSymbolLoader();
$loader
->setLibrary($name)
->setType('function');
try {
$loader->selectAndLoadSymbols();
} catch (PhutilBootloaderException $ex) {
// Ignore this, it happens if a global function's file is removed or
// similar. Worst case is that we fatal when calling the function, which
// is no worse than fataling here.
} catch (PhutilMissingSymbolException $ex) {
// Ignore this, it happens if a global function is removed. Everything
// else loaded so proceed forward: worst case is a fatal when we
// hit a function call to a function which no longer exists, which is
// no worse than fataling here.
}
if (empty($_SERVER['PHUTIL_DISABLE_RUNTIME_EXTENSIONS'])) {
$extdir = $path.DIRECTORY_SEPARATOR.'extensions';
if (Filesystem::pathExists($extdir)) {
$extensions = id(new FileFinder($extdir))
->withSuffix('php')
->withType('f')
->withFollowSymlinks(true)
->setForceMode('php')
->find();
foreach ($extensions as $extension) {
$this->loadExtension(
$name,
$path,
$extdir.DIRECTORY_SEPARATOR.$extension);
}
}
}
return $this;
}
public function registerLibraryMap(array $map) {
$this->libraryMaps[$this->currentLibrary] = $map;
return $this;
}
public function getLibraryMap($name) {
if (isset($this->extendedMaps[$name])) {
return $this->extendedMaps[$name];
}
if (empty($this->libraryMaps[$name])) {
$root = $this->getLibraryRoot($name);
$this->currentLibrary = $name;
if (isset($this->inMemoryMaps[$name])) {
$this->libraryMaps[$name] = $this->inMemoryMaps[$name];
} else {
$okay = include $root.'/__phutil_library_map__.php';
if (!$okay) {
throw new PhutilBootloaderException(
"Include of '{$root}/__phutil_library_map__.php' failed!");
}
}
$map = $this->libraryMaps[$name];
$version = isset($map['__library_version__'])
? $map['__library_version__']
: 1;
switch ($version) {
case 1:
throw new Exception(
'libphutil v1 libraries are no longer supported.');
case 2:
// NOTE: In version 2 of the library format, all parents (both
// classes and interfaces) are stored in the 'xmap'. The value is
// either a string for a single parent (the common case) or an array
// for multiple parents.
foreach ($map['xmap'] as $child => $parents) {
foreach ((array)$parents as $parent) {
$this->classTree[$parent][] = $child;
}
}
break;
default:
throw new Exception("Unsupported library version '{$version}'!");
}
}
$map = $this->libraryMaps[$name];
// If there's an extension map for this library, merge the maps.
if (isset($this->extensionMaps[$name])) {
$emap = $this->extensionMaps[$name];
foreach (array('function', 'class', 'xmap') as $dict_key) {
if (!isset($emap[$dict_key])) {
continue;
}
$map[$dict_key] += $emap[$dict_key];
}
}
$this->extendedMaps[$name] = $map;
return $map;
}
public function getLibraryMapWithoutExtensions($name) {
// This just does all the checks to make sure the library is valid, then
// we throw away the result.
$this->getLibraryMap($name);
return $this->libraryMaps[$name];
}
public function getLibraryRoot($name) {
if (empty($this->registeredLibraries[$name])) {
throw new PhutilBootloaderException(
"The phutil library '{$name}' has not been loaded!");
}
return $this->registeredLibraries[$name];
}
public function getAllLibraries() {
return array_keys($this->registeredLibraries);
}
public function loadLibrarySource($library, $source) {
$path = $this->getLibraryRoot($library).'/'.$source;
$this->executeInclude($path);
}
+ public function loadLibrary($path) {
+ $this->executeInclude($path.'/__phutil_library_init__.php');
+ }
+
private function executeInclude($path) {
// Include the source using `include_once`, but convert any warnings or
// recoverable errors into exceptions.
// Some messages, including "Declaration of X should be compatible with Y",
// do not cause `include_once` to return an error code. Use
// error_get_last() to make sure we're catching everything in every PHP
// version.
// (Also, the severity of some messages changed between versions of PHP.)
// Note that we may enter this method after some earlier, unrelated error.
// In this case, error_get_last() will return information for that error.
// In PHP7 and later we could use error_clear_last() to clear that error,
// but the function does not exist in earlier versions of PHP. Instead,
// check if the value has changed.
// Some parser-like errors, including "class must implement all abstract
// methods", cause PHP to fatal immediately with an E_ERROR. In these
// cases, include_once() does not throw and never returns. We leave
// reporting enabled for these errors since we don't have a way to do
// anything more graceful.
// Likewise, some errors, including "cannot redeclare Class::method()"
// cause PHP to fatal immediately with E_COMPILE_ERROR. Treat these like
// the similar errors which raise E_ERROR.
// See also T12190.
$old_last = error_get_last();
try {
$old = error_reporting(E_ERROR | E_COMPILE_ERROR);
$okay = include_once $path;
error_reporting($old);
} catch (Exception $ex) {
throw $ex;
} catch (ParseError $throwable) {
// NOTE: As of PHP7, syntax errors may raise a ParseError (which is a
// Throwable, not an Exception) with a useless message (like "syntax
// error, unexpected ':'") and a trace which ends a level above this.
// Treating this object normally results in an unusable message which
// does not identify where the syntax error occurred. Converting it to
// a string and taking the first line gives us something reasonable,
// however.
$message = (string)$throwable;
$message = preg_split("/\n/", $message);
$message = reset($message);
throw new Exception($message);
}
if (!$okay) {
throw new Exception("Source file \"{$path}\" failed to load.");
}
$new_last = error_get_last();
if ($new_last !== null) {
if ($new_last !== $old_last) {
$message = $new_last['message'];
throw new Exception(
"Error while loading file \"{$path}\": {$message}");
}
}
}
private function loadExtension($library, $root, $path) {
$old_functions = get_defined_functions();
$old_functions = array_fill_keys($old_functions['user'], true);
$old_classes = array_fill_keys(get_declared_classes(), true);
$old_interfaces = array_fill_keys(get_declared_interfaces(), true);
$this->executeInclude($path);
$new_functions = get_defined_functions();
$new_functions = array_fill_keys($new_functions['user'], true);
$new_classes = array_fill_keys(get_declared_classes(), true);
$new_interfaces = array_fill_keys(get_declared_interfaces(), true);
$add_functions = array_diff_key($new_functions, $old_functions);
$add_classes = array_diff_key($new_classes, $old_classes);
$add_interfaces = array_diff_key($new_interfaces, $old_interfaces);
// NOTE: We can't trust the path we loaded to be the location of these
// symbols, because it might have loaded other paths.
foreach ($add_functions as $func => $ignored) {
$rfunc = new ReflectionFunction($func);
$fpath = Filesystem::resolvePath($rfunc->getFileName(), $root);
$this->extensionMaps[$library]['function'][$func] = $fpath;
}
foreach ($add_classes + $add_interfaces as $class => $ignored) {
$rclass = new ReflectionClass($class);
$cpath = Filesystem::resolvePath($rclass->getFileName(), $root);
$this->extensionMaps[$library]['class'][$class] = $cpath;
$xmap = $rclass->getInterfaceNames();
$parent = $rclass->getParentClass();
if ($parent) {
$xmap[] = $parent->getName();
}
if ($xmap) {
foreach ($xmap as $parent_class) {
$this->classTree[$parent_class][] = $class;
}
if (count($xmap) == 1) {
$xmap = head($xmap);
}
$this->extensionMaps[$library]['xmap'][$class] = $xmap;
}
}
// Clear the extended library cache (should one exist) so we know that
// we need to rebuild it.
unset($this->extendedMaps[$library]);
}
}
diff --git a/src/init/lib/moduleutils.php b/src/init/lib/moduleutils.php
index ad447cff..aba95f5b 100644
--- a/src/init/lib/moduleutils.php
+++ b/src/init/lib/moduleutils.php
@@ -1,53 +1,53 @@
<?php
function phutil_get_library_root($library) {
$bootloader = PhutilBootloader::getInstance();
return $bootloader->getLibraryRoot($library);
}
function phutil_get_library_root_for_path($path) {
foreach (Filesystem::walkToRoot($path) as $dir) {
if (Filesystem::pathExists($dir.'/__phutil_library_init__.php')) {
return $dir;
}
}
return null;
}
function phutil_get_library_name_for_root($path) {
$path = rtrim(Filesystem::resolvePath($path), '/');
$bootloader = PhutilBootloader::getInstance();
$libraries = $bootloader->getAllLibraries();
foreach ($libraries as $library) {
$root = $bootloader->getLibraryRoot($library);
if (rtrim(Filesystem::resolvePath($root), '/') == $path) {
return $library;
}
}
return null;
}
function phutil_get_current_library_name() {
$caller = head(debug_backtrace(false));
$root = phutil_get_library_root_for_path($caller['file']);
return phutil_get_library_name_for_root($root);
}
/**
* Warns about use of deprecated behavior.
*/
function phutil_deprecated($what, $why) {
PhutilErrorHandler::dispatchErrorMessage(
PhutilErrorHandler::DEPRECATED,
$what,
array(
'why' => $why,
));
}
function phutil_load_library($path) {
- require_once $path.'/__phutil_library_init__.php';
+ PhutilBootloader::getInstance()->loadLibrary($path);
}
diff --git a/src/runtime/ArcanistRuntime.php b/src/runtime/ArcanistRuntime.php
index 06262390..66624724 100644
--- a/src/runtime/ArcanistRuntime.php
+++ b/src/runtime/ArcanistRuntime.php
@@ -1,670 +1,676 @@
<?php
final class ArcanistRuntime {
private $workflows;
private $logEngine;
private $lastInterruptTime;
private $stack = array();
public function execute(array $argv) {
try {
$this->checkEnvironment();
} catch (Exception $ex) {
echo "CONFIGURATION ERROR\n\n";
echo $ex->getMessage();
echo "\n\n";
return 1;
}
PhutilTranslator::getInstance()
->setLocale(PhutilLocale::loadLocale('en_US'))
->setTranslations(PhutilTranslation::getTranslationMapForLocale('en_US'));
$log = new ArcanistLogEngine();
$this->logEngine = $log;
try {
return $this->executeCore($argv);
} catch (ArcanistConduitException $ex) {
$log->writeError(pht('CONDUIT'), $ex->getMessage());
} catch (PhutilArgumentUsageException $ex) {
$log->writeError(pht('USAGE EXCEPTION'), $ex->getMessage());
} catch (ArcanistUserAbortException $ex) {
$log->writeError(pht('---'), $ex->getMessage());
}
return 1;
}
private function executeCore(array $argv) {
$log = $this->getLogEngine();
$config_args = array(
array(
'name' => 'library',
'param' => 'path',
'help' => pht('Load a library.'),
'repeat' => true,
),
array(
'name' => 'config',
'param' => 'key=value',
'repeat' => true,
'help' => pht('Specify a runtime configuration value.'),
),
array(
'name' => 'config-file',
'param' => 'path',
'repeat' => true,
'help' => pht(
'Load one or more configuration files. If this flag is provided, '.
'the system and user configuration files are ignored.'),
),
);
$args = id(new PhutilArgumentParser($argv))
->parseStandardArguments();
// If we can test whether STDIN is a TTY, and it isn't, require that "--"
// appear in the argument list. This is intended to make it very hard to
// write unsafe scripts on top of Arcanist.
if (phutil_is_noninteractive()) {
$args->setRequireArgumentTerminator(true);
}
$is_trace = $args->getArg('trace');
$log->setShowTraceMessages($is_trace);
$log->writeTrace(pht('ARGV'), csprintf('%Ls', $argv));
// We're installing the signal handler after parsing "--trace" so that it
// can emit debugging messages. This means there's a very small window at
// startup where signals have no special handling, but we couldn't really
// route them or do anything interesting with them anyway.
$this->installSignalHandler();
$args->parsePartial($config_args, true);
$config_engine = $this->loadConfiguration($args);
$config = $config_engine->newConfigurationSourceList();
$this->loadLibraries($config_engine, $config, $args);
// Now that we've loaded libraries, we can validate configuration.
// Do this before continuing since configuration can impact other
// behaviors immediately and we want to catch any issues right away.
$config->setConfigOptions($config_engine->newConfigOptionsMap());
$config->validateConfiguration($this);
$toolset = $this->newToolset($argv);
$args->parsePartial($toolset->getToolsetArguments());
$workflows = $this->newWorkflows($toolset);
$this->workflows = $workflows;
$phutil_workflows = array();
foreach ($workflows as $key => $workflow) {
$phutil_workflows[$key] = $workflow->newPhutilWorkflow();
$workflow
->setRuntime($this)
->setConfigurationEngine($config_engine)
->setConfigurationSourceList($config);
}
$unconsumed_argv = $args->getUnconsumedArgumentVector();
if (!$unconsumed_argv) {
// TOOLSETS: This means the user just ran "arc" or some other top-level
// toolset without any workflow argument. We should give them a summary
// of the toolset, a list of workflows, and a pointer to "arc help" for
// more details.
// A possible exception is "arc --help", which should perhaps pass
// through and act like "arc help".
throw new PhutilArgumentUsageException(pht('Choose a workflow!'));
}
$alias_effects = id(new ArcanistAliasEngine())
->setRuntime($this)
->setToolset($toolset)
->setWorkflows($workflows)
->setConfigurationSourceList($config)
->resolveAliases($unconsumed_argv);
$result_argv = $this->applyAliasEffects($alias_effects, $unconsumed_argv);
$args->setUnconsumedArgumentVector($result_argv);
// TOOLSETS: Some day, stop falling through to the old "arc" runtime.
try {
return $args->parseWorkflowsFull($phutil_workflows);
} catch (ArcanistMissingArgumentTerminatorException $terminator_exception) {
$log->writeHint(
pht('USAGE'),
pht(
'"%s" is being run noninteractively, but the argument list is '.
'missing "--" to indicate end of flags.',
$toolset->getToolsetKey()));
$log->writeHint(
pht('USAGE'),
pht(
'When running noninteractively, you MUST provide "--" to all '.
'commands (even if they take no arguments).'));
$log->writeHint(
pht('USAGE'),
tsprintf(
'%s <__%s__>',
pht('Learn More:'),
'https://phurl.io/u/noninteractive'));
throw new PhutilArgumentUsageException(
pht('Missing required "--" in argument list.'));
} catch (PhutilArgumentUsageException $usage_exception) {
// TODO: This is very, very hacky; we're trying to let errors like
// "you passed the wrong arguments" through but fall back to classic
// mode if the workflow itself doesn't exist.
if (!preg_match('/invalid command/i', $usage_exception->getMessage())) {
throw $usage_exception;
}
}
$arcanist_root = phutil_get_library_root('arcanist');
$arcanist_root = dirname($arcanist_root);
$bin = $arcanist_root.'/scripts/arcanist.php';
$err = phutil_passthru(
'php -f %R -- %Ls',
$bin,
array_slice($argv, 1));
return $err;
}
/**
* Perform some sanity checks against the possible diversity of PHP builds in
* the wild, like very old versions and builds that were compiled with flags
* that exclude core functionality.
*/
private function checkEnvironment() {
// NOTE: We don't have phutil_is_windows() yet here.
$is_windows = (DIRECTORY_SEPARATOR != '/');
// NOTE: There's a hard PHP version check earlier, in "init-script.php".
if ($is_windows) {
$need_functions = array(
'curl_init' => array('builtin-dll', 'php_curl.dll'),
);
} else {
$need_functions = array(
'curl_init' => array(
'text',
"You need to install the cURL PHP extension, maybe with ".
"'apt-get install php5-curl' or 'yum install php53-curl' or ".
"something similar.",
),
'json_decode' => array('flag', '--without-json'),
);
}
$problems = array();
$config = null;
$show_config = false;
foreach ($need_functions as $fname => $resolution) {
if (function_exists($fname)) {
continue;
}
static $info;
if ($info === null) {
ob_start();
phpinfo(INFO_GENERAL);
$info = ob_get_clean();
$matches = null;
if (preg_match('/^Configure Command =>\s*(.*?)$/m', $info, $matches)) {
$config = $matches[1];
}
}
list($what, $which) = $resolution;
if ($what == 'flag' && strpos($config, $which) !== false) {
$show_config = true;
$problems[] = sprintf(
'The build of PHP you are running was compiled with the configure '.
'flag "%s", which means it does not support the function "%s()". '.
'This function is required for Arcanist to run. Install a standard '.
'build of PHP or rebuild it without this flag. You may also be '.
'able to build or install the relevant extension separately.',
$which,
$fname);
continue;
}
if ($what == 'builtin-dll') {
$problems[] = sprintf(
'The build of PHP you are running does not have the "%s" extension '.
'enabled. Edit your php.ini file and uncomment the line which '.
'reads "extension=%s".',
$which,
$which);
continue;
}
if ($what == 'text') {
$problems[] = $which;
continue;
}
$problems[] = sprintf(
'The build of PHP you are running is missing the required function '.
'"%s()". Rebuild PHP or install the extension which provides "%s()".',
$fname,
$fname);
}
if ($problems) {
if ($show_config) {
$problems[] = "PHP was built with this configure command:\n\n{$config}";
}
$problems = implode("\n\n", $problems);
throw new Exception($problems);
}
}
private function loadConfiguration(PhutilArgumentParser $args) {
$engine = id(new ArcanistConfigurationEngine())
->setArguments($args);
$working_copy = ArcanistWorkingCopy::newFromWorkingDirectory(getcwd());
if ($working_copy) {
$engine->setWorkingCopy($working_copy);
}
return $engine;
}
private function loadLibraries(
ArcanistConfigurationEngine $engine,
ArcanistConfigurationSourceList $config,
PhutilArgumentParser $args) {
$sources = array();
$cli_libraries = $args->getArg('library');
if ($cli_libraries) {
$sources = array();
foreach ($cli_libraries as $cli_library) {
$sources[] = array(
'type' => 'flag',
'library-source' => $cli_library,
);
}
} else {
$items = $config->getStorageValueList('load');
foreach ($items as $item) {
foreach ($item->getValue() as $library_path) {
$sources[] = array(
'type' => 'config',
'config-source' => $item->getConfigurationSource(),
'library-source' => $library_path,
);
}
}
}
foreach ($sources as $spec) {
$library_source = $spec['library-source'];
switch ($spec['type']) {
case 'flag':
$description = pht('runtime --library flag');
break;
case 'config':
$config_source = $spec['config-source'];
$description = pht(
'Configuration (%s)',
$config_source->getSourceDisplayName());
break;
}
$this->loadLibrary($engine, $library_source, $description);
}
}
private function loadLibrary(
ArcanistConfigurationEngine $engine,
$location,
$description) {
// TODO: This is a legacy system that should be replaced with package
// management.
$log = $this->getLogEngine();
$working_copy = $engine->getWorkingCopy();
if ($working_copy) {
$working_copy_root = $working_copy->getPath();
$working_directory = $working_copy->getWorkingDirectory();
} else {
$working_copy_root = null;
$working_directory = getcwd();
}
// Try to resolve the library location. We look in several places, in
// order:
//
// 1. Inside the working copy. This is for phutil libraries within the
// project. For instance "library/src" will resolve to
// "./library/src" if it exists.
// 2. In the same directory as the working copy. This allows you to
// check out a library alongside a working copy and reference it.
// If we haven't resolved yet, "library/src" will try to resolve to
// "../library/src" if it exists.
// 3. Using normal libphutil resolution rules. Generally, this means
// that it checks for libraries next to libphutil, then libraries
// in the PHP include_path.
//
// Note that absolute paths will just resolve absolutely through rule (1).
$resolved = false;
// Check inside the working copy. This also checks absolute paths, since
// they'll resolve absolute and just ignore the project root.
if ($working_copy_root !== null) {
$resolved_location = Filesystem::resolvePath(
$location,
$working_copy_root);
if (Filesystem::pathExists($resolved_location)) {
$location = $resolved_location;
$resolved = true;
}
// If we didn't find anything, check alongside the working copy.
if (!$resolved) {
$resolved_location = Filesystem::resolvePath(
$location,
dirname($working_copy_root));
if (Filesystem::pathExists($resolved_location)) {
$location = $resolved_location;
$resolved = true;
}
}
}
// Look beside "arcanist/". This is rule (3) above.
if (!$resolved) {
$arcanist_root = phutil_get_library_root('arcanist');
- $arcanist_root = dirname($arcanist_root);
+ $arcanist_root = dirname(dirname($arcanist_root));
$resolved_location = Filesystem::resolvePath(
$location,
$arcanist_root);
if (Filesystem::pathExists($resolved_location)) {
$location = $resolved_location;
$resolved = true;
}
}
$log->writeTrace(
pht('LOAD'),
pht('Loading library from "%s"...', $location));
$error = null;
try {
phutil_load_library($location);
- } catch (PhutilBootloaderException $ex) {
- fwrite(
- STDERR,
- '%s',
- tsprintf(
- "**<bg:red> %s </bg>** %s\n",
- pht(
- 'Failed to load phutil library at location "%s". This library '.
- 'is specified by "%s". Check that the setting is correct and '.
- 'the library is located in the right place.',
- $location,
- $description)));
-
- $prompt = pht('Continue without loading library?');
- if (!phutil_console_confirm($prompt)) {
- throw $ex;
- }
} catch (PhutilLibraryConflictException $ex) {
if ($ex->getLibrary() != 'arcanist') {
throw $ex;
}
// NOTE: If you are running `arc` against itself, we ignore the library
// conflict created by loading the local `arc` library (in the current
// working directory) and continue without loading it.
// This means we only execute code in the `arcanist/` directory which is
// associated with the binary you are running, whereas we would normally
// execute local code.
// This can make `arc` development slightly confusing if your setup is
// especially bizarre, but it allows `arc` to be used in automation
// workflows more easily. For some context, see PHI13.
$executing_directory = dirname(dirname(__FILE__));
- $working_directory = dirname($location);
- fwrite(
- STDERR,
- tsprintf(
- "**<bg:yellow> %s </bg>** %s\n",
- pht('VERY META'),
- pht(
- 'You are running one copy of Arcanist (at path "%s") against '.
- 'another copy of Arcanist (at path "%s"). Code in the current '.
- 'working directory will not be loaded or executed.',
- $executing_directory,
- $working_directory)));
+ $log->writeWarn(
+ pht('VERY META'),
+ pht(
+ 'You are running one copy of Arcanist (at path "%s") against '.
+ 'another copy of Arcanist (at path "%s"). Code in the current '.
+ 'working directory will not be loaded or executed.',
+ $executing_directory,
+ $working_directory));
+ } catch (PhutilBootloaderException $ex) {
+ $log->writeError(
+ pht('LIBRARY ERROR'),
+ pht(
+ 'Failed to load library at location "%s". This library '.
+ 'is specified by "%s". Check that the library is up to date.',
+ $location,
+ $description));
+
+ $prompt = pht('Continue without loading library?');
+ if (!phutil_console_confirm($prompt)) {
+ throw $ex;
+ }
+ } catch (Exception $ex) {
+ $log->writeError(
+ pht('LOAD ERROR'),
+ pht(
+ 'Failed to load library at location "%s". This library is '.
+ 'specified by "%s". Check that the setting is correct and the '.
+ 'library is located in the right place.',
+ $location,
+ $description));
+
+ $prompt = pht('Continue without loading library?');
+ if (!phutil_console_confirm($prompt)) {
+ throw $ex;
+ }
}
}
private function newToolset(array $argv) {
$binary = basename($argv[0]);
$toolsets = ArcanistToolset::newToolsetMap();
if (!isset($toolsets[$binary])) {
throw new PhutilArgumentUsageException(
pht(
'Arcanist toolset "%s" is unknown. The Arcanist binary should '.
'be executed so that "argv[0]" identifies a supported toolset. '.
'Rename the binary or install the library that provides the '.
'desired toolset. Current available toolsets: %s.',
$binary,
implode(', ', array_keys($toolsets))));
}
return $toolsets[$binary];
}
private function newWorkflows(ArcanistToolset $toolset) {
$workflows = id(new PhutilClassMapQuery())
->setAncestorClass('ArcanistWorkflow')
->setContinueOnFailure(true)
->execute();
foreach ($workflows as $key => $workflow) {
if (!$workflow->supportsToolset($toolset)) {
unset($workflows[$key]);
}
}
$map = array();
foreach ($workflows as $workflow) {
$key = $workflow->getWorkflowName();
if (isset($map[$key])) {
throw new Exception(
pht(
'Two workflows ("%s" and "%s") both have the same name ("%s") '.
'and both support the current toolset ("%s", "%s"). Each '.
'workflow in a given toolset must have a unique name.',
get_class($workflow),
get_class($map[$key]),
get_class($toolset),
$toolset->getToolsetKey()));
}
$map[$key] = id(clone $workflow)
->setToolset($toolset);
}
return $map;
}
public function getWorkflows() {
return $this->workflows;
}
public function getLogEngine() {
return $this->logEngine;
}
private function applyAliasEffects(array $effects, array $argv) {
assert_instances_of($effects, 'ArcanistAliasEffect');
$log = $this->getLogEngine();
$command = null;
$arguments = null;
foreach ($effects as $effect) {
$message = $effect->getMessage();
if ($message !== null) {
$log->writeHint(pht('ALIAS'), $message);
}
if ($effect->getCommand()) {
$command = $effect->getCommand();
$arguments = $effect->getArguments();
}
}
if ($command !== null) {
$argv = array_merge(array($command), $arguments);
}
return $argv;
}
private function installSignalHandler() {
$log = $this->getLogEngine();
if (!function_exists('pcntl_signal')) {
$log->writeTrace(
pht('PCNTL'),
pht(
'Unable to install signal handler, pcntl_signal() unavailable. '.
'Continuing without signal handling.'));
return;
}
// NOTE: SIGHUP, SIGTERM and SIGWINCH are handled by "PhutilSignalRouter".
// This logic is largely similar to the logic there, but more specific to
// Arcanist workflows.
pcntl_signal(SIGINT, array($this, 'routeSignal'));
}
public function routeSignal($signo) {
switch ($signo) {
case SIGINT:
$this->routeInterruptSignal($signo);
break;
}
}
private function routeInterruptSignal($signo) {
$log = $this->getLogEngine();
$last_interrupt = $this->lastInterruptTime;
$now = microtime(true);
$this->lastInterruptTime = $now;
$should_exit = false;
// If we received another SIGINT recently, always exit. This implements
// "press ^C twice in quick succession to exit" regardless of what the
// workflow may decide to do.
$interval = 2;
if ($last_interrupt !== null) {
if ($now - $last_interrupt < $interval) {
$should_exit = true;
}
}
$handler = null;
if (!$should_exit) {
// Look for an interrupt handler in the current workflow stack.
$stack = $this->getWorkflowStack();
foreach ($stack as $workflow) {
if ($workflow->canHandleSignal($signo)) {
$handler = $workflow;
break;
}
}
// If no workflow in the current execution stack can handle an interrupt
// signal, just exit on the first interrupt.
if (!$handler) {
$should_exit = true;
}
}
// It's common for users to ^C on prompts. Write a newline before writing
// a response to the interrupt so the behavior is a little cleaner. This
// also avoids lines that read "^C [ INTERRUPT ] ...".
$log->writeNewline();
if ($should_exit) {
$log->writeHint(
pht('INTERRUPT'),
pht('Interrupted by SIGINT (^C).'));
exit(128 + $signo);
}
$log->writeHint(
pht('INTERRUPT'),
pht('Press ^C again to exit.'));
$handler->handleSignal($signo);
}
public function pushWorkflow(ArcanistWorkflow $workflow) {
$this->stack[] = $workflow;
return $this;
}
public function popWorkflow() {
if (!$this->stack) {
throw new Exception(pht('Trying to pop an empty workflow stack!'));
}
return array_pop($this->stack);
}
public function getWorkflowStack() {
return $this->stack;
}
}

File Metadata

Mime Type
text/x-diff
Expires
Jan 19 2025, 16:01 (7 w, 1 d ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1126276
Default Alt Text
(35 KB)

Event Timeline