1
0
Fork 0
mirror of https://we.phorge.it/source/arcanist.git synced 2024-11-26 00:32:41 +01:00

Allow caching lint results

Summary:
There is one big decision: How to get linters version.
I've created `getCacheVersion()` which is supposed to be bumped every time engine or any linter is changed.
This is not very nice but the other alternative (detect this automatically) seems worse:

- The engine may be outside repo and may or may not be under version control so getting its version through something like `git log` may not be even possible.
- If it is in the same repo then every rebase will obsolete the whole cache.

Even though bumping the version manually is PITA I still think it's a better solution.

Test Plan:
  $ time arc lint --cache 1
  # verified file
  $ arc arc lint --cache 1
  # added some debug output to see the cached results
  # also observed better time (.57 s instead of 2.19 s)

Reviewers: epriestley

Reviewed By: epriestley

CC: aran, Korvin

Maniphest Tasks: T2036

Differential Revision: https://secure.phabricator.com/D4006
This commit is contained in:
vrana 2012-11-20 16:43:10 -08:00
parent 0cf8ef02d8
commit 3cfd58c29c
4 changed files with 85 additions and 18 deletions

View file

@ -48,6 +48,8 @@ final class ArcanistLintMessage {
'severity' => $this->getSeverity(),
'name' => $this->getName(),
'description' => $this->getDescription(),
'original' => $this->getOriginalText(),
'replacement' => $this->getReplacementText(),
);
}

View file

@ -165,22 +165,6 @@ abstract class ArcanistLintEngine {
$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.");
}
$exceptions = array();
foreach ($linters as $linter_name => $linter) {
try {
@ -251,6 +235,22 @@ abstract class ArcanistLintEngine {
}
}
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.");
}
if ($exceptions) {
throw new PhutilAggregateException('Some linters failed:', $exceptions);
}
@ -291,7 +291,7 @@ abstract class ArcanistLintEngine {
return false;
}
private function getResultForPath($path) {
public function getResultForPath($path) {
if (empty($this->results[$path])) {
$result = new ArcanistLintResult();
$result->setPath($path);
@ -336,6 +336,10 @@ abstract class ArcanistLintEngine {
return $this;
}
public function getCacheVersion() {
return 0;
}
protected function getPEP8WithTextOptions() {
// E101 is subset of TXT2 (Tab Literal).
// E501 is same as TXT3 (Line Too Long).

View file

@ -354,6 +354,13 @@ EOTEXT
'Run lint and unit tests on background. '.
'"0" to disable, "1" to enable (default).',
),
'cache' => array(
'param' => 'bool',
'help' => "0 to disable lint cache (default), 1 to enable.",
'passthru' => array(
'lint' => true,
),
),
'*' => 'paths',
);

View file

@ -120,6 +120,10 @@ EOTEXT
array_keys(ArcanistLintSeverity::getLintSeverities())).
"'. Defaults to '".self::DEFAULT_SEVERITY."'.",
),
'cache' => array(
'param' => 'bool',
'help' => "0 to disable cache (default), 1 to enable.",
),
'*' => 'paths',
);
}
@ -132,6 +136,15 @@ EOTEXT
return true;
}
private function getCacheKey() {
return implode("\n", array(
get_class($this->engine),
$this->getArgument('severity', self::DEFAULT_SEVERITY),
$this->getArgument('lintall'),
$this->engine->getCacheVersion(),
));
}
public function run() {
$working_copy = $this->getWorkingCopy();
@ -176,6 +189,25 @@ EOTEXT
$engine->setMinimumSeverity(
$this->getArgument('severity', self::DEFAULT_SEVERITY));
$cached = false;
if ($this->getArgument('cache')) {
$paths = array_combine($paths, $paths);
$cache = $this->readScratchJSONFile('lint-cache.json');
$cache = idx($cache, $this->getCacheKey(), array());
foreach ($cache as $path => $messages) {
$messages = idx($messages, md5_file($engine->getFilePathOnDisk($path)));
// TODO: Some linters work with the whole directory.
if ($messages !== null) {
foreach ($messages as $message) {
$engine->getResultForPath($path)->addMessage(
ArcanistLintMessage::newFromDictionary($message));
}
$cached = true;
unset($paths[$path]);
}
}
}
// Propagate information about which lines changed to the lint engine.
// This is used so that the lint engine can drop warning messages
// concerning lines that weren't in the change.
@ -201,7 +233,11 @@ EOTEXT
try {
$engine->run();
} catch (Exception $ex) {
$failed = $ex;
if ($ex instanceof ArcanistNoEffectException && $cached) {
// Swallow.
} else {
$failed = $ex;
}
}
$results = $engine->getResults();
@ -365,6 +401,24 @@ EOTEXT
}
$this->unresolvedMessages = $unresolved;
if ($this->getArgument('cache')) {
$cached = array();
foreach ($results as $result) {
$path = $result->getPath();
$hash = md5_file($engine->getFilePathOnDisk($path));
$cached[$path] = array($hash => array());
foreach ($result->getMessages() as $message) {
if (!$message->isPatchApplied()) {
$cached[$path][$hash][] = $message->toDictionary();
}
}
}
$cache = $this->readScratchJSONFile('lint-cache.json');
$cache[$this->getCacheKey()] = $cached;
// TODO: Garbage collection.
$this->writeScratchJSONFile('lint-cache.json', $cache);
}
// Take the most severe lint message severity and use that
// as the result code.
if ($has_errors) {