diff --git a/.github/actions/cache/action.yaml b/.github/actions/cache/action.yaml
index 4280e0f1..906e75b8 100644
--- a/.github/actions/cache/action.yaml
+++ b/.github/actions/cache/action.yaml
@@ -16,13 +16,13 @@ runs:
set -e
# Should be the same command as the one in tools/static/castor.php
- cache_dirname_linux_amd64=$(tests/bin/compile-get-cache-key phar-location-is-not-used-in-cache-key --os=linux --arch=x86_64 --php-extensions=mbstring,phar,posix,tokenizer,pcntl)
+ cache_dirname_linux_amd64=$(tests/bin/compile-get-cache-key phar-location-is-not-used-in-cache-key --os=linux --arch=x86_64 --php-extensions=mbstring,phar,posix,tokenizer,pcntl,curl)
cache_key_linux_amd64=$(basename $cache_dirname_linux_amd64)
echo cache_dirname_linux_amd64=$cache_dirname_linux_amd64 >> $GITHUB_ENV
echo cache_key_linux_amd64=$cache_key_linux_amd64 >> $GITHUB_ENV
# Should be the same command as the one in tools/static/castor.php
- cache_dirname_darwin_amd64=$(tests/bin/compile-get-cache-key phar-location-is-not-used-in-cache-key --os=macos --arch=x86_64 --php-extensions=mbstring,phar,posix,tokenizer,pcntl)
+ cache_dirname_darwin_amd64=$(tests/bin/compile-get-cache-key phar-location-is-not-used-in-cache-key --os=macos --arch=x86_64 --php-extensions=mbstring,phar,posix,tokenizer,pcntl,curl)
cache_key_darwin_amd64=$(basename $cache_dirname_darwin_amd64)
echo cache_dirname_darwin_amd64=$cache_dirname_darwin_amd64 >> $GITHUB_ENV
echo cache_key_darwin_amd64=$cache_key_darwin_amd64 >> $GITHUB_ENV
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 550187f5..6ef7bde6 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -77,8 +77,10 @@ jobs:
if: matrix.castor.method == 'phar' || matrix.castor.method == 'static'
- name: Compile Custom Built PHP along Castor phar for Linux
- # Should be the same command as the one in tools/static/castor.php
- run: bin/castor compile tools/phar/build/castor.linux-amd64.phar --php-extensions=mbstring,phar,posix,tokenizer,pcntl --binary-path=${{ github.workspace }}/${{ matrix.castor.bin }}
+ run: |
+ set -e
+ bin/castor castor:static:linux
+ mv castor.linux-amd64 ${{ github.workspace }}/${{ matrix.castor.bin }}
if: matrix.castor.method == 'static'
# We use box in a test, so we need to make it available everywhere
diff --git a/CHANGELOG.md b/CHANGELOG.md
index b1d21d8a..afeeceb6 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,10 @@
## Not released yet
+## 0.13.1 (2024-02-27)
+
+* Fix instruction for downloading new castor version as a phar
+
## 0.13.0 (2024-02-23)
* Add a `compile` command that puts together a customizable PHP binary with a
diff --git a/phpunit.xml b/phpunit.xml
index 41fc5ce3..6ef8f346 100644
--- a/phpunit.xml
+++ b/phpunit.xml
@@ -28,5 +28,6 @@
+
diff --git a/src/Console/Application.php b/src/Console/Application.php
index 6fcf0958..483f3b4b 100644
--- a/src/Console/Application.php
+++ b/src/Console/Application.php
@@ -17,13 +17,11 @@
use Castor\ListenerDescriptor;
use Castor\PlatformUtil;
use Castor\SectionOutput;
-use Castor\Stub\StubsGenerator;
use Castor\SymfonyTaskDescriptor;
use Castor\TaskDescriptor;
use Castor\TaskDescriptorCollection;
use Castor\VerbosityLevel;
use Castor\WaitForHelper;
-use JoliCode\PhpOsHelper\OsHelper;
use Monolog\Logger;
use Psr\Cache\CacheItemPoolInterface;
use Psr\Log\LogLevel;
@@ -37,17 +35,15 @@
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\ErrorHandler\ErrorHandler;
use Symfony\Component\Filesystem\Filesystem;
-use Symfony\Component\Process\Process;
use Symfony\Component\VarDumper\Cloner\AbstractCloner;
use Symfony\Contracts\Cache\CacheInterface;
-use Symfony\Contracts\HttpClient\Exception\ExceptionInterface as HttpExceptionInterface;
use Symfony\Contracts\HttpClient\HttpClientInterface;
/** @internal */
class Application extends SymfonyApplication
{
public const NAME = 'castor';
- public const VERSION = 'v0.13.0';
+ public const VERSION = 'v0.13.1';
// "Current" objects availables at some point of the lifecycle
private InputInterface $input;
@@ -61,7 +57,6 @@ public function __construct(
public readonly ContextRegistry $contextRegistry,
public readonly EventDispatcher $eventDispatcher,
public readonly ExpressionLanguage $expressionLanguage,
- public readonly StubsGenerator $stubsGenerator,
public readonly Logger $logger,
public readonly Filesystem $fs,
public HttpClientInterface $httpClient,
@@ -170,11 +165,6 @@ protected function doRunCommand(Command $command, InputInterface $input, OutputI
{
$this->command = $command;
- if ('_complete' !== $command->getName() && !class_exists(\RepackedApplication::class)) {
- $this->stubsGenerator->generateStubsIfNeeded($this->rootDir . '/.castor.stub.php');
- $this->displayUpdateWarningIfNeeded(new SymfonyStyle($input, $output));
- }
-
return parent::doRunCommand($command, $input, $output);
}
@@ -255,78 +245,4 @@ private function configureContext(InputInterface $input, OutputInterface $output
$this->contextRegistry->setCurrentContext($context->withName($input->getOption('context')));
}
-
- private function displayUpdateWarningIfNeeded(SymfonyStyle $symfonyStyle): void
- {
- $item = $this->cache->getItem('last-update-warning');
- if ($item->isHit()) {
- return;
- }
-
- // We save it right now, even if there are some failures later. We don't
- // want to waste bandwidth or CPU usage, nor log too much information.
- $item->expiresAfter(60 * 60 * 24);
- $item->set(true);
- $this->cache->save($item);
-
- $response = $this->httpClient->request('GET', 'https://api.github.com/repos/jolicode/castor/releases/latest', [
- 'timeout' => 1,
- ]);
-
- try {
- $latestVersion = $response->toArray();
- } catch (HttpExceptionInterface) {
- $this->logger->info('Failed to fetch latest Castor version from GitHub.');
-
- return;
- }
-
- if (version_compare($latestVersion['tag_name'], self::VERSION, '<=')) {
- return;
- }
-
- $symfonyStyle->block(sprintf('A new Castor version is available (%s, currently running %s).', $latestVersion['tag_name'], self::VERSION), escape: false);
-
- if ($pharPath = \Phar::running(false)) {
- $assets = match (true) {
- OsHelper::isWindows() || OsHelper::isWindowsSubsystemForLinux() => array_filter($latestVersion['assets'], fn (array $asset) => str_contains($asset['name'], 'windows')),
- OsHelper::isMacOS() => array_filter($latestVersion['assets'], fn (array $asset) => str_contains($asset['name'], 'darwin')),
- OsHelper::isUnix() => array_filter($latestVersion['assets'], fn (array $asset) => str_contains($asset['name'], 'linux')),
- default => [],
- };
-
- if (!$assets) {
- $this->logger->info('Failed to detect the correct release url adapted to your system.');
-
- return;
- }
-
- $latestReleaseUrl = reset($assets)['browser_download_url'] ?? null;
-
- if (!$latestReleaseUrl) {
- $this->logger->info('Failed to fetch latest phar url.');
-
- return;
- }
-
- if (OsHelper::isUnix()) {
- $symfonyStyle->block('Run the following command to update Castor:');
- $symfonyStyle->block(sprintf('curl "%s" -Lfso castor && chmod u+x castor && mv castor %s', $latestReleaseUrl, $pharPath), escape: false);
- } else {
- $symfonyStyle->block(sprintf('Download the latest version at %s', $latestReleaseUrl), escape: false);
- }
-
- $symfonyStyle->newLine();
-
- return;
- }
-
- $process = new Process(['composer', 'global', 'config', 'home', '--quiet']);
- $process->run();
- $globalComposerPath = trim($process->getOutput());
-
- if ($globalComposerPath && str_contains(__FILE__, $globalComposerPath)) {
- $symfonyStyle->block('Run the following command to update Castor: composer global update jolicode/castor', escape: false);
- }
- }
}
diff --git a/src/Console/ApplicationFactory.php b/src/Console/ApplicationFactory.php
index e59122d8..98303351 100644
--- a/src/Console/ApplicationFactory.php
+++ b/src/Console/ApplicationFactory.php
@@ -10,6 +10,8 @@
use Castor\ExpressionLanguage;
use Castor\Fingerprint\FingerprintHelper;
use Castor\FunctionFinder;
+use Castor\Listener\GenerateStubsListener;
+use Castor\Listener\UpdateCastorListener;
use Castor\Monolog\Processor\ProcessProcessor;
use Castor\PathHelper;
use Castor\PlatformUtil;
@@ -48,6 +50,15 @@ public static function create(): SymfonyApplication
$logger = new Logger('castor', [], [new ProcessProcessor()]);
$fs = new Filesystem();
$eventDispatcher = new EventDispatcher(logger: $logger);
+ $eventDispatcher->addSubscriber(new UpdateCastorListener(
+ $cache,
+ $httpClient,
+ $logger,
+ ));
+ $eventDispatcher->addSubscriber(new GenerateStubsListener(
+ new StubsGenerator($logger),
+ $rootDir,
+ ));
/** @var SymfonyApplication */
// @phpstan-ignore-next-line
@@ -57,7 +68,6 @@ public static function create(): SymfonyApplication
$contextRegistry,
$eventDispatcher,
new ExpressionLanguage($contextRegistry),
- new StubsGenerator($logger),
$logger,
$fs,
$httpClient,
diff --git a/src/Console/Command/CompileCommand.php b/src/Console/Command/CompileCommand.php
index b583106a..56ec06ff 100644
--- a/src/Console/Command/CompileCommand.php
+++ b/src/Console/Command/CompileCommand.php
@@ -20,6 +20,10 @@
*/
class CompileCommand extends Command
{
+ // When something **important** related to the compilation changed, increase
+ // this version to invalide the cache
+ private const CACHE_VERSION = '1';
+
public function __construct(
private readonly HttpClientInterface $httpClient,
private readonly Filesystem $fs
@@ -37,7 +41,7 @@ protected function configure(): void
->addOption('os', null, InputOption::VALUE_REQUIRED, 'Target OS for PHP compilation', 'linux', ['linux', 'macos'])
->addOption('arch', null, InputOption::VALUE_REQUIRED, 'Target architecture for PHP compilation', 'x86_64', ['x86_64', 'aarch64'])
->addOption('php-version', null, InputOption::VALUE_REQUIRED, 'PHP version in major.minor format', '8.3')
- ->addOption('php-extensions', null, InputOption::VALUE_REQUIRED, 'PHP extensions required, in a comma-separated format. Defaults are the minimum required to run a basic "Hello World" task in Castor.', 'mbstring,phar,posix,tokenizer')
+ ->addOption('php-extensions', null, InputOption::VALUE_REQUIRED, 'PHP extensions required, in a comma-separated format. Defaults are the minimum required to run a basic "Hello World" task in Castor.', 'mbstring,phar,posix,tokenizer,curl')
->addOption('php-rebuild', null, InputOption::VALUE_NONE, 'Ignore cache and force PHP build compilation.')
->setHidden(true)
;
@@ -253,7 +257,7 @@ private function generatePHPBuildCacheKey(InputInterface $input): string
sort($phpExtensions);
hash_update($c, implode(',', $phpExtensions));
- hash_update_file($c, __FILE__);
+ hash_update($c, self::CACHE_VERSION);
return hash_final($c);
}
diff --git a/src/Listener/GenerateStubsListener.php b/src/Listener/GenerateStubsListener.php
new file mode 100644
index 00000000..4ee1f27b
--- /dev/null
+++ b/src/Listener/GenerateStubsListener.php
@@ -0,0 +1,43 @@
+getCommand();
+ if (!$command) {
+ return;
+ }
+ if ('_complete' === $command->getName()) {
+ return;
+ }
+
+ $this->stubsGenerator->generateStubsIfNeeded($this->rootDir . '/.castor.stub.php');
+ }
+
+ public static function getSubscribedEvents(): array
+ {
+ return [
+ // Must be before the command is executed, because we have to check
+ // for many command options
+ ConsoleEvents::COMMAND => 'generateStubs',
+ ];
+ }
+}
diff --git a/src/Listener/UpdateCastorListener.php b/src/Listener/UpdateCastorListener.php
new file mode 100644
index 00000000..ea41a7ad
--- /dev/null
+++ b/src/Listener/UpdateCastorListener.php
@@ -0,0 +1,157 @@
+getCommand();
+ if (!$command) {
+ return;
+ }
+ if ('_complete' === $command->getName()) {
+ return;
+ }
+ $input = $event->getInput();
+ if ($input->hasOption('format') && 'json' === $input->getOption('format')) {
+ return;
+ }
+
+ $this->displayUpdateWarningIfNeeded($input, $event->getOutput());
+ }
+
+ public static function getSubscribedEvents(): array
+ {
+ return [
+ // Must be before the command is executed, because we have to check
+ // for many command options
+ ConsoleEvents::COMMAND => 'checkUpdate',
+ ];
+ }
+
+ private function displayUpdateWarningIfNeeded(InputInterface $input, OutputInterface $output): void
+ {
+ $item = $this->cache->getItem('castor-releases');
+
+ if ($item->isHit()) {
+ $latestVersion = $item->get();
+ } else {
+ $latestVersion = null;
+ $item->expiresAfter(60 * 60 * 24);
+
+ try {
+ $latestVersion = $this
+ ->httpClient
+ ->request('GET', 'https://api.github.com/repos/jolicode/castor/releases/latest', [
+ 'timeout' => 1,
+ ])
+ ->toArray()
+ ;
+ } catch (ExceptionInterface) {
+ $this->logger->info('Failed to fetch latest Castor version from GitHub.');
+
+ $item->expiresAfter(60 * 10);
+
+ return;
+ }
+
+ $this->cache->save($item->set($latestVersion));
+ }
+
+ if (!$latestVersion) {
+ return;
+ }
+
+ if (version_compare($latestVersion['tag_name'], Application::VERSION, '<=')) {
+ return;
+ }
+
+ $symfonyStyle = new SymfonyStyle($input, $output);
+
+ $symfonyStyle->block(sprintf('A new Castor version is available (%s, currently running %s).', $latestVersion['tag_name'], Application::VERSION), escape: false);
+
+ // Installed via phar
+ if ($pharPath = \Phar::running(false)) {
+ $assets = match (true) {
+ OsHelper::isWindows() || OsHelper::isWindowsSubsystemForLinux() => array_filter($latestVersion['assets'], fn (array $asset) => str_contains($asset['name'], 'windows')),
+ OsHelper::isMacOS() => array_filter($latestVersion['assets'], fn (array $asset) => str_contains($asset['name'], 'darwin')),
+ OsHelper::isUnix() => array_filter($latestVersion['assets'], fn (array $asset) => str_contains($asset['name'], 'linux')),
+ default => [],
+ };
+
+ if (!$assets) {
+ $this->logger->info('Failed to detect the correct release url adapted to your system.');
+
+ return;
+ }
+
+ $latestReleaseUrl = reset($assets)['browser_download_url'] ?? null;
+ // Fow now, we force the phar since it has more capabilities than
+ // the static binary, and it's more tested
+ if (!str_ends_with($latestReleaseUrl, '.phar')) {
+ $latestReleaseUrl .= '.phar';
+ }
+
+ if (!$latestReleaseUrl) {
+ $this->logger->info('Failed to fetch latest phar url.');
+
+ return;
+ }
+
+ if (OsHelper::isUnix()) {
+ $symfonyStyle->block('Run the following command to update Castor:');
+ $symfonyStyle->block(sprintf('curl "%s" -Lfso castor && chmod u+x castor && mv castor %s', $latestReleaseUrl, $pharPath), escape: false);
+ } else {
+ $symfonyStyle->block(sprintf('Download the latest version at %s', $latestReleaseUrl), escape: false);
+ }
+
+ $symfonyStyle->newLine();
+
+ return;
+ }
+
+ $globalComposerPath = $this->cache->get('castor-composer-global-path', function (): string {
+ $process = new Process(['composer', 'global', 'config', 'home', '--quiet']);
+ $process->run();
+
+ return trim($process->getOutput());
+ });
+
+ // Installed via composer global
+ if ($globalComposerPath && str_contains(__FILE__, $globalComposerPath)) {
+ $symfonyStyle->block('Run the following command to update Castor: composer global update jolicode/castor', escape: false);
+ }
+ }
+}
diff --git a/tools/static/castor.php b/tools/static/castor.php
index beb8632b..6e0fbf85 100644
--- a/tools/static/castor.php
+++ b/tools/static/castor.php
@@ -6,20 +6,22 @@
use function Castor\run;
+// Extensions should be in sync with .github/actions/cache/action.yaml
+
#[AsTask(description: 'Build static binary for Linux system')]
function linux()
{
- run('bin/castor compile tools/phar/build/castor.linux-amd64.phar --os=linux --arch=x86_64 --binary-path=castor.linux-amd64 --php-extensions=mbstring,phar,posix,tokenizer,pcntl', timeout: 0);
+ run('bin/castor compile tools/phar/build/castor.linux-amd64.phar --os=linux --arch=x86_64 --binary-path=castor.linux-amd64 --php-extensions=mbstring,phar,posix,tokenizer,pcntl,curl', timeout: 0);
}
#[AsTask(description: 'Build static binary for MacOS (amd64) system')]
function darwinAmd64()
{
- run('bin/castor compile tools/phar/build/castor.darwin-amd64.phar --os=macos --arch=x86_64 --binary-path=castor.darwin-amd64 --php-extensions=mbstring,phar,posix,tokenizer,pcntl', timeout: 0);
+ run('bin/castor compile tools/phar/build/castor.darwin-amd64.phar --os=macos --arch=x86_64 --binary-path=castor.darwin-amd64 --php-extensions=mbstring,phar,posix,tokenizer,pcntl,curl', timeout: 0);
}
#[AsTask(description: 'Build static binary for MacOS (arm64) system')]
function darwinArm64()
{
- run('bin/castor compile tools/phar/build/castor.darwin-arm64.phar --os=macos --arch=aarch64 --binary-path=castor.darwin-arm64 --php-extensions=mbstring,phar,posix,tokenizer,pcntl', timeout: 0);
+ run('bin/castor compile tools/phar/build/castor.darwin-arm64.phar --os=macos --arch=aarch64 --binary-path=castor.darwin-arm64 --php-extensions=mbstring,phar,posix,tokenizer,pcntl,curl', timeout: 0);
}