Page MenuHomeSealhub

No OneTemporary

diff --git a/scripts/phutil_analyzer.php b/scripts/phutil_analyzer.php
index e519ae13..80f5201f 100755
--- a/scripts/phutil_analyzer.php
+++ b/scripts/phutil_analyzer.php
@@ -1,361 +1,362 @@
#!/usr/bin/env php
<?php
/*
* Copyright 2011 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
$builtin_classes = get_declared_classes();
$builtin_interfaces = get_declared_interfaces();
$builtin_functions = get_defined_functions();
$builtin_functions = $builtin_functions['internal'];
$builtin = array(
'class' => array_fill_keys($builtin_classes, true) + array(
'PhutilBootloader' => true,
),
'function' => array_fill_keys($builtin_functions, true) + array(
'empty' => true,
'isset' => true,
'echo' => true,
'print' => true,
'exit' => true,
'die' => true,
+ 'phutil_load_library' => true,
),
'interface' => array_fill_keys($builtin_interfaces, true),
);
require_once dirname(__FILE__).'/__init_script__.php';
if ($argc != 2) {
$self = basename($argv[0]);
echo "usage: {$self} <module>\n";
exit(1);
}
phutil_require_module('phutil', 'filesystem');
$dir = Filesystem::resolvePath($argv[1]);
phutil_require_module('phutil', 'parser/xhpast/bin');
phutil_require_module('phutil', 'parser/xhpast/api/tree');
phutil_require_module('arcanist', 'lint/linter/phutilmodule');
phutil_require_module('arcanist', 'lint/message');
phutil_require_module('arcanist', 'parser/phutilmodule');
$data = array();
$futures = array();
foreach (Filesystem::listDirectory($dir, $hidden_files = false) as $file) {
if (!preg_match('/.php$/', $file)) {
continue;
}
$data[$file] = Filesystem::readFile($dir.'/'.$file);
$futures[$file] = xhpast_get_parser_future($data[$file]);
}
$requirements = new PhutilModuleRequirements();
$requirements->addBuiltins($builtin);
$has_init = false;
$has_files = false;
foreach (Futures($futures) as $file => $future) {
try {
$tree = XHPASTTree::newFromDataAndResolvedExecFuture(
$data[$file],
$future->resolve());
} catch (XHPASTSyntaxErrorException $ex) {
echo "Syntax Error! In '{$file}': ".$ex->getMessage()."\n";
exit(1);
}
$root = $tree->getRootNode();
$requirements->setCurrentFile($file);
if ($file == '__init__.php') {
$has_init = true;
$calls = $root->selectDescendantsOfType('n_FUNCTION_CALL');
foreach ($calls as $call) {
$name = $call->getChildByIndex(0);
$call_name = $name->getConcreteString();
if ($call_name == 'phutil_require_source') {
$params = $call->getChildByIndex(1)->getChildren();
if (count($params) !== 1) {
$requirements->addLint(
$call,
$call->getConcreteString(),
ArcanistPhutilModuleLinter::LINT_ANALYZER_SIGNATURE,
"Call to phutil_require_source() must have exactly one argument.");
continue;
}
$param = reset($params);
$value = $param->getStringLiteralValue();
if ($value === null) {
$requirements->addLint(
$param,
$param->getConcreteString(),
ArcanistPhutilModuleLinter::LINT_ANALYZER_SIGNATURE,
"phutil_require_source() parameter must be a string literal.");
continue;
}
$requirements->addSourceDependency($name, $value);
} else if ($call_name == 'phutil_require_module') {
analyze_phutil_require_module($call, $requirements);
}
}
} else {
$has_files = true;
$requirements->addSourceDeclaration(basename($file));
// Function uses:
// - Explicit call
// TODO?: String literal in ReflectionFunction().
$calls = $root->selectDescendantsOfType('n_FUNCTION_CALL');
foreach ($calls as $call) {
$name = $call->getChildByIndex(0);
if ($name->getTypeName() == 'n_VARIABLE' ||
$name->getTypeName() == 'n_VARIABLE_VARIABLE') {
$requirements->addLint(
$name,
$name->getConcreteString(),
ArcanistPhutilModuleLinter::LINT_ANALYZER_DYNAMIC,
"Use of variable function calls prevents dependencies from being ".
"checked statically. This module may have undetectable errors.");
continue;
}
if ($name->getTypeName() == 'n_CLASS_STATIC_ACCESS') {
// We'll pick this up later.
continue;
}
$call_name = $name->getConcreteString();
if ($call_name == 'phutil_require_module') {
analyze_phutil_require_module($call, $requirements);
} else if ($call_name == 'call_user_func' ||
$call_name == 'call_user_func_array') {
$params = $call->getChildByIndex(1)->getChildren();
if (count($params) == 0) {
$requirements->addLint(
$call,
$call->getConcreteString(),
ArcanistPhutilModuleLinter::LINT_ANALYZER_SIGNATURE,
"Call to {$call_name}() must have at least one argument.");
}
$symbol = array_shift($params);
$symbol_value = $symbol->getStringLiteralValue();
if ($symbol_value) {
$requirements->addFunctionDependency(
$symbol,
$symbol_value);
} else {
$requirements->addLint(
$symbol,
$symbol->getConcreteString(),
ArcanistPhutilModuleLinter::LINT_ANALYZER_DYNAMIC,
"Use of variable arguments to {$call_name} prevents dependencies ".
"from being checked statically. This module may have undetectable ".
"errors.");
}
} else {
$requirements->addFunctionDependency(
$name,
$name->getConcreteString());
}
}
$functions = $root->selectDescendantsOfType('n_FUNCTION_DECLARATION');
foreach ($functions as $function) {
$name = $function->getChildByIndex(2);
$requirements->addFunctionDeclaration(
$name,
$name->getConcreteString());
}
// Class uses:
// - new
// - extends (in class declaration)
// - Static method call
// - Static property access
// - Constant use
// TODO?: String literal in ReflectionClass().
// TODO?: String literal in array literal in call_user_func /
// call_user_func_array().
// TODO: Raise a soft warning for use of an unknown class in:
// - Typehints
// - instanceof
// - catch
$classes = $root->selectDescendantsOfType('n_CLASS_DECLARATION');
foreach ($classes as $class) {
$class_name = $class->getChildByIndex(1);
$requirements->addClassDeclaration(
$class_name,
$class_name->getConcreteString());
$extends = $class->getChildByIndex(2);
foreach ($extends->selectDescendantsOfType('n_CLASS_NAME') as $parent) {
$requirements->addClassDependency(
$class_name->getConcreteString(),
$parent,
$parent->getConcreteString());
}
$implements = $class->getChildByIndex(3);
$interfaces = $implements->selectDescendantsOfType('n_CLASS_NAME');
foreach ($interfaces as $interface) {
$requirements->addInterfaceDependency(
$class_name->getConcreteString(),
$interface,
$interface->getConcreteString());
}
}
if (count($classes) > 1) {
foreach ($classes as $class) {
$class_name = $class->getChildByIndex(1);
$class_string = $class_name->getConcreteString();
$requirements->addLint(
$class_name,
$class_string,
ArcanistPhutilModuleLinter::LINT_ANALYZER_MULTIPLE_CLASSES,
"This file declares more than one class. Declare only one class per ".
"file.");
break;
}
}
$uses_of_new = $root->selectDescendantsOfType('n_NEW');
foreach ($uses_of_new as $new_operator) {
$name = $new_operator->getChildByIndex(0);
if ($name->getTypeName() == 'n_VARIABLE' ||
$name->getTypeName() == 'n_VARIABLE_VARIABLE') {
$requirements->addLint(
$name,
$name->getConcreteString(),
ArcanistPhutilModuleLinter::LINT_ANALYZER_DYNAMIC,
"Use of variable class instantiation prevents dependencies from ".
"being checked statically. This module may have undetectable ".
"errors.");
continue;
}
$requirements->addClassDependency(
null,
$name,
$name->getConcreteString());
}
$static_uses = $root->selectDescendantsOfType('n_CLASS_STATIC_ACCESS');
foreach ($static_uses as $static_use) {
$name = $static_use->getChildByIndex(0);
if ($name->getTypeName() != 'n_CLASS_NAME') {
echo "WARNING UNLINTABLE\n";
continue;
}
$name_concrete = $name->getConcreteString();
$magic_names = array(
'static' => true,
'parent' => true,
'self' => true,
);
if (isset($magic_names[$name_concrete])) {
continue;
}
$requirements->addClassDependency(
null,
$name,
$name_concrete);
}
// Interface uses:
// - implements
// - extends (in interface declaration)
$interfaces = $root->selectDescendantsOfType('n_INTERFACE_DECLARATION');
foreach ($interfaces as $interface) {
$interface_name = $interface->getChildByIndex(1);
$requirements->addInterfaceDeclaration(
$interface_name,
$interface_name->getConcreteString());
$extends = $interface->getChildByIndex(2);
foreach ($extends->selectDescendantsOfType('n_CLASS_NAME') as $parent) {
$requirements->addInterfaceDependency(
$class_name->getConcreteString(),
$parent,
$parent->getConcreteString());
}
}
}
}
if (!$has_init && $has_files) {
$requirements->addRawLint(
ArcanistPhutilModuleLinter::LINT_ANALYZER_NO_INIT,
"Create an __init__.php file in this module.");
}
echo json_encode($requirements->toDictionary());
/**
* Parses meaning from calls to phutil_require_module() in __init__.php files.
*
* @group module
*/
function analyze_phutil_require_module(
XHPASTNode $call,
PhutilModuleRequirements $requirements) {
$name = $call->getChildByIndex(0);
$params = $call->getChildByIndex(1)->getChildren();
if (count($params) !== 2) {
$requirements->addLint(
$call,
$call->getConcreteString(),
ArcanistPhutilModuleLinter::LINT_ANALYZER_SIGNATURE,
"Call to phutil_require_module() must have exactly two arguments.");
return;
}
$module_param = array_pop($params);
$library_param = array_pop($params);
$library_value = $library_param->getStringLiteralValue();
if ($library_value === null) {
$requirements->addLint(
$library_param,
$library_param->getConcreteString(),
ArcanistPhutilModuleLinter::LINT_ANALYZER_SIGNATURE,
"phutil_require_module() parameters must be string literals.");
return;
}
$module_value = $module_param->getStringLiteralValue();
if ($module_value === null) {
$requirements->addLint(
$module_param,
$module_param->getConcreteString(),
ArcanistPhutilModuleLinter::LINT_ANALYZER_SIGNATURE,
"phutil_require_module() parameters must be string literals.");
return;
}
$requirements->addModuleDependency(
$name,
$library_value.':'.$module_value);
}
diff --git a/scripts/phutil_mapper.php b/scripts/phutil_mapper.php
index 7edf1133..7ad38d3f 100755
--- a/scripts/phutil_mapper.php
+++ b/scripts/phutil_mapper.php
@@ -1,189 +1,214 @@
#!/usr/bin/env php
<?php
/*
* Copyright 2011 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
require_once dirname(__FILE__).'/__init_script__.php';
+$liberate_mode = false;
+for ($ii = 0; $ii < $argc; $ii++) {
+ if ($argv[$ii] == '--find-paths-for-liberate') {
+ $liberate_mode = true;
+ unset($argv[$ii]);
+ }
+}
+$argv = array_values($argv);
+$argc = count($argv);
+
if ($argc != 2) {
$self = basename($argv[0]);
echo "usage: {$self} <phutil_library_root>\n";
exit(1);
}
phutil_require_module('phutil', 'filesystem');
phutil_require_module('phutil', 'filesystem/filefinder');
phutil_require_module('phutil', 'future/exec');
$root = Filesystem::resolvePath($argv[1]);
if (!@file_exists($root.'/__phutil_library_init__.php')) {
throw new Exception("Provided path is not a phutil library.");
}
+if ($liberate_mode) {
+ ob_start();
+}
+
echo "Finding phutil modules...\n";
$files = id(new FileFinder($root))
->withType('f')
->withSuffix('php')
->excludePath('*/.*')
->setGenerateChecksums(true)
->find();
// NOTE: Sorting by filename ensures that hash computation is stable; it is
// important we sort by name instead of by hash because sorting by hash could
// create a bad cache hit if the user swaps the contents of two files.
ksort($files);
$modules = array();
foreach ($files as $file => $hash) {
if (dirname($file) == $root) {
continue;
}
$modules[Filesystem::readablePath(dirname($file), $root)][] = $hash;
}
echo "Found ".count($files)." files in ".count($modules)." modules.\n";
$signatures = array();
foreach ($modules as $module => $hashes) {
$hashes = implode(' ', $hashes);
$signature = md5($hashes);
$signatures[$module] = $signature;
}
try {
$cache = Filesystem::readFile($root.'/.phutil_module_cache');
} catch (Exception $ex) {
$cache = null;
}
$signature_cache = array();
if ($cache) {
$signature_cache = json_decode($cache, true);
if (!is_array($signature_cache)) {
$signature_cache = array();
}
}
$specs = array();
-$futures = array();
+$need_update = array();
foreach ($signatures as $module => $signature) {
if (isset($signature_cache[$module]) &&
$signature_cache[$module]['signature'] == $signature) {
$specs[$module] = $signature_cache[$module];
} else {
- $futures[$module] = new ExecFuture(
- '%s %s',
- dirname(__FILE__).'/phutil_analyzer.php',
- $root.'/'.$module);
+ $need_update[$module] = true;
}
}
+$futures = array();
+foreach ($need_update as $module => $ignored) {
+ $futures[$module] = new ExecFuture(
+ '%s %s',
+ dirname(__FILE__).'/phutil_analyzer.php',
+ $root.'/'.$module);
+}
+
if ($futures) {
echo "Found ".count($specs)." modules in cache; ".
"analyzing ".count($futures)." modified modules";
foreach (Futures($futures)->limit(8) as $module => $future) {
echo ".";
$specs[$module] = array(
'signature' => $signatures[$module],
'spec' => $future->resolveJSON(),
);
}
echo "\n";
} else {
echo "All modules were found in cache.\n";
}
$class_map = array();
$requires_class_map = array();
$requires_interface_map = array();
$function_map = array();
foreach ($specs as $module => $info) {
$spec = $info['spec'];
foreach (array('class', 'interface') as $type) {
foreach ($spec['declares'][$type] as $class => $where) {
if (!empty($class_map[$class])) {
$prior = $class_map[$class];
echo "\n";
echo "Error: definition of {$type} '{$class}' in module '{$module}' ".
"duplicates prior definition in module '{$prior}'.";
echo "\n";
exit(1);
}
$class_map[$class] = $module;
}
}
if (!empty($spec['chain']['class'])) {
$requires_class_map += $spec['chain']['class'];
}
if (!empty($spec['chain']['interface'])) {
$requires_interface_map += $spec['chain']['interface'];
}
foreach ($spec['declares']['function'] as $function => $where) {
if (!empty($function_map[$function])) {
$prior = $function_map[$function];
echo "\n";
echo "Error: definition of function '{$function}' in module '{$module}' ".
"duplicates prior definition in module '{$prior}'.";
echo "\n";
exit(1);
}
$function_map[$function] = $module;
}
}
echo "\n";
ksort($class_map);
ksort($requires_class_map);
ksort($requires_interface_map);
ksort($function_map);
$library_map = array(
'class' => $class_map,
'function' => $function_map,
'requires_class' => $requires_class_map,
'requires_interface' => $requires_interface_map,
);
$library_map = var_export($library_map, $return_string = true);
$library_map = preg_replace('/\s+$/m', '', $library_map);
$library_map = preg_replace('/array \(/', 'array(', $library_map);
$at = '@';
$map_file = <<<EOPHP
<?php
/**
* This file is automatically generated. Use 'phutil_mapper.php' to rebuild it.
* {$at}generated
*/
phutil_register_library_map({$library_map});
EOPHP;
echo "Writing library map file...\n";
Filesystem::writeFile($root.'/__phutil_library_map__.php', $map_file);
+if ($liberate_mode) {
+ ob_get_clean();
+ echo json_encode(array_keys($need_update))."\n";
+ return;
+}
+
echo "Writing module cache...\n";
Filesystem::writeFile(
$root.'/.phutil_module_cache',
json_encode($specs));
echo "Done.\n";
diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php
index 848a4575..cf5667c8 100644
--- a/src/__phutil_library_map__.php
+++ b/src/__phutil_library_map__.php
@@ -1,129 +1,133 @@
<?php
/**
* This file is automatically generated. Use 'phutil_mapper.php' to rebuild it.
* @generated
*/
phutil_register_library_map(array(
'class' =>
array(
'ArcanistAmendWorkflow' => 'workflow/amend',
'ArcanistApacheLicenseLinter' => 'lint/linter/apachelicense',
'ArcanistApacheLicenseLinterTestCase' => 'lint/linter/apachelicense/__tests__',
'ArcanistBaseUnitTestEngine' => 'unit/engine/base',
'ArcanistBaseWorkflow' => 'workflow/base',
'ArcanistBundle' => 'parser/bundle',
'ArcanistCallConduitWorkflow' => 'workflow/call-conduit',
'ArcanistChooseInvalidRevisionException' => 'exception',
'ArcanistChooseNoRevisionsException' => 'exception',
'ArcanistCommitWorkflow' => 'workflow/commit',
'ArcanistConfiguration' => 'configuration',
'ArcanistCoverWorkflow' => 'workflow/cover',
'ArcanistDiffChange' => 'parser/diff/change',
'ArcanistDiffChangeType' => 'parser/diff/changetype',
'ArcanistDiffHunk' => 'parser/diff/hunk',
'ArcanistDiffParser' => 'parser/diff',
'ArcanistDiffParserTestCase' => 'parser/diff/__tests__',
'ArcanistDiffUtils' => 'difference',
'ArcanistDiffWorkflow' => 'workflow/diff',
'ArcanistDifferentialCommitMessage' => 'differential/commitmessage',
'ArcanistDifferentialCommitMessageParserException' => 'differential/commitmessage',
'ArcanistDifferentialRevisionRef' => 'differential/revision',
'ArcanistExportWorkflow' => 'workflow/export',
'ArcanistFilenameLinter' => 'lint/linter/filename',
'ArcanistGeneratedLinter' => 'lint/linter/generated',
'ArcanistGitAPI' => 'repository/api/git',
'ArcanistGitHookPreReceiveWorkflow' => 'workflow/git-hook-pre-receive',
'ArcanistHelpWorkflow' => 'workflow/help',
+ 'ArcanistLiberateLintEngine' => 'lint/engine/liberate',
+ 'ArcanistLiberateWorkflow' => 'workflow/liberate',
'ArcanistLicenseLinter' => 'lint/linter/license',
'ArcanistLintEngine' => 'lint/engine/base',
'ArcanistLintJSONRenderer' => 'lint/renderer',
'ArcanistLintMessage' => 'lint/message',
'ArcanistLintPatcher' => 'lint/patcher',
'ArcanistLintRenderer' => 'lint/renderer',
'ArcanistLintResult' => 'lint/result',
'ArcanistLintSeverity' => 'lint/severity',
'ArcanistLintSummaryRenderer' => 'lint/renderer',
'ArcanistLintWorkflow' => 'workflow/lint',
'ArcanistLinter' => 'lint/linter/base',
'ArcanistLinterTestCase' => 'lint/linter/base/test',
'ArcanistListWorkflow' => 'workflow/list',
'ArcanistMarkCommittedWorkflow' => 'workflow/mark-committed',
'ArcanistNoEffectException' => 'exception/usage/noeffect',
'ArcanistNoEngineException' => 'exception/usage/noengine',
'ArcanistNoLintLinter' => 'lint/linter/nolint',
'ArcanistNoLintTestCaseMisnamed' => 'lint/linter/nolint/__tests__',
'ArcanistPEP8Linter' => 'lint/linter/pep8',
'ArcanistPatchWorkflow' => 'workflow/patch',
'ArcanistPhutilModuleLinter' => 'lint/linter/phutilmodule',
'ArcanistPhutilTestCase' => 'unit/engine/phutil/testcase',
'ArcanistPhutilTestTerminatedException' => 'unit/engine/phutil/testcase/exception',
'ArcanistRepositoryAPI' => 'repository/api/base',
'ArcanistShellCompleteWorkflow' => 'workflow/shell-complete',
'ArcanistSubversionAPI' => 'repository/api/subversion',
'ArcanistSvnHookPreCommitWorkflow' => 'workflow/svn-hook-pre-commit',
'ArcanistTextLinter' => 'lint/linter/text',
'ArcanistTextLinterTestCase' => 'lint/linter/text/__tests__',
'ArcanistUnitTestResult' => 'unit/result',
'ArcanistUnitWorkflow' => 'workflow/unit',
'ArcanistUsageException' => 'exception/usage',
'ArcanistUserAbortException' => 'exception/usage/userabort',
'ArcanistWorkingCopyIdentity' => 'workingcopyidentity',
'ArcanistXHPASTLinter' => 'lint/linter/xhpast',
'ArcanistXHPASTLinterTestCase' => 'lint/linter/xhpast/__tests__',
'PhutilLintEngine' => 'lint/engine/phutil',
'PhutilModuleRequirements' => 'parser/phutilmodule',
'PhutilUnitTestEngine' => 'unit/engine/phutil',
'PhutilUnitTestEngineTestCase' => 'unit/engine/phutil/__tests__',
'UnitTestableArcanistLintEngine' => 'lint/engine/test',
),
'function' =>
array(
),
'requires_class' =>
array(
'ArcanistAmendWorkflow' => 'ArcanistBaseWorkflow',
'ArcanistApacheLicenseLinter' => 'ArcanistLicenseLinter',
'ArcanistApacheLicenseLinterTestCase' => 'ArcanistLinterTestCase',
'ArcanistCallConduitWorkflow' => 'ArcanistBaseWorkflow',
'ArcanistCommitWorkflow' => 'ArcanistBaseWorkflow',
'ArcanistCoverWorkflow' => 'ArcanistBaseWorkflow',
'ArcanistDiffParserTestCase' => 'ArcanistPhutilTestCase',
'ArcanistDiffWorkflow' => 'ArcanistBaseWorkflow',
'ArcanistExportWorkflow' => 'ArcanistBaseWorkflow',
'ArcanistFilenameLinter' => 'ArcanistLinter',
'ArcanistGeneratedLinter' => 'ArcanistLinter',
'ArcanistGitAPI' => 'ArcanistRepositoryAPI',
'ArcanistGitHookPreReceiveWorkflow' => 'ArcanistBaseWorkflow',
'ArcanistHelpWorkflow' => 'ArcanistBaseWorkflow',
+ 'ArcanistLiberateLintEngine' => 'ArcanistLintEngine',
+ 'ArcanistLiberateWorkflow' => 'ArcanistBaseWorkflow',
'ArcanistLicenseLinter' => 'ArcanistLinter',
'ArcanistLintWorkflow' => 'ArcanistBaseWorkflow',
'ArcanistLinterTestCase' => 'ArcanistPhutilTestCase',
'ArcanistListWorkflow' => 'ArcanistBaseWorkflow',
'ArcanistMarkCommittedWorkflow' => 'ArcanistBaseWorkflow',
'ArcanistNoEffectException' => 'ArcanistUsageException',
'ArcanistNoEngineException' => 'ArcanistUsageException',
'ArcanistNoLintLinter' => 'ArcanistLinter',
'ArcanistNoLintTestCaseMisnamed' => 'ArcanistLinterTestCase',
'ArcanistPEP8Linter' => 'ArcanistLinter',
'ArcanistPatchWorkflow' => 'ArcanistBaseWorkflow',
'ArcanistPhutilModuleLinter' => 'ArcanistLinter',
'ArcanistShellCompleteWorkflow' => 'ArcanistBaseWorkflow',
'ArcanistSubversionAPI' => 'ArcanistRepositoryAPI',
'ArcanistSvnHookPreCommitWorkflow' => 'ArcanistBaseWorkflow',
'ArcanistTextLinter' => 'ArcanistLinter',
'ArcanistTextLinterTestCase' => 'ArcanistLinterTestCase',
'ArcanistUnitWorkflow' => 'ArcanistBaseWorkflow',
'ArcanistUserAbortException' => 'ArcanistUsageException',
'ArcanistXHPASTLinter' => 'ArcanistLinter',
'ArcanistXHPASTLinterTestCase' => 'ArcanistLinterTestCase',
'PhutilLintEngine' => 'ArcanistLintEngine',
'PhutilUnitTestEngine' => 'ArcanistBaseUnitTestEngine',
'PhutilUnitTestEngineTestCase' => 'ArcanistPhutilTestCase',
'UnitTestableArcanistLintEngine' => 'ArcanistLintEngine',
),
'requires_interface' =>
array(
),
));
diff --git a/src/lint/engine/base/ArcanistLintEngine.php b/src/lint/engine/base/ArcanistLintEngine.php
index 472d4350..6cae48f6 100644
--- a/src/lint/engine/base/ArcanistLintEngine.php
+++ b/src/lint/engine/base/ArcanistLintEngine.php
@@ -1,237 +1,237 @@
<?php
/*
* Copyright 2011 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Manages lint execution.
*
* @group lint
*/
abstract class ArcanistLintEngine {
protected $workingCopy;
protected $fileData = array();
protected $charToLine = array();
protected $lineToFirstChar = array();
private $results = array();
private $minimumSeverity = null;
private $changedLines = array();
private $commitHookMode = false;
public function __construct() {
}
public function setWorkingCopy(ArcanistWorkingCopyIdentity $working_copy) {
$this->workingCopy = $working_copy;
return $this;
}
public function getWorkingCopy() {
return $this->workingCopy;
}
public function setPaths($paths) {
$this->paths = $paths;
return $this;
}
public function getPaths() {
return $this->paths;
}
public function setPathChangedLines($path, array $changed) {
$this->changedLines[$path] = array_fill_keys($changed, true);
return $this;
}
public function getPathChangedLines($path) {
return idx($this->changedLines, $path);
}
public function setFileData($data) {
$this->fileData = $data + $this->fileData;
return $this;
}
public function setCommitHookMode($mode) {
$this->commitHookMode = $mode;
return $this;
}
protected function loadData($path) {
if (!isset($this->fileData[$path])) {
$disk_path = $this->getFilePathOnDisk($path);
$this->fileData[$path] = Filesystem::readFile($disk_path);
}
return $this->fileData[$path];
}
public function pathExists($path) {
if ($this->getCommitHookMode()) {
return (idx($this->fileData, $path) !== null);
} else {
$disk_path = $this->getFilePathOnDisk($path);
return Filesystem::pathExists($disk_path);
}
}
public function getFilePathOnDisk($path) {
return Filesystem::resolvePath(
$path,
$this->getWorkingCopy()->getProjectRoot());
}
public function setMinimumSeverity($severity) {
$this->minimumSeverity = $severity;
return $this;
}
public function getCommitHookMode() {
return $this->commitHookMode;
}
public function run() {
$stopped = array();
$linters = $this->buildLinters();
if (!$linters) {
throw new ArcanistNoEffectException("No linters to run.");
}
$have_paths = false;
foreach ($linters as $linter) {
if ($linter->getPaths()) {
$have_paths = true;
break;
}
}
if (!$have_paths) {
throw new ArcanistNoEffectException("No paths are lintable.");
}
foreach ($linters as $linter) {
$linter->setEngine($this);
$paths = $linter->getPaths();
foreach ($paths as $key => $path) {
// Make sure each path has a result generated, even if it is empty
// (i.e., the file has no lint messages).
$result = $this->getResultForPath($path);
if (isset($stopped[$path])) {
unset($paths[$key]);
}
}
$paths = array_values($paths);
if ($paths) {
$linter->willLintPaths($paths);
foreach ($paths as $path) {
$linter->willLintPath($path);
$linter->lintPath($path);
if ($linter->didStopAllLinters()) {
$stopped[$path] = true;
}
}
}
$minimum = $this->minimumSeverity;
foreach ($linter->getLintMessages() as $message) {
if (!ArcanistLintSeverity::isAtLeastAsSevere($message, $minimum)) {
continue;
}
// When a user runs "arc diff", we default to raising only warnings on
// lines they have changed (errors are still raised anywhere in the
// file).
$changed = $this->getPathChangedLines($message->getPath());
if ($changed !== null && !$message->isError()) {
if (empty($changed[$message->getLine()])) {
continue;
}
}
$result = $this->getResultForPath($message->getPath());
$result->addMessage($message);
}
}
foreach ($this->results as $path => $result) {
$disk_path = $this->getFilePathOnDisk($path);
$result->setFilePathOnDisk($disk_path);
if (isset($this->fileData[$path])) {
$result->setData($this->fileData[$path]);
} else if ($disk_path && Filesystem::pathExists($disk_path)) {
// TODO: this may cause us to, e.g., load a large binary when we only
// raised an error about its filename. We could refine this by looking
// through the lint messages and doing this load only if any of them
// have original/replacement text or something like that.
try {
- $this->fileData[$path] = Filesystem::readFile($path);
+ $this->fileData[$path] = Filesystem::readFile($disk_path);
$result->setData($this->fileData[$path]);
} catch (FilesystemException $ex) {
// Ignore this, it's noncritical that we access this data and it
// might be unreadable or a directory or whatever else for plenty
// of legitimate reasons.
}
}
}
return $this->results;
}
abstract protected function buildLinters();
private function getResultForPath($path) {
if (empty($this->results[$path])) {
$result = new ArcanistLintResult();
$result->setPath($path);
$this->results[$path] = $result;
}
return $this->results[$path];
}
public function getLineAndCharFromOffset($path, $offset) {
if (!isset($this->charToLine[$path])) {
$char_to_line = array();
$line_to_first_char = array();
$lines = explode("\n", $this->loadData($path));
$line_number = 0;
$line_start = 0;
foreach ($lines as $line) {
$len = strlen($line) + 1; // Account for "\n".
$line_to_first_char[] = $line_start;
$line_start += $len;
for ($ii = 0; $ii < $len; $ii++) {
$char_to_line[] = $line_number;
}
$line_number++;
}
$this->charToLine[$path] = $char_to_line;
$this->lineToFirstChar[$path] = $line_to_first_char;
}
$line = $this->charToLine[$path][$offset];
$char = $offset - $this->lineToFirstChar[$path][$line];
return array($line, $char);
}
}
diff --git a/src/lint/engine/liberate/ArcanistLiberateLintEngine.php b/src/lint/engine/liberate/ArcanistLiberateLintEngine.php
new file mode 100644
index 00000000..111ad43a
--- /dev/null
+++ b/src/lint/engine/liberate/ArcanistLiberateLintEngine.php
@@ -0,0 +1,38 @@
+<?php
+
+/*
+ * Copyright 2011 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Lint engine which powers 'arc liberate'.
+ *
+ * @group linter
+ */
+class ArcanistLiberateLintEngine extends ArcanistLintEngine {
+
+ public function buildLinters() {
+ // We just run the module linter, 'arc liberate' is only interested in
+ // building __init__.php files.
+
+ $module_linter = new ArcanistPhutilModuleLinter();
+ foreach ($this->getPaths() as $path) {
+ $module_linter->addPath($path);
+ }
+
+ return array($module_linter);
+ }
+
+}
diff --git a/src/lint/engine/liberate/__init__.php b/src/lint/engine/liberate/__init__.php
new file mode 100644
index 00000000..0fe14a7b
--- /dev/null
+++ b/src/lint/engine/liberate/__init__.php
@@ -0,0 +1,13 @@
+<?php
+/**
+ * This file is automatically generated. Lint this module to rebuild it.
+ * @generated
+ */
+
+
+
+phutil_require_module('arcanist', 'lint/engine/base');
+phutil_require_module('arcanist', 'lint/linter/phutilmodule');
+
+
+phutil_require_source('ArcanistLiberateLintEngine.php');
diff --git a/src/lint/linter/phutilmodule/ArcanistPhutilModuleLinter.php b/src/lint/linter/phutilmodule/ArcanistPhutilModuleLinter.php
index 62f469e0..b8d56980 100644
--- a/src/lint/linter/phutilmodule/ArcanistPhutilModuleLinter.php
+++ b/src/lint/linter/phutilmodule/ArcanistPhutilModuleLinter.php
@@ -1,525 +1,529 @@
<?php
/*
* Copyright 2011 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Applies rules for modules in Phutil libraries.
*
* @group linter
*/
class ArcanistPhutilModuleLinter extends ArcanistLinter {
const LINT_UNDECLARED_CLASS = 1;
const LINT_UNDECLARED_FUNCTION = 2;
const LINT_UNDECLARED_INTERFACE = 3;
const LINT_UNDECLARED_SOURCE = 4;
const LINT_UNUSED_MODULE = 5;
const LINT_UNUSED_SOURCE = 6;
const LINT_INIT_REBUILD = 7;
const LINT_UNKNOWN_CLASS = 8;
const LINT_UNKNOWN_FUNCTION = 9;
const LINT_ANALYZER_SIGNATURE = 100;
const LINT_ANALYZER_DYNAMIC = 101;
const LINT_ANALYZER_NO_INIT = 102;
const LINT_ANALYZER_MULTIPLE_CLASSES = 103;
public function getLintNameMap() {
return array(
self::LINT_UNDECLARED_CLASS => 'Use of Undeclared Class',
self::LINT_UNDECLARED_FUNCTION => 'Use of Undeclared Function',
self::LINT_UNDECLARED_INTERFACE => 'Use of Undeclared Interface',
self::LINT_UNDECLARED_SOURCE => 'Use of Nonexistent File',
self::LINT_UNUSED_SOURCE => 'Unused Source',
self::LINT_UNUSED_MODULE => 'Unused Module',
self::LINT_INIT_REBUILD => 'Rebuilt __init__.php File',
self::LINT_UNKNOWN_CLASS => 'Unknown Class',
self::LINT_UNKNOWN_FUNCTION => 'Unknown Function',
self::LINT_ANALYZER_SIGNATURE => 'Analyzer: Bad Call Signature',
self::LINT_ANALYZER_DYNAMIC => 'Analyzer: Dynamic Dependency',
self::LINT_ANALYZER_NO_INIT => 'Analyzer: No __init__.php File',
self::LINT_ANALYZER_MULTIPLE_CLASSES
=> 'Analyzer: File Declares Multiple Classes',
);
}
public function getLinterName() {
return 'PHU';
}
public function getLintSeverityMap() {
return array(
self::LINT_ANALYZER_DYNAMIC => ArcanistLintSeverity::SEVERITY_WARNING,
);
}
private $moduleInfo = array();
private $unknownClasses = array();
private $unknownFunctions = array();
private function setModuleInfo($key, array $info) {
$this->moduleInfo[$key] = $info;
}
private function getModulePathOnDisk($key) {
$info = $this->moduleInfo[$key];
return $info['root'].'/'.$info['module'];
}
private function getModuleDisplayName($key) {
$info = $this->moduleInfo[$key];
return $info['module'];
}
private function isPhutilLibraryMetadata($path) {
$file = basename($path);
return !strncmp('__phutil_library_', $file, strlen('__phutil_library_'));
}
public function willLintPaths(array $paths) {
if ($paths) {
if (!xhpast_is_available()) {
throw new Exception(xhpast_get_build_instructions());
}
}
$modules = array();
$moduleinfo = array();
$project_root = $this->getEngine()->getWorkingCopy()->getProjectRoot();
foreach ($paths as $path) {
$absolute_path = $project_root.'/'.$path;
$library_root = phutil_get_library_root_for_path($absolute_path);
if (!$library_root) {
continue;
}
if ($this->isPhutilLibraryMetadata($path)) {
continue;
}
$library_name = phutil_get_library_name_for_root($library_root);
if (!is_dir($path)) {
$path = dirname($path);
}
$path = Filesystem::resolvePath(
$path,
$project_root);
if ($path == $library_root) {
continue;
}
$module_name = Filesystem::readablePath($path, $library_root);
$module_key = $library_name.':'.$module_name;
if (empty($modules[$module_key])) {
$modules[$module_key] = $module_key;
$this->setModuleInfo($module_key, array(
'library' => $library_name,
'root' => $library_root,
'module' => $module_name,
));
}
}
if (!$modules) {
return;
}
$modules = array_keys($modules);
$arc_root = phutil_get_library_root('arcanist');
$bin = dirname($arc_root).'/scripts/phutil_analyzer.php';
$futures = array();
foreach ($modules as $mkey => $key) {
$disk_path = $this->getModulePathOnDisk($key);
if (Filesystem::pathExists($disk_path)) {
$futures[$key] = new ExecFuture(
'%s %s',
$bin,
$disk_path);
} else {
// This can occur in git when you add a module in HEAD and then remove
// it in unstaged changes in the working copy. Just ignore it.
unset($modules[$mkey]);
}
}
$requirements = array();
- foreach (Futures($futures) as $key => $future) {
+ foreach (Futures($futures)->limit(16) as $key => $future) {
$requirements[$key] = $future->resolveJSON();
}
$dependencies = array();
$futures = array();
foreach ($requirements as $key => $requirement) {
foreach ($requirement['messages'] as $message) {
list($where, $text, $code, $description) = $message;
if ($where) {
$where = array($where);
}
$this->raiseLintInModule(
$key,
$code,
$description,
$where,
$text);
}
foreach ($requirement['requires']['module'] as $req_module => $where) {
if (isset($requirements[$req_module])) {
$dependencies[$req_module] = $requirements[$req_module];
} else {
list($library_name, $module_name) = explode(':', $req_module);
$library_root = phutil_get_library_root($library_name);
$this->setModuleInfo($req_module, array(
'library' => $library_name,
'root' => $library_root,
'module' => $module_name,
));
$disk_path = $this->getModulePathOnDisk($req_module);
if (Filesystem::pathExists($disk_path)) {
$futures[$req_module] = new ExecFuture(
'%s %s',
$bin,
$disk_path);
} else {
$dependencies[$req_module] = array();
}
}
}
}
- foreach (Futures($futures) as $key => $future) {
+ foreach (Futures($futures)->limit(16) as $key => $future) {
$dependencies[$key] = $future->resolveJSON();
}
foreach ($requirements as $key => $spec) {
$deps = array_intersect_key(
$dependencies,
$spec['requires']['module']);
$this->lintModule($key, $spec, $deps);
}
}
private function lintModule($key, $spec, $deps) {
$resolvable = array();
$need_classes = array();
$need_functions = array();
$drop_modules = array();
$used = array();
static $types = array(
'class' => self::LINT_UNDECLARED_CLASS,
'interface' => self::LINT_UNDECLARED_INTERFACE,
'function' => self::LINT_UNDECLARED_FUNCTION,
);
foreach ($types as $type => $lint_code) {
foreach ($spec['requires'][$type] as $name => $places) {
$declared = $this->checkDependency(
$type,
$name,
$deps);
if (!$declared) {
$module = $this->getModuleDisplayName($key);
$message = $this->raiseLintInModule(
$key,
$lint_code,
"Module '{$module}' uses {$type} '{$name}' but does not include ".
"any module which declares it.",
$places);
if ($type == 'class' || $type == 'interface') {
$loader = new PhutilSymbolLoader();
$loader->setType($type);
$loader->setName($name);
$symbols = $loader->selectSymbolsWithoutLoading();
if ($symbols) {
$class_spec = reset($symbols);
try {
$loader->selectAndLoadSymbols();
$loaded = true;
} catch (PhutilMissingSymbolException $ex) {
$loaded = false;
} catch (PhutilBootloaderException $ex) {
$loaded = false;
}
if ($loaded) {
$resolvable[] = $message;
$need_classes[$name] = $class_spec;
} else {
if (empty($this->unknownClasses[$name])) {
$this->unknownClasses[$name] = true;
$library = $class_spec['library'];
$this->raiseLintInModule(
$key,
self::LINT_UNKNOWN_CLASS,
"Class '{$name}' exists in the library map for library ".
"'{$library}', but could not be loaded. You may need to ".
"rebuild the library map.",
$places);
}
}
} else {
if (empty($this->unknownClasses[$name])) {
$this->unknownClasses[$name] = true;
$this->raiseLintInModule(
$key,
self::LINT_UNKNOWN_CLASS,
"Class '{$name}' could not be found in any known library. ".
"You may need to rebuild the map for the library which ".
"contains it.",
$places);
}
}
} else {
$loader = new PhutilSymbolLoader();
$loader->setType($type);
$loader->setName($name);
$symbols = $loader->selectSymbolsWithoutLoading();
if ($symbols) {
$func_spec = reset($symbols);
try {
$loader->selectAndLoadSymbols();
$loaded = true;
} catch (PhutilMissingSymbolException $ex) {
$loaded = false;
} catch (PhutilBootloaderException $ex) {
$loaded = false;
}
if ($loaded) {
$resolvable[] = $message;
$need_functions[$name] = $func_spec;
} else {
if (empty($this->unknownFunctions[$name])) {
$this->unknownFunctions[$name] = true;
$library = $func_spec['library'];
$this->raiseLintInModule(
$key,
self::LINT_UNKNOWN_FUNCTION,
"Function '{$name}' exists in the library map for library ".
"'{$library}', but could not be loaded. You may need to ".
"rebuild the library map.",
$places);
}
}
} else {
if (empty($this->unknownFunctions[$name])) {
$this->unknownFunctions[$name] = true;
$this->raiseLintInModule(
$key,
self::LINT_UNKNOWN_FUNCTION,
"Function '{$name}' could not be found in any known ".
"library. You may need to rebuild the map for the library ".
"which contains it.",
$places);
}
}
}
}
$used[$declared] = true;
}
}
$unused = array_diff_key($deps, $used);
foreach ($unused as $unused_module_key => $ignored) {
$module = $this->getModuleDisplayName($key);
$unused_module = $this->getModuleDisplayName($unused_module_key);
$resolvable[] = $this->raiseLintInModule(
$key,
self::LINT_UNUSED_MODULE,
"Module '{$module}' requires module '{$unused_module}' but does not ".
"use anything it declares.",
$spec['requires']['module'][$unused_module_key]);
$drop_modules[] = $unused_module_key;
}
foreach ($spec['requires']['source'] as $file => $where) {
if (empty($spec['declares']['source'][$file])) {
$module = $this->getModuleDisplayName($key);
$resolvable[] = $this->raiseLintInModule(
$key,
self::LINT_UNDECLARED_SOURCE,
"Module '{$module}' requires source '{$file}', but it does not ".
"exist.",
$where);
}
}
foreach ($spec['declares']['source'] as $file => $ignored) {
if (empty($spec['requires']['source'][$file])) {
$module = $this->getModuleDisplayName($key);
$resolvable[] = $this->raiseLintInModule(
$key,
self::LINT_UNUSED_SOURCE,
"Module '{$module}' does not include source file '{$file}'.",
null);
}
}
if ($resolvable) {
$new_file = $this->buildNewModuleInit(
$key,
$spec,
$need_classes,
$need_functions,
$drop_modules);
$init_path = $this->getModulePathOnDisk($key).'/__init__.php';
- $try_path = Filesystem::readablePath($init_path);
- if (Filesystem::pathExists($try_path)) {
+
+ $root = $this->getEngine()->getWorkingCopy()->getProjectRoot();
+ $try_path = Filesystem::readablePath($init_path, $root);
+ $full_path = Filesystem::resolvePath($try_path, $root);
+ if (Filesystem::pathExists($full_path)) {
$init_path = $try_path;
- $old_file = Filesystem::readFile($init_path);
+ $old_file = Filesystem::readFile($full_path);
} else {
$old_file = '';
}
+
$this->willLintPath($init_path);
$message = $this->raiseLintAtOffset(
null,
self::LINT_INIT_REBUILD,
"This generated phutil '__init__.php' file is suggested to address ".
"lint problems with static dependencies in the module.",
$old_file,
$new_file);
$message->setDependentMessages($resolvable);
foreach ($resolvable as $message) {
$message->setObsolete(true);
}
$message->setGenerateFile(true);
}
}
private function buildNewModuleInit(
$key,
$spec,
$need_classes,
$need_functions,
$drop_modules) {
$init = array();
$init[] = '<?php';
$at = '@';
$init[] = <<<EOHEADER
/**
* This file is automatically generated. Lint this module to rebuild it.
* {$at}generated
*/
EOHEADER;
$init[] = null;
$modules = $spec['requires']['module'];
foreach ($drop_modules as $drop) {
unset($modules[$drop]);
}
foreach ($need_classes as $need => $class_spec) {
$modules[$class_spec['library'].':'.$class_spec['module']] = true;
}
foreach ($need_functions as $need => $func_spec) {
$modules[$func_spec['library'].':'.$func_spec['module']] = true;
}
ksort($modules);
$last = null;
foreach ($modules as $module_key => $ignored) {
if (is_array($ignored)) {
$in_init = false;
$in_file = false;
foreach ($ignored as $where) {
list($file, $line) = explode(':', $where);
if ($file == '__init__.php') {
$in_init = true;
} else {
$in_file = true;
}
}
if ($in_file && !$in_init) {
// If this is a runtime include, don't try to put it in the
// __init__ file.
continue;
}
}
list($library, $module_name) = explode(':', $module_key);
if ($last != $library) {
$last = $library;
if ($last != null) {
$init[] = null;
}
}
$library = "'".addcslashes($library, "'\\")."'";
$module_name = "'".addcslashes($module_name, "'\\")."'";
$init[] = "phutil_require_module({$library}, {$module_name});";
}
$init[] = null;
$init[] = null;
$files = array_keys($spec['declares']['source']);
sort($files);
foreach ($files as $file) {
$file = "'".addcslashes($file, "'\\")."'";
$init[] = "phutil_require_source({$file});";
}
$init[] = null;
return implode("\n", $init);
}
private function checkDependency($type, $name, $deps) {
foreach ($deps as $key => $dep) {
if (isset($dep['declares'][$type][$name])) {
return $key;
}
}
return false;
}
public function raiseLintInModule($key, $code, $desc, $places, $text = null) {
if ($places) {
foreach ($places as $place) {
list($file, $offset) = explode(':', $place);
$this->willLintPath(
Filesystem::readablePath(
$this->getModulePathOnDisk($key).'/'.$file,
$this->getEngine()->getWorkingCopy()->getProjectRoot()));
return $this->raiseLintAtOffset(
$offset,
$code,
$desc,
$text);
}
} else {
$this->willLintPath($this->getModuleDisplayName($key));
return $this->raiseLintAtPath(
$code,
$desc);
}
}
public function lintPath($path) {
return;
}
}
diff --git a/src/workflow/help/ArcanistHelpWorkflow.php b/src/workflow/help/ArcanistHelpWorkflow.php
index 5d5d7760..d4421e82 100644
--- a/src/workflow/help/ArcanistHelpWorkflow.php
+++ b/src/workflow/help/ArcanistHelpWorkflow.php
@@ -1,177 +1,180 @@
<?php
/*
* Copyright 2011 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Seduces the reader with majestic prose.
*
* @group workflow
*/
class ArcanistHelpWorkflow extends ArcanistBaseWorkflow {
public function getCommandHelp() {
return phutil_console_format(<<<EOTEXT
**help** [__command__]
Supports: english
Shows this help. With __command__, shows help about a specific
command.
EOTEXT
);
}
public function getArguments() {
return array(
'*' => 'command',
);
}
public function run() {
$arc_config = $this->getArcanistConfiguration();
$workflows = $arc_config->buildAllWorkflows();
ksort($workflows);
$target = null;
if ($this->getArgument('command')) {
$target = reset($this->getArgument('command'));
if (empty($workflows[$target])) {
throw new ArcanistUsageException(
"Unrecognized command '{$target}'. Try 'arc help'.");
}
}
$cmdref = array();
foreach ($workflows as $command => $workflow) {
if ($target && $target != $command) {
continue;
}
$optref = array();
$arguments = $workflow->getArguments();
$config_arguments = $arc_config->getCustomArgumentsForCommand($command);
// This juggling is to put the extension arguments after the normal
// arguments, and make sure the normal arguments aren't overwritten.
ksort($arguments);
ksort($config_arguments);
foreach ($config_arguments as $argument => $spec) {
if (empty($arguments[$argument])) {
$arguments[$argument] = $spec;
}
}
foreach ($arguments as $argument => $spec) {
if ($argument == '*') {
continue;
}
+ if (!empty($spec['hide'])) {
+ continue;
+ }
if (isset($spec['param'])) {
if (isset($spec['short'])) {
$optref[] = phutil_console_format(
" __--%s__ __%s__, __-%s__ __%s__",
$argument,
$spec['param'],
$spec['short'],
$spec['param']);
} else {
$optref[] = phutil_console_format(
" __--%s__ __%s__",
$argument,
$spec['param']);
}
} else {
if (isset($spec['short'])) {
$optref[] = phutil_console_format(
" __--%s__, __-%s__",
$argument,
$spec['short']);
} else {
$optref[] = phutil_console_format(
" __--%s__",
$argument);
}
}
if (isset($config_arguments[$argument])) {
$optref[] = " (This is a custom option for this ".
"project.)";
}
if (isset($spec['supports'])) {
$optref[] = " Supports: ".
implode(', ', $spec['supports']);
}
if (isset($spec['help'])) {
$docs = $spec['help'];
} else {
$docs = 'This option is not documented.';
}
$docs = phutil_console_wrap($docs, 14);
$optref[] = " {$docs}\n";
}
if ($optref) {
$optref = implode("\n", $optref);
$optref = "\n\n".$optref;
} else {
$optref = "\n";
}
$cmdref[] = $workflow->getCommandHelp().$optref;
}
$cmdref = implode("\n\n", $cmdref);
if ($target) {
echo "\n".$cmdref."\n";
return;
}
$self = 'arc';
echo phutil_console_format(<<<EOTEXT
**NAME**
**{$self}** - arcanist, a code review and revision management utility
**SYNOPSIS**
**{$self}** __command__ [__options__] [__args__]
This help file provides a detailed command reference.
**COMMAND REFERENCE**
{$cmdref}
**OPTION REFERENCE**
__--trace__
Debugging command. Shows underlying commands as they are executed,
and full stack traces when exceptions are thrown.
__--no-ansi__
Output in plain ASCII text only, without color or style.
__--load-phutil-library=/path/to/library__
Ignore libraries listed in .arcconfig and explicitly load specified
libraries instead. Mostly useful for Arcanist development.
__--conduit-uri=...__
Ignore configured Conduit URI and use an explicit one instead. Mostly
useful for Arcanist development.
EOTEXT
);
}
}
diff --git a/src/workflow/liberate/ArcanistLiberateWorkflow.php b/src/workflow/liberate/ArcanistLiberateWorkflow.php
new file mode 100644
index 00000000..645cee4c
--- /dev/null
+++ b/src/workflow/liberate/ArcanistLiberateWorkflow.php
@@ -0,0 +1,336 @@
+<?php
+
+/*
+ * Copyright 2011 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Create and update libphutil libraries.
+ *
+ * This workflow is unusual and involves reexecuting 'arc liberate' as a
+ * subprocess with "--remap" and "--verify". This is because there is no way
+ * to unload or reload a library, so every process is stuck with the library
+ * definition it had when it first loaded. This is normally fine, but
+ * problematic in this case because 'arc liberate' modifies library definitions.
+ *
+ * @group workflow
+ */
+class ArcanistLiberateWorkflow extends ArcanistBaseWorkflow {
+
+ public function getCommandHelp() {
+ return phutil_console_format(<<<EOTEXT
+ **liberate** [__path__]
+ Supports: libphutil
+ Create or update a libphutil library, generating required metadata
+ files like __init__.php.
+EOTEXT
+ );
+ }
+
+ public function getArguments() {
+ return array(
+ 'all' => array(
+ 'help' =>
+ "Drop the module cache before liberating. This will completely ".
+ "reanalyze the entire library. Thorough, but slow!",
+ ),
+ 'force-update' => array(
+ 'help' =>
+ "Force the library map to be updated, even in the presence of ".
+ "lint errors.",
+ ),
+ 'remap' => array(
+ 'hide' => true,
+ 'help' =>
+ "Internal. Run the remap step of liberation. You do not need to ".
+ "run this unless you are debugging the workflow.",
+ ),
+ 'verify' => array(
+ 'hide' => true,
+ 'help' =>
+ "Internal. Run the verify step of liberation. You do not need to ".
+ "run this unless you are debugging the workflow.",
+ ),
+ '*' => 'argv',
+ );
+ }
+
+ public function run() {
+ $argv = $this->getArgument('argv');
+ if (count($argv) > 1) {
+ throw new ArcanistUsageException(
+ "Provide only one path to 'arc liberate'. The path should be a ".
+ "directory where you want to create or update a libphutil library.");
+ } else if (count($argv) == 0) {
+ $path = getcwd();
+ } else {
+ $path = reset($argv);
+ }
+
+ $is_remap = $this->getArgument('remap');
+ $is_verify = $this->getArgument('verify');
+
+ $path = Filesystem::resolvePath($path);
+
+ if (Filesystem::pathExists($path) && is_dir($path)) {
+ $init = id(new FileFinder($path))
+ ->withPath('*/__phutil_library_init__.php')
+ ->find();
+ } else {
+ $init = null;
+ }
+
+ if ($init) {
+ if (count($init) > 1) {
+ throw new ArcanistUsageException(
+ "Specified directory contains more than one libphutil library. Use ".
+ "a more specific path.");
+ }
+ $path = Filesystem::resolvePath(dirname(reset($init)), $path);
+ } else {
+ $found = false;
+ foreach (Filesystem::walkToRoot($path) as $dir) {
+ if (Filesystem::pathExists($dir.'/__phutil_library_init__.php')) {
+ $path = $dir;
+ break;
+ }
+ }
+ if (!$found) {
+ echo "No library currently exists at that path...\n";
+ $this->liberateCreateDirectory($path);
+ $this->liberateCreateLibrary($path);
+ }
+ }
+
+ if ($this->getArgument('remap')) {
+ return $this->liberateRunRemap($path);
+ }
+
+ if ($this->getArgument('verify')) {
+ return $this->liberateRunVerify($path);
+ }
+
+ $readable = Filesystem::readablePath($path);
+ echo "Using library root at '{$readable}'...\n";
+
+ if ($this->getArgument('all')) {
+ echo "Dropping module cache...\n";
+ Filesystem::remove($path.'/.phutil_module_cache');
+ }
+
+ echo "Mapping library...\n";
+
+ // Force a rebuild of the library map before running lint. The remap
+ // operation will load the map before regenerating it, so if a class has
+ // been renamed (say, from OldClass to NewClass) this rebuild will
+ // cause the initial remap to see NewClass and correctly remove includes
+ // caused by use of OldClass.
+ $this->liberateGetChangedPaths($path);
+
+ $arc_bin = $this->getScriptPath('bin/arc');
+
+ do {
+ $future = new ExecFuture(
+ '%s liberate --remap -- %s',
+ $arc_bin,
+ $path);
+ $wrote = $future->resolveJSON();
+ foreach ($wrote as $wrote_path) {
+ echo "Updated '{$wrote_path}'...\n";
+ }
+ } while ($wrote);
+
+ echo "Verifying library...\n";
+
+ $err = 0;
+ $cmd = csprintf('%s liberate --verify -- %s', $arc_bin, $path);
+ passthru($cmd, $err);
+
+ $do_update = (!$err || $this->getArgument('force-update'));
+
+ if ($do_update) {
+ echo "Finalizing library map...\n";
+ execx('%s %s', $this->getPhutilMapperLocation(), $path);
+ }
+
+ if ($err) {
+ if ($do_update) {
+ echo phutil_console_format(
+ "<bg:yellow>** WARNING **</bg> Library update forced, but lint ".
+ "failures remain.\n");
+ } else {
+ echo phutil_console_format(
+ "<bg:red>** UNRESOLVED LINT ERRORS **</bg> This library has ".
+ "unresolved lint failures. The library map was not updated. Use ".
+ "--force-update to force an update.\n");
+ }
+ } else {
+ echo phutil_console_format(
+ "<bg:green>** OKAY **</bg> Library updated.\n");
+ }
+
+ return $err;
+ }
+
+ private function liberateLintModules($path, array $changed) {
+ $engine = $this->liberateBuildLintEngine($path, $changed);
+ if ($engine) {
+ return $engine->run();
+ } else {
+ return array();
+ }
+ }
+
+ private function liberateWritePatches(array $results) {
+ $wrote = array();
+
+ foreach ($results as $result) {
+ if ($result->isPatchable()) {
+ $patcher = ArcanistLintPatcher::newFromArcanistLintResult($result);
+ $patcher->writePatchToDisk();
+ $wrote[] = $result->getPath();
+ }
+ }
+
+ return $wrote;
+ }
+
+ private function liberateBuildLintEngine($path, array $changed) {
+ $lint_map = array();
+ foreach ($changed as $module) {
+ $module_path = $path.'/'.$module;
+ $files = Filesystem::listDirectory($module_path);
+ $lint_map[$module] = $files;
+ }
+
+ $working_copy = ArcanistWorkingCopyIdentity::newFromRootAndConfigFile(
+ $path,
+ json_encode(
+ array(
+ 'project_id' => '__arcliberate__',
+ )),
+ 'arc liberate');
+
+ $engine = new ArcanistLiberateLintEngine();
+ $engine->setWorkingCopy($working_copy);
+
+ $lint_paths = array();
+ foreach ($lint_map as $module => $files) {
+ foreach ($files as $file) {
+ $lint_paths[] = $module.'/'.$file;
+ }
+ }
+
+ if (!$lint_paths) {
+ return null;
+ }
+
+ $engine->setPaths($lint_paths);
+ $engine->setMinimumSeverity(ArcanistLintSeverity::SEVERITY_ERROR);
+
+ return $engine;
+ }
+
+
+ private function liberateCreateDirectory($path) {
+ if (Filesystem::pathExists($path)) {
+ if (!is_dir($path)) {
+ throw new ArcanistUsageException(
+ "Provide a directory to create or update a libphutil library in.");
+ }
+ return;
+ }
+
+ echo "The directory '{$path}' does not exist.";
+ if (!phutil_console_confirm('Do you want to create it?')) {
+ throw new ArcanistUsageException("Cancelled.");
+ }
+
+ execx('mkdir -p %s', $path);
+ }
+
+ private function liberateCreateLibrary($path) {
+ $init_path = $path.'/__phutil_library_init__.php';
+ if (Filesystem::pathExists($init_path)) {
+ return;
+ }
+
+ echo "Creating new libphutil library in '{$path}'.\n";
+ echo "Choose a name for the new library.\n";
+ do {
+ $name = phutil_console_prompt('What do you want to name this library?');
+ if (preg_match('/^[a-z]+$/', $name)) {
+ break;
+ } else {
+ echo "Library name should contain only lowercase letters.\n";
+ }
+ } while (true);
+
+ $template =
+ "<?php\n\n".
+ "phutil_register_library('{$name}', __FILE__);\n";
+
+ echo "Writing '__phutil_library_init__.php' to '{$init_path}'...\n";
+ Filesystem::writeFile($init_path, $template);
+ }
+
+ private function liberateGetChangedPaths($path) {
+ $mapper = $this->getPhutilMapperLocation();
+ $future = new ExecFuture('%s %s --find-paths-for-liberate', $mapper, $path);
+ return $future->resolveJSON();
+ }
+
+ private function getScriptPath($script) {
+ $root = dirname(phutil_get_library_root('arcanist'));
+ return $root.'/'.$script;
+ }
+
+ private function getPhutilMapperLocation() {
+ return $this->getScriptPath('scripts/phutil_mapper.php');
+ }
+
+ private function liberateRunRemap($path) {
+ phutil_load_library($path);
+
+ $paths = $this->liberateGetChangedPaths($path);
+ $results = $this->liberateLintModules($path, $paths);
+ $wrote = $this->liberateWritePatches($results);
+
+ echo json_encode($wrote, true);
+
+ return 0;
+ }
+
+ private function liberateRunVerify($path) {
+ phutil_load_library($path);
+
+ $paths = $this->liberateGetChangedPaths($path);
+ $results = $this->liberateLintModules($path, $paths);
+
+ $renderer = new ArcanistLintRenderer();
+
+ $unresolved = false;
+ foreach ($results as $result) {
+ foreach ($result->getMessages() as $message) {
+ echo $renderer->renderLintResult($result);
+ $unresolved = true;
+ break;
+ }
+ }
+
+ return (int)$unresolved;
+ }
+
+}
diff --git a/src/workflow/liberate/__init__.php b/src/workflow/liberate/__init__.php
new file mode 100644
index 00000000..aab0b048
--- /dev/null
+++ b/src/workflow/liberate/__init__.php
@@ -0,0 +1,26 @@
+<?php
+/**
+ * This file is automatically generated. Lint this module to rebuild it.
+ * @generated
+ */
+
+
+
+phutil_require_module('arcanist', 'exception/usage');
+phutil_require_module('arcanist', 'lint/engine/liberate');
+phutil_require_module('arcanist', 'lint/patcher');
+phutil_require_module('arcanist', 'lint/renderer');
+phutil_require_module('arcanist', 'lint/severity');
+phutil_require_module('arcanist', 'workflow/base');
+phutil_require_module('arcanist', 'workingcopyidentity');
+
+phutil_require_module('phutil', 'console');
+phutil_require_module('phutil', 'filesystem');
+phutil_require_module('phutil', 'filesystem/filefinder');
+phutil_require_module('phutil', 'future/exec');
+phutil_require_module('phutil', 'moduleutils');
+phutil_require_module('phutil', 'utils');
+phutil_require_module('phutil', 'xsprintf/csprintf');
+
+
+phutil_require_source('ArcanistLiberateWorkflow.php');

File Metadata

Mime Type
text/x-diff
Expires
Mon, Dec 23, 13:17 (1 d, 19 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
556919
Default Alt Text
(67 KB)

Event Timeline