Skip to content

Commit 19e33a7

Browse files
authored
fix(release-script): install a module after the siblings it depends on (#513)
Step 4 installs each train sibling on its own — install -f <module>/pom.xml, not a reactor build — so everything it needs must already be in the local repository at the version the bump just wrote. That version exists in no reactor and is not yet on Central, so an install that comes too early cannot resolve it. render-pptx was listed before testing while depending on it at test scope, and had been since the PPTX text-fidelity work. Nothing caught it: the guard checked that the list contains every sibling the examples need, not that the order respects the graph. And a cut only fails on it when the local repository does not already hold graph-compose-testing at the new version — the normal state of a clean machine, not of one that has been building all week. The 2.1.1 cut hit it and stopped at Step 4, after the bump had rewritten thirty files and before any commit, tag or push. testing now installs second. The guard derives the order it requires from the poms rather than restating it, so a new edge fails the build instead of the cut. That guard needed its own fix to work at all: it matched only ${graphcompose.version}, which is how examples/pom.xml pins a sibling, while a module inside the train uses ${project.version}. Reusing that pattern found no dependencies for render-pptx, so the order check passed over the very edge that broke the cut — a guard reading the wrong spelling reports on an empty set and calls it clean. Verified from both sides: with the order fixed it passes, and with the original order it fails naming "render-pptx (position 5) needs testing, installed at 7".
1 parent a2a3e14 commit 19e33a7

3 files changed

Lines changed: 128 additions & 3 deletions

File tree

CHANGELOG.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,18 @@ follow semantic versioning; release dates are ISO 8601.
77

88
### Build
99

10+
- **The cut installs a module after the things it needs.** Step 4 installs each train
11+
sibling on its own, so everything it depends on has to be in the local repository at
12+
the version the bump just wrote — a version that exists in no reactor and not yet on
13+
Central. `render-pptx` was listed before `testing` while depending on it, and had been
14+
since PPTX gained its text-fidelity suite. That stayed invisible: the cut only fails on
15+
it when the local repository does not already hold `graph-compose-testing` at the new
16+
version, which is the normal state of a clean machine and not of one that has been
17+
building all week. The 2.1.1 cut hit it and stopped at Step 4 — after the version bump
18+
had rewritten thirty files, before any commit, tag or push. `testing` now installs
19+
second, and `ReleaseScriptInstallListGuardTest` derives the required order from the
20+
poms rather than restating it, so a new edge cannot be added without failing the build.
21+
1022
- **CI opens the Javadoc jar it is about to publish.** The existing step lints the
1123
engine's sources, which says nothing about whether the artefact Maven Central serves
1224
has anything in it — and that was the failure: `graph-compose` carries no sources of

core/src/test/java/com/demcha/documentation/ReleaseScriptInstallListGuardTest.java

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@
55
import java.io.IOException;
66
import java.nio.file.Files;
77
import java.nio.file.Path;
8+
import java.util.ArrayList;
89
import java.util.LinkedHashSet;
10+
import java.util.List;
11+
import java.util.Map;
912
import java.util.Set;
1013
import java.util.regex.Matcher;
1114
import java.util.regex.Pattern;
@@ -47,6 +50,20 @@ class ReleaseScriptInstallListGuardTest {
4750
"<artifactId>(graph-compose[a-z-]*)</artifactId>\\s*"
4851
+ "<version>\\$\\{graphcompose\\.version}</version>");
4952

53+
/**
54+
* A {@code graph-compose-*} dependency inside a train module.
55+
*
56+
* <p>Separate from {@link #TRAIN_DEPENDENCY} because the two spell the version
57+
* differently: {@code examples/pom.xml} pins {@code ${graphcompose.version}},
58+
* while a sibling inside the train carries {@code ${project.version}}. Reusing
59+
* the examples pattern here matched nothing and the order check passed over the
60+
* very edge that broke the 2.1.1 cut — a guard reading the wrong spelling
61+
* reports on an empty set and calls it clean.</p>
62+
*/
63+
private static final Pattern MODULE_TRAIN_DEPENDENCY = Pattern.compile(
64+
"<artifactId>(graph-compose[a-z-]*)</artifactId>\\s*"
65+
+ "<version>\\$\\{(?:project|graphcompose)\\.version}</version>");
66+
5067
/** The literal PowerShell array the script installs from. */
5168
private static final Pattern INSTALL_LIST = Pattern.compile(
5269
"\\$exampleSnapshotSiblings\\s*=\\s*@\\(([^)]*)\\)", Pattern.DOTALL);
@@ -66,6 +83,91 @@ void releaseScriptInstallsEveryTrainSiblingTheExamplesDependOn() throws IOExcept
6683
.containsAll(required);
6784
}
6885

86+
/**
87+
* A module is installed after everything it depends on.
88+
*
89+
* <p>Step 4 installs each sibling on its own — {@code install -f <module>/pom.xml},
90+
* not a reactor build — so every dependency has to be in the local repository
91+
* already, at the version the bump just wrote. That version exists nowhere else:
92+
* not in a reactor, not on Central. Install a module before its dependency and
93+
* Maven stops with "Could not find artifact …:&lt;new version&gt;", after the tree
94+
* has been rewritten.</p>
95+
*
96+
* <p>Membership was guarded; order was not. {@code render-pptx} has depended on
97+
* {@code testing} since the PPTX text-fidelity work while being installed before
98+
* it, and the cut went green anyway whenever the local repository happened to
99+
* hold that artifact from an earlier build. On a clean machine it does not, and
100+
* the 2.1.1 cut stopped there. The order is derived from the poms rather than
101+
* restated here, so a new edge cannot be added without this noticing.</p>
102+
*/
103+
@Test
104+
void everyInstalledModuleFollowsTheSiblingsItDependsOn() throws IOException {
105+
List<String> order = scriptInstallOrder();
106+
List<String> violations = new ArrayList<>();
107+
108+
for (int i = 0; i < order.size(); i++) {
109+
String module = order.get(i);
110+
for (String dependency : trainSiblingsOf(module)) {
111+
int at = order.indexOf(dependency);
112+
if (at > i) {
113+
violations.add("%s (position %d) needs %s, installed at %d"
114+
.formatted(module, i + 1, dependency, at + 1));
115+
}
116+
}
117+
}
118+
119+
assertThat(violations)
120+
.describedAs("cut-release.ps1 installs these one at a time, so a module listed "
121+
+ "before something it depends on cannot resolve it: the bumped version is "
122+
+ "in no reactor and not yet on Central. This fails the cut at Step 4, with "
123+
+ "the version bump already written across the tree")
124+
.isEmpty();
125+
}
126+
127+
/** The module directories the script installs, in the order it installs them. */
128+
private static List<String> scriptInstallOrder() throws IOException {
129+
String script = Files.readString(PROJECT_ROOT.resolve("scripts/cut-release.ps1"));
130+
Matcher list = INSTALL_LIST.matcher(script);
131+
assertThat(list.find())
132+
.describedAs("cut-release.ps1 no longer declares $exampleSnapshotSiblings")
133+
.isTrue();
134+
135+
List<String> modules = new ArrayList<>();
136+
// The engine is installed by its own command immediately before the loop, so
137+
// it precedes every entry and belongs at the head of the order.
138+
modules.add("core");
139+
Matcher path = Pattern.compile("'([^']+)/pom\\.xml'").matcher(list.group(1));
140+
while (path.find()) {
141+
modules.add(path.group(1));
142+
}
143+
return modules;
144+
}
145+
146+
/** Module directories of the train-versioned siblings {@code module} declares. */
147+
private static Set<String> trainSiblingsOf(String module) throws IOException {
148+
String pom = Files.readString(PROJECT_ROOT.resolve(module + "/pom.xml"))
149+
.replaceAll("(?s)<parent>.*?</parent>", "");
150+
Set<String> modules = new LinkedHashSet<>();
151+
Matcher matcher = MODULE_TRAIN_DEPENDENCY.matcher(pom);
152+
while (matcher.find()) {
153+
String directory = DIRECTORY_BY_ARTIFACT.get(matcher.group(1));
154+
if (directory != null) {
155+
modules.add(directory);
156+
}
157+
}
158+
return modules;
159+
}
160+
161+
/** Artifact id to the directory holding its pom, for the train-versioned modules. */
162+
private static final Map<String, String> DIRECTORY_BY_ARTIFACT = Map.of(
163+
"graph-compose-core", "core",
164+
"graph-compose-render-pdf", "render-pdf",
165+
"graph-compose-render-docx", "render-docx",
166+
"graph-compose-render-pptx", "render-pptx",
167+
"graph-compose-templates", "templates",
168+
"graph-compose-testing", "testing",
169+
"graph-compose", "wrapper");
170+
69171
/**
70172
* Artifact ids of the train-versioned {@code graph-compose-*} siblings the
71173
* examples depend on, ignoring the {@code <parent>} coordinate.

scripts/cut-release.ps1

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -613,9 +613,20 @@ function Build-ExampleCatalogue {
613613
# on but that is missing here fails Step 4 with "Could not find artifact
614614
# …:<new version>", because the just-bumped version exists nowhere yet.
615615
# ReleaseScriptInstallListGuardTest fails the build if the two drift apart.
616-
# render-pptx must follow render-pdf: it depends on it at compile scope.
617-
$exampleSnapshotSiblings = @('render-pdf/pom.xml', 'wrapper/pom.xml', 'render-docx/pom.xml',
618-
'render-pptx/pom.xml', 'templates/pom.xml', 'testing/pom.xml')
616+
#
617+
# ORDER IS PART OF THE CONTRACT, not presentation. Each module is installed on
618+
# its own, so anything it needs must already be in the local repository at the
619+
# just-bumped version — and that version exists nowhere else, not in the
620+
# reactor and not on Central. Two edges matter:
621+
# render-pdf before render-pptx and wrapper (compile scope)
622+
# testing before render-pptx (test scope, since #407)
623+
# The second was wrong from the moment render-pptx took that dependency, and
624+
# stayed invisible: a cut only fails on it when the local repository does not
625+
# already hold graph-compose-testing at the new version, which is the normal
626+
# state of a clean machine. The 2.1.1 cut hit it and stopped at Step 4 —
627+
# before any commit, tag or push, which is the one thing that went right.
628+
$exampleSnapshotSiblings = @('render-pdf/pom.xml', 'testing/pom.xml', 'wrapper/pom.xml',
629+
'render-docx/pom.xml', 'render-pptx/pom.xml', 'templates/pom.xml')
619630
if ($DryRun) {
620631
Write-Host " [DRY RUN] $mvnw -B -ntp -DskipTests install -pl :graph-compose-core" -ForegroundColor Yellow
621632
foreach ($modulePom in $exampleSnapshotSiblings) {

0 commit comments

Comments
 (0)