Skip to content

Commit e2b2523

Browse files
galenlynchCopilot
andauthored
Add UV workflows (#20)
* Change workflows to use uv * Move concurrency and conditionals to calling workflows * Create dependabot.yml * Move workflow files to correct locations * Remove unused input * Checkout the repo with the right token * Add smoke-test-paths as input to ci workflow * Add publish-to-pypi flag to publish.yml * Make sure publish only runs on tags * Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * feat: make pypi-token optional In response to copilot code review * Restore names to steps * Make example package name more explicit * Fix potential security problem * Don't assume lockfile is up to date * Don't assume lockfile is up to date in CI * Actually, CI should respect lockfile as is An out-of-date lockfile should error * chore: prepare for PR against upstream * Restore original workflows --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent 9b9f172 commit e2b2523

8 files changed

Lines changed: 351 additions & 2 deletions

File tree

.github/dependabot.yml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
version: 2
2+
updates:
3+
# Enable version updates for GitHub Actions
4+
- package-ecosystem: "github-actions"
5+
# Workflow files stored in the default location of `.github/workflows`
6+
# You don't need to specify `/.github/workflows` for `directory`. You can use `directory: "/"`.
7+
directory: "/"
8+
schedule:
9+
interval: "weekly"

.github/workflows/bump-version.yml

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
name: Version Bump
2+
3+
on:
4+
workflow_call:
5+
inputs:
6+
default-branch:
7+
description: "Branch to bump (usually 'main')"
8+
required: false
9+
type: string
10+
default: "main"
11+
secrets:
12+
repo-token:
13+
description: "Token with write permission to push"
14+
required: true
15+
16+
permissions:
17+
contents: write
18+
id-token: write
19+
20+
jobs:
21+
bump:
22+
runs-on: ubuntu-latest
23+
24+
steps:
25+
- name: Checkout
26+
uses: actions/checkout@v4
27+
with:
28+
ref: ${{ inputs.default-branch }}
29+
fetch-depth: 0
30+
token: ${{ secrets.repo-token }}
31+
- name: Commitizen bump
32+
id: cz
33+
uses: commitizen-tools/commitizen-action@master
34+
with:
35+
github_token: ${{ secrets.repo-token }}
36+
- name: Show new version
37+
run: echo "Bumped to ${{ steps.cz.outputs.version }}"

.github/workflows/ci-uv.yml

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
name: CI
2+
3+
on:
4+
workflow_call:
5+
inputs:
6+
os:
7+
description: Runner OS
8+
required: true
9+
type: string
10+
python-version:
11+
description: Python version
12+
required: true
13+
type: string
14+
package-name:
15+
description: "The Python package name to test/cover"
16+
required: true
17+
type: string
18+
smoke-test-paths:
19+
description: |
20+
space-separated list of files or directories
21+
to copy into the wheel smoke test.
22+
required: false
23+
type: string
24+
default: "tests"
25+
secrets:
26+
repo-token:
27+
description: GitHub token for checkout/PR operations
28+
required: true
29+
30+
permissions:
31+
contents: read
32+
pull-requests: read
33+
34+
35+
jobs:
36+
tests:
37+
name: Python ${{ inputs.python-version }} on ${{ inputs.os }}
38+
runs-on: ${{ inputs.os }}
39+
40+
steps:
41+
- name: Checkout
42+
uses: actions/checkout@v4
43+
- name: Set up Python
44+
uses: actions/setup-python@v5
45+
with:
46+
python-version: ${{ inputs.python-version }}
47+
- name: Install uv
48+
uses: astral-sh/setup-uv@v6
49+
with:
50+
python-version: ${{ inputs.python-version }}
51+
enable-cache: true
52+
- name: Install project
53+
run: uv sync --frozen
54+
- name: Ruff (lint + format-check)
55+
run: |
56+
uv run --frozen ruff format --check --diff .
57+
uv run --frozen ruff check --output-format=github .
58+
- name: Interrogate (docstring coverage)
59+
run: uv run --frozen interrogate -v
60+
- name: Codespell (typo checker)
61+
run: uv run --frozen codespell --check-filenames
62+
- name: Pytest (unit test)
63+
run: uv run --frozen pytest --cov ${{ inputs.package-name }}
64+
- name: Build
65+
run: uv build
66+
- name: Smoke-test built wheel
67+
run: |
68+
TEMP_DIR=${{ runner.temp }}/wheeltest
69+
mkdir -p "$TEMP_DIR"
70+
for path in ${{ inputs.smoke-test-paths }}; do
71+
cp -r "$path" "$TEMP_DIR/" || echo "Warning: '$path' not found"
72+
done
73+
cp dist/*.whl "$TEMP_DIR"
74+
cd "$TEMP_DIR"
75+
uv venv
76+
source .venv/bin/activate
77+
uv pip install pytest *.whl
78+
pytest -q

.github/workflows/publish.yml

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
name: Publish
2+
3+
on:
4+
workflow_call:
5+
inputs:
6+
python-version:
7+
description: "Python version for build"
8+
required: false
9+
type: string
10+
default: "3.13"
11+
tag-name:
12+
description: "Git tag to release"
13+
required: true
14+
type: string
15+
publish-to-pypi:
16+
description: "Whether to publish to PyPI (true/false)"
17+
required: false
18+
type: boolean
19+
default: false
20+
secrets:
21+
repo-token:
22+
description: "GitHub token for release creation"
23+
required: true
24+
pypi-token:
25+
description: "PyPI API token"
26+
required: false
27+
28+
permissions:
29+
contents: write
30+
31+
jobs:
32+
build-release-publish:
33+
runs-on: ubuntu-latest
34+
# only run on a tag push, or on a manual dispatch *where* the user has
35+
# selected a tag ref (i.e. github.ref starts with refs/tags/)
36+
if: startsWith(github.ref, 'refs/tags/')
37+
steps:
38+
- name: Checkout
39+
uses: actions/checkout@v4
40+
with:
41+
# on push this is already the tag ref; on dispatch you must choose
42+
# the tag in the UI so github.ref is the tag
43+
ref: ${{ github.ref }}
44+
fetch-depth: 0
45+
- name: Set up Python
46+
uses: actions/setup-python@v5
47+
with:
48+
python-version: ${{ inputs.python-version }}
49+
- name: Install uv
50+
uses: astral-sh/setup-uv@v6
51+
with:
52+
python-version: ${{ inputs.python-version }}
53+
enable-cache: true
54+
- name: Install project
55+
run: uv sync
56+
- name: Build
57+
run: uv build
58+
- name: Verify artifacts
59+
run: ls -la dist/
60+
- name: Create GitHub release
61+
uses: softprops/action-gh-release@v2
62+
with:
63+
tag_name: ${{ inputs.tag-name }}
64+
name: Release ${{ inputs.tag-name }}
65+
generate_release_notes: true
66+
files: |
67+
dist/*.tar.gz
68+
dist/*.whl
69+
env:
70+
GITHUB_TOKEN: ${{ secrets.repo-token }}
71+
- name: Publish to PyPI
72+
if: ${{ inputs.publish-to-pypi }}
73+
env:
74+
pypi_token: ${{ secrets.pypi-token }}
75+
run: |
76+
if [ -z "$pypi_token" ]; then
77+
echo "No PyPI token provided, skipping publish."
78+
else
79+
uv publish --token "$pypi_token"
80+
fi

README.md

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,31 @@ aind-github-actions
77

88
GitHub actions workflows are found in .github/workflows.
99

10+
Example calling workflows are in `examples/`
11+
12+
The UV CI workflow depends on your project having a `dev` group of optional
13+
dependencies with `ruff`, `interrogate`, `codespell`, `pytest`, and
14+
`pytest-cov`.
15+
16+
The bump workflow requires `commitizen` configuration in the calling project's
17+
`pyproject.toml`, and that projects follow
18+
[conventional commits](https://www.conventionalcommits.org/en/v1.0.0/).
19+
20+
The publish workflow can be configured to publish to pypi, as well as make a
21+
github release. See the example `examples/publish-call.yml` for more
22+
information.
23+
1024
### Pull requests
1125

12-
For internal members, please create a branch. For external members, please fork the repository and open a pull request from the fork. We'll primarily use [Angular](https://github.com/angular/angular/blob/main/CONTRIBUTING.md#commit) style for commit messages. Roughly, they should follow the pattern:
26+
For internal members, please create a branch. For external members, please fork
27+
the repository and open a pull request from the fork. We'll primarily use
28+
[conventional commits](https://www.conventionalcommits.org/en/v1.0.0/).
1329
```text
1430
<type>(<scope>): <short summary>
1531
```
1632

17-
where scope (optional) describes the packages affected by the code changes and type (mandatory) is one of:
33+
where scope (optional) describes the packages affected by the code changes and
34+
type (mandatory) is one of:
1835

1936
- **build**: Changes that affect build tools or external dependencies (example scopes: pyproject.toml, setup.py)
2037
- **ci**: Changes to our CI configuration files and scripts (examples: .github/workflows/ci.yml)

examples/bump-call.yml

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# Example workflow to use the reusable bump-version workflow
2+
# This workflow is triggered by the completion of a CI workflow and bumps the
3+
# version if the last commit message does not start with 'bump:'. It can also
4+
# be manually triggered via workflow_dispatch.
5+
#
6+
# * The bumped version is pushed to the repository.
7+
# * The workflow uses a service token for authentication.
8+
# * The concurrency group ensures that only one bump job runs at a time for the
9+
# same branch.
10+
# * The workflow runs on the main branch.
11+
# * This calling workflow should be placed in `.github/workflows/bump-call.yml`
12+
# of your repository.
13+
name: Bump Version
14+
15+
on:
16+
workflow_run:
17+
workflows: ["CI"]
18+
branches: [ main ]
19+
types: [ completed ]
20+
workflow_dispatch:
21+
22+
permissions:
23+
contents: write # needed to push commits/tags
24+
id-token: write # needed if your reusable workflow exchanges OIDC tokens
25+
26+
concurrency:
27+
group: bump-${{ github.workflow }}-${{ github.event.workflow_run.head_branch || github.ref }}
28+
cancel-in-progress: true
29+
30+
jobs:
31+
bump:
32+
# Only run when:
33+
# • a CI run just succeeded (and the head commit message doesn't start with 'bump:')
34+
# • OR this is a manual dispatch
35+
if: >
36+
(github.event_name == 'workflow_run' &&
37+
github.event.workflow_run.conclusion == 'success' &&
38+
!startsWith(github.event.workflow_run.head_commit.message, 'bump:')) ||
39+
(github.event_name == 'workflow_dispatch' &&
40+
github.ref == 'refs/heads/main')
41+
uses: AllenNeuralDynamics/aind-github-actions/.github/workflows/bump-version.yml@main
42+
with:
43+
default-branch: main
44+
secrets:
45+
repo-token: ${{ secrets.SERVICE_TOKEN }}

examples/ci-call.yml

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# Example workflow to use the reusable ci workflow
2+
# This workflow is triggered by a push to the main branch, a pull request
3+
# targeting the main branch, or manually via workflow_dispatch. It runs the CI
4+
# tests for the specified Python versions and OS.
5+
#
6+
# * The concurrency group ensures that only one CI job runs at a time for the
7+
# same branch or pull request.
8+
# * This calling workflow should be placed in `.github/workflows/ci-call.yml`
9+
# of your repository.
10+
name: CI
11+
12+
on:
13+
workflow_dispatch:
14+
push:
15+
branches: [ main ]
16+
pull_request:
17+
branches: [ main ]
18+
types: [opened, synchronize, reopened, ready_for_review]
19+
20+
permissions:
21+
contents: read
22+
pull-requests: read
23+
24+
concurrency:
25+
group: ${{ github.workflow }}-${{ github.head_ref || github.ref }}
26+
cancel-in-progress: true
27+
28+
jobs:
29+
run-ci:
30+
strategy:
31+
matrix:
32+
os: [ubuntu-latest]
33+
python-version: [3.9, 3.13]
34+
35+
# Only run when:
36+
# • this is not a draft pull request
37+
# • the actor is not the GitHub Actions bot
38+
# • the commit message does not start with 'bump:'
39+
if: >
40+
(github.event_name != 'pull_request' || github.event.pull_request.draft == false) &&
41+
github.actor != 'github-actions[bot]' &&
42+
!(github.event_name == 'push' && startsWith(github.event.head_commit.message, 'bump:'))
43+
44+
uses: AllenNeuralDynamics/aind-github-actions/.github/workflows/ci-uv.yml@main
45+
with:
46+
os: ${{ matrix.os }}
47+
python-version: ${{ matrix.python-version }}
48+
package-name: <your_package_name> # Replace with the importable package name of your Python package
49+
secrets:
50+
repo-token: ${{ secrets.GITHUB_TOKEN }}

examples/publish-call.yml

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# Example workflow to use the reusable publish workflow
2+
# This workflow is triggered by a push to a tag that matches the pattern 'v[0-9]*'
3+
# (indicating a semantic versioning tag) or manually via workflow_dispatch.
4+
# It creates a release and uploads the package to PyPI.
5+
# * The concurrency group ensures that only one release job runs at a time.
6+
# * This calling workflow should be placed in `.github/workflows/publish-call.yml`
7+
# of your repository.
8+
# * The workflow uses a service token for authentication to PyPI.
9+
name: "Release"
10+
11+
on:
12+
push:
13+
tags:
14+
- 'v[0-9]*' # semver tags
15+
workflow_dispatch:
16+
17+
permissions:
18+
contents: write # create release + upload assets
19+
20+
concurrency:
21+
group: release
22+
cancel-in-progress: false # let releases finish
23+
24+
jobs:
25+
run-release:
26+
uses: AllenNeuralDynamics/aind-github-actions.github/workflows/publish.yml@main
27+
with:
28+
tag-name: ${{ github.ref_name }}
29+
python-version: "3.13"
30+
publish-to-pypi: true # Set to true to publish to PyPI, remove or false to skip
31+
secrets:
32+
repo-token: ${{ secrets.GITHUB_TOKEN }}
33+
pypi-token: ${{ secrets.AIND_PYPI_TOKEN }}

0 commit comments

Comments
 (0)