Changeset View
Changeset View
Standalone View
Standalone View
scripts/celerity_mapper.php
#!/usr/bin/env php | #!/usr/bin/env php | ||||
<?php | <?php | ||||
$package_spec = array( | echo "This script is obsolete. Run `bin/celerity map` instead.\n"; | ||||
'javelin.pkg.js' => array( | exit(1); | ||||
'javelin-util', | |||||
'javelin-install', | |||||
'javelin-event', | |||||
'javelin-stratcom', | |||||
'javelin-behavior', | |||||
'javelin-resource', | |||||
'javelin-request', | |||||
'javelin-vector', | |||||
'javelin-dom', | |||||
'javelin-json', | |||||
'javelin-uri', | |||||
'javelin-workflow', | |||||
'javelin-mask', | |||||
'javelin-typeahead', | |||||
'javelin-typeahead-normalizer', | |||||
'javelin-typeahead-source', | |||||
'javelin-typeahead-preloaded-source', | |||||
'javelin-typeahead-ondemand-source', | |||||
'javelin-tokenizer', | |||||
'javelin-history', | |||||
), | |||||
'core.pkg.js' => array( | |||||
'javelin-behavior-aphront-basic-tokenizer', | |||||
'javelin-behavior-workflow', | |||||
'javelin-behavior-aphront-form-disable-on-submit', | |||||
'phabricator-keyboard-shortcut-manager', | |||||
'phabricator-keyboard-shortcut', | |||||
'javelin-behavior-phabricator-keyboard-shortcuts', | |||||
'javelin-behavior-refresh-csrf', | |||||
'javelin-behavior-phabricator-watch-anchor', | |||||
'javelin-behavior-phabricator-autofocus', | |||||
'phabricator-menu-item', | |||||
'phabricator-dropdown-menu', | |||||
'phabricator-phtize', | |||||
'javelin-behavior-phabricator-oncopy', | |||||
'phabricator-tooltip', | |||||
'javelin-behavior-phabricator-tooltips', | |||||
'phabricator-prefab', | |||||
'javelin-behavior-device', | |||||
'javelin-behavior-toggle-class', | |||||
'javelin-behavior-lightbox-attachments', | |||||
'phabricator-busy', | |||||
'javelin-aphlict', | |||||
'phabricator-notification', | |||||
'javelin-behavior-aphlict-listen', | |||||
'javelin-behavior-phabricator-search-typeahead', | |||||
'javelin-behavior-konami', | |||||
'javelin-behavior-aphlict-dropdown', | |||||
'javelin-behavior-history-install', | |||||
'javelin-behavior-phabricator-gesture', | |||||
'javelin-behavior-phabricator-active-nav', | |||||
'javelin-behavior-phabricator-nav', | |||||
'javelin-behavior-phabricator-remarkup-assist', | |||||
'phabricator-textareautils', | |||||
'phabricator-file-upload', | |||||
'javelin-behavior-global-drag-and-drop', | |||||
'javelin-behavior-phabricator-reveal-content', | |||||
'phabricator-hovercard', | |||||
'javelin-behavior-phabricator-hovercards', | |||||
'javelin-color', | |||||
'javelin-fx', | |||||
), | |||||
'core.pkg.css' => array( | |||||
'phabricator-core-css', | |||||
'phabricator-zindex-css', | |||||
'phui-button-css', | |||||
'phabricator-standard-page-view', | |||||
'aphront-dialog-view-css', | |||||
'phui-form-view-css', | |||||
'aphront-panel-view-css', | |||||
'aphront-table-view-css', | |||||
'aphront-tokenizer-control-css', | |||||
'aphront-typeahead-control-css', | |||||
'aphront-list-filter-view-css', | |||||
'phabricator-jump-nav', | |||||
'phabricator-remarkup-css', | |||||
'syntax-highlighting-css', | |||||
'aphront-pager-view-css', | |||||
'phabricator-transaction-view-css', | |||||
'aphront-tooltip-css', | |||||
'phabricator-flag-css', | |||||
'aphront-error-view-css', | |||||
'sprite-icons-css', | |||||
'sprite-gradient-css', | |||||
'sprite-menu-css', | |||||
'sprite-apps-large-css', | |||||
'sprite-status-css', | |||||
'phabricator-main-menu-view', | |||||
'phabricator-notification-css', | |||||
'phabricator-notification-menu-css', | |||||
'lightbox-attachment-css', | |||||
'phui-header-view-css', | |||||
'phabricator-filetree-view-css', | |||||
'phabricator-nav-view-css', | |||||
'phabricator-side-menu-view-css', | |||||
'phabricator-crumbs-view-css', | |||||
'phui-object-item-list-view-css', | |||||
'global-drag-and-drop-css', | |||||
'phui-spacing-css', | |||||
'phui-form-css', | |||||
'phui-icon-view-css', | |||||
'phabricator-application-launch-view-css', | |||||
'phabricator-action-list-view-css', | |||||
'phui-property-list-view-css', | |||||
'phabricator-tag-view-css', | |||||
'phui-list-view-css', | |||||
), | |||||
'differential.pkg.css' => array( | |||||
'differential-core-view-css', | |||||
'differential-changeset-view-css', | |||||
'differential-results-table-css', | |||||
'differential-revision-history-css', | |||||
'differential-revision-list-css', | |||||
'differential-table-of-contents-css', | |||||
'differential-revision-comment-css', | |||||
'differential-revision-add-comment-css', | |||||
'differential-revision-comment-list-css', | |||||
'phabricator-object-selector-css', | |||||
'phabricator-content-source-view-css', | |||||
'differential-local-commits-view-css', | |||||
'inline-comment-summary-css', | |||||
), | |||||
'differential.pkg.js' => array( | |||||
'phabricator-drag-and-drop-file-upload', | |||||
'phabricator-shaped-request', | |||||
'javelin-behavior-differential-feedback-preview', | |||||
'javelin-behavior-differential-edit-inline-comments', | |||||
'javelin-behavior-differential-populate', | |||||
'javelin-behavior-differential-show-more', | |||||
'javelin-behavior-differential-diff-radios', | |||||
'javelin-behavior-differential-accept-with-errors', | |||||
'javelin-behavior-differential-comment-jump', | |||||
'javelin-behavior-differential-add-reviewers-and-ccs', | |||||
'javelin-behavior-differential-keyboard-navigation', | |||||
'javelin-behavior-aphront-drag-and-drop-textarea', | |||||
'javelin-behavior-phabricator-object-selector', | |||||
'javelin-behavior-repository-crossreference', | |||||
'javelin-behavior-load-blame', | |||||
'differential-inline-comment-editor', | |||||
'javelin-behavior-differential-dropdown-menus', | |||||
'javelin-behavior-differential-toggle-files', | |||||
'javelin-behavior-differential-user-select', | |||||
), | |||||
'diffusion.pkg.css' => array( | |||||
'diffusion-commit-view-css', | |||||
'diffusion-icons-css', | |||||
), | |||||
'diffusion.pkg.js' => array( | |||||
'javelin-behavior-diffusion-pull-lastmodified', | |||||
'javelin-behavior-diffusion-commit-graph', | |||||
'javelin-behavior-audit-preview', | |||||
), | |||||
'maniphest.pkg.css' => array( | |||||
'maniphest-task-summary-css', | |||||
'phabricator-project-tag-css', | |||||
), | |||||
'maniphest.pkg.js' => array( | |||||
'javelin-behavior-maniphest-batch-selector', | |||||
'javelin-behavior-maniphest-transaction-controls', | |||||
'javelin-behavior-maniphest-transaction-preview', | |||||
'javelin-behavior-maniphest-transaction-expand', | |||||
'javelin-behavior-maniphest-subpriority-editor', | |||||
), | |||||
'darkconsole.pkg.js' => array( | |||||
'javelin-behavior-dark-console', | |||||
'javelin-behavior-error-log', | |||||
), | |||||
); | |||||
require_once dirname(__FILE__).'/__init_script__.php'; | |||||
$args = new PhutilArgumentParser($argv); | |||||
$args->setTagline('map static resources'); | |||||
$args->setSynopsis( | |||||
"**celerity_mapper.php** [--output __path__] [--with-custom] <webroot>"); | |||||
$args->parse( | |||||
array( | |||||
array( | |||||
'name' => 'webroot', | |||||
'wildcard' => true, | |||||
), | |||||
)); | |||||
$root = $args->getArg('webroot'); | |||||
if (count($root) != 1 || !is_dir(reset($root))) { | |||||
$args->printHelpAndExit(); | |||||
} | |||||
$root = Filesystem::resolvePath(reset($root)); | |||||
$celerity_path = Filesystem::resolvePath( | |||||
'../src/__celerity_resource_map__.php', | |||||
$root); | |||||
$resource_hash = PhabricatorEnv::getEnvConfig('celerity.resource-hash'); | |||||
$runtime_map = array(); | |||||
echo "Finding raw static resources...\n"; | |||||
$finder = id(new FileFinder($root)) | |||||
->withType('f') | |||||
->withSuffix('png') | |||||
->withSuffix('jpg') | |||||
->withSuffix('gif') | |||||
->withSuffix('swf') | |||||
->withFollowSymlinks(true) | |||||
->setGenerateChecksums(true); | |||||
$raw_files = $finder->find(); | |||||
echo "Processing ".count($raw_files)." files"; | |||||
foreach ($raw_files as $path => $hash) { | |||||
echo "."; | |||||
$path = '/'.Filesystem::readablePath($path, $root); | |||||
$type = CelerityResourceTransformer::getResourceType($path); | |||||
$hash = md5($hash.$path.$resource_hash); | |||||
$uri = '/res/'.substr($hash, 0, 8).$path; | |||||
$runtime_map[$path] = array( | |||||
'hash' => $hash, | |||||
'uri' => $uri, | |||||
'disk' => $path, | |||||
'type' => $type, | |||||
); | |||||
} | |||||
echo "\n"; | |||||
$xformer = id(new CelerityResourceTransformer()) | |||||
->setMinify(false) | |||||
->setRawResourceMap($runtime_map); | |||||
echo "Finding transformable static resources...\n"; | |||||
$finder = id(new FileFinder($root)) | |||||
->withType('f') | |||||
->withSuffix('js') | |||||
->withSuffix('css') | |||||
->withFollowSymlinks(true) | |||||
->setGenerateChecksums(true); | |||||
$files = $finder->find(); | |||||
echo "Processing ".count($files)." files"; | |||||
$file_map = array(); | |||||
foreach ($files as $path => $raw_hash) { | |||||
echo "."; | |||||
$path = '/'.Filesystem::readablePath($path, $root); | |||||
$data = Filesystem::readFile($root.$path); | |||||
$data = $xformer->transformResource($path, $data); | |||||
$hash = md5($data); | |||||
$hash = md5($hash.$path.$resource_hash); | |||||
$file_map[$path] = array( | |||||
'hash' => $hash, | |||||
'disk' => $path, | |||||
); | |||||
} | |||||
echo "\n"; | |||||
$resource_graph = array(); | |||||
$hash_map = array(); | |||||
$parser = new PhutilDocblockParser(); | |||||
foreach ($file_map as $path => $info) { | |||||
$type = CelerityResourceTransformer::getResourceType($path); | |||||
$data = Filesystem::readFile($root.$info['disk']); | |||||
$matches = array(); | |||||
$ok = preg_match('@/[*][*].*?[*]/@s', $data, $matches); | |||||
if (!$ok) { | |||||
throw new Exception( | |||||
"File {$path} does not have a header doc comment. Encode dependency ". | |||||
"data in a header docblock."); | |||||
} | |||||
list($description, $metadata) = $parser->parse($matches[0]); | |||||
$provides = preg_split('/\s+/', trim(idx($metadata, 'provides'))); | |||||
$requires = preg_split('/\s+/', trim(idx($metadata, 'requires'))); | |||||
$provides = array_filter($provides); | |||||
$requires = array_filter($requires); | |||||
if (!$provides) { | |||||
// Tests and documentation-only JS is permitted to @provide no targets. | |||||
continue; | |||||
} | |||||
if (count($provides) > 1) { | |||||
throw new Exception( | |||||
"File {$path} must @provide at most one Celerity target."); | |||||
} | |||||
$provides = reset($provides); | |||||
$uri = '/res/'.substr($info['hash'], 0, 8).$path; | |||||
$hash_map[$provides] = $info['hash']; | |||||
$resource_graph[$provides] = $requires; | |||||
$runtime_map[$provides] = array( | |||||
'uri' => $uri, | |||||
'type' => $type, | |||||
'requires' => $requires, | |||||
'disk' => $path, | |||||
); | |||||
} | |||||
$celerity_resource_graph = new CelerityResourceGraph(); | |||||
$celerity_resource_graph->addNodes($resource_graph); | |||||
$celerity_resource_graph->setResourceGraph($resource_graph); | |||||
$celerity_resource_graph->loadGraph(); | |||||
foreach ($resource_graph as $provides => $requires) { | |||||
$cycle = $celerity_resource_graph->detectCycles($provides); | |||||
if ($cycle) { | |||||
throw new Exception( | |||||
"Cycle detected in resource graph: ". implode($cycle, " => ") | |||||
); | |||||
} | |||||
} | |||||
$package_map = array(); | |||||
foreach ($package_spec as $name => $package) { | |||||
$hashes = array(); | |||||
$type = null; | |||||
foreach ($package as $symbol) { | |||||
if (empty($hash_map[$symbol])) { | |||||
throw new Exception( | |||||
"Package specification for '{$name}' includes '{$symbol}', but that ". | |||||
"symbol is not defined anywhere."); | |||||
} | |||||
if ($type === null) { | |||||
$type = $runtime_map[$symbol]['type']; | |||||
} else { | |||||
$ntype = $runtime_map[$symbol]['type']; | |||||
if ($type !== $ntype) { | |||||
throw new Exception( | |||||
"Package specification for '{$name}' mixes resources of type ". | |||||
"'{$type}' with resources of type '{$ntype}'. Each package may only ". | |||||
"contain one type of resource."); | |||||
} | |||||
} | |||||
$hashes[] = $symbol.':'.$hash_map[$symbol]; | |||||
} | |||||
$key = substr(md5(implode("\n", $hashes)), 0, 8); | |||||
$package_map['packages'][$key] = array( | |||||
'name' => $name, | |||||
'symbols' => $package, | |||||
'uri' => '/res/pkg/'.$key.'/'.$name, | |||||
'type' => $type, | |||||
); | |||||
foreach ($package as $symbol) { | |||||
$package_map['reverse'][$symbol] = $key; | |||||
} | |||||
} | |||||
ksort($runtime_map); | |||||
$runtime_map = var_export($runtime_map, true); | |||||
$runtime_map = preg_replace('/\s+$/m', '', $runtime_map); | |||||
$runtime_map = preg_replace('/array \(/', 'array(', $runtime_map); | |||||
$package_map['packages'] = isort($package_map['packages'], 'name'); | |||||
ksort($package_map['reverse']); | |||||
$package_map = var_export($package_map, true); | |||||
$package_map = preg_replace('/\s+$/m', '', $package_map); | |||||
$package_map = preg_replace('/array \(/', 'array(', $package_map); | |||||
$generated = '@'.'generated'; | |||||
$resource_map = <<<EOFILE | |||||
<?php | |||||
/** | |||||
* This file is automatically generated. Use 'celerity_mapper.php' to rebuild | |||||
* it. | |||||
* {$generated} | |||||
*/ | |||||
celerity_register_resource_map({$runtime_map}, {$package_map}); | |||||
EOFILE; | |||||
echo "Writing map...\n"; | |||||
Filesystem::writeFile($celerity_path, $resource_map); | |||||
echo "Done.\n"; |