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
2 changes: 2 additions & 0 deletions .github/workflows/plone-package.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ on:
pull_request:
branches: [main]
workflow_dispatch:
# release.yml runs the tests before it builds.
workflow_call:

permissions:
contents: read
Expand Down
135 changes: 135 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
name: Release

# Publishes to PyPI with Trusted Publishing when zest.releaser's fullrelease
# pushes a version tag such as 2.0.0b1 (no "v" prefix).
#
# Every action is pinned to a full commit SHA of a release that has been out
# for at least two weeks (cooldown). The comment after each SHA is the tag.

on:
push:
tags:
- '[0-9]*'

permissions:
contents: read

# Never cancel a release half way. The group must differ from the one in
# plone-package.yml, which runs inside this workflow (group names ignore case).
concurrency:
group: publish-${{ github.ref }}
cancel-in-progress: false

jobs:
test:
# actionlint 1.7.12 does not know the self-repository syntax ($/...) yet.
# A reusable workflow given with ./ is read from the commit of this run,
# not from the files of a runner.
uses: ./.github/workflows/plone-package.yml # zizmor: ignore[self-repository]

build:
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
with:
enable-cache: false
- name: Check that the tag is the version in pyproject.toml
env:
TAG: ${{ github.ref_name }}
run: |
version=$(python3 -c 'import tomllib; print(tomllib.load(open("pyproject.toml", "rb"))["project"]["version"])')
if [ "$version" != "$TAG" ]; then
echo "::error::The tag $TAG is not the version $version in pyproject.toml"
exit 1
fi
- name: Build the sdist and the wheel
run: uv build --out-dir dist
- name: Check the distributions
run: uvx twine@7.0.0 check --strict dist/*
- name: Check that the wheel contains the jar
run: |
files=$(unzip -Z1 dist/*.whl)
name=c2/transform/msoffice/msdoc2txt.jar
if ! grep -qxF "$name" <<< "$files"; then
echo "::error::$name is not in the wheel"
exit 1
fi
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: dist
path: dist/
if-no-files-found: error

publish:
needs: build
runs-on: ubuntu-latest
environment:
name: pypi
url: https://pypi.org/p/c2.transform.msoffice
permissions:
# Trusted Publishing
id-token: write
steps:
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: dist
path: dist/
- uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2

github-release:
needs: publish
runs-on: ubuntu-latest
permissions:
# gh release create
contents: write
# Sigstore
id-token: write
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: dist
path: dist/
- name: Sign the distributions
uses: sigstore/gh-action-sigstore-python@790bc6befb9d733738f18d8f895854b453640ec9 # v3.5.0
with:
inputs: >-
./dist/*.tar.gz
./dist/*.whl
- name: Take the notes of this version from CHANGELOG.md
env:
TAG: ${{ github.ref_name }}
run: |
# The section starts at "## <version> (<date>)" and ends before the
# next "## " heading.
awk -v heading="## $TAG (" '
index($0, heading) == 1 { found = 1; next }
found && /^## / { exit }
found { print }
' CHANGELOG.md > "$RUNNER_TEMP/notes.md"
if ! grep -q '[^[:space:]]' "$RUNNER_TEMP/notes.md"; then
echo "::error::No section for $TAG in CHANGELOG.md"
exit 1
fi
- name: Create the GitHub Release
env:
GH_TOKEN: ${{ github.token }}
TAG: ${{ github.ref_name }}
run: |
# A version with a, b or rc (PEP 440) is a pre-release.
prerelease=()
if [[ "$TAG" =~ (a|b|rc)[0-9]+$ ]]; then
prerelease=(--prerelease)
fi
gh release create "$TAG" dist/* \
--repo "$GITHUB_REPOSITORY" \
--verify-tag \
--title "$TAG" \
--notes-file "$RUNNER_TEMP/notes.md" \
"${prerelease[@]}"
38 changes: 37 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
- `setuphandlers.py`: install and uninstall (`post_handler`s).
- `msdoc2txt.jar`: the bundled Java program (Apache POI 5.5.1), built
from `java/` with `java/build.sh`; do not change it by hand.
- `tests/`: pytest tests (not shipped in the wheel).
- `tests/`: pytest tests (not shipped in the wheel or the sdist).
- `test_transform.py`, `test_config.py`: unit tests; `java` is replaced.
- `test_setup.py`: install and uninstall in a Plone site.
- `test_java.py`: runs the real jar on the files in `tests/samples/`.
Expand Down Expand Up @@ -51,6 +51,42 @@ After a change in `java/`, run `java/build.sh` (needs Docker) and
the jar with the change; CI rebuilds it and fails if it differs. If the
output changes, find out why before you change a file in `java/expected/`.

## Releasing

Releases are made with zest.releaser on a maintainer's machine and published
to PyPI by `.github/workflows/release.yml` with Trusted Publishing; no PyPI
token is used. The version is kept only in `pyproject.toml`.

1. Install the tools of the `release` extra in a virtual environment that
has pip (check-manifest builds the sdist in an isolated environment made
with pip):

```shell
pip install "zest.releaser[recommended]" zestreleaser.towncrier zest.pocompile
```

On an up-to-date `main` with a clean working tree, run `fullrelease`.
It runs check-manifest and pyroma, writes the news fragments into
`CHANGELOG.md`, sets the version, commits, makes the tag (such as
`2.0.0b1`, without `v`), sets the next development version and asks
whether to push. Answer yes: it runs `git push` and `git push --tags`.
It does not upload anything (`release = false`).
2. The tag starts the Release workflow on GitHub Actions: the tests of
`plone-package.yml`, then the build (it stops if the tag is not the
version in `pyproject.toml` or the wheel has no `msdoc2txt.jar`).
3. The `publish` job waits for a reviewer of the `pypi` environment to
approve it, then uploads the sdist and the wheel to PyPI.
4. The `github-release` job signs them with Sigstore and makes a GitHub
Release with the section of this version from `CHANGELOG.md` and the
files. Versions with `a`, `b` or `rc` are marked as pre-releases.

Once, before the first release:

- On PyPI, add a trusted publisher to the project: owner `cmscom`,
repository `c2.transform.msoffice`, workflow `release.yml`, environment
`pypi`.
- On GitHub, add the environment `pypi` with required reviewers.

## Test samples

All files in `tests/samples/` were made for this package; do not add
Expand Down
2 changes: 2 additions & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
graft src/c2
include *.md
include pyproject.toml
exclude AGENTS.md
exclude tox.ini
exclude *.txt
prune tests
global-exclude *.pyc
include LICENSE.GPL
include LICENSE.txt
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@ formats are passed to the jar but are not covered by the tests.

## Installation

1. Install Java (a headless JRE is enough) so that `java` is on the `PATH` of
the Zope process.
1. Install Java 11 or later (a headless JRE is enough) so that `java` is on
the `PATH` of the Zope process.
2. Add `c2.transform.msoffice` to your project's dependencies, for example
with pip:

Expand Down
16 changes: 14 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ keywords = [
"search",
]
classifiers = [
"Development Status :: 5 - Production/Stable",
"Development Status :: 4 - Beta",
"Environment :: Web Environment",
"Framework :: Plone",
"Framework :: Plone :: 6.0",
Expand Down Expand Up @@ -227,5 +227,17 @@ source_pkgs = ["c2.transform.msoffice", "tests"]
branch = true
parallel = true

[tool.check-manifest]
# Files in git that are not in the sdist. The sdist has the package, the
# README, the changelog and the licenses only; MANIFEST.in leaves out the
# rest (tests/ with "prune"), and check-manifest ignores .github/ itself.
# The Java source is in the git repository only.
ignore = [
".editorconfig",
"java/**",
"news/**",
]

[tool.zest-releaser]
python-file-with-version = "src/c2/transform/msoffice/__init__.py"
# fullrelease makes the tag; the release workflow uploads to PyPI.
release = false
7 changes: 5 additions & 2 deletions src/c2/transform/msoffice/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,14 @@
# Importing config registers the Office types with the mimetypes module.
from c2.transform.msoffice import config # noqa: F401

import logging
from importlib.metadata import version

import logging

__version__ = "2.0.0.dev0"

PACKAGE_NAME = "c2.transform.msoffice"

# The version is kept only in pyproject.toml, where zest.releaser changes it.
__version__ = version(PACKAGE_NAME)

logger = logging.getLogger(PACKAGE_NAME)
5 changes: 3 additions & 2 deletions tests/test_imports.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
def test_version():
def test_version_is_the_one_of_the_distribution():
from c2.transform.msoffice import __version__
from importlib.metadata import version

assert __version__ == "2.0.0.dev0"
assert __version__ == version("c2.transform.msoffice")


def test_c2_is_a_pep420_namespace():
Expand Down
Loading