Skip to content

Commit

Permalink
fix(fingerprint): Scope fingerprints to project by default
Browse files Browse the repository at this point in the history
  • Loading branch information
TheoD02 committed Sep 16, 2024
1 parent df0603a commit 24d6e30
Show file tree
Hide file tree
Showing 11 changed files with 101 additions and 16 deletions.
1 change: 1 addition & 0 deletions bin/generate-tests.php
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@
'qa:phpstan:update',
// Customized tests
'fingerprint:task-with-a-fingerprint-and-force',
'fingerprint:task-with-a-fingerprint-global',
'fingerprint:task-with-a-fingerprint',
'fingerprint:task-with-complete-fingerprint-check',
'log:all-level',
Expand Down
4 changes: 4 additions & 0 deletions doc/going-further/helpers/fingerprint.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ function task_with_a_fingerprint(): void
> You can use the `$force` parameter of the `fingerprint()` function to force
> the execution of the callback even if the fingerprint has not changed.
> [!NOTE]
> By default the fingerprint is scoped to the current project, but you can use
> the `$global` parameter to make it shared across all projects.
## The `hasher()` function

Most of the time, you will want your fingerprint hash to be based on the content
Expand Down
17 changes: 17 additions & 0 deletions examples/fingerprint.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,23 @@ function task_with_a_fingerprint(): void
io()->writeln('Cool! I finished!');
}

#[AsTask(description: 'Execute a callback only if the global fingerprint has changed (Shared across all projects)')]
function task_with_a_fingerprint_global(): void
{
io()->writeln('Hello Task with Global Fingerprint!');

fingerprint(
callback: function () {
io()->writeln('Cool, no global fingerprint! Executing...');
},
id: 'my_global_fingerprint_check',
fingerprint: my_fingerprint_check(),
global: true, // This ensures that the fingerprint is shared across all projects (not only the current one)
);

io()->writeln('Cool! I finished global!');
}

#[AsTask(description: 'Check if the fingerprint has changed before executing some code')]
function task_with_complete_fingerprint_check(): void
{
Expand Down
22 changes: 18 additions & 4 deletions src/Fingerprint/FingerprintHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace Castor\Fingerprint;

use Castor\Helper\PathHelper;
use Psr\Cache\CacheItemPoolInterface;
use Symfony\Contracts\Cache\CacheInterface;

Expand All @@ -15,9 +16,9 @@ public function __construct(
) {
}

public function verifyFingerprintFromHash(string $id, string $fingerprint): bool
public function verifyFingerprintFromHash(string $id, string $fingerprint, bool $global = false): bool
{
$itemKey = $id . self::SUFFIX;
$itemKey = $this->getItemKey($id, $global);

if (false === $this->cache->hasItem($itemKey)) {
return false;
Expand All @@ -36,9 +37,9 @@ public function verifyFingerprintFromHash(string $id, string $fingerprint): bool
return false;
}

public function postProcessFingerprintForHash(string $id, string $hash): void
public function postProcessFingerprintForHash(string $id, string $hash, bool $global = false): void
{
$itemKey = $id . self::SUFFIX;
$itemKey = $this->getItemKey($id, $global);

$cacheItem = $this->cache->getItem($itemKey);

Expand All @@ -47,4 +48,17 @@ public function postProcessFingerprintForHash(string $id, string $hash): void
$cacheItem->expiresAt(new \DateTimeImmutable('+1 month'));
$this->cache->save($cacheItem);
}

private function getItemKey(string $id, bool $global = false): string
{
if ($global) {
return $id . self::SUFFIX;
}

return \sprintf(
'%s-%s',
hash('xxh128', PathHelper::getRoot()),
$id,
);
}
}
13 changes: 11 additions & 2 deletions src/Helper/HasherHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,12 @@ public function writeFile(string $path, FileHashStrategy $strategy = FileHashStr
$path = getcwd() . '/' . $path;
}

$path = realpath($path);

if (false === $path) {
throw new \InvalidArgumentException(\sprintf('The path "%s" is not a valid path.', $path));
}

if (!is_file($path)) {
throw new \InvalidArgumentException(\sprintf('The path "%s" is not a file.', $path));
}
Expand Down Expand Up @@ -79,11 +85,11 @@ public function writeWithFinder(Finder $finder, FileHashStrategy $strategy = Fil
foreach ($finder as $file) {
switch ($strategy) {
case FileHashStrategy::Content:
hash_update_file($this->hashContext, $file->getPathname());
hash_update_file($this->hashContext, $file->getRealPath());

break;
case FileHashStrategy::MTimes:
hash_update($this->hashContext, "{$file->getPathname()}:{$file->getMTime()}");
hash_update($this->hashContext, "{$file->getRealPath()}:{$file->getMTime()}");

break;
}
Expand All @@ -105,6 +111,9 @@ public function writeGlob(string $pattern, FileHashStrategy $strategy = FileHash
throw new \InvalidArgumentException(\sprintf('The pattern "%s" is invalid.', $pattern));
}

$files = array_map('realpath', $files);
$files = array_filter($files);

foreach ($files as $file) {
switch ($strategy) {
case FileHashStrategy::Content:
Expand Down
14 changes: 7 additions & 7 deletions src/functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -591,34 +591,34 @@ function hasher(string $algo = 'xxh128'): HasherHelper
);
}

function fingerprint_exists(string $id, ?string $fingerprint = null): bool
function fingerprint_exists(string $id, ?string $fingerprint = null, bool $global = false): bool
{
if (null === $fingerprint) {
trigger_deprecation('castor/castor', '0.18.0', 'since 0.18 fingerprint functions require an id argument.');

$fingerprint = $id;
}

return Container::get()->fingerprintHelper->verifyFingerprintFromHash($id, $fingerprint);
return Container::get()->fingerprintHelper->verifyFingerprintFromHash($id, $fingerprint, $global);
}

function fingerprint_save(string $id, ?string $fingerprint = null): void
function fingerprint_save(string $id, ?string $fingerprint = null, bool $global = false): void
{
if (null === $fingerprint) {
trigger_deprecation('castor/castor', '0.18.0', 'since 0.18 fingerprint functions require an id argument.');

$fingerprint = $id;
}

Container::get()->fingerprintHelper->postProcessFingerprintForHash($id, $fingerprint);
Container::get()->fingerprintHelper->postProcessFingerprintForHash($id, $fingerprint, $global);
}

// function fingerprint(callable $callback, string $fingerprint, bool $force = false): bool
/**
* @param string $id
* @param string $fingerprint
*/
function fingerprint(callable $callback, /* string */ $id = null, /* string */ $fingerprint = null, bool $force = false): bool
function fingerprint(callable $callback, /* string */ $id = null, /* string */ $fingerprint = null, bool $force = false, bool $global = false): bool
{
// Could only occur due du BC layer
if (null === $fingerprint && null === $id) {
Expand All @@ -642,9 +642,9 @@ function fingerprint(callable $callback, /* string */ $id = null, /* string */ $
$id = $fingerprint;
}

if ($force || !fingerprint_exists($id, $fingerprint)) {
if ($force || !fingerprint_exists($id, $fingerprint, $global)) {
$callback();
fingerprint_save($id, $fingerprint);
fingerprint_save($id, $fingerprint, $global);

return true;
}
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php

namespace Castor\Tests\Examples\Fingerprint;

class FingerprintTaskWithAGlobalFingerprintTest extends FingerprintedTestCase
{
// fingerprint:task-with-a-fingerprint-global
public function test(): void
{
// Run for the first time, should run
$this->runProcessAndExpect(__FILE__ . '.output_runnable.txt');

// should not run because the fingerprint is the same
$this->runProcessAndExpect(__FILE__ . '.output_not_runnable.txt');

// change file content, should run
$this->runProcessAndExpect(__FILE__ . '.output_runnable.txt', 'Hello World');
}

private function runProcessAndExpect(string $expectedOutputFilePath, string $withFileContent = 'Hello'): void
{
$filepath = \dirname(__DIR__, 3) . '/examples/fingerprint_file.fingerprint_single';
if (file_exists($filepath)) {
unlink($filepath);
}

file_put_contents($filepath, $withFileContent);

$process = $this->runTask(['fingerprint:task-with-a-fingerprint-global']);

if (file_exists($expectedOutputFilePath)) {
$this->assertStringEqualsFile($expectedOutputFilePath, $process->getOutput());
}

$this->assertSame(0, $process->getExitCode());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Hello Task with Global Fingerprint!
Cool! I finished global!
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Hello Task with Global Fingerprint!
Cool, no global fingerprint! Executing...
Cool! I finished global!
1 change: 1 addition & 0 deletions tests/Generated/ListTest.php.output.txt
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ filesystem:filesystem Performs some
filesystem:find Search files and directories on the filesystem
fingerprint:task-with-a-fingerprint Execute a callback only if the fingerprint has changed
fingerprint:task-with-a-fingerprint-and-force Check if the fingerprint has changed before executing a callback (with force option)
fingerprint:task-with-a-fingerprint-global Execute a callback only if the global fingerprint has changed (Shared across all projects)
fingerprint:task-with-complete-fingerprint-check Check if the fingerprint has changed before executing some code
foo:bar Echo foo bar
foo:foo Prints foo
Expand Down

0 comments on commit 24d6e30

Please sign in to comment.