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
45 changes: 26 additions & 19 deletions .github/workflows/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# GitHub Actions Workflows

This directory contains GitHub Actions workflows for automated testing and code quality checks.
This directory contains GitHub Actions workflows for automated testing, code quality checks, and releases.

## Workflows

Expand All @@ -10,8 +10,8 @@ This directory contains GitHub Actions workflows for automated testing and code
- **Matrix**: Python 3.10, 3.12
- **Purpose**: Run comprehensive test suite with coverage reporting
- **Features**:
- Automatic git submodule initialization for `bluetooth_sig` dependency
- Test execution with pytest and coverage reporting (76% coverage)
- Automatic git submodule initialisation for `bluetooth_sig` dependency
- Test execution with pytest and coverage reporting (85% threshold)
- Coverage upload to Codecov for Python 3.12 runs
- Uses project configuration from `pyproject.toml`

Expand All @@ -23,9 +23,18 @@ This directory contains GitHub Actions workflows for automated testing and code
- **Tools**:
- **ruff**: Formatting and linting
- **mypy**: strict for production code, lenient for tests/examples

- **Environment Setup**: All tools run via `python -m` to ensure proper configuration loading

### Release (`release.yml`)

- **Trigger**: Push of a `v*.*.*` tag (e.g. `v0.1.0`)
- **Purpose**: Build, publish to PyPI, and create a GitHub Release
- **Jobs**:
1. **build** — builds sdist + wheel using `hatchling`, verifies with `twine check`
2. **publish-pypi** — publishes to PyPI via trusted publisher (OIDC). Requires the `pypi` GitHub environment.
3. **github-release** — generates release notes with `git-cliff` and creates a GitHub Release with the distribution artefacts.
- **Prerequisites**: The repository must have a `pypi` environment configured with PyPI trusted publisher credentials.

## Local Development

To run the same checks locally:
Expand All @@ -34,17 +43,18 @@ To run the same checks locally:
# Install development dependencies
pip install -e ".[dev]"

# Initialize git submodules (required for UUID registry)
# Initialise git submodules (required for UUID registry)
git submodule update --init --recursive

# Run tests with coverage
python -m pytest tests/ --cov=src/ble_gatt_device --cov-report=term-missing
python -m pytest tests/ --cov=src/bluetooth_sig --cov-report=term-missing

# Run linting
python -m ruff check src/ tests/
python -m ruff format --check src/ tests/

# Run linting tools (use python -m for proper configuration loading)
python -m flake8 src/ tests/ --count --statistics
python -m black --check --diff src/ tests/
python -m isort --check-only --diff src/ tests/
python -m pylint src/ble_gatt_device/ --exit-zero --score y
# Run type checking
python -m mypy src/bluetooth_sig/
```

## Environment Setup Requirements
Expand All @@ -54,21 +64,18 @@ python -m pylint src/ble_gatt_device/ --exit-zero --score y
When testing locally or in agent environments, ensure:

1. **Python 3.11+** is available
1. **Git submodules** are initialized: `git submodule update --init --recursive`
1. **Git submodules** are initialised: `git submodule update --init --recursive`
1. **Package installation** in development mode: `pip install -e ".[dev]"`
1. **Tool execution** via Python modules: Use `python -m tool_name` instead of direct commands
1. **Configuration loading**: flake8-pyproject allows flake8 to read from `pyproject.toml`

### Key Environment Dependencies

- Git submodule `bluetooth_sig` must be present for UUID registry functionality
- All linting tools should be run via `python -m` to ensure proper configuration loading
- Black handles most formatting that would trigger flake8 style errors
- All linting/formatting is handled by `ruff` — configured in `pyproject.toml`

## Notes

- All tool configurations are defined in `pyproject.toml` (no separate `.flake8` file)
- Workflows use `--exit-zero` for pylint to prevent CI failures on minor issues
- Coverage reporting is optional and won't fail the build
- Git submodules are automatically initialized for the Bluetooth SIG UUID registry dependency
- All tool configurations are defined in `pyproject.toml`
- Coverage threshold is 85% (`--cov-fail-under=85`)
- Git submodules are automatically initialised for the Bluetooth SIG UUID registry dependency
- Use `python -m` prefix for all tools to ensure proper package and configuration loading
5 changes: 3 additions & 2 deletions .github/workflows/copilot-setup-steps.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ jobs:
cache-dependency-path: 'pyproject.toml'

- name: Cache system dependencies
uses: actions/cache@v4
uses: actions/cache@v5
with:
path: /var/cache/apt
key: ${{ runner.os }}-apt-copilot-${{ hashFiles('scripts/install-deps.sh') }}
Expand All @@ -58,6 +58,8 @@ jobs:
python -m pip install --upgrade pip
# Install dev, test and examples extras so local setup has BLE example libraries
pip install -e .[dev,test,examples]
env:
SETUPTOOLS_SCM_PRETEND_VERSION: "0.0.0.dev0"

- name: Verify environment
run: |
Expand All @@ -66,5 +68,4 @@ jobs:
- name: Read the instructions
run: |
cat .github/copilot-instructions.md
cat .github/copilot-code-review.md

4 changes: 2 additions & 2 deletions .github/workflows/lint-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ jobs:
cache-dependency-path: 'pyproject.toml'

- name: 'Cache system dependencies'
uses: actions/cache@v4
uses: actions/cache@v5
with:
path: /var/cache/apt
key: ${{ runner.os }}-apt-lint-${{ hashFiles('scripts/install-deps.sh') }}
Expand Down Expand Up @@ -73,7 +73,7 @@ jobs:
cache-dependency-path: 'pyproject.toml'

- name: 'Cache system dependencies'
uses: actions/cache@v4
uses: actions/cache@v5
with:
path: /var/cache/apt
key: ${{ runner.os }}-apt-lint-${{ hashFiles('scripts/install-deps.sh') }}
Expand Down
67 changes: 55 additions & 12 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,61 @@ jobs:
name: dist
path: dist/

validate:
name: Validate packages
needs: build
runs-on: ubuntu-latest
permissions:
contents: read
strategy:
fail-fast: true
matrix:
artifact: [wheel, sdist]
steps:
- uses: actions/checkout@v6
with:
sparse-checkout: |
tests/
pyproject.toml
.gitignore

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

- uses: actions/download-artifact@v7
with:
name: dist
path: dist/

- name: Install package (${{ matrix.artifact }}) with test extras
run: |
if [ "${{ matrix.artifact }}" = "wheel" ]; then
pip install "$(ls dist/*.whl)[test-core]"
else
pip install "$(ls dist/*.tar.gz)[test-core]"
fi

- name: Verify package metadata
run: |
python -c "
import bluetooth_sig
v = getattr(bluetooth_sig, '__version__', None)
print(f'Version: {v}')
assert v, 'Missing __version__'
"

- name: Run packaging smoke tests against installed ${{ matrix.artifact }}
run: |
python -m pytest tests/ \
-m packaging \
--ignore=tests/docs \
--override-ini="pythonpath=" \
-v

publish-pypi:
name: Publish to PyPI
needs: publish-testpypi
needs: validate
runs-on: ubuntu-latest
timeout-minutes: 10
environment: pypi
Expand All @@ -63,16 +115,6 @@ jobs:
permissions:
contents: write
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0

- name: Generate release notes
run: |
pip install "git-cliff~=2.7"
git-cliff --latest --strip header --output RELEASE_NOTES.md
cat RELEASE_NOTES.md

- uses: actions/download-artifact@v7
with:
name: dist
Expand All @@ -83,6 +125,7 @@ jobs:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh release create "${{ github.ref_name }}" \
--repo "${{ github.repository }}" \
--title "${{ github.ref_name }}" \
--notes-file RELEASE_NOTES.md \
--generate-notes \
dist/*
10 changes: 5 additions & 5 deletions .github/workflows/test-coverage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -145,14 +145,14 @@ jobs:
# don't require a token when not pushing pages

- name: Upload benchmark baseline cache
uses: actions/cache@v4
uses: actions/cache@v5
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
with:
path: ./cache
key: ${{ runner.os }}-benchmark

- name: Download previous benchmark data
uses: actions/cache@v4
uses: actions/cache@v5
if: github.event_name == 'pull_request'
with:
path: ./cache
Expand Down Expand Up @@ -221,7 +221,7 @@ jobs:
summary-always: true

- name: Download previous benchmark history
uses: dawidd6/action-download-artifact@v12
uses: dawidd6/action-download-artifact@v16
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
continue-on-error: true
with:
Expand Down Expand Up @@ -302,7 +302,7 @@ jobs:
continue-on-error: true

- name: Download benchmark history
uses: dawidd6/action-download-artifact@v12
uses: dawidd6/action-download-artifact@v16
continue-on-error: true
with:
name: benchmark-history
Expand Down Expand Up @@ -383,7 +383,7 @@ jobs:
pip install -e ".[test]"

- name: Cache Playwright browsers
uses: actions/cache@v4
uses: actions/cache@v5
id: playwright-cache
with:
path: ~/.cache/ms-playwright
Expand Down
5 changes: 4 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,8 @@
"chat.tools.terminal.autoApprove": {
"bluetoothctl": true,
"hciconfig": true
}
},
"python.defaultInterpreterPath": "${workspaceFolder}/../../../.venv/bin/python",
"python.venvPath": "${workspaceFolder}/../../../",
"python.analysis.extraPaths": ["${workspaceFolder}/src"]
}
35 changes: 25 additions & 10 deletions docs/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -487,20 +487,35 @@ def run_pre_build_scripts(app: Sphinx, config: object) -> None:
# =========================================================================
changelog_output = repo_root / "docs" / "source" / "community" / "changelog.md"
try:
print("Generating changelog from git history via git-cliff...")
subprocess.run(
["git-cliff", "--output", str(changelog_output)],
print("Generating changelog from git history via git-changelog...")
result = subprocess.run(
[
"git-changelog",
"--output",
str(changelog_output),
"--convention",
"conventional",
"--provider",
"github",
"--sections",
":all:",
"--include-all",
],
cwd=repo_root,
check=True,
capture_output=True,
text=True,
)
print(f"✓ Changelog generated at {changelog_output}")
except (subprocess.CalledProcessError, FileNotFoundError) as e:
print(f"Warning: git-cliff changelog generation failed: {e}")
# Write a minimal placeholder so docs build doesn't break
changelog_output.write_text(
"# Changelog\n\nChangelog generation requires git-cliff. "
"Install with `pip install git-cliff`.\n"
)
except subprocess.CalledProcessError as e:
print(f"Error: git-changelog failed: {e}")
print(f"stdout: {e.stdout}")
print(f"stderr: {e.stderr}")
raise
except FileNotFoundError as e:
print(f"Error: git-changelog not found: {e}")
print("Install with: pip install git-changelog")
raise

# =========================================================================
# Step 3: Generate architecture diagrams
Expand Down
Loading
Loading