diff --git a/libraries/classes/Command/SetVersionCommand.php b/libraries/classes/Command/SetVersionCommand.php new file mode 100644 index 0000000000..780bd1b498 --- /dev/null +++ b/libraries/classes/Command/SetVersionCommand.php @@ -0,0 +1,102 @@ +setDescription('Sets the version number'); + $this->setHelp('This command generates the PhpMyAdmin\Version class based on the version number provided.'); + $this->addArgument('version', InputArgument::REQUIRED, 'The version number'); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + /** @var string $version */ + $version = $input->getArgument('version'); + + $generatedClass = $this->getGeneratedClass($version); + + if (! $this->writeGeneratedClassFile($generatedClass)) { + // failure + return 1; + } + + $output->writeln('PhpMyAdmin\Version class successfully generated!'); + + // success + return 0; + } + + private function getGeneratedClass(string $version): string + { + // Do not allow any major below 5 + $return = preg_match('/^([5-9]+)\.(\d{1,2})\.(\d{1,2})(-([a-z0-9]+))?$/', $version, $matches); + if ($return === false || $return === 0) { + throw new RangeException('The version number is in the wrong format: ' . $version); + } + + return sprintf( + self::$generatedClassTemplate, + $matches[1], + $matches[2], + $matches[3], + $matches[4] ?? '', + $matches[5] ?? '', + ($matches[5] ?? '') === 'dev' ? 'true' : 'false' + ); + } + + private function writeGeneratedClassFile(string $generatedClass): bool + { + $result = file_put_contents( + ROOT_PATH . 'libraries/classes/Version.php', + $generatedClass + ); + + return $result !== false; + } +} diff --git a/libraries/classes/Config.php b/libraries/classes/Config.php index 263474241e..e442a8cf0e 100644 --- a/libraries/classes/Config.php +++ b/libraries/classes/Config.php @@ -136,12 +136,9 @@ class Config */ public function checkSystem(): void { - $this->set('PMA_VERSION', '5.1.0-rc2'); - /* Major version */ - $this->set( - 'PMA_MAJOR_VERSION', - implode('.', array_slice(explode('.', $this->get('PMA_VERSION'), 3), 0, 2)) - ); + // All the version handling is now done in the Version class + $this->set('PMA_VERSION', Version::VERSION); + $this->set('PMA_MAJOR_VERSION', Version::SERIES); $this->checkWebServerOs(); $this->checkWebServer(); diff --git a/libraries/classes/Version.php b/libraries/classes/Version.php new file mode 100644 index 0000000000..76c2b3c007 --- /dev/null +++ b/libraries/classes/Version.php @@ -0,0 +1,23 @@ +add(new CacheWarmupCommand()); $application->add(new LintCommand($twig)); +$application->add(new SetVersionCommand()); $application->run(); diff --git a/scripts/create-release.sh b/scripts/create-release.sh index 61c7ad1db8..58cc1c5858 100755 --- a/scripts/create-release.sh +++ b/scripts/create-release.sh @@ -238,24 +238,24 @@ delete_phpunit_sandbox() { # Ensure we have tracking branch ensure_local_branch $branch -# Check if we're releasing older -if git cat-file -e $branch:libraries/classes/Config.php 2> /dev/null ; then - CONFIG_LIB=libraries/classes/Config.php -elif git cat-file -e $branch:libraries/Config.php 2> /dev/null ; then - CONFIG_LIB=libraries/Config.php -else - CONFIG_LIB=libraries/Config.class.php -fi +VERSION_FILE=libraries/classes/Version.php + +fetchReleaseFromFile() { + php -r "define('VERSION_SUFFIX', ''); require_once('libraries/classes/Version.php'); echo \PhpMyAdmin\Version::VERSION;" +} + +echo "The actual configured release is: $(fetchReleaseFromFile)" if [ $do_ci -eq 0 -a -$do_daily -eq 0 ] ; then cat <set('PMA_VERSION', '$version'); " + - run ./scripts/console set-version $version + - in $VERSION_FILE Version class: + - check that VERSION, MAJOR, MINOR and PATCH are correct. - in doc/conf.py the line " version = '$version' " - - in README + - in README the "Version" line - in package.json the line " "version": "$version", " - set release date in ChangeLog @@ -269,6 +269,8 @@ END fi fi +echo "The actual configured release is now: $(fetchReleaseFromFile)" + # Create working copy mkdir -p release git worktree prune @@ -290,8 +292,8 @@ fi # Check release version if [ $do_ci -eq 0 -a -$do_daily -eq 0 ] ; then - if ! grep -q "'PMA_VERSION', '$version'" $CONFIG_LIB ; then - echo "There seems to be wrong version in $CONFIG_LIB!" + if ! grep -q "VERSION = '$version'" $VERSION_FILE ; then + echo "There seems to be wrong version in $VERSION_FILE!" exit 2 fi if ! grep -q "version = '$version'" doc/conf.py ; then @@ -601,11 +603,13 @@ Todo now: based on documentation. 7. increment rc count or version in the repository : - - in $CONFIG_LIB Config::__constructor() the line - " \$this->set( 'PMA_VERSION', '2.7.1-dev' ); " - - in Documentation.html (if it exists) the 2 lines - " phpMyAdmin 2.2.2-rc1 - Documentation " - "

phpMyAdmin 2.2.2-rc1 Documentation

" + - run ./scripts/console set-version $version + - in $VERSION_FILE Version class: + - check that VERSION, MAJOR, MINOR and PATCH are correct. + - in README the "Version" line + " Version 2.7.1-dev " + - in package.json the line + " "version": " 2.7.1-dev", " - in doc/conf.py (if it exists) the line " version = '2.7.1-dev' " @@ -615,6 +619,6 @@ Todo now: 10. in case of a new major release ('y' in x.y.0), update the pmaweb/settings.py in website repository to include the new major releases -11. update the Dockerfile in the docker repository to reflect the new version and create a new annotated tag (such as with git tag -s -a 4.7.9-1 -m "Version 4.7.9-1"). Remember to push the tag with git push origin --tags +11. update the Dockerfile in the docker repository to reflect the new version and create a new annotated tag (such as with git tag -s -a 4.7.9-1 -m "Version 4.7.9-1"). Remember to push the tag with git push origin {tagName} END diff --git a/test/classes/Command/SetVersionCommandTest.php b/test/classes/Command/SetVersionCommandTest.php new file mode 100644 index 0000000000..d536c7483a --- /dev/null +++ b/test/classes/Command/SetVersionCommandTest.php @@ -0,0 +1,208 @@ +command = new SetVersionCommand(); + } + + /** + * @return array[] + */ + public function dataProviderBadVersions(): array + { + return [ + [''], + ['4.9.0.1'], + ['4.9'], + ['4-9-0-1'], + ['4-9-0'], + ['0-0-0'], + ['0.0.0'], + ['1.0.0'], + ['2.0.0'], + ['3.0.0'], + ['4.0.0'], + ['0.0.-1'], + ['5.000.0'], + ['5.0.000'], + ['5.0.0-'], + ['5.0.0-foo bar'], + ]; + } + + /** + * @dataProvider dataProviderBadVersions + */ + public function testGetGeneratedClassInvalidVersion(string $version): void + { + if (! class_exists(Command::class)) { + $this->markTestSkipped('The Symfony Console is missing'); + } + + $this->expectException(RangeException::class); + $this->expectExceptionMessage('The version number is in the wrong format: ' . $version); + $this->callFunction( + $this->command, + SetVersionCommand::class, + 'getGeneratedClass', + [$version] + ); + } + + /** + * @return array[] + */ + public function dataProviderGoodVersions(): array + { + return [ + [ + '5.0.0-rc1', + ' public const VERSION = \'5.0.0-rc1\' . VERSION_SUFFIX;' . "\n" + . ' public const SERIES = \'5.0\';' . "\n" + . ' public const MAJOR = 5;' . "\n" + . ' public const MINOR = 0;' . "\n" + . ' public const PATCH = 0;' . "\n" + . ' public const ID = 50000;' . "\n" + . ' public const PRE_RELEASE_NAME = \'rc1\';' . "\n" + . ' public const IS_DEV = false;', + ], + [ + '5.0.0-beta', + ' public const VERSION = \'5.0.0-beta\' . VERSION_SUFFIX;' . "\n" + . ' public const SERIES = \'5.0\';' . "\n" + . ' public const MAJOR = 5;' . "\n" + . ' public const MINOR = 0;' . "\n" + . ' public const PATCH = 0;' . "\n" + . ' public const ID = 50000;' . "\n" + . ' public const PRE_RELEASE_NAME = \'beta\';' . "\n" + . ' public const IS_DEV = false;', + ], + [ + '5.0.0-beta1', + ' public const VERSION = \'5.0.0-beta1\' . VERSION_SUFFIX;' . "\n" + . ' public const SERIES = \'5.0\';' . "\n" + . ' public const MAJOR = 5;' . "\n" + . ' public const MINOR = 0;' . "\n" + . ' public const PATCH = 0;' . "\n" + . ' public const ID = 50000;' . "\n" + . ' public const PRE_RELEASE_NAME = \'beta1\';' . "\n" + . ' public const IS_DEV = false;', + ], + [ + '5.0.0-alpha', + ' public const VERSION = \'5.0.0-alpha\' . VERSION_SUFFIX;' . "\n" + . ' public const SERIES = \'5.0\';' . "\n" + . ' public const MAJOR = 5;' . "\n" + . ' public const MINOR = 0;' . "\n" + . ' public const PATCH = 0;' . "\n" + . ' public const ID = 50000;' . "\n" + . ' public const PRE_RELEASE_NAME = \'alpha\';' . "\n" + . ' public const IS_DEV = false;', + ], + [ + '5.0.0-alpha1', + ' public const VERSION = \'5.0.0-alpha1\' . VERSION_SUFFIX;' . "\n" + . ' public const SERIES = \'5.0\';' . "\n" + . ' public const MAJOR = 5;' . "\n" + . ' public const MINOR = 0;' . "\n" + . ' public const PATCH = 0;' . "\n" + . ' public const ID = 50000;' . "\n" + . ' public const PRE_RELEASE_NAME = \'alpha1\';' . "\n" + . ' public const IS_DEV = false;', + ], + [ + '5.0.0-alpha1', + ' public const VERSION = \'5.0.0-alpha1\' . VERSION_SUFFIX;' . "\n" + . ' public const SERIES = \'5.0\';' . "\n" + . ' public const MAJOR = 5;' . "\n" + . ' public const MINOR = 0;' . "\n" + . ' public const PATCH = 0;' . "\n" + . ' public const ID = 50000;' . "\n" + . ' public const PRE_RELEASE_NAME = \'alpha1\';' . "\n" + . ' public const IS_DEV = false;', + ], + [ + '5.1.0-dev', + ' public const VERSION = \'5.1.0-dev\' . VERSION_SUFFIX;' . "\n" + . ' public const SERIES = \'5.1\';' . "\n" + . ' public const MAJOR = 5;' . "\n" + . ' public const MINOR = 1;' . "\n" + . ' public const PATCH = 0;' . "\n" + . ' public const ID = 50100;' . "\n" + . ' public const PRE_RELEASE_NAME = \'dev\';' . "\n" + . ' public const IS_DEV = true;', + ], + [ + '9.99.99-dev', + ' public const VERSION = \'9.99.99-dev\' . VERSION_SUFFIX;' . "\n" + . ' public const SERIES = \'9.99\';' . "\n" + . ' public const MAJOR = 9;' . "\n" + . ' public const MINOR = 99;' . "\n" + . ' public const PATCH = 99;' . "\n" + . ' public const ID = 99999;' . "\n" + . ' public const PRE_RELEASE_NAME = \'dev\';' . "\n" + . ' public const IS_DEV = true;', + ], + ]; + } + + /** + * @dataProvider dataProviderGoodVersions + */ + public function testGetGeneratedClassValidVersion(string $version, string $content): void + { + if (! class_exists(Command::class)) { + $this->markTestSkipped('The Symfony Console is missing'); + } + + $output = $this->callFunction( + $this->command, + SetVersionCommand::class, + 'getGeneratedClass', + [$version] + ); + $template = <<<'PHP' +assertSame( + sprintf($template, $content), + $output + ); + } +} diff --git a/test/classes/VersionTest.php b/test/classes/VersionTest.php new file mode 100644 index 0000000000..607c26541b --- /dev/null +++ b/test/classes/VersionTest.php @@ -0,0 +1,34 @@ +assertIsString(Version::VERSION); + $this->assertNotEmpty(Version::VERSION); + $this->assertStringContainsString(Version::SERIES, Version::VERSION, 'x.y must be found in x.y.z'); + $this->assertIsInt(Version::MAJOR); + $this->assertIsInt(Version::MINOR); + $this->assertIsInt(Version::PATCH); + $this->assertTrue(Version::MAJOR >= 5);// @phpstan-ignore-line Just checking + $this->assertTrue(Version::MINOR >= 0);// @phpstan-ignore-line Just checking + $this->assertTrue(Version::PATCH >= 0);// @phpstan-ignore-line Just checking + $this->assertTrue(Version::ID >= 50000);// @phpstan-ignore-line Just checking + if (defined('VERSION_SUFFIX')) { + $this->assertIsString(VERSION_SUFFIX); + } + $this->assertIsInt(Version::ID); + $this->assertIsString(Version::PRE_RELEASE_NAME); + $this->assertIsBool(Version::IS_DEV); + } +}