1
0
Fork 0
mirror of https://we.phorge.it/source/arcanist.git synced 2024-11-26 16:52:40 +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(), 'severity' => $this->getSeverity(),
'name' => $this->getName(), 'name' => $this->getName(),
'description' => $this->getDescription(), 'description' => $this->getDescription(),
'original' => $this->getOriginalText(),
'replacement' => $this->getReplacementText(),
); );
} }

View file

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

View file

@ -354,6 +354,13 @@ EOTEXT
'Run lint and unit tests on background. '. 'Run lint and unit tests on background. '.
'"0" to disable, "1" to enable (default).', '"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', '*' => 'paths',
); );

View file

@ -120,6 +120,10 @@ EOTEXT
array_keys(ArcanistLintSeverity::getLintSeverities())). array_keys(ArcanistLintSeverity::getLintSeverities())).
"'. Defaults to '".self::DEFAULT_SEVERITY."'.", "'. Defaults to '".self::DEFAULT_SEVERITY."'.",
), ),
'cache' => array(
'param' => 'bool',
'help' => "0 to disable cache (default), 1 to enable.",
),
'*' => 'paths', '*' => 'paths',
); );
} }
@ -132,6 +136,15 @@ EOTEXT
return true; 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() { public function run() {
$working_copy = $this->getWorkingCopy(); $working_copy = $this->getWorkingCopy();
@ -176,6 +189,25 @@ EOTEXT
$engine->setMinimumSeverity( $engine->setMinimumSeverity(
$this->getArgument('severity', self::DEFAULT_SEVERITY)); $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. // Propagate information about which lines changed to the lint engine.
// This is used so that the lint engine can drop warning messages // This is used so that the lint engine can drop warning messages
// concerning lines that weren't in the change. // concerning lines that weren't in the change.
@ -201,7 +233,11 @@ EOTEXT
try { try {
$engine->run(); $engine->run();
} catch (Exception $ex) { } catch (Exception $ex) {
$failed = $ex; if ($ex instanceof ArcanistNoEffectException && $cached) {
// Swallow.
} else {
$failed = $ex;
}
} }
$results = $engine->getResults(); $results = $engine->getResults();
@ -365,6 +401,24 @@ EOTEXT
} }
$this->unresolvedMessages = $unresolved; $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 // Take the most severe lint message severity and use that
// as the result code. // as the result code.
if ($has_errors) { if ($has_errors) {