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
7 changes: 4 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -335,8 +335,8 @@ jobs:
# reader lands on, the entry point every snippet starts from, and the type
# they spend the rest of their time in.
#
# Three passes, mirroring the order publish.yml deploys in — and the order
# is the whole point, because getting it wrong is silent.
# Three passes, and the order is the whole point, because getting it wrong
# is silent.
#
# The wrapper's javadoc jar is configured in its `release` profile with
# includeDependencySources, so it needs the ENGINE'S SOURCES JAR in the
Expand All @@ -349,7 +349,8 @@ jobs:
# published), which render-pdf needs at test scope. So: build the reactor
# profile-free to get every module including that tests jar, re-install the
# engine under `release` to add its sources jar, then build the wrapper.
# publish.yml gets there by deploying the engine before the wrapper.
# publish.yml gets there by building both in one `-P release` reactor run,
# where the engine's sources jar is attached before the wrapper needs it.
if: matrix.java == '17'
run: |
set -euo pipefail
Expand Down
250 changes: 96 additions & 154 deletions .github/workflows/publish.yml

Large diffs are not rendered by default.

38 changes: 38 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,46 @@ follow semantic versioning; release dates are ISO 8601.

## v2.4.2 — Planned

### Build

- **A release reaches Maven Central as one deployment instead of eight.** Central now counts
every distinct publish operation against an organisation's monthly Release Count, and
`publish.yml` deployed the train as eight separate `-f <module>/pom.xml deploy` runs — eight
deployments, eight validation queues and eight Publish clicks for one version. It now runs
the release profile once over the root reactor, `-P release deploy -pl` the eight train
artifacts: the `central-publishing-maven-plugin` stages every module and uploads one
`central-bundle.zip` from the last. Every coordinate, POM, jar, sources and javadoc jar and
signature is what it was — only the transaction is shared. The deployment validates as a unit,
so a failure no longer leaves the first modules of a version uploaded as separate deployments
and the rest missing.
`graph-compose-fonts` and `graph-compose-emoji` keep their own tags and workflows. The
`start_at` resume input is replaced by `skip_published`, off by default, which leaves out
components the Portal already reports as published, for recovering a version that is
partially live. The uploaded zip is kept as a workflow artifact.

### Tests

- **`PublishTrainGuardTest`** holds the deploy to exactly one `-pl` reactor run, its selection
to the lockstep modules derived from the poms (standalone, publishing, on the reactor
version), and keeps fonts, emoji and the build-only modules out of it. It forbids `-am`, which
would pull fonts and emoji into the deployment, and requires the eight `release` profiles to
declare the plugin identically, because the upload runs with the settings of the module the
reactor orders last. `PublishedModules` reads `-pl` deploys as well as `-f` ones, so the
CodeQL scope guard keeps its inventory.
- **The release smoke can run before anything is published.** `run.sh --staged-repo <dir>` /
`run.ps1 -StagedRepo <dir>` resolves the GraphCompose coordinates from an unzipped
`central-bundle.zip` — a local dry run's, or the tagged run's workflow artifact while the
deployment waits at `VALIDATED` — and everything else from Central. A scenario fails unless
every file of every train artifact it resolved came from the staged directory at the staged
version.

### Documentation

- **The release runbook describes the single deployment.** `docs/contributing/release-process.md`
explains the one-deployment train, how to dry-run it against a dead endpoint and smoke the
staged bundle, and replaces the per-module `start_at` recovery with the failure cases a single
deployment has.

- **A card of a preset shows the code that draws that preset.** The catalogue carries one
compiled block per family — the CVs' builds `BoxedSections`, the invoices' `ModernInvoice` —
and every other card of the family was shown it under a caption saying it came from the
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,13 +100,14 @@ void everyDeployedModuleWithSourcesIsScanned() throws IOException {
/**
* The inventory the second test compares against is itself read out of the publish
* workflows, so it can be emptied by editing them — and an emptier inventory is an
* easier comparison, not a failing one. Both halves below key on the absence of a
* positive signal instead: a publish workflow that deploys nothing, and a train
* whose declared modules are not among the steps read from it.
* easier comparison, not a failing one. This keys on the absence of a positive signal
* instead: a publish workflow that deploys nothing this guard can read. That the
* release train's deploy names every lockstep module is held separately, against the
* poms, by {@link PublishTrainGuardTest}.
*
* <p>Writing a deploy as {@code -pl :graph-compose-fonts} rather than
* {@code -f fonts/pom.xml} is enough to do it, and nothing about that edit looks
* like it touches the scan.</p>
* <p>Writing a deploy in a shape {@link PublishedModules} does not read — neither
* {@code -f <module>/pom.xml} nor {@code -pl :artifact,…} — is enough to empty it, and
* nothing about that edit looks like it touches the scan.</p>
*/
@Test
void everyPublishWorkflowContributesToTheInventoryItIsRead() throws IOException {
Expand All @@ -130,41 +131,8 @@ void everyPublishWorkflowContributesToTheInventoryItIsRead() throws IOException
+ "which will pass without ever asking about it. Either the workflow "
+ "stopped deploying — in which case it should stop being a publish "
+ "workflow — or its deploy is written some way other than "
+ "`-f <module>/pom.xml`, and this guard has to learn that shape before "
+ "the edit lands")
.isEmpty();
}

/**
* The train {@code publish.yml} declares matches the steps it carries.
*
* <p>The workflow states its module set twice and neither statement is derived from
* the other: the {@code order} the resume input is validated against, and the deploy
* steps themselves. A module dropped from the steps — or written in a shape this
* guard cannot read — leaves the two disagreeing, which is the signal that the
* inventory shrank rather than the train.</p>
*/
@Test
void theDeployStepsCoverThePublishTrainTheWorkflowDeclares() throws IOException {
List<String> declared = PublishedModules.declaredTrain(PROJECT_ROOT);
List<String> steps = PublishedModules.deployedByWorkflow(PROJECT_ROOT)
.getOrDefault("publish.yml", List.of());

assertThat(declared)
.describedAs("publish.yml no longer declares its train as `order=\"...\"` — the "
+ "resume validation moved, and with it the second, independent statement "
+ "of what a release publishes that this guard holds the steps against")
.isNotEmpty();

Set<String> missing = new TreeSet<>(declared);
missing.removeAll(steps);

assertThat(missing)
.describedAs("publish.yml names these in its train but this guard finds no deploy "
+ "step for them. Either the module stopped shipping and belongs out of "
+ "the train, or its step is written some way other than "
+ "`-f <module>/pom.xml` — in which case the module is deployed, absent "
+ "from the inventory, and therefore never checked against the scan")
+ "`-f <module>/pom.xml` or `-pl :artifact,…`, and PublishedModules has to "
+ "learn that shape before the edit lands")
.isEmpty();
}

Expand Down
Loading
Loading