Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions gmd-gradle-plugin/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@ gradlePlugin {
displayName = "Gmd Plugin"
description = project.description
tags.set(["markdown", "Groovy", "pdf", "html", "gmd"])
compatibility(it) {
features {
configurationCache = true
}
}
}
}
}
Expand Down Expand Up @@ -58,9 +63,9 @@ test {

project.afterEvaluate {
tasks.named('signPluginMavenPublication') {
enabled = project.properties['signing.keyId'] != null
enabled = project.findProperty('signing.keyId') != null
}
tasks.named("signSimplePluginPluginMarkerMavenPublication") {
enabled = project.properties['signing.keyId'] != null
enabled = project.findProperty('signing.keyId') != null
}
}
5 changes: 5 additions & 0 deletions gmd-gradle-plugin/release.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# GMD Gradle Plugin release history

## 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
- declare configuration-cache support in the Plugin Portal metadata and disable build caching for potentially non-deterministic PDF output

## v3.1.0, 2026-08-02
- resolve all output types without JavaFX dependencies
- declare `processGmd` inputs and outputs and remove stale generated files
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,95 +9,47 @@ import org.gradle.api.artifacts.Dependency
import org.gradle.api.artifacts.repositories.ArtifactRepository
import org.gradle.api.artifacts.repositories.MavenArtifactRepository
import org.gradle.api.tasks.TaskProvider
import org.gradle.process.ExecOperations

import javax.inject.Inject

@CompileStatic
class GmdGradlePlugin implements Plugin<Project> {

ExecOperations execOperations

@Inject
GmdGradlePlugin(ExecOperations execOperations) {
this.execOperations = execOperations
}

@Override
void apply(Project project) {
def extension = project.extensions.create('gmdPlugin', GmdGradlePluginParams)
GmdGradlePluginParams extension = project.extensions.create('gmdPlugin', GmdGradlePluginParams)
extension.sourceDir.convention('src/main/gmd')
extension.targetDir.convention('build/gmd')
extension.outputType.convention('md')
extension.groovyVersion.convention('5.0.8')
extension.log4jVersion.convention('2.26.1')
extension.gmdVersion.convention('3.1.0')
extension.ivyVersion.convention('2.6.0')
extension.runTaskBefore.convention('test')

TaskProvider<Task> processGmdTask = project.tasks.register('processGmd') {
it.inputs.dir(project.provider {
project.file(extension.sourceDir.getOrElse('src/main/gmd'))
})
it.outputs.dir(project.provider {
project.file(extension.targetDir.getOrElse('build/gmd'))
})
it.inputs.property('outputType', project.provider {
extension.outputType.getOrElse('md')
})
it.doLast {
File sourceDir= project.file(extension.sourceDir.getOrElse("src/main/gmd"))
File targetDir= project.file(extension.targetDir.getOrElse("build/gmd"))
String outputType= extension.outputType.getOrElse('md').trim().toLowerCase(Locale.ROOT)
String groovyVersion = extension.groovyVersion.getOrElse('5.0.8')
String log4jVersion = extension.log4jVersion.getOrElse('2.26.1')
String gmdVersion = extension.gmdVersion.getOrElse('3.1.0')
String ivyVersion = extension.ivyVersion.getOrElse('2.6.0')
if (!['md', 'html', 'pdf'].contains(outputType)) {
throw new IllegalArgumentException("Unknown output type ${outputType}, expected either md, html or pdf")
}
TaskProvider<ProcessGmdTask> processGmdTask = project.tasks.register('processGmd', ProcessGmdTask)

if (!sourceDir.exists()) {
project.logger.warn("Source directory ${sourceDir.canonicalPath} does not exist, nothing to do")
return
}
if (!targetDir.exists()) {
if (!targetDir.mkdirs() && !targetDir.isDirectory()) {
throw new IllegalArgumentException("Could not create target directory ${targetDir.canonicalPath}")
}
} else if (!targetDir.isDirectory()) {
throw new IllegalArgumentException("Target path ${targetDir.canonicalPath} is a file, not a directory")
}
project.logger.info("Processing GMD in ${sourceDir} -> ${targetDir}, type: ${outputType}")
cleanStaleGeneratedFiles(project, sourceDir, targetDir, outputType)
project.afterEvaluate {
String sourceDir = extension.sourceDir.get()
String targetDir = extension.targetDir.get()
String outputType = extension.outputType.get()
Configuration configuration = addDependencies(project,
extension.groovyVersion.get(),
extension.log4jVersion.get(),
extension.gmdVersion.get(),
extension.ivyVersion.get()
)

List<ArtifactRepository> addedRepositories = []
Configuration configuration = addDependencies(project, addedRepositories,
groovyVersion, log4jVersion, gmdVersion, ivyVersion
)
// a configuration is a FileCollection, no need to call resolve()
def result = execOperations.javaexec( a -> {
a.classpath = configuration
a.mainClass.set('se.alipsa.gmd.core.GmdProcessor')
a.args = [
sourceDir.canonicalPath,
targetDir.canonicalPath,
outputType
]
})
// cleanup the added repositories
addedRepositories.each { repo ->
project.repositories.remove(repo)
}
result.assertNormalExitValue()
File[] sourceFiles = sourceDir.listFiles()
if (sourceFiles != null && sourceFiles.size() > 0) {
if (targetDir.exists()) {
project.logger.quiet("Gmd files processed and written to ${targetDir.canonicalPath}")
} else {
project.logger.warn("${targetDir.canonicalPath} should exists but does not, something is probably wrong")
}
} else {
project.logger.quiet("No gmd files found in ${sourceDir.canonicalPath}, nothing to do")
}
processGmdTask.configure { ProcessGmdTask task ->
// Resolve all project values during configuration. The task action only
// uses task properties and injected services, which enables the
// configuration cache and parallel task execution.
task.sourceDir.set(project.file(sourceDir))
task.targetDir.set(project.file(targetDir))
task.outputType.set(outputType)
task.classpath.from(configuration)
}
}
project.afterEvaluate {

try {
def runTaskBefore = extension.runTaskBefore.getOrElse('test')
TaskProvider<Task> buildTask = it.tasks.named(runTaskBefore)
TaskProvider<Task> buildTask = project.tasks.named(extension.runTaskBefore.get())
buildTask.configure { Task task ->
task.dependsOn(processGmdTask)
}
Expand All @@ -107,13 +59,12 @@ class GmdGradlePlugin implements Plugin<Project> {
}
}

static Configuration addDependencies(Project project, List<ArtifactRepository> addedRepositories,
static Configuration addDependencies(Project project,
String groovyVersion, String log4jVersion, String gmdVersion,
String ivyVersion) {
def mavenCentral = project.repositories.mavenCentral()
MavenArtifactRepository mavenCentral = project.repositories.mavenCentral()
if (!hasRepository(project, mavenCentral)) {
project.repositories.add(mavenCentral)
addedRepositories.add(mavenCentral)
}

List<Dependency> dependencies = [
Expand All @@ -128,29 +79,6 @@ class GmdGradlePlugin implements Plugin<Project> {
return project.configurations.detachedConfiguration(dependencies.toArray(new Dependency[0]))
}

private static void cleanStaleGeneratedFiles(Project project, File sourceDir, File targetDir, String outputType) {
Set<String> expected = [] as Set
File[] sources = sourceDir.listFiles({ File file -> file.isFile() && file.name.endsWith('.gmd') } as FileFilter)
if (sources != null) {
sources.each { file ->
String base = file.name.substring(0, file.name.length() - 4)
expected.add("${base}.${outputType}".toString())
}
}
File[] generated = targetDir.listFiles({ File file ->
file.isFile() && (file.name.endsWith('.md') || file.name.endsWith('.html') || file.name.endsWith('.pdf'))
} as FileFilter)
if (generated != null) {
generated.findAll { !expected.contains(it.name) }.each { File file ->
if (file.delete()) {
project.logger.lifecycle("Removed stale generated GMD output ${file.absolutePath}")
} else {
project.logger.warn("Could not remove stale generated GMD output ${file.absolutePath}")
}
}
}
}

static boolean hasRepository(Project project, MavenArtifactRepository repo) {
return project.repositories.find {
it instanceof MavenArtifactRepository && it.url == repo.url
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package se.alipsa.gmd.gradle

import groovy.transform.CompileStatic
import org.gradle.api.DefaultTask
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputDirectory
import org.gradle.api.tasks.Optional
import org.gradle.api.tasks.OutputDirectory
import org.gradle.api.tasks.PathSensitive
import org.gradle.api.tasks.PathSensitivity
import org.gradle.api.tasks.TaskAction
import org.gradle.process.ExecOperations
import org.gradle.process.JavaExecSpec
import org.gradle.work.DisableCachingByDefault

import javax.inject.Inject

@CompileStatic
@DisableCachingByDefault(because = 'GMD processing may generate non-deterministic PDF metadata')
abstract class ProcessGmdTask extends DefaultTask {

private final ExecOperations execOperations

@Inject
ProcessGmdTask(ExecOperations execOperations) {
this.execOperations = execOperations
}

@InputDirectory
@Optional
@PathSensitive(PathSensitivity.RELATIVE)
abstract DirectoryProperty getSourceDir()

@OutputDirectory
abstract DirectoryProperty getTargetDir()

@Input
abstract org.gradle.api.provider.Property<String> getOutputType()

@org.gradle.api.tasks.Classpath
abstract ConfigurableFileCollection getClasspath()

@TaskAction
void process() {
File source = getSourceDir().get().asFile
File target = getTargetDir().get().asFile
String output = getOutputType().get().trim().toLowerCase(Locale.ROOT)
if (!['md', 'html', 'pdf'].contains(output)) {
throw new IllegalArgumentException("Unknown output type ${output}, expected either md, html or pdf")
}

if (!source.exists()) {
logger.warn("Source directory ${source.canonicalPath} does not exist, nothing to do")
return
}
if (!target.exists()) {
if (!target.mkdirs() && !target.isDirectory()) {
throw new IllegalArgumentException("Could not create target directory ${target.canonicalPath}")
}
} else if (!target.isDirectory()) {
throw new IllegalArgumentException("Target path ${target.canonicalPath} is a file, not a directory")
}
logger.info("Processing GMD in ${source} -> ${target}, type: ${output}")
cleanStaleGeneratedFiles(source, target, output)

def result = execOperations.javaexec { JavaExecSpec spec ->
spec.classpath = getClasspath()
spec.mainClass.set('se.alipsa.gmd.core.GmdProcessor')
spec.args = [source.canonicalPath, target.canonicalPath, output]
}
result.assertNormalExitValue()
File[] sourceFiles = source.listFiles()
if (sourceFiles != null && sourceFiles.size() > 0) {
if (target.exists()) {
logger.quiet("Gmd files processed and written to ${target.canonicalPath}")
} else {
logger.warn("${target.canonicalPath} should exists but does not, something is probably wrong")
}
} else {
logger.quiet("No gmd files found in ${source.canonicalPath}, nothing to do")
}
}

private void cleanStaleGeneratedFiles(File sourceDir, File targetDir, String outputType) {
Set<String> expected = [] as Set
File[] sources = sourceDir.listFiles({ File file -> file.isFile() && file.name.endsWith('.gmd') } as FileFilter)
if (sources != null) {
sources.each { file ->
String base = file.name.substring(0, file.name.length() - 4)
expected.add("${base}.${outputType}".toString())
}
}
File[] generated = targetDir.listFiles({ File file ->
file.isFile() && (file.name.endsWith('.md') || file.name.endsWith('.html') || file.name.endsWith('.pdf'))
} as FileFilter)
if (generated != null) {
generated.findAll { !expected.contains(it.name) }.each { File file ->
if (file.delete()) {
logger.lifecycle("Removed stale generated GMD output ${file.absolutePath}")
} else {
logger.warn("Could not remove stale generated GMD output ${file.absolutePath}")
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -80,12 +80,20 @@ class GmdGradlePluginTest {

def result = GradleRunner.create()
.withProjectDir(testProjectDir)
.withArguments('processGmd')
.withArguments('processGmd', '--configuration-cache', '--parallel')
.withPluginClasspath()
.forwardOutput()
.build()
assert result.task(":processGmd").outcome == SUCCESS

def cachedResult = GradleRunner.create()
.withProjectDir(testProjectDir)
.withArguments('processGmd', '--configuration-cache', '--parallel')
.withPluginClasspath()
.forwardOutput()
.build()
assert cachedResult.task(":processGmd").outcome in [SUCCESS, org.gradle.testkit.runner.TaskOutcome.UP_TO_DATE]

// the directory differs on a mac even though they point to the same place so cannot include
def expected = "Gmd files processed and written to $targetDir.canonicalPath".toString()
Assertions.assertTrue(result.output.contains(expected), "expected \n$expected, but output was ${result.output}")
Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@

<properties>
<!-- CI-friendly version: change version in ONE place only -->
<revision>3.1.0</revision>
<revision>3.1.1-SNAPSHOT</revision>

<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.release>21</maven.compiler.release>
Expand Down
Loading