aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/.github
diff options
context:
space:
mode:
authorAndreas Gohr <andi@splitbrain.org>2023-02-23 15:03:01 +0100
committerAndreas Gohr <andi@splitbrain.org>2023-02-24 21:45:12 +0100
commit290ea73da0bf2b8df7bddcb78a7c73c52929c96c (patch)
tree26d48bc3bb87791bd3a504097ea2379d3dcbdafa /.github
parenta42c05d2dd170bfdf65b34862ce9ed085297d733 (diff)
downloaddokuwiki-290ea73da0bf2b8df7bddcb78a7c73c52929c96c.tar.gz
dokuwiki-290ea73da0bf2b8df7bddcb78a7c73c52929c96c.zip
Automate the release workflow using GitHub actions
This introduces a two-part release mechanism. A manually triggered workflow asks for the important info like type of release (stable, rc) and code name. It then creates a cleanly mergable pull request. When that pull request is merged, a release is automatically tagged, built and uploaded. Another workflow is introduced to keep track of the deleted.files info. This is one less chore to do on a release. A new scheme for tags is also introduced, making all tags sortable, regardless of their type. They follow the pattern release-YYYY-MM-DD(<hotfixletter>|rc) A script will be used to clean-up the existing tags.
Diffstat (limited to '.github')
-rw-r--r--.github/release.php188
-rw-r--r--.github/version.php52
-rw-r--r--.github/workflows/deletedFiles.yml39
-rw-r--r--.github/workflows/release-build.yml106
-rw-r--r--.github/workflows/release-preparation.yml102
-rw-r--r--.github/workflows/release.yml53
6 files changed, 435 insertions, 105 deletions
diff --git a/.github/release.php b/.github/release.php
new file mode 100644
index 000000000..e74bc93bc
--- /dev/null
+++ b/.github/release.php
@@ -0,0 +1,188 @@
+<?php
+
+if (!defined('DOKU_INC')) define('DOKU_INC', __DIR__ . '/../');
+require_once(DOKU_INC . 'vendor/autoload.php');
+require_once DOKU_INC . 'inc/load.php';
+
+/**
+ * Command Line utility to gather and check data for building a release
+ */
+class Release extends splitbrain\phpcli\CLI
+{
+ // base URL to fetch raw files from the stable branch
+ protected $BASERAW = 'https://raw.githubusercontent.com/splitbrain/dokuwiki/stable/';
+
+ /** @inheritdoc */
+ public function __construct($autocatch = true)
+ {
+ parent::__construct($autocatch);
+
+ $this->error(print_r($_ENV, true));
+
+ // when running on a clone, use the correct base URL
+ $repo = getenv('GITHUB_REPOSITORY');
+ if ($repo) {
+ $this->BASERAW = 'https://raw.githubusercontent.com/' . $repo . '/stable/';
+ }
+ }
+
+
+ protected function setup(\splitbrain\phpcli\Options $options)
+ {
+ $options->setHelp('This tool is used to gather and check data for building a release');
+
+ $options->registerCommand('new', 'Get environment for creating a new release');
+ $options->registerOption('type', 'The type of release to build', null, 'stable|hotfix|rc', 'new');
+ $options->registerOption('date', 'The date to use for the version. Defaults to today', null, 'YYYY-MM-DD', 'new');
+ $options->registerOption('name', 'The codename to use for the version. Defaults to the last used one', null, 'codename', 'new');
+
+ $options->registerCommand('current', 'Get environment of the current release');
+ }
+
+ protected function main(\splitbrain\phpcli\Options $options)
+ {
+ switch ($options->getCmd()) {
+ case 'new':
+ $this->prepareNewEnvironment($options);
+ break;
+ case 'current':
+ $this->prepareCurrentEnvironment($options);
+ break;
+ default:
+ echo $options->help();
+ }
+ }
+
+ /**
+ * Prepare environment for the current branch
+ */
+ protected function prepareCurrentEnvironment(\splitbrain\phpcli\Options $options)
+ {
+ $current = $this->getLocalVersion();
+ // we name files like the string in the VERSION file, with rc at the front
+ $current['file'] = ($current['type'] === 'rc' ? 'rc' : '') . $current['date'] . $current['hotfix'];
+
+ // output to be piped into GITHUB_ENV
+ foreach ($current as $k => $v) {
+ echo "current_$k=$v\n";
+ }
+ }
+
+ /**
+ * Prepare environment for creating a new release
+ */
+ protected function prepareNewEnvironment(\splitbrain\phpcli\Options $options)
+ {
+ $current = $this->getUpstreamVersion();
+
+ // continue if we want to create a new release
+ $next = [
+ 'type' => $options->getOpt('type'),
+ 'date' => $options->getOpt('date'),
+ 'codename' => $options->getOpt('name'),
+ 'hotfix' => '',
+ ];
+ if (!$next['type']) $next['type'] = 'stable';
+ if (!$next['date']) $next['date'] = date('Y-m-d');
+ if (!$next['codename']) $next['codename'] = $current['codename'];
+ $next['codename'] = ucwords(strtolower($next['codename']));
+
+ if (!in_array($next['type'], ['stable', 'hotfix', 'rc'])) {
+ throw new \splitbrain\phpcli\Exception('Invalid release type, use release or rc');
+ }
+
+ if ($next['type'] === 'hotfix') {
+ $next['update'] = floatval($current['update']) + 0.1;
+ $next['codename'] = $current['codename'];
+ $next['date'] = $current['date'];
+ $next['hotfix'] = $this->increaseHotfix($current['hotfix']);
+ } else {
+ $next['update'] = intval($current['update']) + 1;
+ }
+
+ if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $next['date'])) {
+ throw new \splitbrain\phpcli\Exception('Invalid date format, use YYYY-MM-DD');
+ }
+
+ if ($current['date'] > $next['date']) {
+ throw new \splitbrain\phpcli\Exception('Date must be equal or later than the last release');
+ }
+
+ if ($current['type'] === 'rc' && $next['type'] === 'hotfix') {
+ throw new \splitbrain\phpcli\Exception(
+ 'Cannot create hotfixes for release candidates, create a new RC instead'
+ );
+ }
+
+ if ($current['type'] === 'stable' && $next['type'] !== 'hotfix' && $current['codename'] === $next['codename']) {
+ throw new \splitbrain\phpcli\Exception('Codename must be different from the last release');
+ }
+
+ $next['version'] = $next['date'] . ($next['type'] === 'rc' ? 'rc' : $next['hotfix']);
+ $next['raw'] = ($next['type'] === 'rc' ? 'rc' : '') .
+ $next['date'] .
+ $next['hotfix'] .
+ ' "' . $next['codename'] . '"';
+
+ // output to be piped into GITHUB_ENV
+ foreach ($current as $k => $v) {
+ echo "current_$k=$v\n";
+ }
+ foreach ($next as $k => $v) {
+ echo "next_$k=$v\n";
+ }
+ }
+
+ /**
+ * Get current version info from local VERSION file
+ *
+ * @return string[]
+ */
+ protected function getLocalVersion()
+ {
+ $versioninfo = \dokuwiki\Info::parseVersionString(trim(file_get_contents('VERSION')));
+ $doku = file_get_contents('doku.php');
+ if (!preg_match('/\$updateVersion = "(\d+(\.\d+)?)";/', $doku, $m)) {
+ throw new \Exception('Could not find $updateVersion in doku.php');
+ }
+ $versioninfo['update'] = floatval($m[1]);
+ return $versioninfo;
+ }
+
+ /**
+ * Get current version info from stable branch
+ *
+ * @return string[]
+ * @throws Exception
+ */
+ protected function getUpstreamVersion()
+ {
+ // basic version info
+ $versioninfo = \dokuwiki\Info::parseVersionString(trim(file_get_contents($this->BASERAW . 'VERSION')));
+
+ // update version grepped from the doku.php file
+ $doku = file_get_contents($this->BASERAW . 'doku.php');
+ if (!preg_match('/\$updateVersion = "(\d+(\.\d+)?)";/', $doku, $m)) {
+ throw new \Exception('Could not find $updateVersion in doku.php');
+ }
+ $versioninfo['update'] = floatval($m[1]);
+
+ return $versioninfo;
+ }
+
+ /**
+ * Increase the hotfix letter
+ *
+ * (max 26 hotfixes)
+ *
+ * @param string $hotfix
+ * @return string
+ */
+ protected function increaseHotfix($hotfix)
+ {
+ if (empty($hotfix)) return 'a';
+ return substr($hotfix, 0, -1) . chr(ord($hotfix) + 1);
+ }
+}
+
+(new Release())->run();
diff --git a/.github/version.php b/.github/version.php
deleted file mode 100644
index c85a81910..000000000
--- a/.github/version.php
+++ /dev/null
@@ -1,52 +0,0 @@
-<?php
-/**
- * Command line tool to check proper version strings
- *
- * Expects a tag as first argument. Used in release action to ensure proper formats
- * in VERSION file and git tag.
- */
-
-if (!isset($argv[1])) {
- echo "::error::No git tag given, this action should not have run\n";
- exit(1);
-}
-$TAG = $argv[1];
-$TAG = str_replace('refs/tags/', '', $TAG);
-
-if (!file_exists(__DIR__ . '/../VERSION')) {
- echo "::error::No VERSION file found\n";
- exit(1);
-}
-$FILE = trim(file_get_contents(__DIR__ . '/../VERSION'));
-$FILE = explode(' ', $FILE)[0];
-
-
-if(!preg_match('/^release_(stable|candidate)_((\d{4})-(\d{2})-(\d{2})([a-z])?)$/', $TAG, $m)) {
- echo "::error::Git tag does not match expected format: $TAG\n";
- exit(1);
-} else {
- $TAGTYPE = $m[1];
- $TAGVERSION = $m[2];
-}
-
-if(!preg_match('/^(rc)?((\d{4})-(\d{2})-(\d{2})([a-z])?)$/', $FILE, $m)) {
- echo "::error::VERSION file does not match expected format: $FILE\n";
- exit(1);
-} else {
- $FILETYPE = $m[1] == 'rc' ? 'candidate' : 'stable';
- $FILEVERSION = $m[2];
- $TGZVERSION = $m[0];
-}
-
-if($TAGTYPE !== $FILETYPE) {
- echo "::error::Type of release mismatches between git tag and VERSION file: $TAGTYPE <-> $FILETYPE\n";
- exit(1);
-}
-
-if($TAGVERSION !== $FILEVERSION) {
- echo "::error::Version date mismatches between git tag and VERSION file: $TAGVERSION <-> $FILEVERSION\n";
- exit(1);
-}
-
-// still here? all good, export Version
-echo "::set-output name=VERSION::$TGZVERSION\n";
diff --git a/.github/workflows/deletedFiles.yml b/.github/workflows/deletedFiles.yml
new file mode 100644
index 000000000..885c98190
--- /dev/null
+++ b/.github/workflows/deletedFiles.yml
@@ -0,0 +1,39 @@
+# This workflow updates the list of deleted files based on the recent changes and creates a pull request.
+# It compares the current master with the stable branch and adds all deleted files to the data/deleted.files file
+# unless they are already listed there or are excluded from the release archives (export-ignore in .gitattributes).
+
+name: "Update deleted files"
+on:
+ push:
+ branches:
+ - master
+
+jobs:
+ update:
+ name: Update deleted files
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v3
+ with:
+ fetch-depth: 0
+
+ - name: Update deleted files
+ run: |
+ for F in $(git diff origin/stable..HEAD --summary | awk '/^ delete/ && $4 !~ /^(VERSION)/ {print $4}'); do
+ if grep -q "^$F export-ignore" .gitattributes; then
+ continue
+ fi
+ if grep -q "^$F" data/deleted.files; then
+ continue
+ fi
+ echo "$F" >> data/deleted.files
+ done
+
+ - name: Create Pull Request
+ uses: peter-evans/create-pull-request@v4
+ with:
+ commit-message: "Update deleted files"
+ title: "Update deleted files"
+ body: "This updates the list of deleted files based on the recent changes."
+ delete-branch: true
diff --git a/.github/workflows/release-build.yml b/.github/workflows/release-build.yml
new file mode 100644
index 000000000..77eeb2916
--- /dev/null
+++ b/.github/workflows/release-build.yml
@@ -0,0 +1,106 @@
+# This workflow creates a new tag, builds the release archives and uploads them to GitHub and our server
+# It is triggered by pushing to the stable branch, either manually or by merging a PR created by the
+# release-preparation workflow
+
+name: "Release: Tag, Build & Deploy"
+on:
+ push:
+ branches:
+ - stable
+
+jobs:
+
+ tag:
+ name: Tag Release
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v3
+
+ - name: Prepare Environment
+ run: |
+ php .github/release.php current >> $GITHUB_ENV
+
+ - name: Check if a tag already exists
+ run: |
+ if git rev-parse "release-${{ env.current_version }}" >/dev/null 2>&1; then
+ echo "::error::Tag already exists, be sure to update the VERSION file for a hotfix"
+ exit 1
+ fi
+
+ - name: Create tag
+ uses: actions/github-script@v6
+ with:
+ # a privileged token is needed here to create the (protected) tag
+ github-token: ${{ secrets.RELEASE_TOKEN }}
+ script: |
+ const {current_version} = process.env;
+ github.rest.git.createRef({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ ref: `refs/tags/release-${current_version}`,
+ sha: context.sha
+ });
+
+ build:
+ name: Build Release
+ needs: tag
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v3
+
+ - name: Prepare Environment
+ run: |
+ php .github/release.php current >> $GITHUB_ENV
+
+ - name: Build Archives
+ run: |
+ for F in $(awk '/export-ignore/{print $1}' .gitattributes); do
+ rm -rf $F
+ done
+ mkdir -p data/pages/playground
+ echo "====== PlayGround ======" > data/pages/playground/playground.txt
+ cd ..
+ mv ${{ github.event.repository.name }} "dokuwiki-${{ env.current_file }}"
+ tar -czvf "dokuwiki-${{ env.current_file }}.tgz" dokuwiki-${{ env.current_file }}
+ zip -r "dokuwiki-${{ env.current_file }}.zip" dokuwiki-${{ env.current_file }}
+ rm -rf "dokuwiki-${{ env.current_file }}"
+ mkdir ${{ github.event.repository.name }}
+ mv "dokuwiki-${{ env.current_version }}.tgz" ${{ github.event.repository.name }}/
+ mv "dokuwiki-${{ env.current_version }}.zip" ${{ github.event.repository.name }}/
+
+ - name: Release to Github
+ id: release
+ uses: softprops/action-gh-release@v1
+ with:
+ name: DokuWiki ${{ env.current_raw }} [${{ env.current_update }}]
+ tag_name: release-${{ env.current_version }}
+ files: |
+ dokuwiki-${{ env.current_file }}.tgz
+ dokuwiki-${{ env.current_file }}.zip
+ outputs:
+ version: ${{ env.current_version }}
+ file: ${{ env.current_file }}
+ url: ${{ steps.release.outputs.url }}
+
+ deploy:
+ name: Deploy Release
+ needs: build
+ runs-on: ubuntu-latest
+ steps:
+ - name: Download
+ run: |
+ wget ${{ needs.build.outputs.url }}/dokuwiki-${{ needs.build.outputs.file }}.tgz
+
+ - name: Setup SSH Key
+ uses: shimataro/ssh-key-action@v2
+ with:
+ key: ${{ secrets.SSH_PRIVATE_KEY }}
+ # generate with ssh-keyscan -H <server>
+ known_hosts: ${{ secrets.SSH_KNOWN_HOSTS }}
+
+ - name: Deploy to Server
+ run: |
+ scp "dokuwiki-${{ needs.build.outputs.file }}.tgz" ${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST }}:htdocs/src/dokuwiki/
+ ssh ${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST }} "cd htdocs/src/dokuwiki/ && tar -xzvf dokuwiki-${{ needs.build.outputs.file }}.tgz"
diff --git a/.github/workflows/release-preparation.yml b/.github/workflows/release-preparation.yml
new file mode 100644
index 000000000..703b98458
--- /dev/null
+++ b/.github/workflows/release-preparation.yml
@@ -0,0 +1,102 @@
+# This workflow is triggered manually and prepares a new release by creating a pull request
+# All needed info is provided by the user in the workflow_dispatch dialog
+#
+# When the pull request is merged, the release-build workflow will be triggered automatically
+
+name: "Release: Preparation 🚀"
+on:
+ workflow_dispatch:
+ inputs:
+ type:
+ description: 'What type of release is this?'
+ required: true
+ default: 'stable'
+ type: choice
+ options:
+ - stable
+ - hotfix
+ - rc
+ codename:
+ description: 'The codename for this release, empty for same as last'
+ required: false
+ version:
+ description: 'The version date YYYY-MM-DD, empty for today'
+ required: false
+
+jobs:
+ create:
+ name: Prepare Pull Request
+ runs-on: ubuntu-latest
+ steps:
+ - name: Fail if branch is not master
+ if: github.ref != 'refs/heads/master'
+ run: |
+ echo "::error::This workflow should only be triggered on master"
+ exit 1
+
+ - name: Checkout
+ uses: actions/checkout@v3
+ with:
+ fetch-depth: 0
+
+ - name: Set git identity
+ run: |
+ git config --global user.name "${{ github.actor }}"
+ git config --global user.email "${{ github.actor }}@users.noreply.github.com"
+
+ - name: Prepare Environment
+ run: |
+ php .github/release.php new \
+ --date "${{ inputs.version }}" \
+ --name "${{ inputs.codename }}" \
+ --type "${{ inputs.type }}" \
+ >> $GITHUB_ENV
+
+ - name: Check if a tag of the new release already exists
+ run: |
+ if git rev-parse "release-${{ env.next_version }}" >/dev/null 2>&1; then
+ echo "::error::Tag already exists, you may need to build a hotfix instead"
+ exit 1
+ fi
+
+ - name: Create merge commit with version info
+ run: |
+ git merge -s ours origin/stable
+ echo '${{ env.next_raw }}' > VERSION
+ git add VERSION
+ git commit --amend -m 'Release preparations for ${{ env.next_raw }}'
+ git log -1
+ git log origin/stable..master --oneline
+ git checkout -B auto-${{ env.next_version }}
+ git push --set-upstream origin auto-${{ env.next_version }}
+
+ - name: Create pull request
+ uses: repo-sync/pull-request@v2
+ with:
+ source_branch: auto-${{ env.next_version }}
+ destination_branch: stable
+ pr_title: Release Preparations for ${{ env.next_raw }}
+ pr_body: |
+ With accepting this PR, a the stable branch will be updated and the whole release and
+ deployment process will be triggered.
+
+ If you're not happy with the contents of this PR, please close it, fix stuff and trigger
+ the workflow again.
+
+ * ${{ env.current_raw }} -> ${{ env.next_raw }}
+ * Update Version ${{ env.current_update }} -> ${{ env.next_update }}
+
+ Before merging this PR, make sure that:
+
+ - [ ] Ensure all tests pass
+ - [ ] If this is a new stable release, make sure you merged `stable` into `old-stable` first
+ - [ ] Check that a meaningful [changelog](https://www.dokuwiki.org/changes) exists
+
+ After merging, the release workflow will be triggered automatically.
+
+ After this is done, you need to do the following things manually:
+
+ - [ ] Update the [version symlinks](https://download.dokuwiki.org/admin/)
+ - [ ] Update the update message system
+ - [ ] Announce the release on the mailing list, forum, IRC, social media, etc.
+
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
deleted file mode 100644
index 7c86c2835..000000000
--- a/.github/workflows/release.yml
+++ /dev/null
@@ -1,53 +0,0 @@
-name: Build and Publish
-on:
- push:
- tags:
- - '*'
-
-permissions:
- contents: read # to fetch code (actions/checkout)
-
-jobs:
- deploy:
- runs-on: ubuntu-latest
- steps:
- - name: Checkout
- uses: actions/checkout@v3
-
- - name: Setup PHP
- uses: shivammathur/setup-php@v2
- with:
- php-version: 7.4
-
- - name: Get the version
- id: get_version
- run: php .github/version.php "${GITHUB_REF}"
-
- - name: Build TGZ
- run: |
- rm -rf .gitignore
- rm -rf .git
- rm -rf .github
- rm -rf .gitattributes
- rm -rf _test
- rm -f .editorconfig
- mkdir -p data/pages/playground
- echo "====== PlayGround ======" > data/pages/playground/playground.txt
- cd ..
- mv dokuwiki "dokuwiki-${{ steps.get_version.outputs.VERSION }}"
- tar -czvf "dokuwiki-${{ steps.get_version.outputs.VERSION }}.tgz" dokuwiki-${{ steps.get_version.outputs.VERSION }}
- rm -rf "dokuwiki-${{ steps.get_version.outputs.VERSION }}"
- mkdir dokuwiki
- mv "dokuwiki-${{ steps.get_version.outputs.VERSION }}.tgz" dokuwiki/
-
- - name: Setup SSH Key
- uses: shimataro/ssh-key-action@v2
- with:
- key: ${{ secrets.SSH_PRIVATE_KEY }}
- # generate with ssh-keyscan -H <server>
- known_hosts: ${{ secrets.SSH_KNOWN_HOSTS }}
-
- - name: Deploy to Server
- run: |
- scp "dokuwiki-${{ steps.get_version.outputs.VERSION }}.tgz" ${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST }}:htdocs/src/dokuwiki/
- ssh ${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST }} "cd htdocs/src/dokuwiki/ && tar -xzvf dokuwiki-${{ steps.get_version.outputs.VERSION }}.tgz"