Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
25 changes: 15 additions & 10 deletions build-logic/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,28 @@ plugins {
id 'groovy-gradle-plugin'
}

file('../gradle.properties').withInputStream {
Properties props = new Properties()
props.load(it)
project.ext.gradleProperties = props
file('../gradle.properties').withInputStream { is ->
extensions.extraProperties.set(
'gradleProperties',
new Properties().tap { load(is) }
)
}

allprojects {
for (String key : gradleProperties.stringPropertyNames()) {
ext.set(key, gradleProperties.getProperty(key))
allprojects { project ->
gradleProperties.stringPropertyNames().each { key ->
project.extensions.extraProperties.set(
key,
gradleProperties.getProperty(key)
)
}
}

dependencies {
implementation platform("org.apache.grails:grails-bom:${gradleProperties.grailsVersion}")
implementation 'org.apache.grails:grails-gradle-plugins'
implementation "com.adarshr:gradle-test-logger-plugin:${gradleProperties.testLoggerVersion}"
implementation "org.asciidoctor.jvm.convert:org.asciidoctor.jvm.convert.gradle.plugin:${gradleProperties.asciidoctorVersion}"
implementation 'cloud.wondrify:asset-pipeline-gradle'
implementation "com.adarshr:gradle-test-logger-plugin:${gradleProperties.testLoggerVersion}"
implementation 'org.apache.grails:grails-gradle-plugins'
implementation 'org.apache.grails.gradle:grails-publish'
implementation "org.asciidoctor.jvm.convert:org.asciidoctor.jvm.convert.gradle.plugin:${gradleProperties.asciidoctorVersion}"
}

4 changes: 2 additions & 2 deletions build-logic/settings.gradle
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import org.gradle.api.initialization.resolve.RepositoriesMode

rootProject.name = "build-logic"
rootProject.name = 'build-logic'

dependencyResolutionManagement {
repositoriesMode = RepositoriesMode.FAIL_ON_PROJECT_REPOS
Expand All @@ -10,4 +10,4 @@ dependencyResolutionManagement {
}
maven { url = 'https://repo.grails.org/grails/restricted' }
}
}
}
12 changes: 12 additions & 0 deletions build-logic/src/main/groovy/config.app-debug.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
pluginManager.withPlugin('org.springframework.boot') {
tasks.named('bootRun', JavaExec) {
doFirst {
if (project.hasProperty('debugWait')) {
jvmArgs('-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=5005')
}
if (project.hasProperty('debug')) {
jvmArgs('-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005')
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,12 @@
import org.gradle.api.plugins.quality.CheckstyleExtension
import org.gradle.api.plugins.quality.CodeNarcExtension

plugins {
id 'checkstyle'
id 'codenarc'
}

// Resolved relative to the root project directory, which is the parent of build-logic/.
def codeStyleConfigDir = rootProject.file('build-logic/config')
def checkstyleConfigDir = new File(codeStyleConfigDir, 'checkstyle')
def codenarcConfigDir = new File(codeStyleConfigDir, 'codenarc')
def codeStyleConfigDir = rootProject.layout.settingsDirectory.dir('build-logic/config')
def checkstyleConfigDir = codeStyleConfigDir.dir('checkstyle')
def codenarcConfigDir = codeStyleConfigDir.dir('codenarc')

extensions.configure(CheckstyleExtension) {
it.toolVersion = checkstyleVersion
Expand All @@ -26,7 +23,7 @@ tasks.withType(Checkstyle).configureEach {

extensions.configure(CodeNarcExtension) {
it.toolVersion = codenarcVersion
it.configFile = new File(codenarcConfigDir, 'codenarc.groovy')
it.configFile = codenarcConfigDir.file('codenarc.groovy').getAsFile()
it.maxPriority1Violations = 0
it.maxPriority2Violations = 0
it.maxPriority3Violations = 0
Expand All @@ -40,6 +37,6 @@ tasks.withType(CodeNarc).configureEach {
tasks.register('codeStyle') {
group = 'verification'
description = 'Runs all code style checks (Checkstyle + CodeNarc).'
dependsOn tasks.withType(Checkstyle)
dependsOn tasks.withType(CodeNarc)
dependsOn(tasks.withType(Checkstyle))
dependsOn(tasks.withType(CodeNarc))
}
65 changes: 65 additions & 0 deletions build-logic/src/main/groovy/config.compile.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import java.nio.charset.StandardCharsets

plugins {
id 'groovy'
}

tasks.withType(JavaCompile).configureEach {
options.with {
compilerArgs.add('-parameters')
encoding = StandardCharsets.UTF_8.name()
fork = true
incremental = true
release.set(resolveSdkmanJavaMajor(project))
}
options.forkOptions.with {
jvmArgs.add('-Xmx1g')
memoryMaximumSize = '1g'
}
}

tasks.withType(GroovyCompile).configureEach {
options.with {
compilerArgs.add('-parameters')
encoding = StandardCharsets.UTF_8.name()
fork = true
incremental = true
}
groovyOptions.with {
encoding = StandardCharsets.UTF_8.name()
optimizationOptions.indy = false
parameters = true
}
groovyOptions.forkOptions.with {
memoryMaximumSize = '1g'
jvmArgs.add('-Xmx1g')
}
}

private static Provider<Integer> resolveSdkmanJavaMajor(Project project) {
project.providers.provider {
def sdkmanrc = project.rootProject.file('.sdkmanrc')
if (!sdkmanrc.exists()) {
throw new GradleException('Missing .sdkmanrc in root project')
}

def props = new Properties()
sdkmanrc.withInputStream { props.load(it) }

def raw = props.getProperty('java')?.trim()
if (!raw) {
throw new GradleException('Missing java version in root project .sdkmanrc')
}

def major = raw.tokenize('.').first()
if (!(major ==~ /\d+/)) {
throw new GradleException(
"Invalid java version '$raw' in root project .sdkmanrc (major '$major' is not an integer)"
)
}

return major.toInteger()

} as Provider<Integer>
}

Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import org.gradle.testing.jacoco.plugins.JacocoPluginExtension

plugins {
id 'base'
id 'jacoco'
Expand All @@ -10,32 +8,39 @@ extensions.configure(JacocoPluginExtension) {
}

// Configuration for declaring which projects contribute coverage data.
configurations {
coverageDataProjects {
canBeConsumed = false
canBeResolved = true
}
def coverageDataProjects = configurations.register('coverageDataProjects') {
canBeConsumed = false
canBeResolved = true
}

// Lazily collect source directories and class files from all coverageDataProjects dependencies.
def covProjectList = configurations.named('coverageDataProjects').map { config ->
config.dependencies.withType(ProjectDependency).collect { project.project(it.path) }
def covProjectList = coverageDataProjects.map {
it.dependencies.withType(ProjectDependency).collect {
project.project(it.path)
}
}

def allSourceDirs = covProjectList.map { projects ->
projects.findAll { it.plugins.hasPlugin('java') }
.collectMany { it.sourceSets.main.allSource.sourceDirectories.files }
def allSourceDirs = covProjectList.map {
it.findAll { it.plugins.hasPlugin('java') }
.collectMany {
it.extensions.getByType(SourceSetContainer).named('main').get()
.allSource.sourceDirectories.files
}
}

def allClassDirs = covProjectList.map { projects ->
projects.findAll { it.plugins.hasPlugin('java') }
.collectMany { it.sourceSets.main.output.files }
def allClassDirs = covProjectList.map {
it.findAll { it.plugins.hasPlugin('java') }
.collectMany {
it.extensions.getByType(SourceSetContainer).named('main').get()
.output.files
}
}

def allExecFiles = covProjectList.map { projects ->
projects.collectMany { prj ->
prj.layout.buildDirectory.dir('jacoco').get().asFile
.listFiles({ File f -> f.name.endsWith('.exec') } as FileFilter)?.toList() ?: []
def allExecFiles = covProjectList.map {
it.collectMany {
it.fileTree(it.layout.buildDirectory.dir('jacoco')) {
include('**/*.exec')
}.files
}
}

Expand All @@ -44,12 +49,12 @@ def allExecFiles = covProjectList.map { projects ->
// Task dependencies on all Test tasks (test, integrationTest, etc.) in the declared
// projects are derived automatically — no hard-coded project paths needed.
tasks.register('jacocoAggregatedReport', JacocoReport) {
group = 'verification'
description = 'Generates aggregated JaCoCo coverage report across all subprojects.'
group = 'verification'

classDirectories.from(allClassDirs)
executionData.from(allExecFiles)
sourceDirectories.from(allSourceDirs)
classDirectories.from(allClassDirs)

reports {
xml.required = true
Expand All @@ -61,19 +66,19 @@ tasks.register('jacocoAggregatedReport', JacocoReport) {
// After evaluation, wire dependsOn for every Test task in every coverage project.
// This ensures all .exec files exist before the aggregated report collects them.
afterEvaluate {
def projects = configurations.coverageDataProjects.dependencies
def projects = coverageDataProjects.get().dependencies
.withType(ProjectDependency)
.collect { project.project(it.path) }

tasks.named('jacocoAggregatedReport') {
projects.each { prj ->
prj.tasks.withType(Test).each { testTask ->
dependsOn testTask
tasks.named('jacocoAggregatedReport') {reportTask ->
projects.each {
it.tasks.withType(Test).configureEach { testTask ->
reportTask.dependsOn(testTask)
}
}
}
}

tasks.named('check') {
dependsOn tasks.named('jacocoAggregatedReport')
dependsOn('jacocoAggregatedReport')
}
74 changes: 74 additions & 0 deletions build-logic/src/main/groovy/config.docs.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
def docProject = provider {
project(":${project.name - 'root'}docs")
}
def pluginProject = provider {
project(":${project.name - '-root'}")
}

tasks.register('cleanDocs', Delete) {
description = 'Deletes the documentation output'
group = 'documentation'

delete(rootProject.layout.projectDirectory.dir('build/docs'))
}

tasks.register('aggregateGroovyApiDoc', Groovydoc) {
description = 'Generates Groovy API Documentation for the plugin project under build/docs/gapi'
group = 'documentation'

def upstream = pluginProject.flatMap {
it.tasks.named('groovydoc', Groovydoc)
} as Provider<Groovydoc>

dependsOn(tasks.named('cleanDocs'))
dependsOn(upstream)

access = GroovydocAccess.PROTECTED
includeAuthor = false
includeMainForScripts = true
processScripts = true
exclude('**/Application.groovy')


source = { upstream.get().source }
destinationDir = rootProject.layout.buildDirectory.dir('docs/gapi').get().asFile
classpath = files({ upstream.get().classpath })
groovyClasspath = files({ upstream.get().groovyClasspath })
}

tasks.register('docs') {
description = 'Generates the documentation'
group = 'documentation'

dependsOn(
'aggregateGroovyApiDoc',
docProject.get().tasks.named('asciidoctor')
)
finalizedBy(
'copyAsciiDoctorDocs',
'ghPagesRootIndexPage'
)
}

tasks.register('copyAsciiDoctorDocs', Copy) {
group = 'documentation'

from(docProject.flatMap { it.layout.buildDirectory })
into(rootProject.layout.buildDirectory)
include('docs/**')
includeEmptyDirs = false

dependsOn('docs')
}

tasks.register('ghPagesRootIndexPage', Copy) {
description = 'Provides a root index page for historical versions that are currently managed manually'
group = 'documentation'

from(docProject.map { it.layout.projectDirectory.file('src/docs/index.tmpl') })
into(rootProject.layout.buildDirectory.dir('docs'))
rename('index.tmpl', 'ghpages.html')

dependsOn('docs')
}

Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
plugins {
id 'config.grails-assets'
id 'config.app-debug'
id 'org.apache.grails.gradle.grails-web'
id 'org.apache.grails.gradle.grails-gsp'
id 'org.grails.plugins.servertiming.assets'
id 'org.grails.plugins.servertiming.run'
}
}
Original file line number Diff line number Diff line change
@@ -1,20 +1,22 @@
import asset.pipeline.gradle.AssetPipelineExtension

plugins {
id 'cloud.wondrify.asset-pipeline'
}

dependencies {
assetDevelopmentRuntime 'org.webjars.npm:bootstrap'
assetDevelopmentRuntime 'org.webjars.npm:bootstrap-icons'
assetDevelopmentRuntime 'org.webjars.npm:jquery'
add('assetDevelopmentRuntime', 'org.webjars.npm:bootstrap')
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was this because intellij didn't detect the scope? With the asset plugin defined in the same file, this seems unnecessary

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that was the reason. I can revert it if you like it better the DSL way.

Copy link
Copy Markdown
Contributor

@jdaugherty jdaugherty Feb 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm just worried about consistency. It's a shame that IntelliJ seems to always half implements this stuff. What are other people's thoughts? @jamesfredley @sbglasius

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm going to go ahead and merge this, if @jamesfredley or @sbglasius has any feedback on this we can address later.

add('assetDevelopmentRuntime', 'org.webjars.npm:bootstrap-icons')
add('assetDevelopmentRuntime', 'org.webjars.npm:jquery')
}

assets {
excludes = [
extensions.configure(AssetPipelineExtension) {
it.excludes = [
'webjars/jquery/**',
'webjars/bootstrap/**',
'webjars/bootstrap-icons/**'
]
includes = [
it.includes = [
'webjars/jquery/*/dist/jquery.js',
'webjars/bootstrap/*/dist/js/bootstrap.bundle.js',
'webjars/bootstrap/*/dist/css/bootstrap.css',
Expand Down
Loading
Loading