From 05d894c8a11c91a31c190bfde8635a2ec1b71d87 Mon Sep 17 00:00:00 2001 From: DemchaAV Date: Fri, 29 May 2026 01:47:20 +0100 Subject: [PATCH 1/2] chore(build): inherit module versions via aggregator reactor examples and benchmarks inherit their version from a new, non-published graphcompose-build aggregator (pom packaging) instead of hardcoding it. Removes the benchmarks graphcompose.version literal that caused drift after a bump; the dependency now tracks ${project.version}. The library root pom.xml is unchanged so JitPack coordinates are unaffected. A single `mvn -f aggregator/pom.xml versions:set -DnewVersion=X` bumps every module atomically. --- aggregator/pom.xml | 30 ++++++++++++++++++++++++++++++ benchmarks/pom.xml | 11 ++++++++--- examples/pom.xml | 9 +++++++-- 3 files changed, 45 insertions(+), 5 deletions(-) create mode 100644 aggregator/pom.xml diff --git a/aggregator/pom.xml b/aggregator/pom.xml new file mode 100644 index 00000000..e6865733 --- /dev/null +++ b/aggregator/pom.xml @@ -0,0 +1,30 @@ + + + 4.0.0 + + io.github.demchaav + graphcompose-build + 1.6.4 + pom + + GraphCompose Build Aggregator + + Non-published reactor aggregator. Builds the library plus the examples + and benchmarks modules in one pass and gives a single entry point for + `versions:set`, so a version bump propagates to every module at once. + This is NOT a Maven parent and is NOT published to JitPack: the library + root pom.xml stays standalone so consumer coordinates never change. + + Usage: + mvn -f aggregator/pom.xml -DskipTests install # build everything + mvn -f aggregator/pom.xml versions:set -DnewVersion=X # bump all modules + + + + .. + ../examples + ../benchmarks + + diff --git a/benchmarks/pom.xml b/benchmarks/pom.xml index 491def51..2d6582cd 100644 --- a/benchmarks/pom.xml +++ b/benchmarks/pom.xml @@ -4,9 +4,14 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 - io.github.demchaav + + io.github.demchaav + graphcompose-build + 1.6.4 + ../aggregator/pom.xml + + graphcompose-benchmarks - 1.6.4 GraphCompose Benchmarks Performance benchmarks, stress tests, and endurance harnesses for @@ -16,7 +21,7 @@ - 1.6.4 + ${project.version} 17 6.1.0 diff --git a/examples/pom.xml b/examples/pom.xml index 67904c15..66ae456e 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -4,9 +4,14 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 - io.github.demchaav + + io.github.demchaav + graphcompose-build + 1.6.4 + ../aggregator/pom.xml + + graphcompose-examples - 1.6.4 GraphCompose Examples Runnable file-render examples for GraphCompose templates. From 5f830711439041813994a2baddf7e851face0f04 Mon Sep 17 00:00:00 2001 From: DemchaAV Date: Fri, 29 May 2026 01:55:34 +0100 Subject: [PATCH 2/2] test(build): guard version consistency across modules and README MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit VersionConsistencyGuardTest asserts the GraphCompose version is identical across pom.xml, the aggregator, and the inherited examples/benchmarks modules, that benchmarks derives its dependency from ${project.version} rather than a literal, and that the README install snippets match. Wires it into the fast CI guard job so a bump that misses a module — the drift that let benchmarks build against the previous release — fails fast. --- .github/workflows/ci.yml | 2 +- .../VersionConsistencyGuardTest.java | 137 ++++++++++++++++++ 2 files changed, 138 insertions(+), 1 deletion(-) create mode 100644 src/test/java/com/demcha/documentation/VersionConsistencyGuardTest.java diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 79d8dc0b..c1de5f44 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -38,7 +38,7 @@ jobs: - name: Run guards run: | ./mvnw -B -ntp clean \ - "-Dtest=EnginePdfBoundaryTest,CanonicalTemplateComposerPdfBoundaryTest,PdfRenderInterfaceGuardTest,PdfRenderingSystemECSDispatchTest,DocumentationCoverageTest,DocumentationExamplesTest,CanonicalSurfaceGuardTest,TemplateComposeApiTest" \ + "-Dtest=EnginePdfBoundaryTest,CanonicalTemplateComposerPdfBoundaryTest,PdfRenderInterfaceGuardTest,PdfRenderingSystemECSDispatchTest,DocumentationCoverageTest,DocumentationExamplesTest,CanonicalSurfaceGuardTest,TemplateComposeApiTest,VersionConsistencyGuardTest" \ test build-and-test: diff --git a/src/test/java/com/demcha/documentation/VersionConsistencyGuardTest.java b/src/test/java/com/demcha/documentation/VersionConsistencyGuardTest.java new file mode 100644 index 00000000..44ffad76 --- /dev/null +++ b/src/test/java/com/demcha/documentation/VersionConsistencyGuardTest.java @@ -0,0 +1,137 @@ +package com.demcha.documentation; + +import org.junit.jupiter.api.Test; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Guards that the GraphCompose version propagates identically to every module + * and to the README install snippets. + * + *

This is the safety net behind the aggregator-reactor version model + * (see {@code aggregator/pom.xml}): the library {@code pom.xml} is the single + * version source, the aggregator bumps every module in lockstep via + * {@code versions:set}, and the child modules inherit their version rather than + * pinning a literal. This test fails the build the moment a bump leaves any + * module — or the README copy-paste snippet — pointing at a different version, + * which is the drift class that previously let the benchmarks module run + * against the previous release. + */ +class VersionConsistencyGuardTest { + + private static final Path PROJECT_ROOT = Path.of("").toAbsolutePath().normalize(); + + @Test + void everyModuleResolvesToTheRootProjectVersion() throws Exception { + String root = effectiveVersion(PROJECT_ROOT.resolve("pom.xml")); + + assertThat(effectiveVersion(PROJECT_ROOT.resolve("aggregator/pom.xml"))) + .describedAs("aggregator/pom.xml version must equal root pom.xml version (%s)", root) + .isEqualTo(root); + assertThat(effectiveVersion(PROJECT_ROOT.resolve("examples/pom.xml"))) + .describedAs("examples must inherit the root version (%s)", root) + .isEqualTo(root); + assertThat(effectiveVersion(PROJECT_ROOT.resolve("benchmarks/pom.xml"))) + .describedAs("benchmarks must inherit the root version (%s)", root) + .isEqualTo(root); + } + + @Test + void childModulesInheritVersionInsteadOfDeclaringTheirOwn() throws Exception { + assertThat(declaresOwnVersion(PROJECT_ROOT.resolve("examples/pom.xml"))) + .describedAs("examples/pom.xml must inherit from the aggregator parent, not declare its own") + .isFalse(); + assertThat(declaresOwnVersion(PROJECT_ROOT.resolve("benchmarks/pom.xml"))) + .describedAs("benchmarks/pom.xml must inherit from the aggregator parent, not declare its own") + .isFalse(); + } + + @Test + void benchmarksDependencyDerivesVersionAndIsNotHardcoded() throws IOException { + String pom = Files.readString(PROJECT_ROOT.resolve("benchmarks/pom.xml")); + + assertThat(pom) + .describedAs("benchmarks graphcompose.version must derive from ${project.version}") + .contains("${project.version}"); + assertThat(Pattern.compile("\\s*\\d").matcher(pom).find()) + .describedAs("benchmarks must not hardcode a numeric graphcompose.version — that is the original drift source") + .isFalse(); + } + + @Test + void readmeInstallSnippetsMatchTheProjectVersion() throws Exception { + String root = effectiveVersion(PROJECT_ROOT.resolve("pom.xml")); + String readme = Files.readString(PROJECT_ROOT.resolve("README.md")); + + String mavenSnippetVersion = firstGroup(readme, + "GraphCompose\\s*v?([0-9][^<]*)"); + String gradleSnippetVersion = firstGroup(readme, + "GraphCompose:v?([0-9][^\")]*)"); + + assertThat(mavenSnippetVersion) + .describedAs("README Maven/JitPack snippet must reference the current project version (%s)", root) + .isEqualTo(root); + assertThat(gradleSnippetVersion) + .describedAs("README Gradle/JitPack snippet must reference the current project version (%s)", root) + .isEqualTo(root); + } + + private static String firstGroup(String text, String regex) { + Matcher matcher = Pattern.compile(regex).matcher(text); + assertThat(matcher.find()) + .describedAs("Expected a GraphCompose install-snippet version matching /%s/ in README.md", regex) + .isTrue(); + return matcher.group(1); + } + + private static boolean declaresOwnVersion(Path pom) throws Exception { + return directChild(parse(pom).getDocumentElement(), "version") != null; + } + + private static String effectiveVersion(Path pom) throws Exception { + Element project = parse(pom).getDocumentElement(); + + Element ownVersion = directChild(project, "version"); + if (ownVersion != null) { + return ownVersion.getTextContent().trim(); + } + Element parent = directChild(project, "parent"); + if (parent != null) { + Element parentVersion = directChild(parent, "version"); + if (parentVersion != null) { + return parentVersion.getTextContent().trim(); + } + } + throw new IllegalStateException("No / or inherited / in " + pom); + } + + private static Element directChild(Element parent, String name) { + NodeList children = parent.getChildNodes(); + for (int i = 0; i < children.getLength(); i++) { + Node node = children.item(i); + if (node.getNodeType() == Node.ELEMENT_NODE && node.getNodeName().equals(name)) { + return (Element) node; + } + } + return null; + } + + private static Document parse(Path pom) throws Exception { + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + factory.setNamespaceAware(false); + DocumentBuilder builder = factory.newDocumentBuilder(); + return builder.parse(pom.toFile()); + } +}