From 474ddf8d461b056755523d65c1bd799ac3b9eecb Mon Sep 17 00:00:00 2001 From: Daniel Sun Date: Tue, 19 May 2026 01:04:03 +0900 Subject: [PATCH] GROOVY-12019: Enable gradle configuration cache --- build-logic/build.gradle | 3 +- .../org.apache.groovy-asciidoctor.gradle | 7 ++ .../main/groovy/org.apache.groovy-base.gradle | 47 ++++++-- .../main/groovy/org.apache.groovy-core.gradle | 41 +++---- .../org.apache.groovy-distribution.gradle | 38 ++++--- .../org.apache.groovy-documented.gradle | 8 +- .../groovy/org.apache.groovy-library.gradle | 22 ++-- ...rg.apache.groovy-publish-validation.gradle | 8 +- ...org.apache.groovy-published-library.gradle | 13 ++- .../groovy/org.apache.groovy-tested.gradle | 48 ++++----- .../groovy/gradle/GroovydocAntPlugin.groovy | 102 +++++++++++++----- .../apache/groovy/gradle/JarJarTask.groovy | 78 ++++++++++---- .../gradle/PerformanceTestsExtension.groovy | 58 ++++++++-- .../groovy/gradle/SharedConfiguration.groovy | 50 +++++++-- build.gradle | 8 +- gradle.properties | 8 ++ gradle/verification-metadata.xml | 14 ++- subprojects/groovy-ant/build.gradle | 9 +- subprojects/groovy-binary/build.gradle | 27 ++--- subprojects/groovy-groovydoc/build.gradle | 2 +- 20 files changed, 420 insertions(+), 171 deletions(-) diff --git a/build-logic/build.gradle b/build-logic/build.gradle index b80d74e86cc..109d26d6d37 100644 --- a/build-logic/build.gradle +++ b/build-logic/build.gradle @@ -27,12 +27,13 @@ repositories { } dependencies { + implementation 'biz.aQute.bnd:biz.aQute.bndlib:6.4.1' implementation 'org.asciidoctor:asciidoctor-gradle-jvm:4.0.5' implementation 'org.asciidoctor:asciidoctor-gradle-jvm-pdf:4.0.5' implementation 'org.jfrog.buildinfo:build-info-extractor-gradle:6.0.4' implementation 'org.nosphere.apache:creadur-rat-gradle:0.8.1' implementation 'com.github.spotbugs.snom:spotbugs-gradle-plugin:6.4.2' - implementation 'me.champeau.jmh:jmh-gradle-plugin:0.7.2' + implementation 'me.champeau.jmh:jmh-gradle-plugin:0.7.3' implementation 'org.cyclonedx:cyclonedx-gradle-plugin:3.0.2' implementation "com.fasterxml.jackson:jackson-bom:2.21.3" // later version for cyclonedx implementation "org.slf4j:slf4j-api:2.0.17" // later version for cyclonedx diff --git a/build-logic/src/main/groovy/org.apache.groovy-asciidoctor.gradle b/build-logic/src/main/groovy/org.apache.groovy-asciidoctor.gradle index f269a28fdf4..3ac2b10a893 100644 --- a/build-logic/src/main/groovy/org.apache.groovy-asciidoctor.gradle +++ b/build-logic/src/main/groovy/org.apache.groovy-asciidoctor.gradle @@ -40,6 +40,13 @@ configurations { } tasks.withType(AbstractAsciidoctorTask).configureEach { + // asciidoctor-gradle-jvm 4.0.5 stores configuration objects (DefaultDependencyScopeConfiguration + // etc.) inside its extension, which are not serializable by the configuration cache. + // Remove this call once the plugin ships a CC-compatible release. + notCompatibleWithConfigurationCache( + 'asciidoctor-gradle-jvm 4.0.5 is not configuration-cache compatible ' + + '(captures Gradle dependency-management state in its extension)' + ) outputs.cacheIf { true } usesService(ConcurrentExecutionControlBuildService.restrict(AbstractAsciidoctorTask, gradle)) configurations 'asciidocExtensions' diff --git a/build-logic/src/main/groovy/org.apache.groovy-base.gradle b/build-logic/src/main/groovy/org.apache.groovy-base.gradle index b3135b82e30..22784c8e609 100644 --- a/build-logic/src/main/groovy/org.apache.groovy-base.gradle +++ b/build-logic/src/main/groovy/org.apache.groovy-base.gradle @@ -36,7 +36,6 @@ plugins { id 'org.apache.groovy-common' id 'org.apache.groovy-internal' id 'org.apache.groovy-tested' - id 'org.apache.groovy-asciidoctor' } /** @@ -47,6 +46,13 @@ if (sharedConfiguration.hasCodeCoverage.get()) { pluginManager.apply(JacocoPlugin) } +if (sharedConfiguration.isDocumentationBuild && layout.projectDirectory.dir('src/spec/doc').asFile.isDirectory()) { + // The latest available Asciidoctor Gradle plugins still emit a Gradle 9 deprecation + // during apply, so only documentation builds enable them and suppress that noise. + gradle.startParameter.warningMode = org.gradle.api.logging.configuration.WarningMode.None + pluginManager.apply('org.apache.groovy-asciidoctor') +} + def groovyLibrary = project.extensions.create('groovyLibrary', GroovyLibraryExtension, sharedConfiguration, java) java { @@ -97,6 +103,16 @@ configurations { } } } + asciidocElements { + canBeConsumed = true + canBeResolved = false + attributes { + attribute(Category.CATEGORY_ATTRIBUTE, objects.named(Category, Category.DOCUMENTATION)) + attribute(DocsType.DOCS_TYPE_ATTRIBUTE, objects.named(DocsType, DocsType.USER_MANUAL)) + } + // No artifact — projects without src/spec/doc produce an empty variant. + // org.apache.groovy-asciidoctor.gradle adds the actual artifact when applied. + } javadocClasspath { canBeConsumed = true canBeResolved = false @@ -185,10 +201,14 @@ tasks.withType(Jar).configureEach { jar -> tasks.register('jarjar', JarJarTask) { String projectName = project.name from = jar.archiveFile + // Eagerly resolve the module list so the componentFilter closure captures only a + // plain List — never the GroovyLibraryExtension object — keeping the task + // compatible with the Gradle configuration cache. + List repackagedDeps = groovyLibrary.repackagedDependencies.get() repackagedLibraries.from configurations.runtimeClasspath.incoming.artifactView { componentFilter { component -> if (component instanceof ModuleComponentIdentifier) { - return component.module in groovyLibrary.repackagedDependencies.get() + return component.module in repackagedDeps } return false } @@ -213,12 +233,23 @@ tasks.register('jarjar', JarJarTask) { ] outputFile = tasks.named('jar').flatMap { layout.buildDirectory.file("libs/${it.archiveBaseName.get()}-${it.archiveVersion.get()}${(it.archiveClassifier.get() && it.archiveClassifier.get() != 'raw') ? '-' + it.archiveClassifier.get() : ''}.jar") } - withManifest { - String autoModName = "org.apache.${projectName.replace('-','.')}" - attributes('Automatic-Module-Name': autoModName, 'Bundle-Name': "Groovy module: $projectName") - groovyLibrary.configureManifest(it, excludedFromManifest) - classpath = configurations.runtimeClasspath - } + // All manifest data is stored as plain serializable values so the task is + // compatible with the Gradle configuration cache. + bndClasspath.from(configurations.runtimeClasspath) + String gbv = sharedConfiguration.groovyBundleVersion.get() + bndInstruction('Automatic-Module-Name', "org.apache.${projectName.replace('-', '.')}") + bndInstruction('Bundle-Name', "Groovy module: $projectName") + bndInstruction('Bundle-ManifestVersion', '2') + bndInstruction('Bundle-Description', 'Groovy Runtime') + bndInstruction('Bundle-Vendor', 'The Apache Software Foundation') + bndInstruction('Bundle-Version', gbv) + bndInstruction('Bundle-License', 'Apache-2.0') + bndInstruction('Specification-Title', 'Groovy: a powerful, multi-faceted language for the JVM') + bndInstruction('Specification-Vendor', 'The Apache Software Foundation') + bndInstruction('Specification-Version', gbv) + bndInstruction('Implementation-Title', 'Groovy: a powerful, multi-faceted language for the JVM') + bndInstruction('Implementation-Vendor', 'The Apache Software Foundation') + bndInstruction('Implementation-Version', gbv) } tasks.withType(AbstractCompile).configureEach { diff --git a/build-logic/src/main/groovy/org.apache.groovy-core.gradle b/build-logic/src/main/groovy/org.apache.groovy-core.gradle index e3726d65100..7f2484c3639 100644 --- a/build-logic/src/main/groovy/org.apache.groovy-core.gradle +++ b/build-logic/src/main/groovy/org.apache.groovy-core.gradle @@ -72,11 +72,12 @@ tasks.named('jar') { tasks.named('jarjar') { JarJarTask jjt -> def groovyBundleVersion = sharedConfiguration.groovyBundleVersion.get() - jjt.withManifest { - instruction '-nouses', 'true' - instruction 'Export-Package', "*;version=${groovyBundleVersion}" - instruction 'Eclipse-ExtensibleAPI', 'true' // GROOVY-8713, GROOVY-11582 - } + jjt.bndInstruction('-nouses', 'true') + jjt.bndInstruction('Export-Package', "*;version=${groovyBundleVersion}") + jjt.bndInstruction('Eclipse-ExtensibleAPI', 'true') // GROOVY-8713, GROOVY-11582 + jjt.bndInstruction('DynamicImport-Package', '*') // GROOVY-3192 + jjt.bndInstruction('Eclipse-BuddyPolicy', 'dependent') // GROOVY-5571 + jjt.bndInstruction('Main-Class', 'groovy.ui.GroovyMain') } // Gradle classloading magic with Groovy will only work if it finds a *jar* @@ -92,9 +93,9 @@ def bootstrapJar = tasks.register('bootstrapJar', Jar) { // The main Groovy compile tasks has a special setup because // it uses the "bootstrap compiler" -tasks.withType(GroovyCompile).configureEach { +tasks.withType(GroovyCompile).configureEach { groovyCompileTask -> groovyClasspath = files(bootstrapJar, groovyClasspath) - if (it.name == 'compileGroovy') { + if (groovyCompileTask.name == 'compileGroovy') { classpath = files(bootstrapJar, classpath) } options.incremental = true @@ -112,6 +113,8 @@ interface CoreServices { } def execOperations = objects.newInstance(CoreServices).execOperations +def bridgerClasspath = files(rootProject.configurations.tools) +def classesToBridge = groovyCore.classesToBridge tasks.named('compileJava') { options.fork = true @@ -120,9 +123,9 @@ tasks.named('compileJava') { doLast { execOperations.javaexec { spec -> - spec.classpath(rootProject.configurations.tools) + spec.classpath(bridgerClasspath) spec.mainClass = 'org.jboss.bridger.Bridger' - spec.args(groovyCore.classesToBridge.asList().collect { it.absolutePath }) + spec.args(classesToBridge.asList().collect { it.absolutePath }) } } } @@ -159,16 +162,16 @@ def generateGrammarSourceTask = tasks.named("generateGrammarSource") { doLast { def parserFilePattern = 'Groovy*' - def outputPath = generateGrammarSource.outputDirectory.canonicalPath - def parserPackagePath = "${outputPath}/${PARSER_PACKAGE_NAME.replace('.', '/')}" - file(parserPackagePath).mkdirs() - copy { - from outputPath - into parserPackagePath - include parserFilePattern - } - delete fileTree(outputPath) { - include parserFilePattern + File outputPath = outputDirectory + File parserPackagePath = new File(outputPath, PARSER_PACKAGE_NAME.replace('.', '/')) + parserPackagePath.mkdirs() + outputPath.listFiles()?.findAll { it.name.startsWith('Groovy') }?.each { file -> + java.nio.file.Files.copy( + file.toPath(), + new File(parserPackagePath, file.name).toPath(), + java.nio.file.StandardCopyOption.REPLACE_EXISTING + ) + file.delete() } } } diff --git a/build-logic/src/main/groovy/org.apache.groovy-distribution.gradle b/build-logic/src/main/groovy/org.apache.groovy-distribution.gradle index 46ad2181081..4d46ba5897d 100644 --- a/build-logic/src/main/groovy/org.apache.groovy-distribution.gradle +++ b/build-logic/src/main/groovy/org.apache.groovy-distribution.gradle @@ -26,10 +26,15 @@ plugins { id 'org.apache.groovy-common' id 'org.apache.groovy-aggregating-project' id 'org.apache.groovy-doc-aggregator' - id 'org.asciidoctor.jvm.pdf' } def distributionExtension = project.extensions.create('distribution', DistributionExtension, project) +def documentationBuild = sharedConfiguration.isDocumentationBuild && layout.projectDirectory.dir('src/spec/doc').asFile.isDirectory() + +if (documentationBuild) { + pluginManager.apply('org.apache.groovy-asciidoctor') + pluginManager.apply('org.asciidoctor.jvm.pdf') +} configurations { baseProjects { @@ -133,6 +138,9 @@ def distBin = tasks.register('distBin', Zip) { def distSdk = tasks.register("distSdk", Zip) { def groovyBundleVersion = sharedConfiguration.groovyBundleVersion.get() + // Capture project.version at configuration time: accessing project at execution time + // is an "interrupting" CC problem in Gradle 9.x and causes a Gradle-internal exception. + def projectVersion = project.version.toString() description = 'Generates the binary, sources, documentation and full distributions' archiveBaseName = 'apache-groovy' duplicatesStrategy = DuplicatesStrategy.EXCLUDE @@ -149,10 +157,9 @@ def distSdk = tasks.register("distSdk", Zip) { with distributionExtension.srcSpec } doFirst { - def av = project.version.toString() - if ((av.endsWith('SNAPSHOT') && !groovyBundleVersion.endsWith('SNAPSHOT')) - || (!av.endsWith('SNAPSHOT') && groovyBundleVersion.endsWith('SNAPSHOT'))) { - throw new GradleException("Incoherent versions. Found groovyVersion=$av and groovyBundleVersion=${versions.groovyBundle}") + if ((projectVersion.endsWith('SNAPSHOT') && !groovyBundleVersion.endsWith('SNAPSHOT')) + || (!projectVersion.endsWith('SNAPSHOT') && groovyBundleVersion.endsWith('SNAPSHOT'))) { + throw new GradleException("Incoherent versions. Found groovyVersion=$projectVersion and groovyBundleVersion=${versions.groovyBundle}") } } } @@ -198,21 +205,28 @@ tasks.register("installGroovy", Sync) { } tasks.register("doc") { - dependsOn 'javadocAll', 'groovydocAll', 'docGDK', 'asciidocAll', 'asciidoctorPdf' + dependsOn 'javadocAll', 'groovydocAll', 'docGDK' + if (documentationBuild) { + dependsOn 'asciidocAll', 'asciidoctorPdf' + } } tasks.register("asciidocAll", Copy) { from configurations.allAsciidoc - from tasks.named('asciidoctor') + if (documentationBuild) { + from tasks.named('asciidoctor') + } into layout.buildDirectory.dir("asciidocAll/html5") duplicatesStrategy = DuplicatesStrategy.EXCLUDE } -tasks.named('asciidoctorPdf') { - baseDirFollowsSourceFile() - logDocuments = true - sourceDir = file('src/spec/doc') - outputDir = layout.buildDirectory.dir("asciidocAll/pdf") +if (documentationBuild) { + tasks.named('asciidoctorPdf') { + baseDirFollowsSourceFile() + logDocuments = true + sourceDir = file('src/spec/doc') + outputDir = layout.buildDirectory.dir("asciidocAll/pdf") + } } // The Groovy distribution module isn't a Java library diff --git a/build-logic/src/main/groovy/org.apache.groovy-documented.gradle b/build-logic/src/main/groovy/org.apache.groovy-documented.gradle index 3b43cdde348..4cde3d69298 100644 --- a/build-logic/src/main/groovy/org.apache.groovy-documented.gradle +++ b/build-logic/src/main/groovy/org.apache.groovy-documented.gradle @@ -63,9 +63,11 @@ tasks.withType(Javadoc).configureEach { if (JavaVersion.current() >= JavaVersion.VERSION_21) { addStringOption('-link-modularity-mismatch', 'info') } - addStringOption('tag', 'apiNote:a:"API Note:"') - addStringOption('tag', 'implSpec:a:"Implementation Requirements:"') - addStringOption('tag', 'implNote:a:"Implementation Note:"') + tags( + 'apiNote:a:"API Note:"', + 'implSpec:a:"Implementation Requirements:"', + 'implNote:a:"Implementation Note:"' + ) windowTitle = "Groovy ${versions.groovy}" docTitle = "Groovy ${versions.groovy}" classpath += project.file('src/main/java') // to pick up package.html diff --git a/build-logic/src/main/groovy/org.apache.groovy-library.gradle b/build-logic/src/main/groovy/org.apache.groovy-library.gradle index 12232b12fec..ae7618ddcd1 100644 --- a/build-logic/src/main/groovy/org.apache.groovy-library.gradle +++ b/build-logic/src/main/groovy/org.apache.groovy-library.gradle @@ -32,18 +32,16 @@ dependencies { tasks.named('jarjar') { JarJarTask jjt -> def groovyBundleVersion = sharedConfiguration.groovyBundleVersion.get() - jjt.withManifest { - instruction '-nouses', 'true' - instruction 'Export-Package', "*;version=${groovyBundleVersion}" - instruction 'Fragment-Host', 'groovy' // GROOVY-9402, GROOVY-11570 - def folder = file("${projectDir}/src/main/resources/META-INF/services") - if (folder.exists() && folder.listFiles().count { it.name ==~ /^(?!(org.codehaus.groovy.transform.ASTTransformation)$).*$/ } > 0) { - instruction 'Require-Capability', 'osgi.extender;filter:="(osgi.extender=osgi.serviceloader.processor)"' - instruction 'Require-Capability', 'osgi.extender;filter:="(osgi.extender=osgi.serviceloader.registrar)"' - folder.eachFileMatch(~/^(?!(org.codehaus.groovy.transform.ASTTransformation)$).*$/) { - instruction 'Require-Capability', "osgi.serviceloader;filter:=\"(osgi.serviceloader=${it.name})\";cardinality:=multiple" - instruction 'Provide-Capability', "osgi.serviceloader;osgi.serviceloader=\"${it.name}\"" - } + jjt.bndInstruction('-nouses', 'true') + jjt.bndInstruction('Export-Package', "*;version=${groovyBundleVersion}") + jjt.bndInstruction('Fragment-Host', 'groovy') // GROOVY-9402, GROOVY-11570 + def folder = file("${projectDir}/src/main/resources/META-INF/services") + if (folder.exists() && folder.listFiles().count { it.name ==~ /^(?!(org.codehaus.groovy.transform.ASTTransformation)$).*$/ } > 0) { + jjt.bndInstruction('Require-Capability', 'osgi.extender;filter:="(osgi.extender=osgi.serviceloader.processor)"') + jjt.bndInstruction('Require-Capability', 'osgi.extender;filter:="(osgi.extender=osgi.serviceloader.registrar)"') + folder.eachFileMatch(~/^(?!(org.codehaus.groovy.transform.ASTTransformation)$).*$/) { + jjt.bndInstruction('Require-Capability', "osgi.serviceloader;filter:=\"(osgi.serviceloader=${it.name})\";cardinality:=multiple") + jjt.bndInstruction('Provide-Capability', "osgi.serviceloader;osgi.serviceloader=\"${it.name}\"") } } } diff --git a/build-logic/src/main/groovy/org.apache.groovy-publish-validation.gradle b/build-logic/src/main/groovy/org.apache.groovy-publish-validation.gradle index ecb6c057175..8887f99804a 100644 --- a/build-logic/src/main/groovy/org.apache.groovy-publish-validation.gradle +++ b/build-logic/src/main/groovy/org.apache.groovy-publish-validation.gradle @@ -18,10 +18,12 @@ */ tasks.withType(PublishToMavenRepository).configureEach { + def isReleaseVersion = sharedConfiguration.isReleaseVersion.get() doLast { - if (sharedConfiguration.isReleaseVersion.get()) { - pluginManager.withPlugin('java') { - project.configurations.runtimeClasspath.resolvedConfiguration.resolvedArtifacts.each { + if (isReleaseVersion) { + def publishedProject = delegate.project + if (publishedProject.pluginManager.hasPlugin('java')) { + publishedProject.configurations.runtimeClasspath.resolvedConfiguration.resolvedArtifacts.each { if (it.moduleVersion.id.version.endsWith("-SNAPSHOT")) { throw new GradleException("Found snapshot dependency for non-snapshot Groovy: " + it.moduleVersion) } diff --git a/build-logic/src/main/groovy/org.apache.groovy-published-library.gradle b/build-logic/src/main/groovy/org.apache.groovy-published-library.gradle index b07d5717d0d..b7e0e90aa4d 100644 --- a/build-logic/src/main/groovy/org.apache.groovy-published-library.gradle +++ b/build-logic/src/main/groovy/org.apache.groovy-published-library.gradle @@ -21,6 +21,7 @@ import org.cyclonedx.model.License import org.cyclonedx.model.LicenseChoice import org.cyclonedx.model.OrganizationalContact import org.cyclonedx.model.OrganizationalEntity +import org.gradle.api.publish.maven.tasks.PublishToMavenRepository plugins { id 'maven-publish' @@ -819,9 +820,7 @@ publishing { } signing { - required = { - sharedConfiguration.signing.shouldSign(gradle.taskGraph) - } + required = sharedConfiguration.signing.shouldSign() sign publishing.publications.maven if (sharedConfiguration.signing.useGpgCmd.get()) { useGpgCmd() @@ -829,7 +828,7 @@ signing { } gradle.taskGraph.whenReady { taskGraph -> - if (sharedConfiguration.signing.shouldSign(gradle.taskGraph)) { + if (sharedConfiguration.signing.shouldSign()) { // Use Java 6's console or Swing to read input (not suitable for CI) if (!sharedConfiguration.signing.hasAllKeyDetails()) { printf '\n\nWe have to sign some things in this build.' + @@ -858,6 +857,12 @@ gradle.taskGraph.whenReady { taskGraph -> } } +tasks.withType(PublishToMavenRepository).configureEach { + if (repository.name == 'LocalFile') { + mustRunAfter(rootProject.tasks.named('clean')) + } +} + String promptUser(String prompt) { def response = '' if (System.console() != null) { diff --git a/build-logic/src/main/groovy/org.apache.groovy-tested.gradle b/build-logic/src/main/groovy/org.apache.groovy-tested.gradle index afc0cf7a221..c76139adccd 100644 --- a/build-logic/src/main/groovy/org.apache.groovy-tested.gradle +++ b/build-logic/src/main/groovy/org.apache.groovy-tested.gradle @@ -57,9 +57,9 @@ def aggregator = TestResultAggregatorService.register( // so a developer's polluted ~/.groovy/grapes doesn't leak into tests. // Enable on CI with: ./gradlew test -Pgroovy.grape.bridge-cache=true def grapeBridgeCache = (findProperty('groovy.grape.bridge-cache') ?: - System.properties['groovy.grape.bridge-cache']) == 'true' + providers.systemProperty('groovy.grape.bridge-cache').orNull) == 'true' -tasks.withType(Test).configureEach { +tasks.withType(Test).configureEach { testTask -> def fs = objects.newInstance(TestServices).fileSystemOperations def grapeDirectory = new File(temporaryDir, '.groovy') def options = ['-ea', "-Xms${groovyJUnit_ms}", "-Xmx${groovyJUnit_mx}", @@ -80,7 +80,7 @@ tasks.withType(Test).configureEach { systemProperty 'http.agent', "Apache-Maven/3.9.14 (Java ${System.getProperty('java.version')}; ${System.getProperty('os.name')} ${System.getProperty('os.version')})" systemProperty 'groovy.force.illegal.access', findProperty('groovy.force.illegal.access') - def testdb = System.properties['groovy.testdb.props'] + def testdb = providers.systemProperty('groovy.testdb.props').orNull if (testdb) { systemProperty 'groovy.testdb.props', testdb } @@ -88,22 +88,21 @@ tasks.withType(Test).configureEach { // to the test JVM. Lets users override Grape behaviour — debug flags, future // settings — without adding one-off plumbing per flag. Project properties (-P) // win over system properties (-D) when both are set, matching Gradle convention. + // providers.systemPropertiesPrefixedBy() is CC-tracked (unlike System.properties), + // so changes to -D groovy.grape.* flags correctly invalidate the cache entry. def grapeProps = new LinkedHashMap() project.properties.each { k, v -> if (k.toString().startsWith('groovy.grape.') && v != null) { grapeProps[k.toString()] = v.toString() } } - System.properties.each { k, v -> - if (k.toString().startsWith('groovy.grape.') && v != null) { - grapeProps.putIfAbsent(k.toString(), v.toString()) - } + providers.systemPropertiesPrefixedBy('groovy.grape.').get().each { k, v -> + grapeProps.putIfAbsent(k, v) } grapeProps.each { key, value -> systemProperty key, value } - def headless = System.properties['java.awt.headless'] - if (headless == 'true') { + if (providers.systemProperty('java.awt.headless').orNull == 'true') { systemProperty 'java.awt.headless', 'true' } systemProperty 'apple.awt.UIElement', 'true' @@ -132,7 +131,7 @@ tasks.withType(Test).configureEach { scanForTestClasses = true ignoreFailures = false classpath = files('src/test/groovy') + classpath - exclude buildExcludeFilter(it.name == 'test') + exclude buildExcludeFilter(testTask.name == 'test') ext.resultText = '' testLogging { @@ -144,6 +143,7 @@ tasks.withType(Test).configureEach { useJUnitPlatform() usesService(aggregator) usesService(ConcurrentExecutionControlBuildService.restrict(Test, gradle, 2)) + File projectDirectory = project.layout.projectDirectory.asFile doFirst { fs.delete { @@ -164,7 +164,7 @@ tasks.withType(Test).configureEach { into(new File(grapeDirectory, 'grapes')) exclude '**/*.lck', '**/*.lastUpdated' } - logger.lifecycle "Bridged ~/.groovy/grapes -> ${grapeDirectory}/grapes" + testTask.logger.lifecycle "Bridged ~/.groovy/grapes -> ${grapeDirectory}/grapes" } // Also bridge ~/.m2/repository so the test JVM's localm2 Ivy resolver // (configured as file:${user.home}/.m2/repository/) finds artifacts @@ -181,10 +181,10 @@ tasks.withType(Test).configureEach { into(m2Target) exclude '**/*.lastUpdated', '**/_remote.repositories' } - logger.lifecycle "Bridged ~/.m2/repository -> ${m2Target}" + testTask.logger.lifecycle "Bridged ~/.m2/repository -> ${m2Target}" } } - logger.debug "Grape directory: ${grapeDirectory.absolutePath}" + testTask.logger.debug "Grape directory: ${grapeDirectory.absolutePath}" } doLast { @@ -204,7 +204,7 @@ tasks.withType(Test).configureEach { } } fs.delete { - delete(files(".").filter { it.name.endsWith '.class' }) + delete(projectDirectory.listFiles()?.findAll { it.name.endsWith('.class') } ?: []) } } @@ -215,7 +215,7 @@ tasks.withType(Test).configureEach { def green = '\u001B[32m' def yellow = '\u001B[33m' def reset = '\u001B[0m' - logger.lifecycle "${desc.name}: ${green}${result.successfulTestCount} passed${reset}, " + + testTask.logger.lifecycle "${desc.name}: ${green}${result.successfulTestCount} passed${reset}, " + "${yellow}${result.skippedTestCount} skipped${reset} " + "(${result.testCount} tests)" } @@ -278,15 +278,15 @@ Closure buildExcludeFilter(boolean legacyTestSuite) { // Warn (rather than silently NO-SOURCE) when the groovy/grape exclusion // swallows the entirety of a project's test source set. -gradle.taskGraph.whenReady { graph -> - def testTask = tasks.named('test').get() - if (!graph.hasTask(testTask)) return - if (providers.systemProperty('junit.network').getOrNull()) return - boolean hasGrapeTests = sourceSets.test.allSource.srcDirs.any { - new File(it, 'groovy/grape').isDirectory() - } - if (hasGrapeTests) { - logger.warn("WARNING: ${testTask.path} will skip groovy/grape/* tests; set -Djunit.network=true to include them") +boolean hasGrapeTests = sourceSets.test.allSource.srcDirs.any { + new File(it, 'groovy/grape').isDirectory() +} +def junitNetwork = providers.systemProperty('junit.network') +tasks.named('test', Test).configure { testTask -> + doFirst { + if (!junitNetwork.getOrNull() && hasGrapeTests) { + testTask.logger.warn("WARNING: ${testTask.path} will skip groovy/grape/* tests; set -Djunit.network=true to include them") + } } } diff --git a/build-logic/src/main/groovy/org/apache/groovy/gradle/GroovydocAntPlugin.groovy b/build-logic/src/main/groovy/org/apache/groovy/gradle/GroovydocAntPlugin.groovy index dab78236fa8..2c6f5180911 100644 --- a/build-logic/src/main/groovy/org/apache/groovy/gradle/GroovydocAntPlugin.groovy +++ b/build-logic/src/main/groovy/org/apache/groovy/gradle/GroovydocAntPlugin.groovy @@ -20,6 +20,7 @@ package org.apache.groovy.gradle import groovy.transform.CompileDynamic import groovy.transform.CompileStatic +import groovy.ant.AntBuilder import org.gradle.api.GradleException import org.gradle.api.Plugin @@ -70,6 +71,24 @@ class GroovydocAntPlugin implements Plugin { @CompileDynamic private static void configureGroovydocTasks(Project project, GroovydocAntExtension extension) { project.tasks.withType(Groovydoc).configureEach { gdoc -> + def conventionalSourceDirs = project.files('src/main/groovy', 'src/main/java') + if (!gdoc.ext.has('groovydocSourceDirs')) { + SourceSetContainer sourceSets = project.extensions.findByType(SourceSetContainer) + def main = sourceSets?.findByName('main') + if (main != null) { + def configuredSourceDirs = project.files(main.groovy.srcDirs + main.java.srcDirs) + if (!configuredSourceDirs.files.empty) { + gdoc.ext.groovydocSourceDirs = configuredSourceDirs + } + } + if (!gdoc.ext.has('groovydocSourceDirs')) { + if (!conventionalSourceDirs.files.empty) { + gdoc.ext.groovydocSourceDirs = conventionalSourceDirs + } + } + } + def antSourceDirs = resolveSourceDirectoriesInput(project, gdoc, conventionalSourceDirs) + gdoc.inputs.property('antJavaVersion', extension.javaVersion.orElse('')) gdoc.inputs.property('antShowInternal', extension.showInternal) gdoc.inputs.property('antNoIndex', extension.noIndex) @@ -81,6 +100,9 @@ class GroovydocAntPlugin implements Plugin { gdoc.inputs.files(extension.additionalStylesheets) .withPropertyName('antAdditionalStylesheets') .optional(true) + gdoc.inputs.files(project.files(antSourceDirs)) + .withPropertyName('antSourceDirs') + .optional(true) if (!extension.useAntBuilder.get()) { return @@ -88,17 +110,21 @@ class GroovydocAntPlugin implements Plugin { gdoc.actions.clear() gdoc.doLast { - executeGroovydoc(gdoc, extension, project) + GroovydocAntPlugin.executeGroovydoc(delegate as Groovydoc, extension, antSourceDirs) } } } @CompileDynamic - private static void executeGroovydoc(Groovydoc gdoc, GroovydocAntExtension extension, Project project) { + private static void executeGroovydoc(Groovydoc gdoc, GroovydocAntExtension extension, Object sourceDirectories) { File destDir = resolveFile(gdoc.destinationDir) destDir.mkdirs() - List sourceDirs = resolveSourceDirectories(gdoc, project) + def runtimeSourceDirectories = gdoc.ext.has('groovydocSourceDirs') ? gdoc.ext.groovydocSourceDirs : sourceDirectories + List sourceDirs = normalizeSourceDirectories(runtimeSourceDirectories, gdoc.name) + if (sourceDirs.isEmpty()) { + sourceDirs = inferSourceDirectories(gdoc.source.files) + } if (sourceDirs.isEmpty()) { throw new GradleException( "Groovydoc task '${gdoc.name}': no source directories found. " + @@ -113,7 +139,9 @@ class GroovydocAntPlugin implements Plugin { ) } - project.ant.taskdef( + AntBuilder ant = new AntBuilder() + + ant.taskdef( name: 'groovydoc', classname: 'org.codehaus.groovy.ant.Groovydoc', classpath: classpath.asPath @@ -150,8 +178,8 @@ class GroovydocAntPlugin implements Plugin { def overviewText = gdoc.overviewText if (overviewText != null) { File overviewTmp = new File( - project.layout.buildDirectory.get().asFile, - "tmp/${gdoc.name}/overview.html" + gdoc.temporaryDir, + 'overview.html' ) overviewTmp.parentFile.mkdirs() overviewTmp.text = overviewText.asString() @@ -161,7 +189,7 @@ class GroovydocAntPlugin implements Plugin { def links = gdoc.links ?: [] def extraStylesheets = extension.additionalStylesheets.files - project.ant.groovydoc(antArgs) { + ant.groovydoc(antArgs) { links.each { l -> link(packages: (l.packages ?: []).join(','), href: l.url ?: '') } @@ -172,31 +200,55 @@ class GroovydocAntPlugin implements Plugin { } @CompileDynamic - private static List resolveSourceDirectories(Groovydoc gdoc, Project project) { + private static Object resolveSourceDirectoriesInput(Project project, Groovydoc gdoc, FileCollection conventionalSourceDirs) { def override = gdoc.ext.has('groovydocSourceDirs') ? gdoc.ext.groovydocSourceDirs : null if (override != null) { - Collection files - if (override instanceof FileCollection) { - files = ((FileCollection) override).files - } else if (override instanceof Collection) { - files = (Collection) override - } else { - files = project.files(override).files - } - return files.findAll { it.exists() }.unique() as List + return override } SourceSetContainer sourceSets = project.extensions.findByType(SourceSetContainer) - if (sourceSets != null) { - def main = sourceSets.findByName('main') - if (main != null) { - List dirs = [] - dirs.addAll(main.groovy.srcDirs.findAll { it.exists() }) - dirs.addAll(main.java.srcDirs.findAll { it.exists() }) - return dirs.unique() + def main = sourceSets?.findByName('main') + if (main != null) { + def configuredSourceDirs = project.files(main.groovy.srcDirs + main.java.srcDirs) + if (!configuredSourceDirs.files.empty) { + return configuredSourceDirs } } - [] + conventionalSourceDirs + } + + @CompileDynamic + private static List normalizeSourceDirectories(Object value, String taskName) { + if (value == null) { + return [] + } + Collection files + if (value instanceof FileCollection) { + files = ((FileCollection) value).files + } else if (value instanceof Collection) { + files = (Collection) value + } else { + throw new GradleException("Groovydoc task '${taskName}': source directories must be a FileCollection or Collection.") + } + files.findAll { it.exists() }.unique() as List + } + + @CompileDynamic + private static List inferSourceDirectories(Collection files) { + files.collectMany { File file -> + File dir = file.directory ? file : file.parentFile + if (dir == null) { + return [] + } + String path = dir.absolutePath.replace('\\', '/') + for (String marker : ['/src/main/java', '/src/main/groovy', '/src/antlr', '/build/generated/sources/antlr4']) { + int idx = path.indexOf(marker) + if (idx >= 0) { + return [new File(path.substring(0, idx + marker.length()))] + } + } + [dir] + }.findAll { it.exists() }.unique() as List } @CompileDynamic diff --git a/build-logic/src/main/groovy/org/apache/groovy/gradle/JarJarTask.groovy b/build-logic/src/main/groovy/org/apache/groovy/gradle/JarJarTask.groovy index cc24c8873f5..65be072eb8b 100644 --- a/build-logic/src/main/groovy/org/apache/groovy/gradle/JarJarTask.groovy +++ b/build-logic/src/main/groovy/org/apache/groovy/gradle/JarJarTask.groovy @@ -18,16 +18,16 @@ */ package org.apache.groovy.gradle +import aQute.bnd.osgi.Analyzer import groovy.transform.AutoFinal import javax.inject.Inject -import org.gradle.api.Action import org.gradle.api.DefaultTask import org.gradle.api.file.ConfigurableFileCollection import org.gradle.api.file.FileSystemOperations import org.gradle.api.file.RegularFileProperty -import org.gradle.api.java.archives.Manifest +import org.gradle.api.model.ObjectFactory import org.gradle.api.tasks.CacheableTask import org.gradle.api.tasks.Classpath import org.gradle.api.tasks.Input @@ -46,19 +46,25 @@ class JarJarTask extends DefaultTask { @InputFile @Classpath - final RegularFileProperty from = project.objects.fileProperty() + final RegularFileProperty from @InputFiles @Classpath - final ConfigurableFileCollection repackagedLibraries = project.objects.fileCollection() + final ConfigurableFileCollection repackagedLibraries @InputFiles @Classpath - final ConfigurableFileCollection jarjarToolClasspath = project.objects.fileCollection() + final ConfigurableFileCollection jarjarToolClasspath - final protected osgi = project.rootProject.extensions.osgi + /** + * Classpath passed to the BND {@link Analyzer} for OSGi import resolution. + * Typically the project's {@code runtimeClasspath}. + */ + @Classpath + final ConfigurableFileCollection bndClasspath - final protected String projectName = project.name + @Internal + final String projectName @Input @Optional @@ -84,22 +90,41 @@ class JarJarTask extends DefaultTask { Map includedResources = [:] @OutputFile - final RegularFileProperty outputFile = project.objects.fileProperty() + final RegularFileProperty outputFile @Input boolean createManifest = true - private final FileSystemOperations fs + /** + * BND instructions used when generating the OSGi manifest. + * Keys that are added multiple times are accumulated with comma-separators, + * which is the correct BND syntax for multi-value headers such as + * {@code Require-Capability} and {@code Provide-Capability}. + */ + @Input + Map bndInstructions = [:] - private List> manifestTweaks = [] + private final FileSystemOperations fs @Inject - JarJarTask(FileSystemOperations fileSystemOperations) { + JarJarTask(ObjectFactory objects, FileSystemOperations fileSystemOperations) { this.fs = fileSystemOperations + this.from = objects.fileProperty() + this.repackagedLibraries = objects.fileCollection() + this.jarjarToolClasspath = objects.fileCollection() + this.bndClasspath = objects.fileCollection() + this.outputFile = objects.fileProperty() + this.projectName = project.name } - void withManifest(Action action) { - manifestTweaks.add(action) + /** + * Adds (or accumulates) a BND manifest instruction. + * If {@code key} already has a value, the new {@code value} is appended + * with a comma separator — matching BND's multi-value header syntax. + */ + void bndInstruction(String key, String value) { + def existing = bndInstructions.get(key) + bndInstructions[key] = existing != null ? "${existing},${value}" : value } @Internal @@ -164,13 +189,28 @@ class JarJarTask extends DefaultTask { // Step 3: generate an OSGi manifest referencing the repackaged classes if (createManifest) { - def mf = osgi.osgiManifest { - symbolicName = this.projectName - instruction 'Import-Package', '*;resolution:=optional' - classesDir = tmpJar + def analyzer = new Analyzer() + try { + analyzer.setJar(tmpJar) + bndClasspath.files.each { File f -> + if (f.exists()) { + analyzer.addClasspath(f) + } + } + // Defaults — all overridable by entries in bndInstructions + analyzer.setProperty('Bundle-SymbolicName', projectName) + analyzer.setProperty('Import-Package', '*;resolution:=optional') + // Strip BND-generated housekeeping headers that must not appear in released jars + analyzer.setProperty('-removeheaders', + 'Bnd-LastModified,Tool,Created-By,Originally-Created-By,Ant-Version') + // User-specified instructions (override defaults above) + bndInstructions.each { k, v -> analyzer.setProperty(k, v) } + + def manifest = analyzer.calcManifest() + manifestFile.withOutputStream { os -> manifest.write(os) } + } finally { + analyzer.close() } - manifestTweaks*.execute(mf) - mf.writeTo(manifestFile) ant.zip(destfile: outputFile, modificationtime: tstamp, update: true) { zipfileset(dir: manifestFile.parent, includes: manifestFile.name, prefix: 'META-INF') diff --git a/build-logic/src/main/groovy/org/apache/groovy/gradle/PerformanceTestsExtension.groovy b/build-logic/src/main/groovy/org/apache/groovy/gradle/PerformanceTestsExtension.groovy index 35e950c399a..883676156df 100644 --- a/build-logic/src/main/groovy/org/apache/groovy/gradle/PerformanceTestsExtension.groovy +++ b/build-logic/src/main/groovy/org/apache/groovy/gradle/PerformanceTestsExtension.groovy @@ -19,6 +19,8 @@ package org.apache.groovy.gradle import groovy.transform.CompileStatic +import org.gradle.process.CommandLineArgumentProvider +import org.gradle.api.file.ConfigurableFileCollection import org.gradle.api.artifacts.Configuration import org.gradle.api.artifacts.ConfigurationContainer import org.gradle.api.artifacts.dsl.DependencyHandler @@ -28,7 +30,13 @@ import org.gradle.api.attributes.LibraryElements import org.gradle.api.attributes.Usage import org.gradle.api.file.FileCollection import org.gradle.api.file.ProjectLayout +import org.gradle.api.file.RegularFileProperty import org.gradle.api.model.ObjectFactory +import org.gradle.api.tasks.Classpath +import org.gradle.api.tasks.InputFiles +import org.gradle.api.tasks.Internal +import org.gradle.api.tasks.PathSensitive +import org.gradle.api.tasks.PathSensitivity import org.gradle.api.tasks.JavaExec import org.gradle.api.tasks.SourceSetContainer import org.gradle.api.tasks.TaskContainer @@ -43,7 +51,7 @@ class PerformanceTestsExtension { private final DependencyHandler dependencies private final SourceSetContainer sourceSets private final ProjectLayout layout - private final List testFiles = [] + private final ConfigurableFileCollection testFiles @Inject PerformanceTestsExtension(ObjectFactory objects, @@ -59,10 +67,11 @@ class PerformanceTestsExtension { this.dependencies = dependencies this.sourceSets = sourceSets this.layout = layout + this.testFiles = objects.fileCollection() } void testFiles(FileCollection files) { - testFiles.addAll(files.asList()) + testFiles.from(files) } void versions(String... versions) { @@ -106,21 +115,52 @@ class PerformanceTestsExtension { ].each { conf.dependencies.add(dependencies.create(it)) } } def outputFile = layout.buildDirectory.file("compilation-stats-${version}.csv") + def compilationClasspath = objects.fileCollection().from(groovyConf) + def performanceArguments = objects.newInstance(CompilerPerformanceTestArguments) + performanceArguments.outputFile.set(outputFile) + performanceArguments.compilationClasspath.from(compilationClasspath) + performanceArguments.testFiles.from(testFiles) def perfTest = tasks.register("performanceTestGroovy${version}", JavaExec) { je -> je.group = "Performance tests" je.mainClass.set('org.apache.groovy.perf.CompilerPerformanceTest') je.classpath(groovyConf, sourceSets.getByName('test').output) je.jvmArgs = ['-Xms512m', '-Xmx512m'] je.outputs.file(outputFile) - je.doFirst { - def args = [outputFile.get().toString(), "-cp", groovyConf.asPath] - args.addAll(testFiles.collect { it.toString() }) - je.setArgs(args) - println je.args.asList() - } + je.argumentProviders.add(performanceArguments) } tasks.named("performanceTests", PerformanceTestSummary) { pts -> - pts.csvFiles.from(perfTest) + pts.dependsOn(perfTest) + pts.csvFiles.from(outputFile) + } + } + + @CompileStatic + static class CompilerPerformanceTestArguments implements CommandLineArgumentProvider { + @Internal + final RegularFileProperty outputFile + + @Classpath + final ConfigurableFileCollection compilationClasspath + + @InputFiles + @PathSensitive(PathSensitivity.RELATIVE) + final ConfigurableFileCollection testFiles + + @Inject + CompilerPerformanceTestArguments(ObjectFactory objects) { + outputFile = objects.fileProperty() + compilationClasspath = objects.fileCollection() + testFiles = objects.fileCollection() + } + + @Override + Iterable asArguments() { + List args = new ArrayList<>(testFiles.files.size() + 3) + args.add(outputFile.get().asFile.absolutePath) + args.add('-cp') + args.add(compilationClasspath.asPath) + args.addAll(testFiles.files.collect { File file -> file.absolutePath }) + args } } } diff --git a/build-logic/src/main/groovy/org/apache/groovy/gradle/SharedConfiguration.groovy b/build-logic/src/main/groovy/org/apache/groovy/gradle/SharedConfiguration.groovy index 81e6aca9fc1..1158b3292d4 100644 --- a/build-logic/src/main/groovy/org/apache/groovy/gradle/SharedConfiguration.groovy +++ b/build-logic/src/main/groovy/org/apache/groovy/gradle/SharedConfiguration.groovy @@ -20,7 +20,6 @@ package org.apache.groovy.gradle import groovy.transform.CompileStatic import org.gradle.StartParameter -import org.gradle.api.execution.TaskExecutionGraph import org.gradle.api.file.Directory import org.gradle.api.file.ProjectLayout import org.gradle.api.file.RegularFile @@ -45,6 +44,7 @@ class SharedConfiguration { final Provider hasCodeCoverage final Provider targetJavaVersion final Provider groovyTargetBytecodeVersion + final boolean isDocumentationBuild final boolean isRunningOnCI @Nested @@ -71,16 +71,24 @@ class SharedConfiguration { .orElse(providers.systemProperty("installDirectory")) isRunningOnCI = detectCi(rootProjectDirectory, logger) artifactory = new Artifactory(layout, providers, logger) - signing = new Signing(this, objects, providers) + boolean apachePublishRequested = startParameter.taskNames.any { String taskName -> + isApachePublishTask(taskName) + } + signing = new Signing(this, objects, providers, apachePublishRequested) binaryCompatibilityBaselineVersion = providers.gradleProperty("binaryCompatibilityBaseline") + // Evaluate eagerly at construction time (startParameter is available here) so that + // no lazy provider captures a StartParameter reference, which Gradle would otherwise + // need to serialize for the configuration cache. + boolean hasJacocoTask = startParameter.taskNames.any { String name -> name.contains('jacoco') } hasCodeCoverage = providers.gradleProperty("coverage") .map { Boolean.valueOf(it) } - .orElse( - providers.provider { startParameter.taskNames.any { it =~ /jacoco/ } } - ) + .orElse(hasJacocoTask) .orElse(false) targetJavaVersion = providers.gradleProperty("targetJavaVersion") groovyTargetBytecodeVersion = providers.gradleProperty("groovyTargetBytecodeVersion") + isDocumentationBuild = startParameter.taskNames.any { String taskName -> + isDocumentationTask(taskName) + } File javaHome = new File(providers.systemProperty('java.home').get()) String javaVersion = providers.systemProperty('java.version').get() String userdir = providers.systemProperty('user.dir').get() @@ -95,6 +103,28 @@ class SharedConfiguration { isCi } + private static boolean isDocumentationTask(String taskName) { + String normalized = taskName.toLowerCase(Locale.ROOT) + normalized.contains('asciidoc') || + normalized.contains('asciidoctor') || + normalized.contains('javadoc') || + normalized.contains('groovydoc') || + normalized.endsWith('doc') || + normalized.endsWith(':doc') || + normalized.contains('docgdk') || + normalized == 'dist' || + normalized.startsWith('dist') || + normalized.contains(':dist') + } + + private static boolean isApachePublishTask(String taskName) { + String normalized = taskName.trim() + normalized == 'artifactoryPublish' || + normalized.endsWith(':artifactoryPublish') || + normalized == 'publishAllPublicationsToApacheRepository' || + normalized.endsWith(':publishAllPublicationsToApacheRepository') + } + static class Artifactory { final Provider username final Provider password @@ -136,6 +166,7 @@ class SharedConfiguration { static class Signing { private final SharedConfiguration config + private final boolean apachePublishRequested final Property keyId final Property secretKeyRingFile final Property password @@ -143,7 +174,7 @@ class SharedConfiguration { final Provider forceSign final Provider trySign - Signing(SharedConfiguration config, ObjectFactory objects, ProviderFactory providers) { + Signing(SharedConfiguration config, ObjectFactory objects, ProviderFactory providers, boolean apachePublishRequested) { keyId = objects.property(String).convention( providers.gradleProperty("signing.keyId") ) @@ -160,13 +191,12 @@ class SharedConfiguration { trySign = providers.gradleProperty("trySign") .map { Boolean.valueOf(it) }.orElse(false) this.config = config + this.apachePublishRequested = apachePublishRequested } - boolean shouldSign(TaskExecutionGraph taskGraph) { + boolean shouldSign() { trySign.get() || (config.isReleaseVersion.get() && - (forceSign.get() || [':artifactoryPublish', ':publishAllPublicationsToApacheRepository'].any { - taskGraph.hasTask(it) - })) + (forceSign.get() || apachePublishRequested)) } boolean hasAllKeyDetails() { diff --git a/build.gradle b/build.gradle index b54602173e5..ad1626af24a 100644 --- a/build.gradle +++ b/build.gradle @@ -256,10 +256,10 @@ licenseReport { ] } -gradle.taskGraph.whenReady { graph -> - if (graph.hasTask(':generateLicenseReport') && gradle.startParameter.parallelProjectExecutionEnabled) { - throw new GradleException('generateLicenseReport is not compatible with parallel builds (Gradle 9+ issue). Please re-run with: ./gradlew generateLicenseReport --no-parallel') - } +if (gradle.startParameter.parallelProjectExecutionEnabled && gradle.startParameter.taskNames.any { + it == 'generateLicenseReport' || it.endsWith(':generateLicenseReport') +}) { + throw new GradleException('generateLicenseReport is not compatible with parallel builds (Gradle 9+ issue). Please re-run with: ./gradlew generateLicenseReport --no-parallel') } sonarqube { diff --git a/gradle.properties b/gradle.properties index 81f71ccbef7..2ff6b943fb0 100644 --- a/gradle.properties +++ b/gradle.properties @@ -41,6 +41,14 @@ org.gradle.jvmargs=-Xms1200m -Xmx2g -XX:MaxMetaspaceSize=1500m -XX:+UseG1GC # enable the Gradle build cache org.gradle.caching=true +# enable the Gradle configuration cache +org.gradle.configuration-cache=true +# treat CC problems as warnings so that dist/publish builds (which include third-party +# plugins such as asciidoctor-gradle-jvm 4.0.5 that are not yet CC-compatible) succeed +# rather than failing during CC-entry storage. Compile/test builds have no CC problems +# and continue to benefit from the cache as normal. +org.gradle.configuration-cache.problems=warn + # enable --parallel org.gradle.parallel=true diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml index 70aa232ea03..019b0b24089 100644 --- a/gradle/verification-metadata.xml +++ b/gradle/verification-metadata.xml @@ -61,6 +61,7 @@ + @@ -88,6 +89,7 @@ + @@ -1171,9 +1173,9 @@ - - - + + + @@ -1406,6 +1408,12 @@ + + + + + + diff --git a/subprojects/groovy-ant/build.gradle b/subprojects/groovy-ant/build.gradle index 872c7a17efe..45ec426f378 100644 --- a/subprojects/groovy-ant/build.gradle +++ b/subprojects/groovy-ant/build.gradle @@ -55,9 +55,14 @@ dependencies { tasks.withType(Test).configureEach { // Supply the external jar (resolved, not checked in) to the GROOVY-9197 Ant test. + // Accessing 'configurations' inside doFirst (execution time) is forbidden by the + // configuration cache; capture a CC-safe ConfigurableFileCollection at configuration + // time instead, and resolve it lazily when the task action runs. + def externalJarFiles = objects.fileCollection() + .from(configurations.named('externalJarForJointCompilationTest')) + inputs.files externalJarFiles doFirst { - systemProperty 'groovy.ant.test.externalJar', - configurations.externalJarForJointCompilationTest.singleFile.absolutePath + systemProperty 'groovy.ant.test.externalJar', externalJarFiles.singleFile.absolutePath } Integer feature = TargetJavaHomeSupport.featureVersionFromReleaseFile(TargetJavaHomeSupport.targetJavaHome(project)) diff --git a/subprojects/groovy-binary/build.gradle b/subprojects/groovy-binary/build.gradle index 558b7ac2ec5..6a59534b62b 100644 --- a/subprojects/groovy-binary/build.gradle +++ b/subprojects/groovy-binary/build.gradle @@ -21,7 +21,6 @@ import org.apache.tools.ant.filters.ReplaceTokens plugins { id 'org.apache.groovy-distribution' id 'org.apache.groovy-published-library' - id 'org.apache.groovy-asciidoctor' } //only used when testing locally built artifacts, not for publishing @@ -39,12 +38,14 @@ docAggregation { '**/*.interp' // Antlr generated file } -tasks.named('asciidoctor') { - attributes reldir_root: '.', - reldir_jmx: '.', - reldir_swing: '.', - reldir_console: '.', - reldir_groovysh: '.' +if (sharedConfiguration.isDocumentationBuild) { + tasks.named('asciidoctor') { + attributes reldir_root: '.', + reldir_jmx: '.', + reldir_swing: '.', + reldir_console: '.', + reldir_groovysh: '.' + } } distribution { @@ -137,11 +138,13 @@ distribution { into('html/gapi') { from tasks.named('groovydocAll') } - into('html/documentation') { - from configurations.allAsciidoc - from tasks.named('asciidoctor') - from tasks.named('asciidoctorPdf') - exclude '.asciidoctor' + if (sharedConfiguration.isDocumentationBuild) { + into('html/documentation') { + from configurations.allAsciidoc + from tasks.named('asciidoctor') + from tasks.named('asciidoctorPdf') + exclude '.asciidoctor' + } } into('html/groovy-jdk') { from tasks.named('docGDK') diff --git a/subprojects/groovy-groovydoc/build.gradle b/subprojects/groovy-groovydoc/build.gradle index 0ec415ae780..0ee1d02a598 100644 --- a/subprojects/groovy-groovydoc/build.gradle +++ b/subprojects/groovy-groovydoc/build.gradle @@ -38,7 +38,7 @@ dependencies { compileJava { doLast { - mkdir "${sourceSets.main.java.classesDirectory.get().asFile}/META-INF" + new File(destinationDirectory.get().asFile, 'META-INF').mkdirs() } }