From 89354bbee6faf0c5214063e89fe1e28f963a73c5 Mon Sep 17 00:00:00 2001 From: DemchaAV Date: Sun, 5 Jul 2026 20:52:53 +0100 Subject: [PATCH 1/2] build: add graph-compose-core; keep graph-compose as a drop-in wrapper MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The 2.0 module split originally aimed to make bare `graph-compose` the lean core, which would make every existing consumer's `.buildPdf()` throw MissingBackendException at runtime after upgrading. Reverse that: the engine gets its own `graph-compose-core` coordinate, and `graph-compose` stays a drop-in that keeps rendering PDF, so existing consumers upgrade with zero changes. - Rename the root artifact `graph-compose` -> `graph-compose-core` (the engine sources stay at the root dir; only the coordinate string changes). - New wrapper/ module publishes the `graph-compose` coordinate as an empty jar (jar, not pom — a pom would force consumers to add pom) that depends on graph-compose-core. A `graph-compose` dependency now brings the engine transitively and renders PDF unchanged. - Repoint the engine-internal modules that need the engine classes directly (render-docx, testing, qa, benchmarks, including the tests-classifier jar) to graph-compose-core; bundle and examples stay on `graph-compose` (the wrapper). - Wire the aggregator, CI (build gate targets graph-compose-core; the examples job installs the wrapper), publish (deploy the wrapper after the core), cut-release (bump/commit/install the wrapper), jitpack (build both coordinates), and VersionConsistencyGuardTest (wrapper tracks the engine version). CHANGELOG describes the coordinate structure this ships. The core still carries the PDF backend and the templates; those move into their own modules over the rest of the 2.0 cycle. --- .github/workflows/ci.yml | 10 +- .github/workflows/publish.yml | 10 + CHANGELOG.md | 21 +- aggregator/pom.xml | 1 + benchmarks/pom.xml | 4 +- jitpack.yml | 11 +- pom.xml | 10 +- qa/pom.xml | 4 +- render-docx/pom.xml | 2 +- scripts/cut-release.ps1 | 6 +- .../VersionConsistencyGuardTest.java | 3 + testing/pom.xml | 2 +- wrapper/pom.xml | 179 ++++++++++++++++++ 13 files changed, 234 insertions(+), 29 deletions(-) create mode 100644 wrapper/pom.xml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 74ca8ce23..a7395539e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -76,7 +76,7 @@ jobs: # testing module + the engine test-jar on the classpath and so cannot # live in the engine's own test scope). -am pulls the fonts / emoji # upstreams; render-docx / bundle / examples / benchmarks are excluded. - run: ./mvnw -B -ntp -f aggregator/pom.xml clean verify -pl :graph-compose,:graph-compose-testing,:graph-compose-qa -am + run: ./mvnw -B -ntp -f aggregator/pom.xml clean verify -pl :graph-compose-core,:graph-compose-testing,:graph-compose-qa -am - name: Generate Javadoc # Run only on the baseline JDK — Javadoc output is identical @@ -116,9 +116,15 @@ jobs: # Central, so install it into the local repo before building examples. run: ./mvnw -B -ntp -f emoji/pom.xml -DskipTests install - - name: Install root artifact + - name: Install root artifact (graph-compose-core) run: ./mvnw -B -ntp -DskipTests install -pl . + - name: Install graph-compose (the compat wrapper, consumed by the examples module) + # examples depends on the graph-compose coordinate — the empty jar wrapper + # over graph-compose-core. Install it after the core so the examples build + # resolves it (and the engine classes transitively). + run: ./mvnw -B -ntp -f wrapper/pom.xml -DskipTests install + - name: Install graph-compose-render-docx (consumed by the examples module) # The DOCX export example depends on the render-docx backend module, which # is not on Central. Install it after the engine (its graph-compose diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 69f6974f0..293e23b59 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -99,6 +99,16 @@ jobs: CENTRAL_TOKEN: ${{ secrets.CENTRAL_TOKEN }} MAVEN_GPG_PASSPHRASE: ${{ secrets.MAVEN_GPG_PASSPHRASE }} + - name: Publish graph-compose (compat wrapper) to Maven Central + # The empty jar that keeps the `graph-compose` coordinate a drop-in: it + # depends on graph-compose-core, resolved from the local repo installed by + # the engine (core) deploy above. Ships on the same v* tag, lockstep. + run: ./mvnw -B -ntp -f wrapper/pom.xml -P release -DskipTests -Dgpg.skip=false deploy + env: + CENTRAL_USERNAME: ${{ secrets.CENTRAL_USERNAME }} + CENTRAL_TOKEN: ${{ secrets.CENTRAL_TOKEN }} + MAVEN_GPG_PASSPHRASE: ${{ secrets.MAVEN_GPG_PASSPHRASE }} + - name: Publish render-docx to Maven Central # The semantic DOCX/PPTX backend, lockstep-versioned with the engine # (ships on the same v* tag). graph-compose resolves from the local repo diff --git a/CHANGELOG.md b/CHANGELOG.md index f3a72e20b..f398db6d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,16 +30,17 @@ for this cycle. ### Packaging -- 2.0 splits the single `graph-compose` jar into modules: a lean core - (`graph-compose`), pluggable render backends (`graph-compose-render-pdf`, - `graph-compose-render-docx`), `graph-compose-templates`, and - `graph-compose-testing`, with render backends discovered via a `ServiceLoader` - SPI. The `graph-compose` artifact keeps its coordinate but no longer contains - the PDF backend or the templates — add `graph-compose-render-pdf` for PDF - output, or depend on `graph-compose-bundle` for a batteries-included coordinate - (core + render-pdf + templates + fonts + emoji). The optional - `graph-compose-fonts` and `graph-compose-emoji` artifacts are unchanged. Full - install guidance ships with 2.0.0. +- 2.0 splits the monolithic engine into modules. The engine now builds under a new + **`graph-compose-core`** coordinate, and the original **`graph-compose` coordinate + becomes a thin drop-in aggregator** that depends on `graph-compose-core` — so an + existing `graph-compose` dependency keeps compiling and rendering PDF unchanged. + Consumer-testing support (`graph-compose-testing`) and the semantic DOCX/PPTX + backend (`graph-compose-render-docx`) are already separate artifacts; the PDF + render backend and the built-in templates move into their own modules over the + rest of the 2.0 cycle, at which point `graph-compose` aggregates the PDF backend + and `graph-compose-core` becomes the lean, bring-your-own-backend coordinate. The + optional `graph-compose-fonts` / `graph-compose-emoji` artifacts are unchanged. + Full install guidance ships with 2.0.0. - `graph-compose-render-docx` is now a separate artifact: the semantic DOCX/PPTX backend and Apache POI leave the `graph-compose` jar. `DocxSemanticBackend` moves to `com.demcha.compose.document.backend.semantic.docx` and `PptxSemanticBackend` diff --git a/aggregator/pom.xml b/aggregator/pom.xml index e1b7814bd..860cab119 100644 --- a/aggregator/pom.xml +++ b/aggregator/pom.xml @@ -58,6 +58,7 @@ --> .. + ../wrapper ../fonts ../emoji ../render-docx diff --git a/benchmarks/pom.xml b/benchmarks/pom.xml index 760c1c133..473ea7244 100644 --- a/benchmarks/pom.xml +++ b/benchmarks/pom.xml @@ -50,7 +50,7 @@ io.github.demchaav - graph-compose + graph-compose-core ${graphcompose.version} @@ -74,7 +74,7 @@ --> io.github.demchaav - graph-compose + graph-compose-core ${graphcompose.version} test-jar tests diff --git a/jitpack.yml b/jitpack.yml index 503c23c3f..59dde1918 100644 --- a/jitpack.yml +++ b/jitpack.yml @@ -1,14 +1,15 @@ -# JitPack builds the published library artifact -# (com.github.DemchaAV:GraphCompose) from the standalone root pom.xml. +# JitPack builds the published library coordinates +# (com.github.DemchaAV:GraphCompose) from the standalone poms. # # JDK is pinned to 17 — the project's compile baseline # (maven.compiler.release=17) and the JDK the release verify/javadoc gate # runs on — so the jar consumers download is built on the same JDK CI # validates, not a drifting newer one. # -# install is scoped to the root module (-pl .) so JitPack builds only the -# library, never the examples/benchmarks reactor siblings. +# install is scoped to the two published coordinates — graph-compose-core (the +# engine, at the root pom) and graph-compose (the drop-in wrapper in dist/) — so +# JitPack serves both, never the examples/benchmarks reactor siblings. jdk: - openjdk17 install: - - ./mvnw -DskipTests install -pl . + - ./mvnw -DskipTests -f aggregator/pom.xml install -pl :graph-compose-core,:graph-compose diff --git a/pom.xml b/pom.xml index 7156bc88f..e362ae084 100644 --- a/pom.xml +++ b/pom.xml @@ -5,11 +5,11 @@ 4.0.0 io.github.demchaav - graph-compose + graph-compose-core 2.0.0-SNAPSHOT - GraphCompose - A declarative layout engine for programmatic document generation, implemented primarily in Java. + GraphCompose Core + A declarative layout engine for programmatic document generation, implemented primarily in Java. This is the lean engine coordinate; depend on the `graph-compose` artifact for the drop-in, PDF-capable install. https://github.com/DemchaAV/GraphCompose @@ -400,7 +400,7 @@ Build a tests-classifier jar so the sibling `benchmarks/` module can reuse the test-only fixtures (com.demcha.mock.*, etc.) without duplicating them. The artifact is - graph-compose-${version}-tests.jar in the local repository + graph-compose-core-${version}-tests.jar in the local repository after `mvn install`. It is a LOCAL build aid only: the `release` profile disables this execution (phase=none) so the tests jar is never attached and therefore never deployed to @@ -463,7 +463,7 @@ io.github.demchaav - graph-compose + graph-compose-core ${project.version} test-jar tests diff --git a/render-docx/pom.xml b/render-docx/pom.xml index 8b535b4a4..ddb3fedde 100644 --- a/render-docx/pom.xml +++ b/render-docx/pom.xml @@ -78,7 +78,7 @@ io.github.demchaav - graph-compose + graph-compose-core ${project.version} diff --git a/scripts/cut-release.ps1 b/scripts/cut-release.ps1 index 8ab940507..aa1f97f77 100644 --- a/scripts/cut-release.ps1 +++ b/scripts/cut-release.ps1 @@ -304,7 +304,7 @@ function Run-ShowcaseSync { $execProp = '"-Dexec.mainClass=com.demcha.examples.support.ShowcaseSync"' # The examples module depends on these bumped SNAPSHOT siblings (not on # Central); each must be installed before exec:java can resolve them. - $exampleSnapshotSiblings = @('render-docx/pom.xml', 'testing/pom.xml') + $exampleSnapshotSiblings = @('wrapper/pom.xml', 'render-docx/pom.xml', 'testing/pom.xml') if ($DryRun) { Write-Host " [DRY RUN] $mvnw -B -ntp -DskipTests install -pl ." -ForegroundColor Yellow foreach ($modulePom in $exampleSnapshotSiblings) { @@ -504,6 +504,9 @@ try { # automatically). graph-compose-qa is an aggregator child (its version is # inherited) and is never published, so it needs no explicit bump. Update-PomVersion (Join-Path $repoRoot 'testing/pom.xml') $Version + # graph-compose (the graph-compose compat wrapper (wrapper/)) tracks the engine line lockstep; + # its graph-compose-core dep is ${project.version} (follows automatically). + Update-PomVersion (Join-Path $repoRoot 'wrapper/pom.xml') $Version # Bundle tracks the engine line: its project bumps here; its # graph-compose dep is ${project.version} (follows automatically) and its # graph-compose-fonts dep is ${graphcompose.fonts.version} (stays pinned — @@ -574,6 +577,7 @@ try { 'bundle/pom.xml', 'render-docx/pom.xml', 'testing/pom.xml', + 'wrapper/pom.xml', 'examples/pom.xml', 'benchmarks/pom.xml', 'README.md', diff --git a/src/test/java/com/demcha/documentation/VersionConsistencyGuardTest.java b/src/test/java/com/demcha/documentation/VersionConsistencyGuardTest.java index 955d64946..d40f7e49f 100644 --- a/src/test/java/com/demcha/documentation/VersionConsistencyGuardTest.java +++ b/src/test/java/com/demcha/documentation/VersionConsistencyGuardTest.java @@ -67,6 +67,9 @@ void everyModuleResolvesToTheRootProjectVersion() throws Exception { assertThat(effectiveVersion(PROJECT_ROOT.resolve("testing/pom.xml"))) .describedAs("testing (graph-compose-testing) tracks the engine line and must equal the root version (%s)", root) .isEqualTo(root); + assertThat(effectiveVersion(PROJECT_ROOT.resolve("wrapper/pom.xml"))) + .describedAs("wrapper (the graph-compose compat wrapper) tracks the engine line and must equal the root version (%s)", root) + .isEqualTo(root); // NOTE: fonts/pom.xml (graph-compose-fonts) is intentionally NOT checked // here. It carries an independent version line (it ships on its own diff --git a/testing/pom.xml b/testing/pom.xml index 7f9925be2..7701ce1e1 100644 --- a/testing/pom.xml +++ b/testing/pom.xml @@ -78,7 +78,7 @@ io.github.demchaav - graph-compose + graph-compose-core ${project.version} diff --git a/wrapper/pom.xml b/wrapper/pom.xml new file mode 100644 index 000000000..4002d888d --- /dev/null +++ b/wrapper/pom.xml @@ -0,0 +1,179 @@ + + + 4.0.0 + + + io.github.demchaav + graph-compose + 2.0.0-SNAPSHOT + + GraphCompose + The graph-compose coordinate: a drop-in aggregator over graph-compose-core for a PDF-capable install. + https://github.com/DemchaAV/GraphCompose + + + + MIT License + https://opensource.org/licenses/MIT + repo + + + + + + DemchaAV + Artem Demchyshyn + demchishynartem@gmail.com + https://github.com/DemchaAV + + Lead Developer + Architect + + UTC 0 + + + + + scm:git:https://github.com/DemchaAV/GraphCompose.git + scm:git:ssh://git@github.com/DemchaAV/GraphCompose.git + https://github.com/DemchaAV/GraphCompose/tree/main + + + + UTF-8 + 17 + + 3.15.0 + 3.5.0 + 3.4.0 + 3.12.0 + 3.2.8 + 0.11.0 + + + true + + + + + + io.github.demchaav + graph-compose-core + ${project.version} + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + ${maven.compiler.plugin.version} + + ${maven.compiler.release} + + + + org.apache.maven.plugins + maven-jar-plugin + ${maven.jar.plugin.version} + + + + + + + + release + + + + org.apache.maven.plugins + maven-source-plugin + ${maven.source.plugin.version} + + + attach-sources + package + + jar-no-fork + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + ${maven.javadoc.plugin.version} + + + attach-javadocs + package + + jar + + + false + true + + + + + + org.apache.maven.plugins + maven-gpg-plugin + ${maven.gpg.plugin.version} + + + sign-artifacts + verify + + sign + + + ${gpg.skip} + + --pinentry-mode + loopback + + + + + + + org.sonatype.central + central-publishing-maven-plugin + ${central.publishing.plugin.version} + true + + central + false + validated + + + + + + + From 5c92827c839d814f3c4fd1317d7d90b2d698ddf8 Mon Sep 17 00:00:00 2001 From: DemchaAV Date: Sun, 5 Jul 2026 20:58:08 +0100 Subject: [PATCH 2/2] test: guard that the graph-compose wrapper stays a jar over graph-compose-core MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The wrapper's drop-in contract rests on it being a jar (not pom) that depends on graph-compose-core: a pom would force consumers to add pom, and dropping the core dependency would stop `graph-compose` from bringing the engine — either change would silently break every consumer at resolve with no test red. Add a VersionConsistencyGuardTest assertion locking both. Also correct a stale jitpack comment that still referenced the old wrapper directory name. --- jitpack.yml | 2 +- .../VersionConsistencyGuardTest.java | 37 +++++++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/jitpack.yml b/jitpack.yml index 59dde1918..c0859639d 100644 --- a/jitpack.yml +++ b/jitpack.yml @@ -7,7 +7,7 @@ # validates, not a drifting newer one. # # install is scoped to the two published coordinates — graph-compose-core (the -# engine, at the root pom) and graph-compose (the drop-in wrapper in dist/) — so +# engine, at the root pom) and graph-compose (the drop-in wrapper in wrapper/) — so # JitPack serves both, never the examples/benchmarks reactor siblings. jdk: - openjdk17 diff --git a/src/test/java/com/demcha/documentation/VersionConsistencyGuardTest.java b/src/test/java/com/demcha/documentation/VersionConsistencyGuardTest.java index d40f7e49f..8cbf68e61 100644 --- a/src/test/java/com/demcha/documentation/VersionConsistencyGuardTest.java +++ b/src/test/java/com/demcha/documentation/VersionConsistencyGuardTest.java @@ -106,6 +106,25 @@ void childModulesInheritVersionInsteadOfDeclaringTheirOwn() throws Exception { .isFalse(); } + @Test + void graphComposeWrapperStaysAJarOverCore() throws Exception { + Element project = parse(PROJECT_ROOT.resolve("wrapper/pom.xml")).getDocumentElement(); + + // Packaging MUST stay jar (the default when is absent). A pom + // would force every existing graph-compose consumer to add pom + // and break their build — the entire point of the wrapper is a drop-in jar. + Element packaging = directChild(project, "packaging"); + assertThat(packaging == null ? "jar" : packaging.getTextContent().trim()) + .describedAs("wrapper/pom.xml (the graph-compose coordinate) must stay a jar, never pom — a pom would force consumers to add pom") + .isEqualTo("jar"); + + // And it must aggregate the engine: the dependency on graph-compose-core is + // what makes a bare `graph-compose` bring the engine transitively. + assertThat(declaresDependencyOn(project, "graph-compose-core")) + .describedAs("wrapper/pom.xml must depend on graph-compose-core so `graph-compose` still brings the engine") + .isTrue(); + } + @Test void benchmarksDependencyDerivesVersionAndIsNotHardcoded() throws IOException { String pom = Files.readString(PROJECT_ROOT.resolve("benchmarks/pom.xml")); @@ -286,6 +305,24 @@ private static String effectiveVersion(Path pom) throws Exception { throw new IllegalStateException("No / or inherited / in " + pom); } + private static boolean declaresDependencyOn(Element project, String artifactId) { + Element deps = directChild(project, "dependencies"); + if (deps == null) { + return false; + } + NodeList children = deps.getChildNodes(); + for (int i = 0; i < children.getLength(); i++) { + Node node = children.item(i); + if (node.getNodeType() == Node.ELEMENT_NODE && node.getNodeName().equals("dependency")) { + Element aid = directChild((Element) node, "artifactId"); + if (aid != null && artifactId.equals(aid.getTextContent().trim())) { + return true; + } + } + } + return false; + } + private static Element directChild(Element parent, String name) { NodeList children = parent.getChildNodes(); for (int i = 0; i < children.getLength(); i++) {