diff --git a/.github/pr_labeler.yaml b/.github/pr_labeler.yaml new file mode 100644 index 000000000..ab722839f --- /dev/null +++ b/.github/pr_labeler.yaml @@ -0,0 +1,25 @@ +# https://github.com/actions/labeler +breaking: +- head-branch: ['breaking', 'BREAKING'] +bug: +- head-branch: ['fix', 'FIX', 'bug', 'BUG'] +feature: +- head-branch: ['feat', 'FEAT'] +documentation: +- changed-files: + - any-glob-to-any-file: + - docs/** + - images/** + - README.md + - CHANGELOG.md + - CONTRIBUTING.md +enhancement: +- head-branch: ['enhance', 'improve', 'IMPR', 'DJEP'] +- changed-files: + - any-glob-to-any-file: + - '**' + - '!docs/**' + - '!images/**' + - '!README.md' + - '!CHANGELOG.md' + - '!CONTRIBUTING.md' diff --git a/.github/release_drafter.yaml b/.github/release_drafter.yaml new file mode 100644 index 000000000..b1602fa7d --- /dev/null +++ b/.github/release_drafter.yaml @@ -0,0 +1,35 @@ +version-resolver: + major: + labels: + - 'breaking' + minor: + labels: + - 'feature' + patch: + labels: + - 'documentation' + - 'enhancement' + - 'bug' +name-template: '$RESOLVED_VERSION' +tag-template: 'v$RESOLVED_VERSION' +categories: + - title: '💥 Breaking Changes' + labels: + - 'breaking' + - title: '🚀 Features' + labels: + - 'feature' + - title: '⚡️ Enhancements' + labels: + - 'enhancement' + - title: '🐛 Bug Fixes' + labels: + - 'bug' + - title: '📝 Documentation' + label: 'documentation' +change-template: '- $TITLE(#$NUMBER)@$AUTHOR' +change-title-escapes: '\<*_&' # You can add # and @ to disable mentions, and add ` to disable code blocks. +template: | + $CHANGES + + **Full Changelog**: https://github.com/$OWNER/$REPOSITORY/compare/$PREVIOUS_TAG...v$RESOLVED_VERSION \ No newline at end of file diff --git a/.github/workflows/docs.yaml b/.github/workflows/docs.yaml index f434d63d7..5f2b103cb 100644 --- a/.github/workflows/docs.yaml +++ b/.github/workflows/docs.yaml @@ -1,7 +1,10 @@ -name: Manual docs release +name: Docs release on: + # manually trigger workflow_dispatch: + # been called by other workflows workflow_call: + jobs: publish-docs: runs-on: ubuntu-latest diff --git a/.github/workflows/draft_release.yaml b/.github/workflows/draft_release.yaml new file mode 100644 index 000000000..e617aa0e8 --- /dev/null +++ b/.github/workflows/draft_release.yaml @@ -0,0 +1,31 @@ +name: Manual Draft Release +on: + workflow_dispatch: + inputs: + testpypi: + description: 'Release to TestPyPI then skip following' + default: 'false' + type: choice + options: + - 'true' + - 'false' +jobs: + build-release: + permissions: + # write permission is required to create a github release + contents: write + # write permission is required for autolabeler + # otherwise, read permission is required at least + pull-requests: read + runs-on: ubuntu-latest + steps: + # Drafts your next Release notes as Pull Requests are merged into "master" + - name: Draft release notes + id: create_gh_release + uses: release-drafter/release-drafter@v6 + with: + config-name: release_drafter.yaml + disable-autolabeler: true + name: ${{ github.event.inputs.testpypi == 'true' && 'Test $RESOLVED_VERSION' || 'Release $RESOLVED_VERSION' }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/label_prs.yaml b/.github/workflows/label_prs.yaml new file mode 100644 index 000000000..9797a956f --- /dev/null +++ b/.github/workflows/label_prs.yaml @@ -0,0 +1,18 @@ +# https://github.com/actions/labeler +name: "Pull Request Labeler" +on: +- pull_request_target + +jobs: + labeler: + permissions: + contents: read + pull-requests: write + runs-on: ubuntu-latest + steps: + - uses: actions/labeler@v5 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + configuration-path: .github/pr_labeler.yaml + sync-labels: true + dot: true \ No newline at end of file diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index 1b012f1f9..62468a983 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -1,4 +1,4 @@ -name: Test +name: Lint on: push: branches: diff --git a/.github/workflows/post_draft_release_published.yaml b/.github/workflows/post_draft_release_published.yaml new file mode 100644 index 000000000..3daac2f5d --- /dev/null +++ b/.github/workflows/post_draft_release_published.yaml @@ -0,0 +1,154 @@ +name: Post Draft Release Published + +on: + # Once draft release is released, trigger the docs release + release: + types: + ## pre-release and stable release + #- published + ## stable release only + - released +run-name: Post ${{ github.event.release.name }} + +jobs: + call-publish-docs: + uses: ./.github/workflows/docs.yaml + pypi-release: + permissions: + # write permission is required to update version.py + contents: write + pull-requests: write + # Use the oldest supported version to build, just in case there are issues + # for our case, this doesn't matter that much, since the build is for 3.x + strategy: + matrix: + include: + - py_ver: "3.9" + runs-on: ubuntu-latest + env: + PY_VER: ${{matrix.py_ver}} + 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: + - name: Checkout repository + uses: actions/checkout@v4 + with: + token: ${{ secrets.GITHUB_TOKEN }} + # new release needs the updated version.py + - name: Update version.py + run: | + VERSION=$(echo "${{ github.event.release.name }}" | grep -oP '\d+\.\d+\.\d+') + sed -i "s/^__version__ = .*/__version__ = \"$VERSION\"/" datajoint/version.py + cat datajoint/version.py + # Commit the changes + BRANCH_NAME="update-version-$VERSION" + git switch -c $BRANCH_NAME + git config --global user.name "github-actions" + git config --global user.email "github-actions@github.com" + git add datajoint/version.py + git commit -m "Update version.py to $VERSION" + echo "BRANCH_NAME=$BRANCH_NAME" >> $GITHUB_ENV + - name: Update README.md badge + run: | + # commits since the last release + NEW_HREF="https://github.com/datajoint/datajoint-python/compare/${{ github.event.release.tag_name }}...master" + NEW_SRC="https://img.shields.io/github/commits-since/datajoint/datajoint-python/${{ github.event.release.tag_name }}?color=red" + # Update href in the tag + sed -i 's|\(]*href="\)[^"]*\(".*\)|\1'"$NEW_HREF"'\2|' README.md + # Update src in the tag + sed -i 's|\(]*src="\)[^"]*\(".*\)|\1'"$NEW_SRC"'\2|' README.md + git add README.md + git commit -m "Update README.md badge to ${{ github.event.release.tag_name }}" + - name: Set up Python ${{matrix.py_ver}} + uses: actions/setup-python@v5 + with: + python-version: ${{matrix.py_ver}} + # Merging build and release steps just for the simplicity, + # since datajoint-python doesn't have platform specific dependencies or binaries, + # and the build process is fairly fast, so removed upload/download artifacts + - name: Build package + id: build + run: | + python -m pip install build + python -m build . + echo "DJ_WHEEL_PATH=$(ls dist/datajoint-*.whl)" >> $GITHUB_ENV + echo "DJ_SDIST_PATH=$(ls dist/datajoint-*.tar.gz)" >> $GITHUB_ENV + echo "NEW_VERSION=${{github.event.release.resolved_version}}" >> $GITHUB_ENV + - name: Publish package + id: publish + env: + RELEASE_NAME: ${{ github.event.release.name }} + run: | + export HOST_UID=$(id -u) + if [[ "$RELEASE_NAME" =~ ^Test ]]; then + LATEST_PYPI=$(curl -s https://test.pypi.org/pypi/datajoint/json | jq -r '.info.version') + echo "TEST_PYPI=true" >> $GITHUB_ENV + export TWINE_REPOSITORY="testpypi" + export TWINE_USERNAME=${TWINE_TEST_USERNAME} + export TWINE_PASSWORD=${TWINE_TEST_PASSWORD} + else + LATEST_PYPI=$(curl -s https://pypi.org/pypi/datajoint/json | jq -r '.info.version') + echo "TEST_PYPI=false" >> $GITHUB_ENV + export TWINE_REPOSITORY="pypi" + fi + # Check if the new version is different from the latest on PyPI, avoid re-uploading error + if [ "$NEW_VERSION" != "$LATEST_PYPI" ]; then + docker compose run --build --quiet-pull \ + -e TWINE_USERNAME=${TWINE_USERNAME} \ + -e TWINE_PASSWORD=${TWINE_PASSWORD} \ + -e TWINE_REPOSITORY=${TWINE_REPOSITORY} \ + app sh -c "pip install twine && python -m twine upload dist/*" + else + echo "::warning::Latest version $LATEST_PYPI on $TWINE_REPOSITORY is the new version $NEW_VERSION" + fi + # Upload package as release assets + - name: Upload pip wheel asset to release + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} + with: + upload_url: ${{github.event.release.upload_url}} + asset_path: ${{env.DJ_WHEEL_PATH}} + asset_name: pip-datajoint-${{ github.event.release.tag_name }}.whl + asset_content_type: application/zip + - name: Upload pip sdist asset to release + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} + with: + upload_url: ${{github.event.release.upload_url}} + asset_path: ${{env.DJ_SDIST_PATH}} + asset_name: pip-datajoint-${{ github.event.release.tag_name }}.tar.gz + asset_content_type: application/gzip + - name: Create Pull Request + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + git push origin ${{ env.BRANCH_NAME }} + gh pr create \ + --title "[github-actions]Update version.py to ${{ github.event.release.name }}" \ + --body "This PR updates \`version.py\` to match the latest release: ${{ github.event.release.name }}" \ + --base master \ + --head ${{ env.BRANCH_NAME }} \ + --reviewer dimitri-yatsenko,yambottle,ttngu207 + - name: Post release notification to Slack + if: ${{ env.TEST_PYPI == 'false' }} + 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.event.release.tag_name }}\n*URL:* ${{ github.event.release.html_url }}", + "blocks": [ + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": "*New Release Published!* :tada:\n*Repository:* ${{ github.repository }}\n*Version:* ${{ github.event.release.tag_name }}\n*URL:* <${{ github.event.release.html_url }}|View Release>" + } + } + ] + } diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml deleted file mode 100644 index ae848c310..000000000 --- a/.github/workflows/release.yaml +++ /dev/null @@ -1,127 +0,0 @@ -name: Release -on: - workflow_dispatch: - inputs: - testpypi: - description: 'Release to TestPyPI then skip following' - default: 'false' - type: choice - options: - - 'true' - - 'false' -jobs: - build-release: - runs-on: ubuntu-latest - # Use the oldest supported version to build, just in case there are issues - # for our case, this doesn't matter that much, since the build is for 3.x - strategy: - matrix: - include: - - py_ver: "3.9" - env: - PY_VER: ${{matrix.py_ver}} - TWINE_USERNAME: ${{secrets.twine_username}} - TWINE_PASSWORD: ${{secrets.twine_password}} - TWINE_TEST_USERNAME: ${{secrets.twine_test_username}} - TWINE_TEST_PASSWORD: ${{secrets.twine_test_password}} - TESTPYPI: ${{ github.event.inputs.testpypi }} - steps: - - uses: actions/checkout@v4 - - name: Set up Python ${{matrix.py_ver}} - uses: actions/setup-python@v5 - with: - python-version: ${{matrix.py_ver}} - # Merging build and release steps just for the simplicity, - # since datajoint-python doesn't have platform specific dependencies or binaries, - # and the build process is fairly fast, so removed upload/download artifacts - - name: Build package - run: | - python -m pip install build - python -m build . - echo "DJ_WHEEL_PATH=$(ls dist/datajoint-*.whl)" >> $GITHUB_ENV - echo "DJ_SDIST_PATH=$(ls dist/datajoint-*.tar.gz)" >> $GITHUB_ENV - - name: Publish package - run: | - export HOST_UID=$(id -u) - if [ "$TESTPYPI" == "true" ]; then - export TWINE_REPOSITORY="testpypi" - export TWINE_USERNAME=${TWINE_TEST_USERNAME} - export TWINE_PASSWORD=${TWINE_TEST_PASSWORD} - else - export TWINE_REPOSITORY="pypi" - fi - docker compose run --build --quiet-pull \ - -e TWINE_USERNAME=${TWINE_USERNAME} \ - -e TWINE_PASSWORD=${TWINE_PASSWORD} \ - -e TWINE_REPOSITORY=${TWINE_REPOSITORY} \ - app sh -c "pip install twine && python -m twine upload dist/*" - - name: Login to DockerHub - if: ${{ github.event.inputs.testpypi == 'false' }} - uses: docker/login-action@v3 - with: - username: ${{secrets.docker_username}} - password: ${{secrets.docker_password}} - - name: Publish image - if: ${{ github.event.inputs.testpypi == 'false' }} - run: | - IMAGE=$(docker images --filter "reference=datajoint/datajoint*" --format "{{.Repository}}") - TAG=$(docker images --filter "reference=datajoint/datajoint*" --format "{{.Tag}}") - docker push "${IMAGE}:${TAG}" - docker tag "${IMAGE}:${TAG}" "${IMAGE}:${TAG}-${GITHUB_SHA:0:7}" - docker push "${IMAGE}:${TAG}-${GITHUB_SHA:0:7}" - [ "$PY_VER" == "3.9" ] && [ "$DISTRO" == "debian" ] \ - && docker tag "${IMAGE}:${TAG}" "${IMAGE}:latest" \ - && docker push "${IMAGE}:latest" \ - || echo "skipping 'latest' tag..." - # Make sure all above release targets are done first, then make a GH release - - name: Make release notes - if: ${{ github.event.inputs.testpypi == 'false' }} - run: | - DJ_VERSION=$(grep -oP '\d+\.\d+\.\d+' datajoint/version.py) - RELEASE_BODY=$(python -c \ - 'print(open("./CHANGELOG.md").read().split("\n\n")[1].split("\n", 1)[1])' \ - ) - echo "DJ_VERSION=${DJ_VERSION}" >> $GITHUB_ENV - echo "RELEASE_BODY<> $GITHUB_ENV - echo "$RELEASE_BODY" >> $GITHUB_ENV - echo "EOF" >> $GITHUB_ENV - - name: Create GH release - if: ${{ github.event.inputs.testpypi == 'false' }} - id: create_gh_release - uses: actions/create-release@v1 - env: - GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} - with: - tag_name: ${{env.DJ_VERSION}} - release_name: Release ${{env.DJ_VERSION}} - body: ${{env.RELEASE_BODY}} - prerelease: false - draft: false - # Upload package as release assets - - name: Upload pip wheel asset to release - if: ${{ github.event.inputs.testpypi == 'false' }} - uses: actions/upload-release-asset@v1 - env: - GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} - with: - upload_url: ${{steps.create_gh_release.outputs.upload_url}} - asset_path: ${{env.DJ_WHEEL_PATH}} - asset_name: pip-datajoint-${{env.DJ_VERSION}}.whl - asset_content_type: application/zip - - name: Upload pip sdist asset to release - if: ${{ github.event.inputs.testpypi == 'false' }} - uses: actions/upload-release-asset@v1 - env: - GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} - with: - upload_url: ${{steps.create_gh_release.outputs.upload_url}} - asset_path: ${{env.DJ_SDIST_PATH}} - asset_name: pip-datajoint-${{env.DJ_VERSION}}.tar.gz - asset_content_type: application/gzip - # only release docs when a release is published - call-publish-docs: - if: ${{ github.event.inputs.testpypi == 'false' }} - needs: build-release - runs-on: ubuntu-latest - steps: - - uses: ./.github/workflows/docs.yaml diff --git a/README.md b/README.md index 3d3efe6e3..5fe32cb9c 100644 --- a/README.md +++ b/README.md @@ -30,16 +30,32 @@ Since Release - - commit since last release + + commit since last release Test Status - - test status + + test status + + + + + Release Status + + + release status + + + + + Doc Status + + + doc status diff --git a/datajoint/version.py b/datajoint/version.py index cc1d88710..c980ad0d0 100644 --- a/datajoint/version.py +++ b/datajoint/version.py @@ -1,3 +1,6 @@ -__version__ = "0.14.4" +# version bump auto managed by Github Actions: +# label_prs.yaml(prep), release.yaml(bump), post_release.yaml(edit) +# manually set this version will be eventually overwritten by the above actions +__version__ = "0.14.3" assert len(__version__) <= 10 # The log table limits version to the 10 characters