.. _releasing: ******************* Releasing to PyPI ******************* The distribution on PyPI is called ``xnns``; the import package, the command line and the GitHub repository are ``xnn`` (the PyPI name ``xnn`` belongs to someone else). Releases are built and uploaded by the GitHub Actions workflow ``.github/workflows/release.yml`` using `PyPI Trusted Publishing `_, so no API token is stored anywhere. What the workflow does ====================== * On every pushed tag ``vX.Y.Z``: build the sdist and wheel with ``python -m build``, run ``twine check --strict``, verify that the tag equals ``v`` + ``xnn.__version__``, verify that the ``.frc`` force-field files and the D3 tables are inside the wheel, install the wheel into a clean virtual environment and import it, upload to PyPI, then create the GitHub Release for the tag (auto-generated notes) with the files attached. Plain ``git`` is all that is needed on your side. * On a manual **Run workflow**: ``target = testpypi`` uploads the same build to `TestPyPI `_ as a rehearsal, from any branch or tag; ``target = pypi`` publishes a tag that was pushed earlier (select the tag under *Use workflow from*; it must equal ``v`` + ``__version__``); ``target = none`` only builds and checks. The version is defined once, in ``src/xnn/__init__.py`` (``__version__``); ``pyproject.toml`` reads it (``dynamic = ["version"]``). One-time setup ============== 1. **GitHub environments.** In the repository go to *Settings > Environments* and create ``pypi`` and ``testpypi``. Optionally add yourself as a required reviewer of ``pypi`` so every upload needs an explicit approval. 2. **PyPI.** Log in at https://pypi.org, open *Your account > Publishing* and add a *pending* trusted publisher (the project does not exist yet, it is created by the first upload): * PyPI project name: ``xnns`` * Owner: ``molssi-ai`` * Repository name: ``xnn`` (the name the repository has *when the workflow runs*) * Workflow name: ``release.yml`` * Environment name: ``pypi`` 3. **TestPyPI.** The same at https://test.pypi.org with environment ``testpypi``. Cutting a release ================= 1. Bump ``__version__`` in ``src/xnn/__init__.py``, make sure ``pytest tests`` and the strict docs build pass, commit and push to ``main``. 2. Rehearse: *Actions > Release > Run workflow* with ``target = testpypi``. Check https://test.pypi.org/p/xnns and, in a scratch environment, ``pip install --index-url https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple/ xnns``. 3. Tag and push (the tag must equal ``v`` + ``__version__`` or the workflow fails before uploading): .. code-block:: bash git tag -a v0.1.0 -m "xnn 0.1.0" git push origin v0.1.0 4. Watch *Actions > Release*. When it is green the package is at https://pypi.org/p/xnns, ``pip install xnns`` works, and the GitHub Release for the tag exists with the sdist and wheel attached. A version can be uploaded to PyPI only once. If a release has to be redone, bump the version (for example ``0.1.1``) and publish a new release. Metadata rules that PyPI enforces ================================= * No direct URL requirements in the metadata: the Allegro reference implementation (``mir-group/allegro``, a git dependency) is therefore not part of any extra and is installed by hand for its fidelity notebook. * ``twine check --strict`` must pass, so the README has to be valid Markdown with absolute links only. * The ``license`` field is an SPDX expression (``MIT``), which needs ``setuptools >= 77`` at build time. The BSD-3 notice of the vendored SEAMM force-field files travels inside the package itself (``xnn/ffnn/data/LICENSE-SEAMM``), so it is in every wheel and sdist.