SelfUpdateCommand.php 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. <?php
  2. declare(strict_types=1);
  3. /*
  4. * This file is part of PHP CS Fixer.
  5. *
  6. * (c) Fabien Potencier <fabien@symfony.com>
  7. * Dariusz Rumiński <dariusz.ruminski@gmail.com>
  8. *
  9. * This source file is subject to the MIT license that is bundled
  10. * with this source code in the file LICENSE.
  11. */
  12. namespace PhpCsFixer\Console\Command;
  13. use PhpCsFixer\Console\Application;
  14. use PhpCsFixer\Console\SelfUpdate\NewVersionCheckerInterface;
  15. use PhpCsFixer\PharCheckerInterface;
  16. use PhpCsFixer\Preg;
  17. use PhpCsFixer\ToolInfoInterface;
  18. use Symfony\Component\Console\Attribute\AsCommand;
  19. use Symfony\Component\Console\Command\Command;
  20. use Symfony\Component\Console\Input\InputInterface;
  21. use Symfony\Component\Console\Input\InputOption;
  22. use Symfony\Component\Console\Output\ConsoleOutputInterface;
  23. use Symfony\Component\Console\Output\OutputInterface;
  24. /**
  25. * @author Igor Wiedler <igor@wiedler.ch>
  26. * @author Stephane PY <py.stephane1@gmail.com>
  27. * @author Grégoire Pineau <lyrixx@lyrixx.info>
  28. * @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
  29. *
  30. * @internal
  31. */
  32. #[AsCommand(name: 'self-update')]
  33. final class SelfUpdateCommand extends Command
  34. {
  35. /** @var string */
  36. protected static $defaultName = 'self-update';
  37. private NewVersionCheckerInterface $versionChecker;
  38. private ToolInfoInterface $toolInfo;
  39. private PharCheckerInterface $pharChecker;
  40. public function __construct(
  41. NewVersionCheckerInterface $versionChecker,
  42. ToolInfoInterface $toolInfo,
  43. PharCheckerInterface $pharChecker
  44. ) {
  45. parent::__construct();
  46. $this->versionChecker = $versionChecker;
  47. $this->toolInfo = $toolInfo;
  48. $this->pharChecker = $pharChecker;
  49. }
  50. protected function configure(): void
  51. {
  52. $this
  53. ->setAliases(['selfupdate'])
  54. ->setDefinition(
  55. [
  56. new InputOption('--force', '-f', InputOption::VALUE_NONE, 'Force update to next major version if available.'),
  57. ]
  58. )
  59. ->setDescription('Update php-cs-fixer.phar to the latest stable version.')
  60. ->setHelp(
  61. <<<'EOT'
  62. The <info>%command.name%</info> command replace your php-cs-fixer.phar by the
  63. latest version released on:
  64. <comment>https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/releases</comment>
  65. <info>$ php php-cs-fixer.phar %command.name%</info>
  66. EOT
  67. )
  68. ;
  69. }
  70. protected function execute(InputInterface $input, OutputInterface $output): int
  71. {
  72. if ($output instanceof ConsoleOutputInterface) {
  73. $stdErr = $output->getErrorOutput();
  74. $stdErr->writeln(Application::getAboutWithRuntime(true));
  75. }
  76. if (!$this->toolInfo->isInstalledAsPhar()) {
  77. $output->writeln('<error>Self-update is available only for PHAR version.</error>');
  78. return 1;
  79. }
  80. $currentVersion = $this->getApplication()->getVersion();
  81. Preg::match('/^v?(?<major>\d+)\./', $currentVersion, $matches);
  82. $currentMajor = (int) $matches['major'];
  83. try {
  84. $latestVersion = $this->versionChecker->getLatestVersion();
  85. $latestVersionOfCurrentMajor = $this->versionChecker->getLatestVersionOfMajor($currentMajor);
  86. } catch (\Exception $exception) {
  87. $output->writeln(\sprintf(
  88. '<error>Unable to determine newest version: %s</error>',
  89. $exception->getMessage()
  90. ));
  91. return 1;
  92. }
  93. if (1 !== $this->versionChecker->compareVersions($latestVersion, $currentVersion)) {
  94. $output->writeln('<info>PHP CS Fixer is already up-to-date.</info>');
  95. return 0;
  96. }
  97. $remoteTag = $latestVersion;
  98. if (
  99. 0 !== $this->versionChecker->compareVersions($latestVersionOfCurrentMajor, $latestVersion)
  100. && true !== $input->getOption('force')
  101. ) {
  102. $output->writeln(\sprintf('<info>A new major version of PHP CS Fixer is available</info> (<comment>%s</comment>)', $latestVersion));
  103. $output->writeln(\sprintf('<info>Before upgrading please read</info> https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/blob/%s/UPGRADE-v%s.md', $latestVersion, $currentMajor + 1));
  104. $output->writeln('<info>If you are ready to upgrade run this command with</info> <comment>-f</comment>');
  105. $output->writeln('<info>Checking for new minor/patch version...</info>');
  106. if (1 !== $this->versionChecker->compareVersions($latestVersionOfCurrentMajor, $currentVersion)) {
  107. $output->writeln('<info>No minor update for PHP CS Fixer.</info>');
  108. return 0;
  109. }
  110. $remoteTag = $latestVersionOfCurrentMajor;
  111. }
  112. $localFilename = $_SERVER['argv'][0];
  113. $realPath = realpath($localFilename);
  114. if (false !== $realPath) {
  115. $localFilename = $realPath;
  116. }
  117. if (!is_writable($localFilename)) {
  118. $output->writeln(\sprintf('<error>No permission to update</error> "%s" <error>file.</error>', $localFilename));
  119. return 1;
  120. }
  121. $tempFilename = \dirname($localFilename).'/'.basename($localFilename, '.phar').'-tmp.phar';
  122. $remoteFilename = $this->toolInfo->getPharDownloadUri($remoteTag);
  123. if (false === @copy($remoteFilename, $tempFilename)) {
  124. $output->writeln(\sprintf('<error>Unable to download new version</error> %s <error>from the server.</error>', $remoteTag));
  125. return 1;
  126. }
  127. chmod($tempFilename, 0777 & ~umask());
  128. $pharInvalidityReason = $this->pharChecker->checkFileValidity($tempFilename);
  129. if (null !== $pharInvalidityReason) {
  130. unlink($tempFilename);
  131. $output->writeln(\sprintf('<error>The download of</error> %s <error>is corrupt (%s).</error>', $remoteTag, $pharInvalidityReason));
  132. $output->writeln('<error>Please re-run the "self-update" command to try again.</error>');
  133. return 1;
  134. }
  135. rename($tempFilename, $localFilename);
  136. $output->writeln(\sprintf('<info>PHP CS Fixer updated</info> (<comment>%s</comment> -> <comment>%s</comment>)', $currentVersion, $remoteTag));
  137. return 0;
  138. }
  139. }