stages: - test - pypi-twine # --------------------------------------------------------------------------- # Tests run on every push to any branch. # # Live-integration tests need EPMD on the runner — ``make all`` skips # them automatically when EPMD isn't reachable. CI runners typically # don't run EPMD, so we get unit-level coverage in CI and full # integration coverage on the dev machine. # --------------------------------------------------------------------------- test: stage: test tags: ["build"] except: - tags script: - python3 -m venv .venv - . .venv/bin/activate - pip install --upgrade pip wheel - pip install -e ".[dev]" - pytest -v # --------------------------------------------------------------------------- # Ship to PyPI on tag pushes. # # Auth: TWINE_USERNAME + TWINE_PASSWORD env vars come from project-scoped # GitLab CI variables (Settings → CI/CD → Variables, masked + protected). # Standard values: TWINE_USERNAME=__token__ and TWINE_PASSWORD=. # # Trusted Publishing OIDC would be cleaner but PyPI's GitLab provider is # hardcoded to gitlab.com — git.unturf.com self-hosted isn't supported. # See docs/PYPI-TRUSTED-PUBLISHING.md for the migration recipe whenever # PyPI adds custom-issuer support (or whenever we mirror to gitlab.com). # --------------------------------------------------------------------------- pypi-twine: stage: pypi-twine tags: ["build"] only: - tags script: # Sanity-check that the group-scoped CI vars actually landed in the # env on this pipeline. Prints "set" or "MISSING" — never the value, # never echoes them anywhere — so masked/protected flags stay safe. - 'test -n "${TWINE_USERNAME:-}" && echo "TWINE_USERNAME: set" || echo "TWINE_USERNAME: MISSING (check group vars Protected flag vs tag protection)"' - 'test -n "${TWINE_PASSWORD:-}" && echo "TWINE_PASSWORD: set" || echo "TWINE_PASSWORD: MISSING (check group vars Protected flag vs tag protection)"' - python3 -m venv .venv - . .venv/bin/activate - pip install --upgrade pip - pip install build twine - python -m build - twine check dist/* # Twine 6 prefers Trusted Publishing IF TWINE_USERNAME/TWINE_PASSWORD # are unset. With both set, classic auth is used directly. If the # diagnostic above shows MISSING, fix the group var Protected flag # or mark tags as Protected refs on the python/ group. - twine upload --non-interactive dist/*