From 7ad1727e90c7137a6fa3c13d25a4d3489aaaec25 Mon Sep 17 00:00:00 2001 From: Andrew Nicols Date: Fri, 27 Sep 2024 22:54:59 +0800 Subject: [PATCH 01/12] Move version bumper method to library class --- bumpversions.php | 306 ++------------------------------------------- src/helper.php | 313 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 324 insertions(+), 295 deletions(-) create mode 100644 src/helper.php diff --git a/bumpversions.php b/bumpversions.php index 63080a1..289c17c 100644 --- a/bumpversions.php +++ b/bumpversions.php @@ -3,21 +3,25 @@ // Perth is the center of the world. Anything to object? date_default_timezone_set('Australia/Perth'); +require_once(__DIR__ . '/src/helper.php'); + +$helper = new \MoodleHQ\MoodleRelease\Helper(); + // We need the branch and the bump type (weekly. minor, major) try { $shortoptions = 'b:t:p:r:d:i:'; $longoptions = array('branch:', 'type:', 'path:', 'rc:', 'date:', 'isdevbranch:'); $options = getopt($shortoptions, $longoptions); - $branch = get_option_from_options_array($options, 'b', 'branch'); - $type = get_option_from_options_array($options, 't', 'type'); - $path = get_option_from_options_array($options, 'p', 'path'); - $rc = get_option_from_options_array($options, 'r', 'rc'); - $date = get_option_from_options_array($options, 'd', 'date'); - $isdevbranch = (bool)get_option_from_options_array($options, 'i', 'isdevbranch'); + $branch = $helper->get_option_from_options_array($options, 'b', 'branch'); + $type = $helper->get_option_from_options_array($options, 't', 'type'); + $path = $helper->get_option_from_options_array($options, 'p', 'path'); + $rc = $helper->get_option_from_options_array($options, 'r', 'rc'); + $date = $helper->get_option_from_options_array($options, 'd', 'date'); + $isdevbranch = (bool)$helper->get_option_from_options_array($options, 'i', 'isdevbranch'); $path = rtrim($path, '/').'/version.php'; - $release = bump_version($path, $branch, $type, $rc, $date, $isdevbranch); + $release = $helper->bump_version($path, $branch, $type, $rc, $date, $isdevbranch); $result = 0; } catch (Exception $ex) { $release = $ex->getMessage(); @@ -25,291 +29,3 @@ } echo $release; exit($result); - - - -function bump_version($path, $branch, $type, $rc, $date, $isdevbranch) { - - validate_branch($branch); - validate_type($type); - validate_path($path); - - $versionfile = file_get_contents($path); - - validate_version_file($versionfile, $branch); - - $is19 = ($branch === 'MOODLE_19_STABLE'); - $isstable = branch_is_stable($branch, $isdevbranch); - $today = date('Ymd'); - - $integerversioncurrent = null; - $decimalversioncurrent = null; - $commentcurrent = null; - $releasecurrent = null; - $buildcurrent = null; - $branchcurrent = null; - $maturitycurrent = null; - - $branchquote = null; - $releasequote = null; - - $integerversionnew = null; - $decimalversionnew = null; - $commentnew = null; - $releasenew = null; - $buildnew = null; - $branchnew = null; - $maturitynew = null; - - if (!preg_match('#^ *\$version *= *(?P\d{10})\.(?P\d{2})\d?[^\/]*(?P/[^\n]*)#m', $versionfile, $matches)) { - throw new Exception('Could not determine version.', __LINE__); - } - $integerversionnew = $integerversioncurrent = $matches['integer']; - $decimalversionnew = $decimalversioncurrent = $matches['decimal']; - $commentnew = $commentcurrent = $matches['comment']; - - if (!preg_match('#^ *\$release *= *(?P\'|")(?P[^ \+]+\+?) *\(Build: (?P\d{8})\)\1#m', $versionfile, $matches)) { - throw new Exception('Could not determine the release.', __LINE__); - } - $releasenew = $releasecurrent = $matches['release']; - $releasequote = $matches['quote']; - $buildcurrent = $matches['build']; - $buildnew = empty($date) ? $today : $date; // Observe forced date. - - if (!$is19) { - if (!preg_match('# *\$branch *= *(?P\'|")(?P\d+)\1#m', $versionfile, $matches)) { - throw new Exception('Could not determine branch.', __LINE__); - } - $branchquote = $matches['quote']; - $branchnew = $branchcurrent = $matches['branch']; - if (!preg_match('# *\$maturity *= *(?PMATURITY_[A-Z]+)#m', $versionfile, $matches)) { - throw new Exception('Could not determine maturity.', __LINE__); - } - $maturitynew = $maturitycurrent = $matches['maturity']; - } - - if ($isstable) { - // It's a stable branch. - if ($type === 'weekly') { - // It's a stable branch. We need to bump the minor version and add a + if this was the first - // weekly release after a major or minor release. - if (strpos($releasenew, '+') === false) { - // Add the + - $releasenew .= '+'; - } - $decimalversionnew++; - $maturitynew = 'MATURITY_STABLE'; - } else if ($type === 'minor' || $type === 'major') { - // If it's minor fine, it's if major then stable gets a minor release. - // 2.6+ => 2.6.1 - // 2.6.12+ => 2.6.13 - if (strpos($releasenew, '+') !== false) { - // Strip the +1 off - $releasenew = substr($releasenew, 0, -1); - } - if (preg_match('#^(?P\d+\.\d+)\.(?P\d+)#', $releasenew, $matches)) { - $increment = $matches['increment'] + 1; - $releasenew = $matches['version'].'.'.(string)$increment; - } else { - // First minor release on this stable branch. Yay X.Y.1. - $releasenew .= '.1'; - } - $integerversionnew = (int)$integerversionnew + 1; - $integerversionnew = (string)$integerversionnew; - $decimalversionnew = '00'; - // Now handle build date for releases. - if (empty($date)) { // If no date has been forced, stable minors always are released on Monday. - if ((int)date('N') !== 1) { // If today is not Monday, calculate next one. - $buildnew = date('Ymd', strtotime('next monday')); - } - } - } - - } else { - // Ok it's a development branch. - if ($type === 'weekly' || $type === 'minor') { - // If it's weekly, ok, if it's minor the dev branch doesn't get a minor release so really it's a weekly anyway. - // It's a dev branch. We need to bump the version, if the version is already higher than today*100 then we need - // to bump accordingly. - // If under beta or rc, make weekly behave exactly as on-demand. - if (strpos($releasecurrent, 'beta') !== false or strpos($releasecurrent, 'rc') !== false) { - // Add the + if missing. - if (strpos($releasenew, '+') === false) { - // Add the + - $releasenew .= '+'; - } - list($integerversionnew, $decimalversionnew) = bump_dev_ensure_higher($integerversionnew, $decimalversionnew); - } else if (strpos($releasecurrent, 'dev') === false) { - // Must be immediately after a major release. Bump the release version and set maturity to Alpha. - $releasenew = (float)$releasenew + 0.1; - $releasenew = (string)$releasenew.'dev'; - $maturitynew = 'MATURITY_ALPHA'; - } - list($integerversionnew, $decimalversionnew) = bump_dev_ensure_higher($integerversionnew, $decimalversionnew); - } else if ($type === 'beta') { - $releasenew = preg_replace('#^(\d+.\d+) *(dev|beta)\+?#', '$1', $releasenew); - $branchnew = $branchcurrent; // Branch doesn't change in beta releases ever. - $releasenew .= 'beta'; - list($integerversionnew, $decimalversionnew) = bump_dev_ensure_higher($integerversionnew, $decimalversionnew); - $maturitynew = 'MATURITY_BETA'; - } else if ($type === 'rc') { - $releasenew = preg_replace('#^(\d+.\d+) *(dev|beta|rc\d)\+?#', '$1', $releasenew); - $branchnew = $branchcurrent; // Branch doesn't change in rc releases ever. - $releasenew .= 'rc'.$rc; - list($integerversionnew, $decimalversionnew) = bump_dev_ensure_higher($integerversionnew, $decimalversionnew); - $maturitynew = 'MATURITY_RC'; - } else if ($type === 'on-demand') { - // Add the + if missing (normally applies to post betas & rcs only, - // but it's not wrong to generalize it to any on-demand). - if (strpos($releasenew, '+') === false) { - // Add the + - $releasenew .= '+'; - } - list($integerversionnew, $decimalversionnew) = bump_dev_ensure_higher($integerversionnew, $decimalversionnew); - } else if ($type === 'on-sync') { - $decimalversionnew++; - } else if ($type === 'back-to-dev') { - if (strpos($releasecurrent, 'dev') === false) { // Ensure it's not a "dev" version already. - // Must be immediately after a major release. Bump comment, release and maturity. - $commentnew = '// YYYYMMDD = weekly release date of this DEV branch.'; - // Normalise a little bit the release, getting rid of everything after the numerical part. - $releasenew = preg_replace('/^([0-9.]+).*$/', '\1', $releasenew); - // Split the major and minor parts of the release for further process. - list($releasemajor, $releaseminor) = explode('.', $releasenew); - $releasenew = $releasemajor . '.' . (++$releaseminor); // Increment to next dev version. - $releasenew = $releasenew . 'dev'; - // The branch is the major followed by 2-chars minor. - $branchnew = $releasemajor . str_pad($releaseminor, 2, '0', STR_PAD_LEFT); - $maturitynew = 'MATURITY_ALPHA'; - if (empty($date)) { // If no date has been forced, back-to-dev have same build date than majors. - if ((int)date('N') !== 1) { // If today is not Monday, calculate next one. - $buildnew = date('Ymd', strtotime('next Monday')); - } - } - } - } else { - // Awesome major release! - $releasenew = preg_replace('#^(\d+.\d+) *(dev|beta|rc\d+)\+?#', '$1', $releasenew); - $branchnew = $branchcurrent; // Branch doesn't change in major releases ever. - list($integerversionnew, $decimalversionnew) = bump_dev_ensure_higher($integerversionnew, $decimalversionnew); - $maturitynew = 'MATURITY_STABLE'; - // Now handle builddate for releases. - if (empty($date)) { // If no date has been forced, dev majors always are released on Monday. - if ((int)date('N') !== 1) { // If today is not Monday, calculate next one. - $buildnew = date('Ymd', strtotime('next Monday')); - } - } - $commentnew = '// ' . $buildnew . ' = branching date YYYYMMDD - do not modify!'; - // TODO: Move this to bump_dev_ensure_higher() to keep things clear. Require new params. - // Also force version for major releases. Must match "next Monday" or --date (if specified) - if (empty($date)) { // If no date has been forced, dev majors always are released on Monday. - if ((int)date('N') !== 1) { // If today is not Monday, calculate next one. - $integerversionnew = date('Ymd', strtotime('next Monday')) . '00'; - } - } else { - $integerversionnew = $date . '00'; // Apply $date also to major versions. - } - $decimalversionnew = '00'; // Majors always have the decimal reset to .00. - } - } - - // Replace the old version with the new version. - if (strlen($decimalversionnew) === 1) { - $decimalversionnew = '0'.$decimalversionnew; - } - $versionfile = str_replace($integerversioncurrent.'.'.$decimalversioncurrent, $integerversionnew.'.'.$decimalversionnew, $versionfile); - // Replace the old build with the new build. - $versionfile = str_replace('Build: '.$buildcurrent, 'Build: '.$buildnew, $versionfile); - // Replace the old release with the new release if they've changed. - if ($releasecurrent !== $releasenew) { - $versionfile = str_replace($releasequote.$releasecurrent, $releasequote.$releasenew, $versionfile); - } - // Replace the old comment with the new one if they've changed - if ($commentcurrent !== $commentnew) { - $versionfile = str_replace($commentcurrent, $commentnew, $versionfile); - } - - if (!$is19) { - // Replace the branch value if need be. - if ($branchcurrent !== $branchnew) { - $versionfile = str_replace($branchquote.$branchcurrent.$branchquote, $branchquote.$branchnew.$branchquote, $versionfile); - } - // Replace the maturity value if need be. - if ($maturitycurrent !== $maturitynew) { - $versionfile = str_replace('= '.$maturitycurrent, '= '.$maturitynew, $versionfile); - } - } - - file_put_contents($path, $versionfile); - - return $releasenew; -} - -function bump_dev_ensure_higher($versionint, $versiondec) { - $today = date('Ymd'); - if ($versionint >= $today*100) { - // Integer version is already past today * 100, increment version decimal part instead of integer part. - $versiondec = (int)$versiondec + 1; - $versiondec = (string)$versiondec; - } else { - $versionint = $today.'00'; - $versiondec = '00'; - } - return array($versionint, $versiondec); -} - -function branch_is_stable($branch, $isdevbranch) { - return (strpos($branch, '_STABLE') !== false && !$isdevbranch); -} - -function validate_branch($branch) { - if (!preg_match('#^(main|MOODLE_(\d+)_STABLE)$#', $branch, $matches)) { - throw new Exception('Invalid branch given', __LINE__); - } - return true; -} - -function validate_type($type) { - $types = array('weekly', 'minor', 'major', 'beta', 'rc', 'on-demand', 'on-sync', 'back-to-dev'); - if (!in_array($type, $types)) { - throw new Exception('Invalid type given.', __LINE__); - } - return true; -} - -function validate_path($path) { - if (file_exists($path) && is_readable($path)) { - if (is_writable($path)) { - return true; - } - throw new Exception('Path cannot be written to.', __LINE__); - } - throw new Exception('Invalid path given.', __LINE__); -} - -function validate_version_file($contents, $branch) { - $hasversion = strpos($contents, '$version ') !== false; - $hasrelease = strpos($contents, '$release ') !== false; - $hasbranch = strpos($contents, '$branch ') !== false; - $hasmaturity = strpos($contents, '$maturity ') !== false; - - if ($hasversion && $hasrelease && $hasbranch && $hasmaturity) { - return true; - } - if ($branch === 'MOODLE_19_STABLE' && $hasversion && $hasrelease) { - return true; - } - throw new Exception('Invalid version file found.', __LINE__); -} - -function get_option_from_options_array(array $options, $short, $long) { - if (!isset($options[$short]) && !isset($options[$long])) { - throw new Exception("Required option -$short|--$long must be provided.", __LINE__); - } - if ((isset($options[$short]) && is_array($options[$short])) || - (isset($options[$long]) && is_array($options[$long])) || - (isset($options[$short]) && isset($options[$long]))) { - throw new Exception("Option -$short|--$long specified more than once.", __LINE__); - } - return (isset($options[$short])) ? $options[$short] : $options[$long]; -} diff --git a/src/helper.php b/src/helper.php new file mode 100644 index 0000000..601cabb --- /dev/null +++ b/src/helper.php @@ -0,0 +1,313 @@ +. + +namespace MoodleHQ\MoodleRelease; + +/** + * Helper library for Moodle Release scripts. + * + * @package core + * @copyright 2024 Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class Helper { + function bump_version($path, $branch, $type, $rc, $date, $isdevbranch) { + + validate_branch($branch); + validate_type($type); + validate_path($path); + + $versionfile = file_get_contents($path); + + validate_version_file($versionfile, $branch); + + $is19 = ($branch === 'MOODLE_19_STABLE'); + $isstable = branch_is_stable($branch, $isdevbranch); + $today = date('Ymd'); + + $integerversioncurrent = null; + $decimalversioncurrent = null; + $commentcurrent = null; + $releasecurrent = null; + $buildcurrent = null; + $branchcurrent = null; + $maturitycurrent = null; + + $branchquote = null; + $releasequote = null; + + $integerversionnew = null; + $decimalversionnew = null; + $commentnew = null; + $releasenew = null; + $buildnew = null; + $branchnew = null; + $maturitynew = null; + + if (!preg_match('#^ *\$version *= *(?P\d{10})\.(?P\d{2})\d?[^\/]*(?P/[^\n]*)#m', $versionfile, $matches)) { + throw new Exception('Could not determine version.', __LINE__); + } + $integerversionnew = $integerversioncurrent = $matches['integer']; + $decimalversionnew = $decimalversioncurrent = $matches['decimal']; + $commentnew = $commentcurrent = $matches['comment']; + + if (!preg_match('#^ *\$release *= *(?P\'|")(?P[^ \+]+\+?) *\(Build: (?P\d{8})\)\1#m', $versionfile, $matches)) { + throw new Exception('Could not determine the release.', __LINE__); + } + $releasenew = $releasecurrent = $matches['release']; + $releasequote = $matches['quote']; + $buildcurrent = $matches['build']; + $buildnew = empty($date) ? $today : $date; // Observe forced date. + + if (!$is19) { + if (!preg_match('# *\$branch *= *(?P\'|")(?P\d+)\1#m', $versionfile, $matches)) { + throw new Exception('Could not determine branch.', __LINE__); + } + $branchquote = $matches['quote']; + $branchnew = $branchcurrent = $matches['branch']; + if (!preg_match('# *\$maturity *= *(?PMATURITY_[A-Z]+)#m', $versionfile, $matches)) { + throw new Exception('Could not determine maturity.', __LINE__); + } + $maturitynew = $maturitycurrent = $matches['maturity']; + } + + if ($isstable) { + // It's a stable branch. + if ($type === 'weekly') { + // It's a stable branch. We need to bump the minor version and add a + if this was the first + // weekly release after a major or minor release. + if (strpos($releasenew, '+') === false) { + // Add the + + $releasenew .= '+'; + } + $decimalversionnew++; + $maturitynew = 'MATURITY_STABLE'; + } else if ($type === 'minor' || $type === 'major') { + // If it's minor fine, it's if major then stable gets a minor release. + // 2.6+ => 2.6.1 + // 2.6.12+ => 2.6.13 + if (strpos($releasenew, '+') !== false) { + // Strip the +1 off + $releasenew = substr($releasenew, 0, -1); + } + if (preg_match('#^(?P\d+\.\d+)\.(?P\d+)#', $releasenew, $matches)) { + $increment = $matches['increment'] + 1; + $releasenew = $matches['version'].'.'.(string)$increment; + } else { + // First minor release on this stable branch. Yay X.Y.1. + $releasenew .= '.1'; + } + $integerversionnew = (int)$integerversionnew + 1; + $integerversionnew = (string)$integerversionnew; + $decimalversionnew = '00'; + // Now handle build date for releases. + if (empty($date)) { // If no date has been forced, stable minors always are released on Monday. + if ((int)date('N') !== 1) { // If today is not Monday, calculate next one. + $buildnew = date('Ymd', strtotime('next monday')); + } + } + } + + } else { + // Ok it's a development branch. + if ($type === 'weekly' || $type === 'minor') { + // If it's weekly, ok, if it's minor the dev branch doesn't get a minor release so really it's a weekly anyway. + // It's a dev branch. We need to bump the version, if the version is already higher than today*100 then we need + // to bump accordingly. + // If under beta or rc, make weekly behave exactly as on-demand. + if (strpos($releasecurrent, 'beta') !== false or strpos($releasecurrent, 'rc') !== false) { + // Add the + if missing. + if (strpos($releasenew, '+') === false) { + // Add the + + $releasenew .= '+'; + } + list($integerversionnew, $decimalversionnew) = bump_dev_ensure_higher($integerversionnew, $decimalversionnew); + } else if (strpos($releasecurrent, 'dev') === false) { + // Must be immediately after a major release. Bump the release version and set maturity to Alpha. + $releasenew = (float)$releasenew + 0.1; + $releasenew = (string)$releasenew.'dev'; + $maturitynew = 'MATURITY_ALPHA'; + } + list($integerversionnew, $decimalversionnew) = bump_dev_ensure_higher($integerversionnew, $decimalversionnew); + } else if ($type === 'beta') { + $releasenew = preg_replace('#^(\d+.\d+) *(dev|beta)\+?#', '$1', $releasenew); + $branchnew = $branchcurrent; // Branch doesn't change in beta releases ever. + $releasenew .= 'beta'; + list($integerversionnew, $decimalversionnew) = bump_dev_ensure_higher($integerversionnew, $decimalversionnew); + $maturitynew = 'MATURITY_BETA'; + } else if ($type === 'rc') { + $releasenew = preg_replace('#^(\d+.\d+) *(dev|beta|rc\d)\+?#', '$1', $releasenew); + $branchnew = $branchcurrent; // Branch doesn't change in rc releases ever. + $releasenew .= 'rc'.$rc; + list($integerversionnew, $decimalversionnew) = bump_dev_ensure_higher($integerversionnew, $decimalversionnew); + $maturitynew = 'MATURITY_RC'; + } else if ($type === 'on-demand') { + // Add the + if missing (normally applies to post betas & rcs only, + // but it's not wrong to generalize it to any on-demand). + if (strpos($releasenew, '+') === false) { + // Add the + + $releasenew .= '+'; + } + list($integerversionnew, $decimalversionnew) = bump_dev_ensure_higher($integerversionnew, $decimalversionnew); + } else if ($type === 'on-sync') { + $decimalversionnew++; + } else if ($type === 'back-to-dev') { + if (strpos($releasecurrent, 'dev') === false) { // Ensure it's not a "dev" version already. + // Must be immediately after a major release. Bump comment, release and maturity. + $commentnew = '// YYYYMMDD = weekly release date of this DEV branch.'; + // Normalise a little bit the release, getting rid of everything after the numerical part. + $releasenew = preg_replace('/^([0-9.]+).*$/', '\1', $releasenew); + // Split the major and minor parts of the release for further process. + list($releasemajor, $releaseminor) = explode('.', $releasenew); + $releasenew = $releasemajor . '.' . (++$releaseminor); // Increment to next dev version. + $releasenew = $releasenew . 'dev'; + // The branch is the major followed by 2-chars minor. + $branchnew = $releasemajor . str_pad($releaseminor, 2, '0', STR_PAD_LEFT); + $maturitynew = 'MATURITY_ALPHA'; + if (empty($date)) { // If no date has been forced, back-to-dev have same build date than majors. + if ((int)date('N') !== 1) { // If today is not Monday, calculate next one. + $buildnew = date('Ymd', strtotime('next Monday')); + } + } + } + } else { + // Awesome major release! + $releasenew = preg_replace('#^(\d+.\d+) *(dev|beta|rc\d+)\+?#', '$1', $releasenew); + $branchnew = $branchcurrent; // Branch doesn't change in major releases ever. + list($integerversionnew, $decimalversionnew) = bump_dev_ensure_higher($integerversionnew, $decimalversionnew); + $maturitynew = 'MATURITY_STABLE'; + // Now handle builddate for releases. + if (empty($date)) { // If no date has been forced, dev majors always are released on Monday. + if ((int)date('N') !== 1) { // If today is not Monday, calculate next one. + $buildnew = date('Ymd', strtotime('next Monday')); + } + } + $commentnew = '// ' . $buildnew . ' = branching date YYYYMMDD - do not modify!'; + // TODO: Move this to bump_dev_ensure_higher() to keep things clear. Require new params. + // Also force version for major releases. Must match "next Monday" or --date (if specified) + if (empty($date)) { // If no date has been forced, dev majors always are released on Monday. + if ((int)date('N') !== 1) { // If today is not Monday, calculate next one. + $integerversionnew = date('Ymd', strtotime('next Monday')) . '00'; + } + } else { + $integerversionnew = $date . '00'; // Apply $date also to major versions. + } + $decimalversionnew = '00'; // Majors always have the decimal reset to .00. + } + } + + // Replace the old version with the new version. + if (strlen($decimalversionnew) === 1) { + $decimalversionnew = '0'.$decimalversionnew; + } + $versionfile = str_replace($integerversioncurrent.'.'.$decimalversioncurrent, $integerversionnew.'.'.$decimalversionnew, $versionfile); + // Replace the old build with the new build. + $versionfile = str_replace('Build: '.$buildcurrent, 'Build: '.$buildnew, $versionfile); + // Replace the old release with the new release if they've changed. + if ($releasecurrent !== $releasenew) { + $versionfile = str_replace($releasequote.$releasecurrent, $releasequote.$releasenew, $versionfile); + } + // Replace the old comment with the new one if they've changed + if ($commentcurrent !== $commentnew) { + $versionfile = str_replace($commentcurrent, $commentnew, $versionfile); + } + + if (!$is19) { + // Replace the branch value if need be. + if ($branchcurrent !== $branchnew) { + $versionfile = str_replace($branchquote.$branchcurrent.$branchquote, $branchquote.$branchnew.$branchquote, $versionfile); + } + // Replace the maturity value if need be. + if ($maturitycurrent !== $maturitynew) { + $versionfile = str_replace('= '.$maturitycurrent, '= '.$maturitynew, $versionfile); + } + } + + file_put_contents($path, $versionfile); + + return $releasenew; + } + + function bump_dev_ensure_higher($versionint, $versiondec) { + $today = date('Ymd'); + if ($versionint >= $today*100) { + // Integer version is already past today * 100, increment version decimal part instead of integer part. + $versiondec = (int)$versiondec + 1; + $versiondec = (string)$versiondec; + } else { + $versionint = $today.'00'; + $versiondec = '00'; + } + return array($versionint, $versiondec); + } + + function branch_is_stable($branch, $isdevbranch) { + return (strpos($branch, '_STABLE') !== false && !$isdevbranch); + } + + function validate_branch($branch) { + if (!preg_match('#^(main|MOODLE_(\d+)_STABLE)$#', $branch, $matches)) { + throw new Exception('Invalid branch given', __LINE__); + } + return true; + } + + function validate_type($type) { + $types = array('weekly', 'minor', 'major', 'beta', 'rc', 'on-demand', 'on-sync', 'back-to-dev'); + if (!in_array($type, $types)) { + throw new Exception('Invalid type given.', __LINE__); + } + return true; + } + + function validate_path($path) { + if (file_exists($path) && is_readable($path)) { + if (is_writable($path)) { + return true; + } + throw new Exception('Path cannot be written to.', __LINE__); + } + throw new Exception('Invalid path given.', __LINE__); + } + + function validate_version_file($contents, $branch) { + $hasversion = strpos($contents, '$version ') !== false; + $hasrelease = strpos($contents, '$release ') !== false; + $hasbranch = strpos($contents, '$branch ') !== false; + $hasmaturity = strpos($contents, '$maturity ') !== false; + + if ($hasversion && $hasrelease && $hasbranch && $hasmaturity) { + return true; + } + if ($branch === 'MOODLE_19_STABLE' && $hasversion && $hasrelease) { + return true; + } + throw new Exception('Invalid version file found.', __LINE__); + } + + function get_option_from_options_array(array $options, $short, $long) { + if (!isset($options[$short]) && !isset($options[$long])) { + throw new Exception("Required option -$short|--$long must be provided.", __LINE__); + } + if ((isset($options[$short]) && is_array($options[$short])) || + (isset($options[$long]) && is_array($options[$long])) || + (isset($options[$short]) && isset($options[$long]))) { + throw new Exception("Option -$short|--$long specified more than once.", __LINE__); + } + return (isset($options[$short])) ? $options[$short] : $options[$long]; + } + +} From 5d0d77171a855cd3e2642605e843f6274878c53d Mon Sep 17 00:00:00 2001 From: Andrew Nicols Date: Fri, 27 Sep 2024 23:29:43 +0800 Subject: [PATCH 02/12] Refactor Helper library --- bumpversions.php | 43 +++++++++---- src/helper.php | 153 ++++++++++++++++++++++++++++++++++++++--------- 2 files changed, 157 insertions(+), 39 deletions(-) diff --git a/bumpversions.php b/bumpversions.php index 289c17c..7d72068 100644 --- a/bumpversions.php +++ b/bumpversions.php @@ -1,27 +1,50 @@ . + +use MoodleHQ\MoodleRelease\Helper; // Perth is the center of the world. Anything to object? date_default_timezone_set('Australia/Perth'); require_once(__DIR__ . '/src/helper.php'); -$helper = new \MoodleHQ\MoodleRelease\Helper(); +$helper = new Helper(); // We need the branch and the bump type (weekly. minor, major) try { $shortoptions = 'b:t:p:r:d:i:'; - $longoptions = array('branch:', 'type:', 'path:', 'rc:', 'date:', 'isdevbranch:'); + $longoptions = [ + 'branch:', + 'type:', + 'path:', + 'rc:', + 'date:', + 'isdevbranch:', + ]; $options = getopt($shortoptions, $longoptions); - $branch = $helper->get_option_from_options_array($options, 'b', 'branch'); - $type = $helper->get_option_from_options_array($options, 't', 'type'); - $path = $helper->get_option_from_options_array($options, 'p', 'path'); - $rc = $helper->get_option_from_options_array($options, 'r', 'rc'); - $date = $helper->get_option_from_options_array($options, 'd', 'date'); - $isdevbranch = (bool)$helper->get_option_from_options_array($options, 'i', 'isdevbranch'); - $path = rtrim($path, '/').'/version.php'; + $branch = Helper::getOption($options, 'b', 'branch'); + $type = Helper::getOption($options, 't', 'type'); + $path = Helper::getOption($options, 'p', 'path'); + $rc = Helper::getOption($options, 'r', 'rc'); + $date = Helper::getOption($options, 'd', 'date'); + $isdevbranch = (bool) Helper::getOption($options, 'i', 'isdevbranch'); + $path = rtrim($path, '/') . '/version.php'; - $release = $helper->bump_version($path, $branch, $type, $rc, $date, $isdevbranch); + $release = Helper::bumpVersion($path, $branch, $type, $rc, $date, $isdevbranch); $result = 0; } catch (Exception $ex) { $release = $ex->getMessage(); diff --git a/src/helper.php b/src/helper.php index 601cabb..54d6c9c 100644 --- a/src/helper.php +++ b/src/helper.php @@ -16,6 +16,8 @@ namespace MoodleHQ\MoodleRelease; +use Exception; + /** * Helper library for Moodle Release scripts. * @@ -24,18 +26,37 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class Helper { - function bump_version($path, $branch, $type, $rc, $date, $isdevbranch) { + /** + * Bump the version. + * + * @param string $path + * @param string $branch + * @param string $type + * @param string $rc + * @param string $date + * @param bool $isdevbranch + * @throws Exception + * @return mixed + */ + public static function bumpVersion( + string $path, + string $branch, + string $type, + string $rc, + string $date, + bool $isdevbranch, + ) { - validate_branch($branch); - validate_type($type); - validate_path($path); + self::isBranchNameValid($branch); + self::isTypeValid($type); + self::isPathValid($path); $versionfile = file_get_contents($path); - validate_version_file($versionfile, $branch); + self::isVersionFileValid($versionfile, $branch); $is19 = ($branch === 'MOODLE_19_STABLE'); - $isstable = branch_is_stable($branch, $isdevbranch); + $isstable = self::isBranchStable($branch, $isdevbranch); $today = date('Ymd'); $integerversioncurrent = null; @@ -134,25 +155,25 @@ function bump_version($path, $branch, $type, $rc, $date, $isdevbranch) { // Add the + $releasenew .= '+'; } - list($integerversionnew, $decimalversionnew) = bump_dev_ensure_higher($integerversionnew, $decimalversionnew); + list($integerversionnew, $decimalversionnew) = self::getValidatedVersionNumber($integerversionnew, $decimalversionnew); } else if (strpos($releasecurrent, 'dev') === false) { // Must be immediately after a major release. Bump the release version and set maturity to Alpha. $releasenew = (float)$releasenew + 0.1; $releasenew = (string)$releasenew.'dev'; $maturitynew = 'MATURITY_ALPHA'; } - list($integerversionnew, $decimalversionnew) = bump_dev_ensure_higher($integerversionnew, $decimalversionnew); + list($integerversionnew, $decimalversionnew) = self::getValidatedVersionNumber($integerversionnew, $decimalversionnew); } else if ($type === 'beta') { $releasenew = preg_replace('#^(\d+.\d+) *(dev|beta)\+?#', '$1', $releasenew); $branchnew = $branchcurrent; // Branch doesn't change in beta releases ever. $releasenew .= 'beta'; - list($integerversionnew, $decimalversionnew) = bump_dev_ensure_higher($integerversionnew, $decimalversionnew); + list($integerversionnew, $decimalversionnew) = self::getValidatedVersionNumber($integerversionnew, $decimalversionnew); $maturitynew = 'MATURITY_BETA'; } else if ($type === 'rc') { $releasenew = preg_replace('#^(\d+.\d+) *(dev|beta|rc\d)\+?#', '$1', $releasenew); $branchnew = $branchcurrent; // Branch doesn't change in rc releases ever. $releasenew .= 'rc'.$rc; - list($integerversionnew, $decimalversionnew) = bump_dev_ensure_higher($integerversionnew, $decimalversionnew); + list($integerversionnew, $decimalversionnew) = self::getValidatedVersionNumber($integerversionnew, $decimalversionnew); $maturitynew = 'MATURITY_RC'; } else if ($type === 'on-demand') { // Add the + if missing (normally applies to post betas & rcs only, @@ -161,7 +182,7 @@ function bump_version($path, $branch, $type, $rc, $date, $isdevbranch) { // Add the + $releasenew .= '+'; } - list($integerversionnew, $decimalversionnew) = bump_dev_ensure_higher($integerversionnew, $decimalversionnew); + list($integerversionnew, $decimalversionnew) = self::getValidatedVersionNumber($integerversionnew, $decimalversionnew); } else if ($type === 'on-sync') { $decimalversionnew++; } else if ($type === 'back-to-dev') { @@ -187,7 +208,7 @@ function bump_version($path, $branch, $type, $rc, $date, $isdevbranch) { // Awesome major release! $releasenew = preg_replace('#^(\d+.\d+) *(dev|beta|rc\d+)\+?#', '$1', $releasenew); $branchnew = $branchcurrent; // Branch doesn't change in major releases ever. - list($integerversionnew, $decimalversionnew) = bump_dev_ensure_higher($integerversionnew, $decimalversionnew); + list($integerversionnew, $decimalversionnew) = self::getValidatedVersionNumber($integerversionnew, $decimalversionnew); $maturitynew = 'MATURITY_STABLE'; // Now handle builddate for releases. if (empty($date)) { // If no date has been forced, dev majors always are released on Monday. @@ -196,7 +217,7 @@ function bump_version($path, $branch, $type, $rc, $date, $isdevbranch) { } } $commentnew = '// ' . $buildnew . ' = branching date YYYYMMDD - do not modify!'; - // TODO: Move this to bump_dev_ensure_higher() to keep things clear. Require new params. + // TODO: Move this to self::getValidatedVersionNumber() to keep things clear. Require new params. // Also force version for major releases. Must match "next Monday" or --date (if specified) if (empty($date)) { // If no date has been forced, dev majors always are released on Monday. if ((int)date('N') !== 1) { // If today is not Monday, calculate next one. @@ -241,39 +262,88 @@ function bump_version($path, $branch, $type, $rc, $date, $isdevbranch) { return $releasenew; } - function bump_dev_ensure_higher($versionint, $versiondec) { + /** + * Ensure that the buymped version is higher than the current one. + * + * @param int $versionint The integer part of the version + * @param int $versiondec The decimal part of the version + * @return array + */ + public static function getValidatedVersionNumber( + int $versionint, + int $versiondec, + ): array { $today = date('Ymd'); - if ($versionint >= $today*100) { + if ($versionint >= $today * 100) { // Integer version is already past today * 100, increment version decimal part instead of integer part. - $versiondec = (int)$versiondec + 1; - $versiondec = (string)$versiondec; + $versiondec = (int) $versiondec + 1; + $versiondec = (string) $versiondec; } else { - $versionint = $today.'00'; + $versionint = $today . '00'; $versiondec = '00'; } - return array($versionint, $versiondec); + return [ + $versionint, + $versiondec, + ]; } - function branch_is_stable($branch, $isdevbranch) { - return (strpos($branch, '_STABLE') !== false && !$isdevbranch); + /** + * Check if the branch is a stable branch. + * + * @param string $branch The branch name + * @param bool $isdevbranch Whether the branch is a development branch + * @return bool + */ + public static function isBranchStable( + string $branch, + bool $isdevbranch, + ): bool { + return (strpos($branch, '_STABLE') !== false && !$isdevbranch); } - function validate_branch($branch) { + /** + * Validate the branch. + * + * @param string $branch The branch name + * @throws Exception + */ + public static function isBranchNameValid( + string $branch, + ): bool { if (!preg_match('#^(main|MOODLE_(\d+)_STABLE)$#', $branch, $matches)) { throw new Exception('Invalid branch given', __LINE__); } return true; } - function validate_type($type) { - $types = array('weekly', 'minor', 'major', 'beta', 'rc', 'on-demand', 'on-sync', 'back-to-dev'); + /** + * Check whether the type is valid. + * + * @param string $type The type of the release + * @return bool + * @throws Exception + */ + public static function isTypeValid( + string $type, + ): bool { + $types = ['weekly', 'minor', 'major', 'beta', 'rc', 'on-demand', 'on-sync', 'back-to-dev']; if (!in_array($type, $types)) { throw new Exception('Invalid type given.', __LINE__); } return true; } - function validate_path($path) { + /** + * Check whether the path is valid. + * + * @param string $path The path to the version file + * @return bool + * @throws Exception + */ + public static function isPathValid( + string $path, + ): bool { if (file_exists($path) && is_readable($path)) { if (is_writable($path)) { return true; @@ -283,7 +353,18 @@ function validate_path($path) { throw new Exception('Invalid path given.', __LINE__); } - function validate_version_file($contents, $branch) { + /** + * Validate the version file. + * + * @param string $contents The contents of the version file + * @param string $branch The branch name + * @return bool + * @throws Exception + */ + public static function isVersionFileValid( + string $contents, + string $branch, + ): bool { $hasversion = strpos($contents, '$version ') !== false; $hasrelease = strpos($contents, '$release ') !== false; $hasbranch = strpos($contents, '$branch ') !== false; @@ -298,13 +379,27 @@ function validate_version_file($contents, $branch) { throw new Exception('Invalid version file found.', __LINE__); } - function get_option_from_options_array(array $options, $short, $long) { + /** + * Get the value of an option from the options array. + * + * @param array $options THe options configuration + * @param string $short The short name of the option + * @param string $long The long name of the option + * @return mixed + */ + public static function getOption( + array $options, + string $short, + string $long, + ): mixed { if (!isset($options[$short]) && !isset($options[$long])) { throw new Exception("Required option -$short|--$long must be provided.", __LINE__); } - if ((isset($options[$short]) && is_array($options[$short])) || + if ( + (isset($options[$short]) && is_array($options[$short])) || (isset($options[$long]) && is_array($options[$long])) || - (isset($options[$short]) && isset($options[$long]))) { + (isset($options[$short]) && isset($options[$long])) + ) { throw new Exception("Option -$short|--$long specified more than once.", __LINE__); } return (isset($options[$short])) ? $options[$short] : $options[$long]; From 41a62395c7adb211600f7178484ddca169a8babc Mon Sep 17 00:00:00 2001 From: Andrew Nicols Date: Fri, 27 Sep 2024 23:52:38 +0800 Subject: [PATCH 03/12] Add unit tests for PHP Component --- .gitignore | 5 + bumpversions.php | 2 +- composer.json | 21 + composer.lock | 1695 ++++++++++++++++++++++++ phpunit.xml | 25 + src/Helper.php | 322 +++++ src/VersionInfo.php | 249 ++++ src/helper.php | 408 ------ tests/bootstrap.php | 19 + tests/fixtures/versions/4.3.0.php | 37 + tests/fixtures/versions/4.3.2.php | 37 + tests/fixtures/versions/4.4.0-beta.php | 37 + tests/fixtures/versions/4.4.0-rc1.php | 37 + tests/fixtures/versions/4.4.0-rc2.php | 37 + tests/fixtures/versions/4.4.0.php | 37 + tests/unit/HelperTest.php | 365 +++++ tests/unit/VersionInfoTest.php | 633 +++++++++ 17 files changed, 3557 insertions(+), 409 deletions(-) create mode 100644 composer.json create mode 100644 composer.lock create mode 100644 phpunit.xml create mode 100644 src/Helper.php create mode 100644 src/VersionInfo.php delete mode 100644 src/helper.php create mode 100644 tests/bootstrap.php create mode 100644 tests/fixtures/versions/4.3.0.php create mode 100644 tests/fixtures/versions/4.3.2.php create mode 100644 tests/fixtures/versions/4.4.0-beta.php create mode 100644 tests/fixtures/versions/4.4.0-rc1.php create mode 100644 tests/fixtures/versions/4.4.0-rc2.php create mode 100644 tests/fixtures/versions/4.4.0.php create mode 100644 tests/unit/HelperTest.php create mode 100644 tests/unit/VersionInfoTest.php diff --git a/.gitignore b/.gitignore index 053c952..bf82344 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,8 @@ /cvsmoodle21/ /cvsmoodle22/ /cvsmoodle23/ +/vendor/ +.phpunit.result.cache +.phpunit.cache +coverage +coverage.txt diff --git a/bumpversions.php b/bumpversions.php index 7d72068..e5ca3d5 100644 --- a/bumpversions.php +++ b/bumpversions.php @@ -19,7 +19,7 @@ // Perth is the center of the world. Anything to object? date_default_timezone_set('Australia/Perth'); -require_once(__DIR__ . '/src/helper.php'); +require_once(__DIR__ . '/vendor/autoload.php'); $helper = new Helper(); diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..42fefbb --- /dev/null +++ b/composer.json @@ -0,0 +1,21 @@ +{ + "name": "moodlehq/mdlrelease", + "description": "Moodle Release tool", + "type": "project", + "require-dev": { + "phpunit/phpunit": "^10.5", + "mikey179/vfsstream": "^1.6" + }, + "license": "GPLv3", + "autoload": { + "psr-4": { + "MoodleHQ\\MoodleRelease\\": "src/" + } + }, + "authors": [ + { + "name": "Andrew Nicols", + "email": "andrew@nicols.co.uk" + } + ] +} diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..9540fd0 --- /dev/null +++ b/composer.lock @@ -0,0 +1,1695 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "07601379410b796e1830d7d8fb98132e", + "packages": [], + "packages-dev": [ + { + "name": "mikey179/vfsstream", + "version": "v1.6.12", + "source": { + "type": "git", + "url": "https://github.com/bovigo/vfsStream.git", + "reference": "fe695ec993e0a55c3abdda10a9364eb31c6f1bf0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/bovigo/vfsStream/zipball/fe695ec993e0a55c3abdda10a9364eb31c6f1bf0", + "reference": "fe695ec993e0a55c3abdda10a9364eb31c6f1bf0", + "shasum": "" + }, + "require": { + "php": ">=7.1.0" + }, + "require-dev": { + "phpunit/phpunit": "^7.5||^8.5||^9.6", + "yoast/phpunit-polyfills": "^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.6.x-dev" + } + }, + "autoload": { + "psr-0": { + "org\\bovigo\\vfs\\": "src/main/php" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Frank Kleine", + "homepage": "http://frankkleine.de/", + "role": "Developer" + } + ], + "description": "Virtual file system to mock the real file system in unit tests.", + "homepage": "http://vfs.bovigo.org/", + "support": { + "issues": "https://github.com/bovigo/vfsStream/issues", + "source": "https://github.com/bovigo/vfsStream/tree/master", + "wiki": "https://github.com/bovigo/vfsStream/wiki" + }, + "time": "2024-08-29T18:43:31+00:00" + }, + { + "name": "myclabs/deep-copy", + "version": "1.12.0", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c", + "reference": "3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "conflict": { + "doctrine/collections": "<1.6.8", + "doctrine/common": "<2.13.3 || >=3 <3.2.2" + }, + "require-dev": { + "doctrine/collections": "^1.6.8", + "doctrine/common": "^2.13.3 || ^3.2.2", + "phpspec/prophecy": "^1.10", + "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" + }, + "type": "library", + "autoload": { + "files": [ + "src/DeepCopy/deep_copy.php" + ], + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Create deep copies (clones) of your objects", + "keywords": [ + "clone", + "copy", + "duplicate", + "object", + "object graph" + ], + "support": { + "issues": "https://github.com/myclabs/DeepCopy/issues", + "source": "https://github.com/myclabs/DeepCopy/tree/1.12.0" + }, + "funding": [ + { + "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", + "type": "tidelift" + } + ], + "time": "2024-06-12T14:39:25+00:00" + }, + { + "name": "nikic/php-parser", + "version": "v5.3.0", + "source": { + "type": "git", + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "3abf7425cd284141dc5d8d14a9ee444de3345d1a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/3abf7425cd284141dc5d8d14a9ee444de3345d1a", + "reference": "3abf7425cd284141dc5d8d14a9ee444de3345d1a", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "ext-json": "*", + "ext-tokenizer": "*", + "php": ">=7.4" + }, + "require-dev": { + "ircmaxell/php-yacc": "^0.0.7", + "phpunit/phpunit": "^9.0" + }, + "bin": [ + "bin/php-parse" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + } + }, + "autoload": { + "psr-4": { + "PhpParser\\": "lib/PhpParser" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov" + } + ], + "description": "A PHP parser written in PHP", + "keywords": [ + "parser", + "php" + ], + "support": { + "issues": "https://github.com/nikic/PHP-Parser/issues", + "source": "https://github.com/nikic/PHP-Parser/tree/v5.3.0" + }, + "time": "2024-09-29T13:56:26+00:00" + }, + { + "name": "phar-io/manifest", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/phar-io/manifest.git", + "reference": "54750ef60c58e43759730615a392c31c80e23176" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176", + "reference": "54750ef60c58e43759730615a392c31c80e23176", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "ext-phar": "*", + "ext-xmlwriter": "*", + "phar-io/version": "^3.0.1", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", + "support": { + "issues": "https://github.com/phar-io/manifest/issues", + "source": "https://github.com/phar-io/manifest/tree/2.0.4" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2024-03-03T12:33:53+00:00" + }, + { + "name": "phar-io/version", + "version": "3.2.1", + "source": { + "type": "git", + "url": "https://github.com/phar-io/version.git", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Library for handling version information and constraints", + "support": { + "issues": "https://github.com/phar-io/version/issues", + "source": "https://github.com/phar-io/version/tree/3.2.1" + }, + "time": "2022-02-21T01:04:05+00:00" + }, + { + "name": "phpunit/php-code-coverage", + "version": "10.1.16", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "7e308268858ed6baedc8704a304727d20bc07c77" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/7e308268858ed6baedc8704a304727d20bc07c77", + "reference": "7e308268858ed6baedc8704a304727d20bc07c77", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "ext-xmlwriter": "*", + "nikic/php-parser": "^4.19.1 || ^5.1.0", + "php": ">=8.1", + "phpunit/php-file-iterator": "^4.1.0", + "phpunit/php-text-template": "^3.0.1", + "sebastian/code-unit-reverse-lookup": "^3.0.0", + "sebastian/complexity": "^3.2.0", + "sebastian/environment": "^6.1.0", + "sebastian/lines-of-code": "^2.0.2", + "sebastian/version": "^4.0.1", + "theseer/tokenizer": "^1.2.3" + }, + "require-dev": { + "phpunit/phpunit": "^10.1" + }, + "suggest": { + "ext-pcov": "PHP extension that provides line coverage", + "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "10.1.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", + "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/10.1.16" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-08-22T04:31:57+00:00" + }, + { + "name": "phpunit/php-file-iterator", + "version": "4.1.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "a95037b6d9e608ba092da1b23931e537cadc3c3c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/a95037b6d9e608ba092da1b23931e537cadc3c3c", + "reference": "a95037b6d9e608ba092da1b23931e537cadc3c3c", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", + "security": "https://github.com/sebastianbergmann/php-file-iterator/security/policy", + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/4.1.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-08-31T06:24:48+00:00" + }, + { + "name": "phpunit/php-invoker", + "version": "4.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-invoker.git", + "reference": "f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7", + "reference": "f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "ext-pcntl": "*", + "phpunit/phpunit": "^10.0" + }, + "suggest": { + "ext-pcntl": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Invoke callables with a timeout", + "homepage": "https://github.com/sebastianbergmann/php-invoker/", + "keywords": [ + "process" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-invoker/issues", + "source": "https://github.com/sebastianbergmann/php-invoker/tree/4.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T06:56:09+00:00" + }, + { + "name": "phpunit/php-text-template", + "version": "3.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "0c7b06ff49e3d5072f057eb1fa59258bf287a748" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/0c7b06ff49e3d5072f057eb1fa59258bf287a748", + "reference": "0c7b06ff49e3d5072f057eb1fa59258bf287a748", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-text-template/issues", + "security": "https://github.com/sebastianbergmann/php-text-template/security/policy", + "source": "https://github.com/sebastianbergmann/php-text-template/tree/3.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-08-31T14:07:24+00:00" + }, + { + "name": "phpunit/php-timer", + "version": "6.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "e2a2d67966e740530f4a3343fe2e030ffdc1161d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/e2a2d67966e740530f4a3343fe2e030ffdc1161d", + "reference": "e2a2d67966e740530f4a3343fe2e030ffdc1161d", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-timer/issues", + "source": "https://github.com/sebastianbergmann/php-timer/tree/6.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T06:57:52+00:00" + }, + { + "name": "phpunit/phpunit", + "version": "10.5.35", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "7ac8b4e63f456046dcb4c9787da9382831a1874b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/7ac8b4e63f456046dcb4c9787da9382831a1874b", + "reference": "7ac8b4e63f456046dcb4c9787da9382831a1874b", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xml": "*", + "ext-xmlwriter": "*", + "myclabs/deep-copy": "^1.12.0", + "phar-io/manifest": "^2.0.4", + "phar-io/version": "^3.2.1", + "php": ">=8.1", + "phpunit/php-code-coverage": "^10.1.16", + "phpunit/php-file-iterator": "^4.1.0", + "phpunit/php-invoker": "^4.0.0", + "phpunit/php-text-template": "^3.0.1", + "phpunit/php-timer": "^6.0.0", + "sebastian/cli-parser": "^2.0.1", + "sebastian/code-unit": "^2.0.0", + "sebastian/comparator": "^5.0.2", + "sebastian/diff": "^5.1.1", + "sebastian/environment": "^6.1.0", + "sebastian/exporter": "^5.1.2", + "sebastian/global-state": "^6.0.2", + "sebastian/object-enumerator": "^5.0.0", + "sebastian/recursion-context": "^5.0.0", + "sebastian/type": "^4.0.0", + "sebastian/version": "^4.0.1" + }, + "suggest": { + "ext-soap": "To be able to generate mocks based on WSDL files" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "10.5-dev" + } + }, + "autoload": { + "files": [ + "src/Framework/Assert/Functions.php" + ], + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/phpunit/issues", + "security": "https://github.com/sebastianbergmann/phpunit/security/policy", + "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.35" + }, + "funding": [ + { + "url": "https://phpunit.de/sponsors.html", + "type": "custom" + }, + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit", + "type": "tidelift" + } + ], + "time": "2024-09-19T10:52:21+00:00" + }, + { + "name": "sebastian/cli-parser", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/cli-parser.git", + "reference": "c34583b87e7b7a8055bf6c450c2c77ce32a24084" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/c34583b87e7b7a8055bf6c450c2c77ce32a24084", + "reference": "c34583b87e7b7a8055bf6c450c2c77ce32a24084", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for parsing CLI options", + "homepage": "https://github.com/sebastianbergmann/cli-parser", + "support": { + "issues": "https://github.com/sebastianbergmann/cli-parser/issues", + "security": "https://github.com/sebastianbergmann/cli-parser/security/policy", + "source": "https://github.com/sebastianbergmann/cli-parser/tree/2.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-02T07:12:49+00:00" + }, + { + "name": "sebastian/code-unit", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit.git", + "reference": "a81fee9eef0b7a76af11d121767abc44c104e503" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/a81fee9eef0b7a76af11d121767abc44c104e503", + "reference": "a81fee9eef0b7a76af11d121767abc44c104e503", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the PHP code units", + "homepage": "https://github.com/sebastianbergmann/code-unit", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit/issues", + "source": "https://github.com/sebastianbergmann/code-unit/tree/2.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T06:58:43+00:00" + }, + { + "name": "sebastian/code-unit-reverse-lookup", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", + "reference": "5e3a687f7d8ae33fb362c5c0743794bbb2420a1d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/5e3a687f7d8ae33fb362c5c0743794bbb2420a1d", + "reference": "5e3a687f7d8ae33fb362c5c0743794bbb2420a1d", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Looks up which function or method a line of code belongs to", + "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", + "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/3.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T06:59:15+00:00" + }, + { + "name": "sebastian/comparator", + "version": "5.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "2d3e04c3b4c1e84a5e7382221ad8883c8fbc4f53" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/2d3e04c3b4c1e84a5e7382221ad8883c8fbc4f53", + "reference": "2d3e04c3b4c1e84a5e7382221ad8883c8fbc4f53", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-mbstring": "*", + "php": ">=8.1", + "sebastian/diff": "^5.0", + "sebastian/exporter": "^5.0" + }, + "require-dev": { + "phpunit/phpunit": "^10.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "https://github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/comparator/issues", + "security": "https://github.com/sebastianbergmann/comparator/security/policy", + "source": "https://github.com/sebastianbergmann/comparator/tree/5.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-08-12T06:03:08+00:00" + }, + { + "name": "sebastian/complexity", + "version": "3.2.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/complexity.git", + "reference": "68ff824baeae169ec9f2137158ee529584553799" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/68ff824baeae169ec9f2137158ee529584553799", + "reference": "68ff824baeae169ec9f2137158ee529584553799", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^4.18 || ^5.0", + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.2-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for calculating the complexity of PHP code units", + "homepage": "https://github.com/sebastianbergmann/complexity", + "support": { + "issues": "https://github.com/sebastianbergmann/complexity/issues", + "security": "https://github.com/sebastianbergmann/complexity/security/policy", + "source": "https://github.com/sebastianbergmann/complexity/tree/3.2.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-12-21T08:37:17+00:00" + }, + { + "name": "sebastian/diff", + "version": "5.1.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "c41e007b4b62af48218231d6c2275e4c9b975b2e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/c41e007b4b62af48218231d6c2275e4c9b975b2e", + "reference": "c41e007b4b62af48218231d6c2275e4c9b975b2e", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0", + "symfony/process": "^6.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff", + "udiff", + "unidiff", + "unified diff" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/diff/issues", + "security": "https://github.com/sebastianbergmann/diff/security/policy", + "source": "https://github.com/sebastianbergmann/diff/tree/5.1.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-02T07:15:17+00:00" + }, + { + "name": "sebastian/environment", + "version": "6.1.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "8074dbcd93529b357029f5cc5058fd3e43666984" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/8074dbcd93529b357029f5cc5058fd3e43666984", + "reference": "8074dbcd93529b357029f5cc5058fd3e43666984", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "suggest": { + "ext-posix": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "https://github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/environment/issues", + "security": "https://github.com/sebastianbergmann/environment/security/policy", + "source": "https://github.com/sebastianbergmann/environment/tree/6.1.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-23T08:47:14+00:00" + }, + { + "name": "sebastian/exporter", + "version": "5.1.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "955288482d97c19a372d3f31006ab3f37da47adf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/955288482d97c19a372d3f31006ab3f37da47adf", + "reference": "955288482d97c19a372d3f31006ab3f37da47adf", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": ">=8.1", + "sebastian/recursion-context": "^5.0" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "https://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/exporter/issues", + "security": "https://github.com/sebastianbergmann/exporter/security/policy", + "source": "https://github.com/sebastianbergmann/exporter/tree/5.1.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-02T07:17:12+00:00" + }, + { + "name": "sebastian/global-state", + "version": "6.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "987bafff24ecc4c9ac418cab1145b96dd6e9cbd9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/987bafff24ecc4c9ac418cab1145b96dd6e9cbd9", + "reference": "987bafff24ecc4c9ac418cab1145b96dd6e9cbd9", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "sebastian/object-reflector": "^3.0", + "sebastian/recursion-context": "^5.0" + }, + "require-dev": { + "ext-dom": "*", + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Snapshotting of global state", + "homepage": "https://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/global-state/issues", + "security": "https://github.com/sebastianbergmann/global-state/security/policy", + "source": "https://github.com/sebastianbergmann/global-state/tree/6.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-02T07:19:19+00:00" + }, + { + "name": "sebastian/lines-of-code", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/lines-of-code.git", + "reference": "856e7f6a75a84e339195d48c556f23be2ebf75d0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/856e7f6a75a84e339195d48c556f23be2ebf75d0", + "reference": "856e7f6a75a84e339195d48c556f23be2ebf75d0", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^4.18 || ^5.0", + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for counting the lines of code in PHP source code", + "homepage": "https://github.com/sebastianbergmann/lines-of-code", + "support": { + "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", + "security": "https://github.com/sebastianbergmann/lines-of-code/security/policy", + "source": "https://github.com/sebastianbergmann/lines-of-code/tree/2.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-12-21T08:38:20+00:00" + }, + { + "name": "sebastian/object-enumerator", + "version": "5.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "202d0e344a580d7f7d04b3fafce6933e59dae906" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/202d0e344a580d7f7d04b3fafce6933e59dae906", + "reference": "202d0e344a580d7f7d04b3fafce6933e59dae906", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "sebastian/object-reflector": "^3.0", + "sebastian/recursion-context": "^5.0" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Traverses array structures and object graphs to enumerate all referenced objects", + "homepage": "https://github.com/sebastianbergmann/object-enumerator/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", + "source": "https://github.com/sebastianbergmann/object-enumerator/tree/5.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T07:08:32+00:00" + }, + { + "name": "sebastian/object-reflector", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-reflector.git", + "reference": "24ed13d98130f0e7122df55d06c5c4942a577957" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/24ed13d98130f0e7122df55d06c5c4942a577957", + "reference": "24ed13d98130f0e7122df55d06c5c4942a577957", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Allows reflection of object attributes, including inherited and non-public ones", + "homepage": "https://github.com/sebastianbergmann/object-reflector/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-reflector/issues", + "source": "https://github.com/sebastianbergmann/object-reflector/tree/3.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T07:06:18+00:00" + }, + { + "name": "sebastian/recursion-context", + "version": "5.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "05909fb5bc7df4c52992396d0116aed689f93712" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/05909fb5bc7df4c52992396d0116aed689f93712", + "reference": "05909fb5bc7df4c52992396d0116aed689f93712", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "https://github.com/sebastianbergmann/recursion-context", + "support": { + "issues": "https://github.com/sebastianbergmann/recursion-context/issues", + "source": "https://github.com/sebastianbergmann/recursion-context/tree/5.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T07:05:40+00:00" + }, + { + "name": "sebastian/type", + "version": "4.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/type.git", + "reference": "462699a16464c3944eefc02ebdd77882bd3925bf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/462699a16464c3944eefc02ebdd77882bd3925bf", + "reference": "462699a16464c3944eefc02ebdd77882bd3925bf", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the types of the PHP type system", + "homepage": "https://github.com/sebastianbergmann/type", + "support": { + "issues": "https://github.com/sebastianbergmann/type/issues", + "source": "https://github.com/sebastianbergmann/type/tree/4.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T07:10:45+00:00" + }, + { + "name": "sebastian/version", + "version": "4.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "c51fa83a5d8f43f1402e3f32a005e6262244ef17" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c51fa83a5d8f43f1402e3f32a005e6262244ef17", + "reference": "c51fa83a5d8f43f1402e3f32a005e6262244ef17", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", + "support": { + "issues": "https://github.com/sebastianbergmann/version/issues", + "source": "https://github.com/sebastianbergmann/version/tree/4.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-07T11:34:05+00:00" + }, + { + "name": "theseer/tokenizer", + "version": "1.2.3", + "source": { + "type": "git", + "url": "https://github.com/theseer/tokenizer.git", + "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", + "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + } + ], + "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", + "support": { + "issues": "https://github.com/theseer/tokenizer/issues", + "source": "https://github.com/theseer/tokenizer/tree/1.2.3" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2024-03-03T12:36:25+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": [], + "platform-dev": [], + "plugin-api-version": "2.6.0" +} diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 0000000..45f745e --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,25 @@ + + + + + tests/unit + + + + + + + + + + + + src + + + diff --git a/src/Helper.php b/src/Helper.php new file mode 100644 index 0000000..98750e6 --- /dev/null +++ b/src/Helper.php @@ -0,0 +1,322 @@ +. + +namespace MoodleHQ\MoodleRelease; + +use Exception; + +/** + * Helper library for Moodle Release scripts. + * + * @copyright 2024 Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class Helper +{ + /** + * Get information about the next version + * + * @param string $path + * @param string $branch + * @param string $type + * @param string $rc + * @param string $date + * @param bool $isdevbranch + * @throws Exception + * @return mixed + */ + public static function getNextVersion( + string $path, + string $branch, + string $type, + string $rc, + string $date, + bool $isdevbranch, + ) { + self::requireBranchNameValid($branch); + self::requireTypeValid($type); + self::requirePathValid($path); + + $versionfile = file_get_contents($path); + $currentVersionInfo = VersionInfo::fromVersionFile($versionfile, $branch); + $nextVersionInfo = $currentVersionInfo->getNextVersion($branch, $type, $rc, $date, $isdevbranch); + + return [ + 'is19' => ($branch === 'MOODLE_19_STABLE'), + 'versionfile' => $versionfile, + 'current' => $currentVersionInfo, + 'new' => $nextVersionInfo, + ]; + } + + /** + * Bump the version. + * + * @param string $path + * @param string $branch + * @param string $type + * @param string $rc + * @param string $date + * @param bool $isdevbranch + * @throws Exception + * @return string + */ + public static function bumpVersion( + string $path, + string $branch, + string $type, + string $rc, + string $date, + bool $isdevbranch, + ): string { + [ + 'is19' => $is19, + 'versionfile' => $versionfile, + 'current' => $currentVersionInfo, + 'new' => $newVersionInfo, + ] = self::getNextVersion($path, $branch, $type, $rc, $date, $isdevbranch); + + $versionfile = str_replace($currentVersionInfo->integerversion . '.' . $currentVersionInfo->decimalversion, $newVersionInfo->integerversion.'.'.$newVersionInfo->decimalversion, $versionfile); + // Replace the old build with the new build. + $versionfile = str_replace('Build: '.$currentVersionInfo->build, 'Build: '.$newVersionInfo->build, $versionfile); + // Replace the old release with the new release if they've changed. + if ($currentVersionInfo->release !== $newVersionInfo->release) { + $versionfile = str_replace($currentVersionInfo->releasequote.$currentVersionInfo->release, $newVersionInfo->releasequote.$newVersionInfo->release, $versionfile); + } + // Replace the old comment with the new one if they've changed + if ($currentVersionInfo->comment !== $newVersionInfo->comment) { + $versionfile = str_replace($currentVersionInfo->comment, $newVersionInfo->comment, $versionfile); + } + + if (!$is19) { + // Replace the branch value if need be. + if ($currentVersionInfo->branch !== $newVersionInfo->branch) { + $versionfile = str_replace($currentVersionInfo->branchquote.$currentVersionInfo->branch.$newVersionInfo->branchquote, $newVersionInfo->branchquote.$newVersionInfo->branch.$newVersionInfo->branchquote, $versionfile); + } + // Replace the maturity value if need be. + if ($currentVersionInfo->maturity !== $newVersionInfo->maturity) { + $versionfile = str_replace('= '.$currentVersionInfo->maturity, '= '.$newVersionInfo->maturity, $versionfile); + } + } + + file_put_contents($path, $versionfile); + + return $newVersionInfo->release; + } + + /** + * Ensure that the buymped version is higher than the current one. + * + * @param int $versionint The integer part of the version + * @param int $versiondec The decimal part of the version + * @return array + */ + public static function getValidatedVersionNumber( + int $versionint, + int $versiondec, + ): array { + $today = date('Ymd'); + if ($versionint >= $today * 100) { + // Integer version is already past today * 100, increment version decimal part instead of integer part. + $versiondec = (int) $versiondec + 1; + $versiondec = sprintf("%'02d", $versiondec) ; + } else { + $versionint = $today . '00'; + $versiondec = '00'; + } + if ($versiondec >= 100) { + // Decimal version is already past 99, increment integer part and reset decimal part. + $versionint = (int) $versionint + 1; + $versiondec = '00'; + } + + return [ + (int) $versionint, + (string) $versiondec, + ]; + } + + /** + * Check if the branch is a stable branch. + * + * @param string $branch The branch name + * @param bool $isdevbranch Whether the branch is a development branch + * @return bool + */ + public static function isBranchStable( + string $branch, + bool $isdevbranch, + ): bool { + return (strpos($branch, '_STABLE') !== false && !$isdevbranch); + } + + /** + * Check whether a branch name is valid. + * + * @param string $branch The branch name + * @return bool + */ + public static function isBranchNameValid( + string $branch, + ): bool { + return (preg_match('#^(main|MOODLE_(\d+)_STABLE)$#', $branch, $matches)); + } + + /** + * Ensure the branch name is valid. + * + * @param string $branch The branch name + * @throws Exception + */ + public static function requireBranchNameValid( + string $branch, + ): void { + if (!self::isBranchNameValid($branch)) { + throw new Exception('Invalid branch given', __LINE__); + } + } + + /** + * Check whether the type is valid. + * + * @param string $type The type of the release + * @return bool + */ + public static function isTypeValid( + string $type, + ): bool { + $types = ['weekly', 'minor', 'major', 'beta', 'rc', 'on-demand', 'on-sync', 'back-to-dev']; + return in_array($type, $types); + } + + /** + * Ensure the type is valid. + * + * @param string $type The type of the release + * @throws Exception + */ + public static function requireTypeValid( + string $type, + ): void { + if (!self::isTypeValid($type)) { + throw new Exception('Invalid type given.', __LINE__); + } + } + + /** + * Check whether the path is valid. + * + * @param string $path The path to the version file + * @return bool + */ + public static function isPathValid( + string $path, + ): bool { + if (file_exists($path) && is_readable($path)) { + if (is_writable($path)) { + return true; + } + } + return false; + } + + /** + * Ensure the path is valid. + * + * @param string $path The path to the version file + * @throws Exception + */ + public static function requirePathValid( + string $path, + ): void { + if (file_exists($path) && is_readable($path)) { + if (is_writable($path)) { + return; + } + throw new Exception('Path cannot be written to.', __LINE__); + } + throw new Exception('Invalid path given.', __LINE__); + } + + /** + * Validate the version file. + * + * @param string $contents The contents of the version file + * @param string $branch The branch name + * @return bool + */ + public static function isVersionFileValid( + string $contents, + string $branch, + ): bool { + $hasversion = strpos($contents, '$version ') !== false; + $hasrelease = strpos($contents, '$release ') !== false; + $hasbranch = strpos($contents, '$branch ') !== false; + $hasmaturity = strpos($contents, '$maturity ') !== false; + + if ($hasversion && $hasrelease && $hasbranch && $hasmaturity) { + return true; + } + if ($branch === 'MOODLE_19_STABLE' && $hasversion && $hasrelease) { + return true; + } + + return false; + } + + /** + * Ensure the version file is valid. + * + * @param string $contents The contents of the version file + * @param string $branch The branch name + * @throws Exception + */ + public static function requireVersionFileValid( + string $contents, + string $branch, + ): void { + if (!self::isVersionFileValid($contents, $branch)) { + throw new Exception('Invalid version file found.', __LINE__); + } + } + + /** + * Get the value of an option from the options array. + * + * @param array $options THe options configuration + * @param string $short The short name of the option + * @param string $long The long name of the option + * @return mixed + */ + public static function getOption( + array $options, + string $short, + string $long, + ): mixed { + if (!isset($options[$short]) && !isset($options[$long])) { + throw new Exception("Required option -$short|--$long must be provided.", __LINE__); + } + if ( + (isset($options[$short]) && is_array($options[$short])) || + (isset($options[$long]) && is_array($options[$long])) || + (isset($options[$short]) && isset($options[$long])) + ) { + throw new Exception("Option -$short|--$long specified more than once.", __LINE__); + } + return (isset($options[$short])) ? $options[$short] : $options[$long]; + } + +} diff --git a/src/VersionInfo.php b/src/VersionInfo.php new file mode 100644 index 0000000..9b98ee5 --- /dev/null +++ b/src/VersionInfo.php @@ -0,0 +1,249 @@ +. + +namespace MoodleHQ\MoodleRelease; + +use Exception; + +/** + * Version Information. + * + * @copyright 2024 Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class VersionInfo +{ + public function __construct( + public readonly int $integerversion, + public readonly string $decimalversion, + public readonly string $comment, + public readonly string $release, + public readonly string $build, + public readonly string $branch, + public readonly string $maturity, + public readonly string $branchquote, + public readonly string $releasequote, + ) { + } + + public static function fromVersionFile(string $versionfile, string $branch): self { + Helper::requireVersionFileValid($versionfile, $branch); + + $is19 = ($branch === 'MOODLE_19_STABLE'); + + if (!preg_match('#^ *\$version *= *(?P\d{10})\.(?P\d{2})\d?[^\/]*(?P/[^\n]*)#m', $versionfile, $matches)) { + throw new Exception('Could not determine version.', __LINE__); + } + $integerversion = $matches['integer']; + $decimalversion = $matches['decimal']; + $comment = $matches['comment']; + + if (!preg_match('#^ *\$release *= *(?P\'|")(?P[^ \+]+\+?) *\(Build: (?P\d{8})\)\1#m', $versionfile, $matches)) { + throw new Exception('Could not determine the release.', __LINE__); + } + $release = $matches['release']; + $releasequote = $matches['quote']; + $buildcurrent = $matches['build']; + + if (!$is19) { + if (!preg_match('# *\$branch *= *(?P\'|")(?P\d+)\1#m', $versionfile, $matches)) { + throw new Exception('Could not determine branch.', __LINE__); + } + $branchquote = $matches['quote']; + $branch = $matches['branch']; + if (!preg_match('# *\$maturity *= *(?PMATURITY_[A-Z]+)#m', $versionfile, $matches)) { + throw new Exception('Could not determine maturity.', __LINE__); + } + $maturity = $matches['maturity']; + } + + return new self( + integerversion: $integerversion, + decimalversion: $decimalversion, + comment: $comment, + release: $release, + build: $buildcurrent, + branch: $branch, + maturity: $maturity, + branchquote: $branchquote, + releasequote: $releasequote, + ); + } + + public function getNextVersion( + string $branch, + string $type, + string $rc, + bool $isdevbranch, + ?string $date = null, + ): self { + $today = date('Ymd'); + $isstable = Helper::isBranchStable($branch, $isdevbranch); + $build = empty($date) ? $today : $date; // Observe forced date. + + $release = $this->release; + $decimalversion = $this->decimalversion; + $integerversion = $this->integerversion; + $comment = $this->comment; + $branchcurrent = $this->branch; + $maturity = $this->maturity; + + if ($isstable) { + // It's a stable branch. + if ($type === 'weekly') { + // It's a stable branch. We need to bump the minor version and add a + if this was the first + // weekly release after a major or minor release. + if (strpos($release, '+') === false) { + // Add the + + $release = $release .= "+"; + } + + $decimalversion++; + $maturity = 'MATURITY_STABLE'; + } else if ($type === 'minor' || $type === 'major') { + // If it's minor fine, it's if major then stable gets a minor release. + // 2.6+ => 2.6.1 + // 2.6.12+ => 2.6.13 + if (strpos($release, '+') !== false) { + // Strip the +1 off + $release = substr($release, 0, -1); + } + if (preg_match('#^(?P\d+\.\d+)\.(?P\d+)#', $release, $matches)) { + $increment = $matches['increment'] + 1; + $release = $matches['version'].'.'.(string)$increment; + } else { + // First minor release on this stable branch. Yay X.Y.1. + $release .= '.1'; + } + $integerversion = (int)$integerversion + 1; + $integerversion = (string)$integerversion; + $decimalversion = '00'; + // Now handle build date for releases. + if (empty($date)) { // If no date has been forced, stable minors always are released on Monday. + if ((int)date('N') !== 1) { // If today is not Monday, calculate next one. + $build = date('Ymd', strtotime('next monday')); + } + } + } + + } else { + // Ok it's a development branch. + if ($type === 'weekly' || $type === 'minor') { + // If it's weekly, ok, if it's minor the dev branch doesn't get a minor release so really it's a weekly anyway. + // It's a dev branch. We need to bump the version, if the version is already higher than today*100 then we need + // to bump accordingly. + // If under beta or rc, make weekly behave exactly as on-demand. + if (strpos($release, 'beta') !== false or strpos($release, 'rc') !== false) { + // Add the + if missing. + if (strpos($release, '+') === false) { + // Add the + + $release .= '+'; + } + list($integerversion, $decimalversion) = Helper::getValidatedVersionNumber($integerversion, $decimalversion); + } else if (strpos($release, 'dev') === false) { + // Must be immediately after a major release. Bump the release version and set maturity to Alpha. + $release = (float)$release + 0.1; + $release = (string)$release.'dev'; + $maturity = 'MATURITY_ALPHA'; + } + list($integerversion, $decimalversion) = Helper::getValidatedVersionNumber($integerversion, $decimalversion); + } else if ($type === 'beta') { + $release = preg_replace('#^(\d+.\d+) *(dev|beta)\+?#', '$1', $release); + $branch = $branchcurrent; // Branch doesn't change in beta releases ever. + $release .= 'beta'; + list($integerversion, $decimalversion) = Helper::getValidatedVersionNumber($integerversion, $decimalversion); + $maturity = 'MATURITY_BETA'; + } else if ($type === 'rc') { + $release = preg_replace('#^(\d+.\d+) *(dev|beta|rc\d)\+?#', '$1', $release); + $branch = $branchcurrent; // Branch doesn't change in rc releases ever. + $release .= 'rc'.$rc; + list($integerversion, $decimalversion) = Helper::getValidatedVersionNumber($integerversion, $decimalversion); + $maturity = 'MATURITY_RC'; + } else if ($type === 'on-demand') { + // Add the + if missing (normally applies to post betas & rcs only, + // but it's not wrong to generalize it to any on-demand). + if (strpos($release, '+') === false) { + // Add the + + $release .= '+'; + } + list($integerversion, $decimalversion) = Helper::getValidatedVersionNumber($integerversion, $decimalversion); + } else if ($type === 'on-sync') { + $decimalversion++; + } else if ($type === 'back-to-dev') { + if (strpos($release, 'dev') === false) { // Ensure it's not a "dev" version already. + // Must be immediately after a major release. Bump comment, release and maturity. + $comment = '// YYYYMMDD = weekly release date of this DEV branch.'; + // Normalise a little bit the release, getting rid of everything after the numerical part. + $release = preg_replace('/^([0-9.]+).*$/', '\1', $release); + // Split the major and minor parts of the release for further process. + list($releasemajor, $releaseminor) = explode('.', $release); + $release = $releasemajor . '.' . (++$releaseminor); // Increment to next dev version. + $release = $release . 'dev'; + // The branch is the major followed by 2-chars minor. + $branch = $releasemajor . str_pad($releaseminor, 2, '0', STR_PAD_LEFT); + $maturity = 'MATURITY_ALPHA'; + if (empty($date)) { // If no date has been forced, back-to-dev have same build date than majors. + if ((int)date('N') !== 1) { // If today is not Monday, calculate next one. + $build = date('Ymd', strtotime('next Monday')); + } + } + } + } else if ($type === 'major') { + // Awesome major release! + $release = preg_replace('#^(\d+.\d+) *(dev|beta|rc\d+)\+?#', '$1', $release); + $branch = $branchcurrent; // Branch doesn't change in major releases ever. + list($integerversion, $decimalversion) = Helper::getValidatedVersionNumber($integerversion, $decimalversion); + $maturity = 'MATURITY_STABLE'; + // Now handle builddate for releases. + if (empty($date)) { // If no date has been forced, dev majors always are released on Monday. + if ((int)date('N') !== 1) { // If today is not Monday, calculate next one. + $build = date('Ymd', strtotime('next Monday')); + } + } + $comment = '// ' . $build . ' = branching date YYYYMMDD - do not modify!'; + // TODO: Move this to Helper::getValidatedVersionNumber() to keep things clear. Require params. + // Also force version for major releases. Must match "next Monday" or --date (if specified) + if (empty($date)) { // If no date has been forced, dev majors always are released on Monday. + if ((int)date('N') !== 1) { // If today is not Monday, calculate next one. + $integerversion = date('Ymd', strtotime('next Monday')) . '00'; + } + } else { + $integerversion = $date . '00'; // Apply $date also to major versions. + } + $decimalversion = '00'; // Majors always have the decimal reset to .00. + } else { + throw new Exception('Unknown type of release requested.', __LINE__); + } + } + + // Replace the old version with the new version. + if (strlen($decimalversion) === 1) { + $decimalversion = '0'.$decimalversion; + } + + return new self( + integerversion: $integerversion, + decimalversion: $decimalversion, + comment: $comment, + release: $release, + build: $build, + branch: $branch, + maturity: $maturity, + branchquote: $this->branchquote, + releasequote: $this->releasequote, + ); + } +} diff --git a/src/helper.php b/src/helper.php deleted file mode 100644 index 54d6c9c..0000000 --- a/src/helper.php +++ /dev/null @@ -1,408 +0,0 @@ -. - -namespace MoodleHQ\MoodleRelease; - -use Exception; - -/** - * Helper library for Moodle Release scripts. - * - * @package core - * @copyright 2024 Andrew Lyons - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -class Helper { - /** - * Bump the version. - * - * @param string $path - * @param string $branch - * @param string $type - * @param string $rc - * @param string $date - * @param bool $isdevbranch - * @throws Exception - * @return mixed - */ - public static function bumpVersion( - string $path, - string $branch, - string $type, - string $rc, - string $date, - bool $isdevbranch, - ) { - - self::isBranchNameValid($branch); - self::isTypeValid($type); - self::isPathValid($path); - - $versionfile = file_get_contents($path); - - self::isVersionFileValid($versionfile, $branch); - - $is19 = ($branch === 'MOODLE_19_STABLE'); - $isstable = self::isBranchStable($branch, $isdevbranch); - $today = date('Ymd'); - - $integerversioncurrent = null; - $decimalversioncurrent = null; - $commentcurrent = null; - $releasecurrent = null; - $buildcurrent = null; - $branchcurrent = null; - $maturitycurrent = null; - - $branchquote = null; - $releasequote = null; - - $integerversionnew = null; - $decimalversionnew = null; - $commentnew = null; - $releasenew = null; - $buildnew = null; - $branchnew = null; - $maturitynew = null; - - if (!preg_match('#^ *\$version *= *(?P\d{10})\.(?P\d{2})\d?[^\/]*(?P/[^\n]*)#m', $versionfile, $matches)) { - throw new Exception('Could not determine version.', __LINE__); - } - $integerversionnew = $integerversioncurrent = $matches['integer']; - $decimalversionnew = $decimalversioncurrent = $matches['decimal']; - $commentnew = $commentcurrent = $matches['comment']; - - if (!preg_match('#^ *\$release *= *(?P\'|")(?P[^ \+]+\+?) *\(Build: (?P\d{8})\)\1#m', $versionfile, $matches)) { - throw new Exception('Could not determine the release.', __LINE__); - } - $releasenew = $releasecurrent = $matches['release']; - $releasequote = $matches['quote']; - $buildcurrent = $matches['build']; - $buildnew = empty($date) ? $today : $date; // Observe forced date. - - if (!$is19) { - if (!preg_match('# *\$branch *= *(?P\'|")(?P\d+)\1#m', $versionfile, $matches)) { - throw new Exception('Could not determine branch.', __LINE__); - } - $branchquote = $matches['quote']; - $branchnew = $branchcurrent = $matches['branch']; - if (!preg_match('# *\$maturity *= *(?PMATURITY_[A-Z]+)#m', $versionfile, $matches)) { - throw new Exception('Could not determine maturity.', __LINE__); - } - $maturitynew = $maturitycurrent = $matches['maturity']; - } - - if ($isstable) { - // It's a stable branch. - if ($type === 'weekly') { - // It's a stable branch. We need to bump the minor version and add a + if this was the first - // weekly release after a major or minor release. - if (strpos($releasenew, '+') === false) { - // Add the + - $releasenew .= '+'; - } - $decimalversionnew++; - $maturitynew = 'MATURITY_STABLE'; - } else if ($type === 'minor' || $type === 'major') { - // If it's minor fine, it's if major then stable gets a minor release. - // 2.6+ => 2.6.1 - // 2.6.12+ => 2.6.13 - if (strpos($releasenew, '+') !== false) { - // Strip the +1 off - $releasenew = substr($releasenew, 0, -1); - } - if (preg_match('#^(?P\d+\.\d+)\.(?P\d+)#', $releasenew, $matches)) { - $increment = $matches['increment'] + 1; - $releasenew = $matches['version'].'.'.(string)$increment; - } else { - // First minor release on this stable branch. Yay X.Y.1. - $releasenew .= '.1'; - } - $integerversionnew = (int)$integerversionnew + 1; - $integerversionnew = (string)$integerversionnew; - $decimalversionnew = '00'; - // Now handle build date for releases. - if (empty($date)) { // If no date has been forced, stable minors always are released on Monday. - if ((int)date('N') !== 1) { // If today is not Monday, calculate next one. - $buildnew = date('Ymd', strtotime('next monday')); - } - } - } - - } else { - // Ok it's a development branch. - if ($type === 'weekly' || $type === 'minor') { - // If it's weekly, ok, if it's minor the dev branch doesn't get a minor release so really it's a weekly anyway. - // It's a dev branch. We need to bump the version, if the version is already higher than today*100 then we need - // to bump accordingly. - // If under beta or rc, make weekly behave exactly as on-demand. - if (strpos($releasecurrent, 'beta') !== false or strpos($releasecurrent, 'rc') !== false) { - // Add the + if missing. - if (strpos($releasenew, '+') === false) { - // Add the + - $releasenew .= '+'; - } - list($integerversionnew, $decimalversionnew) = self::getValidatedVersionNumber($integerversionnew, $decimalversionnew); - } else if (strpos($releasecurrent, 'dev') === false) { - // Must be immediately after a major release. Bump the release version and set maturity to Alpha. - $releasenew = (float)$releasenew + 0.1; - $releasenew = (string)$releasenew.'dev'; - $maturitynew = 'MATURITY_ALPHA'; - } - list($integerversionnew, $decimalversionnew) = self::getValidatedVersionNumber($integerversionnew, $decimalversionnew); - } else if ($type === 'beta') { - $releasenew = preg_replace('#^(\d+.\d+) *(dev|beta)\+?#', '$1', $releasenew); - $branchnew = $branchcurrent; // Branch doesn't change in beta releases ever. - $releasenew .= 'beta'; - list($integerversionnew, $decimalversionnew) = self::getValidatedVersionNumber($integerversionnew, $decimalversionnew); - $maturitynew = 'MATURITY_BETA'; - } else if ($type === 'rc') { - $releasenew = preg_replace('#^(\d+.\d+) *(dev|beta|rc\d)\+?#', '$1', $releasenew); - $branchnew = $branchcurrent; // Branch doesn't change in rc releases ever. - $releasenew .= 'rc'.$rc; - list($integerversionnew, $decimalversionnew) = self::getValidatedVersionNumber($integerversionnew, $decimalversionnew); - $maturitynew = 'MATURITY_RC'; - } else if ($type === 'on-demand') { - // Add the + if missing (normally applies to post betas & rcs only, - // but it's not wrong to generalize it to any on-demand). - if (strpos($releasenew, '+') === false) { - // Add the + - $releasenew .= '+'; - } - list($integerversionnew, $decimalversionnew) = self::getValidatedVersionNumber($integerversionnew, $decimalversionnew); - } else if ($type === 'on-sync') { - $decimalversionnew++; - } else if ($type === 'back-to-dev') { - if (strpos($releasecurrent, 'dev') === false) { // Ensure it's not a "dev" version already. - // Must be immediately after a major release. Bump comment, release and maturity. - $commentnew = '// YYYYMMDD = weekly release date of this DEV branch.'; - // Normalise a little bit the release, getting rid of everything after the numerical part. - $releasenew = preg_replace('/^([0-9.]+).*$/', '\1', $releasenew); - // Split the major and minor parts of the release for further process. - list($releasemajor, $releaseminor) = explode('.', $releasenew); - $releasenew = $releasemajor . '.' . (++$releaseminor); // Increment to next dev version. - $releasenew = $releasenew . 'dev'; - // The branch is the major followed by 2-chars minor. - $branchnew = $releasemajor . str_pad($releaseminor, 2, '0', STR_PAD_LEFT); - $maturitynew = 'MATURITY_ALPHA'; - if (empty($date)) { // If no date has been forced, back-to-dev have same build date than majors. - if ((int)date('N') !== 1) { // If today is not Monday, calculate next one. - $buildnew = date('Ymd', strtotime('next Monday')); - } - } - } - } else { - // Awesome major release! - $releasenew = preg_replace('#^(\d+.\d+) *(dev|beta|rc\d+)\+?#', '$1', $releasenew); - $branchnew = $branchcurrent; // Branch doesn't change in major releases ever. - list($integerversionnew, $decimalversionnew) = self::getValidatedVersionNumber($integerversionnew, $decimalversionnew); - $maturitynew = 'MATURITY_STABLE'; - // Now handle builddate for releases. - if (empty($date)) { // If no date has been forced, dev majors always are released on Monday. - if ((int)date('N') !== 1) { // If today is not Monday, calculate next one. - $buildnew = date('Ymd', strtotime('next Monday')); - } - } - $commentnew = '// ' . $buildnew . ' = branching date YYYYMMDD - do not modify!'; - // TODO: Move this to self::getValidatedVersionNumber() to keep things clear. Require new params. - // Also force version for major releases. Must match "next Monday" or --date (if specified) - if (empty($date)) { // If no date has been forced, dev majors always are released on Monday. - if ((int)date('N') !== 1) { // If today is not Monday, calculate next one. - $integerversionnew = date('Ymd', strtotime('next Monday')) . '00'; - } - } else { - $integerversionnew = $date . '00'; // Apply $date also to major versions. - } - $decimalversionnew = '00'; // Majors always have the decimal reset to .00. - } - } - - // Replace the old version with the new version. - if (strlen($decimalversionnew) === 1) { - $decimalversionnew = '0'.$decimalversionnew; - } - $versionfile = str_replace($integerversioncurrent.'.'.$decimalversioncurrent, $integerversionnew.'.'.$decimalversionnew, $versionfile); - // Replace the old build with the new build. - $versionfile = str_replace('Build: '.$buildcurrent, 'Build: '.$buildnew, $versionfile); - // Replace the old release with the new release if they've changed. - if ($releasecurrent !== $releasenew) { - $versionfile = str_replace($releasequote.$releasecurrent, $releasequote.$releasenew, $versionfile); - } - // Replace the old comment with the new one if they've changed - if ($commentcurrent !== $commentnew) { - $versionfile = str_replace($commentcurrent, $commentnew, $versionfile); - } - - if (!$is19) { - // Replace the branch value if need be. - if ($branchcurrent !== $branchnew) { - $versionfile = str_replace($branchquote.$branchcurrent.$branchquote, $branchquote.$branchnew.$branchquote, $versionfile); - } - // Replace the maturity value if need be. - if ($maturitycurrent !== $maturitynew) { - $versionfile = str_replace('= '.$maturitycurrent, '= '.$maturitynew, $versionfile); - } - } - - file_put_contents($path, $versionfile); - - return $releasenew; - } - - /** - * Ensure that the buymped version is higher than the current one. - * - * @param int $versionint The integer part of the version - * @param int $versiondec The decimal part of the version - * @return array - */ - public static function getValidatedVersionNumber( - int $versionint, - int $versiondec, - ): array { - $today = date('Ymd'); - if ($versionint >= $today * 100) { - // Integer version is already past today * 100, increment version decimal part instead of integer part. - $versiondec = (int) $versiondec + 1; - $versiondec = (string) $versiondec; - } else { - $versionint = $today . '00'; - $versiondec = '00'; - } - return [ - $versionint, - $versiondec, - ]; - } - - /** - * Check if the branch is a stable branch. - * - * @param string $branch The branch name - * @param bool $isdevbranch Whether the branch is a development branch - * @return bool - */ - public static function isBranchStable( - string $branch, - bool $isdevbranch, - ): bool { - return (strpos($branch, '_STABLE') !== false && !$isdevbranch); - } - - /** - * Validate the branch. - * - * @param string $branch The branch name - * @throws Exception - */ - public static function isBranchNameValid( - string $branch, - ): bool { - if (!preg_match('#^(main|MOODLE_(\d+)_STABLE)$#', $branch, $matches)) { - throw new Exception('Invalid branch given', __LINE__); - } - return true; - } - - /** - * Check whether the type is valid. - * - * @param string $type The type of the release - * @return bool - * @throws Exception - */ - public static function isTypeValid( - string $type, - ): bool { - $types = ['weekly', 'minor', 'major', 'beta', 'rc', 'on-demand', 'on-sync', 'back-to-dev']; - if (!in_array($type, $types)) { - throw new Exception('Invalid type given.', __LINE__); - } - return true; - } - - /** - * Check whether the path is valid. - * - * @param string $path The path to the version file - * @return bool - * @throws Exception - */ - public static function isPathValid( - string $path, - ): bool { - if (file_exists($path) && is_readable($path)) { - if (is_writable($path)) { - return true; - } - throw new Exception('Path cannot be written to.', __LINE__); - } - throw new Exception('Invalid path given.', __LINE__); - } - - /** - * Validate the version file. - * - * @param string $contents The contents of the version file - * @param string $branch The branch name - * @return bool - * @throws Exception - */ - public static function isVersionFileValid( - string $contents, - string $branch, - ): bool { - $hasversion = strpos($contents, '$version ') !== false; - $hasrelease = strpos($contents, '$release ') !== false; - $hasbranch = strpos($contents, '$branch ') !== false; - $hasmaturity = strpos($contents, '$maturity ') !== false; - - if ($hasversion && $hasrelease && $hasbranch && $hasmaturity) { - return true; - } - if ($branch === 'MOODLE_19_STABLE' && $hasversion && $hasrelease) { - return true; - } - throw new Exception('Invalid version file found.', __LINE__); - } - - /** - * Get the value of an option from the options array. - * - * @param array $options THe options configuration - * @param string $short The short name of the option - * @param string $long The long name of the option - * @return mixed - */ - public static function getOption( - array $options, - string $short, - string $long, - ): mixed { - if (!isset($options[$short]) && !isset($options[$long])) { - throw new Exception("Required option -$short|--$long must be provided.", __LINE__); - } - if ( - (isset($options[$short]) && is_array($options[$short])) || - (isset($options[$long]) && is_array($options[$long])) || - (isset($options[$short]) && isset($options[$long])) - ) { - throw new Exception("Option -$short|--$long specified more than once.", __LINE__); - } - return (isset($options[$short])) ? $options[$short] : $options[$long]; - } - -} diff --git a/tests/bootstrap.php b/tests/bootstrap.php new file mode 100644 index 0000000..8a1057e --- /dev/null +++ b/tests/bootstrap.php @@ -0,0 +1,19 @@ +. + +declare(strict_types=1); + +require_once(__DIR__ . '/../vendor/autoload.php'); diff --git a/tests/fixtures/versions/4.3.0.php b/tests/fixtures/versions/4.3.0.php new file mode 100644 index 0000000..2d27e7d --- /dev/null +++ b/tests/fixtures/versions/4.3.0.php @@ -0,0 +1,37 @@ +. + +/** + * MOODLE VERSION INFORMATION + * + * This file defines the current version of the core Moodle code being used. + * This is compared against the values stored in the database to determine + * whether upgrades should be performed (see lib/db/*.php) + * + * @package core + * @copyright 1999 onwards Martin Dougiamas (http://dougiamas.com) + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +$version = 2023100900.00; // 20231009 = branching date YYYYMMDD - do not modify! + // RR = release increments - 00 in DEV branches. + // .XX = incremental changes. +$release = '4.3 (Build: 20231009)'; // Human-friendly version name +$branch = '403'; // This version's branch. +$maturity = MATURITY_STABLE; // This version's maturity level. diff --git a/tests/fixtures/versions/4.3.2.php b/tests/fixtures/versions/4.3.2.php new file mode 100644 index 0000000..b245e32 --- /dev/null +++ b/tests/fixtures/versions/4.3.2.php @@ -0,0 +1,37 @@ +. + +/** + * MOODLE VERSION INFORMATION + * + * This file defines the current version of the core Moodle code being used. + * This is compared against the values stored in the database to determine + * whether upgrades should be performed (see lib/db/*.php) + * + * @package core + * @copyright 1999 onwards Martin Dougiamas (http://dougiamas.com) + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +$version = 2023100902.00; // 20231009 = branching date YYYYMMDD - do not modify! + // RR = release increments - 00 in DEV branches. + // .XX = incremental changes. +$release = '4.3.2 (Build: 20231222)'; // Human-friendly version name +$branch = '403'; // This version's branch. +$maturity = MATURITY_STABLE; // This version's maturity level. diff --git a/tests/fixtures/versions/4.4.0-beta.php b/tests/fixtures/versions/4.4.0-beta.php new file mode 100644 index 0000000..b882e07 --- /dev/null +++ b/tests/fixtures/versions/4.4.0-beta.php @@ -0,0 +1,37 @@ +. + +/** + * MOODLE VERSION INFORMATION + * + * This file defines the current version of the core Moodle code being used. + * This is compared against the values stored in the database to determine + * whether upgrades should be performed (see lib/db/*.php) + * + * @package core + * @copyright 1999 onwards Martin Dougiamas (http://dougiamas.com) + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +$version = 2024041200.01; // YYYYMMDD = weekly release date of this DEV branch. + // RR = release increments - 00 in DEV branches. + // .XX = incremental changes. +$release = '4.4beta (Build: 20240412)'; // Human-friendly version name +$branch = '404'; // This version's branch. +$maturity = MATURITY_BETA; // This version's maturity level. diff --git a/tests/fixtures/versions/4.4.0-rc1.php b/tests/fixtures/versions/4.4.0-rc1.php new file mode 100644 index 0000000..9b11b6e --- /dev/null +++ b/tests/fixtures/versions/4.4.0-rc1.php @@ -0,0 +1,37 @@ +. + +/** + * MOODLE VERSION INFORMATION + * + * This file defines the current version of the core Moodle code being used. + * This is compared against the values stored in the database to determine + * whether upgrades should be performed (see lib/db/*.php) + * + * @package core + * @copyright 1999 onwards Martin Dougiamas (http://dougiamas.com) + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +$version = 2024041600.00; // YYYYMMDD = weekly release date of this DEV branch. + // RR = release increments - 00 in DEV branches. + // .XX = incremental changes. +$release = '4.4rc1 (Build: 20240416)'; // Human-friendly version name +$branch = '404'; // This version's branch. +$maturity = MATURITY_RC; // This version's maturity level. diff --git a/tests/fixtures/versions/4.4.0-rc2.php b/tests/fixtures/versions/4.4.0-rc2.php new file mode 100644 index 0000000..951461b --- /dev/null +++ b/tests/fixtures/versions/4.4.0-rc2.php @@ -0,0 +1,37 @@ +. + +/** + * MOODLE VERSION INFORMATION + * + * This file defines the current version of the core Moodle code being used. + * This is compared against the values stored in the database to determine + * whether upgrades should be performed (see lib/db/*.php) + * + * @package core + * @copyright 1999 onwards Martin Dougiamas (http://dougiamas.com) + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +$version = 2024041900.00; // YYYYMMDD = weekly release date of this DEV branch. + // RR = release increments - 00 in DEV branches. + // .XX = incremental changes. +$release = '4.4rc2 (Build: 20240419)'; // Human-friendly version name +$branch = '404'; // This version's branch. +$maturity = MATURITY_RC; // This version's maturity level. diff --git a/tests/fixtures/versions/4.4.0.php b/tests/fixtures/versions/4.4.0.php new file mode 100644 index 0000000..b361a9b --- /dev/null +++ b/tests/fixtures/versions/4.4.0.php @@ -0,0 +1,37 @@ +. + +/** + * MOODLE VERSION INFORMATION + * + * This file defines the current version of the core Moodle code being used. + * This is compared against the values stored in the database to determine + * whether upgrades should be performed (see lib/db/*.php) + * + * @package core + * @copyright 1999 onwards Martin Dougiamas (http://dougiamas.com) + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +$version = 2024042200.00; // 20240422 = branching date YYYYMMDD - do not modify! + // RR = release increments - 00 in DEV branches. + // .XX = incremental changes. +$release = '4.4 (Build: 20240422)'; // Human-friendly version name +$branch = '404'; // This version's branch. +$maturity = MATURITY_STABLE; // This version's maturity level. diff --git a/tests/unit/HelperTest.php b/tests/unit/HelperTest.php new file mode 100644 index 0000000..47f4b6a --- /dev/null +++ b/tests/unit/HelperTest.php @@ -0,0 +1,365 @@ +. + +declare(strict_types=1); + +namespace MoodleHQ\MoodleRelease; + +use org\bovigo\vfs\vfsStream; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\TestCase; + +#[CoversClass(Helper::class)] +final class HelperTest extends TestCase +{ + public static function setUpBeforeClass(): void + { + date_default_timezone_set('Australia/Perth'); + } + + #[DataProvider('validTypeProvider')] + public function testIsTypeValid(string $type, bool $expected): void + { + $this->assertEquals($expected, Helper::isTypeValid($type)); + } + + #[DataProvider('validTypeProvider')] + public function testRequireTypeValid(string $type, bool $expected): void + { + if ($expected) { + $this->assertNull(Helper::requireTypeValid($type)); + } else { + $this->expectException(\Exception::class); + Helper::requireTypeValid($type); + } + } + + public static function validTypeProvider(): array + { + return [ + ['weekly', true], + ['minor', true], + ['major', true], + ['beta', true], + ['rc', true], + ['on-demand', true], + ['on-sync', true], + ['back-to-dev', true], + ['faketype', false,] + ]; + } + + #[DataProvider('getValidatedVersionNumberProvider')] + public function testGetValidatedVersionNumber( + int $int, + int $dec, + int $expectedint, + string $expecteddec + ): void { + [$newint, $newdec] = Helper::getValidatedVersionNumber($int, $dec); + $this->assertSame($expectedint, $newint); + $this->assertSame($expecteddec, $newdec); + } + + public static function getValidatedVersionNumberProvider(): array + { + date_default_timezone_set('Australia/Perth'); + $now = new \DateTimeImmutable(); + $today = $now->format('Ymd') * 100; + $yesterday = $now->modify('-1 day')->format('Ymd') * 100; + $tomorrow = $now->modify('+1 day')->format('Ymd') * 100; + + return [ + 'Valid yesterday' => [ + $yesterday, + 0, + $today, + '00', + ], + 'Valid today' => [ + $today, + 0, + $today, + '01', + ], + 'Valid today with increment' => [ + $today + 1, + 0, + $today + 1, + '01', + ], + 'End of today' => [ + $today, + 99, + $today + 1, + '00', + ], + 'Tomorrow' => [ + $tomorrow, + 0, + $tomorrow, + '01', + ], + ]; + } + + #[DataProvider('stableVersionProvider')] + public function testIsBranchStable( + string $branch, + bool $isdevbranch, + bool $expected, + ): void { + $this->assertSame($expected, Helper::isBranchStable($branch, $isdevbranch)); + } + + public static function stableVersionProvider(): array + { + return [ + 'main' => ['main', true, false], + 'master' => ['master', true, false], + 'MOODLE_19_STABLE' => ['MOODLE_19_STABLE', false, true], + 'MOODLE_401_STABLE' => ['MOODLE_401_STABLE', false, true], + 'MOODLE_500_STABLE' => ['MOODLE_500_STABLE', false, true], + 'MOODLE_500_STABLE in parallel develoipment' => ['MOODLE_500_STABLE', true, false], + ]; + } + + #[DataProvider('isBranchNameValidProvider')] + public function testIsBranchNameValid( + string $name, + bool $expected, + ): void { + $this->assertEquals($expected, Helper::isBranchNameValid($name)); + } + + #[DataProvider('isBranchNameValidProvider')] + public function testRequireBranchNameValid( + string $name, + bool $expected, + ): void { + if ($expected) { + $this->assertNull(Helper::requireBranchNameValid($name)); + } else { + $this->expectException(\Exception::class); + Helper::requireBranchNameValid($name); + } + } + + public static function isBranchNameValidProvider(): array + { + return [ + 'main' => ['main', true], + 'master' => ['master', false], + 'MOODLE_19_STABLE' => ['MOODLE_19_STABLE', true], + 'MOODLE_401_STABLE' => ['MOODLE_401_STABLE', true], + 'MOODLE_500_STABLE' => ['MOODLE_500_STABLE', true], + 'MOODLE_500_STABLE in parallel develoipment' => ['MOODLE_500_STABLE', true], + ]; + } + + #[DataProvider('pathValidProvider')] + public function testIsPathValid( + string $path, + bool $expected, + ): void { + // Set up a virtual file system. + $root = vfsStream::setup('root', structure: [ + 'path' => [ + 'to' => [ + 'unreadable' => '', + 'readable' => [ + 'not' => [ + 'writeable' => '', + ], + 'and' => [ + 'writeable' => '', + ], + ], + ], + ], + ]); + $root->getChild('path/to/unreadable')->chmod(0000); + $root->getChild('path/to/readable/not/writeable')->chmod(0444); + $root->getChild('path/to/readable/and/writeable')->chmod(0666); + + $path = vfsStream::url("root/{$path}"); + $this->assertEquals( + $expected, + Helper::isPathValid($path), + ); + } + + #[DataProvider('pathValidProvider')] + public function testRequirePathValid( + string $path, + bool $expected, + ?string $message, + ): void { + // Set up a virtual file system. + $root = vfsStream::setup('root', structure: [ + 'path' => [ + 'to' => [ + 'unreadable' => '', + 'readable' => [ + 'not' => [ + 'writeable' => '', + ], + 'and' => [ + 'writeable' => '', + ], + ], + ], + ], + ]); + $root->getChild('path/to/unreadable')->chmod(0000); + $root->getChild('path/to/readable/not/writeable')->chmod(0444); + $root->getChild('path/to/readable/and/writeable')->chmod(0666); + + $path = vfsStream::url("root/{$path}"); + if ($expected) { + $this->assertNull(Helper::requirePathValid($path)); + } else { + $this->expectException(\Exception::class); + $this->expectExceptionMessage($message); + Helper::requirePathValid($path); + } + } + + public static function pathValidProvider(): array + { + return [ + 'Not readable' => [ + 'path/to/unreadable', + false, + 'Invalid path given.' + ], + 'Not writeable' => [ + 'path/to/readable/not/writeable', + false, + 'Path cannot be written to.', + ], + 'Readable and writable' => [ + 'path/to/readable/and/writeable', + true, + null, + ], + ]; + } + + #[DataProvider('versionFileProvider')] + public function testIsVersionFileValid( + string $content, + string $branch, + bool $expected, + ): void { + $this->assertEquals( + $expected, + Helper::isVersionFileValid($content, $branch), + ); + } + + #[DataProvider('versionFileProvider')] + public function testRequireVersionFileValid( + string $content, + string $branch, + bool $expected, + ): void { + if ($expected) { + $this->assertNull(Helper::requireVersionFileValid($content, $branch)); + } else { + $this->expectException(\Exception::class); + Helper::requireVersionFileValid($content, $branch); + } + } + + public static function versionFileProvider(): array { + return [ + 'Valid version file' => [ + 'content' => << 'MOODLE_405_STABLE', + 'expected' => true, + ], + 'Missing version' => [ + 'content' => << 'MOODLE_405_STABLE', + 'expected' => false, + ], + 'Missing release' => [ + 'content' => << 'MOODLE_405_STABLE', + 'expected' => false, + ], + 'Missing branch' => [ + 'content' => << 'MOODLE_405_STABLE', + 'expected' => false, + ], + 'Missing maturity' => [ + 'content' => << 'MOODLE_405_STABLE', + 'expected' => false, + ], + 'Valid 19' => [ + 'content' => << 'MOODLE_19_STABLE', + 'expected' => true, + ], + 'Missing version 19' => [ + 'content' => << 'MOODLE_19_STABLE', + 'expected' => false, + ], + 'Missing release 19' => [ + 'content' => << 'MOODLE_19_STABLE', + 'expected' => false, + ], + ]; + } +} diff --git a/tests/unit/VersionInfoTest.php b/tests/unit/VersionInfoTest.php new file mode 100644 index 0000000..b7517e1 --- /dev/null +++ b/tests/unit/VersionInfoTest.php @@ -0,0 +1,633 @@ +. + +declare(strict_types=1); + +namespace MoodleHQ\MoodleRelease; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\TestCase; + +#[CoversClass(VersionInfo::class)] +final class VersionInfoTest extends TestCase +{ + #[DataProvider('nextVersionFromMajorProvider')] + #[DataProvider('nextVersionFromMinorProvider')] + #[DataProvider('nextVersionFromWeeklyProvider')] + #[DataProvider('nextVersionFromDevelopmentProvider')] + #[DataProvider('nextVersionFromBetaProvider')] + public function testGetNextVersion( + array $currentVersionArgs, + array $nextVersionArgs, + array $expectations, + ): void + { + $version = new VersionInfo(...$currentVersionArgs); + $nextVersion = $version->getNextVersion(...$nextVersionArgs); + + foreach ($expectations as $property => $expectedValue) { + self::assertSame($expectedValue, $nextVersion->{$property}); + } + } + + public static function nextVersionFromMajorProvider(): array + { + $majorVersion = [ + 'integerversion' => 2024092300, + 'decimalversion' => '00', + 'comment' => '// 20240923 = branching date YYYYMMDD - do not modify!', + 'release' => '4.5', + 'build' => '20240921', + 'branch' => '405', + 'maturity' => 'MATURITY_STABLE', + 'branchquote' => "'", + 'releasequote' => "'", + ]; + + return [ + 'Weekly version from major' => [ + $majorVersion, + [ + 'branch' => 'MOODLE_405_STABLE', + 'type' => 'weekly', + 'rc' => '', + 'date' => '20240926', + 'isdevbranch' => false, + ], + [ + 'integerversion' => 2024092300, + 'decimalversion' => '01', + 'release' => '4.5+', + 'build' => '20240926', + 'branchquote' => "'", + 'releasequote' => "'", + ], + ], + 'Minor version from major' => [ + $majorVersion, + [ + 'branch' => 'MOODLE_405_STABLE', + 'type' => 'minor', + 'rc' => '', + 'date' => '20240923', + 'isdevbranch' => false, + ], + [ + 'integerversion' => 2024092301, + 'decimalversion' => '00', + 'release' => '4.5.1', + 'build' => '20240923', + 'branchquote' => "'", + 'releasequote' => "'", + ], + ], + 'Major version from major' => [ + // Note: A Major release also includes minors for stable branches. + $majorVersion, + [ + 'branch' => 'MOODLE_405_STABLE', + 'type' => 'major', + 'rc' => '', + 'date' => '20240923', + 'isdevbranch' => false, + ], + [ + 'integerversion' => 2024092301, + 'decimalversion' => '00', + 'release' => '4.5.1', + 'build' => '20240923', + 'branchquote' => "'", + 'releasequote' => "'", + ], + ], + 'Development version from major' => [ + $majorVersion, + [ + 'branch' => 'MOODLE_500_STABLE', + 'type' => 'weekly', + 'rc' => '', + 'date' => '20240923', + 'isdevbranch' => true, + ], + [ + 'integerversion' => date('Ymd') * 100, + 'decimalversion' => '00', + 'release' => '4.6dev', // Note: The tooling has not yet been updated to handle the new versioning scheme. + 'build' => '20240923', + 'branchquote' => "'", + 'releasequote' => "'", + ], + ], + ]; + } + + public static function nextVersionFromMinorProvider(): array + { + $minorVersion = [ + 'integerversion' => 2024092301, + 'decimalversion' => '00', + 'comment' => '// 20240923 = branching date YYYYMMDD - do not modify!', + 'release' => '4.5.1', + 'build' => '20240921', + 'branch' => '405', + 'maturity' => 'MATURITY_STABLE', + 'branchquote' => "'", + 'releasequote' => "'", + ]; + + return [ + 'Weekly version from minor' => [ + $minorVersion, + [ + 'branch' => 'MOODLE_405_STABLE', + 'type' => 'weekly', + 'rc' => '', + 'date' => '20240926', + 'isdevbranch' => false, + ], + [ + 'integerversion' => 2024092301, + 'decimalversion' => '01', + 'release' => '4.5.1+', + 'build' => '20240926', + 'branchquote' => "'", + 'releasequote' => "'", + ], + ], + 'Minor version from minor' => [ + $minorVersion, + [ + 'branch' => 'MOODLE_405_STABLE', + 'type' => 'minor', + 'rc' => '', + 'date' => '20240923', + 'isdevbranch' => false, + ], + [ + 'integerversion' => 2024092302, + 'decimalversion' => '00', + 'release' => '4.5.2', + 'build' => '20240923', + 'branchquote' => "'", + 'releasequote' => "'", + ], + ], + 'Major version from minor' => [ + $minorVersion, + [ + 'branch' => 'MOODLE_405_STABLE', + 'type' => 'major', + 'rc' => '', + 'date' => '20240923', + 'isdevbranch' => false, + ], + [ + 'integerversion' => 2024092302, + 'decimalversion' => '00', + 'release' => '4.5.2', + 'build' => '20240923', + 'branchquote' => "'", + 'releasequote' => "'", + ], + ], + ]; + } + public static function nextVersionFromWeeklyProvider(): array + { + $weeklyVersion = [ + 'integerversion' => 2024092301, + 'decimalversion' => '00', + 'comment' => '// 20240923 = branching date YYYYMMDD - do not modify!', + 'release' => '4.5.1+', + 'build' => '20240921', + 'branch' => '405', + 'maturity' => 'MATURITY_STABLE', + 'branchquote' => "'", + 'releasequote' => "'", + ]; + + return [ + 'Weekly version from weekly' => [ + $weeklyVersion, + [ + 'branch' => 'MOODLE_405_STABLE', + 'type' => 'weekly', + 'rc' => '', + 'date' => '20240926', + 'isdevbranch' => false, + ], + [ + 'integerversion' => 2024092301, + 'decimalversion' => '01', + 'release' => '4.5.1+', + 'build' => '20240926', + 'branchquote' => "'", + 'releasequote' => "'", + ], + ], + 'Minor version from weekly' => [ + $weeklyVersion, + [ + 'branch' => 'MOODLE_405_STABLE', + 'type' => 'minor', + 'rc' => '', + 'date' => '20240923', + 'isdevbranch' => false, + ], + [ + 'integerversion' => 2024092302, + 'decimalversion' => '00', + 'release' => '4.5.2', + 'build' => '20240923', + 'branchquote' => "'", + 'releasequote' => "'", + ], + ], + 'Major version from weekly' => [ + $weeklyVersion, + [ + 'branch' => 'MOODLE_405_STABLE', + 'type' => 'major', + 'rc' => '', + 'date' => '20240923', + 'isdevbranch' => false, + ], + [ + 'integerversion' => 2024092302, + 'decimalversion' => '00', + 'release' => '4.5.2', + 'build' => '20240923', + 'branchquote' => "'", + 'releasequote' => "'", + ], + ], + ]; + } + public static function nextVersionFromDevelopmentProvider(): array + { + $developmentVersion = [ + 'integerversion' => 2024092301, + 'decimalversion' => '00', + 'comment' => '// 20240923 = branching date YYYYMMDD - do not modify!', + 'release' => '5.0dev', + 'build' => '20240921', + 'branch' => '500', + 'maturity' => 'MATURITY_ALPHA', + 'branchquote' => "'", + 'releasequote' => "'", + ]; + + return [ + 'Weekly version from development' => [ + $developmentVersion, + [ + 'branch' => 'MOODLE_500_STABLE', + 'type' => 'weekly', + 'rc' => '', + 'date' => '20240926', + 'isdevbranch' => true, + ], + [ + 'integerversion' => date('Ymd') * 100, + 'decimalversion' => '00', + 'release' => '5.0dev', + 'build' => '20240926', + 'branchquote' => "'", + 'releasequote' => "'", + 'maturity' => 'MATURITY_ALPHA', + ], + ], + 'Minor version from development' => [ + // Note: Development versions do not get minor. We treat it as a synonym for weekly. + $developmentVersion, + [ + 'branch' => 'MOODLE_500_STABLE', + 'type' => 'minor', + 'rc' => '', + 'date' => '20240923', + 'isdevbranch' => true, + ], + [ + 'integerversion' => date('Ymd') * 100, + 'decimalversion' => '00', + 'release' => '5.0dev', + 'build' => '20240923', + 'branchquote' => "'", + 'releasequote' => "'", + 'maturity' => 'MATURITY_ALPHA', + ], + ], + 'Beta version from development' => [ + $developmentVersion, + [ + 'branch' => 'MOODLE_500_STABLE', + 'type' => 'beta', + 'rc' => '', + 'date' => '20240923', + 'isdevbranch' => true, + ], + [ + 'integerversion' => date('Ymd') * 100, + 'decimalversion' => '00', + 'release' => '5.0beta', + 'build' => '20240923', + 'branchquote' => "'", + 'releasequote' => "'", + 'maturity' => 'MATURITY_BETA', + ], + ], + 'RC version from development' => [ + $developmentVersion, + [ + 'branch' => 'MOODLE_500_STABLE', + 'type' => 'rc', + 'rc' => '', + 'date' => '20240923', + 'isdevbranch' => true, + ], + [ + 'integerversion' => date('Ymd') * 100, + 'decimalversion' => '00', + 'release' => '5.0rc', + 'build' => '20240923', + 'branchquote' => "'", + 'releasequote' => "'", + 'maturity' => 'MATURITY_RC', + ], + ], + // 'On-Demand from development' => [ + // // Note: This is just a standard weekly release of the development branch. + // $developmentVersion, + // [ + // 'branch' => 'MOODLE_500_STABLE', + // 'type' => 'on-demand', + // 'rc' => '', + // 'date' => '20240923', + // 'isdevbranch' => true, + // ], + // [ + // 'integerversion' => date('Ymd') * 100, + // 'decimalversion' => '00', + // 'release' => '5.0dev', + // 'build' => '20240923', + // 'branchquote' => "'", + // 'releasequote' => "'", + // 'maturity' => 'MATURITY_ALPHA', // No change. + // ], + // ], + 'Major version from development' => [ + $developmentVersion, + [ + 'branch' => 'MOODLE_500_STABLE', + 'type' => 'major', + 'rc' => '', + 'date' => '20240923', + 'isdevbranch' => true, + ], + [ + 'integerversion' => 2024092300, + 'decimalversion' => '00', + 'release' => '5.0', + 'build' => '20240923', + 'branchquote' => "'", + 'releasequote' => "'", + 'maturity' => 'MATURITY_STABLE', + ], + ], + 'Back to dev from development' => [ + $developmentVersion, + [ + 'branch' => 'MOODLE_500_STABLE', + 'type' => 'back-to-dev', + 'rc' => '', + 'date' => '20240923', + 'isdevbranch' => true, + ], + [ + 'integerversion' => 2024092301, + 'decimalversion' => '00', + 'release' => '5.0dev', + 'build' => '20240923', + 'branchquote' => "'", + 'releasequote' => "'", + 'maturity' => 'MATURITY_ALPHA', + ], + ], + ]; + } + + public static function nextVersionFromBetaProvider(): array + { + $version = [ + 'integerversion' => 2024092301, + 'decimalversion' => '00', + 'comment' => '// 20240923 = branching date YYYYMMDD - do not modify!', + 'release' => '5.0beta', + 'build' => '20240921', + 'branch' => '500', + 'maturity' => 'MATURITY_BETA', + 'branchquote' => "'", + 'releasequote' => "'", + ]; + + return [ + 'Beta version from beta' => [ + $version, + [ + 'branch' => 'MOODLE_500_STABLE', + 'type' => 'beta', + 'rc' => '', + 'date' => '20240923', + 'isdevbranch' => true, + ], + [ + 'integerversion' => date('Ymd') * 100, + 'decimalversion' => '00', + 'release' => '5.0beta', + 'build' => '20240923', + 'branchquote' => "'", + 'releasequote' => "'", + 'maturity' => 'MATURITY_BETA', + ], + ], + 'RC version from beta' => [ + $version, + [ + 'branch' => 'MOODLE_500_STABLE', + 'type' => 'rc', + 'rc' => '', + 'date' => '20240923', + 'isdevbranch' => true, + ], + [ + 'integerversion' => date('Ymd') * 100, + 'decimalversion' => '00', + 'release' => '5.0rc', + 'build' => '20240923', + 'branchquote' => "'", + 'releasequote' => "'", + 'maturity' => 'MATURITY_RC', + ], + ], + 'On-Demand from beta' => [ + // Note: This is just a standard weekly release of the development branch. + $version, + [ + 'branch' => 'MOODLE_500_STABLE', + 'type' => 'on-demand', + 'rc' => '', + 'date' => '20240923', + 'isdevbranch' => true, + ], + [ + 'integerversion' => date('Ymd') * 100, + 'decimalversion' => '00', + 'release' => '5.0beta+', + 'build' => '20240923', + 'branchquote' => "'", + 'releasequote' => "'", + 'maturity' => 'MATURITY_BETA', + ], + ], + 'Major version from beta' => [ + $version, + [ + 'branch' => 'MOODLE_500_STABLE', + 'type' => 'major', + 'rc' => '', + 'date' => '20240923', + 'isdevbranch' => true, + ], + [ + 'integerversion' => 2024092300, + 'decimalversion' => '00', + 'release' => '5.0', + 'build' => '20240923', + 'branchquote' => "'", + 'releasequote' => "'", + 'maturity' => 'MATURITY_STABLE', + ], + ], + ]; + } + + #[DataProvider('versionFileProvider')] + public function testFromVersionFile( + string $versionFileName, + string $branch, + array $expectations, + ): void + { + $versionFileContent = file_get_contents($versionFileName); + $version = VersionInfo::fromVersionFile($versionFileContent, $branch); + + foreach ($expectations as $property => $expectedValue) { + self::assertSame($expectedValue, $version->{$property}); + } + } + + public static function versionFileProvider(): array + { + return [ + '4.3.0' => [ + dirname(__DIR__) . '/fixtures/versions/4.3.0.php', + 'MOODLE_403_STABLE', + [ + 'integerversion' => 2023100900, + 'decimalversion' => '00', + 'release' => '4.3', + 'build' => '20231009', + 'branch' => '403', + 'maturity' => 'MATURITY_STABLE', + ], + ], + '4.3.2' => [ + dirname(__DIR__) . '/fixtures/versions/4.3.2.php', + 'MOODLE_403_STABLE', + [ + 'integerversion' => 2023100902, + 'decimalversion' => '00', + 'release' => '4.3.2', + 'build' => '20231222', + 'branch' => '403', + 'maturity' => 'MATURITY_STABLE', + ], + ], + '4.4.0-beta' => [ + dirname(__DIR__) . '/fixtures/versions/4.4.0-beta.php', + 'MOODLE_404_STABLE', + [ + 'integerversion' => 2024041200, + 'decimalversion' => '01', + 'release' => '4.4beta', + 'build' => '20240412', + 'branch' => '404', + 'maturity' => 'MATURITY_BETA', + ], + ], + '4.4.0-rc1' => [ + dirname(__DIR__) . '/fixtures/versions/4.4.0-rc1.php', + 'MOODLE_404_STABLE', + [ + 'integerversion' => 2024041600, + 'decimalversion' => '00', + 'release' => '4.4rc1', + 'build' => '20240416', + 'branch' => '404', + 'maturity' => 'MATURITY_RC', + ], + ], + '4.4.0' => [ + dirname(__DIR__) . '/fixtures/versions/4.4.0.php', + 'MOODLE_404_STABLE', + [ + 'integerversion' => 2024042200, + 'decimalversion' => '00', + 'release' => '4.4', + 'build' => '20240422', + 'branch' => '404', + 'maturity' => 'MATURITY_STABLE', + ], + ], + ]; + } + + #[DataProvider('invalidVersionFileProvider')] + public function testFromVersionFileInvalid( + string $versionFileName, + string $branch, + string $expectedExceptionMessage, + ): void { + $this->expectException(\Exception::class); + $this->expectExceptionMessage($expectedExceptionMessage); + + $versionFileContent = file_get_contents($versionFileName); + VersionInfo::fromVersionFile($versionFileContent, $branch); + } + + public static function invalidVersionFileProvider(): array + { + return [ + 'Invalid version file' => [ + dirname(__DIR__) . '/fixtures/versions/invalidVersion.php', + 'MOODLE_404_STABLE', + 'Could not determine version.', + ], + ]; + } + +} From 0af5c3965625c0758c6fa82f20f039d21bec87c5 Mon Sep 17 00:00:00 2001 From: Andrew Nicols Date: Tue, 1 Oct 2024 08:52:31 +0800 Subject: [PATCH 04/12] Drop support for Moodle 1.9 --- src/Helper.php | 26 ++++++++++++-------------- src/VersionInfo.php | 20 ++++++++------------ tests/unit/HelperTest.php | 25 +------------------------ 3 files changed, 21 insertions(+), 50 deletions(-) diff --git a/src/Helper.php b/src/Helper.php index 98750e6..afd408a 100644 --- a/src/Helper.php +++ b/src/Helper.php @@ -55,7 +55,6 @@ public static function getNextVersion( $nextVersionInfo = $currentVersionInfo->getNextVersion($branch, $type, $rc, $date, $isdevbranch); return [ - 'is19' => ($branch === 'MOODLE_19_STABLE'), 'versionfile' => $versionfile, 'current' => $currentVersionInfo, 'new' => $nextVersionInfo, @@ -83,7 +82,6 @@ public static function bumpVersion( bool $isdevbranch, ): string { [ - 'is19' => $is19, 'versionfile' => $versionfile, 'current' => $currentVersionInfo, 'new' => $newVersionInfo, @@ -101,15 +99,13 @@ public static function bumpVersion( $versionfile = str_replace($currentVersionInfo->comment, $newVersionInfo->comment, $versionfile); } - if (!$is19) { - // Replace the branch value if need be. - if ($currentVersionInfo->branch !== $newVersionInfo->branch) { - $versionfile = str_replace($currentVersionInfo->branchquote.$currentVersionInfo->branch.$newVersionInfo->branchquote, $newVersionInfo->branchquote.$newVersionInfo->branch.$newVersionInfo->branchquote, $versionfile); - } - // Replace the maturity value if need be. - if ($currentVersionInfo->maturity !== $newVersionInfo->maturity) { - $versionfile = str_replace('= '.$currentVersionInfo->maturity, '= '.$newVersionInfo->maturity, $versionfile); - } + // Replace the branch value if need be. + if ($currentVersionInfo->branch !== $newVersionInfo->branch) { + $versionfile = str_replace($currentVersionInfo->branchquote.$currentVersionInfo->branch.$newVersionInfo->branchquote, $newVersionInfo->branchquote.$newVersionInfo->branch.$newVersionInfo->branchquote, $versionfile); + } + // Replace the maturity value if need be. + if ($currentVersionInfo->maturity !== $newVersionInfo->maturity) { + $versionfile = str_replace('= '.$currentVersionInfo->maturity, '= '.$newVersionInfo->maturity, $versionfile); } file_put_contents($path, $versionfile); @@ -172,6 +168,11 @@ public static function isBranchStable( public static function isBranchNameValid( string $branch, ): bool { + if (str_contains($branch, 'MOODLE_19_STABLE')) { + // Moodle 1.9 is no longer supported by this tooling. + return false; + } + return (preg_match('#^(main|MOODLE_(\d+)_STABLE)$#', $branch, $matches)); } @@ -270,9 +271,6 @@ public static function isVersionFileValid( if ($hasversion && $hasrelease && $hasbranch && $hasmaturity) { return true; } - if ($branch === 'MOODLE_19_STABLE' && $hasversion && $hasrelease) { - return true; - } return false; } diff --git a/src/VersionInfo.php b/src/VersionInfo.php index 9b98ee5..bdd5c44 100644 --- a/src/VersionInfo.php +++ b/src/VersionInfo.php @@ -42,8 +42,6 @@ public function __construct( public static function fromVersionFile(string $versionfile, string $branch): self { Helper::requireVersionFileValid($versionfile, $branch); - $is19 = ($branch === 'MOODLE_19_STABLE'); - if (!preg_match('#^ *\$version *= *(?P\d{10})\.(?P\d{2})\d?[^\/]*(?P/[^\n]*)#m', $versionfile, $matches)) { throw new Exception('Could not determine version.', __LINE__); } @@ -58,17 +56,15 @@ public static function fromVersionFile(string $versionfile, string $branch): sel $releasequote = $matches['quote']; $buildcurrent = $matches['build']; - if (!$is19) { - if (!preg_match('# *\$branch *= *(?P\'|")(?P\d+)\1#m', $versionfile, $matches)) { - throw new Exception('Could not determine branch.', __LINE__); - } - $branchquote = $matches['quote']; - $branch = $matches['branch']; - if (!preg_match('# *\$maturity *= *(?PMATURITY_[A-Z]+)#m', $versionfile, $matches)) { - throw new Exception('Could not determine maturity.', __LINE__); - } - $maturity = $matches['maturity']; + if (!preg_match('# *\$branch *= *(?P\'|")(?P\d+)\1#m', $versionfile, $matches)) { + throw new Exception('Could not determine branch.', __LINE__); + } + $branchquote = $matches['quote']; + $branch = $matches['branch']; + if (!preg_match('# *\$maturity *= *(?PMATURITY_[A-Z]+)#m', $versionfile, $matches)) { + throw new Exception('Could not determine maturity.', __LINE__); } + $maturity = $matches['maturity']; return new self( integerversion: $integerversion, diff --git a/tests/unit/HelperTest.php b/tests/unit/HelperTest.php index 47f4b6a..e8fc204 100644 --- a/tests/unit/HelperTest.php +++ b/tests/unit/HelperTest.php @@ -131,7 +131,6 @@ public static function stableVersionProvider(): array return [ 'main' => ['main', true, false], 'master' => ['master', true, false], - 'MOODLE_19_STABLE' => ['MOODLE_19_STABLE', false, true], 'MOODLE_401_STABLE' => ['MOODLE_401_STABLE', false, true], 'MOODLE_500_STABLE' => ['MOODLE_500_STABLE', false, true], 'MOODLE_500_STABLE in parallel develoipment' => ['MOODLE_500_STABLE', true, false], @@ -164,7 +163,7 @@ public static function isBranchNameValidProvider(): array return [ 'main' => ['main', true], 'master' => ['master', false], - 'MOODLE_19_STABLE' => ['MOODLE_19_STABLE', true], + 'MOODLE_19_STABLE' => ['MOODLE_19_STABLE', false], 'MOODLE_401_STABLE' => ['MOODLE_401_STABLE', true], 'MOODLE_500_STABLE' => ['MOODLE_500_STABLE', true], 'MOODLE_500_STABLE in parallel develoipment' => ['MOODLE_500_STABLE', true], @@ -338,28 +337,6 @@ public static function versionFileProvider(): array { 'branch' => 'MOODLE_405_STABLE', 'expected' => false, ], - 'Valid 19' => [ - 'content' => << 'MOODLE_19_STABLE', - 'expected' => true, - ], - 'Missing version 19' => [ - 'content' => << 'MOODLE_19_STABLE', - 'expected' => false, - ], - 'Missing release 19' => [ - 'content' => << 'MOODLE_19_STABLE', - 'expected' => false, - ], ]; } } From 257550e76d5a2be6c7dd46fb23fa9db10a90423e Mon Sep 17 00:00:00 2001 From: Andrew Nicols Date: Tue, 1 Oct 2024 11:04:57 +0800 Subject: [PATCH 05/12] Refactor version file generation --- src/Helper.php | 114 +++++++---------- src/VersionInfo.php | 128 ++++++++++++++++--- src/templates/version.php.tpl | 37 ++++++ tests/unit/HelperTest.php | 98 +++++++++++++-- tests/unit/VersionInfoTest.php | 217 +++++++++++++++++++++++++-------- 5 files changed, 446 insertions(+), 148 deletions(-) create mode 100644 src/templates/version.php.tpl diff --git a/src/Helper.php b/src/Helper.php index afd408a..c8117f9 100644 --- a/src/Helper.php +++ b/src/Helper.php @@ -27,90 +27,69 @@ class Helper { /** - * Get information about the next version + * Bump the version. * - * @param string $path - * @param string $branch - * @param string $type - * @param string $rc - * @param string $date - * @param bool $isdevbranch + * @param string $path The path to the versio file + * @param string $branch The branch name to set + * @param string $type The type of release + * @param string $rc If a release candidate, the RC number + * @param string $date The date to use for the version + * @param bool $isdevbranch Whether this is a developmentbranch * @throws Exception - * @return mixed + * @return string */ - public static function getNextVersion( + public static function bumpVersion( string $path, string $branch, string $type, string $rc, string $date, bool $isdevbranch, - ) { + ): string { + // Require that the new branch name is valid. self::requireBranchNameValid($branch); self::requireTypeValid($type); self::requirePathValid($path); - $versionfile = file_get_contents($path); - $currentVersionInfo = VersionInfo::fromVersionFile($versionfile, $branch); - $nextVersionInfo = $currentVersionInfo->getNextVersion($branch, $type, $rc, $date, $isdevbranch); + $currentVersionInfo = VersionInfo::fromVersionFile($path); + $newVersionInfo = $currentVersionInfo->getNextVersion($branch, $type, $rc, $isdevbranch, $date); - return [ - 'versionfile' => $versionfile, - 'current' => $currentVersionInfo, - 'new' => $nextVersionInfo, - ]; + file_put_contents($path, $newVersionInfo->generateVersionFile()); + + return $newVersionInfo->release; } /** - * Bump the version. + * Determine the next branch number based on the current one. * - * @param string $path - * @param string $branch - * @param string $type - * @param string $rc - * @param string $date - * @param bool $isdevbranch - * @throws Exception - * @return string + * Note: This function is valid for Moodle 4.0 and later and follows + * the Moodle versioning scheme. + * + * If there is an exceptional release and the life of a branch is extended + * this function will not work as expected. If this happens this function + * will need to be extended as appropriate in the circumstances. + * + * @param int $branch The branch number in integer form + * @return int The new branch number */ - public static function bumpVersion( - string $path, - string $branch, - string $type, - string $rc, - string $date, - bool $isdevbranch, - ): string { - [ - 'versionfile' => $versionfile, - 'current' => $currentVersionInfo, - 'new' => $newVersionInfo, - ] = self::getNextVersion($path, $branch, $type, $rc, $date, $isdevbranch); - - $versionfile = str_replace($currentVersionInfo->integerversion . '.' . $currentVersionInfo->decimalversion, $newVersionInfo->integerversion.'.'.$newVersionInfo->decimalversion, $versionfile); - // Replace the old build with the new build. - $versionfile = str_replace('Build: '.$currentVersionInfo->build, 'Build: '.$newVersionInfo->build, $versionfile); - // Replace the old release with the new release if they've changed. - if ($currentVersionInfo->release !== $newVersionInfo->release) { - $versionfile = str_replace($currentVersionInfo->releasequote.$currentVersionInfo->release, $newVersionInfo->releasequote.$newVersionInfo->release, $versionfile); - } - // Replace the old comment with the new one if they've changed - if ($currentVersionInfo->comment !== $newVersionInfo->comment) { - $versionfile = str_replace($currentVersionInfo->comment, $newVersionInfo->comment, $versionfile); + public static function getNextBranchNumber( + int $branch, + ): int { + if ($branch <= 404) { + return $branch + 1; } - // Replace the branch value if need be. - if ($currentVersionInfo->branch !== $newVersionInfo->branch) { - $versionfile = str_replace($currentVersionInfo->branchquote.$currentVersionInfo->branch.$newVersionInfo->branchquote, $newVersionInfo->branchquote.$newVersionInfo->branch.$newVersionInfo->branchquote, $versionfile); - } - // Replace the maturity value if need be. - if ($currentVersionInfo->maturity !== $newVersionInfo->maturity) { - $versionfile = str_replace('= '.$currentVersionInfo->maturity, '= '.$newVersionInfo->maturity, $versionfile); - } + $releasemajor = (int) substr($branch, 0, 1); + $releaseminor = (int) substr($branch, 2, 1); - file_put_contents($path, $versionfile); + if ($releaseminor >= 3) { + $releasemajor++; + $releaseminor = 0; + } else { + $releaseminor++; + } - return $newVersionInfo->release; + return (int) sprintf('%d%02d', $releasemajor, $releaseminor); } /** @@ -128,7 +107,7 @@ public static function getValidatedVersionNumber( if ($versionint >= $today * 100) { // Integer version is already past today * 100, increment version decimal part instead of integer part. $versiondec = (int) $versiondec + 1; - $versiondec = sprintf("%'02d", $versiondec) ; + $versiondec = sprintf("%'02d", $versiondec); } else { $versionint = $today . '00'; $versiondec = '00'; @@ -173,7 +152,11 @@ public static function isBranchNameValid( return false; } - return (preg_match('#^(main|MOODLE_(\d+)_STABLE)$#', $branch, $matches)); + if ($branch === 'main') { + return true; + } + + return (preg_match('#^(MOODLE_(\d+)_STABLE)$#', $branch, $matches)); } /** @@ -256,12 +239,10 @@ public static function requirePathValid( * Validate the version file. * * @param string $contents The contents of the version file - * @param string $branch The branch name * @return bool */ public static function isVersionFileValid( string $contents, - string $branch, ): bool { $hasversion = strpos($contents, '$version ') !== false; $hasrelease = strpos($contents, '$release ') !== false; @@ -284,9 +265,8 @@ public static function isVersionFileValid( */ public static function requireVersionFileValid( string $contents, - string $branch, ): void { - if (!self::isVersionFileValid($contents, $branch)) { + if (!self::isVersionFileValid($contents)) { throw new Exception('Invalid version file found.', __LINE__); } } diff --git a/src/VersionInfo.php b/src/VersionInfo.php index bdd5c44..4535ab4 100644 --- a/src/VersionInfo.php +++ b/src/VersionInfo.php @@ -26,21 +26,48 @@ */ class VersionInfo { + /** @var int The name of the branch either in integer form, or MOODLE_(\d+)_STABLE form */ + public readonly int $branch; + + /** + * Construct a new instance of the VersionInfo class. + * + * @param int $integerversion The integer version number. + * @param string $decimalversion Part of the version number after the dot. + * @param string $comment The comment to use for the version. + * @param string $release The release name + * @param string $build The build number + * @param int|string $branch The branch number or name + * @param string $maturity The maturity of the branch + * @param string $branchquote The quote used for the branch + * @param string $releasequote The quote used for the release + */ public function __construct( public readonly int $integerversion, public readonly string $decimalversion, public readonly string $comment, public readonly string $release, public readonly string $build, - public readonly string $branch, + int|string $branch, public readonly string $maturity, public readonly string $branchquote, public readonly string $releasequote, ) { + if (is_string($branch)) { + $branch = preg_replace('#^MOODLE_(\d+)_STABLE$#', '$1', $branch); + } + $this->branch = $branch; } - public static function fromVersionFile(string $versionfile, string $branch): self { - Helper::requireVersionFileValid($versionfile, $branch); + /** + * Create a new VersionInfo from the content of a version.php file. + * + * @param string $versionfile The content of the file + * @throws \Exception + * @return self + */ + public static function fromVersionContent(string $versionfile): self { + Helper::requireVersionFileValid($versionfile); if (!preg_match('#^ *\$version *= *(?P\d{10})\.(?P\d{2})\d?[^\/]*(?P/[^\n]*)#m', $versionfile, $matches)) { throw new Exception('Could not determine version.', __LINE__); @@ -79,6 +106,31 @@ public static function fromVersionFile(string $versionfile, string $branch): sel ); } + /** + * Create a new VersionInfo from the path to a version.php file + * + * @param string $path The path to the file + * @throws \Exception + * @return self + */ + public static function fromVersionFile(string $path): self { + Helper::requirePathValid($path); + $versionfile = file_get_contents($path); + + return self::fromVersionContent($versionfile); + } + + /** + * Get the VersionInfo instance for the Moodle version that follows this one. + * + * @param string $branch The branch name + * @param string $type The release type + * @param string $rc The release candidate number + * @param bool $isdevbranch Whether this is a development branch + * @param mixed $date The date to use for the version + * @throws \Exception + * @return VersionInfo + */ public function getNextVersion( string $branch, string $type, @@ -179,22 +231,29 @@ public function getNextVersion( } else if ($type === 'on-sync') { $decimalversion++; } else if ($type === 'back-to-dev') { - if (strpos($release, 'dev') === false) { // Ensure it's not a "dev" version already. - // Must be immediately after a major release. Bump comment, release and maturity. - $comment = '// YYYYMMDD = weekly release date of this DEV branch.'; - // Normalise a little bit the release, getting rid of everything after the numerical part. - $release = preg_replace('/^([0-9.]+).*$/', '\1', $release); - // Split the major and minor parts of the release for further process. - list($releasemajor, $releaseminor) = explode('.', $release); - $release = $releasemajor . '.' . (++$releaseminor); // Increment to next dev version. - $release = $release . 'dev'; - // The branch is the major followed by 2-chars minor. - $branch = $releasemajor . str_pad($releaseminor, 2, '0', STR_PAD_LEFT); - $maturity = 'MATURITY_ALPHA'; - if (empty($date)) { // If no date has been forced, back-to-dev have same build date than majors. - if ((int)date('N') !== 1) { // If today is not Monday, calculate next one. - $build = date('Ymd', strtotime('next Monday')); - } + // We perform back-to-dev on the `main` branch only. + if (strpos($release, 'dev') !== false) { // Ensure it's not a "dev" version already. + throw new Exception('Back-to-dev is only allowed on non-dev branches.', __LINE__); + } + // Must be immediately after a major release. Bump comment, release and maturity. + $comment = '// YYYYMMDD = weekly release date of this DEV branch.'; + + if ($branch !== 'main') { + throw new Exception('Back-to-dev is only allowed on the main branch.', __LINE__); + } + + $branch = Helper::getNextBranchNumber($this->branch); + + // This require knowledge of our branching scheme. + $releasemajor = (int) substr($branch, 0, 1); + $releaseminor = (int) substr($branch, 2, 1); + + $release = "{$releasemajor}.{$releaseminor}dev"; + + $maturity = 'MATURITY_ALPHA'; + if (empty($date)) { // If no date has been forced, back-to-dev have same build date than majors. + if ((int)date('N') !== 1) { // If today is not Monday, calculate next one. + $build = date('Ymd', strtotime('next Monday')); } } } else if ($type === 'major') { @@ -230,6 +289,10 @@ public function getNextVersion( $decimalversion = '0'.$decimalversion; } + if ($branch === 'main') { + $branch = $this->branch; + } + return new self( integerversion: $integerversion, decimalversion: $decimalversion, @@ -242,4 +305,31 @@ public function getNextVersion( releasequote: $this->releasequote, ); } + + /** + * Generate the content of the version.php file based on the standard template. + * + * @return string + */ + public function generateVersionFile(): string { + $versionFile = file_get_contents(__DIR__ . '/templates/version.php.tpl'); + + $replacements = [ + 'INTEGERVERSION' => $this->integerversion, + 'DECIMALVERSION' => $this->decimalversion, + 'COMMENT' => $this->comment, + 'RELEASE' => $this->release, + 'BUILD' => $this->build, + 'BRANCH' => $this->branch, + 'MATURITY' => sprintf("%-20s", "{$this->maturity};"), + ]; + + $versionFile = str_replace( + search: array_keys($replacements), + replace: array_values($replacements), + subject: $versionFile, + ); + + return $versionFile; + } } diff --git a/src/templates/version.php.tpl b/src/templates/version.php.tpl new file mode 100644 index 0000000..207d4d6 --- /dev/null +++ b/src/templates/version.php.tpl @@ -0,0 +1,37 @@ +. + +/** + * MOODLE VERSION INFORMATION. + * + * This file defines the current version of the core Moodle code being used. + * This is compared against the values stored in the database to determine + * whether upgrades should be performed (see lib/db/*.php) + * + * @package core + * @copyright 1999 onwards Martin Dougiamas (http://dougiamas.com) + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +$version = INTEGERVERSION.DECIMALVERSION; COMMENT + // RR = release increments - 00 in DEV branches. + // .XX = incremental changes. +$release = 'RELEASE (Build: BUILD)'; // Human-friendly version name +$branch = 'BRANCH'; // This version's branch. +$maturity = MATURITY // This version's maturity level. diff --git a/tests/unit/HelperTest.php b/tests/unit/HelperTest.php index e8fc204..748b72c 100644 --- a/tests/unit/HelperTest.php +++ b/tests/unit/HelperTest.php @@ -161,9 +161,9 @@ public function testRequireBranchNameValid( public static function isBranchNameValidProvider(): array { return [ - 'main' => ['main', true], 'master' => ['master', false], 'MOODLE_19_STABLE' => ['MOODLE_19_STABLE', false], + 'main' => ['main', true], 'MOODLE_401_STABLE' => ['MOODLE_401_STABLE', true], 'MOODLE_500_STABLE' => ['MOODLE_500_STABLE', true], 'MOODLE_500_STABLE in parallel develoipment' => ['MOODLE_500_STABLE', true], @@ -262,26 +262,24 @@ public static function pathValidProvider(): array #[DataProvider('versionFileProvider')] public function testIsVersionFileValid( string $content, - string $branch, bool $expected, ): void { $this->assertEquals( $expected, - Helper::isVersionFileValid($content, $branch), + Helper::isVersionFileValid($content), ); } #[DataProvider('versionFileProvider')] public function testRequireVersionFileValid( string $content, - string $branch, bool $expected, ): void { if ($expected) { - $this->assertNull(Helper::requireVersionFileValid($content, $branch)); + $this->assertNull(Helper::requireVersionFileValid($content)); } else { $this->expectException(\Exception::class); - Helper::requireVersionFileValid($content, $branch); + Helper::requireVersionFileValid($content); } } @@ -298,7 +296,6 @@ public static function versionFileProvider(): array { \$branch = '405'; // This version's branch. \$maturity = MATURITY_BETA; // This version's maturity level. EOF, - 'branch' => 'MOODLE_405_STABLE', 'expected' => true, ], 'Missing version' => [ @@ -307,7 +304,6 @@ public static function versionFileProvider(): array { \$branch = '405'; // This version's branch. \$maturity = MATURITY_BETA; // This version's maturity level. EOF, - 'branch' => 'MOODLE_405_STABLE', 'expected' => false, ], 'Missing release' => [ @@ -316,7 +312,6 @@ public static function versionFileProvider(): array { \$branch = '405'; // This version's branch. \$maturity = MATURITY_BETA; // This version's maturity level. EOF, - 'branch' => 'MOODLE_405_STABLE', 'expected' => false, ], 'Missing branch' => [ @@ -325,7 +320,6 @@ public static function versionFileProvider(): array { \$release = '4.5beta (Build: 20240928)'; // Human-friendly version name \$maturity = MATURITY_BETA; // This version's maturity level. EOF, - 'branch' => 'MOODLE_405_STABLE', 'expected' => false, ], 'Missing maturity' => [ @@ -334,9 +328,91 @@ public static function versionFileProvider(): array { \$release = '4.5beta (Build: 20240928)'; // Human-friendly version name \$branch = '405'; // This version's branch. EOF, - 'branch' => 'MOODLE_405_STABLE', 'expected' => false, ], ]; } + + #[DataProvider('getNextBranchNumberProvider')] + public function testGetNextBranchNumber( + int $current, + int $expected, + ): void { + $this->assertSame( + $expected, + Helper::getNextBranchNumber($current), + ); + } + + public static function getNextBranchNumberProvider(): array + { + return [ + '404' => [404, 405], + '405' => [405, 500], + '500' => [500, 501], + '501' => [501, 502], + '502' => [502, 503], + '503' => [503, 600], + ]; + } + + #[DataProvider('validOptionProvider')] + public function testGetOption( + array $options, + string $long, + string $short, + mixed $expected, + ): void { + $this->assertSame( + $expected, + Helper::getOption($options, $short, $long), + ); + } + public static function validOptionProvider(): array + { + return [ + [ + 'options' => ['long' => 'value', 'other' => 'other'], + 'long' => 'long', + 'short' => 'l', + 'expected' => 'value', + ], + [ + 'options' => ['long' => 'value', 'other' => '9999'], + 'long' => 'other', + 'short' => 'o', + 'expected' => '9999', + ], + ]; + } + + #[DataProvider('invalidOptionProvider')] + public function testInvalidGetOption( + array $options, + string $long, + string $short, + string $expected, + ): void { + $this->expectException(\Exception::class); + $this->expectExceptionMessage($expected); + Helper::getOption($options, $short, $long); + } + + public static function invalidOptionProvider(): array + { + return [ + [ + 'options' => ['long' => 'value', 'l' => 'other'], + 'long' => 'long', + 'short' => 'l', + 'expected' => 'Option -l|--long specified more than once.', + ], + [ + 'options' => ['long' => 'value', 'other' => '9999'], + 'long' => 'asdf', + 'short' => 'a', + 'expected' => 'Required option -a|--asdf must be provided.', + ], + ]; + } } diff --git a/tests/unit/VersionInfoTest.php b/tests/unit/VersionInfoTest.php index b7517e1..8e33032 100644 --- a/tests/unit/VersionInfoTest.php +++ b/tests/unit/VersionInfoTest.php @@ -52,7 +52,7 @@ public static function nextVersionFromMajorProvider(): array 'comment' => '// 20240923 = branching date YYYYMMDD - do not modify!', 'release' => '4.5', 'build' => '20240921', - 'branch' => '405', + 'branch' => 'MOODLE_405_STABLE', 'maturity' => 'MATURITY_STABLE', 'branchquote' => "'", 'releasequote' => "'", @@ -114,6 +114,25 @@ public static function nextVersionFromMajorProvider(): array 'releasequote' => "'", ], ], + 'Back to development version from major' => [ + $majorVersion, + [ + 'branch' => 'main', + 'type' => 'back-to-dev', + 'rc' => '', + 'date' => '20240923', + 'isdevbranch' => true, + ], + [ + // TODO: Calculate the correct version. This is based on the day of the week. + // 'integerversion' => date('Ymd') * 100, + 'decimalversion' => '00', + 'release' => '5.0dev', + 'build' => '20240923', + 'branchquote' => "'", + 'releasequote' => "'", + ], + ], 'Development version from major' => [ $majorVersion, [ @@ -143,7 +162,7 @@ public static function nextVersionFromMinorProvider(): array 'comment' => '// 20240923 = branching date YYYYMMDD - do not modify!', 'release' => '4.5.1', 'build' => '20240921', - 'branch' => '405', + 'branch' => 405, 'maturity' => 'MATURITY_STABLE', 'branchquote' => "'", 'releasequote' => "'", @@ -214,7 +233,7 @@ public static function nextVersionFromWeeklyProvider(): array 'comment' => '// 20240923 = branching date YYYYMMDD - do not modify!', 'release' => '4.5.1+', 'build' => '20240921', - 'branch' => '405', + 'branch' => 'MOODLE_405_STABLE', 'maturity' => 'MATURITY_STABLE', 'branchquote' => "'", 'releasequote' => "'", @@ -279,7 +298,7 @@ public static function nextVersionFromWeeklyProvider(): array } public static function nextVersionFromDevelopmentProvider(): array { - $developmentVersion = [ + $version = [ 'integerversion' => 2024092301, 'decimalversion' => '00', 'comment' => '// 20240923 = branching date YYYYMMDD - do not modify!', @@ -292,8 +311,26 @@ public static function nextVersionFromDevelopmentProvider(): array ]; return [ + 'On-sync version from major' => [ + $version, + [ + 'branch' => 'MOODLE_405_STABLE', + 'type' => 'on-sync', + 'rc' => '', + 'date' => '20240926', + 'isdevbranch' => true, + ], + [ + 'integerversion' => 2024092301, + 'decimalversion' => '01', + 'release' => '5.0dev', + 'build' => '20240926', + 'branchquote' => "'", + 'releasequote' => "'", + ], + ], 'Weekly version from development' => [ - $developmentVersion, + $version, [ 'branch' => 'MOODLE_500_STABLE', 'type' => 'weekly', @@ -313,7 +350,7 @@ public static function nextVersionFromDevelopmentProvider(): array ], 'Minor version from development' => [ // Note: Development versions do not get minor. We treat it as a synonym for weekly. - $developmentVersion, + $version, [ 'branch' => 'MOODLE_500_STABLE', 'type' => 'minor', @@ -332,7 +369,7 @@ public static function nextVersionFromDevelopmentProvider(): array ], ], 'Beta version from development' => [ - $developmentVersion, + $version, [ 'branch' => 'MOODLE_500_STABLE', 'type' => 'beta', @@ -351,7 +388,7 @@ public static function nextVersionFromDevelopmentProvider(): array ], ], 'RC version from development' => [ - $developmentVersion, + $version, [ 'branch' => 'MOODLE_500_STABLE', 'type' => 'rc', @@ -390,7 +427,7 @@ public static function nextVersionFromDevelopmentProvider(): array // ], // ], 'Major version from development' => [ - $developmentVersion, + $version, [ 'branch' => 'MOODLE_500_STABLE', 'type' => 'major', @@ -408,25 +445,7 @@ public static function nextVersionFromDevelopmentProvider(): array 'maturity' => 'MATURITY_STABLE', ], ], - 'Back to dev from development' => [ - $developmentVersion, - [ - 'branch' => 'MOODLE_500_STABLE', - 'type' => 'back-to-dev', - 'rc' => '', - 'date' => '20240923', - 'isdevbranch' => true, - ], - [ - 'integerversion' => 2024092301, - 'decimalversion' => '00', - 'release' => '5.0dev', - 'build' => '20240923', - 'branchquote' => "'", - 'releasequote' => "'", - 'maturity' => 'MATURITY_ALPHA', - ], - ], + ]; } @@ -525,15 +544,88 @@ public static function nextVersionFromBetaProvider(): array ]; } + #[DataProvider('invalidNextVersionMigrationsProvider')] + public function testGetNextVersionInvalidTransition( + array $currentVersionArgs, + array $nextVersionArgs, + ): void { + $version = new VersionInfo(...$currentVersionArgs); + + $this->expectException(\Exception::class); + $version->getNextVersion(...$nextVersionArgs); + } + + + public static function invalidNextVersionMigrationsProvider(): array + { + $developmentVersion = [ + 'integerversion' => 2024092301, + 'decimalversion' => '00', + 'comment' => '// 20240923 = branching date YYYYMMDD - do not modify!', + 'release' => '5.0dev', + 'build' => '20240921', + 'branch' => '500', + 'maturity' => 'MATURITY_ALPHA', + 'branchquote' => "'", + 'releasequote' => "'", + ]; + $majorVersion = [ + 'integerversion' => 2024092300, + 'decimalversion' => '00', + 'comment' => '// 20240923 = branching date YYYYMMDD - do not modify!', + 'release' => '4.5', + 'build' => '20240921', + 'branch' => 'MOODLE_405_STABLE', + 'maturity' => 'MATURITY_STABLE', + 'branchquote' => "'", + 'releasequote' => "'", + ]; + + return [ + 'Back to dev from development' => [ + $developmentVersion, + [ + 'branch' => 'MOODLE_500_STABLE', + 'type' => 'back-to-dev', + 'rc' => '', + 'date' => '20240923', + 'isdevbranch' => true, + ], + ], + 'Back to dev from major with incorrect release info' => [ + $majorVersion, + [ + 'branch' => 'MOODLE_500_STABLE', + 'type' => 'back-to-dev', + 'rc' => '', + 'date' => '20240923', + 'isdevbranch' => true, + ], + ], + ]; + } + #[DataProvider('versionFileProvider')] - public function testFromVersionFile( + public function testFromVersionContent( string $versionFileName, - string $branch, array $expectations, ): void { $versionFileContent = file_get_contents($versionFileName); - $version = VersionInfo::fromVersionFile($versionFileContent, $branch); + $version = VersionInfo::fromVersionContent($versionFileContent); + + foreach ($expectations as $property => $expectedValue) { + self::assertSame($expectedValue, $version->{$property}); + } + } + + #[DataProvider('versionFileProvider')] + public function testFromVersionFile( + string $versionFileName, + array $expectations, + ): void + { + $version = VersionInfo::fromVersionFile($versionFileName); foreach ($expectations as $property => $expectedValue) { self::assertSame($expectedValue, $version->{$property}); @@ -545,89 +637,112 @@ public static function versionFileProvider(): array return [ '4.3.0' => [ dirname(__DIR__) . '/fixtures/versions/4.3.0.php', - 'MOODLE_403_STABLE', [ 'integerversion' => 2023100900, 'decimalversion' => '00', 'release' => '4.3', 'build' => '20231009', - 'branch' => '403', + 'branch' => 403, 'maturity' => 'MATURITY_STABLE', ], ], '4.3.2' => [ dirname(__DIR__) . '/fixtures/versions/4.3.2.php', - 'MOODLE_403_STABLE', [ 'integerversion' => 2023100902, 'decimalversion' => '00', 'release' => '4.3.2', 'build' => '20231222', - 'branch' => '403', + 'branch' => 403, 'maturity' => 'MATURITY_STABLE', ], ], '4.4.0-beta' => [ dirname(__DIR__) . '/fixtures/versions/4.4.0-beta.php', - 'MOODLE_404_STABLE', [ 'integerversion' => 2024041200, 'decimalversion' => '01', 'release' => '4.4beta', 'build' => '20240412', - 'branch' => '404', + 'branch' => 404, 'maturity' => 'MATURITY_BETA', ], ], '4.4.0-rc1' => [ dirname(__DIR__) . '/fixtures/versions/4.4.0-rc1.php', - 'MOODLE_404_STABLE', [ 'integerversion' => 2024041600, 'decimalversion' => '00', 'release' => '4.4rc1', 'build' => '20240416', - 'branch' => '404', + 'branch' => 404, 'maturity' => 'MATURITY_RC', ], ], '4.4.0' => [ dirname(__DIR__) . '/fixtures/versions/4.4.0.php', - 'MOODLE_404_STABLE', [ 'integerversion' => 2024042200, 'decimalversion' => '00', 'release' => '4.4', 'build' => '20240422', - 'branch' => '404', + 'branch' => 404, 'maturity' => 'MATURITY_STABLE', ], ], ]; } - #[DataProvider('invalidVersionFileProvider')] - public function testFromVersionFileInvalid( - string $versionFileName, - string $branch, + #[DataProvider('invalidVersionContentProvider')] + public function testFromVersionContentInvalid( + string $versionFileContent, string $expectedExceptionMessage, ): void { $this->expectException(\Exception::class); $this->expectExceptionMessage($expectedExceptionMessage); - $versionFileContent = file_get_contents($versionFileName); - VersionInfo::fromVersionFile($versionFileContent, $branch); + VersionInfo::fromVersionContent($versionFileContent); } - public static function invalidVersionFileProvider(): array + public static function invalidVersionContentProvider(): array { return [ 'Invalid version file' => [ - dirname(__DIR__) . '/fixtures/versions/invalidVersion.php', - 'MOODLE_404_STABLE', + << [ + << [ + << [ + << Date: Tue, 1 Oct 2024 12:41:07 +0800 Subject: [PATCH 06/12] Add GH Action for CI tests --- .github/workflows/ci.yml | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..405b6c7 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,28 @@ +name: CI Tests +on: [push, pull_request] + +jobs: + PHPUnit: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: + - ubuntu-latest + php: + - 8.2 + - 8.3 + - 8.4 + + steps: + - uses: actions/checkout@v2 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + + - name: Install dependencies + run: composer install + + - name: Run PHPUnit + run: vendor/bin/phpunit From 94984b020bfc7761e741deadececf2b07eb2883c Mon Sep 17 00:00:00 2001 From: Andrew Nicols Date: Tue, 1 Oct 2024 12:41:17 +0800 Subject: [PATCH 07/12] Coding style fixes --- .github/workflows/ci.yml | 28 ++- .gitignore | 1 + composer.json | 12 +- composer.lock | 384 ++++++++++++++++++++++++++++++++- phpcs.xml | 13 ++ phpunit.xml | 2 +- src/Helper.php | 78 ++++--- src/VersionInfo.php | 153 +++++++------ tests/bootstrap.php | 1 + tests/unit/HelperTest.php | 9 +- tests/unit/VersionInfoTest.php | 71 +++--- 11 files changed, 610 insertions(+), 142 deletions(-) create mode 100644 phpcs.xml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 405b6c7..8d3ed01 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,15 +14,37 @@ jobs: - 8.4 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Setup PHP uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php }} - - name: Install dependencies - run: composer install + - uses: php-actions/composer@v6 # or alternative dependency management - name: Run PHPUnit run: vendor/bin/phpunit + cs: + name: Coding standards + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: 8.3 + tools: cs2pr, phpcs + coverage: none + + - uses: php-actions/composer@v6 # or alternative dependency management + + - name: PHP CodeSniffer + run: phpcs -q --no-colors --report=checkstyle | cs2pr + + - uses: php-actions/phpstan@v3 + with: + path: src/ + level: 9 diff --git a/.gitignore b/.gitignore index bf82344..529323a 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,4 @@ .phpunit.cache coverage coverage.txt +.phpcs-cache diff --git a/composer.json b/composer.json index 42fefbb..5dd6dba 100644 --- a/composer.json +++ b/composer.json @@ -4,7 +4,10 @@ "type": "project", "require-dev": { "phpunit/phpunit": "^10.5", - "mikey179/vfsstream": "^1.6" + "mikey179/vfsstream": "^1.6", + "phpcsstandards/phpcsextra": "^1.2.0", + "squizlabs/php_codesniffer": "^3.10", + "phpstan/phpstan": "^1.12" }, "license": "GPLv3", "autoload": { @@ -17,5 +20,10 @@ "name": "Andrew Nicols", "email": "andrew@nicols.co.uk" } - ] + ], + "config": { + "allow-plugins": { + "dealerdirect/phpcodesniffer-composer-installer": true + } + } } diff --git a/composer.lock b/composer.lock index 9540fd0..daa2513 100644 --- a/composer.lock +++ b/composer.lock @@ -4,9 +4,87 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "07601379410b796e1830d7d8fb98132e", + "content-hash": "1743706096250c60c72fcc740108995b", "packages": [], "packages-dev": [ + { + "name": "dealerdirect/phpcodesniffer-composer-installer", + "version": "v1.0.0", + "source": { + "type": "git", + "url": "https://github.com/PHPCSStandards/composer-installer.git", + "reference": "4be43904336affa5c2f70744a348312336afd0da" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPCSStandards/composer-installer/zipball/4be43904336affa5c2f70744a348312336afd0da", + "reference": "4be43904336affa5c2f70744a348312336afd0da", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0 || ^2.0", + "php": ">=5.4", + "squizlabs/php_codesniffer": "^2.0 || ^3.1.0 || ^4.0" + }, + "require-dev": { + "composer/composer": "*", + "ext-json": "*", + "ext-zip": "*", + "php-parallel-lint/php-parallel-lint": "^1.3.1", + "phpcompatibility/php-compatibility": "^9.0", + "yoast/phpunit-polyfills": "^1.0" + }, + "type": "composer-plugin", + "extra": { + "class": "PHPCSStandards\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin" + }, + "autoload": { + "psr-4": { + "PHPCSStandards\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Franck Nijhof", + "email": "franck.nijhof@dealerdirect.com", + "homepage": "http://www.frenck.nl", + "role": "Developer / IT Manager" + }, + { + "name": "Contributors", + "homepage": "https://github.com/PHPCSStandards/composer-installer/graphs/contributors" + } + ], + "description": "PHP_CodeSniffer Standards Composer Installer Plugin", + "homepage": "http://www.dealerdirect.com", + "keywords": [ + "PHPCodeSniffer", + "PHP_CodeSniffer", + "code quality", + "codesniffer", + "composer", + "installer", + "phpcbf", + "phpcs", + "plugin", + "qa", + "quality", + "standard", + "standards", + "style guide", + "stylecheck", + "tests" + ], + "support": { + "issues": "https://github.com/PHPCSStandards/composer-installer/issues", + "source": "https://github.com/PHPCSStandards/composer-installer" + }, + "time": "2023-01-05T11:28:13+00:00" + }, { "name": "mikey179/vfsstream", "version": "v1.6.12", @@ -295,6 +373,230 @@ }, "time": "2022-02-21T01:04:05+00:00" }, + { + "name": "phpcsstandards/phpcsextra", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/PHPCSStandards/PHPCSExtra.git", + "reference": "11d387c6642b6e4acaf0bd9bf5203b8cca1ec489" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPCSStandards/PHPCSExtra/zipball/11d387c6642b6e4acaf0bd9bf5203b8cca1ec489", + "reference": "11d387c6642b6e4acaf0bd9bf5203b8cca1ec489", + "shasum": "" + }, + "require": { + "php": ">=5.4", + "phpcsstandards/phpcsutils": "^1.0.9", + "squizlabs/php_codesniffer": "^3.8.0" + }, + "require-dev": { + "php-parallel-lint/php-console-highlighter": "^1.0", + "php-parallel-lint/php-parallel-lint": "^1.3.2", + "phpcsstandards/phpcsdevcs": "^1.1.6", + "phpcsstandards/phpcsdevtools": "^1.2.1", + "phpunit/phpunit": "^4.5 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.0" + }, + "type": "phpcodesniffer-standard", + "extra": { + "branch-alias": { + "dev-stable": "1.x-dev", + "dev-develop": "1.x-dev" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-3.0-or-later" + ], + "authors": [ + { + "name": "Juliette Reinders Folmer", + "homepage": "https://github.com/jrfnl", + "role": "lead" + }, + { + "name": "Contributors", + "homepage": "https://github.com/PHPCSStandards/PHPCSExtra/graphs/contributors" + } + ], + "description": "A collection of sniffs and standards for use with PHP_CodeSniffer.", + "keywords": [ + "PHP_CodeSniffer", + "phpcbf", + "phpcodesniffer-standard", + "phpcs", + "standards", + "static analysis" + ], + "support": { + "issues": "https://github.com/PHPCSStandards/PHPCSExtra/issues", + "security": "https://github.com/PHPCSStandards/PHPCSExtra/security/policy", + "source": "https://github.com/PHPCSStandards/PHPCSExtra" + }, + "funding": [ + { + "url": "https://github.com/PHPCSStandards", + "type": "github" + }, + { + "url": "https://github.com/jrfnl", + "type": "github" + }, + { + "url": "https://opencollective.com/php_codesniffer", + "type": "open_collective" + } + ], + "time": "2023-12-08T16:49:07+00:00" + }, + { + "name": "phpcsstandards/phpcsutils", + "version": "1.0.12", + "source": { + "type": "git", + "url": "https://github.com/PHPCSStandards/PHPCSUtils.git", + "reference": "87b233b00daf83fb70f40c9a28692be017ea7c6c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPCSStandards/PHPCSUtils/zipball/87b233b00daf83fb70f40c9a28692be017ea7c6c", + "reference": "87b233b00daf83fb70f40c9a28692be017ea7c6c", + "shasum": "" + }, + "require": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.4.1 || ^0.5 || ^0.6.2 || ^0.7 || ^1.0", + "php": ">=5.4", + "squizlabs/php_codesniffer": "^3.10.0 || 4.0.x-dev@dev" + }, + "require-dev": { + "ext-filter": "*", + "php-parallel-lint/php-console-highlighter": "^1.0", + "php-parallel-lint/php-parallel-lint": "^1.3.2", + "phpcsstandards/phpcsdevcs": "^1.1.6", + "yoast/phpunit-polyfills": "^1.1.0 || ^2.0.0" + }, + "type": "phpcodesniffer-standard", + "extra": { + "branch-alias": { + "dev-stable": "1.x-dev", + "dev-develop": "1.x-dev" + } + }, + "autoload": { + "classmap": [ + "PHPCSUtils/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-3.0-or-later" + ], + "authors": [ + { + "name": "Juliette Reinders Folmer", + "homepage": "https://github.com/jrfnl", + "role": "lead" + }, + { + "name": "Contributors", + "homepage": "https://github.com/PHPCSStandards/PHPCSUtils/graphs/contributors" + } + ], + "description": "A suite of utility functions for use with PHP_CodeSniffer", + "homepage": "https://phpcsutils.com/", + "keywords": [ + "PHP_CodeSniffer", + "phpcbf", + "phpcodesniffer-standard", + "phpcs", + "phpcs3", + "standards", + "static analysis", + "tokens", + "utility" + ], + "support": { + "docs": "https://phpcsutils.com/", + "issues": "https://github.com/PHPCSStandards/PHPCSUtils/issues", + "security": "https://github.com/PHPCSStandards/PHPCSUtils/security/policy", + "source": "https://github.com/PHPCSStandards/PHPCSUtils" + }, + "funding": [ + { + "url": "https://github.com/PHPCSStandards", + "type": "github" + }, + { + "url": "https://github.com/jrfnl", + "type": "github" + }, + { + "url": "https://opencollective.com/php_codesniffer", + "type": "open_collective" + } + ], + "time": "2024-05-20T13:34:27+00:00" + }, + { + "name": "phpstan/phpstan", + "version": "1.12.5", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan.git", + "reference": "7e6c6cb7cecb0a6254009a1a8a7d54ec99812b17" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/7e6c6cb7cecb0a6254009a1a8a7d54ec99812b17", + "reference": "7e6c6cb7cecb0a6254009a1a8a7d54ec99812b17", + "shasum": "" + }, + "require": { + "php": "^7.2|^8.0" + }, + "conflict": { + "phpstan/phpstan-shim": "*" + }, + "bin": [ + "phpstan", + "phpstan.phar" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan - PHP Static Analysis Tool", + "keywords": [ + "dev", + "static analysis" + ], + "support": { + "docs": "https://phpstan.org/user-guide/getting-started", + "forum": "https://github.com/phpstan/phpstan/discussions", + "issues": "https://github.com/phpstan/phpstan/issues", + "security": "https://github.com/phpstan/phpstan/security/policy", + "source": "https://github.com/phpstan/phpstan-src" + }, + "funding": [ + { + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://github.com/phpstan", + "type": "github" + } + ], + "time": "2024-09-26T12:45:22+00:00" + }, { "name": "phpunit/php-code-coverage", "version": "10.1.16", @@ -1633,6 +1935,86 @@ ], "time": "2023-02-07T11:34:05+00:00" }, + { + "name": "squizlabs/php_codesniffer", + "version": "3.10.3", + "source": { + "type": "git", + "url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git", + "reference": "62d32998e820bddc40f99f8251958aed187a5c9c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/62d32998e820bddc40f99f8251958aed187a5c9c", + "reference": "62d32998e820bddc40f99f8251958aed187a5c9c", + "shasum": "" + }, + "require": { + "ext-simplexml": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.3.4" + }, + "bin": [ + "bin/phpcbf", + "bin/phpcs" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Greg Sherwood", + "role": "Former lead" + }, + { + "name": "Juliette Reinders Folmer", + "role": "Current lead" + }, + { + "name": "Contributors", + "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer/graphs/contributors" + } + ], + "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", + "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer", + "keywords": [ + "phpcs", + "standards", + "static analysis" + ], + "support": { + "issues": "https://github.com/PHPCSStandards/PHP_CodeSniffer/issues", + "security": "https://github.com/PHPCSStandards/PHP_CodeSniffer/security/policy", + "source": "https://github.com/PHPCSStandards/PHP_CodeSniffer", + "wiki": "https://github.com/PHPCSStandards/PHP_CodeSniffer/wiki" + }, + "funding": [ + { + "url": "https://github.com/PHPCSStandards", + "type": "github" + }, + { + "url": "https://github.com/jrfnl", + "type": "github" + }, + { + "url": "https://opencollective.com/php_codesniffer", + "type": "open_collective" + } + ], + "time": "2024-09-18T10:38:58+00:00" + }, { "name": "theseer/tokenizer", "version": "1.2.3", diff --git a/phpcs.xml b/phpcs.xml new file mode 100644 index 0000000..aa17cd0 --- /dev/null +++ b/phpcs.xml @@ -0,0 +1,13 @@ + + + + + + + + + src + tests + + + diff --git a/phpunit.xml b/phpunit.xml index 45f745e..a900720 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -1,7 +1,7 @@ + bootstrap="tests/bootstrap.php"> tests/unit diff --git a/src/Helper.php b/src/Helper.php index c8117f9..30bce87 100644 --- a/src/Helper.php +++ b/src/Helper.php @@ -1,4 +1,5 @@ - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @copyright 2024 Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class Helper { /** * Bump the version. * - * @param string $path The path to the versio file - * @param string $branch The branch name to set - * @param string $type The type of release - * @param string $rc If a release candidate, the RC number - * @param string $date The date to use for the version - * @param bool $isdevbranch Whether this is a developmentbranch + * @param string $path The path to the versio file + * @param string $branch The branch name to set + * @param string $type The type of release + * @param string $rc If a release candidate, the RC number + * @param string $date The date to use for the version + * @param bool $isdevbranch Whether this is a developmentbranch * @throws Exception * @return string */ @@ -69,7 +70,7 @@ public static function bumpVersion( * this function will not work as expected. If this happens this function * will need to be extended as appropriate in the circumstances. * - * @param int $branch The branch number in integer form + * @param int $branch The branch number in integer form * @return int The new branch number */ public static function getNextBranchNumber( @@ -79,8 +80,8 @@ public static function getNextBranchNumber( return $branch + 1; } - $releasemajor = (int) substr($branch, 0, 1); - $releaseminor = (int) substr($branch, 2, 1); + $releasemajor = (int) substr((string) $branch, 0, 1); + $releaseminor = (int) substr((string) $branch, 2, 1); if ($releaseminor >= 3) { $releasemajor++; @@ -95,40 +96,41 @@ public static function getNextBranchNumber( /** * Ensure that the buymped version is higher than the current one. * - * @param int $versionint The integer part of the version - * @param int $versiondec The decimal part of the version - * @return array + * @param int $versionint The integer part of the version + * @param string|int $versiondec The decimal part of the version + * @return array */ public static function getValidatedVersionNumber( int $versionint, - int $versiondec, + string|int $versiondec, ): array { $today = date('Ymd'); + $versiondec = (int) $versiondec; + if ($versionint >= $today * 100) { // Integer version is already past today * 100, increment version decimal part instead of integer part. $versiondec = (int) $versiondec + 1; - $versiondec = sprintf("%'02d", $versiondec); } else { $versionint = $today . '00'; - $versiondec = '00'; + $versiondec = 0; } if ($versiondec >= 100) { // Decimal version is already past 99, increment integer part and reset decimal part. $versionint = (int) $versionint + 1; - $versiondec = '00'; + $versiondec = 0; } return [ - (int) $versionint, - (string) $versiondec, + 'versionint' => (int) $versionint, + 'versiondec' => (string) sprintf("%'02d", $versiondec), ]; } /** * Check if the branch is a stable branch. * - * @param string $branch The branch name - * @param bool $isdevbranch Whether the branch is a development branch + * @param string $branch The branch name + * @param bool $isdevbranch Whether the branch is a development branch * @return bool */ public static function isBranchStable( @@ -141,7 +143,7 @@ public static function isBranchStable( /** * Check whether a branch name is valid. * - * @param string $branch The branch name + * @param string $branch The branch name * @return bool */ public static function isBranchNameValid( @@ -156,13 +158,13 @@ public static function isBranchNameValid( return true; } - return (preg_match('#^(MOODLE_(\d+)_STABLE)$#', $branch, $matches)); + return !!(preg_match('#^(MOODLE_(\d+)_STABLE)$#', $branch, $matches)); } /** * Ensure the branch name is valid. * - * @param string $branch The branch name + * @param string $branch The branch name * @throws Exception */ public static function requireBranchNameValid( @@ -176,7 +178,7 @@ public static function requireBranchNameValid( /** * Check whether the type is valid. * - * @param string $type The type of the release + * @param string $type The type of the release * @return bool */ public static function isTypeValid( @@ -189,7 +191,7 @@ public static function isTypeValid( /** * Ensure the type is valid. * - * @param string $type The type of the release + * @param string $type The type of the release * @throws Exception */ public static function requireTypeValid( @@ -203,7 +205,7 @@ public static function requireTypeValid( /** * Check whether the path is valid. * - * @param string $path The path to the version file + * @param string $path The path to the version file * @return bool */ public static function isPathValid( @@ -220,7 +222,7 @@ public static function isPathValid( /** * Ensure the path is valid. * - * @param string $path The path to the version file + * @param string $path The path to the version file * @throws Exception */ public static function requirePathValid( @@ -238,7 +240,7 @@ public static function requirePathValid( /** * Validate the version file. * - * @param string $contents The contents of the version file + * @param string $contents The contents of the version file * @return bool */ public static function isVersionFileValid( @@ -259,8 +261,7 @@ public static function isVersionFileValid( /** * Ensure the version file is valid. * - * @param string $contents The contents of the version file - * @param string $branch The branch name + * @param string $contents The contents of the version file * @throws Exception */ public static function requireVersionFileValid( @@ -274,9 +275,9 @@ public static function requireVersionFileValid( /** * Get the value of an option from the options array. * - * @param array $options THe options configuration - * @param string $short The short name of the option - * @param string $long The long name of the option + * @param array $options The options configuration + * @param string $short The short name of the option + * @param string $long The long name of the option * @return mixed */ public static function getOption( @@ -287,14 +288,9 @@ public static function getOption( if (!isset($options[$short]) && !isset($options[$long])) { throw new Exception("Required option -$short|--$long must be provided.", __LINE__); } - if ( - (isset($options[$short]) && is_array($options[$short])) || - (isset($options[$long]) && is_array($options[$long])) || - (isset($options[$short]) && isset($options[$long])) - ) { + if (array_key_exists($short, $options) && array_key_exists($long, $options)) { throw new Exception("Option -$short|--$long specified more than once.", __LINE__); } return (isset($options[$short])) ? $options[$short] : $options[$long]; } - } diff --git a/src/VersionInfo.php b/src/VersionInfo.php index 4535ab4..659def4 100644 --- a/src/VersionInfo.php +++ b/src/VersionInfo.php @@ -1,4 +1,5 @@ - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @copyright 2024 Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class VersionInfo { /** @var int The name of the branch either in integer form, or MOODLE_(\d+)_STABLE form */ public readonly int $branch; + /** @var string The integer version number */ + public readonly string $decimalversion; + /** * Construct a new instance of the VersionInfo class. * - * @param int $integerversion The integer version number. - * @param string $decimalversion Part of the version number after the dot. - * @param string $comment The comment to use for the version. - * @param string $release The release name - * @param string $build The build number - * @param int|string $branch The branch number or name - * @param string $maturity The maturity of the branch - * @param string $branchquote The quote used for the branch - * @param string $releasequote The quote used for the release + * @param int $integerversion The integer version number. + * @param int $decimalversion Part of the version number after the dot. + * @param string $comment The comment to use for the version. + * @param string $release The release name + * @param string $build The build number + * @param int|string $branch The branch number or name + * @param string $maturity The maturity of the branch + * @param string $branchquote The quote used for the branch + * @param string $releasequote The quote used for the release */ public function __construct( public readonly int $integerversion, - public readonly string $decimalversion, + int $decimalversion, public readonly string $comment, public readonly string $release, public readonly string $build, @@ -56,27 +60,32 @@ public function __construct( if (is_string($branch)) { $branch = preg_replace('#^MOODLE_(\d+)_STABLE$#', '$1', $branch); } - $this->branch = $branch; + $this->branch = (int) $branch; + + $this->decimalversion = sprintf("%02d", $decimalversion); } /** * Create a new VersionInfo from the content of a version.php file. * - * @param string $versionfile The content of the file + * @param string $versionfile The content of the file * @throws \Exception * @return self */ - public static function fromVersionContent(string $versionfile): self { + public static function fromVersionContent(string $versionfile): self + { Helper::requireVersionFileValid($versionfile); - if (!preg_match('#^ *\$version *= *(?P\d{10})\.(?P\d{2})\d?[^\/]*(?P/[^\n]*)#m', $versionfile, $matches)) { + $commentregex = '#^ *\$version *= *(?P\d{10})\.(?P\d{2})\d?[^\/]*(?P/[^\n]*)#m'; + if (!preg_match($commentregex, $versionfile, $matches)) { throw new Exception('Could not determine version.', __LINE__); } $integerversion = $matches['integer']; $decimalversion = $matches['decimal']; $comment = $matches['comment']; - if (!preg_match('#^ *\$release *= *(?P\'|")(?P[^ \+]+\+?) *\(Build: (?P\d{8})\)\1#m', $versionfile, $matches)) { + $releaseregex = '#^ *\$release *= *(?P\'|")(?P[^ \+]+\+?) *\(Build: (?P\d{8})\)\1#m'; + if (!preg_match($releaseregex, $versionfile, $matches)) { throw new Exception('Could not determine the release.', __LINE__); } $release = $matches['release']; @@ -94,8 +103,8 @@ public static function fromVersionContent(string $versionfile): self { $maturity = $matches['maturity']; return new self( - integerversion: $integerversion, - decimalversion: $decimalversion, + integerversion: (int) $integerversion, + decimalversion: (int) $decimalversion, comment: $comment, release: $release, build: $buildcurrent, @@ -109,13 +118,17 @@ public static function fromVersionContent(string $versionfile): self { /** * Create a new VersionInfo from the path to a version.php file * - * @param string $path The path to the file + * @param string $path The path to the file * @throws \Exception * @return self */ - public static function fromVersionFile(string $path): self { + public static function fromVersionFile(string $path): self + { Helper::requirePathValid($path); $versionfile = file_get_contents($path); + if (!$versionfile) { + throw new Exception('Could not read the version file.', __LINE__); + } return self::fromVersionContent($versionfile); } @@ -123,11 +136,11 @@ public static function fromVersionFile(string $path): self { /** * Get the VersionInfo instance for the Moodle version that follows this one. * - * @param string $branch The branch name - * @param string $type The release type - * @param string $rc The release candidate number - * @param bool $isdevbranch Whether this is a development branch - * @param mixed $date The date to use for the version + * @param string $branch The branch name + * @param string $type The release type + * @param string $rc The release candidate number + * @param bool $isdevbranch Whether this is a development branch + * @param string|null $date The date to use for the version * @throws \Exception * @return VersionInfo */ @@ -161,7 +174,7 @@ public function getNextVersion( $decimalversion++; $maturity = 'MATURITY_STABLE'; - } else if ($type === 'minor' || $type === 'major') { + } elseif ($type === 'minor' || $type === 'major') { // If it's minor fine, it's if major then stable gets a minor release. // 2.6+ => 2.6.1 // 2.6.12+ => 2.6.13 @@ -170,8 +183,8 @@ public function getNextVersion( $release = substr($release, 0, -1); } if (preg_match('#^(?P\d+\.\d+)\.(?P\d+)#', $release, $matches)) { - $increment = $matches['increment'] + 1; - $release = $matches['version'].'.'.(string)$increment; + $increment = (int) $matches['increment'] + 1; + $release = $matches['version'] . '.' . (string)$increment; } else { // First minor release on this stable branch. Yay X.Y.1. $release .= '.1'; @@ -186,13 +199,13 @@ public function getNextVersion( } } } - } else { // Ok it's a development branch. if ($type === 'weekly' || $type === 'minor') { - // If it's weekly, ok, if it's minor the dev branch doesn't get a minor release so really it's a weekly anyway. - // It's a dev branch. We need to bump the version, if the version is already higher than today*100 then we need - // to bump accordingly. + // If it's weekly, ok. + // If it's minor the dev branch doesn't get a minor release so really it's a weekly anyway. + // It's a dev branch. We need to bump the version. + // If the version is already higher than today * 100 then we need to bump accordingly. // If under beta or rc, make weekly behave exactly as on-demand. if (strpos($release, 'beta') !== false or strpos($release, 'rc') !== false) { // Add the + if missing. @@ -200,37 +213,48 @@ public function getNextVersion( // Add the + $release .= '+'; } - list($integerversion, $decimalversion) = Helper::getValidatedVersionNumber($integerversion, $decimalversion); - } else if (strpos($release, 'dev') === false) { - // Must be immediately after a major release. Bump the release version and set maturity to Alpha. - $release = (float)$release + 0.1; - $release = (string)$release.'dev'; - $maturity = 'MATURITY_ALPHA'; + } elseif (strpos($release, 'dev') === false) { + throw new Exception( + 'Weekly releases are only allowed on dev branches. Have you run a back-to-dev release yet?', + __LINE__, + ); } - list($integerversion, $decimalversion) = Helper::getValidatedVersionNumber($integerversion, $decimalversion); - } else if ($type === 'beta') { + [ + 'versionint' => $integerversion, + 'versiondec' => $decimalversion, + ] = Helper::getValidatedVersionNumber($integerversion, $decimalversion); + } elseif ($type === 'beta') { $release = preg_replace('#^(\d+.\d+) *(dev|beta)\+?#', '$1', $release); $branch = $branchcurrent; // Branch doesn't change in beta releases ever. $release .= 'beta'; - list($integerversion, $decimalversion) = Helper::getValidatedVersionNumber($integerversion, $decimalversion); + [ + 'versionint' => $integerversion, + 'versiondec' => $decimalversion, + ] = Helper::getValidatedVersionNumber($integerversion, $decimalversion); $maturity = 'MATURITY_BETA'; - } else if ($type === 'rc') { + } elseif ($type === 'rc') { $release = preg_replace('#^(\d+.\d+) *(dev|beta|rc\d)\+?#', '$1', $release); $branch = $branchcurrent; // Branch doesn't change in rc releases ever. - $release .= 'rc'.$rc; - list($integerversion, $decimalversion) = Helper::getValidatedVersionNumber($integerversion, $decimalversion); + $release .= 'rc' . $rc; + [ + 'versionint' => $integerversion, + 'versiondec' => $decimalversion, + ] = Helper::getValidatedVersionNumber($integerversion, $decimalversion); $maturity = 'MATURITY_RC'; - } else if ($type === 'on-demand') { + } elseif ($type === 'on-demand') { // Add the + if missing (normally applies to post betas & rcs only, // but it's not wrong to generalize it to any on-demand). if (strpos($release, '+') === false) { // Add the + $release .= '+'; } - list($integerversion, $decimalversion) = Helper::getValidatedVersionNumber($integerversion, $decimalversion); - } else if ($type === 'on-sync') { + [ + 'versionint' => $integerversion, + 'versiondec' => $decimalversion, + ] = Helper::getValidatedVersionNumber($integerversion, $decimalversion); + } elseif ($type === 'on-sync') { $decimalversion++; - } else if ($type === 'back-to-dev') { + } elseif ($type === 'back-to-dev') { // We perform back-to-dev on the `main` branch only. if (strpos($release, 'dev') !== false) { // Ensure it's not a "dev" version already. throw new Exception('Back-to-dev is only allowed on non-dev branches.', __LINE__); @@ -245,8 +269,8 @@ public function getNextVersion( $branch = Helper::getNextBranchNumber($this->branch); // This require knowledge of our branching scheme. - $releasemajor = (int) substr($branch, 0, 1); - $releaseminor = (int) substr($branch, 2, 1); + $releasemajor = (int) substr((string) $branch, 0, 1); + $releaseminor = (int) substr((string) $branch, 2, 1); $release = "{$releasemajor}.{$releaseminor}dev"; @@ -256,11 +280,14 @@ public function getNextVersion( $build = date('Ymd', strtotime('next Monday')); } } - } else if ($type === 'major') { + } elseif ($type === 'major') { // Awesome major release! $release = preg_replace('#^(\d+.\d+) *(dev|beta|rc\d+)\+?#', '$1', $release); $branch = $branchcurrent; // Branch doesn't change in major releases ever. - list($integerversion, $decimalversion) = Helper::getValidatedVersionNumber($integerversion, $decimalversion); + [ + 'versionint' => $integerversion, + 'versiondec' => $decimalversion, + ] = Helper::getValidatedVersionNumber($integerversion, $decimalversion); $maturity = 'MATURITY_STABLE'; // Now handle builddate for releases. if (empty($date)) { // If no date has been forced, dev majors always are released on Monday. @@ -284,18 +311,17 @@ public function getNextVersion( } } - // Replace the old version with the new version. - if (strlen($decimalversion) === 1) { - $decimalversion = '0'.$decimalversion; - } - if ($branch === 'main') { $branch = $this->branch; } + if ($release === null) { + throw new Exception('Could not determine the release.', __LINE__); + } + return new self( - integerversion: $integerversion, - decimalversion: $decimalversion, + integerversion: (int) $integerversion, + decimalversion: (int) $decimalversion, comment: $comment, release: $release, build: $build, @@ -311,9 +337,14 @@ public function getNextVersion( * * @return string */ - public function generateVersionFile(): string { + public function generateVersionFile(): string + { $versionFile = file_get_contents(__DIR__ . '/templates/version.php.tpl'); + if ($versionFile === false) { + throw new Exception('Could not read the version template file.', __LINE__); + } + $replacements = [ 'INTEGERVERSION' => $this->integerversion, 'DECIMALVERSION' => $this->decimalversion, diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 8a1057e..6b1c25c 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -1,4 +1,5 @@ $newint, + 'versiondec' => $newdec, + ] = Helper::getValidatedVersionNumber($int, $dec); $this->assertSame($expectedint, $newint); $this->assertSame($expecteddec, $newdec); } @@ -283,7 +287,8 @@ public function testRequireVersionFileValid( } } - public static function versionFileProvider(): array { + public static function versionFileProvider(): array + { return [ 'Valid version file' => [ 'content' => <<getNextVersion(...$nextVersionArgs); @@ -48,7 +48,7 @@ public static function nextVersionFromMajorProvider(): array { $majorVersion = [ 'integerversion' => 2024092300, - 'decimalversion' => '00', + 'decimalversion' => 0, 'comment' => '// 20240923 = branching date YYYYMMDD - do not modify!', 'release' => '4.5', 'build' => '20240921', @@ -133,24 +133,6 @@ public static function nextVersionFromMajorProvider(): array 'releasequote' => "'", ], ], - 'Development version from major' => [ - $majorVersion, - [ - 'branch' => 'MOODLE_500_STABLE', - 'type' => 'weekly', - 'rc' => '', - 'date' => '20240923', - 'isdevbranch' => true, - ], - [ - 'integerversion' => date('Ymd') * 100, - 'decimalversion' => '00', - 'release' => '4.6dev', // Note: The tooling has not yet been updated to handle the new versioning scheme. - 'build' => '20240923', - 'branchquote' => "'", - 'releasequote' => "'", - ], - ], ]; } @@ -158,7 +140,7 @@ public static function nextVersionFromMinorProvider(): array { $minorVersion = [ 'integerversion' => 2024092301, - 'decimalversion' => '00', + 'decimalversion' => 0, 'comment' => '// 20240923 = branching date YYYYMMDD - do not modify!', 'release' => '4.5.1', 'build' => '20240921', @@ -229,7 +211,7 @@ public static function nextVersionFromWeeklyProvider(): array { $weeklyVersion = [ 'integerversion' => 2024092301, - 'decimalversion' => '00', + 'decimalversion' => 0, 'comment' => '// 20240923 = branching date YYYYMMDD - do not modify!', 'release' => '4.5.1+', 'build' => '20240921', @@ -300,7 +282,7 @@ public static function nextVersionFromDevelopmentProvider(): array { $version = [ 'integerversion' => 2024092301, - 'decimalversion' => '00', + 'decimalversion' => 0, 'comment' => '// 20240923 = branching date YYYYMMDD - do not modify!', 'release' => '5.0dev', 'build' => '20240921', @@ -453,7 +435,7 @@ public static function nextVersionFromBetaProvider(): array { $version = [ 'integerversion' => 2024092301, - 'decimalversion' => '00', + 'decimalversion' => 0, 'comment' => '// 20240923 = branching date YYYYMMDD - do not modify!', 'release' => '5.0beta', 'build' => '20240921', @@ -483,6 +465,25 @@ public static function nextVersionFromBetaProvider(): array 'maturity' => 'MATURITY_BETA', ], ], + 'Weekly version from beta' => [ + $version, + [ + 'branch' => 'MOODLE_500_STABLE', + 'type' => 'weekly', + 'rc' => '', + 'date' => '20240923', + 'isdevbranch' => true, + ], + [ + 'integerversion' => date('Ymd') * 100, + 'decimalversion' => '00', + 'release' => '5.0beta+', + 'build' => '20240923', + 'branchquote' => "'", + 'releasequote' => "'", + 'maturity' => 'MATURITY_BETA', + ], + ], 'RC version from beta' => [ $version, [ @@ -560,7 +561,7 @@ public static function invalidNextVersionMigrationsProvider(): array { $developmentVersion = [ 'integerversion' => 2024092301, - 'decimalversion' => '00', + 'decimalversion' => 0, 'comment' => '// 20240923 = branching date YYYYMMDD - do not modify!', 'release' => '5.0dev', 'build' => '20240921', @@ -571,7 +572,7 @@ public static function invalidNextVersionMigrationsProvider(): array ]; $majorVersion = [ 'integerversion' => 2024092300, - 'decimalversion' => '00', + 'decimalversion' => 0, 'comment' => '// 20240923 = branching date YYYYMMDD - do not modify!', 'release' => '4.5', 'build' => '20240921', @@ -602,6 +603,16 @@ public static function invalidNextVersionMigrationsProvider(): array 'isdevbranch' => true, ], ], + 'Development version from major' => [ + $majorVersion, + [ + 'branch' => 'MOODLE_500_STABLE', + 'type' => 'weekly', + 'rc' => '', + 'date' => '20240923', + 'isdevbranch' => true, + ], + ], ]; } @@ -609,8 +620,7 @@ public static function invalidNextVersionMigrationsProvider(): array public function testFromVersionContent( string $versionFileName, array $expectations, - ): void - { + ): void { $versionFileContent = file_get_contents($versionFileName); $version = VersionInfo::fromVersionContent($versionFileContent); @@ -623,8 +633,7 @@ public function testFromVersionContent( public function testFromVersionFile( string $versionFileName, array $expectations, - ): void - { + ): void { $version = VersionInfo::fromVersionFile($versionFileName); foreach ($expectations as $property => $expectedValue) { From b95f8194c51a8b8115361c31b6804b0d2a93a518 Mon Sep 17 00:00:00 2001 From: Andrew Nicols Date: Tue, 1 Oct 2024 13:07:32 +0800 Subject: [PATCH 08/12] Add helper to get next version --- bumpversions.php | 2 -- get_next_version_number.php | 48 +++++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 2 deletions(-) create mode 100644 get_next_version_number.php diff --git a/bumpversions.php b/bumpversions.php index e5ca3d5..b337e61 100644 --- a/bumpversions.php +++ b/bumpversions.php @@ -21,8 +21,6 @@ require_once(__DIR__ . '/vendor/autoload.php'); -$helper = new Helper(); - // We need the branch and the bump type (weekly. minor, major) try { $shortoptions = 'b:t:p:r:d:i:'; diff --git a/get_next_version_number.php b/get_next_version_number.php new file mode 100644 index 0000000..0f9abd0 --- /dev/null +++ b/get_next_version_number.php @@ -0,0 +1,48 @@ +. + +use MoodleHQ\MoodleRelease\Helper; +use MoodleHQ\MoodleRelease\VersionInfo; + +// Perth is the center of the world. Anything to object? +date_default_timezone_set('Australia/Perth'); + +require_once(__DIR__ . '/vendor/autoload.php'); + +// We need the branch and the bump type (weekly. minor, major) + +$shortoptions = 'b:t:p:r:d:i:'; +$longoptions = [ + 'branch:', + 'type:', + 'path:', + 'rc:', + 'date:', + 'isdevbranch:', +]; + +$options = getopt($shortoptions, $longoptions); +$branch = Helper::getOption($options, 'b', 'branch'); +$type = Helper::getOption($options, 't', 'type'); +$path = Helper::getOption($options, 'p', 'path'); +$rc = Helper::getOption($options, 'r', 'rc'); +$date = Helper::getOption($options, 'd', 'date'); +$isdevbranch = (bool) Helper::getOption($options, 'i', 'isdevbranch'); +$path = rtrim($path, '/') . '/version.php'; + +$currentVersion = VersionInfo::fromVersionFile($path); +$nextVersion = $currentVersion->getNextVersion($branch, $type, $rc, $date, $isdevbranch); +echo $nextVersion->release; From 39ca50376eb30b663a5a757d4026903fed4bb074 Mon Sep 17 00:00:00 2001 From: Andrew Nicols Date: Tue, 1 Oct 2024 13:46:39 +0800 Subject: [PATCH 09/12] Get correct version number of upgrade notes --- prerelease.sh | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/prerelease.sh b/prerelease.sh index bafe853..448dfd4 100755 --- a/prerelease.sh +++ b/prerelease.sh @@ -236,7 +236,12 @@ get_new_stable_branch() { } generate_upgrade_notes() { - local type=$1 + local branch=$1 + local type=$2 + local pwd=$3 + local rc=$4 + local date=$5 + local isdevbranch=$6 cd ${mydir}/gitmirror if [ -f .grunt/upgradenotes.mjs ]; then @@ -250,11 +255,14 @@ generate_upgrade_notes() { npm ci --no-progress > "${tmpfile}" 2>&1 || \ output " ${R}Error running npm ci. Details:${N} $(<"${tmpfile}")" output " - Generating upgrade notes" + + local release=`php ${mydir}/bumpversions.php -b "$branch" -t "$type" -p "$pwd" -r "$rc" -d "$date" -i "$isdevbranch"` + if [ $type == "major" ] || [ $type == "minor" ]; then - .grunt/upgradenotes.mjs release -d > "${tmpfile}" 2>&1 || \ + .grunt/upgradenotes.mjs release -d "${release}" > "${tmpfile}" 2>&1 || \ output " ${R}Error running upgradenotes.mjs. Details:${N} $(<"${tmpfile}")" else - .grunt/upgradenotes.mjs release > "${tmpfile}" 2>&1 || \ + .grunt/upgradenotes.mjs release "${release}" > "${tmpfile}" 2>&1 || \ output " ${R}Error running upgradenotes.mjs. Details:${N} $(<"${tmpfile}")" fi rm -f "${tmpfile}" @@ -787,7 +795,7 @@ for branch in ${branches[@]}; # Now generate the upgrade notes. if $upgradenotes ; then output " - Generating upgrade notes..." - generate_upgrade_notes "$_type" + generate_upgrade_notes "$branch" "$_type" "$pwd" "$_rc" "$_date" "$isdevbranch" if git_unstaged_changes ; then # Add any upgrade files. git add -A From 46c23221432a024e5b84a96404f7bcbf9128d910 Mon Sep 17 00:00:00 2001 From: Andrew Nicols Date: Tue, 1 Oct 2024 14:51:59 +0800 Subject: [PATCH 10/12] Configure code coverage reporting --- .github/workflows/ci.yml | 16 ++++++++++++++-- .gitignore | 1 + 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8d3ed01..4c7f33d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,5 +1,5 @@ name: CI Tests -on: [push, pull_request] +on: [push, pull_request, workflow_dispatch] jobs: PHPUnit: @@ -23,8 +23,20 @@ jobs: - uses: php-actions/composer@v6 # or alternative dependency management + - name: Run PHPUnit - run: vendor/bin/phpunit + run: vendor/bin/phpunit --coverage-clover ./coverage.xml + env: + XDEBUG_MODE: coverage + + - name: Upload to Codecov + uses: codecov/codecov-action@v4 + with: + token: ${{ secrets.CODECOV_TOKEN }} + files: ./coverage.xml + verbose: true + + cs: name: Coding standards runs-on: ubuntu-latest diff --git a/.gitignore b/.gitignore index 529323a..999f612 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,4 @@ coverage coverage.txt .phpcs-cache +coverage.xml From c809af30254fd4b5b426cb06cb22349fd623ae95 Mon Sep 17 00:00:00 2001 From: Andrew Nicols Date: Tue, 1 Oct 2024 22:00:24 +0800 Subject: [PATCH 11/12] Return true for require methods --- src/Helper.php | 20 +++++++++++++++----- tests/unit/HelperTest.php | 8 ++++---- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/src/Helper.php b/src/Helper.php index 30bce87..368bf4b 100644 --- a/src/Helper.php +++ b/src/Helper.php @@ -166,13 +166,16 @@ public static function isBranchNameValid( * * @param string $branch The branch name * @throws Exception + * @return bool */ public static function requireBranchNameValid( string $branch, - ): void { + ): bool { if (!self::isBranchNameValid($branch)) { throw new Exception('Invalid branch given', __LINE__); } + + return true; } /** @@ -193,13 +196,16 @@ public static function isTypeValid( * * @param string $type The type of the release * @throws Exception + * @return bool */ public static function requireTypeValid( string $type, - ): void { + ): bool { if (!self::isTypeValid($type)) { throw new Exception('Invalid type given.', __LINE__); } + + return true; } /** @@ -224,13 +230,14 @@ public static function isPathValid( * * @param string $path The path to the version file * @throws Exception + * @return bool */ public static function requirePathValid( string $path, - ): void { + ): bool { if (file_exists($path) && is_readable($path)) { if (is_writable($path)) { - return; + return true; } throw new Exception('Path cannot be written to.', __LINE__); } @@ -263,13 +270,16 @@ public static function isVersionFileValid( * * @param string $contents The contents of the version file * @throws Exception + * @return bool */ public static function requireVersionFileValid( string $contents, - ): void { + ): bool { if (!self::isVersionFileValid($contents)) { throw new Exception('Invalid version file found.', __LINE__); } + + return true; } /** diff --git a/tests/unit/HelperTest.php b/tests/unit/HelperTest.php index 258c19d..63d8763 100644 --- a/tests/unit/HelperTest.php +++ b/tests/unit/HelperTest.php @@ -42,7 +42,7 @@ public function testIsTypeValid(string $type, bool $expected): void public function testRequireTypeValid(string $type, bool $expected): void { if ($expected) { - $this->assertNull(Helper::requireTypeValid($type)); + $this->assertTrue(Helper::requireTypeValid($type)); } else { $this->expectException(\Exception::class); Helper::requireTypeValid($type); @@ -155,7 +155,7 @@ public function testRequireBranchNameValid( bool $expected, ): void { if ($expected) { - $this->assertNull(Helper::requireBranchNameValid($name)); + $this->assertTrue(Helper::requireBranchNameValid($name)); } else { $this->expectException(\Exception::class); Helper::requireBranchNameValid($name); @@ -234,7 +234,7 @@ public function testRequirePathValid( $path = vfsStream::url("root/{$path}"); if ($expected) { - $this->assertNull(Helper::requirePathValid($path)); + $this->assertTrue(Helper::requirePathValid($path)); } else { $this->expectException(\Exception::class); $this->expectExceptionMessage($message); @@ -280,7 +280,7 @@ public function testRequireVersionFileValid( bool $expected, ): void { if ($expected) { - $this->assertNull(Helper::requireVersionFileValid($content)); + $this->assertTrue(Helper::requireVersionFileValid($content)); } else { $this->expectException(\Exception::class); Helper::requireVersionFileValid($content); From 99aa5d84513a729d30a620ce0f30425018212e96 Mon Sep 17 00:00:00 2001 From: Andrew Nicols Date: Tue, 1 Oct 2024 21:48:39 +0800 Subject: [PATCH 12/12] Update installation instructions --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index fa46def..087bc56 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,7 @@ Set up is dead easy thanks to a the installation script: git clone https://github.com/moodlehq/mdlrelease.git cd mdlrelease + composer install ./install.sh protocol username protocol: defaults to 'ssh', also accepts 'https'.