From 7fb93a76ec9cba0e7d05ad61104bd09761e3c70d Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Wed, 30 Oct 2024 18:27:43 +0000 Subject: [PATCH] Extract a CI workflow for release PRs this allows some automatic validation of release PRs before merging, which should catch some mistakes. --- .github/workflows/draft-release-from-pr.yml | 59 ++--------- .github/workflows/draft-release-pr-check.yml | 105 +++++++++++++++++++ build/dump-version-info.php | 17 ++- phpstan.neon.dist | 1 + 4 files changed, 129 insertions(+), 53 deletions(-) create mode 100644 .github/workflows/draft-release-pr-check.yml diff --git a/.github/workflows/draft-release-from-pr.yml b/.github/workflows/draft-release-from-pr.yml index a1c71e9710a..87d91b05dd9 100644 --- a/.github/workflows/draft-release-from-pr.yml +++ b/.github/workflows/draft-release-from-pr.yml @@ -1,6 +1,8 @@ name: Draft release from PR on: + #presume that pull_request_target is safe at this point, since the PR was approved and merged + #we need write access to prepare the release & create comments pull_request_target: types: - closed @@ -13,61 +15,14 @@ on: - "src/VersionInfo.php" jobs: - check-validity: - name: Check that this PR merge is a valid release target - if: github.event.pull_request.merged == true - runs-on: ubuntu-20.04 - strategy: - fail-fast: false - matrix: - php-version: [8.2] - - outputs: - valid: ${{ steps.validate.outputs.VALID_RELEASE == 'true' - && steps.check-permission.outputs.require-result == 'true' - && steps.check-permission.outputs.check-result == 'false' - }} - - steps: - - uses: actions/checkout@v4 - - - name: Setup PHP - uses: shivammathur/setup-php@2.31.1 - with: - php-version: ${{ matrix.php-version }} - - - name: Restore Composer package cache - uses: actions/cache@v4 - with: - path: | - ~/.cache/composer/files - ~/.cache/composer/vcs - key: "composer-v2-cache-${{ hashFiles('./composer.lock') }}" - restore-keys: | - composer-v2-cache- - - - name: Install Composer dependencies - run: composer install --no-dev --prefer-dist --no-interaction --ignore-platform-reqs - - - name: Check if PR author has write access - id: check-permission - uses: actions-cool/check-user-permission@v2 - with: - token: ${{ secrets.GITHUB_TOKEN }} - require: write - username: ${{ github.event.pull_request.user.login }} - #technically this would be fine for dependabot but generally bots don't count as team members - check-bot: true - - - name: Check IS_DEVELOPMENT_BUILD flag - id: validate - run: | - echo VALID_RELEASE=$(php build/dump-version-info.php is_dev) >> $GITHUB_OUTPUT + check: + name: Check release trigger conditions + uses: ./.github/workflows/draft-release-pr-check.yml draft: name: Create GitHub draft release - needs: [check-validity] - if: needs.check-validity.outputs.valid == 'true' + needs: [check] + if: needs.check.outputs.valid == 'true' uses: ./.github/workflows/draft-release.yml diff --git a/.github/workflows/draft-release-pr-check.yml b/.github/workflows/draft-release-pr-check.yml new file mode 100644 index 00000000000..973dbf5fa51 --- /dev/null +++ b/.github/workflows/draft-release-pr-check.yml @@ -0,0 +1,105 @@ +name: Draft release from PR + +on: + #do checks on every PR update + pull_request: + branches: + - stable + - minor-next + - major-next + - "legacy/*" + paths: + - "src/VersionInfo.php" + + #allow this workflow to be invoked on PR merge, prior to creating the release + workflow_call: + outputs: + valid: ${{ jobs.check-intent.outputs.valid && jobs.check-validity.result == 'success' }} + +permissions: + contents: read #for user access check + +jobs: + check-intent: + name: Check if PR is intended to trigger a release + runs-on: ubuntu-20.04 + strategy: + fail-fast: false + matrix: + php-version: [8.2] + + outputs: + valid: ${{ steps.validate.outputs.VALID_RELEASE == 'true' + && steps.check-permission.outputs.require-result == 'true' + && steps.check-permission.outputs.check-result == 'false' + }} + + steps: + - uses: actions/checkout@v4 + + - name: Setup PHP + uses: shivammathur/setup-php@2.31.1 + with: + php-version: ${{ matrix.php-version }} + + - name: Restore Composer package cache + uses: actions/cache@v4 + with: + path: | + ~/.cache/composer/files + ~/.cache/composer/vcs + key: "composer-v2-cache-${{ hashFiles('./composer.lock') }}" + restore-keys: | + composer-v2-cache- + + - name: Install Composer dependencies + run: composer install --no-dev --prefer-dist --no-interaction --ignore-platform-reqs + + - name: Check if PR author has write access + id: check-permission + uses: actions-cool/check-user-permission@v2 + with: + token: ${{ secrets.GITHUB_TOKEN }} + require: write + username: ${{ github.event.pull_request.user.login }} + #technically this would be fine for dependabot but generally bots don't count as team members + check-bot: true + + - name: Check IS_DEVELOPMENT_BUILD flag + id: validate + run: | + echo VALID_RELEASE=$(php build/dump-version-info.php is_dev) >> $GITHUB_OUTPUT + + check-validity: + name: Check if PR complies with release rules + #don't do these checks if this isn't a release - we don't want to generate unnecessary failed statuses + if: needs.check-release-intent.outputs.valid == 'true' + + runs-on: ubuntu-20.04 + + steps: + - name: Check changelog file is present + id: file-presence + run: | + CHANGELOG_FILE="changelogs/$(php build/dump-version-info.php changelog_file_name)" + if [ ! -f "${{ github.workspace }}/$CHANGELOG_FILE" ]; then + echo "::error::$CHANGELOG_FILE does not exist" + exit 1 + fi + echo FILE="$CHANGELOG_FILE" >> $GITHUB_OUTPUT + + - name: Check header is present in changelog file + run: | + FILE="${{ steps.file-presence.outputs.FILE }}" + VERSION="$(php build/dump-version-info.php base_version)" + if [ ! grep -Fqx "# $VERSION" "${{ github.workspace }}/$FILE" ]; then + echo "::error::Header for $VERSION not found in $FILE" + exit 1 + fi + + - name: Check version is valid for the selected channel + run: | + if [ "$(php build/dump-version-info.php suffix_valid)" != "true" ]; then + echo "::error::Version $(php build/dump-version-info.php base_version) is not allowed on the $CHANNEL channel" + exit 1 + fi diff --git a/build/dump-version-info.php b/build/dump-version-info.php index ec1abf4c331..8898d7cabf5 100644 --- a/build/dump-version-info.php +++ b/build/dump-version-info.php @@ -53,7 +53,22 @@ return $result . ".md"; }, "changelog_md_header" => fn() : string => str_replace(".", "", VersionInfo::BASE_VERSION), - "prerelease" => fn() : bool => VersionInfo::VERSION()->getSuffix() !== "" + "prerelease" => fn() : bool => VersionInfo::VERSION()->getSuffix() !== "", + "channel" => VersionInfo::BUILD_CHANNEL, + "suffix_valid" => function() : bool{ + //TODO: maybe this should be put into its own script? + $suffix = VersionInfo::VERSION()->getSuffix(); + if(VersionInfo::BUILD_CHANNEL === "stable"){ + //stable builds may not have suffixes + return $suffix === ""; + } + if(VersionInfo::BUILD_CHANNEL === "alpha" || VersionInfo::BUILD_CHANNEL === "beta"){ + $upperChannel = strtoupper(VersionInfo::BUILD_CHANNEL); + $upperSuffix = strtoupper($suffix); + return str_starts_with($upperSuffix, $upperChannel) && is_numeric(substr($upperSuffix, strlen($upperChannel))); + } + return true; + } ]; if(count($argv) !== 2 || !isset($options[$argv[1]])){ fwrite(STDERR, "Please provide an option (one of: " . implode(", ", array_keys($options)) . PHP_EOL); diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 2fde67d6ca9..b96e4348ddc 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -40,6 +40,7 @@ parameters: - build/php dynamicConstantNames: - pocketmine\VersionInfo::IS_DEVELOPMENT_BUILD + - pocketmine\VersionInfo::BUILD_CHANNEL - pocketmine\DEBUG - pocketmine\IS_DEVELOPMENT_BUILD stubFiles: