Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Publish release checklist and automate release verification #758

Merged
merged 2 commits into from
Dec 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
126 changes: 126 additions & 0 deletions .github/release-checklist.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
# Release checklist

## Before Release

### General

- [ ] Verify, and if necessary, update the version constraints for dependencies in the `composer.json` - PR #xxx
- [ ] Verify that any new functions have type declarations (ClassName/array/callable) whenever possible.
- [ ] Verify that the license tags all refer to the _new_ organisation and no longer to Squizlabs. (easily overlooked in new files)
- [ ] Verify that `@copyright` tags in new files use `@copyright 20xx PHPCSStandards and contributors`.

### Wiki

- [ ] Fetch changes and check against vandalism.
- [ ] Verify that any new `public` properties are listed on the Customizable Properties page in the Wiki.
- [ ] Verify that any new sniffs which have `public` properties are listed on the Customizable Properties page in the Wiki.
- [ ] Verify that any new CLI options are listed in the Wiki.
- [ ] Verify that any new Reports have a section in the Reports page in the Wiki.

### Majors only

- [ ] Move old changelog entries to `CHANGELOG_OLD.md` file.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where is this file located? I was not able to find it in the repository.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It doesn't exist yet as there has not been a new major release since the repo was taken over.

- [ ] Verify that everything deprecated during the previous major was removed.
- [ ] Update the wiki for any references to anything deprecated/removed.
- [ ] Change `Config::STABILITY` from "dev" to "stable" for the branch for the new major. - PR #xxx

### Prepare changelog

- [ ] Prepare changelog for the release and submit the PR. - PR #xxx
- Based on the tickets in the milestone.
- Double-check that any issues which were closed by PRs included in the release, have the milestone set.
- Compare with auto-generated release notes to ensure nothing is missed.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know what the auto-generated release notes are. I'm sharing this here in case you think it should be clarified in the release checklist.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, let me guess, you don't do releases ?

If you go to a repo on which you have the authority to create a release....
➡️ Go to the owner/repo/releases page
➡️ Click on the "Draft a new release" button
➡️ Enter a tag name for the release...

And you will see a new button become available:
image

If you click on it, it will give you a machine generated changelog based on PR titles (which is horrible in my opinion as PR titles are often not intended for end-users and a changelog should be written for end-users):

image

Does that make it clearer for you ?

IMO this does not need further clarification in the checklist as, even if someone has never seen this before, they will come across the button anyhow the first time they do a release and will know it from then on.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, let me guess, you don't do releases ?

It is been a couple of years since the last time that I did a GitHub release :-)

Does that make it clearer for you ?

Yes, thanks!

IMO this does not need further clarification in the checklist as, even if someone has never seen this before, they will come across the button anyhow the first time they do a release and will know it from then on.

👍

- :pencil2: Remember to add a release link at the bottom!
- [ ] Prepare extra sections for the GH release notes.
- Use "New contributors" list from the auto-generated notes.
- Use the milestone to gather the stats.
- Add sponsor link.
- Remove square brackets from all ticket links or make them proper full links (as GH markdown parser doesn't parse these correctly).
- Change all contributor links to full inline links (as GH markdown parser on the Releases page doesn't parse these correctly).
```md
---

### New Contributors

The PHP_CodeSniffer project is happy to welcome the following new contributors:
@...., @....

### Statistics

**Closed**: # issues
**Merged**: ## pull requests

If you like to stay informed about releases and more, follow [@phpcs on Mastodon](https://phpc.social/@phpcs) or [@PHP_CodeSniffer on X](https://x.com/PHP_CodeSniffer).

Please consider [funding the PHP_CodeSniffer project](https://opencollective.com/php_codesniffer). If you already do so: thank you!
```

### Milestone

- [ ] Close the milestone
- [ ] Open a new milestone for the next release
- [ ] If any open PRs/issues which were milestoned for this release did not make it into the release, update their milestone.


## Release

- [ ] Merge the changelog PR.
For now, cherrypick the changelog to the 4.0 branch.
- [ ] Make sure all CI builds for `master` are green.
- [ ] Create a tag for the release & push it.
- [ ] Make sure all CI builds are green.
- [ ] Download the PHAR files from the GH Actions test build page.
- [ ] Sign the PHAR files using:
```bash
gpg -u [email protected] --detach-sign --output phpcs.phar.asc phpcs.phar
gpg -u [email protected] --detach-sign --output phpcbf.phar.asc phpcbf.phar
gpg -u [email protected] --detach-sign --output phpcs-x.x.x.phar.asc phpcs-x.x.x.phar
gpg -u [email protected] --detach-sign --output phpcbf-x.x.x.phar.asc phpcbf-x.x.x.phar
```
- If, for whatever reason, the key is no longer available or has expired:
-> generate a new key following the steps here: <https://phar.io/howto/generate-gpg-key.html>.
-> upload the new key following the steps here: <https://phar.io/howto/uploading-public-keys.html>.
-> update the key information in the README x 3.
-> update the key info in the verify-release GHA workflow.
- [ ] Get the SHA of the files for the phive.xml file
```bash
# Linux
sha256sum ./phpcs-x.x.x.phar
sha256sum ./phpcbf-x.x.x.phar

# Windows
certutil -hashfile ./phpcs-x.x.x.phar SHA256
certutil -hashfile ./phpcbf-x.x.x.phar SHA256
```
- Update the `gh-pages` branch:
- [ ] Add the new release to the `phive.xml` file.
- [ ] Add the versioned PHAR files + keys in PHAR dir.
- [ ] Add the unversioned PHAR files + keys in root dir.
- [ ] Verify the attestations of the PHAR files.
```bash
gh attestation verify phpcs.phar -o PHPCSStandards
gh attestation verify phpcbf.phar -o PHPCSStandards
gh attestation verify phars/phpcs-x.x.x.phar -o PHPCSStandards
gh attestation verify phars/phpcbf-x.x.x.phar -o PHPCSStandards
```
- [ ] Commit & push the changes.
- [ ] Verify that the website regenerated correctly and that the phars can be downloaded.
- [ ] Create a release & copy & paste the changelog to it.
- [ ] Upload the unversioned PHAR files + asc files to the release.
- [ ] Announce the release in the discussions forum by checking the checkbox at the bottom of the release page.
- [ ] Make sure all CI builds are green, including the verify-release workflow.


## After Release

- [ ] Update the version number in the `Config::VERSION` class constant in the `src/Config.php` file to the _next_ (patch) version.
This can always be adjusted again later if needs be if it is decided that the next version will be a minor/major, but at least for dev
it should clearly show that this is bleeding edge/unreleased.
- [ ] Close release announcement in the "Discussions" for previous minors (leave the announcements related to the current minor open).


### Publicize

- [ ] Post on Mastodon about the release (official account).
- [ ] Post on X about the release (official account).
- [ ] Post on LinkedIn (personal account).
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not something that needs to be addressed now, but this line might need adjustment in the future if someone other than you were to make a release as it mentions a personal account.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, there is no account on LI for PHPCS as a project, as LI doesn't really accommodate that situation, so in that case, either the action item would be skipped or done from the account of whomever does the release, providing they have a LinkedIn account.

203 changes: 203 additions & 0 deletions .github/workflows/verify-release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
name: Verify release

on:
# Run whenever a release is published.
release:
types: [published]
# And whenever this workflow is updated.
push:
paths:
- '.github/workflows/verify-release.yml'
pull_request:
paths:
- '.github/workflows/verify-release.yml'
# Allow manually triggering the workflow.
workflow_dispatch:

# Cancels all previous workflow runs for the same branch that have not yet completed.
concurrency:
# The concurrency group contains the workflow name and the branch name.
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
##################################################################################
# Verify the release is available in all the right places and works as expected. #
##################################################################################
verify-available-downloads:
runs-on: ubuntu-latest

# Only run this workflow in the context of this repo.
if: github.repository_owner == 'PHPCSStandards'

strategy:
fail-fast: false
matrix:
download_flavour:
- "Release assets"
- "Unversioned web"
- "Versioned web"
pharfile:
- 'phpcs'
- 'phpcbf'

name: "${{ matrix.download_flavour }}: ${{ matrix.pharfile }}"

steps:
- name: Retrieve latest release info
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This job performs steps that are also performed in the job below. Have you considered moving the repeated steps to a separate job that can be reused?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure what you mean by "separate job that can be reused" ?

I did consider moving some steps to a reusable workflow, but that's not actually an option.
A job using a reusable workflow can't have extra steps aside from the ones in the reusable workflow itself, so it wouldn't allow for the other check steps.

The other option would be to create a composite action, but that would need a separate repo, which I think is completely over the top.

Also see: https://docs.github.com/en/actions/sharing-automations/avoiding-duplication

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't have much experience with GitHub Actions. On a quick search, I came across the two options you mentioned and started wondering if they could be used here. However, I was not aware of the limitations you mentioned.

The only other option that I considered that I'm not sure as well if it would work or not, is to create a job to run the following steps:

steps:
- name: Retrieve latest release info
uses: octokit/[email protected]
id: get_latest_release
with:
route: GET /repos/PHPCSStandards/PHP_CodeSniffer/releases/latest
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: "DEBUG: Show API request failure status"
if: ${{ failure() }}
run: "echo No release found. Request failed with status ${{ steps.get_latest_release.outputs.status }}"
- name: Grab latest tag name from API response
id: version
run: |
echo "TAG=${{ fromJson(steps.get_latest_release.outputs.data).tag_name }}" >> "$GITHUB_OUTPUT"
- name: "DEBUG: Show tag name found in API response"
run: "echo ${{ steps.version.outputs.TAG }}"

Then use needs (https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions#jobsjob_idneeds) to make this new job run before verify-available-downloads and verify-phive. It seems that there is a way to share the latest version between different jobs: https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/passing-information-between-jobs

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see what you mean, but for those two very simple steps (+ two debug steps), I think that would be overengineering and that would delay the builds unnecessarily with the needs.

uses: octokit/[email protected]
id: get_latest_release
with:
route: GET /repos/PHPCSStandards/PHP_CodeSniffer/releases/latest
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: "DEBUG: Show API request failure status"
if: ${{ failure() }}
run: "echo No release found. Request failed with status ${{ steps.get_latest_release.outputs.status }}"

- name: Grab latest tag name from API response
id: version
run: |
echo "TAG=${{ fromJson(steps.get_latest_release.outputs.data).tag_name }}" >> "$GITHUB_OUTPUT"

- name: "DEBUG: Show tag name found in API response"
run: "echo ${{ steps.version.outputs.TAG }}"

- name: Set source URL and file name
id: source
shell: bash
run: |
if [[ "${{ matrix.download_flavour }}" == "Release assets" ]]; then
echo 'SRC=https://github.com/PHPCSStandards/PHP_CodeSniffer/releases/latest/download/' >> "$GITHUB_OUTPUT"
echo "FILE=${{ matrix.pharfile }}.phar" >> "$GITHUB_OUTPUT"
elif [[ "${{ matrix.download_flavour }}" == "Unversioned web" ]]; then
echo 'SRC=https://phars.phpcodesniffer.com/' >> "$GITHUB_OUTPUT"
echo "FILE=${{ matrix.pharfile }}.phar" >> "$GITHUB_OUTPUT"
else
echo 'SRC=https://phars.phpcodesniffer.com/phars/' >> "$GITHUB_OUTPUT"
echo "FILE=${{ matrix.pharfile }}-${{ steps.version.outputs.TAG }}.phar" >> "$GITHUB_OUTPUT"
fi

- name: Verify PHAR file is available and download
run: "wget -O ${{ steps.source.outputs.FILE }} ${{ steps.source.outputs.SRC }}${{ steps.source.outputs.FILE }}"

- name: Verify signature file is available and download
run: "wget -O ${{ steps.source.outputs.FILE }}.asc ${{ steps.source.outputs.SRC }}${{ steps.source.outputs.FILE }}.asc"

- name: "DEBUG: List files"
run: ls -Rlh

- name: Verify attestation of the PHAR file
run: gh attestation verify ${{ steps.source.outputs.FILE }} -o PHPCSStandards
env:
GH_TOKEN: ${{ github.token }}

- name: Download public key
env:
FINGERPRINT: "0x689DAD778FF08760E046228BA978220305CD5C32"
run: gpg --keyserver "hkps://keys.openpgp.org" --recv-keys "$FINGERPRINT"

- name: Verify signature of the PHAR file
run: gpg --verify ${{ steps.source.outputs.FILE }}.asc ${{ steps.source.outputs.FILE }}

- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: 'latest'
ini-values: error_reporting=-1, display_errors=On
coverage: none

# Note: the `.` is in the command to make it work for both PHPCS as well PHPCBF.
- name: Verify the PHAR is nominally functional
run: php ${{ steps.source.outputs.FILE }} . -e --standard=PSR12
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason to use the -e parameter here? I'm asking as this parameter does not exist for phpcbf? I wonder if it would be preferable to use a command that includes parameters that exist for both phpcs and phpcbf?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question. The problem is that these workflows are being run in a virgin environment (we're not checking out the repo), so there are no PHP files which could be scanned, so this seemed like a workable solution which would a) at the very least run the PHAR file and b) give a 0 exit code in both cases.

If you have a suggestion for another command, I'd be happy to consider it.
(and I did already consider -h (help), but felt that it bowed out a little too early and had no risk of a non-0 exit code, which is why I didn't go with that)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we generate a trivial PHP file to check against? echo '<?php echo "Hello, World!\n";' > hello.php

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you have a suggestion for another command, I'd be happy to consider it.
(and I did already consider -h (help), but felt that it bowed out a little too early and had no risk of a non-0 exit code, which is why I didn't go with that)

-h is precisely what I had in mind, but I missed the limitations that you mentioned. I can't think of a better command.

Can we generate a trivial PHP file to check against? echo '<?php echo "Hello, World!\n";' > hello.php

That seems like a good approach if it is straightforward to implement.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you both don't mind, I'd like to test the workflow with the upcoming 3.11.2 release tomorrow. Happy to iterate on the exact command after that if either of you want to submit a PR for that.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good to me.


- name: Grab the version
id: asset_version
env:
FILE_NAME: ${{ steps.source.outputs.FILE }}
# yamllint disable-line rule:line-length
run: echo "VERSION=$(php "$FILE_NAME" --version | grep --only-matching --max-count=1 --extended-regexp '\b[0-9]+(\.[0-9]+)+')" >> "$GITHUB_OUTPUT"

- name: "DEBUG: Show grabbed version"
run: echo ${{ steps.asset_version.outputs.VERSION }}

- name: Fail the build if the PHAR is not the correct version
if: ${{ steps.asset_version.outputs.VERSION != steps.version.outputs.TAG }}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we should include a message here outputting that there was a version mismatch and print both versions to make it easier to debug failures.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that would be possible. Not sure it is really needed.

For the record: both version numbers are already printed out in "DEBUG" steps earlier on in the workflow, so debugging this shouldn't be hard as it is.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is fair

run: exit 1

# #########################################
# Verify install via PHIVE.
# #########################################
verify-phive:
runs-on: ubuntu-latest

# Only run this workflow in the context of this repo.
if: github.repository_owner == 'PHPCSStandards'

strategy:
fail-fast: false
matrix:
pharfile:
- 'phpcs'
- 'phpcbf'

name: "PHIVE: ${{ matrix.pharfile }}"

steps:
- name: Retrieve latest release info
uses: octokit/[email protected]
id: get_latest_release
with:
route: GET /repos/PHPCSStandards/PHP_CodeSniffer/releases/latest
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: "DEBUG: Show API request failure status"
if: ${{ failure() }}
run: "echo No release found. Request failed with status ${{ steps.get_latest_release.outputs.status }}"

- name: Grab latest tag name from API response
id: version
run: |
echo "TAG=${{ fromJson(steps.get_latest_release.outputs.data).tag_name }}" >> "$GITHUB_OUTPUT"

- name: "DEBUG: Show tag name found in API response"
run: "echo ${{ steps.version.outputs.TAG }}"

- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: 'latest'
ini-values: error_reporting=-1, display_errors=On
coverage: none
tools: phive

- name: Install
run: phive install ${{ matrix.pharfile }} --copy --trust-gpg-keys 689DAD778FF08760E046228BA978220305CD5C32

- name: "DEBUG: List files"
run: ls -R

- name: Verify attestation of the PHAR file
run: gh attestation verify ./tools/${{ matrix.pharfile }} -o PHPCSStandards
env:
GH_TOKEN: ${{ github.token }}

# Note: the `.` is in the command to make it work for both PHPCS as well PHPCBF.
- name: Verify the PHAR is nominally functional
run: php ./tools/${{ matrix.pharfile }} . -e --standard=PSR12

- name: Grab the version
id: asset_version
env:
FILE_NAME: ./tools/${{ matrix.pharfile }}
# yamllint disable-line rule:line-length
run: echo "VERSION=$(php "$FILE_NAME" --version | grep --only-matching --max-count=1 --extended-regexp '\b[0-9]+(\.[0-9]+)+')" >> "$GITHUB_OUTPUT"

- name: "DEBUG: Show grabbed version"
run: echo ${{ steps.asset_version.outputs.VERSION }}

- name: Fail the build if the PHAR is not the correct version
if: ${{ steps.asset_version.outputs.VERSION != steps.version.outputs.TAG }}
run: exit 1
3 changes: 2 additions & 1 deletion .remarkrc
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
{
"skipUrlPatterns": [
"^https?://github\\.com/PHPCSStandards/PHP_CodeSniffer/compare/[0-9\\.]+?\\.{3}[0-9\\.]+",
"^https?://github\\.com/[A-Za-z0-9-]+"
"^https?://github\\.com/[A-Za-z0-9-]+",
"^https?://x\\.com/PHP_CodeSniffer"
]
}
],
Expand Down
Loading