Skip to content

Commit

Permalink
chore: Migrate release process from custom shell script to Makefile
Browse files Browse the repository at this point in the history
- No longer build tarball in vagrant
- No longer use local clone before building tarball
- Exclude more files to keep release tarball clean
- Migrate large parts of shell script to Makefile target
- Keep using curl API calls for Github release as the `gh` commandline
  tool doesn't have multi-account support.
  • Loading branch information
doobry-systemli committed Nov 30, 2024
1 parent 859991e commit f8e56fd
Show file tree
Hide file tree
Showing 4 changed files with 118 additions and 89 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# 3.10.1 (2024.11.20)

* Migrate large parts of release process to Makefile

# 3.10.0 (2024.11.10)

* Upgrade to Symfony 6.4.14 (#659)
Expand Down
125 changes: 100 additions & 25 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
GIT_VERSION := $(shell git --no-pager describe --tags --always)
GIT_COMMIT := $(shell git rev-parse --verify HEAD)
GIT_DATE := $(firstword $(shell git --no-pager show --date=iso-strict --format="%ad" --name-only))
BUILD_DATE := $(shell date)
RELEASE_FILE := userli-${GIT_VERSION}.tar.gz
SHA_ALGORITHMS := 256 512
PWD_NAME := $(shell basename $(shell pwd))
APP_NAME:= $(notdir $(CURDIR))
RELEASE_FILE:=$(APP_NAME)-$(VERSION).tar.gz
SHA_ALGORITHMS:=256 512
PWD_NAME:=$(shell basename $(shell pwd))

clean:
git reset --hard
git clean --force -d
# Release variables
VERSION_CHANGELOG:=$(shell sed -ne 's/^\#\s\([0-9\.]\+\)\s.*$$/\1/p' CHANGELOG.md | head -n1)
DATE_CHANGELOG:=$(shell sed -n "s/# $(VERSION) (\(.*\))/\1/p" CHANGELOG.md)
TODAY:=$(shell date +%Y.%m.%d)

# Yarn tool
YARN:=$(shell command -v yarn)
ifndef YARN
YARN:=$(shell command -v yarnpkg)
endif

vendors:
composer install
Expand All @@ -28,34 +32,105 @@ security-check: vendors
bin/local-php-security-checker

integration: vendors lint
yarn
yarn encore dev
$(YARN)
$(YARN) encore dev
bin/behat -f progress

prepare:
mkdir -p build

release: clean prepare
APP_ENV=prod composer install --no-dev --ignore-platform-reqs
build-checks:
@if [ -n "$$(git status --porcelain)" ]; then \
echo "Git repo not clean!"; \
exit 1; \
fi
ifndef VERSION
$(error Missing $$VERSION)
endif
ifneq ($(VERSION),$(VERSION_CHANGELOG))
$(error Version missmatch between $$VERSION $(VERSION) version in CHANGELOG.md $(VERSION_CHANGELOG))
endif
ifneq ($(DATE_CHANGELOG),$(TODAY))
$(error Release date ($(DATE_CHANGELOG)) is not today ($(TODAY)))
endif

build: build-checks prepare
APP_ENV=prod composer install --no-dev --ignore-platform-reqs --no-scripts
APP_ENV=prod composer dump-autoload
yarn --pure-lockfile
yarn encore production
$(YARN) --pure-lockfile
$(YARN) encore production
# Create a release tarball
tar --exclude='${PWD_NAME}/.env.*' --exclude='${PWD_NAME}/.git*' \
--exclude='${PWD_NAME}/.*.yml' --exclude='${PWD_NAME}/behat.yml' \
--exclude='${PWD_NAME}/bin/github-release.sh' \
--exclude='${PWD_NAME}/build' --exclude='${PWD_NAME}/features' \
--exclude='${PWD_NAME}/Makefile' --exclude='${PWD_NAME}/node_modules' \
--exclude='${PWD_NAME}/phpunit.xml' --exclude='${PWD_NAME}/tests' \
--exclude='${PWD_NAME}/ansible' --exclude='${PWD_NAME}/var/cache/*' \
--exclude='${PWD_NAME}/var/log/*' --exclude='${PWD_NAME}/webpack.config.js' \
--exclude='${PWD_NAME}/yarn.lock' --exclude='${PWD_NAME}/Vagrantfile' \
tar --exclude='${PWD_NAME}/.env.*' \
--exclude='${PWD_NAME}/.git*' \
--exclude='${PWD_NAME}/.idea' \
--exclude='${PWD_NAME}/.php-cs-fixer.cache' \
--exclude='${PWD_NAME}/.phpunit*' \
--exclude='${PWD_NAME}/.vagrant' \
--exclude='${PWD_NAME}/.*.yml' \
--exclude='${PWD_NAME}/ansible' \
--exclude='${PWD_NAME}/behat.yml' \
--exclude='${PWD_NAME}/bin/behat*' \
--exclude='${PWD_NAME}/bin/crypt-gpg-pinentry' \
--exclude='${PWD_NAME}/bin/doctrine*' \
--exclude='${PWD_NAME}/bin/github-release.sh' \
--exclude='${PWD_NAME}/bin/local-php-security-checker' \
--exclude='${PWD_NAME}/bin/patch-type-declarations' \
--exclude='${PWD_NAME}/bin/php*' \
--exclude='${PWD_NAME}/bin/rector' \
--exclude='${PWD_NAME}/bin/simple-phpunit' \
--exclude='${PWD_NAME}/bin/sql-formatter' \
--exclude='${PWD_NAME}/bin/uaparser' \
--exclude='${PWD_NAME}/bin/var-dump-server' \
--exclude='${PWD_NAME}/bin/yaml-lint' \
--exclude='${PWD_NAME}/build' \
--exclude='${PWD_NAME}/composer.*' \
--exclude='${PWD_NAME}/features' \
--exclude='${PWD_NAME}/Makefile' \
--exclude='${PWD_NAME}/mkdocs.yml' \
--exclude='${PWD_NAME}/node_modules' \
--exclude='${PWD_NAME}/package.json' \
--exclude='${PWD_NAME}/phpunit.xml' \
--exclude='${PWD_NAME}/rector.php' \
--exclude='${PWD_NAME}/requirements.yml' \
--exclude='${PWD_NAME}/sonar-project.properties' \
--exclude='${PWD_NAME}/symfony.lock' \
--exclude='${PWD_NAME}/tests' \
--exclude='${PWD_NAME}/Vagrantfile' \
--exclude='${PWD_NAME}/var/cache/*' \
--exclude='${PWD_NAME}/var/db_test.sqlite' \
--exclude='${PWD_NAME}/var/log/*' \
--exclude='${PWD_NAME}/vendor/bin/.phpunit' \
--exclude='${PWD_NAME}/webpack.config.js' \
--exclude='${PWD_NAME}/yarn.lock' \
-czf build/${RELEASE_FILE} ../${PWD_NAME}
# Generate SHA hash sum files
for sha in ${SHA_ALGORITHMS}; do \
shasum -a "$${sha}" "build/${RELEASE_FILE}" >"build/${RELEASE_FILE}.sha$${sha}"; \
done

release-checks:
ifndef GITHUB_API_TOKEN
$(error Missing $$GITHUB_API_TOKEN)
endif
ifndef GPG_SIGN_KEY
$(error Missing $$GPG_SIGN_KEY)
endif
@if git tag | grep -qFx $(VERSION); then \
echo "Git tag already exists!"; \
echo "Delete it with 'git tag -d $(VERSION)'"; \
exit 1; \
fi

# Publish to Github
release: release-checks
# sign release tarball
gpg -u $(GPG_SIGN_KEY) --output "build/$(RELEASE_FILE).asc" --armor --detach-sign --batch --yes build/$(RELEASE_FILE)
# git tag and push
git tag $(VERSION) -m "Release $(VERSION)"
git push origin "refs/tags/$(VERSION)"
# create Github release via shell script (`gh` doesn't have multi-account support)
VERSION=$(VERSION) GITHUB_API_TOKEN=$(GITHUB_API_TOKEN) ./bin/github-release.sh

reset: clean
rm -f php_cs.cache
rm -rf node-modules
Expand Down
70 changes: 10 additions & 60 deletions bin/github-release.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,12 @@

set -e

# configuration
vagrant="yes" # run build process in vagrant

# idea taken from the following projects:
# * https://gist.github.com/stefanbuck/ce788fee19ab6eb0b4447a85fc99f447
# * https://github.com/NicoHood/gpgit

if [ -z "$1" ]; then
printf "Error: release version required as first argument\n" >&2
exit 1
fi

if [ -z "$GPG_SIGN_KEY" ]; then
printf "Error: environment variable \$GPG_SIGN_KEY needs to be set\n" >&2
if [ -z "$VERSION" ]; then
printf "Error: environment variable \$VERSION needs to be set\n" >&2
exit 1
fi

Expand All @@ -24,78 +16,36 @@ if [ -z "$GITHUB_API_TOKEN" ]; then
exit 1
fi

# set release variables
version="$1"
today="$(date +%Y.%m.%d)"

# set Github variables
gh_group="systemli"
gh_project="userli"
gh_api="https://api.github.com"
gh_repo="$gh_api/repos/${gh_group}/${gh_project}"
gh_tags="$gh_repo/releases/tags/$version"
gh_tags="$gh_repo/releases/tags/$VERSION"
gh_auth="Authorization: token $GITHUB_API_TOKEN"
curl_args="--location --remote-header-name --remote-name #"

# parse CHANGELOG.md
if ! grep -qx "# $version (.*)" CHANGELOG.md; then
printf "Error: Couldn't find section for version %s in CHANGELOG.md\n" "$version" >&2
exit 1
elif ! grep -qx "# $version ($today)" CHANGELOG.md; then
date="$(sed -n "s/# $version (\(.*\))/\1/p" CHANGELOG.md)"
printf "Error: Release date \"%s\" != \"%s\" (today) for version %s in CHANGELOG.md\n" "$date" "$today" "$version" >&2
exit 1
fi

gh_notes="$(awk "/^# $version/{flag=1; next} /^# [0-9]+/{flag=0} flag" CHANGELOG.md | grep '[^[:blank:]]' | awk -vORS='\\n' 1)"

# make a gpg-signed tag for the release
git tag --sign --message "Release $version" "$version"

# create release tarball
tarball="build/userli-$(git --no-pager describe --tags --always).tar.gz"
if [ "$vagrant" = "yes" ]; then
(vagrant up;
vagrant ssh -c "tempdir=\"\$(mktemp -d)\";
git clone /vagrant \"\$tempdir/userli-$version\";
make prepare;
(cd \"\$tempdir/userli-$version\";
make release;
cp -a build/userli* /vagrant/build/);
rm -r \"\$tempdir\"")
else
make release
fi
if [ ! -f "$tarball" ]; then
printf "Error: release tarball %s not created\n" "$tarball" >&2
exit 1
fi

# gpg-sign release tarball
gpg -u ${GPG_SIGN_KEY} --output "${tarball}.asc" --armor --detach-sign --batch --yes "$tarball"
gh_notes="$(awk "/^# $VERSION/{flag=1; next} /^# [0-9]+/{flag=0} flag" CHANGELOG.md | grep '[^[:blank:]]' | awk -vORS='\\n' 1)"

# validate token
curl --output /dev/null --silent --header "$auth" $gh_repo || { printf "Error: Invalid repo, token or network issue\n" >&2; exit 1; }

# push git tag
git push origin "refs/tags/${version}" >/dev/null

# create release on Github
api_json=$(printf '{"tag_name": "%s","target_commitish": "%s","name": "%s","body": "%s","draft": false,"prerelease": %s}' "$version" "main" "$version" "$gh_notes" "false")
api_json=$(printf '{"tag_name": "%s","target_commitish": "%s","name": "%s","body": "%s","draft": false,"prerelease": %s}' "$VERSION" "main" "$VERSION" "$gh_notes" "false")
gh_release="$(curl --silent --proto-redir https --data "$api_json" "$gh_repo/releases" -H "Accept: application/vnd.github.v3+json" -H "$gh_auth")"

# read asset tags
gh_response="$(curl --silent --header "$auth" "$gh_tags")"
gh_tag_response="$(curl --silent --header "$gh_auth" "$gh_tags")"

# get release id
eval $(printf "$gh_response" | grep -m 1 "id.:" | grep -w id | tr : = | tr -cd '[[:alnum:]]=')
[ "$id" ] || { printf "Error: Failed to get release id for tag: %s\n" "$version"; printf "%s\n" "$gh_response" | awk 'length($0)<100' >&2; exit 1; }
eval $(printf "$gh_tag_response" | grep -m 1 "id.:" | grep -w id | tr : = | tr -cd '[[:alnum:]]=')
[ "$id" ] || { printf "Error: Failed to get release id for tag: %s\n" "$VERSION"; printf "%s\n" "$gh_tag_response" | awk 'length($0)<100' >&2; exit 1; }

# upload to Github
for ext in "" ".asc" ".sha256" ".sha512"; do
gh_asset="https://uploads.github.com/repos/${gh_group}/${gh_project}/releases/${id}/assets?name=$(basename ${tarball}${ext})"
gh_asset="https://uploads.github.com/repos/${gh_group}/${gh_project}/releases/${id}/assets?name=userli-${VERSION}.tar.gz${ext}"
curl --silent --proto-redir https "$gh_asset" \
-H "Content-Type: application/octet-stream" \
-H "Accept: application/vnd.github.v3+json" \
-H "$gh_auth" --data-binary @"${tarball}${ext}"
-H "$gh_auth" --data-binary @"build/userli-${VERSION}.tar.gz${ext}"
done
8 changes: 4 additions & 4 deletions docs/development/release.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@
Release tarballs are the preferred way to install Userli. This page explains how to create them.
<!--more-->

First, you'll need a [Github API token](https://github.com/settings/tokens).
The token needs the following priviledges:
First, you need a [Github API token](https://github.com/settings/tokens).
The token needs the following privileges:

public_repo, repo:status, repo_deployment

Now, execute the following script. It will create a version tag, release and
Now, run the Makefile target `release`. It will create a version tag, Github release and
copy the info from `CHANGELOG.md` to the release info.

$ GITHUB_API_TOKEN=<token> GPG_SIGN_KEY="<key_id>" ./bin/github-release.sh <version>
$ VERSION=<version> GITHUB_API_TOKEN=<token> GPG_SIGN_KEY="<key_id>" make release

0 comments on commit f8e56fd

Please sign in to comment.