public static function getInstance() { if (!self::$instance) { self::$instance = new PhutilBootloader(); } return self::$instance; }
private function loadVersions(PhabricatorUser $viewer) { $specs = array('phabricator', 'arcanist', 'phutil'); $all_libraries = PhutilBootloader::getInstance()->getAllLibraries(); // This puts the core libraries at the top: $other_libraries = array_diff($all_libraries, $specs); $specs = array_merge($specs, $other_libraries); $futures = array(); foreach ($specs as $lib) { $root = dirname(phutil_get_library_root($lib)); $futures[$lib] = id(new ExecFuture('git log --format=%s -n 1 --', '%H %ct'))->setCWD($root); } $results = array(); foreach ($futures as $key => $future) { list($err, $stdout) = $future->resolve(); if (!$err) { list($hash, $epoch) = explode(' ', $stdout); $version = pht('%s (%s)', $hash, phabricator_date($epoch, $viewer)); } else { $version = pht('Unknown'); } $results[$key] = $version; } return $results; }
public function generateData() { $lib_data = array(); foreach (PhutilBootloader::getInstance()->getAllLibraries() as $lib) { $lib_data[$lib] = phutil_get_library_root($lib); } return array('config' => PhabricatorEnv::getAllConfigKeys(), 'libraries' => $lib_data); }
public function render() { $user = $this->getUser(); $trace = $this->trace; $libraries = PhutilBootloader::getInstance()->getAllLibraries(); // TODO: Make this configurable? $path = 'https://secure.phabricator.com/diffusion/%s/browse/master/src/'; $callsigns = array('arcanist' => 'ARC', 'phutil' => 'PHU', 'phabricator' => 'P'); $rows = array(); $depth = count($trace); foreach ($trace as $part) { $lib = null; $file = idx($part, 'file'); $relative = $file; foreach ($libraries as $library) { $root = phutil_get_library_root($library); if (Filesystem::isDescendant($file, $root)) { $lib = $library; $relative = Filesystem::readablePath($file, $root); break; } } $where = ''; if (isset($part['class'])) { $where .= $part['class'] . '::'; } if (isset($part['function'])) { $where .= $part['function'] . '()'; } if ($file) { if (isset($callsigns[$lib])) { $attrs = array('title' => $file); try { $attrs['href'] = $user->loadEditorLink('/src/' . $relative, $part['line'], $callsigns[$lib]); } catch (Exception $ex) { // The database can be inaccessible. } if (empty($attrs['href'])) { $attrs['href'] = sprintf($path, $callsigns[$lib]) . str_replace(DIRECTORY_SEPARATOR, '/', $relative) . '$' . $part['line']; $attrs['target'] = '_blank'; } $file_name = phutil_tag('a', $attrs, $relative); } else { $file_name = phutil_tag('span', array('title' => $file), $relative); } $file_name = hsprintf('%s : %d', $file_name, $part['line']); } else { $file_name = phutil_tag('em', array(), '(Internal)'); } $rows[] = array($depth--, $lib, $file_name, $where); } $table = new AphrontTableView($rows); $table->setHeaders(array(pht('Depth'), pht('Library'), pht('File'), pht('Where'))); $table->setColumnClasses(array('n', '', '', 'wide')); return phutil_tag('div', array('class' => 'exception-trace'), $table->render()); }
/** * This is more of an acceptance test case instead of a unit test. It verifies * that all the library map is up-to-date. */ public function testLibraryMap() { $library = phutil_get_current_library_name(); $root = phutil_get_library_root($library); $new_library_map = id(new PhutilLibraryMapBuilder($root))->buildMap(); $bootloader = PhutilBootloader::getInstance(); $old_library_map = $bootloader->getLibraryMapWithoutExtensions($library); unset($old_library_map[PhutilLibraryMapBuilder::LIBRARY_MAP_VERSION_KEY]); $this->assertEqual($new_library_map, $old_library_map, 'The library map does not appear to be up-to-date. Try ' . 'rebuilding the map with `arc liberate`.'); }
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; }
/** * This is more of an acceptance test case instead of a unit test. It verifies * that all the library map is up-to-date. */ public function testLibraryMap() { $root = $this->getLibraryRoot(); $library = phutil_get_library_name_for_root($root); $new_library_map = id(new PhutilLibraryMapBuilder($root))->buildMap(); $bootloader = PhutilBootloader::getInstance(); $old_library_map = $bootloader->getLibraryMapWithoutExtensions($library); unset($old_library_map[PhutilLibraryMapBuilder::LIBRARY_MAP_VERSION_KEY]); $identical = $new_library_map === $old_library_map; if (!$identical) { $differences = $this->getMapDifferences($old_library_map, $new_library_map); sort($differences); } else { $differences = array(); } $this->assertTrue($identical, pht("The library map is out of date. Rebuild it with `%s`.\n" . "These entries differ: %s.", 'arc liberate', implode(', ', $differences))); }
private function renderStackTrace($trace) { $libraries = PhutilBootloader::getInstance()->getAllLibraries(); // TODO: Make this configurable? $host = 'https://secure.phabricator.com'; $browse = array('arcanist' => $host . '/diffusion/ARC/browse/origin:master/src/', 'phutil' => $host . '/diffusion/PHU/browse/origin:master/src/', 'phabricator' => $host . '/diffusion/P/browse/origin:master/src/'); $rows = array(); $depth = count($trace); foreach ($trace as $part) { $lib = null; $file = idx($part, 'file'); $relative = $file; foreach ($libraries as $library) { $root = phutil_get_library_root($library); if (Filesystem::isDescendant($file, $root)) { $lib = $library; $relative = Filesystem::readablePath($file, $root); break; } } $where = ''; if (isset($part['class'])) { $where .= $part['class'] . '::'; } if (isset($part['function'])) { $where .= $part['function'] . '()'; } if ($file) { if (isset($browse[$lib])) { $file_name = phutil_render_tag('a', array('href' => $browse[$lib] . $relative . '$' . $part['line'], 'title' => $file, 'target' => '_blank'), phutil_escape_html($relative)); } else { $file_name = phutil_render_tag('span', array('title' => $file), phutil_escape_html($relative)); } $file_name = $file_name . ' : ' . (int) $part['line']; } else { $file_name = '<em>(Internal)</em>'; } $rows[] = array($depth--, phutil_escape_html($lib), $file_name, phutil_escape_html($where)); } $table = new AphrontTableView($rows); $table->setHeaders(array('Depth', 'Library', 'File', 'Where')); $table->setColumnClasses(array('n', '', '', 'wide')); return '<div class="exception-trace">' . '<div class="exception-trace-header">Stack Trace</div>' . $table->render() . '</div>'; }
public function run() { $bootloader = PhutilBootloader::getInstance(); $affected_modules = array(); foreach ($this->getPaths() as $path) { $library_root = phutil_get_library_root_for_path($path); if (!$library_root) { continue; } $library_name = phutil_get_library_name_for_root($library_root); if (!$library_name) { throw new Exception("Attempting to run unit tests on a libphutil library which has not " . "been loaded, at:\n\n" . " {$library_root}\n\n" . "This probably means one of two things:\n\n" . " - You may need to add this library to .arcconfig.\n" . " - You may be running tests on a copy of libphutil or arcanist\n" . " using a different copy of libphutil or arcanist. This\n" . " operation is not supported."); } $path = Filesystem::resolvePath($path); if (!is_dir($path)) { $path = dirname($path); } if ($path == $library_root) { continue; } $library_path = Filesystem::readablePath($path, $library_root); do { // Add the module and all parent modules as affected modules, which // means we'll look for __tests__ to run here and in any containing // module. $affected_modules[$library_name . ':' . $library_path] = array('name' => $library_name, 'root' => $library_root, 'path' => $library_path); $library_path = dirname($library_path); } while ($library_path != '.'); } $tests = array(); foreach ($affected_modules as $library_info) { $library_name = $library_info['name']; $library_root = $library_info['root']; $module = $library_info['path']; if (basename($module) == '__tests__') { // Okay, this is a __tests__ module. } else { $exists = $bootloader->moduleExists($library_name, $module . '/__tests__'); if ($exists) { // This is a module which has a __tests__ module in it. $module .= '/__tests__'; } else { // Look for a parent named __tests__. $rpos = strrpos($module, '/__tests__'); if ($rpos === false) { // No tests to run since there is no child or parent module named // __tests__. continue; } // Select the parent named __tests__. $module = substr($module, 0, $rpos + strlen('/__tests__')); } } $module_key = $library_name . ':' . $module; $tests[$module_key] = array('library' => $library_name, 'root' => $library_root, 'module' => $module); } if (!$tests) { throw new ArcanistNoEffectException("No tests to run."); } $run_tests = array(); foreach ($tests as $test) { $symbols = id(new PhutilSymbolLoader())->setType('class')->setLibrary($test['library'])->setModule($test['module'])->setAncestorClass('ArcanistPhutilTestCase')->selectAndLoadSymbols(); foreach ($symbols as $symbol) { $run_tests[$symbol['name']] = true; } } $run_tests = array_keys($run_tests); if (!$run_tests) { throw new ArcanistNoEffectException("No tests to run. You may need to rebuild the phutil library map."); } $enable_coverage = $this->getEnableCoverage(); if ($enable_coverage !== false) { if (!function_exists('xdebug_start_code_coverage')) { if ($enable_coverage === true) { throw new ArcanistUsageException("You specified --coverage but xdebug is not available, so " . "coverage can not be enabled for PhutilUnitTestEngine."); } } else { $enable_coverage = true; } } $results = array(); foreach ($run_tests as $test_class) { PhutilSymbolLoader::loadClass($test_class); $test_case = newv($test_class, array()); $test_case->setEnableCoverage($enable_coverage); $test_case->setProjectRoot($this->getWorkingCopy()->getProjectRoot()); $test_case->setPaths($this->getPaths()); $results[] = $test_case->run(); } if ($results) { $results = call_user_func_array('array_merge', $results); } return $results; }
/** * @task internal */ private function loadSymbol(array $symbol_spec) { // Check if we've already loaded the symbol; bail if we have. $name = $symbol_spec['name']; $is_function = $symbol_spec['type'] == 'function'; if ($is_function) { if (function_exists($name)) { return; } } else { if (class_exists($name, false) || interface_exists($name, false)) { return; } } $lib_name = $symbol_spec['library']; $where = $symbol_spec['where']; $bootloader = PhutilBootloader::getInstance(); $bootloader->loadLibrarySource($lib_name, $where); // Check that we successfully loaded the symbol from wherever it was // supposed to be defined. $load_failed = null; if ($is_function) { if (!function_exists($name)) { $load_failed = 'function'; } } else { if (!class_exists($name, false) && !interface_exists($name, false)) { $load_failed = 'class or interface'; } } if ($load_failed !== null) { $lib_path = phutil_get_library_root($lib_name); throw new PhutilMissingSymbolException($name, $load_failed, "the symbol map for library '{$lib_name}' (at '{$lib_path}') claims " . "this {$load_failed} is defined in '{$where}', but loading that " . "source file did not cause the {$load_failed} to become defined."); } }
public function launchDaemon($daemon, array $argv, $debug = false) { $symbols = $this->loadAvailableDaemonClasses(); $symbols = ipull($symbols, 'name', 'name'); if (empty($symbols[$daemon])) { throw new Exception("Daemon '{$daemon}' is not known."); } $pid_dir = $this->getControlDirectory('pid'); $log_dir = $this->getControlDirectory('log') . '/daemons.log'; $libphutil_root = dirname(phutil_get_library_root('phutil')); $launch_daemon = $libphutil_root . '/scripts/daemon/'; // TODO: This should be a much better user experience. Filesystem::assertExists($pid_dir); Filesystem::assertIsDirectory($pid_dir); Filesystem::assertWritable($pid_dir); foreach ($argv as $key => $arg) { $argv[$key] = escapeshellarg($arg); } $bootloader = PhutilBootloader::getInstance(); $all_libraries = $bootloader->getAllLibraries(); $non_default_libraries = array_diff($all_libraries, array('phutil', 'phabricator')); $extra_libraries = array(); foreach ($non_default_libraries as $library) { $extra_libraries[] = csprintf('--load-phutil-library=%s', phutil_get_library_root($library)); } $command = csprintf("./launch_daemon.php " . "%s " . "--load-phutil-library=%s " . "%C " . "--conduit-uri=%s " . "--phd=%s " . ($debug ? '--trace ' : '--daemonize '), $daemon, phutil_get_library_root('phabricator'), implode(' ', $extra_libraries), PhabricatorEnv::getURI('/api/'), $pid_dir); if (!$debug) { // If we're running "phd debug", send output straight to the console // instead of to a logfile. $command = csprintf("%C --log=%s", $command, $log_dir); } // Append the daemon's argv. $command = csprintf("%C %C", $command, implode(' ', $argv)); if ($debug) { // Don't terminate when the user sends ^C; it will be sent to the // subprocess which will terminate normally. pcntl_signal(SIGINT, array('PhabricatorDaemonControl', 'ignoreSignal')); echo "\n libphutil/scripts/daemon/ \$ {$command}\n\n"; phutil_passthru('(cd %s && exec %C)', $launch_daemon, $command); } else { $future = new ExecFuture('exec %C', $command); // Play games to keep 'ps' looking reasonable. $future->setCWD($launch_daemon); $future->resolvex(); } }
public function run() { $bootloader = PhutilBootloader::getInstance(); $project_root = $this->getWorkingCopy()->getProjectRoot(); $look_here = array(); foreach ($this->getPaths() as $path) { $library_root = phutil_get_library_root_for_path($path); if (!$library_root) { continue; } $library_name = phutil_get_library_name_for_root($library_root); if (!$library_name) { throw new Exception("Attempting to run unit tests on a libphutil library which has not " . "been loaded, at:\n\n" . " {$library_root}\n\n" . "This probably means one of two things:\n\n" . " - You may need to add this library to .arcconfig.\n" . " - You may be running tests on a copy of libphutil or arcanist\n" . " using a different copy of libphutil or arcanist. This\n" . " operation is not supported."); } $path = Filesystem::resolvePath($path, $project_root); if (!is_dir($path)) { $path = dirname($path); } if ($path == $library_root) { continue; } if (!Filesystem::isDescendant($path, $library_root)) { // We have encountered some kind of symlink maze -- for instance, $path // is some symlink living outside the library that links into some file // inside the library. Just ignore these cases, since the affected file // does not actually lie within the library. continue; } $library_path = Filesystem::readablePath($path, $library_root); do { $look_here[$library_name . ':' . $library_path] = array('library' => $library_name, 'path' => $library_path); $library_path = dirname($library_path); } while ($library_path != '.'); } // Look for any class that extends ArcanistPhutilTestCase inside a // __tests__ directory in any parent directory of every affected file. // // The idea is that "infrastructure/__tests__/" tests defines general tests // for all of "infrastructure/", and those tests run for any change in // "infrastructure/". However, "infrastructure/concrete/rebar/__tests__/" // defines more specific tests that run only when rebar/ (or some // subdirectory) changes. $run_tests = array(); foreach ($look_here as $path_info) { $library = $path_info['library']; $path = $path_info['path']; $symbols = id(new PhutilSymbolLoader())->setType('class')->setLibrary($library)->setPathPrefix($path . '/__tests__/')->setAncestorClass('ArcanistPhutilTestCase')->setConcreteOnly(true)->selectAndLoadSymbols(); foreach ($symbols as $symbol) { $run_tests[$symbol['name']] = true; } } $run_tests = array_keys($run_tests); if (!$run_tests) { throw new ArcanistNoEffectException("No tests to run."); } $enable_coverage = $this->getEnableCoverage(); if ($enable_coverage !== false) { if (!function_exists('xdebug_start_code_coverage')) { if ($enable_coverage === true) { throw new ArcanistUsageException("You specified --coverage but xdebug is not available, so " . "coverage can not be enabled for PhutilUnitTestEngine."); } } else { $enable_coverage = true; } } $results = array(); foreach ($run_tests as $test_class) { $test_case = newv($test_class, array()); $test_case->setEnableCoverage($enable_coverage); $test_case->setProjectRoot($project_root); $test_case->setPaths($this->getPaths()); $results[] = $test_case->run(); } if ($results) { $results = call_user_func_array('array_merge', $results); } return $results; }
public function willLintPaths(array $paths) { if (!xhpast_is_available()) { throw new Exception(xhpast_get_build_instructions()); } // NOTE: For now, we completely ignore paths and just lint every library in // its entirety. This is simpler and relatively fast because we don't do any // detailed checks and all the data we need for this comes out of module // caches. $bootloader = PhutilBootloader::getInstance(); $libs = $bootloader->getAllLibraries(); // Load the up-to-date map for each library, without loading the library // itself. This means lint results will accurately reflect the state of // the working copy. $symbols = array(); foreach ($libs as $lib) { $root = phutil_get_library_root($lib); try { $symbols[$lib] = id(new PhutilLibraryMapBuilder($root))->buildFileSymbolMap(); } catch (XHPASTSyntaxErrorException $ex) { // If the library contains a syntax error then there isn't much that we // can do. continue; } } $all_symbols = array(); foreach ($symbols as $library => $map) { // Check for files which declare more than one class/interface in the same // file, or mix function definitions with class/interface definitions. We // must isolate autoloadable symbols to one per file so the autoloader // can't end up in an unresolvable cycle. foreach ($map as $file => $spec) { $have = idx($spec, 'have', array()); $have_classes = idx($have, 'class', array()) + idx($have, 'interface', array()); $have_functions = idx($have, 'function'); if ($have_functions && $have_classes) { $function_list = implode(', ', array_keys($have_functions)); $class_list = implode(', ', array_keys($have_classes)); $this->raiseLintInLibrary($library, $file, end($have_functions), self::LINT_ONE_CLASS_PER_FILE, "File '{$file}' mixes function ({$function_list}) and " . "class/interface ({$class_list}) definitions in the same file. " . "A file which declares a class or an interface MUST " . "declare nothing else."); } else { if (count($have_classes) > 1) { $class_list = implode(', ', array_keys($have_classes)); $this->raiseLintInLibrary($library, $file, end($have_classes), self::LINT_ONE_CLASS_PER_FILE, "File '{$file}' declares more than one class or interface " . "({$class_list}). A file which declares a class or interface MUST " . "declare nothing else."); } } } // Check for duplicate symbols: two files providing the same class or // function. foreach ($map as $file => $spec) { $have = idx($spec, 'have', array()); foreach (array('class', 'function', 'interface') as $type) { $libtype = $type == 'interface' ? 'class' : $type; foreach (idx($have, $type, array()) as $symbol => $offset) { if (empty($all_symbols[$libtype][$symbol])) { $all_symbols[$libtype][$symbol] = array('library' => $library, 'file' => $file, 'offset' => $offset); continue; } $osrc = $all_symbols[$libtype][$symbol]['file']; $olib = $all_symbols[$libtype][$symbol]['library']; $this->raiseLintInLibrary($library, $file, $offset, self::LINT_DUPLICATE_SYMBOL, "Definition of {$type} '{$symbol}' in '{$file}' in library " . "'{$library}' duplicates prior definition in '{$osrc}' in " . "library '{$olib}'."); } } } } $types = array('class', 'function', 'interface', 'class/interface'); foreach ($symbols as $library => $map) { // Check for unknown symbols: uses of classes, functions or interfaces // which are not defined anywhere. We reference the list of all symbols // we built up earlier. foreach ($map as $file => $spec) { $need = idx($spec, 'need', array()); foreach ($types as $type) { $libtype = $type; if ($type == 'interface' || $type == 'class/interface') { $libtype = 'class'; } foreach (idx($need, $type, array()) as $symbol => $offset) { if (!empty($all_symbols[$libtype][$symbol])) { // Symbol is defined somewhere. continue; } $libphutil_root = dirname(phutil_get_library_root('phutil')); $this->raiseLintInLibrary($library, $file, $offset, self::LINT_UNKNOWN_SYMBOL, "Use of unknown {$type} '{$symbol}'. Common causes are:\n\n" . " - Your libphutil/ is out of date.\n" . " This is the most common cause.\n" . " Update this copy of libphutil: {$libphutil_root}\n" . "\n" . " - Some other library is out of date.\n" . " Update the library this symbol appears in.\n" . "\n" . " - This symbol is misspelled.\n" . " Spell the symbol name correctly.\n" . " Symbol name spelling is case-sensitive.\n" . "\n" . " - This symbol was added recently.\n" . " Run `arc liberate` on the library it was added to.\n" . "\n" . " - This symbol is external. Use `@phutil-external-symbol`.\n" . " Use `grep` to find usage examples of this directive.\n" . "\n" . "*** ALTHOUGH USUALLY EASY TO FIX, THIS IS A SERIOUS ERROR.\n" . "*** THIS ERROR IS YOUR FAULT. YOU MUST RESOLVE IT."); } } } } }
public static function adjustFilePath($path) { // Compute known library locations so we can emit relative paths if the // file resides inside a known library. This is a little cleaner to read, // and limits the number of false positives we get about full path // disclosure via HackerOne. $bootloader = PhutilBootloader::getInstance(); $libraries = $bootloader->getAllLibraries(); $roots = array(); foreach ($libraries as $library) { $root = $bootloader->getLibraryRoot($library); // For these libraries, the effective root is one level up. switch ($library) { case 'phutil': case 'arcanist': case 'phabricator': $root = dirname($root); break; } if (!strncmp($root, $path, strlen($root))) { return '<' . $library . '>' . substr($path, strlen($root)); } } return $path; }
/** * @task internal */ private function loadSymbol(array $symbol_spec) { // Check if we've already loaded the symbol; bail if we have. $name = $symbol_spec['name']; $is_function = $symbol_spec['type'] == 'function'; if ($is_function) { if (function_exists($name)) { return; } } else { if (class_exists($name, false) || interface_exists($name, false)) { return; } } $lib_name = $symbol_spec['library']; $bootloader = PhutilBootloader::getInstance(); $version = $bootloader->getLibraryFormatVersion($lib_name); switch ($version) { case 1: // TODO: Remove this once we drop libphutil v1 support. $bootloader->loadModule($symbol_spec['library'], $symbol_spec['module']); break; case 2: $bootloader->loadLibrarySource($symbol_spec['library'], $symbol_spec['where']); break; } // Check that we successfully loaded the symbol from wherever it was // supposed to be defined. if ($is_function) { if (!function_exists($name)) { throw new PhutilMissingSymbolException($name); } } else { if (!class_exists($name, false) && !interface_exists($name, false)) { throw new PhutilMissingSymbolException($name); } } }
function phutil_load_library($path) { PhutilBootloader::getInstance()->loadLibrary($path); }
public function launchDaemon($daemon, array $argv, $debug = false) { $symbols = $this->loadAvailableDaemonClasses(); $symbols = ipull($symbols, 'name', 'name'); if (empty($symbols[$daemon])) { throw new Exception("Daemon '{$daemon}' is not loaded, misspelled or abstract."); } $libphutil_root = dirname(phutil_get_library_root('phutil')); $launch_daemon = $libphutil_root . '/scripts/daemon/'; foreach ($argv as $key => $arg) { $argv[$key] = escapeshellarg($arg); } $flags = array(); if ($debug || PhabricatorEnv::getEnvConfig('phd.trace')) { $flags[] = '--trace'; } if ($debug || PhabricatorEnv::getEnvConfig('phd.verbose')) { $flags[] = '--verbose'; } if (!$debug) { $flags[] = '--daemonize'; } $bootloader = PhutilBootloader::getInstance(); foreach ($bootloader->getAllLibraries() as $library) { if ($library == 'phutil') { // No need to load libphutil, it's necessarily loaded implicitly by the // daemon itself. continue; } $flags[] = csprintf('--load-phutil-library=%s', phutil_get_library_root($library)); } $flags[] = csprintf('--conduit-uri=%s', PhabricatorEnv::getURI('/api/')); if (!$debug) { $log_file = $this->getLogDirectory() . '/daemons.log'; $flags[] = csprintf('--log=%s', $log_file); } $pid_dir = $this->getPIDDirectory(); // TODO: This should be a much better user experience. Filesystem::assertExists($pid_dir); Filesystem::assertIsDirectory($pid_dir); Filesystem::assertWritable($pid_dir); $flags[] = csprintf('--phd=%s', $pid_dir); $command = csprintf('./launch_daemon.php %s %C %C', $daemon, implode(' ', $flags), implode(' ', $argv)); if ($debug) { // Don't terminate when the user sends ^C; it will be sent to the // subprocess which will terminate normally. pcntl_signal(SIGINT, array('PhabricatorDaemonControl', 'ignoreSignal')); echo "\n libphutil/scripts/daemon/ \$ {$command}\n\n"; phutil_passthru('(cd %s && exec %C)', $launch_daemon, $command); } else { $future = new ExecFuture('exec %C', $command); // Play games to keep 'ps' looking reasonable. $future->setCWD($launch_daemon); $future->resolvex(); } }
private function renderStackTrace($trace, PhabricatorUser $user) { $libraries = PhutilBootloader::getInstance()->getAllLibraries(); $version = PhabricatorEnv::getEnvConfig('phabricator.version'); if (preg_match('/[^a-f0-9]/i', $version)) { $version = ''; } // TODO: Make this configurable? $path = 'https://secure.phabricator.com/diffusion/%s/browse/master/src/'; $callsigns = array('arcanist' => 'ARC', 'phutil' => 'PHU', 'phabricator' => 'P'); $rows = array(); $depth = count($trace); foreach ($trace as $part) { $lib = null; $file = idx($part, 'file'); $relative = $file; foreach ($libraries as $library) { $root = phutil_get_library_root($library); if (Filesystem::isDescendant($file, $root)) { $lib = $library; $relative = Filesystem::readablePath($file, $root); break; } } $where = ''; if (isset($part['class'])) { $where .= $part['class'] . '::'; } if (isset($part['function'])) { $where .= $part['function'] . '()'; } if ($file) { if (isset($callsigns[$lib])) { $attrs = array('title' => $file); try { $attrs['href'] = $user->loadEditorLink('/src/' . $relative, $part['line'], $callsigns[$lib]); } catch (Exception $ex) { // The database can be inaccessible. } if (empty($attrs['href'])) { $attrs['href'] = sprintf($path, $callsigns[$lib]) . str_replace(DIRECTORY_SEPARATOR, '/', $relative) . ($version && $lib == 'phabricator' ? ';' . $version : '') . '$' . $part['line']; $attrs['target'] = '_blank'; } $file_name = phutil_render_tag('a', $attrs, phutil_escape_html($relative)); } else { $file_name = phutil_render_tag('span', array('title' => $file), phutil_escape_html($relative)); } $file_name = $file_name . ' : ' . (int) $part['line']; } else { $file_name = '<em>(Internal)</em>'; } $rows[] = array($depth--, phutil_escape_html($lib), $file_name, phutil_escape_html($where)); } $table = new AphrontTableView($rows); $table->setHeaders(array('Depth', 'Library', 'File', 'Where')); $table->setColumnClasses(array('n', '', '', 'wide')); return '<div class="exception-trace">' . '<div class="exception-trace-header">Stack Trace</div>' . $table->render() . '</div>'; }
public function willLintPaths(array $paths) { if (!xhpast_is_available()) { throw new Exception(xhpast_get_build_instructions()); } // NOTE: For now, we completely ignore paths and just lint every library in // its entirety. This is simpler and relatively fast because we don't do any // detailed checks and all the data we need for this comes out of module // caches. $bootloader = PhutilBootloader::getInstance(); $libs = $bootloader->getAllLibraries(); // Load the up-to-date map for each library, without loading the library // itself. This means lint results will accurately reflect the state of // the working copy. $arc_root = dirname(phutil_get_library_root('arcanist')); $bin = "{$arc_root}/scripts/phutil_rebuild_map.php"; $symbols = array(); foreach ($libs as $lib) { // Do these one at a time since they individually fanout to saturate // available system resources. $future = new ExecFuture('%s --show --quiet --ugly -- %s', $bin, phutil_get_library_root($lib)); $symbols[$lib] = $future->resolveJSON(); } $all_symbols = array(); foreach ($symbols as $library => $map) { // Check for files which declare more than one class/interface in the same // file, or mix function definitions with class/interface definitions. We // must isolate autoloadable symbols to one per file so the autoloader // can't end up in an unresolvable cycle. foreach ($map as $file => $spec) { $have = idx($spec, 'have', array()); $have_classes = idx($have, 'class', array()) + idx($have, 'interface', array()); $have_functions = idx($have, 'function'); if ($have_functions && $have_classes) { $function_list = implode(', ', array_keys($have_functions)); $class_list = implode(', ', array_keys($have_classes)); $this->raiseLintInLibrary($library, $file, end($have_functions), self::LINT_ONE_CLASS_PER_FILE, "File '{$file}' mixes function ({$function_list}) and " . "class/interface ({$class_list}) definitions in the same file. " . "A file which declares a class or an interface MUST " . "declare nothing else."); } else { if (count($have_classes) > 1) { $class_list = implode(', ', array_keys($have_classes)); $this->raiseLintInLibrary($library, $file, end($have_classes), self::LINT_ONE_CLASS_PER_FILE, "File '{$file}' declares more than one class or interface " . "({$class_list}). A file which declares a class or interface MUST " . "declare nothing else."); } } } // Check for duplicate symbols: two files providing the same class or // function. foreach ($map as $file => $spec) { $have = idx($spec, 'have', array()); foreach (array('class', 'function', 'interface') as $type) { $libtype = $type == 'interface' ? 'class' : $type; foreach (idx($have, $type, array()) as $symbol => $offset) { if (empty($all_symbols[$libtype][$symbol])) { $all_symbols[$libtype][$symbol] = array('library' => $library, 'file' => $file, 'offset' => $offset); continue; } $osrc = $all_symbols[$libtype][$symbol]['file']; $olib = $all_symbols[$libtype][$symbol]['library']; $this->raiseLintInLibrary($library, $file, $offset, self::LINT_DUPLICATE_SYMBOL, "Definition of {$type} '{$symbol}' in '{$file}' in library " . "'{$library}' duplicates prior definition in '{$osrc}' in " . "library '{$olib}'."); } } } } foreach ($symbols as $library => $map) { // Check for unknown symbols: uses of classes, functions or interfaces // which are not defined anywhere. We reference the list of all symbols // we built up earlier. foreach ($map as $file => $spec) { $need = idx($spec, 'need', array()); foreach (array('class', 'function', 'interface') as $type) { $libtype = $type == 'interface' ? 'class' : $type; foreach (idx($need, $type, array()) as $symbol => $offset) { if (!empty($all_symbols[$libtype][$symbol])) { // Symbol is defined somewhere. continue; } $this->raiseLintInLibrary($library, $file, $offset, self::LINT_UNKNOWN_SYMBOL, "Use of unknown {$type} '{$symbol}'. This symbol is not defined " . "in any loaded phutil library."); } } } } }
#!/usr/bin/env php <?php // NOTE: This is substantially the same as the libphutil/ "launch_daemon.php" // script, except it loads the Phabricator environment and adds some Phabricator // specific flags. declare (ticks=1); $root = dirname(dirname(dirname(__FILE__))); require_once $root . '/scripts/__init_script__.php'; $overseer = new PhutilDaemonOverseer($argv); $bootloader = PhutilBootloader::getInstance(); foreach ($bootloader->getAllLibraries() as $library) { $overseer->addLibrary(phutil_get_library_root($library)); } $overseer->run();
/** * @task internal */ private function loadClassOrInterfaceSymbol(array $symbol_spec) { $name = $symbol_spec['name']; if (class_exists($name, false) || interface_exists($name, false)) { return; } $bootloader = PhutilBootloader::getInstance(); $bootloader->loadModule($symbol_spec['library'], $symbol_spec['module']); if (!class_exists($name, false) && !interface_exists($name, false)) { throw new PhutilMissingSymbolException($name); } }
public static function getLibraryVersions() { $libinfo = array(); $bootloader = PhutilBootloader::getInstance(); foreach ($bootloader->getAllLibraries() as $library) { $root = phutil_get_library_root($library); $try_paths = array($root, dirname($root)); $libinfo[$library] = array(); $get_refs = array('master'); foreach ($try_paths as $try_path) { // Try to read what the HEAD of the repository is pointed at. This is // normally the name of a branch ("ref"). $try_file = $try_path . '/.git/HEAD'; if (@file_exists($try_file)) { $head = @file_get_contents($try_file); $matches = null; if (preg_match('(^ref: refs/heads/(.*)$)', trim($head), $matches)) { $libinfo[$library]['head'] = trim($matches[1]); $get_refs[] = trim($matches[1]); } else { $libinfo[$library]['head'] = trim($head); } break; } } // Try to read which commit relevant branch heads are at. foreach (array_unique($get_refs) as $ref) { foreach ($try_paths as $try_path) { $try_file = $try_path . '/.git/refs/heads/' . $ref; if (@file_exists($try_file)) { $hash = @file_get_contents($try_file); if ($hash) { $libinfo[$library]['ref.' . $ref] = substr(trim($hash), 0, 12); break; } } } } // Look for extension files. $custom = @scandir($root . '/extensions/'); if ($custom) { $count = 0; foreach ($custom as $custom_path) { if (preg_match('/\\.php$/', $custom_path)) { $count++; } } if ($count) { $libinfo[$library]['custom'] = $count; } } } ksort($libinfo); return $libinfo; }