Skip to content

Commit 906eba6

Browse files
committed
update the build and publish CI
1 parent 83e362e commit 906eba6

File tree

3 files changed

+109
-105
lines changed

3 files changed

+109
-105
lines changed

.github/workflows/build.yaml

Lines changed: 24 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,16 @@
1+
## GitHub Actions Workflow: Build Package
2+
## - Triggers:
3+
## - Manual (workflow_dispatch): only 'dev' and 'custom' are allowed.
4+
## - dev: updates version in pyproject.toml to X.Y.Z.devYYMMDD and builds.
5+
## - custom: mutates version by appending the provided custom_suffix to base X.Y.Z and builds.
6+
## - Tags (push): 'X.Y.Z', 'X.Y.ZrcN', 'X.Y.Z.postN' → no version mutation; build as-is.
7+
## - Jobs:
8+
## - validate: determines version_type and version_suffix (manual vs tags).
9+
## - build: sets up Python + PDM, applies dev versioning only for manual dev builds, then builds and uploads wheel artifact.
10+
## - release: on tags only, drafts a GitHub Release and attaches the built wheel.
11+
## Notes:
12+
## - Version mutation is performed only for manual dev builds; all tag builds keep the version from pyproject.toml.
13+
## - PDM is set up via pdm-project/setup-pdm for consistency and caching.
114
name: Build Package
215

316
on:
@@ -10,12 +23,9 @@ on:
1023
default: 'dev'
1124
options:
1225
- dev
13-
- release
14-
- rc
15-
- post
1626
- custom
1727
custom_suffix:
18-
description: 'Custom version suffix (only used when version_type is custom, rc, or post)'
28+
description: 'Custom version suffix (only used when version_type is custom)'
1929
required: false
2030
type: string
2131
push:
@@ -56,45 +66,19 @@ jobs:
5666
if: "!startsWith(github.ref, 'refs/tags/')"
5767
run: |
5868
VERSION_TYPE="${{ github.event.inputs.version_type || 'dev' }}"
59-
echo "version_type=${VERSION_TYPE}" >> $GITHUB_OUTPUT
60-
6169
case "${VERSION_TYPE}" in
6270
"dev")
71+
echo "version_type=dev" >> $GITHUB_OUTPUT
6372
echo "version_suffix=.dev$(date +%y%m%d)" >> $GITHUB_OUTPUT
6473
;;
65-
"release")
66-
echo "version_suffix=" >> $GITHUB_OUTPUT
67-
;;
68-
"rc")
69-
SUFFIX="${{ github.event.inputs.custom_suffix }}"
70-
if [[ -n "${SUFFIX}" ]]; then
71-
if [[ "${SUFFIX}" =~ ^rc[0-9]+$ ]]; then
72-
echo "version_suffix=${SUFFIX}" >> $GITHUB_OUTPUT
73-
else
74-
echo "version_suffix=rc${SUFFIX}" >> $GITHUB_OUTPUT
75-
fi
76-
else
77-
echo "version_suffix=rc1" >> $GITHUB_OUTPUT
78-
fi
79-
;;
80-
"post")
81-
SUFFIX="${{ github.event.inputs.custom_suffix }}"
82-
if [[ -n "${SUFFIX}" ]]; then
83-
if [[ "${SUFFIX}" =~ ^\.post[0-9]+$ ]]; then
84-
echo "version_suffix=${SUFFIX}" >> $GITHUB_OUTPUT
85-
else
86-
echo "version_suffix=.post${SUFFIX}" >> $GITHUB_OUTPUT
87-
fi
88-
else
89-
echo "version_suffix=.post1" >> $GITHUB_OUTPUT
90-
fi
91-
;;
9274
"custom")
75+
echo "version_type=custom" >> $GITHUB_OUTPUT
9376
echo "version_suffix=${{ github.event.inputs.custom_suffix }}" >> $GITHUB_OUTPUT
9477
;;
9578
*)
96-
echo "Unknown version type: ${VERSION_TYPE}"
97-
exit 1
79+
# Restrict manual triggers to dev/custom only; fallback to dev
80+
echo "version_type=dev" >> $GITHUB_OUTPUT
81+
echo "version_suffix=.dev$(date +%y%m%d)" >> $GITHUB_OUTPUT
9882
;;
9983
esac
10084
@@ -111,20 +95,13 @@ jobs:
11195
with:
11296
python-version: '3.10'
11397

114-
- name: Install PDM
115-
run: |
116-
curl -sSL https://raw.githubusercontent.com/pdm-project/pdm/main/install-pdm.py | python3 -
98+
- name: Setup PDM
99+
uses: pdm-project/setup-pdm@v4
117100

118-
- name: Update version
101+
- name: Update version for dev/custom builds
102+
if: ${{ (needs.validate.outputs.version_type == 'dev' || needs.validate.outputs.version_type == 'custom') && !startsWith(github.ref, 'refs/tags/') }}
119103
run: |
120-
VERSION_TYPE="${{ needs.validate.outputs.version_type }}"
121-
VERSION_SUFFIX="${{ needs.validate.outputs.version_suffix }}"
122-
123-
# Only update version for dev builds when there's no tag on this commit
124-
if [ "${VERSION_TYPE}" = "dev" ] && [ ! "${{ startsWith(github.ref, 'refs/tags/') }}" = "true" ]; then
125-
python .github/workflows/update_version.py "${VERSION_TYPE}" "${VERSION_SUFFIX}"
126-
fi
127-
# For tagged releases (rc, post, release), we use the version as-is from pyproject.toml
104+
python .github/workflows/update_version.py "${{ needs.validate.outputs.version_type }}" "${{ needs.validate.outputs.version_suffix }}"
128105
129106
- name: Build package
130107
run: |

.github/workflows/publish.yaml

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
## GitHub Actions Workflow: Publish to PyPI/TestPyPI
2+
## - Triggers:
3+
## - Release published: publishes the project to the configured PyPI repository.
4+
## - Manual (workflow_dispatch): publishes to the specified repository input (pypi or testpypi).
5+
## - Behavior:
6+
## - Sets up Python and PDM, builds the distribution with PDM, then runs `pdm publish`.
7+
## - Uses OIDC (id-token: write) for Trusted Publisher authentication (PEP 740).
8+
## Ensure the repository is configured as a Trusted Publisher on PyPI/TestPyPI.
9+
## - Note:
10+
## - This workflow rebuilds before publishing. If you prefer publishing the exact artifact
11+
## built in the build workflow, adjust this to download and reuse that wheel instead.
12+
name: Publish to PyPI
13+
14+
on:
15+
release:
16+
types: [published]
17+
18+
workflow_dispatch:
19+
inputs:
20+
repository:
21+
description: 'PyPI repository to publish to (pypi or testpypi)'
22+
required: true
23+
default: 'pypi'
24+
25+
jobs:
26+
publish:
27+
runs-on: ubuntu-latest
28+
permissions:
29+
contents: read
30+
id-token: write # Required for private repositories and trusted publishers
31+
32+
steps:
33+
- uses: actions/checkout@v4
34+
35+
- name: Set up Python
36+
uses: actions/setup-python@v5
37+
with:
38+
python-version: '3.10'
39+
40+
- name: Setup PDM
41+
uses: pdm-project/setup-pdm@v4
42+
43+
- name: Build Package
44+
run: pdm build
45+
46+
47+
- name: Set Repository
48+
if: github.event_name == 'workflow_dispatch'
49+
run: echo "PYPI_REPOSITORY=${{ github.event.inputs.repository }}" >> $GITHUB_ENV
50+
51+
- name: Publish Package
52+
run: |
53+
pdm publish --repository ${{ env.PYPI_REPOSITORY || 'pypi' }}

.github/workflows/update_version.py

Lines changed: 32 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -4,68 +4,55 @@
44
import re
55
import sys
66
from pathlib import Path
7+
from typing import Optional
78

89

9-
def update_version(version_type="dev", custom_suffix=None):
10+
def update_version(version_type: str = "dev", custom_suffix: Optional[str] = None) -> str:
1011
"""
11-
Update version in pyproject.toml based on version type.
12-
13-
Args:
14-
version_type: 'dev', 'release', 'rc', or 'custom'
15-
custom_suffix: Custom suffix for version (used with 'custom' type)
12+
Update the `version` field in `pyproject.toml` for supported manual build types.
13+
14+
- dev: sets suffix to `.devYYMMDD` based on today's date.
15+
- custom: appends `custom_suffix` verbatim to the base X.Y.Z (e.g., rc1, .post2, -alpha).
16+
- Other version types are no-ops and keep the current version unchanged.
17+
18+
Returns the new version string that was written.
1619
"""
1720
# Read pyproject.toml
1821
pyproject_path = Path("pyproject.toml")
1922
if not pyproject_path.exists():
2023
raise FileNotFoundError("pyproject.toml not found")
2124

22-
content = pyproject_path.read_text()
25+
content: str = pyproject_path.read_text()
2326

2427
# Extract current version
25-
version_match = re.search(r'^version\s*=\s*"([^"]+)"', content, re.MULTILINE)
28+
version_match: Optional[re.Match[str]] = re.search(r'^version\s*=\s*"([^"]+)"', content, re.MULTILINE)
2629
if not version_match:
2730
raise ValueError("Could not find version in pyproject.toml")
2831

29-
current_version = version_match.group(1)
32+
current_version: str = version_match.group(1)
3033

3134
# Parse the base version (remove any existing suffixes)
32-
base_version_match = re.match(r'^(\d+\.\d+\.\d+)', current_version)
35+
base_version_match: Optional[re.Match[str]] = re.match(r'^(\d+\.\d+\.\d+)', current_version)
3336
if not base_version_match:
3437
raise ValueError(f"Invalid version format: {current_version}")
3538

36-
base_version = base_version_match.group(1)
39+
base_version: str = base_version_match.group(1)
3740

38-
# Generate new version based on type
41+
# Only dev/custom builds mutate the version; all others keep current version
3942
if version_type == "dev":
40-
date_suffix = datetime.datetime.now().strftime("%y%m%d")
41-
new_version = f"{base_version}.dev{date_suffix}"
42-
elif version_type == "release":
43-
# For release, we keep the current version as-is
44-
new_version = current_version
45-
elif version_type == "rc":
46-
# For release candidate, extract rc number from custom_suffix or default to rc1
47-
if custom_suffix and custom_suffix.startswith("rc"):
48-
new_version = f"{base_version}{custom_suffix}"
49-
else:
50-
rc_num = custom_suffix if custom_suffix else "1"
51-
new_version = f"{base_version}rc{rc_num}"
52-
elif version_type == "post":
53-
# For post-release, extract post number from custom_suffix or increment existing
54-
if custom_suffix and custom_suffix.startswith(".post"):
55-
new_version = f"{base_version}{custom_suffix}"
56-
else:
57-
post_num = custom_suffix if custom_suffix else "1"
58-
new_version = f"{base_version}.post{post_num}"
43+
# Generate a date-based dev suffix like .dev250101
44+
date_suffix: str = datetime.datetime.now().strftime("%y%m%d")
45+
new_version: str = f"{base_version}.dev{date_suffix}"
5946
elif version_type == "custom":
60-
if custom_suffix:
61-
new_version = f"{base_version}{custom_suffix}"
62-
else:
63-
raise ValueError("Custom suffix required for custom version type")
47+
if not custom_suffix:
48+
raise ValueError("custom_suffix is required when version_type is 'custom'")
49+
new_version = f"{base_version}{custom_suffix}"
6450
else:
65-
raise ValueError(f"Unknown version type: {version_type}")
51+
print(f"No change: leaving version as {current_version}")
52+
return current_version
6653

6754
# Update version in content
68-
updated_content = re.sub(
55+
updated_content: str = re.sub(
6956
r'^version\s*=\s*"[^"]+"',
7057
f'version = "{new_version}"',
7158
content,
@@ -79,24 +66,11 @@ def update_version(version_type="dev", custom_suffix=None):
7966

8067

8168
if __name__ == "__main__":
82-
# Parse command line arguments
83-
if len(sys.argv) < 2:
84-
# Default to dev version
85-
update_version("dev")
86-
elif len(sys.argv) == 2:
87-
# First argument could be version_type or custom_suffix for backward compatibility
88-
arg = sys.argv[1]
89-
if arg in ["dev", "release", "rc", "post", "custom"]:
90-
update_version(arg)
91-
else:
92-
# Treat as custom suffix for backward compatibility
93-
update_version("custom", arg)
94-
elif len(sys.argv) == 3:
95-
# version_type and custom_suffix
96-
version_type = sys.argv[1]
97-
custom_suffix = sys.argv[2]
98-
update_version(version_type, custom_suffix)
99-
else:
100-
print("Usage: update_version.py [version_type] [custom_suffix]")
101-
print("Version types: dev, release, rc, post, custom")
102-
sys.exit(1)
69+
# Accept optional args for compatibility; act on 'dev' or 'custom'
70+
version_type_arg: str = "dev"
71+
if len(sys.argv) >= 2:
72+
version_type_arg = sys.argv[1]
73+
custom_suffix_arg: Optional[str] = None
74+
if len(sys.argv) >= 3:
75+
custom_suffix_arg = sys.argv[2]
76+
update_version(version_type_arg, custom_suffix_arg)

0 commit comments

Comments
 (0)