From 1dfa92a56ddeb1de85874465a692fafd396b0c3a Mon Sep 17 00:00:00 2001 From: Guy Sartorelli <36352093+GuySartorelli@users.noreply.github.com> Date: Tue, 7 May 2024 11:22:22 +1200 Subject: [PATCH] NEW Use supported modules repo for branch-based logic (#87) --- .editorconfig | 16 ++ .gitignore | 4 +- action.php | 7 + action.yml | 24 +++ composer.json | 8 + consts.php | 199 ++----------------------- hardcoded.php | 17 ++- job_creator.php | 313 ++++++++++++++++++--------------------- tests/JobCreatorTest.php | 40 ++--- 9 files changed, 239 insertions(+), 389 deletions(-) create mode 100644 .editorconfig create mode 100644 composer.json diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..50eff41 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,16 @@ +# For more information about the properties used in +# this file, please see the EditorConfig documentation: +# http://editorconfig.org/ + +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_size = 4 +indent_style = space +insert_final_newline = true +trim_trailing_whitespace = true + +[*.md] +trim_trailing_whitespace = false diff --git a/.gitignore b/.gitignore index 8b2ccc7..2f297e8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ .phpunit.result.cache .cow.pat.json -.vscode \ No newline at end of file +.vscode +composer.lock +vendor/ \ No newline at end of file diff --git a/action.php b/action.php index dabbdbb..20770dd 100644 --- a/action.php +++ b/action.php @@ -1,5 +1,12 @@ [ - '7.1', - '7.2', - '7.3', - '7.4' - ], - '4.10' => [ - '7.3', - '7.4', - '8.0', - ], - '4.11' => [ - '7.4', - '8.0', - '8.1', - ], - '4' => [ - '7.4', - '8.0', - '8.1', - ], - '5.0' => [ - '8.1', - '8.2', - ], - '5.1' => [ - '8.1', - '8.2', - ], - '5.2' => [ - '8.1', - '8.2', - '8.3', - ], - '5' => [ - '8.1', - '8.2', - '8.3', - ], - '6' => [ - '8.1', - '8.2', - '8.3', - ], -]; - const DB_MYSQL_57 = 'mysql57'; const DB_MYSQL_57_PDO = 'mysql57pdo'; const DB_MYSQL_80 = 'mysql80'; const DB_PGSQL = 'pgsql'; const DB_MARIADB = 'mariadb'; -// Used when determining the version of installer to used. Intentionally doesn't include recipes -const LOCKSTEPPED_REPOS = [ - 'silverstripe-admin', - 'silverstripe-asset-admin', - 'silverstripe-assets', - 'silverstripe-campaign-admin', - 'silverstripe-cms', - 'silverstripe-errorpage', - 'silverstripe-framework', - 'silverstripe-reports', - 'silverstripe-siteconfig', - 'silverstripe-versioned', - 'silverstripe-versioned-admin', +/** Repositories which must not have installer included in the matrix, even though their type suggests they should */ +const NO_INSTALLER_REPOS = [ + 'silverstripe-config', +]; + +/** Repositories that either don't have a composer type or have a weird composer type, though we still want installer */ +const FORCE_INSTALLER_REPOS = [ + 'silverstripe-behat-extension', + 'silverstripe-serve', // recipe-solr-search doesn't include recipe-cms or recipe-core unlike our other recipes 'recipe-solr-search', // recipe-blog requires a theme for the unit tests to work, the config and dependency for which are @@ -75,135 +23,6 @@ 'recipe-blog', ]; -// lockstepped repos that are "three less" e.g. silverstripe/admin 1 is for CMS 4 -const LOCKSTEPPED_REPOS_VERSION_IS_THREE_LESS = [ - 'silverstripe-admin', - 'silverstripe-asset-admin', - 'silverstripe-assets', - 'silverstripe-campaign-admin', - 'silverstripe-errorpage', - 'silverstripe-versioned', - 'silverstripe-versioned-admin', -]; - -// Repositories that do not require silverstripe/installer to be explicitly added as a dependency for testing -const NO_INSTALLER_LOCKSTEPPED_REPOS = [ - // these are/include recipe-cms or recipe-core, so we don't want to composer require installer - // in .travis.yml they used the 'self' provision rather than 'standard' - 'recipe-authoring-tools', - 'recipe-ccl', - 'recipe-cms', - 'recipe-collaboration', - 'recipe-content-blocks', - 'recipe-core', - 'recipe-form-building', - 'recipe-kitchen-sink', - 'recipe-reporting-tools', - 'recipe-services', - 'silverstripe-installer', -]; - -const NO_INSTALLER_UNLOCKSTEPPED_REPOS = [ - 'silverstripe-config', -]; - -// Repositories that either don't have a composer type or have a weird composer type, though we still want installer -const FORCE_INSTALLER_UNLOCKEDSTEPPED_REPOS = [ - 'silverstripe-behat-extension', - 'silverstripe-serve', -]; - -const CMS_TO_REPO_MAJOR_VERSIONS = [ - '4' => [ - 'recipe-authoring-tools' => '1', - 'recipe-blog' => '1', - 'recipe-ccl' => '2', - 'recipe-cms' => '4', - 'recipe-collaboration' => '1', - 'recipe-content-blocks' => '2', - 'recipe-core' => '4', - 'recipe-form-building' => '1', - 'recipe-kitchen-sink' => '4', - 'recipe-reporting-tools' => '1', - 'recipe-services' => '1', - 'silverstripe-installer' => '4', - ], - '5' => [ - 'MinkFacebookWebDriver' => '2', - 'recipe-authoring-tools' => '2', - 'recipe-blog' => '2', - 'recipe-ccl' => '3', - 'recipe-cms' => '5', - 'recipe-collaboration' => '2', - 'recipe-content-blocks' => '3', - 'recipe-core' => '5', - 'recipe-form-building' => '2', - 'recipe-kitchen-sink' => '5', - 'recipe-reporting-tools' => '2', - 'recipe-services' => '2', - 'recipe-testing' => '3', - 'silverstripe-installer' => '5', - 'silverstripe-admin' => '2', - 'silverstripe-asset-admin' => '2', - 'silverstripe-assets' => '2', - 'silverstripe-behat-extension' => '5', - 'silverstripe-campaign-admin' => '2', - 'silverstripe-cms' => '5', - 'silverstripe-errorpage' => '2', - 'silverstripe-event-dispatcher' => '1', - 'silverstripe-framework' => '5', - 'silverstripe-frameworktest' => '1', - 'silverstripe-graphql' => '5', - 'silverstripe-login-forms' => '5', - 'silverstripe-mimevalidator' => '3', - 'silverstripe-registry' => '3', - 'silverstripe-reports' => '5', - 'silverstripe-serve' => '3', - 'silverstripe-session-manager' => '2', - 'silverstripe-siteconfig' => '5', - 'silverstripe-testsession' => '3', - 'silverstripe-versioned' => '2', - 'silverstripe-versioned-admin' => '2', - ], - '6' => [ - 'MinkFacebookWebDriver' => '3', - 'recipe-authoring-tools' => '3', - 'recipe-blog' => '3', - 'recipe-ccl' => '4', - 'recipe-cms' => '6', - 'recipe-collaboration' => '3', - 'recipe-content-blocks' => '4', - 'recipe-core' => '6', - 'recipe-form-building' => '3', - 'recipe-kitchen-sink' => '6', - 'recipe-reporting-tools' => '3', - 'recipe-services' => '3', - 'recipe-testing' => '4', - 'silverstripe-installer' => '6', - 'silverstripe-admin' => '3', - 'silverstripe-asset-admin' => '3', - 'silverstripe-assets' => '3', - 'silverstripe-behat-extension' => '6', - 'silverstripe-campaign-admin' => '3', - 'silverstripe-cms' => '6', - 'silverstripe-errorpage' => '3', - 'silverstripe-event-dispatcher' => '2', - 'silverstripe-framework' => '6', - 'silverstripe-frameworktest' => '2', - 'silverstripe-graphql' => '6', - 'silverstripe-login-forms' => '6', - 'silverstripe-mimevalidator' => '4', - 'silverstripe-registry' => '4', - 'silverstripe-reports' => '6', - 'silverstripe-serve' => '4', - 'silverstripe-session-manager' => '3', - 'silverstripe-siteconfig' => '6', - 'silverstripe-testsession' => '4', - 'silverstripe-versioned' => '3', - 'silverstripe-versioned-admin' => '3', - ], -]; - // use hardcoded.php to bulk update update this after creating a .cow.pat.json // for multiple versions, use an array e.g. silverstripe-mymodule => ['2.3', '2.4'] const INSTALLER_TO_REPO_MINOR_VERSIONS = [ diff --git a/hardcoded.php b/hardcoded.php index 75f23da..d5d3e62 100644 --- a/hardcoded.php +++ b/hardcoded.php @@ -3,6 +3,15 @@ // Used to generate data for consts.php INSTALLER_TO_REPO_MINOR_VERSIONS // Copy .cow.pat.json from the current release into the directory this script is being run from (file is .gitignored) +use SilverStripe\SupportedModules\MetaData; + +$autoloadPath = __DIR__ . '/vendor/autoload.php'; +if (!file_exists($autoloadPath)) { + throw new RuntimeException('Run composer install before this script'); +} + +require_once $autoloadPath; + include 'consts.php'; $filename = '.cow.pat.json'; @@ -29,10 +38,12 @@ function repoName(string $name) function parseNode(string $name, stdClass $node, array &$versions) { + $repoData = MetaData::getMetaDataByPackagistName($name); $repoName = repoName($name); - if (!in_array($repoName, LOCKSTEPPED_REPOS) && - !in_array($repoName, NO_INSTALLER_LOCKSTEPPED_REPOS) && - !in_array($repoName, NO_INSTALLER_UNLOCKSTEPPED_REPOS) + if ( + (!isset($repoData['lockstepped']) || !$repoData['lockstepped']) && + (!isset($repoData['type']) || $repoData['type'] !== 'recipe') && + !in_array($repoName, NO_INSTALLER_REPOS) ) { preg_match('#^([0-9]+\.[0-9]+)#', $node->Version, $m); $versions[$repoName] = $m[1]; diff --git a/job_creator.php b/job_creator.php index c65ac48..c626d82 100644 --- a/job_creator.php +++ b/job_creator.php @@ -1,5 +1,8 @@ repoName = explode('/', $this->githubRepository)[1]; // repo should not use installer - if (in_array($this->repoName, NO_INSTALLER_LOCKSTEPPED_REPOS) || in_array($this->repoName, NO_INSTALLER_UNLOCKSTEPPED_REPOS)) { + if (!$this->needsInstallerVersion()) { return ''; } - $branch = $this->getCleanedBranch(true); - $isReleaseBranch = preg_match('#^[0-9\.]+-release$#', $branch); - $cmsMajor = $this->getCmsMajor(); + + $cmsMajor = BranchLogic::getCmsMajor($this->repoData, $this->branch, $this->getComposerJsonContent()) ?: MetaData::LOWEST_SUPPORTED_CMS_MAJOR; // repo is a lockstepped repo - if (in_array($this->repoName, LOCKSTEPPED_REPOS) && (is_numeric($branch) || $isReleaseBranch)) { - if ($isReleaseBranch) { - return 'dev-' . preg_replace('#^([0-9])#', $cmsMajor, $branch); - } + if (isset($this->repoData['lockstepped']) && $this->repoData['lockstepped'] && is_numeric($this->branch)) { // e.g. ['4', '11'] - $portions = explode('.', $branch); + $portions = explode('.', $this->branch); if (count($portions) == 1) { return $cmsMajor . '.x-dev'; } else { @@ -52,59 +56,47 @@ public function getInstallerVersion( foreach (INSTALLER_TO_REPO_MINOR_VERSIONS[$installerVersion] as $_repo => $_repoVersions) { $repoVersions = is_array($_repoVersions) ? $_repoVersions : [$_repoVersions]; foreach ($repoVersions as $repoVersion) { - if ($this->repoName === $_repo && $repoVersion === preg_replace('#-release$#', '', $branch)) { - if ($isReleaseBranch) { - return 'dev-' . $installerVersion . '-release'; - } + if ($this->repoName === $_repo && $repoVersion === preg_replace('#-release$#', '', $this->branch)) { return $installerVersion . '.x-dev'; } } } } if (file_exists($this->composerJsonPath)) { - $json = json_decode(file_get_contents($this->composerJsonPath)); - // We shouldn't try to infer the installer version for regular repositories - // that weren't already detected via the const-based logic above - $silverstripeRepoTypes = [ - 'silverstripe-vendormodule', - 'silverstripe-module', - 'silverstripe-recipe', - 'silverstripe-theme', - ]; - if ((!isset($json->type) || !in_array($json->type, $silverstripeRepoTypes) - && !in_array($this->repoName, FORCE_INSTALLER_UNLOCKEDSTEPPED_REPOS) - )) { - return ''; - } + $json = $this->getComposerJsonContent(); // has a lockstepped .x-dev requirement in composer.json - foreach (LOCKSTEPPED_REPOS as $lockedSteppedRepo) { - $composerRepo = 'silverstripe/' . str_replace('silverstripe-', '', $lockedSteppedRepo); - if (isset($json->require->{$composerRepo})) { - $version = $json->require->{$composerRepo}; - if (preg_match('#^([0-9\.]+)\.x\-dev$#', $version, $matches)) { - $versionNumber = $matches[1]; - // If the lockstepped dependency is "three less" (e.g. silverstripe/admin is 3 major - // versions behind silverstripe/installer), account for that here. - if (in_array($lockedSteppedRepo, LOCKSTEPPED_REPOS_VERSION_IS_THREE_LESS)) { - $versionNumber += 3; + foreach ($this->lockSteppedRepos as $lockedSteppedRepo => $majorVersionMapping) { + if (isset($json->require->{$lockedSteppedRepo})) { + $version = $json->require->{$lockedSteppedRepo}; + if (preg_match('#^([0-9]+)(\.[0-9]+)?\.x\-dev$#', $version, $matches)) { + $dependencyMajorVersion = $matches[1]; + $dependencyMinorSuffix = $matches[2] ?? null; + $versionNumber = null; + foreach ($majorVersionMapping as $cmsMajorFromMap => $repoBranches) { + if (is_numeric($cmsMajorFromMap) && in_array($dependencyMajorVersion, $repoBranches)) { + $versionNumber = $cmsMajorFromMap . $dependencyMinorSuffix; + break; + } + } + if ($versionNumber !== null) { + return $versionNumber . '.x-dev'; } - return $versionNumber . '.x-dev'; } } } } // fallback to use the next-minor or latest-minor version of installer - $installerVersions = array_keys(INSTALLER_TO_PHP_VERSIONS); + $installerVersions = array_keys(MetaData::PHP_VERSIONS_FOR_CMS_RELEASES); $installerVersions = array_filter($installerVersions, fn($version) => substr($version, 0, 1) === $cmsMajor); - if (preg_match('#^[1-9]+[0-9]*$#', $branch)) { + if (preg_match('#^[1-9]+[0-9]*$#', $this->branch)) { // next-minor e.g. 4 return $cmsMajor . '.x-dev'; } else { // current-minor e.g. 4.11 // remove major versions - $installerVersions = array_diff($installerVersions, ['4', '5', '6', '7', '8', '9']); - // get the minor portions of the verisons e.g. [9, 10, 11] + $installerVersions = array_filter($installerVersions, fn ($v) => !ctype_digit((string) $v)); + // get the minor portions of the versions e.g for ['4.9', '4.10', '4.11'] this returns [9, 10, 11] $minorPortions = array_map(fn($portions) => (int) explode('.', $portions)[1], $installerVersions); if (count($minorPortions) === 0) { return $cmsMajor . '.x-dev'; @@ -134,13 +126,10 @@ public function getInstallerVersion( return $cmsMajor . '.x-dev'; } - if ($isReleaseBranch) { - return 'dev-' . $installerVersion . '-release'; - } return $installerVersion . '.x-dev'; } } - + public function createJob(int $phpIndex, array $opts): array { $default = [ @@ -163,8 +152,8 @@ public function createJob(int $phpIndex, array $opts): array 'endtoend_config' => '', 'js' => false, 'doclinting' => false, - // Needs full setup if installerVersion is set, OR this is one of the no-installer lockstepped repos - 'needs_full_setup' => $this->installerVersion !== '' || in_array($this->repoName, NO_INSTALLER_LOCKSTEPPED_REPOS), + // Needs full setup if installerVersion is set, OR this is a recipe + 'needs_full_setup' => $this->installerVersion !== '' || (!empty($this->repoData) && $this->repoData['type'] === 'recipe'), ]; return array_merge($default, $opts); } @@ -181,7 +170,7 @@ private function isAllowedPhpVersion(string $phpVersion) $this->composerPhpConstraint = null; return true; } - $json = json_decode(file_get_contents($this->composerJsonPath)); + $json = $this->getComposerJsonContent(); if (!isset($json->require->php)) { $this->composerPhpConstraint = null; return true; @@ -248,16 +237,15 @@ private function isAllowedPhpVersion(string $phpVersion) * Get the branch name from the installer version and left only the minor version * e.g. 4.10.x-dev -> 4.10 */ - private function getBranchName(): string + private function getInstallerBranch(): string { $version = str_replace('.x-dev', '', $this->installerVersion); - if (in_array($this->repoName, NO_INSTALLER_LOCKSTEPPED_REPOS)) { - $cmsMajor = $this->getCmsMajor(); - $branch = $this->getCleanedBranch(); - if (preg_match('#^[1-9]$#', $branch)) { - $version = $cmsMajor; - } elseif (preg_match('#^[1-9]\.([0-9]+)$#', $branch, $matches)) { + if (!in_array($this->repoName, FORCE_INSTALLER_REPOS) && isset($this->repoData['type']) && $this->repoData['type'] === 'recipe') { + $cmsMajor = BranchLogic::getCmsMajor($this->repoData, $this->branch, $this->getComposerJsonContent()) ?: MetaData::LOWEST_SUPPORTED_CMS_MAJOR; + if (preg_match('#^[1-9]\.([0-9]+)$#', $this->branch, $matches)) { $version = sprintf('%d.%d', $cmsMajor, $matches[1]); + } else { + $version = $cmsMajor; } } @@ -291,99 +279,10 @@ private function getPhpVersion(int $phpIndex): string return $phpVersion; } } - - throw new Exception("No valid PHP version allowed"); - } - private function getCmsMajor(): string - { - $cmsMajor = $this->getCmsMajorFromBranch(); - if ($cmsMajor == '') { - $cmsMajor = $this->getCmsMajorFromComposerJson(); - } - // fallback to cms 4 - return $cmsMajor ?: '4'; - } - - private function getCmsMajorFromBranch(): string - { - $branch = $this->getCleanedBranch(); - $branchMajor = ''; - if (preg_match('#^[1-9]$#', $branch)) { - $branchMajor = $branch; - } elseif (preg_match('#^([1-9])\.[0-9]+$#', $branch, $matches)) { - $branchMajor = $matches[1]; - } - foreach (array_keys(CMS_TO_REPO_MAJOR_VERSIONS) as $cmsMajor) { - if (isset(CMS_TO_REPO_MAJOR_VERSIONS[$cmsMajor][$this->repoName])) { - if (CMS_TO_REPO_MAJOR_VERSIONS[$cmsMajor][$this->repoName] === $branchMajor) { - return $cmsMajor; - } - } - } - return ''; - } - - private function getCmsMajorFromComposerJson(): string - { - if (!file_exists($this->composerJsonPath)) { - return ''; - } - $json = json_decode(file_get_contents($this->composerJsonPath)); - foreach ($json->require as $dep => $version) { - // will match the first numeric character - if (!preg_match('#([0-9])#', $version, $m)) { - continue; - } - $composerVersionMajor = $m[1]; - if (strpos($dep, '/') === false) { - continue; - } - $repo = ''; - if (preg_match('#^silverstripe/recipe-#', $dep) || - in_array($dep, [ - 'silverstripe/comment-notifications', - 'silverstripe/MinkFacebookWebDriver', - 'silverstripe/vendor-plugin', - ])) { - $repo = str_replace('silverstripe/', '', $dep); - } elseif (preg_match('#^(silverstripe|cwp)/#', $dep)) { - $repo = str_replace('/', '-', $dep); - } - foreach (array_keys(CMS_TO_REPO_MAJOR_VERSIONS) as $cmsMajor) { - if (isset(CMS_TO_REPO_MAJOR_VERSIONS[$cmsMajor][$repo])) { - if (CMS_TO_REPO_MAJOR_VERSIONS[$cmsMajor][$repo] === $composerVersionMajor) { - return $cmsMajor; - } - } - } - } - return ''; + throw new Exception("No valid PHP version allowed"); } - private function getCleanedBranch(bool $allowReleaseSuffix = false): string - { - $branch = $this->branch; - // e.g. pulls/4.10/some-bugfix or pulls/4/some-feature - // for push events to the creative-commoners account - if (preg_match('#^pulls/([0-9\.]+(-release)?)/#', $branch, $matches)) { - $branch = $matches[1]; - } - // fallback to parent branch if available - if ( - !is_numeric($branch) && - $this->parentBranch && - (is_numeric($this->parentBranch) || preg_match('#^[0-9\.]+-release$#', $this->parentBranch)) - ) { - $branch = $this->parentBranch; - } - // e.g. 4.10-release - if (!$allowReleaseSuffix) { - $branch = preg_replace('#^([0-9\.]+)-release$#', '$1', $branch); - } - return $branch; - } - private function parseBoolValue($value): bool { return ($value === true || $value === 'true'); @@ -408,17 +307,18 @@ private function createPhpunitJobs( 'phpunit' => true, 'phpunit_suite' => $suite, ]); - // this same mysql pdo test is also created for the phpcoverage job, so only add it here if - // not creating a phpcoverage job. - // note: phpcoverage also runs unit tests - if ($this->getCmsMajor() === '4' && !$this->doRunPhpCoverage($run)) { - $matrix['include'][] = $this->createJob(1, [ - 'db' => DB_MYSQL_57_PDO, - 'phpunit' => true, - 'phpunit_suite' => $suite, - ]); - } - if ($this->getCmsMajor() === '4') { + $cmsMajor = BranchLogic::getCmsMajor($this->repoData, $this->branch, $this->getComposerJsonContent()) ?: MetaData::LOWEST_SUPPORTED_CMS_MAJOR; + if ($cmsMajor === '4') { + if (!$this->doRunPhpCoverage($run)) { + // this same mysql pdo test is also created for the phpcoverage job, so only add it here if + // not creating a phpcoverage job. + // note: phpcoverage also runs unit tests + $matrix['include'][] = $this->createJob(1, [ + 'db' => DB_MYSQL_57_PDO, + 'phpunit' => true, + 'phpunit_suite' => $suite, + ]); + } $matrix['include'][] = $this->createJob(3, [ 'db' => DB_MYSQL_80, 'phpunit' => true, @@ -444,7 +344,7 @@ private function createPhpunitJobs( */ private function getListOfPhpVersionsByBranchName(): array { - return INSTALLER_TO_PHP_VERSIONS[$this->getBranchName()] ?? INSTALLER_TO_PHP_VERSIONS['4']; + return MetaData::PHP_VERSIONS_FOR_CMS_RELEASES[$this->getInstallerBranch()] ?? MetaData::PHP_VERSIONS_FOR_CMS_RELEASES['4']; } /** @@ -515,9 +415,10 @@ private function buildDynamicMatrix( 'phplinting' => true ]); } + $cmsMajor = BranchLogic::getCmsMajor($this->repoData, $this->branch, $this->getComposerJsonContent()) ?: MetaData::LOWEST_SUPPORTED_CMS_MAJOR; // phpcoverage also runs unit tests if ($this->doRunPhpCoverage($run, $this->githubRepository)) { - if ($simpleMatrix || $composerInstall || $this->getCmsMajor() !== '4') { + if ($simpleMatrix || $composerInstall || $cmsMajor !== '4') { $matrix['include'][] = $this->createJob(0, [ 'phpcoverage' => true ]); @@ -530,7 +431,6 @@ private function buildDynamicMatrix( } // endtoend / behat if ($run['endtoend'] && file_exists('behat.yml')) { - $cmsMajor = $this->getCmsMajor(); $graphql3 = !$simpleMatrix && $cmsMajor == '4'; $job = $this->createJob(0, [ 'endtoend' => true, @@ -593,11 +493,9 @@ public function getInputs(string $yml): array public function createJson(string $yml): string { $inputs = $this->getInputs($yml); - // $myRef will either be a branch for push (i.e cron) and pull-request (target branch), or a semver tag - $myRef = $inputs['github_my_ref']; - $unstableTagRx = '#^([0-9]+)\.([0-9]+)\.[0-9]+-((alpha|beta|rc))[0-9]+$#'; - $isTag = preg_match('#^([0-9]+)\.([0-9]+)\.[0-9]+$#', $myRef, $m) || preg_match($unstableTagRx, $myRef, $m); - $this->branch = $isTag ? sprintf('%d.%d', $m[1], $m[2]) : $myRef; + $this->githubRepository = $inputs['github_repository']; + $this->repoName = explode('/', $this->githubRepository)[1]; + $this->parseRepositoryMetadata(); // parent branch is a best attempt to get the parent branch of the branch via bash // it's used for working out the version of installer to use on github push events @@ -605,7 +503,12 @@ public function createJson(string $yml): string $this->parentBranch = $inputs['parent_branch']; } - $this->githubRepository = $inputs['github_repository']; + // $myRef will either be a branch for push (i.e cron) and pull-request (target branch), or a semver tag + $myRef = $inputs['github_my_ref']; + $unstableTagRx = '#^([0-9]+)\.([0-9]+)\.[0-9]+-((alpha|beta|rc))[0-9]+$#'; + $isTag = preg_match('#^([0-9]+)\.([0-9]+)\.[0-9]+$#', $myRef, $m) || preg_match($unstableTagRx, $myRef, $m); + $this->branch = $this->getCleanedBranch($isTag ? sprintf('%d.%d', $m[1], $m[2]) : $myRef); + $this->installerVersion = $this->getInstallerVersion(); if (preg_match($unstableTagRx, $myRef, $m)) { $this->installerVersion = str_replace('.x-dev', '.0-' . $m[3] . '1', $this->installerVersion); @@ -647,7 +550,7 @@ public function createJson(string $yml): string $matrix = ['include' => []]; if ($composerInstall) { - $json = json_decode(file_get_contents($this->composerJsonPath)); + $json = $this->getComposerJsonContent(); if (isset($json->config->platform->php) && preg_match('#^[0-9\.]+$#', $json->config->platform->php)) { $this->phpVersionOverride = $json->config->platform->php; } @@ -737,4 +640,76 @@ public function createJson(string $yml): string $json = str_replace("\n", '', $json); return trim($json); } -} \ No newline at end of file + + public function parseRepositoryMetadata() + { + $this->repoData = MetaData::getMetaDataForRepository($this->githubRepository, true); + $this->lockSteppedRepos = MetaData::getMetaDataForLocksteppedRepos(); + } + + public function getCleanedBranch(string $branch): string + { + // e.g. pulls/4.10/some-bugfix or pulls/4/some-feature + // for push events to the creative-commoners account + if (preg_match('#^pulls/([0-9]+(\.[0-9]+)*)/#', $branch, $matches)) { + return $matches[1]; + } + // fallback to parent branch if available + if ( + !$this->branchIsSemver($branch) && + $this->parentBranch && + $this->branchIsSemver($this->parentBranch) + ) { + return $this->parentBranch; + } + return $branch; + } + + private function branchIsSemver(string $branch): bool + { + return preg_match('/^[0-9]+(\.[0-9]+)*$/', $branch); + } + + private function needsInstallerVersion(): bool + { + if (in_array($this->repoName, FORCE_INSTALLER_REPOS)) { + return true; + } + if (in_array($this->repoName, NO_INSTALLER_REPOS)) { + return false; + } + // We shouldn't try to infer the installer version for regular repositories + // that we don't know anything about + if (empty($this->repoData)) { + if (!file_exists($this->composerJsonPath)) { + return false; + } + $json = $this->getComposerJsonContent(); + $silverstripeRepoTypes = [ + 'silverstripe-vendormodule', + 'silverstripe-module', + 'silverstripe-recipe', + 'silverstripe-theme', + ]; + return isset($json->type) && in_array($json->type, $silverstripeRepoTypes); + } + if ($this->repoData['type'] === 'recipe') { + return false; + } + return true; + } + + private function getComposerJsonContent(): ?stdClass + { + if (!$this->composerJsonContent) { + if (!file_exists($this->composerJsonPath)) { + return null; + } + $this->composerJsonContent = json_decode(file_get_contents($this->composerJsonPath)); + if ($this->composerJsonContent === null) { + throw new RuntimeException('Could not decode composer.json - last error was: ' . json_last_error_msg()); + } + } + return $this->composerJsonContent; + } +} diff --git a/tests/JobCreatorTest.php b/tests/JobCreatorTest.php index 4672efa..21a0d5f 100644 --- a/tests/JobCreatorTest.php +++ b/tests/JobCreatorTest.php @@ -1,6 +1,7 @@ githubRepository = $githubRepository; $creator->repoName = explode('/', $githubRepository)[1]; - $creator->branch = $branch; + $creator->branch = $creator->getCleanedBranch($branch); + $creator->parseRepositoryMetadata(); $actual = $creator->createJob($phpIndex, $opts); foreach ($expected as $key => $expectedVal) { $this->assertSame($expectedVal, $actual[$key]); @@ -49,22 +51,22 @@ public function provideCreateJob(): array ]], // test that NO_INSTALLER_LOCKSTEPPED_REPOS base max PHP version from $branch ['myaccount/silverstripe-installer', '4.10', 99, [], [ - 'php' => max(INSTALLER_TO_PHP_VERSIONS['4.10']) + 'php' => max(MetaData::PHP_VERSIONS_FOR_CMS_RELEASES['4.10']) ]], ['myaccount/silverstripe-installer', '4.11', 99, [], [ - 'php' => max(INSTALLER_TO_PHP_VERSIONS['4.11']) + 'php' => max(MetaData::PHP_VERSIONS_FOR_CMS_RELEASES['4.11']) ]], ['myaccount/silverstripe-installer', '4', 99, [], [ - 'php' => max(INSTALLER_TO_PHP_VERSIONS['4']) + 'php' => max(MetaData::PHP_VERSIONS_FOR_CMS_RELEASES['4']) ]], ['myaccount/silverstripe-installer', '5.0', 99, [], [ - 'php' => max(INSTALLER_TO_PHP_VERSIONS['5.0']) + 'php' => max(MetaData::PHP_VERSIONS_FOR_CMS_RELEASES['5.0']) ]], ['myaccount/silverstripe-installer', '5', 99, [], [ - 'php' => max(INSTALLER_TO_PHP_VERSIONS['5']) + 'php' => max(MetaData::PHP_VERSIONS_FOR_CMS_RELEASES['5']) ]], ['myaccount/silverstripe-installer', '6', 99, [], [ - 'php' => max(INSTALLER_TO_PHP_VERSIONS['6']) + 'php' => max(MetaData::PHP_VERSIONS_FOR_CMS_RELEASES['6']) ]], ]; } @@ -91,7 +93,8 @@ public function testGetInstallerVersion( } $creator->githubRepository = $githubRepository; $creator->repoName = explode('/', $githubRepository)[1]; - $creator->branch = $branch; + $creator->branch = $creator->getCleanedBranch($branch); + $creator->parseRepositoryMetadata(); $actual = $creator->getInstallerVersion($installerBranchesJson); $this->assertSame($expected, $actual); } finally { @@ -105,9 +108,7 @@ private function getInstallerBranchesJson(): array { return [ ['name' => '4'], - ['name' => '4-release'], ['name' => '4.10'], - ['name' => '4.10-release'], ['name' => '4.11'], ['name' => '4.12'], ['name' => '4.13'], @@ -121,7 +122,7 @@ private function getInstallerBranchesJson(): array private function getCurrentMinorInstallerVersion(string $cmsMajor): string { - $versions = array_keys(INSTALLER_TO_PHP_VERSIONS); + $versions = array_keys(MetaData::PHP_VERSIONS_FOR_CMS_RELEASES); $versions = array_filter($versions, fn($version) => substr($version, 0, 1) === $cmsMajor); natsort($versions); $versions = array_reverse($versions); @@ -131,7 +132,6 @@ private function getCurrentMinorInstallerVersion(string $cmsMajor): string public function provideGetInstallerVersion(): array { $nextMinorCms4 = '4.x-dev'; - $nextMinorCms4Release = 'dev-' . $this->getCurrentMinorInstallerVersion('4') . '-release'; $currentMinorCms4 = $this->getCurrentMinorInstallerVersion('4') . '.x-dev'; return [ // no-installer repo @@ -141,8 +141,6 @@ public function provideGetInstallerVersion(): array ['myaccount/recipe-cms', 'pulls/4/myfeature', ''], ['myaccount/recipe-cms', 'pulls/4.10/myfeature', ''], ['myaccount/recipe-cms', 'pulls/burger/myfeature', ''], - ['myaccount/recipe-cms', '4-release', ''], - ['myaccount/recipe-cms', '4.10-release', ''], ['myaccount/recipe-cms', '5', ''], ['myaccount/recipe-cms', '5.1', ''], ['myaccount/recipe-cms', '6', ''], @@ -154,9 +152,6 @@ public function provideGetInstallerVersion(): array ['myaccount/silverstripe-framework', 'pulls/4/mybugfix', '4.x-dev'], ['myaccount/silverstripe-framework', 'pulls/4.10/mybugfix', '4.10.x-dev'], ['myaccount/silverstripe-framework', 'pulls/burger/myfeature', $currentMinorCms4], - ['myaccount/silverstripe-framework', '4-release', 'dev-4-release'], - ['myaccount/silverstripe-framework', '4.10-release', 'dev-4.10-release'], - ['myaccount/silverstripe-framework', 'pulls/4.10-release/some-change', 'dev-4.10-release'], ['myaccount/silverstripe-framework', '5', '5.x-dev'], ['myaccount/silverstripe-framework', '5.1', '5.1.x-dev'], ['myaccount/silverstripe-framework', '6', '6.x-dev'], @@ -167,9 +162,6 @@ public function provideGetInstallerVersion(): array ['myaccount/silverstripe-admin', 'pulls/1/mybugfix', '4.x-dev'], ['myaccount/silverstripe-admin', 'pulls/1.10/mybugfix', '4.10.x-dev'], ['myaccount/silverstripe-admin', 'pulls/burger/myfeature', $currentMinorCms4], - ['myaccount/silverstripe-admin', '1-release', 'dev-4-release'], - ['myaccount/silverstripe-admin', '1.10-release', 'dev-4.10-release'], - ['myaccount/silverstripe-admin', 'pulls/1.10-release/some-change', 'dev-4.10-release'], ['myaccount/silverstripe-admin', '2', '5.x-dev'], ['myaccount/silverstripe-admin', '2.1', '5.1.x-dev'], ['myaccount/silverstripe-admin', '3', '6.x-dev'], @@ -180,16 +172,12 @@ public function provideGetInstallerVersion(): array ['myaccount/silverstripe-tagfield', 'pulls/2/mybugfix', $nextMinorCms4], ['myaccount/silverstripe-tagfield', 'pulls/2.9/mybugfix', $currentMinorCms4], ['myaccount/silverstripe-tagfield', 'pulls/burger/myfeature', $currentMinorCms4], - ['myaccount/silverstripe-tagfield', '2-release', 'dev-' . $this->getCurrentMinorInstallerVersion('4') . '-release'], - ['myaccount/silverstripe-tagfield', '2.9-release', $nextMinorCms4Release], - ['myaccount/silverstripe-tagfield', 'pulls/2.9-release/some-change', $nextMinorCms4Release], // non-lockstepped repo, fallback to major version of installer (is missing 6.0 installer branch) ['myaccount/silverstripe-tagfield', '4.0', '6.x-dev', [['name' => '6']], ['silverstripe/framework' => '^6']], // hardcoded repo version ['myaccount/silverstripe-session-manager', '1', $nextMinorCms4], ['myaccount/silverstripe-session-manager', '1.2', '4.10.x-dev'], ['myaccount/silverstripe-session-manager', 'burger', $currentMinorCms4], - ['myaccount/silverstripe-session-manager', '1.2-release', 'dev-4.10-release'], // hardcoded repo version using array ['myaccount/silverstripe-html5', '2', $nextMinorCms4], ['myaccount/silverstripe-html5', '2.2', '4.10.x-dev'], @@ -635,7 +623,7 @@ public function provideParentBranch(): array parent_branch: '4.10-release' EOT ]), - 'dev-4.10-release' + '4.11.x-dev' ], [ implode("\n", [ @@ -1022,7 +1010,7 @@ public function testComposerInstall( $yml = implode("\n", [ str_replace('composer_install: false', 'composer_install: ' . $composerInstall, $this->getGenericYml()), <<