Skip to content

Commit 04b3430

Browse files
Merge pull request #1228 from RonnyPfannschmidt/move-code-to-vcs-versioning
Move code to vcs versioning
2 parents 33f19ad + b75bcf4 commit 04b3430

File tree

180 files changed

+10755
-4249
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

180 files changed

+10755
-4249
lines changed

.cursor/rules/test-running.mdc

Lines changed: 0 additions & 12 deletions
This file was deleted.

.github/workflows/README.md

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
# Reusable Workflows for Towncrier-based Releases
2+
3+
This directory contains reusable GitHub Actions workflows that other projects can use to implement the same towncrier-based release process.
4+
5+
## Available Reusable Workflows
6+
7+
### `reusable-towncrier-release.yml`
8+
9+
Determines the next version using the `towncrier-fragments` version scheme and builds the changelog.
10+
11+
**Inputs:**
12+
- `project_name` (required): Name of the project (used for labeling and tag prefix)
13+
- `project_directory` (required): Directory containing the project (relative to repository root)
14+
15+
**Outputs:**
16+
- `version`: The determined next version
17+
- `has_fragments`: Whether fragments were found
18+
19+
**Behavior:**
20+
- ✅ Strict validation - workflow fails if changelog fragments or version data is missing
21+
- ✅ No fallback values - ensures data integrity for releases
22+
- ✅ Clear error messages to guide troubleshooting
23+
24+
**Example usage:**
25+
26+
```yaml
27+
jobs:
28+
determine-version:
29+
uses: pypa/setuptools-scm/.github/workflows/reusable-towncrier-release.yml@main
30+
with:
31+
project_name: my-project
32+
project_directory: ./
33+
```
34+
35+
## Using These Workflows in Your Project
36+
37+
### Prerequisites
38+
39+
1. **Add vcs-versioning dependency** to your project
40+
2. **Configure towncrier** in your `pyproject.toml`:
41+
42+
```toml
43+
[tool.towncrier]
44+
directory = "changelog.d"
45+
filename = "CHANGELOG.md"
46+
start_string = "<!-- towncrier release notes start -->\n"
47+
template = "changelog.d/template.md"
48+
title_format = "## {version} ({project_date})"
49+
50+
[[tool.towncrier.type]]
51+
directory = "removal"
52+
name = "Removed"
53+
showcontent = true
54+
55+
[[tool.towncrier.type]]
56+
directory = "feature"
57+
name = "Added"
58+
showcontent = true
59+
60+
[[tool.towncrier.type]]
61+
directory = "bugfix"
62+
name = "Fixed"
63+
showcontent = true
64+
```
65+
66+
3. **Create changelog structure**:
67+
- `changelog.d/` directory
68+
- `changelog.d/template.md` (towncrier template)
69+
- `CHANGELOG.md` with the start marker
70+
71+
4. **Add the version scheme entry point** (if using vcs-versioning):
72+
73+
The `towncrier-fragments` version scheme is provided by vcs-versioning 0.2.0+.
74+
75+
### Complete Example Workflow
76+
77+
```yaml
78+
name: Create Release
79+
80+
on:
81+
workflow_dispatch:
82+
inputs:
83+
create_release:
84+
description: 'Create release'
85+
required: true
86+
type: boolean
87+
default: false
88+
89+
permissions:
90+
contents: write
91+
pull-requests: write
92+
93+
jobs:
94+
determine-version:
95+
uses: pypa/setuptools-scm/.github/workflows/reusable-towncrier-release.yml@main
96+
with:
97+
project_name: my-project
98+
project_directory: ./
99+
100+
create-release-pr:
101+
needs: determine-version
102+
if: needs.determine-version.outputs.has_fragments == 'true'
103+
runs-on: ubuntu-latest
104+
steps:
105+
- uses: actions/checkout@v5
106+
107+
- name: Download changelog artifacts
108+
uses: actions/download-artifact@v4
109+
with:
110+
name: changelog-my-project
111+
112+
- name: Create Pull Request
113+
uses: peter-evans/create-pull-request@v7
114+
with:
115+
commit-message: "Release v${{ needs.determine-version.outputs.version }}"
116+
branch: release-${{ needs.determine-version.outputs.version }}
117+
title: "Release v${{ needs.determine-version.outputs.version }}"
118+
labels: release:my-project
119+
body: |
120+
Automated release PR for version ${{ needs.determine-version.outputs.version }}
121+
```
122+
123+
## Architecture
124+
125+
The workflow system is designed with these principles:
126+
127+
1. **Version scheme is single source of truth** - No version calculation in scripts
128+
2. **Reusable components** - Other projects can use the same workflows
129+
3. **Manual approval** - Release PRs must be reviewed and merged
130+
4. **Project-prefixed tags** - Enable monorepo releases (`project-vX.Y.Z`)
131+
5. **Automated but controlled** - Automation with human approval gates
132+
6. **Fail fast** - No fallback values; workflows fail explicitly if required data is missing
133+
7. **No custom scripts** - Uses PR title parsing and built-in tools only
134+
135+
## Version Bump Logic
136+
137+
The `towncrier-fragments` version scheme determines bumps based on fragment types:
138+
139+
| Fragment Type | Version Bump | Example |
140+
|---------------|--------------|---------|
141+
| `removal` | Major (X.0.0) | Breaking changes |
142+
| `feature`, `deprecation` | Minor (0.X.0) | New features |
143+
| `bugfix`, `doc`, `misc` | Patch (0.0.X) | Bug fixes |
144+
145+
## Support
146+
147+
For issues or questions about these workflows:
148+
- Open an issue at https://github.com/pypa/setuptools-scm/issues
149+
- See full documentation in [CONTRIBUTING.md](../../CONTRIBUTING.md)
150+

.github/workflows/api-check.yml

Lines changed: 15 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -28,52 +28,22 @@ jobs:
2828
with:
2929
python-version: '3.11'
3030

31-
- name: Install dependencies
32-
run: |
33-
pip install -U pip setuptools
34-
pip install -e .[test]
35-
pip install griffe
31+
- name: Install the latest version of uv
32+
uses: astral-sh/setup-uv@v6
3633

37-
- name: Run griffe API check
38-
id: griffe-check
39-
continue-on-error: true
34+
- name: Get latest release tag
35+
id: latest-tag
4036
run: |
41-
echo "Running griffe API stability check..."
42-
if griffe check setuptools_scm -ssrc -f github; then
43-
echo "api_check_result=success" >> $GITHUB_OUTPUT
44-
echo "exit_code=0" >> $GITHUB_OUTPUT
45-
else
46-
exit_code=$?
47-
echo "api_check_result=warning" >> $GITHUB_OUTPUT
48-
echo "exit_code=$exit_code" >> $GITHUB_OUTPUT
49-
exit $exit_code
50-
fi
37+
# Get the latest git tag (griffe needs a git ref)
38+
LATEST_TAG=$(git describe --tags --abbrev=0 origin/main 2>/dev/null || echo "v9.2.1")
39+
echo "Latest release tag: $LATEST_TAG"
40+
echo "tag=$LATEST_TAG" >> $GITHUB_OUTPUT
5141
52-
- name: Report API check result
53-
if: always()
54-
uses: actions/github-script@v8
55-
with:
56-
script: |
57-
const result = '${{ steps.griffe-check.outputs.api_check_result }}'
58-
const exitCode = '${{ steps.griffe-check.outputs.exit_code }}'
42+
- name: Install dependencies
43+
run: uv sync --all-packages --all-groups
5944

60-
if (result === 'success') {
61-
core.notice('API stability check passed - no breaking changes detected')
62-
await core.summary
63-
.addHeading('✅ API Stability Check: Passed', 2)
64-
.addRaw('No breaking changes detected in the public API')
65-
.write()
66-
} else if (result === 'warning') {
67-
core.warning(`API stability check detected breaking changes (exit code: ${exitCode}). Please review the API changes above.`)
68-
await core.summary
69-
.addHeading('⚠️ API Stability Warning', 2)
70-
.addRaw('Breaking changes detected in the public API. Please review the changes reported above.')
71-
.addRaw(`\n\nExit code: ${exitCode}`)
72-
.write()
73-
} else {
74-
core.error('API stability check failed to run properly')
75-
await core.summary
76-
.addHeading('❌ API Stability Check: Failed', 2)
77-
.addRaw('The griffe check failed to execute. This may indicate griffe is not installed or there was an error.')
78-
.write()
79-
}
45+
- name: Check API stability against latest release
46+
run: |
47+
echo "Comparing current code against tag: ${{ steps.latest-tag.outputs.tag }}"
48+
# Use local check_api.py script which includes griffe-public-wildcard-imports extension
49+
uv run --no-sync python setuptools-scm/check_api.py --against ${{ steps.latest-tag.outputs.tag }}
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
name: Create Release Tags
2+
3+
on:
4+
pull_request:
5+
types: [closed]
6+
branches:
7+
- main
8+
9+
permissions:
10+
contents: write
11+
12+
jobs:
13+
create-tags:
14+
# Only run if PR was merged and has release labels
15+
if: |
16+
github.event.pull_request.merged == true &&
17+
(contains(github.event.pull_request.labels.*.name, 'release:setuptools-scm') ||
18+
contains(github.event.pull_request.labels.*.name, 'release:vcs-versioning'))
19+
runs-on: ubuntu-latest
20+
steps:
21+
- uses: actions/checkout@v5
22+
with:
23+
fetch-depth: 0
24+
ref: ${{ github.event.pull_request.merge_commit_sha }}
25+
26+
- name: Setup Python
27+
uses: actions/setup-python@v6
28+
with:
29+
python-version: '3.11'
30+
31+
- name: Configure git
32+
run: |
33+
git config user.name "github-actions[bot]"
34+
git config user.email "github-actions[bot]@users.noreply.github.com"
35+
36+
- name: Create tags
37+
id: create-tags
38+
run: |
39+
set -e
40+
41+
TAGS_CREATED=""
42+
PR_TITLE="${{ github.event.pull_request.title }}"
43+
44+
# Check if we should release setuptools-scm
45+
if echo "${{ toJson(github.event.pull_request.labels.*.name) }}" | grep -q "release:setuptools-scm"; then
46+
# Extract version from PR title: "Release: setuptools-scm v9.3.0, ..."
47+
VERSION=$(echo "$PR_TITLE" | grep -oP 'setuptools-scm v\K[0-9]+\.[0-9]+\.[0-9]+')
48+
49+
if [ -z "$VERSION" ]; then
50+
echo "ERROR: Failed to extract setuptools-scm version from PR title"
51+
echo "PR title: $PR_TITLE"
52+
echo "Expected format: 'Release: setuptools-scm vX.Y.Z'"
53+
exit 1
54+
fi
55+
56+
TAG="setuptools-scm-v$VERSION"
57+
echo "Creating tag: $TAG"
58+
59+
git tag -a "$TAG" -m "Release setuptools-scm v$VERSION"
60+
git push origin "$TAG"
61+
62+
TAGS_CREATED="$TAGS_CREATED $TAG"
63+
echo "setuptools_scm_tag=$TAG" >> $GITHUB_OUTPUT
64+
echo "setuptools_scm_version=$VERSION" >> $GITHUB_OUTPUT
65+
fi
66+
67+
# Check if we should release vcs-versioning
68+
if echo "${{ toJson(github.event.pull_request.labels.*.name) }}" | grep -q "release:vcs-versioning"; then
69+
# Extract version from PR title: "Release: ..., vcs-versioning v0.2.0"
70+
VERSION=$(echo "$PR_TITLE" | grep -oP 'vcs-versioning v\K[0-9]+\.[0-9]+\.[0-9]+')
71+
72+
if [ -z "$VERSION" ]; then
73+
echo "ERROR: Failed to extract vcs-versioning version from PR title"
74+
echo "PR title: $PR_TITLE"
75+
echo "Expected format: 'Release: vcs-versioning vX.Y.Z'"
76+
exit 1
77+
fi
78+
79+
TAG="vcs-versioning-v$VERSION"
80+
echo "Creating tag: $TAG"
81+
82+
git tag -a "$TAG" -m "Release vcs-versioning v$VERSION"
83+
git push origin "$TAG"
84+
85+
TAGS_CREATED="$TAGS_CREATED $TAG"
86+
echo "vcs_versioning_tag=$TAG" >> $GITHUB_OUTPUT
87+
echo "vcs_versioning_version=$VERSION" >> $GITHUB_OUTPUT
88+
fi
89+
90+
echo "tags_created=$TAGS_CREATED" >> $GITHUB_OUTPUT
91+
92+
- name: Extract changelog for setuptools-scm
93+
if: steps.create-tags.outputs.setuptools_scm_version
94+
id: changelog-setuptools-scm
95+
run: |
96+
VERSION="${{ steps.create-tags.outputs.setuptools_scm_version }}"
97+
cd setuptools-scm
98+
99+
# Extract the changelog section for this version
100+
# Read from version heading until next version heading or EOF
101+
CHANGELOG=$(awk "/^## $VERSION/,/^## [0-9]/" CHANGELOG.md | sed '1d;$d')
102+
103+
# Save to file for GitHub release
104+
echo "$CHANGELOG" > /tmp/changelog-setuptools-scm.md
105+
106+
- name: Extract changelog for vcs-versioning
107+
if: steps.create-tags.outputs.vcs_versioning_version
108+
id: changelog-vcs-versioning
109+
run: |
110+
VERSION="${{ steps.create-tags.outputs.vcs_versioning_version }}"
111+
cd vcs-versioning
112+
113+
# Extract the changelog section for this version
114+
CHANGELOG=$(awk "/^## $VERSION/,/^## [0-9]/" CHANGELOG.md | sed '1d;$d')
115+
116+
# Save to file for GitHub release
117+
echo "$CHANGELOG" > /tmp/changelog-vcs-versioning.md
118+
119+
- name: Create GitHub Release for setuptools-scm
120+
if: steps.create-tags.outputs.setuptools_scm_tag
121+
uses: softprops/action-gh-release@v2
122+
with:
123+
tag_name: ${{ steps.create-tags.outputs.setuptools_scm_tag }}
124+
name: setuptools-scm v${{ steps.create-tags.outputs.setuptools_scm_version }}
125+
body_path: /tmp/changelog-setuptools-scm.md
126+
draft: false
127+
prerelease: false
128+
129+
- name: Create GitHub Release for vcs-versioning
130+
if: steps.create-tags.outputs.vcs_versioning_tag
131+
uses: softprops/action-gh-release@v2
132+
with:
133+
tag_name: ${{ steps.create-tags.outputs.vcs_versioning_tag }}
134+
name: vcs-versioning v${{ steps.create-tags.outputs.vcs_versioning_version }}
135+
body_path: /tmp/changelog-vcs-versioning.md
136+
draft: false
137+
prerelease: false
138+
139+
- name: Summary
140+
run: |
141+
echo "## Tags Created" >> $GITHUB_STEP_SUMMARY
142+
echo "${{ steps.create-tags.outputs.tags_created }}" >> $GITHUB_STEP_SUMMARY
143+
echo "" >> $GITHUB_STEP_SUMMARY
144+
echo "PyPI upload will be triggered automatically by tag push." >> $GITHUB_STEP_SUMMARY
145+

0 commit comments

Comments
 (0)