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
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,18 @@ follow semantic versioning; release dates are ISO 8601.

### Build

- **The cut installs a module after the things it needs.** Step 4 installs each train
sibling on its own, so everything it depends on has to be in the local repository at
the version the bump just wrote — a version that exists in no reactor and not yet on
Central. `render-pptx` was listed before `testing` while depending on it, and had been
since PPTX gained its text-fidelity suite. That stayed invisible: the cut only fails on
it when the local repository does not already hold `graph-compose-testing` at the new
version, which is the normal state of a clean machine and not of one that has been
building all week. The 2.1.1 cut hit it and stopped at Step 4 — after the version bump
had rewritten thirty files, before any commit, tag or push. `testing` now installs
second, and `ReleaseScriptInstallListGuardTest` derives the required order from the
poms rather than restating it, so a new edge cannot be added without failing the build.

- **CI opens the Javadoc jar it is about to publish.** The existing step lints the
engine's sources, which says nothing about whether the artefact Maven Central serves
has anything in it — and that was the failure: `graph-compose` carries no sources of
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
Expand Down Expand Up @@ -47,6 +50,20 @@ class ReleaseScriptInstallListGuardTest {
"<artifactId>(graph-compose[a-z-]*)</artifactId>\\s*"
+ "<version>\\$\\{graphcompose\\.version}</version>");

/**
* A {@code graph-compose-*} dependency inside a train module.
*
* <p>Separate from {@link #TRAIN_DEPENDENCY} because the two spell the version
* differently: {@code examples/pom.xml} pins {@code ${graphcompose.version}},
* while a sibling inside the train carries {@code ${project.version}}. Reusing
* the examples pattern here matched nothing and the order check passed over the
* very edge that broke the 2.1.1 cut — a guard reading the wrong spelling
* reports on an empty set and calls it clean.</p>
*/
private static final Pattern MODULE_TRAIN_DEPENDENCY = Pattern.compile(
"<artifactId>(graph-compose[a-z-]*)</artifactId>\\s*"
+ "<version>\\$\\{(?:project|graphcompose)\\.version}</version>");

/** The literal PowerShell array the script installs from. */
private static final Pattern INSTALL_LIST = Pattern.compile(
"\\$exampleSnapshotSiblings\\s*=\\s*@\\(([^)]*)\\)", Pattern.DOTALL);
Expand All @@ -66,6 +83,91 @@ void releaseScriptInstallsEveryTrainSiblingTheExamplesDependOn() throws IOExcept
.containsAll(required);
}

/**
* A module is installed after everything it depends on.
*
* <p>Step 4 installs each sibling on its own — {@code install -f <module>/pom.xml},
* not a reactor build — so every dependency has to be in the local repository
* already, at the version the bump just wrote. That version exists nowhere else:
* not in a reactor, not on Central. Install a module before its dependency and
* Maven stops with "Could not find artifact …:&lt;new version&gt;", after the tree
* has been rewritten.</p>
*
* <p>Membership was guarded; order was not. {@code render-pptx} has depended on
* {@code testing} since the PPTX text-fidelity work while being installed before
* it, and the cut went green anyway whenever the local repository happened to
* hold that artifact from an earlier build. On a clean machine it does not, and
* the 2.1.1 cut stopped there. The order is derived from the poms rather than
* restated here, so a new edge cannot be added without this noticing.</p>
*/
@Test
void everyInstalledModuleFollowsTheSiblingsItDependsOn() throws IOException {
List<String> order = scriptInstallOrder();
List<String> violations = new ArrayList<>();

for (int i = 0; i < order.size(); i++) {
String module = order.get(i);
for (String dependency : trainSiblingsOf(module)) {
int at = order.indexOf(dependency);
if (at > i) {
violations.add("%s (position %d) needs %s, installed at %d"
.formatted(module, i + 1, dependency, at + 1));
}
}
}

assertThat(violations)
.describedAs("cut-release.ps1 installs these one at a time, so a module listed "
+ "before something it depends on cannot resolve it: the bumped version is "
+ "in no reactor and not yet on Central. This fails the cut at Step 4, with "
+ "the version bump already written across the tree")
.isEmpty();
}

/** The module directories the script installs, in the order it installs them. */
private static List<String> scriptInstallOrder() throws IOException {
String script = Files.readString(PROJECT_ROOT.resolve("scripts/cut-release.ps1"));
Matcher list = INSTALL_LIST.matcher(script);
assertThat(list.find())
.describedAs("cut-release.ps1 no longer declares $exampleSnapshotSiblings")
.isTrue();

List<String> modules = new ArrayList<>();
// The engine is installed by its own command immediately before the loop, so
// it precedes every entry and belongs at the head of the order.
modules.add("core");
Matcher path = Pattern.compile("'([^']+)/pom\\.xml'").matcher(list.group(1));
while (path.find()) {
modules.add(path.group(1));
}
return modules;
}

/** Module directories of the train-versioned siblings {@code module} declares. */
private static Set<String> trainSiblingsOf(String module) throws IOException {
String pom = Files.readString(PROJECT_ROOT.resolve(module + "/pom.xml"))
.replaceAll("(?s)<parent>.*?</parent>", "");
Set<String> modules = new LinkedHashSet<>();
Matcher matcher = MODULE_TRAIN_DEPENDENCY.matcher(pom);
while (matcher.find()) {
String directory = DIRECTORY_BY_ARTIFACT.get(matcher.group(1));
if (directory != null) {
modules.add(directory);
}
}
return modules;
}

/** Artifact id to the directory holding its pom, for the train-versioned modules. */
private static final Map<String, String> DIRECTORY_BY_ARTIFACT = Map.of(
"graph-compose-core", "core",
"graph-compose-render-pdf", "render-pdf",
"graph-compose-render-docx", "render-docx",
"graph-compose-render-pptx", "render-pptx",
"graph-compose-templates", "templates",
"graph-compose-testing", "testing",
"graph-compose", "wrapper");

/**
* Artifact ids of the train-versioned {@code graph-compose-*} siblings the
* examples depend on, ignoring the {@code <parent>} coordinate.
Expand Down
17 changes: 14 additions & 3 deletions scripts/cut-release.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -613,9 +613,20 @@ function Build-ExampleCatalogue {
# on but that is missing here fails Step 4 with "Could not find artifact
# …:<new version>", because the just-bumped version exists nowhere yet.
# ReleaseScriptInstallListGuardTest fails the build if the two drift apart.
# render-pptx must follow render-pdf: it depends on it at compile scope.
$exampleSnapshotSiblings = @('render-pdf/pom.xml', 'wrapper/pom.xml', 'render-docx/pom.xml',
'render-pptx/pom.xml', 'templates/pom.xml', 'testing/pom.xml')
#
# ORDER IS PART OF THE CONTRACT, not presentation. Each module is installed on
# its own, so anything it needs must already be in the local repository at the
# just-bumped version — and that version exists nowhere else, not in the
# reactor and not on Central. Two edges matter:
# render-pdf before render-pptx and wrapper (compile scope)
# testing before render-pptx (test scope, since #407)
# The second was wrong from the moment render-pptx took that dependency, and
# stayed invisible: a cut only fails on it when the local repository does not
# already hold graph-compose-testing at the new version, which is the normal
# state of a clean machine. The 2.1.1 cut hit it and stopped at Step 4 —
# before any commit, tag or push, which is the one thing that went right.
$exampleSnapshotSiblings = @('render-pdf/pom.xml', 'testing/pom.xml', 'wrapper/pom.xml',
'render-docx/pom.xml', 'render-pptx/pom.xml', 'templates/pom.xml')
if ($DryRun) {
Write-Host " [DRY RUN] $mvnw -B -ntp -DskipTests install -pl :graph-compose-core" -ForegroundColor Yellow
foreach ($modulePom in $exampleSnapshotSiblings) {
Expand Down
Loading