Skip to content

Commit

Permalink
Fix #3117 - invalidate all caches when composer lockfile changes
Browse files Browse the repository at this point in the history
  • Loading branch information
muglug committed Apr 12, 2020
1 parent 77270dc commit 370ffa2
Show file tree
Hide file tree
Showing 18 changed files with 274 additions and 108 deletions.
27 changes: 21 additions & 6 deletions src/Psalm/Internal/Analyzer/ProjectAnalyzer.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
use Psalm\Internal\Provider\FileProvider;
use Psalm\Internal\Provider\FileReferenceProvider;
use Psalm\Internal\Provider\ParserCacheProvider;
use Psalm\Internal\Provider\ProjectCacheProvider;
use Psalm\Internal\Provider\Providers;
use Psalm\Issue\InvalidFalsableReturnType;
use Psalm\Issue\InvalidNullableReturnType;
Expand Down Expand Up @@ -117,6 +118,9 @@ class ProjectAnalyzer
/** @var ?ParserCacheProvider */
private $parser_cache_provider;

/** @var ?ProjectCacheProvider */
public $project_cache_provider;

/** @var FileReferenceProvider */
private $file_reference_provider;

Expand Down Expand Up @@ -229,6 +233,7 @@ public function __construct(
}

$this->parser_cache_provider = $providers->parser_cache_provider;
$this->project_cache_provider = $providers->project_cache_provider;
$this->file_provider = $providers->file_provider;
$this->classlike_storage_provider = $providers->classlike_storage_provider;
$this->file_reference_provider = $providers->file_reference_provider;
Expand Down Expand Up @@ -262,6 +267,16 @@ public function __construct(
$this->addProjectFile($file_path);
}

if ($this->project_cache_provider && $this->project_cache_provider->hasLockfileChanged()) {
$this->progress->debug(
'Composer lockfile change detected, clearing cache' . "\n"
);

Config::removeCacheDirectory($config->getCacheDirectory());

$this->project_cache_provider->updateComposerLockHash();
}

self::$instance = $this;
}

Expand Down Expand Up @@ -475,8 +490,8 @@ public function check($base_dir, $is_diff = false)

if ($is_diff
&& $reference_cache
&& $this->parser_cache_provider
&& $this->parser_cache_provider->canDiffFiles()
&& $this->project_cache_provider
&& $this->project_cache_provider->canDiffFiles()
) {
$deleted_files = $this->file_reference_provider->getDeletedReferencedFiles();
$diff_files = $deleted_files;
Expand Down Expand Up @@ -553,9 +568,9 @@ public function check($base_dir, $is_diff = false)
true
);

if ($this->parser_cache_provider) {
if ($this->project_cache_provider && $this->parser_cache_provider) {
$removed_parser_files = $this->parser_cache_provider->deleteOldParserCaches(
$is_diff ? $this->parser_cache_provider->getLastRun() : $start_checks
$is_diff ? $this->project_cache_provider->getLastRun() : $start_checks
);

if ($removed_parser_files) {
Expand Down Expand Up @@ -1009,13 +1024,13 @@ protected function getDiffFilesInDir($dir_name, Config $config)
{
$file_extensions = $config->getFileExtensions();

if (!$this->parser_cache_provider) {
if (!$this->parser_cache_provider || !$this->project_cache_provider) {
throw new \UnexpectedValueException('Parser cache provider cannot be null here');
}

$diff_files = [];

$last_run = $this->parser_cache_provider->getLastRun();
$last_run = $this->project_cache_provider->getLastRun();

$file_paths = $this->file_provider->getFilesInDir($dir_name, $file_extensions);

Expand Down
72 changes: 17 additions & 55 deletions src/Psalm/Internal/Provider/ParserCacheProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,6 @@ class ParserCacheProvider
const FILE_HASHES = 'file_hashes_json';
const PARSER_CACHE_DIRECTORY = 'php-parser';
const FILE_CONTENTS_CACHE_DIRECTORY = 'file-caches';
const GOOD_RUN_NAME = 'good_run';

/**
* @var int|null
*/
private $last_run = null;

/**
* A map of filename hashes to contents hashes
Expand Down Expand Up @@ -334,31 +328,19 @@ public function cacheFileContents($file_path, $file_contents)
}

/**
* @return bool
*/
public function canDiffFiles()
{
$cache_directory = Config::getInstance()->getCacheDirectory();

return $cache_directory && file_exists($cache_directory . DIRECTORY_SEPARATOR . self::GOOD_RUN_NAME);
}

/**
* @param float $start_time
* @param float $time_before
*
* @return void
* @return int
*/
public function processSuccessfulRun($start_time)
public function deleteOldParserCaches($time_before)
{
$cache_directory = Config::getInstance()->getCacheDirectory();

if (!$cache_directory) {
return;
if ($cache_directory) {
return 0;
}

$run_cache_location = $cache_directory . DIRECTORY_SEPARATOR . self::GOOD_RUN_NAME;

touch($run_cache_location, (int)$start_time);
$removed_count = 0;

$cache_directory .= DIRECTORY_SEPARATOR . self::PARSER_CACHE_DIRECTORY;

Expand All @@ -372,44 +354,29 @@ public function processSuccessfulRun($start_time)
continue;
}

touch($full_path);
}
}
}

/**
* @return int
*/
public function getLastRun()
{
if ($this->last_run === null) {
$cache_directory = Config::getInstance()->getCacheDirectory();

if (file_exists($cache_directory . DIRECTORY_SEPARATOR . self::GOOD_RUN_NAME)) {
$this->last_run = filemtime($cache_directory . DIRECTORY_SEPARATOR . self::GOOD_RUN_NAME);
} else {
$this->last_run = 0;
if (filemtime($full_path) < $time_before && is_writable($full_path)) {
unlink($full_path);
++$removed_count;
}
}
}

return $this->last_run;
return $removed_count;
}

/**
* @param float $time_before
* @param float $start_time
*
* @return int
* @return void
*/
public function deleteOldParserCaches($time_before)
public function processSuccessfulRun()
{
$cache_directory = Config::getInstance()->getCacheDirectory();

if ($cache_directory) {
return 0;
if (!$cache_directory) {
return;
}

$removed_count = 0;

$cache_directory .= DIRECTORY_SEPARATOR . self::PARSER_CACHE_DIRECTORY;

if (is_dir($cache_directory)) {
Expand All @@ -422,14 +389,9 @@ public function deleteOldParserCaches($time_before)
continue;
}

if (filemtime($full_path) < $time_before && is_writable($full_path)) {
unlink($full_path);
++$removed_count;
}
touch($full_path);
}
}

return $removed_count;
}

/**
Expand Down
144 changes: 144 additions & 0 deletions src/Psalm/Internal/Provider/ProjectCacheProvider.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
<?php
namespace Psalm\Internal\Provider;

use const DIRECTORY_SEPARATOR;
use function file_exists;
use function file_get_contents;
use function file_put_contents;
use function is_array;
use function is_readable;
use Psalm\Internal\Analyzer\IssueData;
use Psalm\Config;
use function serialize;
use function unserialize;

/**
* @psalm-type TaggedCodeType = array<int, array{0: int, 1: string}>
*/
/**
* Used to determine which files reference other files, necessary for using the --diff
* option from the command line.
*/
class ProjectCacheProvider
{
const GOOD_RUN_NAME = 'good_run';
const COMPOSER_LOCK_HASH = 'composer_lock_hash';

/**
* @var int|null
*/
private $last_run = null;

/**
* @var string|null
*/
private $composer_lock_hash = null;

private $composer_lock_location;

public function __construct(string $composer_lock_location)
{
$this->composer_lock_location = $composer_lock_location;
}

/**
* @return bool
*/
public function canDiffFiles()
{
$cache_directory = Config::getInstance()->getCacheDirectory();

return $cache_directory && file_exists($cache_directory . DIRECTORY_SEPARATOR . self::GOOD_RUN_NAME);
}

/**
* @param float $start_time
*
* @return void
*/
public function processSuccessfulRun($start_time)
{
$cache_directory = Config::getInstance()->getCacheDirectory();

if (!$cache_directory) {
return;
}

$run_cache_location = $cache_directory . DIRECTORY_SEPARATOR . self::GOOD_RUN_NAME;

\touch($run_cache_location, (int)$start_time);
}

/**
* @return int
*/
public function getLastRun()
{
if ($this->last_run === null) {
$cache_directory = Config::getInstance()->getCacheDirectory();

if (file_exists($cache_directory . DIRECTORY_SEPARATOR . self::GOOD_RUN_NAME)) {
$this->last_run = \filemtime($cache_directory . DIRECTORY_SEPARATOR . self::GOOD_RUN_NAME);
} else {
$this->last_run = 0;
}
}

return $this->last_run;
}

public function hasLockfileChanged() : bool
{
if (!file_exists($this->composer_lock_location)) {
return true;
}

$lockfile_contents = file_get_contents($this->composer_lock_location);

if (!$lockfile_contents) {
return true;
}

$sha1 = \sha1($lockfile_contents);

$changed = $sha1 !== $this->getComposerLockHash();

$this->composer_lock_hash = $sha1;

return $changed;
}

public function updateComposerLockHash() : void
{
$cache_directory = Config::getInstance()->getCacheDirectory();

if (!$cache_directory || !$this->composer_lock_hash) {
return;
}

if (!file_exists($cache_directory)) {
\mkdir($cache_directory, 0777, true);
}

$lock_hash_location = $cache_directory . DIRECTORY_SEPARATOR . self::COMPOSER_LOCK_HASH;

file_put_contents($lock_hash_location, $this->composer_lock_hash);
}

protected function getComposerLockHash() : string
{
if ($this->composer_lock_hash === null) {
$cache_directory = Config::getInstance()->getCacheDirectory();

$lock_hash_location = $cache_directory . DIRECTORY_SEPARATOR . self::COMPOSER_LOCK_HASH;

if (file_exists($lock_hash_location)) {
$this->composer_lock_hash = file_get_contents($lock_hash_location) ?: '';
} else {
$this->composer_lock_hash = '';
}
}

return $this->composer_lock_hash;
}
}
15 changes: 11 additions & 4 deletions src/Psalm/Internal/Provider/Providers.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,15 +36,22 @@ class Providers
*/
public $file_reference_provider;

/**
* @var ?ProjectCacheProvider
*/
public $project_cache_provider;

public function __construct(
FileProvider $file_provider,
ParserCacheProvider $parser_cache_provider = null,
FileStorageCacheProvider $file_storage_cache_provider = null,
ClassLikeStorageCacheProvider $classlike_storage_cache_provider = null,
FileReferenceCacheProvider $file_reference_cache_provider = null
?ParserCacheProvider $parser_cache_provider = null,
?FileStorageCacheProvider $file_storage_cache_provider = null,
?ClassLikeStorageCacheProvider $classlike_storage_cache_provider = null,
?FileReferenceCacheProvider $file_reference_cache_provider = null,
?ProjectCacheProvider $project_cache_provider = null
) {
$this->file_provider = $file_provider;
$this->parser_cache_provider = $parser_cache_provider;
$this->project_cache_provider = $project_cache_provider;

$this->file_storage_provider = new FileStorageProvider($file_storage_cache_provider);
$this->classlike_storage_provider = new ClassLikeStorageProvider($classlike_storage_cache_provider);
Expand Down
6 changes: 5 additions & 1 deletion src/Psalm/IssueBuffer.php
Original file line number Diff line number Diff line change
Expand Up @@ -614,8 +614,12 @@ function (IssueData $d1, IssueData $d2) : int {
if ($is_full && $start_time) {
$codebase->file_reference_provider->removeDeletedFilesFromReferences();

if ($project_analyzer->project_cache_provider) {
$project_analyzer->project_cache_provider->processSuccessfulRun($start_time);
}

if ($codebase->statements_provider->parser_cache_provider) {
$codebase->statements_provider->parser_cache_provider->processSuccessfulRun($start_time);
$codebase->statements_provider->parser_cache_provider->processSuccessfulRun();
}
}

Expand Down
3 changes: 2 additions & 1 deletion src/psalm-language-server.php
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,8 @@ function ($arg) use ($valid_long_options, $valid_short_options) {
new Psalm\Internal\Provider\ParserCacheProvider($config),
new Psalm\Internal\Provider\FileStorageCacheProvider($config),
new Psalm\Internal\Provider\ClassLikeStorageCacheProvider($config),
new Psalm\Internal\Provider\FileReferenceCacheProvider($config)
new Psalm\Internal\Provider\FileReferenceCacheProvider($config),
new Psalm\Internal\Provider\ProjectCacheProvider($current_dir . DIRECTORY_SEPARATOR . 'composer.lock')
);

$project_analyzer = new ProjectAnalyzer(
Expand Down
Loading

0 comments on commit 370ffa2

Please sign in to comment.