From f64bfce2f4dc25ea5c52510ff4f6ad3c15c7871f Mon Sep 17 00:00:00 2001 From: per Date: Sun, 9 Aug 2026 09:24:39 +0200 Subject: [PATCH 01/16] prepare 3.2.0-SNAPSHOT --- gmd-core/pom.xml | 6 +++--- gmd-core/release.md | 7 +++++++ gmd-gradle-plugin/build.gradle | 4 ++-- gmd-gradle-plugin/release.md | 5 +++++ gmd-gradle-plugin/src/test/manual-test/build.gradle | 4 ++-- gmd-maven-plugin/release.md | 5 +++++ .../src/main/java/se/alipsa/gmd/maven/GmdMavenPlugin.java | 8 ++++++-- gmd-maven-plugin/src/test/projects/manual/pom.xml | 2 +- highlightjs-jvm/pom.xml | 7 +++++++ highlightjs-jvm/release.md | 6 ++++++ pom.xml | 4 ++-- 11 files changed, 46 insertions(+), 12 deletions(-) diff --git a/gmd-core/pom.xml b/gmd-core/pom.xml index 7066aea..6c75aba 100644 --- a/gmd-core/pom.xml +++ b/gmd-core/pom.xml @@ -39,9 +39,9 @@ UTF-8 21 - 1.1.65 - 0.29.0 - 6.1.2 + 1.1.70 + 0.30.0 + 6.1.3 ${project.build.directory}/site/groovydoc se.alipsa.gmd.core.Gmd diff --git a/gmd-core/release.md b/gmd-core/release.md index 8fcc4f3..3052a3b 100644 --- a/gmd-core/release.md +++ b/gmd-core/release.md @@ -1,5 +1,12 @@ # Gmd Release History +## v3.2.0, unreleased +- remove obsolete JavaFX filtering from the fat jar assembly +- restore the deprecated `HtmlDecorator.BOOTSTRAP_CSS` constant for source compatibility +- upgrade the JUnit BOM from 6.1.2 to 6.1.3 +- upgrade OpenHTMLToPDF from 1.1.65 to 1.1.70 +- upgrade CommonMark from 0.29.0 to 0.30.0 + ## v3.1.0, 2026-08-02 - add the independent `highlightjs-jvm` module using Rhino and a syntax-transpiled Highlight.js bundle - highlight code blocks synchronously before PDF rendering diff --git a/gmd-gradle-plugin/build.gradle b/gmd-gradle-plugin/build.gradle index 56ebb86..170b2ba 100644 --- a/gmd-gradle-plugin/build.gradle +++ b/gmd-gradle-plugin/build.gradle @@ -7,7 +7,7 @@ plugins { id 'signing' id 'maven-publish' id 'com.gradle.plugin-publish' version '2.1.1' - id "io.github.ben-manes.versions" version "0.58.0" + id "io.github.ben-manes.versions" version "0.61.0" } group = 'se.alipsa.gmd' @@ -38,7 +38,7 @@ repositories { } dependencies { - testImplementation platform('org.junit:junit-bom:6.1.2') + testImplementation platform('org.junit:junit-bom:6.1.3') testImplementation 'org.junit.jupiter:junit-jupiter' testImplementation 'org.junit.platform:junit-platform-launcher' } diff --git a/gmd-gradle-plugin/release.md b/gmd-gradle-plugin/release.md index 052c929..5044107 100644 --- a/gmd-gradle-plugin/release.md +++ b/gmd-gradle-plugin/release.md @@ -1,5 +1,10 @@ # GMD Gradle Plugin release history +## v3.2.0, unreleased +- validate the Gradle Plugin Portal publication before uploading the plugin +- upgrade the Gradle Versions Plugin from 0.58.0 to 0.61.0 +- upgrade the JUnit BOM from 6.1.2 to 6.1.3 + ## v3.1.1, in progress - replace the deprecated `Project.getProperties()` calls used by signing configuration with `findProperty`, keeping the plugin compatible with Gradle 10 - make `processGmd` compatible with the Gradle configuration cache and parallel execution diff --git a/gmd-gradle-plugin/src/test/manual-test/build.gradle b/gmd-gradle-plugin/src/test/manual-test/build.gradle index 1553ad4..92313bd 100644 --- a/gmd-gradle-plugin/src/test/manual-test/build.gradle +++ b/gmd-gradle-plugin/src/test/manual-test/build.gradle @@ -12,7 +12,7 @@ repositories { dependencies { implementation 'org.apache.groovy:groovy:5.0.8' - testImplementation(platform('org.junit:junit-bom:6.1.2')) + testImplementation(platform('org.junit:junit-bom:6.1.3')) testImplementation 'org.junit.jupiter:junit-jupiter' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' } @@ -28,4 +28,4 @@ test { testLogging { showStandardStreams = true } -} \ No newline at end of file +} diff --git a/gmd-maven-plugin/release.md b/gmd-maven-plugin/release.md index 909e17f..1961eb9 100644 --- a/gmd-maven-plugin/release.md +++ b/gmd-maven-plugin/release.md @@ -1,5 +1,10 @@ # GMD Maven Plugin release history +## v3.2.0, unreleased +- validate Maven Central release artifacts before publishing +- publish source and Javadoc artifacts as part of the release workflow +- replace deprecated Maven `@Component` injection with JSR-330 `@Inject` + ## v3.1.0, 2026-08-02 - resolve all output types without JavaFX dependencies - resolve relative source and target paths from the Maven project base directory diff --git a/gmd-maven-plugin/src/main/java/se/alipsa/gmd/maven/GmdMavenPlugin.java b/gmd-maven-plugin/src/main/java/se/alipsa/gmd/maven/GmdMavenPlugin.java index 0ef5853..90dd3d9 100644 --- a/gmd-maven-plugin/src/main/java/se/alipsa/gmd/maven/GmdMavenPlugin.java +++ b/gmd-maven-plugin/src/main/java/se/alipsa/gmd/maven/GmdMavenPlugin.java @@ -4,11 +4,15 @@ import java.util.ArrayList; import java.util.List; import java.util.Locale; +import javax.inject.Inject; import org.apache.maven.execution.MavenSession; import org.apache.maven.plugin.AbstractMojo; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.MojoFailureException; -import org.apache.maven.plugins.annotations.*; +import org.apache.maven.plugins.annotations.LifecyclePhase; +import org.apache.maven.plugins.annotations.Mojo; +import org.apache.maven.plugins.annotations.Parameter; +import org.apache.maven.plugins.annotations.ResolutionScope; import org.apache.maven.project.MavenProject; import org.eclipse.aether.RepositorySystem; import org.eclipse.aether.RepositorySystemSession; @@ -52,7 +56,7 @@ public class GmdMavenPlugin extends AbstractMojo { @Parameter(defaultValue = "${session}", readonly = true, required = true) private MavenSession session; - @Component + @Inject private RepositorySystem repositorySystem; /** diff --git a/gmd-maven-plugin/src/test/projects/manual/pom.xml b/gmd-maven-plugin/src/test/projects/manual/pom.xml index 12047d4..86c885d 100644 --- a/gmd-maven-plugin/src/test/projects/manual/pom.xml +++ b/gmd-maven-plugin/src/test/projects/manual/pom.xml @@ -17,7 +17,7 @@ org.junit junit-bom - 6.1.2 + 6.1.3 pom import diff --git a/highlightjs-jvm/pom.xml b/highlightjs-jvm/pom.xml index 4006ba5..7cd6333 100644 --- a/highlightjs-jvm/pom.xml +++ b/highlightjs-jvm/pom.xml @@ -98,6 +98,13 @@ + + org.codehaus.mojo + versions-maven-plugin + + file://${project.parent.basedir}/version-plugin-rules.xml + + diff --git a/highlightjs-jvm/release.md b/highlightjs-jvm/release.md index 4a27deb..0d00bbf 100644 --- a/highlightjs-jvm/release.md +++ b/highlightjs-jvm/release.md @@ -1,5 +1,11 @@ # Highlight.js JVM release history +## v3.2.0, unreleased +- upgrade the Babel build dependencies +- require Node 22 for bundle generation and verify the generated bundle in CI +- add Maven publication metadata and attach source and Javadoc artifacts +- add Javadocs to the public highlighter APIs + ## v3.1.0, 2026-08-02 - add the independent `highlightjs-jvm` module - expose Highlight.js string highlighting through the `SyntaxHighlighter` API diff --git a/pom.xml b/pom.xml index 5c7aebe..e552f62 100644 --- a/pom.xml +++ b/pom.xml @@ -22,7 +22,7 @@ - 3.1.1-SNAPSHOT + 3.2.0-SNAPSHOT UTF-8 21 @@ -31,7 +31,7 @@ 5.0.8 1.23.1 - 6.1.2 + 6.1.3 2.26.1 From 552c6bc9c1b1ecb594e64bce536438e2fff41f0a Mon Sep 17 00:00:00 2001 From: per Date: Sun, 9 Aug 2026 12:27:40 +0200 Subject: [PATCH 02/16] address review findings --- CLAUDE.md | 10 ++++++---- gmd-core/release.md | 4 ++-- .../groovy/se/alipsa/gmd/core/HtmlDecorator.groovy | 7 ++++--- .../test/groovy/test/alipsa/groovy/gmd/GmdTest.groovy | 11 +++++------ gmd-gradle-plugin/readme.md | 2 +- gmd-gradle-plugin/release.md | 3 ++- .../se/alipsa/gmd/gradle/GmdGradlePluginParams.groovy | 2 +- gmd-maven-plugin/readme.md | 2 +- gmd-maven-plugin/release.md | 7 ++++--- .../main/java/se/alipsa/gmd/maven/GmdMavenPlugin.java | 4 ++-- .../test/alipsa/gmd/maven/GmdMavenPluginTest.java | 2 ++ highlightjs-jvm/release.md | 9 +++++---- 12 files changed, 35 insertions(+), 28 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 3fbc338..0302db4 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -63,12 +63,14 @@ java -jar gmd-core/target/gmd-3.1.0.jar toPdfRaw input.gmd output.pdf ```xml - 3.1.0 + 3.2.0-SNAPSHOT ``` All child modules automatically inherit this version via `${revision}`. The `flatten-maven-plugin` resolves this during build/deployment. +When bumping `revision`, also update the default `gmdVersion` in both the Maven and Gradle plugins and their user documentation so the plugins do not silently run an older core version. + ## Project Structure ### Module Organization @@ -149,8 +151,8 @@ PDF Output ## Key Dependencies - **Groovy**: 5.0.8 (groovy, groovy-templates, groovy-jsr223) -- **Markdown**: commonmark 0.29.0 + GFM tables extension -- **PDF**: openhtmltopdf 1.1.65 (core, pdfbox, mathml, svg) +- **Markdown**: commonmark 0.30.0 + GFM tables extension +- **PDF**: openhtmltopdf 1.1.70 (core, pdfbox, mathml, svg) - **Syntax highlighting**: Highlight.js 11.7.0, syntax-transpiled and evaluated by Rhino 1.9.1 - **HTML fragments**: Jsoup 1.23.1 - **Matrix**: se.alipsa.matrix BOM 2.5.1 (charts, core, xchart) @@ -177,7 +179,7 @@ Rhino keeps mutable Highlight.js state in a shared scope, so calls are synchroni ## Testing -Tests use JUnit 5 (Jupiter 6.1.2): +Tests use JUnit Jupiter 6.1.3: - `gmd-core/src/test/groovy/test/alipsa/groovy/gmd/` - GmdTest.groovy - GmdTemplateEngineTest.groovy diff --git a/gmd-core/release.md b/gmd-core/release.md index 3052a3b..98a7c22 100644 --- a/gmd-core/release.md +++ b/gmd-core/release.md @@ -1,8 +1,7 @@ # Gmd Release History ## v3.2.0, unreleased -- remove obsolete JavaFX filtering from the fat jar assembly -- restore the deprecated `HtmlDecorator.BOOTSTRAP_CSS` constant for source compatibility +- restore the `HtmlDecorator.BOOTSTRAP_CSS` and `HIGHLIGHT_JS_CSS` compatibility constants - upgrade the JUnit BOM from 6.1.2 to 6.1.3 - upgrade OpenHTMLToPDF from 1.1.65 to 1.1.70 - upgrade CommonMark from 0.29.0 to 0.30.0 @@ -25,6 +24,7 @@ - close th tags and quote attribute values in Html - omit missing stylesheets instead of emitting a link with an empty href - restore the Highlight.js stylesheet lost in the source tree move; the jar now ships exactly one theme +- remove obsolete JavaFX filtering from the fat jar assembly ### v3.0.2, 2026-02-06 - use CI-friendly parent version (`${revision}`) instead of a fixed parent version diff --git a/gmd-core/src/main/groovy/se/alipsa/gmd/core/HtmlDecorator.groovy b/gmd-core/src/main/groovy/se/alipsa/gmd/core/HtmlDecorator.groovy index 3d70cdc..e117d69 100644 --- a/gmd-core/src/main/groovy/se/alipsa/gmd/core/HtmlDecorator.groovy +++ b/gmd-core/src/main/groovy/se/alipsa/gmd/core/HtmlDecorator.groovy @@ -8,14 +8,15 @@ class HtmlDecorator { private static final Logger log = LogManager.getLogger(HtmlDecorator.class) public static final String HIGHLIGHT_JS_CSS_PATH = "/highlightJs/styles/default.min.css" + public static final String HIGHLIGHT_JS_CSS = "\n\n" public static final String BOOTSTRAP_CSS_PATH = "/META-INF/resources/webjars/bootstrap/5.3.8/css/bootstrap.css" /** - * The external URL of the Bootstrap stylesheet. + * The external-form URL of the Bootstrap stylesheet, suitable for use in an HTML href. * - * @deprecated Use {@link #BOOTSTRAP_CSS_PATH} when referring to the classpath resource. + * {@link #BOOTSTRAP_CSS_PATH} is the classpath resource path and is not a replacement for + * this external URL. */ - @Deprecated public static final String BOOTSTRAP_CSS = resourceUrlExternalForm(BOOTSTRAP_CSS_PATH) public static final String HTML5_DECLARATION = "\n" diff --git a/gmd-core/src/test/groovy/test/alipsa/groovy/gmd/GmdTest.groovy b/gmd-core/src/test/groovy/test/alipsa/groovy/gmd/GmdTest.groovy index 792b32a..dafec4b 100644 --- a/gmd-core/src/test/groovy/test/alipsa/groovy/gmd/GmdTest.groovy +++ b/gmd-core/src/test/groovy/test/alipsa/groovy/gmd/GmdTest.groovy @@ -226,12 +226,11 @@ class GmdTest extends AbstractGmdTest { } @Test - void retainsDeprecatedBootstrapCssCompatibilityConstant() { - def resourceUrl = se.alipsa.gmd.core.HtmlDecorator.class - .getResource(se.alipsa.gmd.core.HtmlDecorator.BOOTSTRAP_CSS_PATH) - .toExternalForm() - - assertEquals(resourceUrl, se.alipsa.gmd.core.HtmlDecorator.BOOTSTRAP_CSS) + void retainsBootstrapCssCompatibilityConstants() { + assertTrue(!se.alipsa.gmd.core.HtmlDecorator.BOOTSTRAP_CSS.isEmpty()) + assertTrue(se.alipsa.gmd.core.HtmlDecorator.BOOTSTRAP_CSS.endsWith('bootstrap.css')) + assertTrue(se.alipsa.gmd.core.HtmlDecorator.HIGHLIGHT_JS_CSS.contains('default.min.css')) + assertTrue(se.alipsa.gmd.core.HtmlDecorator.HIGHLIGHT_JS_CSS.contains("href='")) } @Test diff --git a/gmd-gradle-plugin/readme.md b/gmd-gradle-plugin/readme.md index 9ea46ae..2823c17 100644 --- a/gmd-gradle-plugin/readme.md +++ b/gmd-gradle-plugin/readme.md @@ -24,7 +24,7 @@ Possible parameters are: `outputType=html` writes a complete decorated HTML document, including the doctype, embedded styles, and syntax-highlighted code blocks. - `groovyVersion` - the version of Groovy to use. Default is `5.0.8` -- `gmdVersion` - the version of GMD to use. Default is `3.1.0` +- `gmdVersion` - the version of GMD to use. Default is `3.2.0-SNAPSHOT` - `log4jVersion` - the version of log4j to use. Default is `2.26.1` - `ivyVersion` - the version of ivy to use. Default is `2.6.0` - `runTaskBefore` - the task that the gmd plugin should run before. Default is 'test' diff --git a/gmd-gradle-plugin/release.md b/gmd-gradle-plugin/release.md index 5044107..54e8f2d 100644 --- a/gmd-gradle-plugin/release.md +++ b/gmd-gradle-plugin/release.md @@ -1,7 +1,7 @@ # GMD Gradle Plugin release history ## v3.2.0, unreleased -- validate the Gradle Plugin Portal publication before uploading the plugin +- align the default GMD core version with the current `3.2.0-SNAPSHOT` project revision - upgrade the Gradle Versions Plugin from 0.58.0 to 0.61.0 - upgrade the JUnit BOM from 6.1.2 to 6.1.3 @@ -14,6 +14,7 @@ - resolve all output types without JavaFX dependencies - declare `processGmd` inputs and outputs and remove stale generated files - validate output types and source/target directories before processing +- validate the Gradle Plugin Portal publication before uploading the plugin ## v3.0.2, 2026-02-06 - use CI-friendly parent version (`${revision}`) instead of a fixed parent version diff --git a/gmd-gradle-plugin/src/main/groovy/se/alipsa/gmd/gradle/GmdGradlePluginParams.groovy b/gmd-gradle-plugin/src/main/groovy/se/alipsa/gmd/gradle/GmdGradlePluginParams.groovy index 1c98700..f087944 100644 --- a/gmd-gradle-plugin/src/main/groovy/se/alipsa/gmd/gradle/GmdGradlePluginParams.groovy +++ b/gmd-gradle-plugin/src/main/groovy/se/alipsa/gmd/gradle/GmdGradlePluginParams.groovy @@ -36,7 +36,7 @@ interface GmdGradlePluginParams { Property getLog4jVersion() /** - * The version of GMD to use. Default is 3.1.0 + * The version of GMD to use. Default is 3.2.0-SNAPSHOT */ Property getGmdVersion() diff --git a/gmd-maven-plugin/readme.md b/gmd-maven-plugin/readme.md index f697f1f..5af7fdb 100644 --- a/gmd-maven-plugin/readme.md +++ b/gmd-maven-plugin/readme.md @@ -34,7 +34,7 @@ Possible configuration parameters are: embedded styles, and syntax-highlighted code blocks. - `groovyVersion` - the version of Groovy to use. Default is `5.0.8` - `log4jVersion` - the version of Log4j to use. Default is `2.26.1` -- `gmdVersion` - the version of GMD core to use. Default is `3.1.0` +- `gmdVersion` - the version of GMD core to use. Default is `3.2.0-SNAPSHOT` - `ivyVersion` - the version of Ivy to use. Default is `2.6.0` If you don't want to run the plugin explicitly, you can add it to an existing lifecycle as follows: diff --git a/gmd-maven-plugin/release.md b/gmd-maven-plugin/release.md index 1961eb9..94c808a 100644 --- a/gmd-maven-plugin/release.md +++ b/gmd-maven-plugin/release.md @@ -1,15 +1,16 @@ # GMD Maven Plugin release history ## v3.2.0, unreleased -- validate Maven Central release artifacts before publishing -- publish source and Javadoc artifacts as part of the release workflow -- replace deprecated Maven `@Component` injection with JSR-330 `@Inject` +- align the default GMD core version with the current `3.2.0-SNAPSHOT` project revision ## v3.1.0, 2026-08-02 - resolve all output types without JavaFX dependencies - resolve relative source and target paths from the Maven project base directory - validate output types and source/target directories before processing - remove JavaFX runtime dependency from the plugin +- validate Maven Central release artifacts before publishing +- publish source and Javadoc artifacts as part of the release workflow +- replace deprecated Maven `@Component` injection with JSR-330 `@Inject` ## v3.0.2, 2026-02-06 - use CI-friendly parent version (`${revision}`) instead of a fixed parent version diff --git a/gmd-maven-plugin/src/main/java/se/alipsa/gmd/maven/GmdMavenPlugin.java b/gmd-maven-plugin/src/main/java/se/alipsa/gmd/maven/GmdMavenPlugin.java index 90dd3d9..76b4c1e 100644 --- a/gmd-maven-plugin/src/main/java/se/alipsa/gmd/maven/GmdMavenPlugin.java +++ b/gmd-maven-plugin/src/main/java/se/alipsa/gmd/maven/GmdMavenPlugin.java @@ -44,7 +44,7 @@ public class GmdMavenPlugin extends AbstractMojo { @Parameter(name = "log4jVersion", property = "processGmd.log4jVersion", defaultValue = "2.26.1") private String log4jVersion; - @Parameter(name = "gmdVersion", property = "processGmd.gmdVersion", defaultValue = "3.1.0") + @Parameter(name = "gmdVersion", property = "processGmd.gmdVersion", defaultValue = "3.2.0-SNAPSHOT") private String gmdVersion; @Parameter(name = "ivyVersion", property = "processGmd.ivyVersion", defaultValue = "2.6.0") @@ -115,7 +115,7 @@ public String getLog4jVersion() { } /** - * The version of GMD core to use. Default is 3.1.0 + * The version of GMD core to use. Default is 3.2.0-SNAPSHOT * * @return The version of GMD core to use. */ diff --git a/gmd-maven-plugin/src/test/java/test/alipsa/gmd/maven/GmdMavenPluginTest.java b/gmd-maven-plugin/src/test/java/test/alipsa/gmd/maven/GmdMavenPluginTest.java index 3030e5b..53a49a3 100644 --- a/gmd-maven-plugin/src/test/java/test/alipsa/gmd/maven/GmdMavenPluginTest.java +++ b/gmd-maven-plugin/src/test/java/test/alipsa/gmd/maven/GmdMavenPluginTest.java @@ -8,6 +8,7 @@ import java.io.File; import java.nio.file.Files; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; @MojoTest @@ -18,6 +19,7 @@ public class GmdMavenPluginTest { public void testGmdMavenPlugin(GmdMavenPlugin plugin) throws Exception { File pomFile = new File("src/test/projects/"); assertTrue(pomFile.exists()); + assertEquals("3.2.0-SNAPSHOT", plugin.getGmdVersion()); // Execute the plugin plugin.execute(); diff --git a/highlightjs-jvm/release.md b/highlightjs-jvm/release.md index 0d00bbf..4664b05 100644 --- a/highlightjs-jvm/release.md +++ b/highlightjs-jvm/release.md @@ -1,10 +1,7 @@ # Highlight.js JVM release history ## v3.2.0, unreleased -- upgrade the Babel build dependencies -- require Node 22 for bundle generation and verify the generated bundle in CI -- add Maven publication metadata and attach source and Javadoc artifacts -- add Javadocs to the public highlighter APIs +- upgrade the JUnit BOM used by module tests to 6.1.3 ## v3.1.0, 2026-08-02 - add the independent `highlightjs-jvm` module @@ -15,3 +12,7 @@ syntax-only transpile, no polyfills, no hand-editing - return null instead of auto-detecting when a language is unknown or absent - synchronize access because Highlight.js keeps mutable state in the shared scope +- upgrade the Babel build dependencies +- require Node 22 for bundle generation and verify the generated bundle in CI +- add Maven publication metadata and attach source and Javadoc artifacts +- add Javadocs to the public highlighter APIs From e318a92e5459285a39b423650d8f939ee97a86e8 Mon Sep 17 00:00:00 2001 From: per Date: Sun, 9 Aug 2026 12:43:10 +0200 Subject: [PATCH 03/16] fix release version defaults --- CLAUDE.md | 8 ++++---- README.md | 4 ++-- gmd-core/release.md | 2 +- .../src/test/groovy/test/alipsa/groovy/gmd/GmdTest.groovy | 7 ++++++- gmd-gradle-plugin/build.gradle | 8 +++++++- gmd-gradle-plugin/readme.md | 2 +- gmd-gradle-plugin/release.md | 2 +- .../se/alipsa/gmd/gradle/GmdGradlePluginParams.groovy | 2 +- gmd-maven-plugin/readme.md | 2 +- gmd-maven-plugin/release.md | 6 +++--- .../src/main/java/se/alipsa/gmd/maven/GmdMavenPlugin.java | 4 ++-- .../java/test/alipsa/gmd/maven/GmdMavenPluginTest.java | 2 -- highlightjs-jvm/release.md | 6 +++--- 13 files changed, 32 insertions(+), 23 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 0302db4..2072aba 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -51,9 +51,9 @@ mvn versions:display-dependency-updates # Maven dependency updates only mvn -pl gmd-core clean package # Use the fat JAR -java -jar gmd-core/target/gmd-3.1.0.jar toHtml input.gmd output.html -java -jar gmd-core/target/gmd-3.1.0.jar toPdf input.gmd output.pdf -java -jar gmd-core/target/gmd-3.1.0.jar toPdfRaw input.gmd output.pdf +java -jar gmd-core/target/gmd-3.2.0-SNAPSHOT.jar toHtml input.gmd output.html +java -jar gmd-core/target/gmd-3.2.0-SNAPSHOT.jar toPdf input.gmd output.pdf +java -jar gmd-core/target/gmd-3.2.0-SNAPSHOT.jar toPdfRaw input.gmd output.pdf ``` ## Version Management @@ -69,7 +69,7 @@ java -jar gmd-core/target/gmd-3.1.0.jar toPdfRaw input.gmd output.pdf All child modules automatically inherit this version via `${revision}`. The `flatten-maven-plugin` resolves this during build/deployment. -When bumping `revision`, also update the default `gmdVersion` in both the Maven and Gradle plugins and their user documentation so the plugins do not silently run an older core version. +When bumping `revision`, verify that the Maven `${plugin.version}` default and Gradle manifest-based default remain synchronized with the core version, and update their user documentation if the default behavior changes. ## Project Structure diff --git a/README.md b/README.md index 06202b7..3c06b42 100644 --- a/README.md +++ b/README.md @@ -168,7 +168,7 @@ The library, which requires Java 21 or later, is available from maven central: Gradle: ```groovy def groovyVersion = '5.0.8' // any 4.x version should work -implementation "se.alipsa.gmd:gmd-core:3.1.0" +implementation "se.alipsa.gmd:gmd-core:3.2.0-SNAPSHOT" implementation "org.apache.groovy:groovy:${groovyVersion}" implementation "org.apache.groovy:groovy-templates:${groovyVersion}" implementation "org.apache.groovy:groovy-jsr223:${groovyVersion}" @@ -182,7 +182,7 @@ Maven: se.alipsa.gmd gmd-core - 3.1.0 + 3.2.0-SNAPSHOT org.apache.groovy diff --git a/gmd-core/release.md b/gmd-core/release.md index 98a7c22..2bb05cf 100644 --- a/gmd-core/release.md +++ b/gmd-core/release.md @@ -5,6 +5,7 @@ - upgrade the JUnit BOM from 6.1.2 to 6.1.3 - upgrade OpenHTMLToPDF from 1.1.65 to 1.1.70 - upgrade CommonMark from 0.29.0 to 0.30.0 +- remove obsolete JavaFX filtering from the fat jar assembly ## v3.1.0, 2026-08-02 - add the independent `highlightjs-jvm` module using Rhino and a syntax-transpiled Highlight.js bundle @@ -24,7 +25,6 @@ - close th tags and quote attribute values in Html - omit missing stylesheets instead of emitting a link with an empty href - restore the Highlight.js stylesheet lost in the source tree move; the jar now ships exactly one theme -- remove obsolete JavaFX filtering from the fat jar assembly ### v3.0.2, 2026-02-06 - use CI-friendly parent version (`${revision}`) instead of a fixed parent version diff --git a/gmd-core/src/test/groovy/test/alipsa/groovy/gmd/GmdTest.groovy b/gmd-core/src/test/groovy/test/alipsa/groovy/gmd/GmdTest.groovy index dafec4b..786bd0a 100644 --- a/gmd-core/src/test/groovy/test/alipsa/groovy/gmd/GmdTest.groovy +++ b/gmd-core/src/test/groovy/test/alipsa/groovy/gmd/GmdTest.groovy @@ -228,9 +228,14 @@ class GmdTest extends AbstractGmdTest { @Test void retainsBootstrapCssCompatibilityConstants() { assertTrue(!se.alipsa.gmd.core.HtmlDecorator.BOOTSTRAP_CSS.isEmpty()) + assertNotEquals(se.alipsa.gmd.core.HtmlDecorator.BOOTSTRAP_CSS_PATH, + se.alipsa.gmd.core.HtmlDecorator.BOOTSTRAP_CSS) assertTrue(se.alipsa.gmd.core.HtmlDecorator.BOOTSTRAP_CSS.endsWith('bootstrap.css')) + assertTrue(se.alipsa.gmd.core.HtmlDecorator.BOOTSTRAP_CSS.startsWith('file:') + || se.alipsa.gmd.core.HtmlDecorator.BOOTSTRAP_CSS.startsWith('jar:')) assertTrue(se.alipsa.gmd.core.HtmlDecorator.HIGHLIGHT_JS_CSS.contains('default.min.css')) - assertTrue(se.alipsa.gmd.core.HtmlDecorator.HIGHLIGHT_JS_CSS.contains("href='")) + assertTrue(se.alipsa.gmd.core.HtmlDecorator.HIGHLIGHT_JS_CSS.contains("href='file:") + || se.alipsa.gmd.core.HtmlDecorator.HIGHLIGHT_JS_CSS.contains("href='jar:")) } @Test diff --git a/gmd-gradle-plugin/build.gradle b/gmd-gradle-plugin/build.gradle index 170b2ba..a92db57 100644 --- a/gmd-gradle-plugin/build.gradle +++ b/gmd-gradle-plugin/build.gradle @@ -11,9 +11,15 @@ plugins { } group = 'se.alipsa.gmd' -version = project.findProperty("publish.version") ?: "unspecified" +version = project.findProperty("publish.version") ?: "3.2.0-SNAPSHOT" description = 'Compiles gmd files into markdown, html or pdf' +jar { + manifest { + attributes('Implementation-Version': project.version) + } +} + gradlePlugin { website.set("https://github.com/Alipsa/GroovyMarkdown") vcsUrl.set("https://github.com/Alipsa/GroovyMarkdown.git") diff --git a/gmd-gradle-plugin/readme.md b/gmd-gradle-plugin/readme.md index 2823c17..9decabd 100644 --- a/gmd-gradle-plugin/readme.md +++ b/gmd-gradle-plugin/readme.md @@ -24,7 +24,7 @@ Possible parameters are: `outputType=html` writes a complete decorated HTML document, including the doctype, embedded styles, and syntax-highlighted code blocks. - `groovyVersion` - the version of Groovy to use. Default is `5.0.8` -- `gmdVersion` - the version of GMD to use. Default is `3.2.0-SNAPSHOT` +- `gmdVersion` - the version of GMD to use. Defaults to the Gradle plugin version. - `log4jVersion` - the version of log4j to use. Default is `2.26.1` - `ivyVersion` - the version of ivy to use. Default is `2.6.0` - `runTaskBefore` - the task that the gmd plugin should run before. Default is 'test' diff --git a/gmd-gradle-plugin/release.md b/gmd-gradle-plugin/release.md index 54e8f2d..c7e332d 100644 --- a/gmd-gradle-plugin/release.md +++ b/gmd-gradle-plugin/release.md @@ -4,6 +4,7 @@ - align the default GMD core version with the current `3.2.0-SNAPSHOT` project revision - upgrade the Gradle Versions Plugin from 0.58.0 to 0.61.0 - upgrade the JUnit BOM from 6.1.2 to 6.1.3 +- validate the Gradle Plugin Portal publication before uploading the plugin ## v3.1.1, in progress - replace the deprecated `Project.getProperties()` calls used by signing configuration with `findProperty`, keeping the plugin compatible with Gradle 10 @@ -14,7 +15,6 @@ - resolve all output types without JavaFX dependencies - declare `processGmd` inputs and outputs and remove stale generated files - validate output types and source/target directories before processing -- validate the Gradle Plugin Portal publication before uploading the plugin ## v3.0.2, 2026-02-06 - use CI-friendly parent version (`${revision}`) instead of a fixed parent version diff --git a/gmd-gradle-plugin/src/main/groovy/se/alipsa/gmd/gradle/GmdGradlePluginParams.groovy b/gmd-gradle-plugin/src/main/groovy/se/alipsa/gmd/gradle/GmdGradlePluginParams.groovy index f087944..fcc2797 100644 --- a/gmd-gradle-plugin/src/main/groovy/se/alipsa/gmd/gradle/GmdGradlePluginParams.groovy +++ b/gmd-gradle-plugin/src/main/groovy/se/alipsa/gmd/gradle/GmdGradlePluginParams.groovy @@ -36,7 +36,7 @@ interface GmdGradlePluginParams { Property getLog4jVersion() /** - * The version of GMD to use. Default is 3.2.0-SNAPSHOT + * The version of GMD to use. Defaults to the Gradle plugin version. */ Property getGmdVersion() diff --git a/gmd-maven-plugin/readme.md b/gmd-maven-plugin/readme.md index 5af7fdb..dc83dfc 100644 --- a/gmd-maven-plugin/readme.md +++ b/gmd-maven-plugin/readme.md @@ -34,7 +34,7 @@ Possible configuration parameters are: embedded styles, and syntax-highlighted code blocks. - `groovyVersion` - the version of Groovy to use. Default is `5.0.8` - `log4jVersion` - the version of Log4j to use. Default is `2.26.1` -- `gmdVersion` - the version of GMD core to use. Default is `3.2.0-SNAPSHOT` +- `gmdVersion` - the version of GMD core to use. Defaults to the plugin version. - `ivyVersion` - the version of Ivy to use. Default is `2.6.0` If you don't want to run the plugin explicitly, you can add it to an existing lifecycle as follows: diff --git a/gmd-maven-plugin/release.md b/gmd-maven-plugin/release.md index 94c808a..ca98497 100644 --- a/gmd-maven-plugin/release.md +++ b/gmd-maven-plugin/release.md @@ -2,15 +2,15 @@ ## v3.2.0, unreleased - align the default GMD core version with the current `3.2.0-SNAPSHOT` project revision +- validate Maven Central release artifacts before publishing +- publish source and Javadoc artifacts as part of the release workflow +- replace deprecated Maven `@Component` injection with JSR-330 `@Inject` ## v3.1.0, 2026-08-02 - resolve all output types without JavaFX dependencies - resolve relative source and target paths from the Maven project base directory - validate output types and source/target directories before processing - remove JavaFX runtime dependency from the plugin -- validate Maven Central release artifacts before publishing -- publish source and Javadoc artifacts as part of the release workflow -- replace deprecated Maven `@Component` injection with JSR-330 `@Inject` ## v3.0.2, 2026-02-06 - use CI-friendly parent version (`${revision}`) instead of a fixed parent version diff --git a/gmd-maven-plugin/src/main/java/se/alipsa/gmd/maven/GmdMavenPlugin.java b/gmd-maven-plugin/src/main/java/se/alipsa/gmd/maven/GmdMavenPlugin.java index 76b4c1e..8b344d0 100644 --- a/gmd-maven-plugin/src/main/java/se/alipsa/gmd/maven/GmdMavenPlugin.java +++ b/gmd-maven-plugin/src/main/java/se/alipsa/gmd/maven/GmdMavenPlugin.java @@ -44,7 +44,7 @@ public class GmdMavenPlugin extends AbstractMojo { @Parameter(name = "log4jVersion", property = "processGmd.log4jVersion", defaultValue = "2.26.1") private String log4jVersion; - @Parameter(name = "gmdVersion", property = "processGmd.gmdVersion", defaultValue = "3.2.0-SNAPSHOT") + @Parameter(name = "gmdVersion", property = "processGmd.gmdVersion", defaultValue = "${plugin.version}") private String gmdVersion; @Parameter(name = "ivyVersion", property = "processGmd.ivyVersion", defaultValue = "2.6.0") @@ -115,7 +115,7 @@ public String getLog4jVersion() { } /** - * The version of GMD core to use. Default is 3.2.0-SNAPSHOT + * The version of GMD core to use. Defaults to the plugin version. * * @return The version of GMD core to use. */ diff --git a/gmd-maven-plugin/src/test/java/test/alipsa/gmd/maven/GmdMavenPluginTest.java b/gmd-maven-plugin/src/test/java/test/alipsa/gmd/maven/GmdMavenPluginTest.java index 53a49a3..3030e5b 100644 --- a/gmd-maven-plugin/src/test/java/test/alipsa/gmd/maven/GmdMavenPluginTest.java +++ b/gmd-maven-plugin/src/test/java/test/alipsa/gmd/maven/GmdMavenPluginTest.java @@ -8,7 +8,6 @@ import java.io.File; import java.nio.file.Files; -import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; @MojoTest @@ -19,7 +18,6 @@ public class GmdMavenPluginTest { public void testGmdMavenPlugin(GmdMavenPlugin plugin) throws Exception { File pomFile = new File("src/test/projects/"); assertTrue(pomFile.exists()); - assertEquals("3.2.0-SNAPSHOT", plugin.getGmdVersion()); // Execute the plugin plugin.execute(); diff --git a/highlightjs-jvm/release.md b/highlightjs-jvm/release.md index 4664b05..6c458b3 100644 --- a/highlightjs-jvm/release.md +++ b/highlightjs-jvm/release.md @@ -2,6 +2,9 @@ ## v3.2.0, unreleased - upgrade the JUnit BOM used by module tests to 6.1.3 +- require Node 22 for bundle generation and verify the generated bundle in CI +- add Maven publication metadata and attach source and Javadoc artifacts +- add Javadocs to the public highlighter APIs ## v3.1.0, 2026-08-02 - add the independent `highlightjs-jvm` module @@ -13,6 +16,3 @@ - return null instead of auto-detecting when a language is unknown or absent - synchronize access because Highlight.js keeps mutable state in the shared scope - upgrade the Babel build dependencies -- require Node 22 for bundle generation and verify the generated bundle in CI -- add Maven publication metadata and attach source and Javadoc artifacts -- add Javadocs to the public highlighter APIs From 7b8c1db92035cb74ebd765ae11743d710e920d44 Mon Sep 17 00:00:00 2001 From: per Date: Sun, 9 Aug 2026 12:55:22 +0200 Subject: [PATCH 04/16] update release notes --- gmd-gradle-plugin/release.md | 2 +- gmd-maven-plugin/release.md | 2 +- highlightjs-jvm/release.md | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/gmd-gradle-plugin/release.md b/gmd-gradle-plugin/release.md index c7e332d..cfaea35 100644 --- a/gmd-gradle-plugin/release.md +++ b/gmd-gradle-plugin/release.md @@ -1,7 +1,7 @@ # GMD Gradle Plugin release history ## v3.2.0, unreleased -- align the default GMD core version with the current `3.2.0-SNAPSHOT` project revision +- derive the default GMD core version from the Gradle plugin artifact version, with a snapshot fallback for development builds - upgrade the Gradle Versions Plugin from 0.58.0 to 0.61.0 - upgrade the JUnit BOM from 6.1.2 to 6.1.3 - validate the Gradle Plugin Portal publication before uploading the plugin diff --git a/gmd-maven-plugin/release.md b/gmd-maven-plugin/release.md index ca98497..410abe6 100644 --- a/gmd-maven-plugin/release.md +++ b/gmd-maven-plugin/release.md @@ -1,7 +1,7 @@ # GMD Maven Plugin release history ## v3.2.0, unreleased -- align the default GMD core version with the current `3.2.0-SNAPSHOT` project revision +- make the default GMD core version follow the Maven plugin version to prevent release drift - validate Maven Central release artifacts before publishing - publish source and Javadoc artifacts as part of the release workflow - replace deprecated Maven `@Component` injection with JSR-330 `@Inject` diff --git a/highlightjs-jvm/release.md b/highlightjs-jvm/release.md index 6c458b3..c408ca3 100644 --- a/highlightjs-jvm/release.md +++ b/highlightjs-jvm/release.md @@ -4,6 +4,7 @@ - upgrade the JUnit BOM used by module tests to 6.1.3 - require Node 22 for bundle generation and verify the generated bundle in CI - add Maven publication metadata and attach source and Javadoc artifacts +- configure the versions Maven plugin to use the shared repository version rules - add Javadocs to the public highlighter APIs ## v3.1.0, 2026-08-02 From bced0b3f3498a2fe4f881bffdec98bf79116ab34 Mon Sep 17 00:00:00 2001 From: per Date: Sun, 9 Aug 2026 15:46:32 +0200 Subject: [PATCH 05/16] address follow-up review findings --- CLAUDE.md | 2 +- README.md | 4 ++-- .../se/alipsa/gmd/core/HtmlDecorator.groovy | 2 +- gmd-gradle-plugin/build.gradle | 17 ++++++++++++++++- gmd-gradle-plugin/release.md | 3 ++- gmd-maven-plugin/pom.xml | 9 +++++++++ .../alipsa/gmd/maven/GmdMavenPluginTest.java | 2 ++ highlightjs-jvm/release.md | 2 +- 8 files changed, 34 insertions(+), 7 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 2072aba..3c69d8d 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -201,7 +201,7 @@ Requires: ### Gradle Plugin Portal (gmd-gradle-plugin) ```bash cd gmd-gradle-plugin -./gradlew publishPlugins +./gradlew publishPlugins -Ppublish.version= ``` ### GitHub Releases diff --git a/README.md b/README.md index 3c06b42..06202b7 100644 --- a/README.md +++ b/README.md @@ -168,7 +168,7 @@ The library, which requires Java 21 or later, is available from maven central: Gradle: ```groovy def groovyVersion = '5.0.8' // any 4.x version should work -implementation "se.alipsa.gmd:gmd-core:3.2.0-SNAPSHOT" +implementation "se.alipsa.gmd:gmd-core:3.1.0" implementation "org.apache.groovy:groovy:${groovyVersion}" implementation "org.apache.groovy:groovy-templates:${groovyVersion}" implementation "org.apache.groovy:groovy-jsr223:${groovyVersion}" @@ -182,7 +182,7 @@ Maven: se.alipsa.gmd gmd-core - 3.2.0-SNAPSHOT + 3.1.0 org.apache.groovy diff --git a/gmd-core/src/main/groovy/se/alipsa/gmd/core/HtmlDecorator.groovy b/gmd-core/src/main/groovy/se/alipsa/gmd/core/HtmlDecorator.groovy index e117d69..30f28e8 100644 --- a/gmd-core/src/main/groovy/se/alipsa/gmd/core/HtmlDecorator.groovy +++ b/gmd-core/src/main/groovy/se/alipsa/gmd/core/HtmlDecorator.groovy @@ -8,7 +8,7 @@ class HtmlDecorator { private static final Logger log = LogManager.getLogger(HtmlDecorator.class) public static final String HIGHLIGHT_JS_CSS_PATH = "/highlightJs/styles/default.min.css" - public static final String HIGHLIGHT_JS_CSS = "\n\n" + public static final String HIGHLIGHT_JS_CSS = styleLink(HIGHLIGHT_JS_CSS_PATH, "\n", "\n") public static final String BOOTSTRAP_CSS_PATH = "/META-INF/resources/webjars/bootstrap/5.3.8/css/bootstrap.css" /** diff --git a/gmd-gradle-plugin/build.gradle b/gmd-gradle-plugin/build.gradle index a92db57..e2293ee 100644 --- a/gmd-gradle-plugin/build.gradle +++ b/gmd-gradle-plugin/build.gradle @@ -11,7 +11,15 @@ plugins { } group = 'se.alipsa.gmd' -version = project.findProperty("publish.version") ?: "3.2.0-SNAPSHOT" +def publishVersion = project.findProperty("publish.version") +def pomDocument = javax.xml.parsers.DocumentBuilderFactory.newInstance() + .newDocumentBuilder() + .parse(file('../pom.xml')) +def revision = pomDocument.getElementsByTagName('revision').item(0)?.getTextContent()?.trim() +if (!revision) { + throw new GradleException('Could not read the project revision from ../pom.xml') +} +version = publishVersion ?: revision description = 'Compiles gmd files into markdown, html or pdf' jar { @@ -68,6 +76,13 @@ test { } project.afterEvaluate { + tasks.matching { it.name == 'publishPlugins' }.configureEach { + doFirst { + if (!publishVersion) { + throw new GradleException('publishPlugins requires -Ppublish.version=') + } + } + } tasks.named('signPluginMavenPublication') { enabled = project.findProperty('signing.keyId') != null } diff --git a/gmd-gradle-plugin/release.md b/gmd-gradle-plugin/release.md index cfaea35..5621e6c 100644 --- a/gmd-gradle-plugin/release.md +++ b/gmd-gradle-plugin/release.md @@ -1,7 +1,8 @@ # GMD Gradle Plugin release history ## v3.2.0, unreleased -- derive the default GMD core version from the Gradle plugin artifact version, with a snapshot fallback for development builds +- derive the default GMD core version from the Gradle plugin artifact version, with a released fallback for development and repackaged builds +- derive the development artifact version from the root Maven revision and require an explicit version when publishing - upgrade the Gradle Versions Plugin from 0.58.0 to 0.61.0 - upgrade the JUnit BOM from 6.1.2 to 6.1.3 - validate the Gradle Plugin Portal publication before uploading the plugin diff --git a/gmd-maven-plugin/pom.xml b/gmd-maven-plugin/pom.xml index 9402322..ece36a3 100644 --- a/gmd-maven-plugin/pom.xml +++ b/gmd-maven-plugin/pom.xml @@ -157,6 +157,15 @@ org.apache.maven.plugins maven-compiler-plugin + + org.apache.maven.plugins + maven-surefire-plugin + + + ${project.version} + + + org.apache.maven.plugins maven-plugin-plugin diff --git a/gmd-maven-plugin/src/test/java/test/alipsa/gmd/maven/GmdMavenPluginTest.java b/gmd-maven-plugin/src/test/java/test/alipsa/gmd/maven/GmdMavenPluginTest.java index 3030e5b..64d9e70 100644 --- a/gmd-maven-plugin/src/test/java/test/alipsa/gmd/maven/GmdMavenPluginTest.java +++ b/gmd-maven-plugin/src/test/java/test/alipsa/gmd/maven/GmdMavenPluginTest.java @@ -8,6 +8,7 @@ import java.io.File; import java.nio.file.Files; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; @MojoTest @@ -18,6 +19,7 @@ public class GmdMavenPluginTest { public void testGmdMavenPlugin(GmdMavenPlugin plugin) throws Exception { File pomFile = new File("src/test/projects/"); assertTrue(pomFile.exists()); + assertEquals(System.getProperty("gmd.plugin.version"), plugin.getGmdVersion()); // Execute the plugin plugin.execute(); diff --git a/highlightjs-jvm/release.md b/highlightjs-jvm/release.md index c408ca3..4933ce2 100644 --- a/highlightjs-jvm/release.md +++ b/highlightjs-jvm/release.md @@ -6,6 +6,7 @@ - add Maven publication metadata and attach source and Javadoc artifacts - configure the versions Maven plugin to use the shared repository version rules - add Javadocs to the public highlighter APIs +- upgrade the Babel build dependencies ## v3.1.0, 2026-08-02 - add the independent `highlightjs-jvm` module @@ -16,4 +17,3 @@ syntax-only transpile, no polyfills, no hand-editing - return null instead of auto-detecting when a language is unknown or absent - synchronize access because Highlight.js keeps mutable state in the shared scope -- upgrade the Babel build dependencies From 6c8a21b663ea9f297abcdd974c5cc2dcda028770 Mon Sep 17 00:00:00 2001 From: per Date: Sun, 9 Aug 2026 16:26:28 +0200 Subject: [PATCH 06/16] fix plugin version test and cache handling --- CLAUDE.md | 2 +- gmd-gradle-plugin/build.gradle | 21 ++++++++++++------- gmd-gradle-plugin/release.md | 2 +- .../src/main/resources/gmd-version.properties | 1 + .../alipsa/gmd/maven/GmdMavenPluginTest.java | 6 +++++- 5 files changed, 22 insertions(+), 10 deletions(-) create mode 100644 gmd-gradle-plugin/src/main/resources/gmd-version.properties diff --git a/CLAUDE.md b/CLAUDE.md index 3c69d8d..90ed549 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -69,7 +69,7 @@ java -jar gmd-core/target/gmd-3.2.0-SNAPSHOT.jar toPdfRaw input.gmd output.pdf All child modules automatically inherit this version via `${revision}`. The `flatten-maven-plugin` resolves this during build/deployment. -When bumping `revision`, verify that the Maven `${plugin.version}` default and Gradle manifest-based default remain synchronized with the core version, and update their user documentation if the default behavior changes. +When bumping `revision`, verify that the Maven `${plugin.version}` default and Gradle resource/manifest-based default remain synchronized with the core version, and update their user documentation if the default behavior changes. ## Project Structure diff --git a/gmd-gradle-plugin/build.gradle b/gmd-gradle-plugin/build.gradle index e2293ee..73fa4e2 100644 --- a/gmd-gradle-plugin/build.gradle +++ b/gmd-gradle-plugin/build.gradle @@ -12,14 +12,15 @@ plugins { group = 'se.alipsa.gmd' def publishVersion = project.findProperty("publish.version") -def pomDocument = javax.xml.parsers.DocumentBuilderFactory.newInstance() - .newDocumentBuilder() - .parse(file('../pom.xml')) -def revision = pomDocument.getElementsByTagName('revision').item(0)?.getTextContent()?.trim() -if (!revision) { - throw new GradleException('Could not read the project revision from ../pom.xml') -} +def revision = providers.fileContents(layout.projectDirectory.file('../pom.xml')).asText.map { pomText -> + def revisionMatcher = pomText =~ /\s*([^<]+)\s*<\/revision>/ + if (!revisionMatcher.find()) { + throw new GradleException('Could not read the project revision from ../pom.xml') + } + revisionMatcher.group(1).trim() +}.get() version = publishVersion ?: revision +def pluginVersion = version.toString() description = 'Compiles gmd files into markdown, html or pdf' jar { @@ -28,6 +29,12 @@ jar { } } +processResources { + filesMatching('gmd-version.properties') { + expand(gmdVersion: pluginVersion) + } +} + gradlePlugin { website.set("https://github.com/Alipsa/GroovyMarkdown") vcsUrl.set("https://github.com/Alipsa/GroovyMarkdown.git") diff --git a/gmd-gradle-plugin/release.md b/gmd-gradle-plugin/release.md index 5621e6c..ee6060c 100644 --- a/gmd-gradle-plugin/release.md +++ b/gmd-gradle-plugin/release.md @@ -1,7 +1,7 @@ # GMD Gradle Plugin release history ## v3.2.0, unreleased -- derive the default GMD core version from the Gradle plugin artifact version, with a released fallback for development and repackaged builds +- derive the default GMD core version from a generated plugin-version resource, then the artifact manifest, with a released fallback for older and repackaged builds - derive the development artifact version from the root Maven revision and require an explicit version when publishing - upgrade the Gradle Versions Plugin from 0.58.0 to 0.61.0 - upgrade the JUnit BOM from 6.1.2 to 6.1.3 diff --git a/gmd-gradle-plugin/src/main/resources/gmd-version.properties b/gmd-gradle-plugin/src/main/resources/gmd-version.properties new file mode 100644 index 0000000..fe31744 --- /dev/null +++ b/gmd-gradle-plugin/src/main/resources/gmd-version.properties @@ -0,0 +1 @@ +gmd.version=${gmdVersion} diff --git a/gmd-maven-plugin/src/test/java/test/alipsa/gmd/maven/GmdMavenPluginTest.java b/gmd-maven-plugin/src/test/java/test/alipsa/gmd/maven/GmdMavenPluginTest.java index 64d9e70..6396317 100644 --- a/gmd-maven-plugin/src/test/java/test/alipsa/gmd/maven/GmdMavenPluginTest.java +++ b/gmd-maven-plugin/src/test/java/test/alipsa/gmd/maven/GmdMavenPluginTest.java @@ -9,6 +9,7 @@ import java.nio.file.Files; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; @MojoTest @@ -19,7 +20,10 @@ public class GmdMavenPluginTest { public void testGmdMavenPlugin(GmdMavenPlugin plugin) throws Exception { File pomFile = new File("src/test/projects/"); assertTrue(pomFile.exists()); - assertEquals(System.getProperty("gmd.plugin.version"), plugin.getGmdVersion()); + String expectedVersion = System.getProperty("gmd.plugin.version"); + assertNotNull(expectedVersion); + assertNotNull(plugin.getGmdVersion()); + assertEquals(expectedVersion, plugin.getGmdVersion()); // Execute the plugin plugin.execute(); From 6a555b8f010ddf028baf06ae1f9376456238833f Mon Sep 17 00:00:00 2001 From: per Date: Sun, 9 Aug 2026 17:18:09 +0200 Subject: [PATCH 07/16] harden generated plugin version handling --- gmd-gradle-plugin/build.gradle | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/gmd-gradle-plugin/build.gradle b/gmd-gradle-plugin/build.gradle index 73fa4e2..638bdbf 100644 --- a/gmd-gradle-plugin/build.gradle +++ b/gmd-gradle-plugin/build.gradle @@ -12,12 +12,30 @@ plugins { group = 'se.alipsa.gmd' def publishVersion = project.findProperty("publish.version") -def revision = providers.fileContents(layout.projectDirectory.file('../pom.xml')).asText.map { pomText -> - def revisionMatcher = pomText =~ /\s*([^<]+)\s*<\/revision>/ - if (!revisionMatcher.find()) { +def revision = providers.fileContents(layout.projectDirectory.file('../pom.xml')).asText.orElse('').map { pomText -> + if (!pomText) { throw new GradleException('Could not read the project revision from ../pom.xml') } - revisionMatcher.group(1).trim() + try { + def documentBuilderFactory = javax.xml.parsers.DocumentBuilderFactory.newInstance() + documentBuilderFactory.setNamespaceAware(true) + def document = documentBuilderFactory.newDocumentBuilder().parse( + new org.xml.sax.InputSource(new java.io.StringReader(pomText)) + ) + def revisionNodes = document.getElementsByTagNameNS('*', 'revision') + if (revisionNodes.length == 0) { + throw new GradleException('Could not read the project revision from ../pom.xml') + } + String value = revisionNodes.item(0).getTextContent()?.trim() + if (!value) { + throw new GradleException('Could not read the project revision from ../pom.xml') + } + value + } catch (GradleException e) { + throw e + } catch (Exception e) { + throw new GradleException('Could not read the project revision from ../pom.xml', e) + } }.get() version = publishVersion ?: revision def pluginVersion = version.toString() @@ -30,6 +48,7 @@ jar { } processResources { + inputs.property('gmdVersion', pluginVersion) filesMatching('gmd-version.properties') { expand(gmdVersion: pluginVersion) } From 4eed1ee4597cca98bbab825def2d77fc706b5a9d Mon Sep 17 00:00:00 2001 From: per Date: Sun, 9 Aug 2026 17:34:24 +0200 Subject: [PATCH 08/16] simplify Gradle plugin version resolution --- CLAUDE.md | 2 +- gmd-gradle-plugin/build.gradle | 6 ------ gmd-gradle-plugin/release.md | 2 +- 3 files changed, 2 insertions(+), 8 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 90ed549..e18997f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -69,7 +69,7 @@ java -jar gmd-core/target/gmd-3.2.0-SNAPSHOT.jar toPdfRaw input.gmd output.pdf All child modules automatically inherit this version via `${revision}`. The `flatten-maven-plugin` resolves this during build/deployment. -When bumping `revision`, verify that the Maven `${plugin.version}` default and Gradle resource/manifest-based default remain synchronized with the core version, and update their user documentation if the default behavior changes. +When bumping `revision`, verify that the Maven `${plugin.version}` default and Gradle resource-based default remain synchronized with the core version, and update their user documentation if the default behavior changes. ## Project Structure diff --git a/gmd-gradle-plugin/build.gradle b/gmd-gradle-plugin/build.gradle index 638bdbf..d0a8052 100644 --- a/gmd-gradle-plugin/build.gradle +++ b/gmd-gradle-plugin/build.gradle @@ -41,12 +41,6 @@ version = publishVersion ?: revision def pluginVersion = version.toString() description = 'Compiles gmd files into markdown, html or pdf' -jar { - manifest { - attributes('Implementation-Version': project.version) - } -} - processResources { inputs.property('gmdVersion', pluginVersion) filesMatching('gmd-version.properties') { diff --git a/gmd-gradle-plugin/release.md b/gmd-gradle-plugin/release.md index ee6060c..eff623f 100644 --- a/gmd-gradle-plugin/release.md +++ b/gmd-gradle-plugin/release.md @@ -1,7 +1,7 @@ # GMD Gradle Plugin release history ## v3.2.0, unreleased -- derive the default GMD core version from a generated plugin-version resource, then the artifact manifest, with a released fallback for older and repackaged builds +- derive the default GMD core version from a generated plugin-version resource populated from the root Maven revision - derive the development artifact version from the root Maven revision and require an explicit version when publishing - upgrade the Gradle Versions Plugin from 0.58.0 to 0.61.0 - upgrade the JUnit BOM from 6.1.2 to 6.1.3 From 0e36fabf0f592d3c68d43fba50b07086d0ad225b Mon Sep 17 00:00:00 2001 From: per Date: Sun, 9 Aug 2026 17:52:51 +0200 Subject: [PATCH 09/16] harden Gradle version metadata parsing --- gmd-gradle-plugin/build.gradle | 55 +++++++++++-------- gmd-gradle-plugin/release.md | 2 +- .../gmd/gradle/GmdGradlePluginTest.groovy | 6 +- 3 files changed, 37 insertions(+), 26 deletions(-) diff --git a/gmd-gradle-plugin/build.gradle b/gmd-gradle-plugin/build.gradle index d0a8052..8932a43 100644 --- a/gmd-gradle-plugin/build.gradle +++ b/gmd-gradle-plugin/build.gradle @@ -12,31 +12,42 @@ plugins { group = 'se.alipsa.gmd' def publishVersion = project.findProperty("publish.version") -def revision = providers.fileContents(layout.projectDirectory.file('../pom.xml')).asText.orElse('').map { pomText -> - if (!pomText) { - throw new GradleException('Could not read the project revision from ../pom.xml') - } - try { - def documentBuilderFactory = javax.xml.parsers.DocumentBuilderFactory.newInstance() - documentBuilderFactory.setNamespaceAware(true) - def document = documentBuilderFactory.newDocumentBuilder().parse( - new org.xml.sax.InputSource(new java.io.StringReader(pomText)) - ) - def revisionNodes = document.getElementsByTagNameNS('*', 'revision') - if (revisionNodes.length == 0) { +def revision = null +if (!publishVersion) { + revision = providers.fileContents(layout.projectDirectory.file('../pom.xml')).asText.orElse('').map { pomText -> + if (!pomText) { throw new GradleException('Could not read the project revision from ../pom.xml') } - String value = revisionNodes.item(0).getTextContent()?.trim() - if (!value) { - throw new GradleException('Could not read the project revision from ../pom.xml') + try { + def documentBuilderFactory = javax.xml.parsers.DocumentBuilderFactory.newInstance() + documentBuilderFactory.setNamespaceAware(true) + documentBuilderFactory.setFeature(javax.xml.XMLConstants.FEATURE_SECURE_PROCESSING, true) + documentBuilderFactory.setFeature('http://apache.org/xml/features/disallow-doctype-decl', true) + documentBuilderFactory.setFeature('http://apache.org/xml/features/nonvalidating/load-external-dtd', false) + documentBuilderFactory.setFeature('http://xml.org/sax/features/external-general-entities', false) + documentBuilderFactory.setFeature('http://xml.org/sax/features/external-parameter-entities', false) + documentBuilderFactory.setAttribute(javax.xml.XMLConstants.ACCESS_EXTERNAL_DTD, '') + documentBuilderFactory.setAttribute(javax.xml.XMLConstants.ACCESS_EXTERNAL_SCHEMA, '') + documentBuilderFactory.setXIncludeAware(false) + documentBuilderFactory.setExpandEntityReferences(false) + def document = documentBuilderFactory.newDocumentBuilder().parse( + new org.xml.sax.InputSource(new java.io.StringReader(pomText)) + ) + String value = javax.xml.xpath.XPathFactory.newInstance().newXPath().evaluate( + "/*[local-name()='project']/*[local-name()='properties']/*[local-name()='revision']/text()", + document + )?.trim() + if (!value) { + throw new GradleException('Could not read the project revision from ../pom.xml') + } + value + } catch (GradleException e) { + throw e + } catch (Exception e) { + throw new GradleException('Could not read the project revision from ../pom.xml', e) } - value - } catch (GradleException e) { - throw e - } catch (Exception e) { - throw new GradleException('Could not read the project revision from ../pom.xml', e) - } -}.get() + }.get() +} version = publishVersion ?: revision def pluginVersion = version.toString() description = 'Compiles gmd files into markdown, html or pdf' diff --git a/gmd-gradle-plugin/release.md b/gmd-gradle-plugin/release.md index eff623f..ed4c0c3 100644 --- a/gmd-gradle-plugin/release.md +++ b/gmd-gradle-plugin/release.md @@ -1,7 +1,7 @@ # GMD Gradle Plugin release history ## v3.2.0, unreleased -- derive the default GMD core version from a generated plugin-version resource populated from the root Maven revision +- derive the default GMD core version from a generated plugin-version resource populated from the root Maven revision, failing clearly if metadata is unavailable - derive the development artifact version from the root Maven revision and require an explicit version when publishing - upgrade the Gradle Versions Plugin from 0.58.0 to 0.61.0 - upgrade the JUnit BOM from 6.1.2 to 6.1.3 diff --git a/gmd-gradle-plugin/src/test/groovy/test/alipsa/gmd/gradle/GmdGradlePluginTest.groovy b/gmd-gradle-plugin/src/test/groovy/test/alipsa/gmd/gradle/GmdGradlePluginTest.groovy index 10df153..71ec23e 100644 --- a/gmd-gradle-plugin/src/test/groovy/test/alipsa/gmd/gradle/GmdGradlePluginTest.groovy +++ b/gmd-gradle-plugin/src/test/groovy/test/alipsa/gmd/gradle/GmdGradlePluginTest.groovy @@ -55,13 +55,13 @@ class GmdGradlePluginTest { group = 'test.alipsa.gmd' version = '1.0.0-SNAPSHOT' repositories { - // Enable us to to use local snapshots - mavenLocal() + mavenCentral() } gmdPlugin { sourceDir = 'src/test/gmd' targetDir = 'build/target' outputType = 'html' + gmdVersion = '3.1.0' // Keep the standalone TestKit test independent of unpublished snapshots. runTaskBefore = 'build' // we dont have tests so specify the task to not get a warning } """.stripIndent() @@ -70,7 +70,7 @@ class GmdGradlePluginTest { settingsFile.text = """ pluginManagement { repositories { - mavenLocal() + mavenCentral() } plugins { id 'se.alipsa.gmd.gmd-gradle-plugin' version "1.0.0" From 305ddbba6baf1ecf130c298a4d376c351cbe98a1 Mon Sep 17 00:00:00 2001 From: per Date: Sun, 9 Aug 2026 18:14:22 +0200 Subject: [PATCH 10/16] test generated Gradle version metadata --- gmd-gradle-plugin/build.gradle | 2 +- .../gmd/gradle/GmdGradlePluginTest.groovy | 20 +++++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/gmd-gradle-plugin/build.gradle b/gmd-gradle-plugin/build.gradle index 8932a43..acae194 100644 --- a/gmd-gradle-plugin/build.gradle +++ b/gmd-gradle-plugin/build.gradle @@ -34,7 +34,7 @@ if (!publishVersion) { new org.xml.sax.InputSource(new java.io.StringReader(pomText)) ) String value = javax.xml.xpath.XPathFactory.newInstance().newXPath().evaluate( - "/*[local-name()='project']/*[local-name()='properties']/*[local-name()='revision']/text()", + "/*[local-name()='project']/*[local-name()='properties']/*[local-name()='revision']", document )?.trim() if (!value) { diff --git a/gmd-gradle-plugin/src/test/groovy/test/alipsa/gmd/gradle/GmdGradlePluginTest.groovy b/gmd-gradle-plugin/src/test/groovy/test/alipsa/gmd/gradle/GmdGradlePluginTest.groovy index 71ec23e..0f111c3 100644 --- a/gmd-gradle-plugin/src/test/groovy/test/alipsa/gmd/gradle/GmdGradlePluginTest.groovy +++ b/gmd-gradle-plugin/src/test/groovy/test/alipsa/gmd/gradle/GmdGradlePluginTest.groovy @@ -4,10 +4,30 @@ import groovy.ant.AntBuilder import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Test import org.gradle.testkit.runner.GradleRunner +import se.alipsa.gmd.gradle.GmdGradlePlugin + +import java.util.Properties + import static org.gradle.testkit.runner.TaskOutcome.SUCCESS class GmdGradlePluginTest { + @Test + void defaultGmdVersionComesFromGeneratedResource() { + URL resource = GmdGradlePlugin.class.getResource('/gmd-version.properties') + Assertions.assertNotNull(resource, 'The plugin version resource must be generated during processResources') + + Properties properties = new Properties() + resource.withInputStream { properties.load(it) } + String resourceVersion = properties.getProperty('gmd.version') + Assertions.assertNotNull(resourceVersion) + Assertions.assertFalse(resourceVersion.contains('$'), "The generated resource must be expanded: $resourceVersion") + + def method = GmdGradlePlugin.class.getDeclaredMethod('defaultGmdVersion') + method.setAccessible(true) + Assertions.assertEquals(resourceVersion, method.invoke(null)) + } + @Test void testPlugin() { File targetDir = null From 50244be413ee07eefd2b933dcef8f92b16c61fc6 Mon Sep 17 00:00:00 2001 From: per Date: Sun, 9 Aug 2026 18:50:02 +0200 Subject: [PATCH 11/16] strengthen version and processor contract tests --- .../test/alipsa/groovy/gmd/GmdProcessorTest.groovy | 12 ++++++++++++ gmd-gradle-plugin/build.gradle | 1 + .../alipsa/gmd/gradle/GmdGradlePluginTest.groovy | 3 +++ 3 files changed, 16 insertions(+) diff --git a/gmd-core/src/test/groovy/test/alipsa/groovy/gmd/GmdProcessorTest.groovy b/gmd-core/src/test/groovy/test/alipsa/groovy/gmd/GmdProcessorTest.groovy index 0265d67..ad12753 100644 --- a/gmd-core/src/test/groovy/test/alipsa/groovy/gmd/GmdProcessorTest.groovy +++ b/gmd-core/src/test/groovy/test/alipsa/groovy/gmd/GmdProcessorTest.groovy @@ -39,4 +39,16 @@ class GmdProcessorTest extends AbstractGmdTest { assertTrue(md.contains('# Hi')) assertTrue(!md.contains(' Date: Sun, 9 Aug 2026 21:09:07 +0200 Subject: [PATCH 12/16] close test coverage gaps --- .../alipsa/groovy/gmd/GmdProcessorTest.groovy | 5 +++ gmd-gradle-plugin/build.gradle | 1 + .../gmd/gradle/GmdGradlePluginTest.groovy | 34 +++++++++++++++++++ 3 files changed, 40 insertions(+) diff --git a/gmd-core/src/test/groovy/test/alipsa/groovy/gmd/GmdProcessorTest.groovy b/gmd-core/src/test/groovy/test/alipsa/groovy/gmd/GmdProcessorTest.groovy index ad12753..e4f4350 100644 --- a/gmd-core/src/test/groovy/test/alipsa/groovy/gmd/GmdProcessorTest.groovy +++ b/gmd-core/src/test/groovy/test/alipsa/groovy/gmd/GmdProcessorTest.groovy @@ -3,6 +3,7 @@ package test.alipsa.groovy.gmd import org.junit.jupiter.api.Test import se.alipsa.gmd.core.GmdProcessor +import static org.junit.jupiter.api.Assertions.assertEquals import static org.junit.jupiter.api.Assertions.assertTrue class GmdProcessorTest extends AbstractGmdTest { @@ -18,6 +19,7 @@ class GmdProcessorTest extends AbstractGmdTest { void htmlOutputIsACompleteDocument() { File src = sourceDirWith('doc', "# Hi\n\n```{groovy}\nout.println('x')\n```\n") File target = new File(AbstractGmdTest.testOutputDir, 'out-doc') + target.deleteDir() new GmdProcessor().process(src.absolutePath, target.absolutePath, 'html') @@ -32,6 +34,7 @@ class GmdProcessorTest extends AbstractGmdTest { void mdOutputIsStillPlainMarkdown() { File src = sourceDirWith('plain', "# Hi\n\n```{groovy echo=false}\nout.println('x')\n```\n") File target = new File(AbstractGmdTest.testOutputDir, 'out-plain') + target.deleteDir() new GmdProcessor().process(src.absolutePath, target.absolutePath, 'md') @@ -44,7 +47,9 @@ class GmdProcessorTest extends AbstractGmdTest { void commandLineEntryPointAcceptsSourceTargetAndOutputTypeArguments() { File src = sourceDirWith('command-line', "# Hi\n") File target = new File(AbstractGmdTest.testOutputDir, 'out-command-line') + target.deleteDir() + assertEquals('se.alipsa.gmd.core.GmdProcessor', GmdProcessor.name) GmdProcessor.main([src.absolutePath, target.absolutePath, 'md'] as String[]) File output = new File(target, 'command-line.md') diff --git a/gmd-gradle-plugin/build.gradle b/gmd-gradle-plugin/build.gradle index c40e968..875d396 100644 --- a/gmd-gradle-plugin/build.gradle +++ b/gmd-gradle-plugin/build.gradle @@ -90,6 +90,7 @@ dependencies { test { systemProperty('gmd.plugin.version', pluginVersion) + systemProperty('gmd.publish.version', publishVersion ?: '') useJUnitPlatform() testLogging { // set options for log level LIFECYCLE diff --git a/gmd-gradle-plugin/src/test/groovy/test/alipsa/gmd/gradle/GmdGradlePluginTest.groovy b/gmd-gradle-plugin/src/test/groovy/test/alipsa/gmd/gradle/GmdGradlePluginTest.groovy index ac3f0d6..431863a 100644 --- a/gmd-gradle-plugin/src/test/groovy/test/alipsa/gmd/gradle/GmdGradlePluginTest.groovy +++ b/gmd-gradle-plugin/src/test/groovy/test/alipsa/gmd/gradle/GmdGradlePluginTest.groovy @@ -12,6 +12,36 @@ import static org.gradle.testkit.runner.TaskOutcome.SUCCESS class GmdGradlePluginTest { + private static String rootPomRevision() { + def factory = javax.xml.parsers.DocumentBuilderFactory.newInstance() + factory.setNamespaceAware(true) + factory.setFeature(javax.xml.XMLConstants.FEATURE_SECURE_PROCESSING, true) + factory.setFeature('http://apache.org/xml/features/disallow-doctype-decl', true) + factory.setFeature('http://xml.org/sax/features/external-general-entities', false) + factory.setFeature('http://xml.org/sax/features/external-parameter-entities', false) + factory.setAttribute(javax.xml.XMLConstants.ACCESS_EXTERNAL_DTD, '') + factory.setAttribute(javax.xml.XMLConstants.ACCESS_EXTERNAL_SCHEMA, '') + factory.setXIncludeAware(false) + factory.setExpandEntityReferences(false) + def document = factory.newDocumentBuilder().parse(new File('../pom.xml')) + def project = document.documentElement + def properties = directElementChild(project, 'properties') + def revision = directElementChild(properties, 'revision') + Assertions.assertNotNull(revision, 'The root POM must define project/properties/revision') + revision.getTextContent().trim() + } + + private static org.w3c.dom.Node directElementChild(org.w3c.dom.Node parent, String localName) { + Assertions.assertNotNull(parent, "Expected a direct $localName element") + for (int i = 0; i < parent.getChildNodes().getLength(); i++) { + def child = parent.getChildNodes().item(i) + if (child.getNodeType() == org.w3c.dom.Node.ELEMENT_NODE && child.getLocalName() == localName) { + return child + } + } + return null + } + @Test void defaultGmdVersionComesFromGeneratedResource() { URL resource = GmdGradlePlugin.class.getResource('/gmd-version.properties') @@ -21,8 +51,12 @@ class GmdGradlePluginTest { resource.withInputStream { properties.load(it) } String resourceVersion = properties.getProperty('gmd.version') String expectedVersion = System.getProperty('gmd.plugin.version') + String publishVersion = System.getProperty('gmd.publish.version') Assertions.assertNotNull(expectedVersion) Assertions.assertNotNull(resourceVersion) + if (!publishVersion) { + Assertions.assertEquals(rootPomRevision(), expectedVersion) + } Assertions.assertEquals(expectedVersion, resourceVersion) Assertions.assertFalse(resourceVersion.contains('$'), "The generated resource must be expanded: $resourceVersion") From c21fe6ca1bc15391b3f17c9886a7d941110a01d9 Mon Sep 17 00:00:00 2001 From: per Date: Sun, 9 Aug 2026 21:37:07 +0200 Subject: [PATCH 13/16] harden remaining test and fork paths --- .../groovy/test/alipsa/groovy/gmd/GmdProcessorTest.groovy | 4 ++++ gmd-gradle-plugin/build.gradle | 1 + .../test/alipsa/gmd/gradle/GmdGradlePluginTest.groovy | 6 ++++-- .../src/main/java/se/alipsa/gmd/maven/GmdMavenPlugin.java | 2 +- 4 files changed, 10 insertions(+), 3 deletions(-) diff --git a/gmd-core/src/test/groovy/test/alipsa/groovy/gmd/GmdProcessorTest.groovy b/gmd-core/src/test/groovy/test/alipsa/groovy/gmd/GmdProcessorTest.groovy index e4f4350..0cc8f10 100644 --- a/gmd-core/src/test/groovy/test/alipsa/groovy/gmd/GmdProcessorTest.groovy +++ b/gmd-core/src/test/groovy/test/alipsa/groovy/gmd/GmdProcessorTest.groovy @@ -4,6 +4,7 @@ import org.junit.jupiter.api.Test import se.alipsa.gmd.core.GmdProcessor import static org.junit.jupiter.api.Assertions.assertEquals +import static org.junit.jupiter.api.Assertions.assertFalse import static org.junit.jupiter.api.Assertions.assertTrue class GmdProcessorTest extends AbstractGmdTest { @@ -20,6 +21,7 @@ class GmdProcessorTest extends AbstractGmdTest { File src = sourceDirWith('doc', "# Hi\n\n```{groovy}\nout.println('x')\n```\n") File target = new File(AbstractGmdTest.testOutputDir, 'out-doc') target.deleteDir() + assertFalse(target.exists(), "Could not clear test target ${target.absolutePath}") new GmdProcessor().process(src.absolutePath, target.absolutePath, 'html') @@ -35,6 +37,7 @@ class GmdProcessorTest extends AbstractGmdTest { File src = sourceDirWith('plain', "# Hi\n\n```{groovy echo=false}\nout.println('x')\n```\n") File target = new File(AbstractGmdTest.testOutputDir, 'out-plain') target.deleteDir() + assertFalse(target.exists(), "Could not clear test target ${target.absolutePath}") new GmdProcessor().process(src.absolutePath, target.absolutePath, 'md') @@ -48,6 +51,7 @@ class GmdProcessorTest extends AbstractGmdTest { File src = sourceDirWith('command-line', "# Hi\n") File target = new File(AbstractGmdTest.testOutputDir, 'out-command-line') target.deleteDir() + assertFalse(target.exists(), "Could not clear test target ${target.absolutePath}") assertEquals('se.alipsa.gmd.core.GmdProcessor', GmdProcessor.name) GmdProcessor.main([src.absolutePath, target.absolutePath, 'md'] as String[]) diff --git a/gmd-gradle-plugin/build.gradle b/gmd-gradle-plugin/build.gradle index 875d396..7d571ba 100644 --- a/gmd-gradle-plugin/build.gradle +++ b/gmd-gradle-plugin/build.gradle @@ -91,6 +91,7 @@ dependencies { test { systemProperty('gmd.plugin.version', pluginVersion) systemProperty('gmd.publish.version', publishVersion ?: '') + systemProperty('gmd.root.pom', layout.projectDirectory.file('../pom.xml').asFile.absolutePath) useJUnitPlatform() testLogging { // set options for log level LIFECYCLE diff --git a/gmd-gradle-plugin/src/test/groovy/test/alipsa/gmd/gradle/GmdGradlePluginTest.groovy b/gmd-gradle-plugin/src/test/groovy/test/alipsa/gmd/gradle/GmdGradlePluginTest.groovy index 431863a..9263f62 100644 --- a/gmd-gradle-plugin/src/test/groovy/test/alipsa/gmd/gradle/GmdGradlePluginTest.groovy +++ b/gmd-gradle-plugin/src/test/groovy/test/alipsa/gmd/gradle/GmdGradlePluginTest.groovy @@ -23,7 +23,9 @@ class GmdGradlePluginTest { factory.setAttribute(javax.xml.XMLConstants.ACCESS_EXTERNAL_SCHEMA, '') factory.setXIncludeAware(false) factory.setExpandEntityReferences(false) - def document = factory.newDocumentBuilder().parse(new File('../pom.xml')) + String rootPomPath = System.getProperty('gmd.root.pom') + Assertions.assertNotNull(rootPomPath, 'The root POM path must be provided by the Gradle test task') + def document = factory.newDocumentBuilder().parse(new File(rootPomPath)) def project = document.documentElement def properties = directElementChild(project, 'properties') def revision = directElementChild(properties, 'revision') @@ -32,7 +34,7 @@ class GmdGradlePluginTest { } private static org.w3c.dom.Node directElementChild(org.w3c.dom.Node parent, String localName) { - Assertions.assertNotNull(parent, "Expected a direct $localName element") + Assertions.assertNotNull(parent, "Expected a parent element containing direct $localName") for (int i = 0; i < parent.getChildNodes().getLength(); i++) { def child = parent.getChildNodes().item(i) if (child.getNodeType() == org.w3c.dom.Node.ELEMENT_NODE && child.getLocalName() == localName) { diff --git a/gmd-maven-plugin/src/main/java/se/alipsa/gmd/maven/GmdMavenPlugin.java b/gmd-maven-plugin/src/main/java/se/alipsa/gmd/maven/GmdMavenPlugin.java index 8b344d0..8d516b5 100644 --- a/gmd-maven-plugin/src/main/java/se/alipsa/gmd/maven/GmdMavenPlugin.java +++ b/gmd-maven-plugin/src/main/java/se/alipsa/gmd/maven/GmdMavenPlugin.java @@ -178,7 +178,7 @@ public void execute() throws MojoExecutionException, MojoFailureException { command.add(getJavaExecutable()); command.add("-cp"); command.add(classpath.toString()); - command.add("se.alipsa.gmd.core.GmdProcessor"); + command.add(se.alipsa.gmd.core.GmdProcessor.class.getName()); command.add(srcDir.getCanonicalPath()); command.add(outputDirectory.getCanonicalPath()); command.add(normalizedOutputType); From e97a378ae212c086e7e767a4c48355e6e7d3c2bd Mon Sep 17 00:00:00 2001 From: per Date: Sun, 9 Aug 2026 22:04:20 +0200 Subject: [PATCH 14/16] cover remaining test paths --- .../alipsa/groovy/gmd/AbstractGmdTest.groovy | 7 +- .../alipsa/gmd/maven/GmdMavenPluginTest.java | 88 +++++++++++++++++++ 2 files changed, 93 insertions(+), 2 deletions(-) diff --git a/gmd-core/src/test/groovy/test/alipsa/groovy/gmd/AbstractGmdTest.groovy b/gmd-core/src/test/groovy/test/alipsa/groovy/gmd/AbstractGmdTest.groovy index a0450a5..df39bcb 100644 --- a/gmd-core/src/test/groovy/test/alipsa/groovy/gmd/AbstractGmdTest.groovy +++ b/gmd-core/src/test/groovy/test/alipsa/groovy/gmd/AbstractGmdTest.groovy @@ -8,8 +8,11 @@ class AbstractGmdTest { @BeforeAll static void init() { - if (!testOutputDir.exists()) { - testOutputDir.mkdirs() + if (testOutputDir.exists()) { + testOutputDir.deleteDir() + } + if (testOutputDir.exists() || (!testOutputDir.mkdirs() && !testOutputDir.isDirectory())) { + throw new IllegalStateException("Could not recreate test output directory ${testOutputDir.absolutePath}") } } } diff --git a/gmd-maven-plugin/src/test/java/test/alipsa/gmd/maven/GmdMavenPluginTest.java b/gmd-maven-plugin/src/test/java/test/alipsa/gmd/maven/GmdMavenPluginTest.java index 6396317..01ca834 100644 --- a/gmd-maven-plugin/src/test/java/test/alipsa/gmd/maven/GmdMavenPluginTest.java +++ b/gmd-maven-plugin/src/test/java/test/alipsa/gmd/maven/GmdMavenPluginTest.java @@ -2,15 +2,35 @@ import org.apache.maven.api.plugin.testing.InjectMojo; import org.apache.maven.api.plugin.testing.MojoTest; +import org.apache.maven.execution.MavenSession; import org.junit.jupiter.api.Test; import se.alipsa.gmd.maven.GmdMavenPlugin; import java.io.File; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.lang.reflect.Field; import java.nio.file.Files; +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; + +import org.eclipse.aether.RepositorySystem; +import org.eclipse.aether.RepositorySystemSession; +import org.eclipse.aether.artifact.Artifact; +import org.eclipse.aether.repository.LocalRepositoryManager; +import org.eclipse.aether.resolution.ArtifactRequest; +import org.eclipse.aether.resolution.ArtifactResult; +import org.eclipse.aether.resolution.DependencyRequest; +import org.eclipse.aether.resolution.DependencyResult; +import org.mockito.Mockito; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; @MojoTest public class GmdMavenPluginTest { @@ -47,4 +67,72 @@ public void testGmdMavenPlugin(GmdMavenPlugin plugin) throws Exception { assertTrue(testInlineHtml.contains(" and the time is ")); } + @Test + @InjectMojo(goal = "processGmd", pom = "src/test/projects/pom.xml") + public void testGmdMavenPluginWithResolvedDependencies(GmdMavenPlugin plugin) throws Exception { + RepositorySystem repositorySystem = Mockito.mock(RepositorySystem.class); + RepositorySystemSession repositorySession = Mockito.mock(RepositorySystemSession.class); + LocalRepositoryManager localRepositoryManager = Mockito.mock(LocalRepositoryManager.class); + MavenSession session = Mockito.mock(MavenSession.class); + DependencyResult dependencyResult = new DependencyResult(new DependencyRequest()); + + when(session.getRepositorySession()).thenReturn(repositorySession); + when(repositorySession.getLocalRepositoryManager()).thenReturn(localRepositoryManager); + when(repositorySystem.resolveDependencies(any(RepositorySystemSession.class), any(DependencyRequest.class))) + .thenReturn(dependencyResult); + + List artifactResults = Arrays.stream(System.getProperty("java.class.path") + .split(java.util.regex.Pattern.quote(File.pathSeparator))) + .map(path -> { + Artifact artifact = Mockito.mock(Artifact.class); + when(artifact.getFile()).thenReturn(new File(path)); + return new ArtifactResult(new ArtifactRequest()).setArtifact(artifact); + }) + .toList(); + dependencyResult.setArtifactResults(artifactResults); + + setField(plugin, "repositorySystem", repositorySystem); + setField(plugin, "session", session); + setField(plugin, "targetDir", "target/gmd-resolved"); + File outputDirectory = new File(plugin.getTargetDir()); + deleteDirectory(outputDirectory); + assertFalse(outputDirectory.exists(), "Could not clear resolved-dependency test output"); + + try { + plugin.execute(); + + File testHtml = new File(plugin.getTargetDir(), "test.html"); + assertTrue(testHtml.isFile(), "The resolved-dependency fork did not write test.html"); + assertTrue(Files.readString(testHtml.toPath()).contains("

Greetings

")); + Mockito.verify(repositorySystem).resolveDependencies(any(RepositorySystemSession.class), any(DependencyRequest.class)); + } finally { + deleteDirectory(outputDirectory); + } + } + + private static void deleteDirectory(File directory) throws IOException { + if (!directory.exists()) { + return; + } + try (var paths = Files.walk(directory.toPath())) { + try { + paths.sorted(Comparator.reverseOrder()).forEach(path -> { + try { + Files.deleteIfExists(path); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + }); + } catch (UncheckedIOException e) { + throw e.getCause(); + } + } + } + + private static void setField(Object target, String name, Object value) throws Exception { + Field field = target.getClass().getDeclaredField(name); + field.setAccessible(true); + field.set(target, value); + } + } From a6711e8e2d32286294518e966a0854ed18b73c40 Mon Sep 17 00:00:00 2001 From: per Date: Sun, 9 Aug 2026 23:18:27 +0200 Subject: [PATCH 15/16] harden fork failure and test isolation --- .../alipsa/groovy/gmd/AbstractGmdTest.groovy | 15 +---- .../alipsa/groovy/gmd/GmdHighlightTest.groovy | 2 +- .../alipsa/groovy/gmd/GmdProcessorTest.groovy | 10 +-- .../test/alipsa/groovy/gmd/GmdTest.groovy | 30 ++++----- .../se/alipsa/gmd/maven/GmdMavenPlugin.java | 11 +++- .../alipsa/gmd/maven/GmdMavenPluginTest.java | 66 +++++++++++++++---- 6 files changed, 88 insertions(+), 46 deletions(-) diff --git a/gmd-core/src/test/groovy/test/alipsa/groovy/gmd/AbstractGmdTest.groovy b/gmd-core/src/test/groovy/test/alipsa/groovy/gmd/AbstractGmdTest.groovy index df39bcb..83307f9 100644 --- a/gmd-core/src/test/groovy/test/alipsa/groovy/gmd/AbstractGmdTest.groovy +++ b/gmd-core/src/test/groovy/test/alipsa/groovy/gmd/AbstractGmdTest.groovy @@ -1,18 +1,9 @@ package test.alipsa.groovy.gmd -import org.junit.jupiter.api.BeforeAll +import org.junit.jupiter.api.io.TempDir class AbstractGmdTest { - static File testOutputDir = new File("build/test-results/") - - @BeforeAll - static void init() { - if (testOutputDir.exists()) { - testOutputDir.deleteDir() - } - if (testOutputDir.exists() || (!testOutputDir.mkdirs() && !testOutputDir.isDirectory())) { - throw new IllegalStateException("Could not recreate test output directory ${testOutputDir.absolutePath}") - } - } + @TempDir + File testOutputDir } diff --git a/gmd-core/src/test/groovy/test/alipsa/groovy/gmd/GmdHighlightTest.groovy b/gmd-core/src/test/groovy/test/alipsa/groovy/gmd/GmdHighlightTest.groovy index 36309cb..a0ce8e2 100644 --- a/gmd-core/src/test/groovy/test/alipsa/groovy/gmd/GmdHighlightTest.groovy +++ b/gmd-core/src/test/groovy/test/alipsa/groovy/gmd/GmdHighlightTest.groovy @@ -32,7 +32,7 @@ class GmdHighlightTest extends AbstractGmdTest { def html = gmd.gmdToHtmlDoc(text) // create a pdf file from the html - def pdfFile = new File(AbstractGmdTest.testOutputDir, "testHighlight.pdf") + def pdfFile = new File(testOutputDir, "testHighlight.pdf") if (pdfFile.exists()) pdfFile.delete() gmd.processHtmlAndSaveAsPdf(html, pdfFile) //gmd.gmdToPdf(text, pdfFile) diff --git a/gmd-core/src/test/groovy/test/alipsa/groovy/gmd/GmdProcessorTest.groovy b/gmd-core/src/test/groovy/test/alipsa/groovy/gmd/GmdProcessorTest.groovy index 0cc8f10..96a26fe 100644 --- a/gmd-core/src/test/groovy/test/alipsa/groovy/gmd/GmdProcessorTest.groovy +++ b/gmd-core/src/test/groovy/test/alipsa/groovy/gmd/GmdProcessorTest.groovy @@ -9,8 +9,8 @@ import static org.junit.jupiter.api.Assertions.assertTrue class GmdProcessorTest extends AbstractGmdTest { - private static File sourceDirWith(String name, String content) { - File dir = new File(AbstractGmdTest.testOutputDir, "src-${name}") + private File sourceDirWith(String name, String content) { + File dir = new File(testOutputDir, "src-${name}") dir.mkdirs() new File(dir, "${name}.gmd").text = content return dir @@ -19,7 +19,7 @@ class GmdProcessorTest extends AbstractGmdTest { @Test void htmlOutputIsACompleteDocument() { File src = sourceDirWith('doc', "# Hi\n\n```{groovy}\nout.println('x')\n```\n") - File target = new File(AbstractGmdTest.testOutputDir, 'out-doc') + File target = new File(testOutputDir, 'out-doc') target.deleteDir() assertFalse(target.exists(), "Could not clear test target ${target.absolutePath}") @@ -35,7 +35,7 @@ class GmdProcessorTest extends AbstractGmdTest { @Test void mdOutputIsStillPlainMarkdown() { File src = sourceDirWith('plain', "# Hi\n\n```{groovy echo=false}\nout.println('x')\n```\n") - File target = new File(AbstractGmdTest.testOutputDir, 'out-plain') + File target = new File(testOutputDir, 'out-plain') target.deleteDir() assertFalse(target.exists(), "Could not clear test target ${target.absolutePath}") @@ -49,7 +49,7 @@ class GmdProcessorTest extends AbstractGmdTest { @Test void commandLineEntryPointAcceptsSourceTargetAndOutputTypeArguments() { File src = sourceDirWith('command-line', "# Hi\n") - File target = new File(AbstractGmdTest.testOutputDir, 'out-command-line') + File target = new File(testOutputDir, 'out-command-line') target.deleteDir() assertFalse(target.exists(), "Could not clear test target ${target.absolutePath}") diff --git a/gmd-core/src/test/groovy/test/alipsa/groovy/gmd/GmdTest.groovy b/gmd-core/src/test/groovy/test/alipsa/groovy/gmd/GmdTest.groovy index 786bd0a..8338280 100644 --- a/gmd-core/src/test/groovy/test/alipsa/groovy/gmd/GmdTest.groovy +++ b/gmd-core/src/test/groovy/test/alipsa/groovy/gmd/GmdTest.groovy @@ -53,10 +53,10 @@ class GmdTest extends AbstractGmdTest { } def exception = assertThrows(GmdException) { - gmd.gmdToPdf('# Test', new File(AbstractGmdTest.testOutputDir, 'failed.pdf')) + gmd.gmdToPdf('# Test', new File(testOutputDir, 'failed.pdf')) } def processException = assertThrows(GmdException) { - gmd.processHtmlAndSaveAsPdf('

Test

', new File(AbstractGmdTest.testOutputDir, 'failed-process.pdf')) + gmd.processHtmlAndSaveAsPdf('

Test

', new File(testOutputDir, 'failed-process.pdf')) } assertInstanceOf(IllegalStateException, exception.cause) @@ -66,7 +66,7 @@ class GmdTest extends AbstractGmdTest { @Test void gmdToHtmlFile() { def gmd = new Gmd() - def htmlfFile = new File(AbstractGmdTest.testOutputDir, "gmdToHtmlFile.pdf") + def htmlfFile = new File(testOutputDir, "gmdToHtmlFile.pdf") if (htmlfFile.exists()) htmlfFile.delete() gmd.gmdToHtml(text, htmlfFile) assertEquals(gmd.gmdToHtmlDoc(text), htmlfFile.text) @@ -79,30 +79,30 @@ class GmdTest extends AbstractGmdTest { @Test void gmdToPdf() { def gmd = new Gmd() - def pdfFile = new File(AbstractGmdTest.testOutputDir, "gmdToPdf.pdf") + def pdfFile = new File(testOutputDir, "gmdToPdf.pdf") if (pdfFile.exists()) pdfFile.delete() def html = gmd.gmdToHtmlDoc(text) gmd.htmlToPdf(html, pdfFile) assertTrue(pdfFile.exists()) - def pdfFile2 = new File(AbstractGmdTest.testOutputDir, "gmdToPdf2.pdf") + def pdfFile2 = new File(testOutputDir, "gmdToPdf2.pdf") if (pdfFile2.exists()) pdfFile2.delete() gmd.gmdToPdf(text, pdfFile2) assertTrue(pdfFile.exists()) // Files might differ with a few bytes assertEquals((pdfFile.length()/15).intValue(), (pdfFile2.length()/15).intValue()) - def pdfFile3 = new File(AbstractGmdTest.testOutputDir, "gmdToPdf3.pdf") + def pdfFile3 = new File(testOutputDir, "gmdToPdf3.pdf") gmd.gmdToPdf(text, pdfFile3) assertEquals((pdfFile2.length()/15).intValue(), (pdfFile3.length()/15).intValue()) } @Test void toPdfRawIsUndecorated() { - File source = new File(AbstractGmdTest.testOutputDir, 'rawVsStyled.gmd') + File source = new File(testOutputDir, 'rawVsStyled.gmd') source.text = "# Test\n\n```groovy\ndef a = 1\n```\n" - File styled = new File(AbstractGmdTest.testOutputDir, 'styled.pdf') - File raw = new File(AbstractGmdTest.testOutputDir, 'raw.pdf') + File styled = new File(testOutputDir, 'styled.pdf') + File raw = new File(testOutputDir, 'raw.pdf') if (styled.exists()) styled.delete() if (raw.exists()) raw.delete() @@ -285,7 +285,7 @@ class GmdTest extends AbstractGmdTest { def text = '## Hello `=name`!' def gmd = new Gmd() def html = gmd.gmdToHtmlDoc(text, [name: "Per"]) - def pdfFile = new File(AbstractGmdTest.testOutputDir, "gmdToPdfWithParameter.pdf") + def pdfFile = new File(testOutputDir, "gmdToPdfWithParameter.pdf") if (pdfFile.exists()) pdfFile.delete() gmd.htmlToPdf(html, pdfFile) assertTrue(pdfFile.exists()) @@ -317,7 +317,7 @@ class GmdTest extends AbstractGmdTest { assertTrue(html.contains("

Some equations

\n

X = ∑(√2π + ∛3)

\n")) - def pdfFile = new File(AbstractGmdTest.testOutputDir, "testPdfWithSpecialCharacters.pdf") + def pdfFile = new File(testOutputDir, "testPdfWithSpecialCharacters.pdf") if (pdfFile.exists()) pdfFile.delete() gmd.htmlToPdf(html, pdfFile) assertTrue(pdfFile.exists()) @@ -569,7 +569,7 @@ out.println(chart) assertTrue(md.contains('# Employees')) assertTrue(md.contains("![''](data:image/svg+xml;base64,")) - def htmlFile = new File(AbstractGmdTest.testOutputDir, "testXChart.html") + def htmlFile = new File(testOutputDir, "testXChart.html") gmd.gmdToHtml(text, htmlFile) assertTrue(htmlFile.exists()) assertTrue(htmlFile.length() > 100, "No html content") @@ -581,7 +581,7 @@ out.println(chart) void testMathmlToPDF() { def html = IOUtils.toString(this.class.getResource('/mathml.html'), StandardCharsets.UTF_8) Gmd gmd = new Gmd() - def pdfFile = new File(AbstractGmdTest.testOutputDir, "testMathmlToPDF.pdf") + def pdfFile = new File(testOutputDir, "testMathmlToPDF.pdf") gmd.htmlToPdf(html, pdfFile) assertTrue(pdfFile.exists()) println("Wrote $pdfFile.absolutePath") @@ -591,12 +591,12 @@ out.println(chart) void gmdCommandLineTest() { String file = getClass().getResource("/test.gmd").getFile() - File htmlFile = new File(AbstractGmdTest.testOutputDir,"gmdCommandLineTest.html") + File htmlFile = new File(testOutputDir,"gmdCommandLineTest.html") Gmd.main("toHtml", file, htmlFile.absolutePath) assertTrue(htmlFile.exists()) assertTrue(htmlFile.length() > 100, "No html content") - File pdfFile = new File(AbstractGmdTest.testOutputDir, "gmdCommandLineTest.pdf") + File pdfFile = new File(testOutputDir, "gmdCommandLineTest.pdf") Gmd.main("toPdf", file, pdfFile.absolutePath) assertTrue(pdfFile.exists()) assertTrue(pdfFile.length() > 100, "No pdf content") diff --git a/gmd-maven-plugin/src/main/java/se/alipsa/gmd/maven/GmdMavenPlugin.java b/gmd-maven-plugin/src/main/java/se/alipsa/gmd/maven/GmdMavenPlugin.java index 8d516b5..bdf66d9 100644 --- a/gmd-maven-plugin/src/main/java/se/alipsa/gmd/maven/GmdMavenPlugin.java +++ b/gmd-maven-plugin/src/main/java/se/alipsa/gmd/maven/GmdMavenPlugin.java @@ -178,7 +178,7 @@ public void execute() throws MojoExecutionException, MojoFailureException { command.add(getJavaExecutable()); command.add("-cp"); command.add(classpath.toString()); - command.add(se.alipsa.gmd.core.GmdProcessor.class.getName()); + command.add(getGmdProcessorClassName()); command.add(srcDir.getCanonicalPath()); command.add(outputDirectory.getCanonicalPath()); command.add(normalizedOutputType); @@ -262,4 +262,13 @@ private String getJavaExecutable() { String javaHome = System.getProperty("java.home"); return javaHome + File.separator + "bin" + File.separator + "java"; } + + /** + * Returns the entry point used by the forked GMD process. + * + * @return the fully qualified GmdProcessor class name + */ + protected String getGmdProcessorClassName() { + return se.alipsa.gmd.core.GmdProcessor.class.getName(); + } } diff --git a/gmd-maven-plugin/src/test/java/test/alipsa/gmd/maven/GmdMavenPluginTest.java b/gmd-maven-plugin/src/test/java/test/alipsa/gmd/maven/GmdMavenPluginTest.java index 01ca834..799d5ad 100644 --- a/gmd-maven-plugin/src/test/java/test/alipsa/gmd/maven/GmdMavenPluginTest.java +++ b/gmd-maven-plugin/src/test/java/test/alipsa/gmd/maven/GmdMavenPluginTest.java @@ -3,6 +3,8 @@ import org.apache.maven.api.plugin.testing.InjectMojo; import org.apache.maven.api.plugin.testing.MojoTest; import org.apache.maven.execution.MavenSession; +import org.apache.maven.plugin.MojoFailureException; +import org.apache.maven.project.MavenProject; import org.junit.jupiter.api.Test; import se.alipsa.gmd.maven.GmdMavenPlugin; @@ -28,6 +30,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.when; @@ -98,16 +101,39 @@ public void testGmdMavenPluginWithResolvedDependencies(GmdMavenPlugin plugin) th deleteDirectory(outputDirectory); assertFalse(outputDirectory.exists(), "Could not clear resolved-dependency test output"); - try { - plugin.execute(); + plugin.execute(); - File testHtml = new File(plugin.getTargetDir(), "test.html"); - assertTrue(testHtml.isFile(), "The resolved-dependency fork did not write test.html"); - assertTrue(Files.readString(testHtml.toPath()).contains("

Greetings

")); - Mockito.verify(repositorySystem).resolveDependencies(any(RepositorySystemSession.class), any(DependencyRequest.class)); - } finally { - deleteDirectory(outputDirectory); - } + File testHtml = new File(plugin.getTargetDir(), "test.html"); + assertTrue(testHtml.isFile(), "The resolved-dependency fork did not write test.html"); + assertTrue(Files.readString(testHtml.toPath()).contains("

Greetings

")); + Mockito.verify(repositorySystem).resolveDependencies(any(RepositorySystemSession.class), any(DependencyRequest.class)); + } + + @Test + public void testGmdMavenPluginReportsForkFailure() throws Exception { + FailingGmdMavenPlugin plugin = new FailingGmdMavenPlugin(); + RepositorySystem repositorySystem = Mockito.mock(RepositorySystem.class); + RepositorySystemSession repositorySession = Mockito.mock(RepositorySystemSession.class); + LocalRepositoryManager localRepositoryManager = Mockito.mock(LocalRepositoryManager.class); + MavenSession session = Mockito.mock(MavenSession.class); + MavenProject project = Mockito.mock(MavenProject.class); + DependencyResult dependencyResult = new DependencyResult(new DependencyRequest()); + + when(session.getRepositorySession()).thenReturn(repositorySession); + when(repositorySession.getLocalRepositoryManager()).thenReturn(localRepositoryManager); + when(repositorySystem.resolveDependencies(any(RepositorySystemSession.class), any(DependencyRequest.class))) + .thenReturn(dependencyResult); + when(project.getBasedir()).thenReturn(new File("src/test/projects/manual").getAbsoluteFile()); + + setField(plugin, "repositorySystem", repositorySystem); + setField(plugin, "session", session); + setField(plugin, "project", project); + setField(plugin, "sourceDir", "src/test/gmd"); + setField(plugin, "targetDir", "target/gmd-bad-main"); + setField(plugin, "outputType", "html"); + + MojoFailureException exception = assertThrows(MojoFailureException.class, plugin::execute); + assertTrue(exception.getMessage().contains("GmdProcessor exited with code")); } private static void deleteDirectory(File directory) throws IOException { @@ -130,9 +156,25 @@ private static void deleteDirectory(File directory) throws IOException { } private static void setField(Object target, String name, Object value) throws Exception { - Field field = target.getClass().getDeclaredField(name); - field.setAccessible(true); - field.set(target, value); + Class type = target.getClass(); + while (type != null) { + try { + Field field = type.getDeclaredField(name); + field.setAccessible(true); + field.set(target, value); + return; + } catch (NoSuchFieldException e) { + type = type.getSuperclass(); + } + } + throw new NoSuchFieldException(name); + } + + private static final class FailingGmdMavenPlugin extends GmdMavenPlugin { + @Override + protected String getGmdProcessorClassName() { + return "se.alipsa.gmd.core.MissingGmdProcessor"; + } } } From 94a22aa8ff3650362d1da6363783f4ad305dc0fe Mon Sep 17 00:00:00 2001 From: per Date: Sun, 9 Aug 2026 23:40:15 +0200 Subject: [PATCH 16/16] integrate generated Gradle version default --- .../alipsa/gmd/gradle/GmdGradlePlugin.groovy | 36 ++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/gmd-gradle-plugin/src/main/groovy/se/alipsa/gmd/gradle/GmdGradlePlugin.groovy b/gmd-gradle-plugin/src/main/groovy/se/alipsa/gmd/gradle/GmdGradlePlugin.groovy index 8f50b67..e849861 100644 --- a/gmd-gradle-plugin/src/main/groovy/se/alipsa/gmd/gradle/GmdGradlePlugin.groovy +++ b/gmd-gradle-plugin/src/main/groovy/se/alipsa/gmd/gradle/GmdGradlePlugin.groovy @@ -10,6 +10,10 @@ import org.gradle.api.artifacts.repositories.ArtifactRepository import org.gradle.api.artifacts.repositories.MavenArtifactRepository import org.gradle.api.tasks.TaskProvider +import java.io.IOException +import java.io.InputStream +import java.util.Properties + @CompileStatic class GmdGradlePlugin implements Plugin { @@ -21,7 +25,7 @@ class GmdGradlePlugin implements Plugin { extension.outputType.convention('md') extension.groovyVersion.convention('5.0.8') extension.log4jVersion.convention('2.26.1') - extension.gmdVersion.convention('3.1.0') + extension.gmdVersion.convention(project.providers.provider { defaultGmdVersion() }) extension.ivyVersion.convention('2.6.0') extension.runTaskBefore.convention('test') @@ -59,6 +63,36 @@ class GmdGradlePlugin implements Plugin { } } + private static String defaultGmdVersion() { + InputStream stream = GmdGradlePlugin.class.getResourceAsStream('/gmd-version.properties') + if (stream == null) { + throw new IllegalStateException( + 'GMD core version metadata is missing from the Gradle plugin; set gmdPlugin.gmdVersion explicitly' + ) + } + try { + Properties properties = new Properties() + properties.load(stream) + String version = properties.getProperty('gmd.version') + String normalizedVersion = version == null ? null : version.trim() + if (normalizedVersion == null || normalizedVersion.isEmpty() + || normalizedVersion.contains('$') || normalizedVersion.contains('{')) { + throw new IllegalStateException( + 'GMD core version metadata is invalid; set gmdPlugin.gmdVersion explicitly' + ) + } + return normalizedVersion + } catch (IOException e) { + throw new IllegalStateException('Could not read GMD core version metadata', e) + } finally { + try { + stream.close() + } catch (IOException ignored) { + // Ignore cleanup failures while resolving the version resource. + } + } + } + static Configuration addDependencies(Project project, String groovyVersion, String log4jVersion, String gmdVersion, String ivyVersion) {