Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F2891970
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Award Token
Flag For Later
Advanced/Developer...
View Handle
View Hovercard
Size
35 KB
Referenced Files
None
Subscribers
None
View Options
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
Details
Attached
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)
Attached To
Mode
rARC Arcanist
Attached
Detach File
Event Timeline
Log In to Comment