Skip to content
Draft
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
35 changes: 0 additions & 35 deletions .github/release_drafter.yaml

This file was deleted.

49 changes: 49 additions & 0 deletions .github/scripts/check_release_tag.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
#!/usr/bin/env bash
# Decide whether a release tag can be published.
#
# check_release_tag.sh <tag> [<branch>]
#
# Fails on a tag that is not vX.Y.Z, on a lightweight tag, and on a tag whose
# commit is not on <branch> (default: master). Succeeds without publishing on a
# tag whose message has a "Retroactive:" line: such tags record past releases.
# Writes publish=true or publish=false to $GITHUB_OUTPUT when it is set.
set -euo pipefail

tag="${1:?usage: check_release_tag.sh <tag> [<branch>]}"
branch="${2:-master}"
output="${GITHUB_OUTPUT:-/dev/null}"

fail() {
echo "::error::$1" >&2
shift
for line in "$@"; do echo "$line" >&2; done
exit 1
}

[[ "$tag" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]] || fail "$tag is not a release tag (vX.Y.Z)."

# actions/checkout can store an annotated tag as a lightweight ref, so fetch the
# tag object itself, and the branch the tag must be on.
git fetch --quiet --force origin "refs/tags/$tag:refs/tags/$tag"
git fetch --quiet --no-tags origin "+refs/heads/$branch:refs/remotes/origin/$branch"

if [[ "$(git cat-file -t "refs/tags/$tag")" != "tag" ]]; then
fail "$tag is a lightweight tag. A release tag is annotated, with the release notes as its message:" \
" git tag -d $tag" \
" git tag -a $tag <commit> --cleanup=verbatim -F notes.md" \
" git push --force origin refs/tags/$tag"
fi

commit="$(git rev-parse "refs/tags/$tag^{commit}")"
if ! git merge-base --is-ancestor "$commit" "refs/remotes/origin/$branch"; then
fail "$tag points to $commit, which is not on $branch."
fi

if git for-each-ref "refs/tags/$tag" --format='%(contents)' | grep -q '^Retroactive:'; then
echo "::notice::$tag records a past release (its message has a Retroactive: line); nothing to publish."
echo "publish=false" >> "$output"
exit 0
fi

echo "$tag is an annotated tag on $branch; publishing."
echo "publish=true" >> "$output"
31 changes: 0 additions & 31 deletions .github/workflows/draft_release.yaml

This file was deleted.

2 changes: 2 additions & 0 deletions .github/workflows/lint.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ on:
- "**" # every branch
- "!gh-pages" # exclude gh-pages branch
- "!stage*" # exclude branches beginning with stage
# The release workflow runs this as its gate.
workflow_call:
jobs:
lint:
runs-on: ubuntu-latest
Expand Down
108 changes: 0 additions & 108 deletions .github/workflows/post_draft_release_published.yaml

This file was deleted.

134 changes: 134 additions & 0 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
name: Release

# A release is an annotated tag vX.Y.Z on master whose message is the release
# notes. Pushing it runs the test suite, builds the package at the tag (the
# version comes from the tag through hatch-vcs), publishes it to PyPI, and
# creates the GitHub release with the tag's message as its notes.
#
# To publish an existing tag to TestPyPI instead, run this workflow by hand from
# that tag ("Use workflow from" -> Tags) with testpypi checked.
on:
push:
tags:
- "v[0-9]+.[0-9]+.[0-9]+"
workflow_dispatch:
inputs:
testpypi:
description: "Publish to TestPyPI, without a GitHub release or notification"
type: boolean
default: true

permissions:
contents: read

concurrency:
group: release-${{ github.ref_name }}
cancel-in-progress: false

jobs:
check:
runs-on: ubuntu-latest
outputs:
publish: ${{ steps.check.outputs.publish }}
steps:
- name: Require a tag
if: github.ref_type != 'tag'
run: |
echo "::error::Run this workflow from a release tag (vX.Y.Z), not from a branch."
exit 1
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Check the tag
id: check
run: .github/scripts/check_release_tag.sh "$GITHUB_REF_NAME" master

lint:
needs: check
if: needs.check.outputs.publish == 'true'
uses: ./.github/workflows/lint.yaml

test:
needs: check
if: needs.check.outputs.publish == 'true'
uses: ./.github/workflows/test.yaml

publish:
needs: [check, lint, test]
if: needs.check.outputs.publish == 'true'
runs-on: ubuntu-latest
permissions:
# write permission is required to create the GitHub release
contents: write
env:
TAG: ${{ github.ref_name }}
TEST_PYPI: ${{ github.event_name == 'workflow_dispatch' && inputs.testpypi }}
TWINE_USERNAME: ${{ secrets.twine_username }}
TWINE_PASSWORD: ${{ secrets.twine_password }}
TWINE_TEST_USERNAME: ${{ secrets.twine_test_username }}
TWINE_TEST_PASSWORD: ${{ secrets.twine_test_password }}
steps:
- uses: actions/checkout@v4
with:
# Full history and tags, so hatch-vcs derives the version from the tag.
fetch-depth: 0
- name: Fetch the tag object
# actions/checkout can store an annotated tag as a lightweight ref.
run: git fetch --force origin "refs/tags/$TAG:refs/tags/$TAG"
- name: Set up Python
uses: actions/setup-python@v5
with:
# The oldest supported Python; the build is pure Python either way.
python-version: "3.10"
- name: Build
run: |
python -m pip install build
python -m build .
- name: Check the built version
run: |
version="${TAG#v}"
for file in dist/*; do
case "$(basename "$file")" in
"datajoint-$version-"*.whl | "datajoint-$version.tar.gz") ;;
*) echo "::error::$file does not carry version $version"; exit 1 ;;
esac
done
- name: Publish to PyPI
run: |
python -m pip install --upgrade twine
if [[ "$TEST_PYPI" == "true" ]]; then
export TWINE_REPOSITORY="testpypi"
export TWINE_USERNAME="$TWINE_TEST_USERNAME"
export TWINE_PASSWORD="$TWINE_TEST_PASSWORD"
else
export TWINE_REPOSITORY="pypi"
fi
# --skip-existing keeps re-runs idempotent.
python -m twine upload --skip-existing dist/*
- name: Create the GitHub release
if: env.TEST_PYPI != 'true'
env:
GH_TOKEN: ${{ github.token }}
run: |
git for-each-ref "refs/tags/$TAG" --format='%(contents)' > "$RUNNER_TEMP/notes.md"
gh release create "$TAG" --verify-tag --title "DataJoint Library ${TAG#v}" \
--notes-file "$RUNNER_TEMP/notes.md" dist/*
- name: Post release notification to Slack
if: env.TEST_PYPI != 'true'
uses: slackapi/slack-github-action@v2.0.0
with:
webhook: ${{ secrets.SLACK_WEBHOOK_URL }}
webhook-type: incoming-webhook
payload: |
{
"text": "*New Release Published!* :tada: \n*Repository:* ${{ github.repository }}\n*Version:* ${{ github.ref_name }}\n*URL:* ${{ github.server_url }}/${{ github.repository }}/releases/tag/${{ github.ref_name }}",
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "*New Release Published!* :tada:\n*Repository:* ${{ github.repository }}\n*Version:* ${{ github.ref_name }}\n*URL:* <${{ github.server_url }}/${{ github.repository }}/releases/tag/${{ github.ref_name }}|View Release>"
}
}
]
}
3 changes: 3 additions & 0 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ on:
- "pixi.lock"
- ".github/workflows/test.yaml"

# The release workflow runs this as its gate.
workflow_call:

jobs:
test:
runs-on: ubuntu-latest
Expand Down
Loading
Loading