Skip to content
Merged
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
118 changes: 109 additions & 9 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,34 +4,65 @@ on:
push:
branches: [master]
pull_request:
branches: [master]
workflow_dispatch:

concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true

# Read-only by default. A job that needs more asks for it itself, so a
# compromised dependency in one step cannot push to the repository.
permissions:
contents: read

env:
PYTHONUNBUFFERED: "1"
PIP_DISABLE_PIP_VERSION_CHECK: "1"
FORCE_COLOR: "1"

jobs:
lint:
name: Lint and types
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/setup-python@v7
with:
python-version: "3.12"
cache: pip
cache-dependency-path: pyproject.toml
- run: pip install -e ".[dev]"
# No `ruff format --check` yet: twenty files predate the formatter and
# reformatting them belongs in its own commit, not in a lint gate that
# would be red from the day it lands. pre-commit formats what a
# contributor touches in the meantime.
- name: ruff check
run: python -m ruff check .
- name: mypy
run: python -m mypy glia

test:
name: Tests (Python ${{ matrix.python-version }})
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
python-version: ["3.10", "3.11", "3.12"]
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v7

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
uses: actions/setup-python@v7
with:
python-version: ${{ matrix.python-version }}
cache: pip
cache-dependency-path: pyproject.toml

- name: Install (dev extras)
run: |
python -m pip install --upgrade pip
pip install -e ".[dev]"

- name: Lint (ruff)
run: python -m ruff check .

- name: Types (mypy)
run: python -m mypy glia

- name: Tests (pytest, fully offline) + coverage gate
run: python -m pytest -q --cov=glia --cov-report=term-missing --cov-fail-under=90

Expand All @@ -41,3 +72,72 @@ jobs:
echo "--- $f ---"
python "$f"
done

platforms:
name: Tests (${{ matrix.os }})
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [windows-latest, macos-latest]
steps:
- uses: actions/checkout@v7
- uses: actions/setup-python@v7
with:
python-version: "3.12"
cache: pip
cache-dependency-path: pyproject.toml
- run: pip install -e ".[dev]"
# The suite only, without the coverage gate and the examples loop. The
# shell ships Windows and macOS binaries, so the library underneath them
# has to be known to work there; the gate and the examples are already
# measured once on Linux and measuring them three times says nothing new.
- name: Tests
run: python -m pytest -q

build:
name: Package builds and installs cleanly
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/setup-python@v7
with:
python-version: "3.12"
- run: pip install build twine
- name: Build the wheel and sdist
run: python -m build
- name: Check the metadata
run: twine check dist/*
# An editable install hides packaging mistakes: it puts the working tree
# on the path, so a module missing from the wheel still imports and a
# broken console script still resolves.
- name: Install the wheel into a clean environment
run: |
python -m venv /tmp/fresh
/tmp/fresh/bin/pip install dist/*.whl
# The distribution is glia-agents and the import package is glia.
# That mismatch is the one packaging mistake nobody notices locally.
/tmp/fresh/bin/python -c "import glia; print(glia.__name__)"
/tmp/fresh/bin/glia-shell --help > /dev/null
- uses: actions/upload-artifact@v7
with:
name: dist
path: dist/
retention-days: 14

ci:
name: CI
runs-on: ubuntu-latest
if: always()
needs: [lint, test, platforms, build]
steps:
# One aggregate check to require in branch protection. Without it, adding
# a job to the matrix silently leaves it unrequired, and a red job stops
# blocking merges.
- name: Fail if any job did not succeed
if: contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled')
run: |
echo "One or more jobs failed:"
echo '${{ toJSON(needs) }}'
exit 1
- run: echo "All checks passed."
39 changes: 39 additions & 0 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
name: CodeQL

on:
push:
branches: [master]
pull_request:
branches: [master]
schedule:
# Weekly, because advisories land between pushes: a rule added after the
# last commit would otherwise never run against this code.
- cron: "29 5 * * 1"
workflow_dispatch:

permissions:
contents: read

jobs:
analyze:
name: Analyze Python
runs-on: ubuntu-latest
if: github.event.repository.visibility == 'public'
permissions:
security-events: write
actions: read
contents: read
steps:
- uses: actions/checkout@v7

- uses: github/codeql-action/init@v4
with:
languages: python
# security-and-quality over the default: this library executes
# user-declared tools and serves HTTP from the shell, so the extra
# checks earn their noise.
queries: security-and-quality

- uses: github/codeql-action/analyze@v4
with:
category: "/language:python"
49 changes: 49 additions & 0 deletions .github/workflows/dependabot-auto-merge.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
name: Dependabot auto-merge

# What this does and does not do:
#
# auto-merged - GitHub Actions bumps, and patch-level bumps of anything else
# left for you - every minor and major bump of a runtime dependency
#
# A patch release and an action bump are the updates that pile up unread until
# the queue is too long to review honestly. A minor bump can change behaviour,
# so it keeps a human. Auto-merge is queued, not immediate: GitHub still waits
# for the required checks to pass, and a red build leaves the PR open.

on: pull_request_target

permissions:
contents: read

jobs:
auto-merge:
if: github.actor == 'dependabot[bot]'
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
# Reads the update type from the PR that Dependabot opened. Nothing from
# the branch is checked out or executed, which is what makes
# pull_request_target safe to use here.
- id: metadata
uses: dependabot/fetch-metadata@v2

- name: Approve and queue the merge
if: >-
steps.metadata.outputs.package-ecosystem == 'github_actions' ||
steps.metadata.outputs.update-type == 'version-update:semver-patch'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR: ${{ github.event.pull_request.html_url }}
run: |
gh pr review --approve "$PR"
gh pr merge --auto --squash "$PR"

- name: Explain why this one was left alone
if: >-
steps.metadata.outputs.package-ecosystem != 'github_actions' &&
steps.metadata.outputs.update-type != 'version-update:semver-patch'
env:
TYPE: ${{ steps.metadata.outputs.update-type }}
run: echo "$TYPE is not auto-merged; this pull request needs a human."
4 changes: 2 additions & 2 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,11 @@ jobs:
deploy-docs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v7
with:
fetch-depth: 0

- uses: actions/setup-python@v5
- uses: actions/setup-python@v7
with:
python-version: "3.12"

Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ jobs:
permissions:
id-token: write # required for Trusted Publishing (OIDC)
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v7

- uses: actions/setup-python@v5
- uses: actions/setup-python@v7
with:
python-version: "3.12"

Expand Down
8 changes: 4 additions & 4 deletions .github/workflows/release-app.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,9 @@ jobs:
bin: dist/glia-shell
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v7

- uses: actions/setup-python@v5
- uses: actions/setup-python@v7
with:
python-version: "3.12"

Expand All @@ -64,7 +64,7 @@ jobs:
run: cp "${{ matrix.bin }}" "${{ matrix.asset }}"

- name: Upload artifact
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: ${{ matrix.asset }}
path: ${{ matrix.asset }}
Expand All @@ -76,7 +76,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Download all binaries
uses: actions/download-artifact@v4
uses: actions/download-artifact@v8
with:
path: bins
merge-multiple: true
Expand Down
42 changes: 42 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Install with: pre-commit install
#
# ruff-format runs here and not in CI on purpose. Twenty files predate the
# formatter; a CI gate would be red on day one, while this hook only touches
# what a commit already changes.
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.15.20
hooks:
- id: ruff
args: [--fix]
- id: ruff-format

- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v5.0.0
hooks:
- id: check-added-large-files
args: [--maxkb=512]
- id: check-merge-conflict
- id: check-toml
- id: check-yaml
- id: end-of-file-fixer
- id: mixed-line-ending
args: [--fix=lf]
- id: trailing-whitespace

- repo: local
hooks:
# A PyInstaller build directory is the output of release-app.yml. It is
# large, it is per-platform, and it has no business in the history.
- id: reject-pyinstaller-output
name: reject PyInstaller build output
entry: build/ and dist/ are release output, not source
language: fail
files: ^(build|dist)/

- id: pytest
name: pytest
entry: pytest -q
language: system
pass_filenames: false
stages: [pre-push]