diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 60ed232..2caefe6 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -11,6 +11,10 @@ concurrency: permissions: contents: read +env: + # The release (a push to main) builds against PyPI versions only, never [tool.uv.sources]. + UV_NO_SOURCES: true + jobs: release: runs-on: ubuntu-latest diff --git a/.github/workflows/static_analysis.yml b/.github/workflows/static_analysis.yml index 17666a8..60fd5c0 100644 --- a/.github/workflows/static_analysis.yml +++ b/.github/workflows/static_analysis.yml @@ -10,6 +10,11 @@ on: - main - devel +env: + # A PR to main or a push to main (the release) uses PyPI versions only; devel uses the + # devel branches from [tool.uv.sources]. + UV_NO_SOURCES: ${{ (github.base_ref || github.ref_name) == 'main' }} + jobs: ruff: runs-on: ubuntu-latest diff --git a/.github/workflows/test_pytest.yml b/.github/workflows/test_pytest.yml index acf5884..71ee1b3 100644 --- a/.github/workflows/test_pytest.yml +++ b/.github/workflows/test_pytest.yml @@ -10,6 +10,11 @@ on: - main - devel +env: + # A PR to main or a push to main (the release) uses PyPI versions only; devel uses the + # devel branches from [tool.uv.sources]. + UV_NO_SOURCES: ${{ (github.base_ref || github.ref_name) == 'main' }} + jobs: pytest: runs-on: ubuntu-latest @@ -41,3 +46,25 @@ jobs: files: coverage.xml flags: python-${{ matrix.python-version }} fail_ci_if_error: false + + # PyPI rejects a release whose metadata has a direct reference (name @ git+...), so a pull + # request to main fails here instead of the release after the merge. + no-direct-references: + if: (github.base_ref || github.ref_name) == 'main' + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v7 + + - name: Set up uv + uses: astral-sh/setup-uv@v7 + with: + python-version: "3.12" + + - name: Check that the wheel has no direct references (name @ git+...) + run: | + uv build --wheel -o dist + if unzip -p dist/*.whl '*.dist-info/METADATA' | grep -E '^Requires-Dist: [^;]*@'; then + echo "::error::Requires-Dist has a direct reference; use a version range and [tool.uv.sources]" + exit 1 + fi diff --git a/.github/workflows/test_tutorials.yml b/.github/workflows/test_tutorials.yml index a6dbc68..c111d2d 100644 --- a/.github/workflows/test_tutorials.yml +++ b/.github/workflows/test_tutorials.yml @@ -10,6 +10,11 @@ on: - main - devel +env: + # A PR to main or a push to main (the release) uses PyPI versions only; devel uses the + # devel branches from [tool.uv.sources]. + UV_NO_SOURCES: ${{ (github.base_ref || github.ref_name) == 'main' }} + jobs: tutorials: runs-on: ubuntu-latest diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 2ff09ec..2d2fa6d 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -18,7 +18,12 @@ cache: paths: - .cache/pip +# PyPI versions only when targeting main; otherwise the devel branches from [tool.uv.sources] +.uv-no-sources: &uv-no-sources + - export UV_NO_SOURCES=$([ "${CI_MERGE_REQUEST_TARGET_BRANCH_NAME:-$CI_COMMIT_BRANCH}" = main ] && echo true || echo false) + before_script: + - *uv-no-sources - python --version ; pip --version # For debugging - pip install uv - uv sync --extra dev @@ -39,7 +44,8 @@ build: pages: image: node:22 - before_script: [] + before_script: + - *uv-no-sources script: - apt-get update && apt-get install -y python3 python3-pip python3-venv - pip install uv && uv sync --extra docs diff --git a/docs/src/content/docs/development/publishing.md b/docs/src/content/docs/development/publishing.md index c8665b5..76e37b8 100644 --- a/docs/src/content/docs/development/publishing.md +++ b/docs/src/content/docs/development/publishing.md @@ -46,11 +46,21 @@ It fails when a dependency, or an optional dependency, is - a version range that no release on PyPI satisfies (for example `cunumpy>=0.7` before 0.7.0 is published), so a dependency has to be released before the package that needs it. -During development a dependency can come from git without touching the published metadata, with -`[tool.uv.sources]` (`cunumpy = { git = "https://github.com/org/cunumpy", branch = "devel" }`) -next to a plain `"cunumpy>=0.6.3"` requirement. Run the check locally with +The `Test Pytest` workflow also builds the wheel on pull requests to `main` and fails if its +metadata has a direct reference. Run the check locally with `uv run --no-project --with packaging python scripts/check_pypi_dependencies.py`. +### Development against the devel branches + +Pull requests to `devel` test against the devel branches of the max-models packages, pinned in +`uv.lock`; pull requests to `main` and the release use PyPI only (CI sets `UV_NO_SOURCES`). Each +max-models dependency is a plain `"cunumpy>=0.6.3"` requirement plus an entry in +`[tool.uv.sources]`, spelled the same in every repository: `cunumpy = { git = +"https://github.com/max-models/cunumpy", branch = "devel" }`. Refresh the pinned devel commit +with `uv lock --upgrade-package cunumpy`, and reproduce a run against PyPI with +`UV_NO_SOURCES=true uv sync --extra test` (it rewrites `uv.lock`, so `git restore uv.lock` +afterwards). + ### Coverage The tests upload coverage to [Codecov](https://codecov.io/). Public repositories need no diff --git a/pyproject.toml b/pyproject.toml index c8a0308..511c0cb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,6 +20,8 @@ classifiers = [ "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.14", ] +# PyPI rejects direct references (name @ git+...), so only version ranges go here; the +# max-models packages come from their devel branches through [tool.uv.sources] dependencies = [ "numpy", ] @@ -73,3 +75,9 @@ ini_options.addopts = "-ra" run.omit = [ "*/tests/*" ] run.source = [ "app" ] report.show_missing = true + +# PRs to devel test against the devel branches of the max-models packages (pinned in +# uv.lock); PRs to main and the release use PyPI only (CI sets UV_NO_SOURCES). +[tool.uv.sources] +# one entry per max-models package in [project], spelled exactly like this in every repo: +# cunumpy = { git = "https://github.com/max-models/cunumpy", branch = "devel" }