diff --git a/src/__phutil_library_init__.php b/src/__phutil_library_init__.php index fa02a12..7876c86 100644 --- a/src/__phutil_library_init__.php +++ b/src/__phutil_library_init__.php @@ -1,322 +1,329 @@ loadModule($library, $module); } /** * @group library */ function phutil_require_source($source) { PhutilBootloader::getInstance()->loadSource($source); } /** * @group library */ function phutil_register_library($library, $path) { PhutilBootloader::getInstance()->registerLibrary($library, $path); } /** * @group library */ function phutil_register_library_map(array $map) { PhutilBootloader::getInstance()->registerLibraryMap($map); } /** * @group library */ function phutil_load_library($path) { PhutilBootloader::getInstance()->loadLibrary($path); } /** * @group library */ function phutil_is_windows() { // We can also use PHP_OS, but that's kind of sketchy because it returns // "WINNT" for Windows 7 and "Darwin" for Mac OS X. Practically, testing for // DIRECTORY_SEPARATOR is more straightforward. return (DIRECTORY_SEPARATOR != '/'); } +/** + * @group library + */ +function phutil_is_hiphop_runtime() { + return (array_key_exists('HPHP', $_ENV) && $_ENV['HPHP'] === 1); +} + /** * @group library */ final class PhutilBootloader { private static $instance; private $registeredLibraries = array(); private $libraryMaps = array(); private $moduleStack = array(); private $currentLibrary = null; public static function getInstance() { if (!self::$instance) { self::$instance = new PhutilBootloader(); } return self::$instance; } private function __construct() { // This method intentionally left blank. } public function registerLibrary($name, $path) { if (basename($path) != '__phutil_library_init__.php') { throw new PhutilBootloaderException( 'Only directories with a __phutil_library_init__.php file may be '. 'registered as libphutil libraries.'); } $path = dirname($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; return $this; } public function registerLibraryMap(array $map) { $this->libraryMaps[$this->currentLibrary] = $map; return $this; } public function getLibraryMap($name) { if (empty($this->libraryMaps[$name])) { $root = $this->getLibraryRoot($name); $this->currentLibrary = $name; $okay = include $root.'/__phutil_library_map__.php'; if (!$okay) { throw new PhutilBootloaderException( "Include of '{$root}/__phutil_library_map__.php' failed!"); } } 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); } private function pushModuleStack($library, $module) { array_push($this->moduleStack, $this->getLibraryRoot($library).'/'.$module); return $this; } private function popModuleStack() { array_pop($this->moduleStack); } private function peekModuleStack() { return end($this->moduleStack); } public function loadLibrary($path) { $root = null; if (!empty($_SERVER['PHUTIL_LIBRARY_ROOT'])) { if ($path[0] != '/') { $root = $_SERVER['PHUTIL_LIBRARY_ROOT']; } } $okay = $this->executeInclude($root.$path.'/__phutil_library_init__.php'); if (!$okay) { throw new PhutilBootloaderException( "Include of '{$path}/__phutil_library_init__.php' failed!"); } } public function loadModule($library, $module) { $this->pushModuleStack($library, $module); phutil_require_source('__init__.php'); $this->popModuleStack(); } public function loadClass($name, $library, $module) { $this->pushModuleStack($library, $module); phutil_require_source($name.'.php'); $this->popModuleStack(); } public function loadSource($source) { $base = $this->peekModuleStack(); $okay = $this->executeInclude($base.'/'.$source); if (!$okay) { throw new PhutilBootloaderException( "Include of '{$base}/{$source}' failed!"); } } public function moduleExists($library, $module) { $path = $this->getLibraryRoot($library); return @file_exists($path.'/'.$module.'/__init__.php'); } private function executeInclude($path) { // Suppress warning spew if the file does not exist; we'll throw an // exception instead. We still emit error text in the case of syntax errors. $old = error_reporting(E_ALL & ~E_WARNING); $okay = include_once $path; error_reporting($old); return $okay; } } /** * @group library */ final class PhutilBootloaderException extends Exception { } /** * Thrown when you attempt to load two different copies of a library with the * same name. Trying to load the second copy of the library will trigger this, * and the library will not be loaded. * * This means you've either done something silly (like tried to explicitly load * two different versions of the same library into the same program -- this * won't work because they'll have namespace conflicts), or your configuration * might have some problems which caused two parts of your program to try to * load the same library but end up loading different copies of it, or there * may be some subtle issue like running 'arc' in a different Arcanist working * directory. (Some bootstrapping workflows like that which run low-level * library components on other copies of themselves are expected to fail.) * * To resolve this, you need to make sure your program loads no more than one * copy of each libphutil library, but exactly how you approach this depends on * why it's happening in the first place. * * @task info Getting Exception Information * @task construct Creating Library Conflict Exceptions * @group library */ final class PhutilLibraryConflictException extends Exception { private $library; private $oldPath; private $newPath; /** * Create a new library conflict exception. * * @param string The name of the library which conflicts with an existing * library. * @param string The path of the already-loaded library. * @param string The path of the attempting-to-load library. * * @task construct */ public function __construct($library, $old_path, $new_path) { $this->library = $library; $this->oldPath = $old_path; $this->newPath = $new_path; $message = "Library conflict! The library '{$library}' has already been ". "loaded (from '{$old_path}') but is now being loaded again ". "from a new location ('{$new_path}'). You can not load ". "multiple copies of the same library into a program."; parent::__construct($message); } /** * Retrieve the name of the library in conflict. * * @return string The name of the library which conflicts with an existing * library. * @task info */ public function getLibrary() { return $this->library; } /** * Get the path to the library which has already been loaded earlier in the * program's execution. * * @return string The path of the already-loaded library. * @task info */ public function getOldPath() { return $this->oldPath; } /** * Get the path to the library which is causing this conflict. * * @return string The path of the attempting-to-load library. * @task info */ public function getNewPath() { return $this->newPath; } } phutil_register_library('phutil', __FILE__); phutil_require_module('phutil', 'symbols'); /** * @group library */ function __phutil_autoload($class) { try { PhutilSymbolLoader::loadClass($class); } catch (PhutilMissingSymbolException $ex) { // If there are other SPL autoloaders installed, we need to give them a // chance to load the class. Throw the exception if we're the last // autoloader; if not, swallow it and let them take a shot. $autoloaders = spl_autoload_functions(); $last = end($autoloaders); if ($last == '__phutil_autoload') { throw $ex; } } } spl_autoload_register('__phutil_autoload', $throw = true); diff --git a/src/filesystem/deferredlog/__tests__/PhutilDeferredLogTestCase.php b/src/filesystem/deferredlog/__tests__/PhutilDeferredLogTestCase.php index f677f3d..6c025a5 100644 --- a/src/filesystem/deferredlog/__tests__/PhutilDeferredLogTestCase.php +++ b/src/filesystem/deferredlog/__tests__/PhutilDeferredLogTestCase.php @@ -1,131 +1,136 @@ checkLog( "derp\n", "derp", array()); $this->checkLog( "[20 Aug 1984] alincoln\n", "[%T] %u", array( 'T' => '20 Aug 1984', 'u' => 'alincoln', )); $this->checkLog( "%%%%%\n", "%%%%%%%%%%", array( '%' => '%', )); $this->checkLog( "\\000\\001\\002\n", "%a%b%c", array( 'a' => chr(0), 'b' => chr(1), 'c' => chr(2), )); $this->checkLog( "Download: 100%\n", "Download: %C", array( 'C' => '100%', )); $this->checkLog( "- bee -\n", "%a %b %c", array( 'b' => 'bee', )); $this->checkLog( "\\\\\n", "%b", array( 'b' => '\\', )); $this->checkLog( "a\t\\t\n", "%a\t%b", array( 'a' => 'a', 'b' => "\t", )); $this->checkLog( "\\001ab\n", "\1a%a", array( 'a' => 'b', )); } public function testLogWriteFailure() { $caught = null; try { - new PhutilDeferredLog('/derp/derp/derp/derp/derp/derp/derp', 'derp'); + $log = new PhutilDeferredLog('/derp/derp/derp/derp/derp', 'derp'); + if (phutil_is_hiphop_runtime()) { + // In HipHop exceptions thrown in destructors are not normally + // catchable, so call __destruct() explicitly. + $log->__destruct(); + } } catch (Exception $ex) { $caught = $ex; } $this->assertEqual(true, $caught instanceof Exception); } public function testManyWriters() { $root = phutil_get_library_root('phutil').'/../'; $bin = $root.'scripts/test/deferred_log.php'; $n_writers = 3; $n_lines = 8; $tmp = new TempFile(); $futures = array(); for ($ii = 0; $ii < $n_writers; $ii++) { $futures[] = new ExecFuture("%s %d %s", $bin, $n_lines, (string)$tmp); } Futures($futures)->resolveAll(); $this->assertEqual( str_repeat("abcdefghijklmnopqrstuvwxyz\n", $n_writers * $n_lines), Filesystem::readFile($tmp)); } private function checkLog($expect, $format, $data) { $tmp = new TempFile(); $log = new PhutilDeferredLog($tmp, $format); $log->setData($data); unset($log); $this->assertEqual($expect, Filesystem::readFile($tmp), $format); } }