Changeset View
Changeset View
Standalone View
Standalone View
src/moduleutils/PhutilLibraryMapBuilder.php
<?php | <?php | ||||
/** | /** | ||||
* Build maps of libphutil libraries. libphutil uses the library map to locate | * Build maps of libphutil libraries. libphutil uses the library map to locate | ||||
* and load classes and functions in the library. | * and load classes and functions in the library. | ||||
* | * | ||||
* @task map Mapping libphutil Libraries | * @task map Mapping libphutil Libraries | ||||
* @task path Path Management | * @task path Path Management | ||||
* @task symbol Symbol Analysis and Caching | * @task symbol Symbol Analysis and Caching | ||||
* @task source Source Management | * @task source Source Management | ||||
*/ | */ | ||||
final class PhutilLibraryMapBuilder extends Phobject { | final class PhutilLibraryMapBuilder extends Phobject { | ||||
private $root; | private $root; | ||||
private $quiet = true; | |||||
private $subprocessLimit = 8; | private $subprocessLimit = 8; | ||||
private $fileSymbolMap; | private $fileSymbolMap; | ||||
private $librarySymbolMap; | private $librarySymbolMap; | ||||
const LIBRARY_MAP_VERSION_KEY = '__library_version__'; | const LIBRARY_MAP_VERSION_KEY = '__library_version__'; | ||||
const LIBRARY_MAP_VERSION = 2; | const LIBRARY_MAP_VERSION = 2; | ||||
Show All 10 Lines | /* -( Mapping libphutil Libraries )---------------------------------------- */ | ||||
* | * | ||||
* @task map | * @task map | ||||
*/ | */ | ||||
public function __construct($root) { | public function __construct($root) { | ||||
$this->root = $root; | $this->root = $root; | ||||
} | } | ||||
/** | /** | ||||
* Control status output. Use `--quiet` to set this. | |||||
* | |||||
* @param bool If true, don't show status output. | |||||
* @return this | |||||
* | |||||
* @task map | |||||
*/ | |||||
public function setQuiet($quiet) { | |||||
$this->quiet = $quiet; | |||||
return $this; | |||||
} | |||||
/** | |||||
* Control subprocess parallelism limit. Use `--limit` to set this. | * Control subprocess parallelism limit. Use `--limit` to set this. | ||||
* | * | ||||
* @param int Maximum number of subprocesses to run in parallel. | * @param int Maximum number of subprocesses to run in parallel. | ||||
* @return this | * @return this | ||||
* | * | ||||
* @task map | * @task map | ||||
*/ | */ | ||||
public function setSubprocessLimit($limit) { | public function setSubprocessLimit($limit) { | ||||
Show All 40 Lines | /* -( Mapping libphutil Libraries )---------------------------------------- */ | ||||
* | * | ||||
* @return void | * @return void | ||||
* | * | ||||
* @task map | * @task map | ||||
*/ | */ | ||||
public function buildAndWriteMap() { | public function buildAndWriteMap() { | ||||
$library_map = $this->buildMap(); | $library_map = $this->buildMap(); | ||||
$this->log(pht('Writing map...')); | |||||
$this->writeLibraryMap($library_map); | $this->writeLibraryMap($library_map); | ||||
} | } | ||||
/** | |||||
* Write a status message to the user, if not running in quiet mode. | |||||
* | |||||
* @param string Message to write. | |||||
* @return this | |||||
* | |||||
* @task map | |||||
*/ | |||||
private function log($message) { | |||||
if (!$this->quiet) { | |||||
@fwrite(STDERR, "%s\n", $message); | |||||
} | |||||
return $this; | |||||
} | |||||
/* -( Path Management )---------------------------------------------------- */ | /* -( Path Management )---------------------------------------------------- */ | ||||
/** | /** | ||||
* Get the path to some file in the library. | * Get the path to some file in the library. | ||||
* | * | ||||
* @param string A library-relative path. If omitted, returns the library | * @param string A library-relative path. If omitted, returns the library | ||||
* root path. | * root path. | ||||
▲ Show 20 Lines • Show All 92 Lines • ▼ Show 20 Lines | private function writeSymbolCache(array $symbol_map, array $source_map) { | ||||
$cache = array( | $cache = array( | ||||
self::SYMBOL_CACHE_VERSION_KEY => self::SYMBOL_CACHE_VERSION, | self::SYMBOL_CACHE_VERSION_KEY => self::SYMBOL_CACHE_VERSION, | ||||
); | ); | ||||
foreach ($symbol_map as $file => $symbols) { | foreach ($symbol_map as $file => $symbols) { | ||||
$cache[$source_map[$file]] = $symbols; | $cache[$source_map[$file]] = $symbols; | ||||
} | } | ||||
$json = json_encode($cache); | $json = json_encode($cache); | ||||
try { | |||||
Filesystem::writeFile($cache_file, $json); | Filesystem::writeFile($cache_file, $json); | ||||
cspeckmim: This is the only case where a log is written out in case of error. There's another `Filesystem… | |||||
} catch (FilesystemException $ex) { | |||||
$this->log(pht('Unable to save the cache!')); | |||||
} | |||||
} | } | ||||
/** | /** | ||||
* Drop the symbol cache, forcing a clean rebuild. | * Drop the symbol cache, forcing a clean rebuild. | ||||
* | * | ||||
* @return this | * @return this | ||||
* | * | ||||
* @task symbol | * @task symbol | ||||
*/ | */ | ||||
public function dropSymbolCache() { | public function dropSymbolCache() { | ||||
$this->log(pht('Dropping symbol cache...')); | |||||
Filesystem::remove($this->getPathForSymbolCache()); | Filesystem::remove($this->getPathForSymbolCache()); | ||||
} | } | ||||
/** | /** | ||||
* Build a future which returns a `extract-symbols.php` analysis of a source | * Build a future which returns a `extract-symbols.php` analysis of a source | ||||
* file. | * file. | ||||
* | * | ||||
* @param string Relative path to the source file to analyze. | * @param string Relative path to the source file to analyze. | ||||
▲ Show 20 Lines • Show All 183 Lines • ▼ Show 20 Lines | EOPHP; | ||||
/** | /** | ||||
* Analyze the library, generating the file and symbol maps. | * Analyze the library, generating the file and symbol maps. | ||||
* | * | ||||
* @return void | * @return void | ||||
*/ | */ | ||||
private function analyzeLibrary() { | private function analyzeLibrary() { | ||||
// Identify all the ".php" source files in the library. | // Identify all the ".php" source files in the library. | ||||
$this->log(pht('Finding source files...')); | |||||
$source_map = $this->loadSourceFileMap(); | $source_map = $this->loadSourceFileMap(); | ||||
$this->log( | |||||
pht('Found %s files.', new PhutilNumber(count($source_map)))); | |||||
// Load the symbol cache with existing parsed symbols. This allows us | // Load the symbol cache with existing parsed symbols. This allows us | ||||
// to remap libraries quickly by analyzing only changed files. | // to remap libraries quickly by analyzing only changed files. | ||||
$this->log(pht('Loading symbol cache...')); | |||||
$symbol_cache = $this->loadSymbolCache(); | $symbol_cache = $this->loadSymbolCache(); | ||||
// If the XHPAST binary is not up-to-date, build it now. Otherwise, | // If the XHPAST binary is not up-to-date, build it now. Otherwise, | ||||
// `extract-symbols.php` will attempt to build the binary and will fail | // `extract-symbols.php` will attempt to build the binary and will fail | ||||
// miserably because it will be trying to build the same file multiple | // miserably because it will be trying to build the same file multiple | ||||
// times in parallel. | // times in parallel. | ||||
if (!PhutilXHPASTBinary::isAvailable()) { | if (!PhutilXHPASTBinary::isAvailable()) { | ||||
PhutilXHPASTBinary::build(); | PhutilXHPASTBinary::build(); | ||||
} | } | ||||
// Build out the symbol analysis for all the files in the library. For | // Build out the symbol analysis for all the files in the library. For | ||||
// each file, check if it's in cache. If we miss in the cache, do a fresh | // each file, check if it's in cache. If we miss in the cache, do a fresh | ||||
// analysis. | // analysis. | ||||
$symbol_map = array(); | $symbol_map = array(); | ||||
$futures = array(); | $futures = array(); | ||||
foreach ($source_map as $file => $hash) { | foreach ($source_map as $file => $hash) { | ||||
if (!empty($symbol_cache[$hash])) { | if (!empty($symbol_cache[$hash])) { | ||||
$symbol_map[$file] = $symbol_cache[$hash]; | $symbol_map[$file] = $symbol_cache[$hash]; | ||||
continue; | continue; | ||||
} | } | ||||
$futures[$file] = $this->buildSymbolAnalysisFuture($file); | $futures[$file] = $this->buildSymbolAnalysisFuture($file); | ||||
} | } | ||||
$this->log( | |||||
pht('Found %s files in cache.', new PhutilNumber(count($symbol_map)))); | |||||
// Run the analyzer on any files which need analysis. | // Run the analyzer on any files which need analysis. | ||||
if ($futures) { | if ($futures) { | ||||
$limit = $this->subprocessLimit; | $limit = $this->subprocessLimit; | ||||
$this->log( | |||||
pht( | |||||
'Analyzing %s file(s) with %s subprocess(es)...', | |||||
phutil_count($futures), | |||||
new PhutilNumber($limit))); | |||||
$progress = new PhutilConsoleProgressBar(); | $progress = new PhutilConsoleProgressBar(); | ||||
if ($this->quiet) { | |||||
$progress->setQuiet(true); | |||||
} | |||||
$progress->setTotal(count($futures)); | $progress->setTotal(count($futures)); | ||||
$futures = id(new FutureIterator($futures)) | $futures = id(new FutureIterator($futures)) | ||||
->limit($limit); | ->limit($limit); | ||||
foreach ($futures as $file => $future) { | foreach ($futures as $file => $future) { | ||||
$result = $future->resolveJSON(); | $result = $future->resolveJSON(); | ||||
if (empty($result['error'])) { | if (empty($result['error'])) { | ||||
$symbol_map[$file] = $result; | $symbol_map[$file] = $result; | ||||
Show All 11 Lines | private function analyzeLibrary() { | ||||
$this->fileSymbolMap = $symbol_map; | $this->fileSymbolMap = $symbol_map; | ||||
// We're done building the cache, so write it out immediately. Note that | // We're done building the cache, so write it out immediately. Note that | ||||
// we've only retained entries for files we found, so this implicitly cleans | // we've only retained entries for files we found, so this implicitly cleans | ||||
// out old cache entries. | // out old cache entries. | ||||
$this->writeSymbolCache($symbol_map, $source_map); | $this->writeSymbolCache($symbol_map, $source_map); | ||||
// Our map is up to date, so either show it on stdout or write it to disk. | // Our map is up to date, so either show it on stdout or write it to disk. | ||||
$this->log(pht('Building library map...')); | |||||
$this->librarySymbolMap = $this->buildLibraryMap($symbol_map); | $this->librarySymbolMap = $this->buildLibraryMap($symbol_map); | ||||
} | } | ||||
} | } |
This is the only case where a log is written out in case of error. There's another Filesystem::writeFile() in writeLibraryMap() and isn't in a try/catch. Maybe this try/catch should be removed? I think removing it will result in rebuild-map.php exiting with a non-zero exit code which should propagate properly I think.