lucene/gradle/java/modules.gradle

260 lines
13 KiB
Groovy

/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Configure miscellaneous aspects required for supporting the java module system layer.
// Debugging utilities.
apply from: buildscript.sourceFile.toPath().resolveSibling("modules-debugging.gradle")
allprojects {
plugins.withType(JavaPlugin) {
// We won't be using gradle's built-in automatic module finder.
java {
modularity.inferModulePath.set(false)
}
// Map convention configuration names to "modular" corresponding configurations.
Closure<String> moduleConfigurationNameFor = { String configurationName ->
return "module" + configurationName.capitalize().replace("Classpath", "Path")
}
//
// For each source set, create explicit configurations for declaring modular dependencies.
// These "modular" configurations correspond 1:1 to Gradle's conventions but have a 'module' prefix
// and a capitalized remaining part of the conventional name. For example, an 'api' configuration in
// the main source set would have a corresponding 'moduleApi' configuration for declaring modular
// dependencies.
//
// Gradle's java plugin "convention" configurations extend from their modular counterparts
// so all dependencies end up on classpath by default for backward compatibility with other
// tasks and gradle infrastructure.
//
// At the same time, we also know which dependencies (and their transitive graph of dependencies!)
// should be placed on module-path only.
//
// Note that an explicit configuration of modular dependencies also opens up the possibility of automatically
// validating whether the dependency configuration for a gradle project is consistent with the information in
// the module-info descriptor because there is a (nearly?) direct correspondence between the two:
//
// moduleApi - 'requires transitive'
// moduleImplementation - 'requires'
// moduleCompileOnly - 'requires static'
//
project.sourceSets.all { SourceSet sourceSet ->
ConfigurationContainer configurations = project.configurations
// Create modular configurations for convention configurations.
Closure<Configuration> createModuleConfigurationForConvention = { String configurationName ->
Configuration conventionConfiguration = configurations.maybeCreate(configurationName)
Configuration moduleConfiguration = configurations.maybeCreate(moduleConfigurationNameFor(configurationName))
moduleConfiguration.canBeConsumed(false)
moduleConfiguration.canBeResolved(false)
conventionConfiguration.extendsFrom(moduleConfiguration)
project.logger.info("Created module configuration for '${conventionConfiguration.name}': ${moduleConfiguration.name}")
return moduleConfiguration
}
Configuration moduleApi = createModuleConfigurationForConvention(sourceSet.apiConfigurationName)
Configuration moduleImplementation = createModuleConfigurationForConvention(sourceSet.implementationConfigurationName)
moduleImplementation.extendsFrom(moduleApi)
Configuration moduleRuntimeOnly = createModuleConfigurationForConvention(sourceSet.runtimeOnlyConfigurationName)
Configuration moduleCompileOnly = createModuleConfigurationForConvention(sourceSet.compileOnlyConfigurationName)
// sourceSet.compileOnlyApiConfigurationName // This seems like a very esoteric use case, leave out.
// Set up compilation module path configuration combining corresponding convention configurations.
Closure<Configuration> createResolvableModuleConfiguration = { String configurationName ->
Configuration conventionConfiguration = configurations.maybeCreate(configurationName)
Configuration moduleConfiguration = configurations.maybeCreate(
moduleConfigurationNameFor(conventionConfiguration.name))
moduleConfiguration.canBeConsumed(false)
moduleConfiguration.canBeResolved(true)
moduleConfiguration.attributes {
// Prefer class folders over JARs. The exception is made for tests projects which require a composition
// of classes and resources, otherwise split into two folders.
if (project.name.endsWith(".tests")) {
attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, objects.named(LibraryElements, LibraryElements.JAR))
} else {
attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, objects.named(LibraryElements, LibraryElements.CLASSES))
}
}
project.logger.info("Created resolvable module configuration for '${conventionConfiguration.name}': ${moduleConfiguration.name}")
return moduleConfiguration
}
Configuration compileModulePathConfiguration = createResolvableModuleConfiguration(sourceSet.compileClasspathConfigurationName)
compileModulePathConfiguration.extendsFrom(moduleCompileOnly, moduleImplementation)
Configuration runtimeModulePathConfiguration = createResolvableModuleConfiguration(sourceSet.runtimeClasspathConfigurationName)
runtimeModulePathConfiguration.extendsFrom(moduleRuntimeOnly, moduleImplementation)
// Create and register a source set extension for manipulating classpath/ module-path
ModularPathsExtension modularPaths = new ModularPathsExtension(project, sourceSet,
compileModulePathConfiguration,
runtimeModulePathConfiguration)
sourceSet.extensions.add("modularPaths", modularPaths)
// Customized the JavaCompile for this source set so that it has proper module path.
tasks.named(sourceSet.getCompileJavaTaskName()).configure({ JavaCompile task ->
task.dependsOn modularPaths.compileModulePathConfiguration
// LUCENE-10327: don't allow gradle to emit an empty sourcepath as it would break compilation.
task.options.setSourcepath(sourceSet.java.sourceDirectories)
// Add modular dependencies and their transitive dependencies to module path.
task.options.compilerArgumentProviders.add((CommandLineArgumentProvider) {
def modularPathFiles = modularPaths.compileModulePathConfiguration.files
def extraArgs = []
if (!modularPathFiles.isEmpty()) {
if (!modularPaths.hasModuleDescriptor()) {
// We're compiling a non-module so we'll bring everything on module path in
// otherwise things wouldn't be part of the resolved module graph.
extraArgs += ["--add-modules", "ALL-MODULE-PATH"]
}
extraArgs += ["--module-path", modularPathFiles.join(File.pathSeparator)]
}
task.logger.info("Module path for ${task.path}:\n " + modularPathFiles.sort().join("\n "))
return extraArgs
})
// LUCENE-10304: if we modify the classpath here, IntelliJ no longer sees the dependencies as compile-time
// dependencies, don't know why.
if (!rootProject.ext.isIdea) {
// Modify the default classpath by removing anything already placed on module path.
// This could be done in a fancier way but a set difference is just fine for us here. Use a lazy
// provider to delay computation of the actual path.
task.classpath = files({ ->
def trimmedClasspath = sourceSet.compileClasspath - modularPaths.compileModulePathConfiguration
task.logger.info("Class path for ${task.path}:\n " + trimmedClasspath.files.sort().join("\n "))
return trimmedClasspath
})
}
})
}
//
// Configure the (default) test task to use module paths.
//
// There is no explicit connection between source sets and test tasks so there is no way (?)
// to do this automatically, convention-style.
// This closure can be used to configure a different task, with a different source set, should we
// have the need for it.
Closure<Void> configureTestTaskForSourceSet = { Test task, SourceSet sourceSet ->
task.configure {
Configuration modulePath = task.project.configurations.maybeCreate(
moduleConfigurationNameFor(sourceSet.getRuntimeClasspathConfigurationName()))
task.dependsOn modulePath
// Add modular dependencies and their transitive dependencies to module path.
task.jvmArgumentProviders.add((CommandLineArgumentProvider) {
def extraArgs = []
// Determine whether the source set classes themselves should be appended
// to classpath or module path.
boolean sourceSetIsAModule = sourceSet.modularPaths.hasModuleDescriptor()
if (!modulePath.isEmpty() || sourceSetIsAModule) {
if (sourceSetIsAModule) {
// Add source set outputs to module path.
extraArgs += ["--module-path", (modulePath + sourceSet.output.classesDirs).files.join(File.pathSeparator)]
// Ideally, we should only add the sourceset's module here, everything else would be resolved via the
// module descriptor. But this would require parsing the module descriptor and may cause JVM version conflicts
// so keeping it simple.
extraArgs += ["--add-modules", "ALL-MODULE-PATH"]
} else {
extraArgs += ["--module-path", modulePath.files.join(File.pathSeparator)]
// In this case we're running a non-module against things on the module path so let's bring in
// everything on module path into the resolution graph.
extraArgs += ["--add-modules", "ALL-MODULE-PATH"]
}
}
task.logger.info("Module path for ${task.path}:\n " + modulePath.files.sort().join("\n "))
return extraArgs
})
// Modify the default classpath by removing anything already placed on module path.
// This could be done in a fancier way but a set difference is just fine for us here. Use a lazy
// provider to delay computation of the actual path.
task.classpath = files({ ->
def trimmedClasspath = sourceSet.runtimeClasspath - modulePath
boolean sourceSetIsAModule = sourceSet.modularPaths.hasModuleDescriptor()
if (sourceSetIsAModule) {
// also subtract the sourceSet's output directories.
trimmedClasspath = trimmedClasspath - sourceSet.output.classesDirs
}
task.logger.info("Class path for ${task.path}:\n " + trimmedClasspath.files.sort().join("\n "))
return trimmedClasspath
})
}
}
// Configure (tasks.test, sourceSets.test)
tasks.matching { it.name ==~ /test(_[0-9]+)?/ }.all { Test task ->
configureTestTaskForSourceSet(task, task.project.sourceSets.test)
}
// Configure module versions.
tasks.withType(JavaCompile).configureEach { task ->
// TODO: LUCENE-10267: workaround for gradle bug. Remove when the corresponding issue is fixed.
task.options.compilerArgumentProviders.add((CommandLineArgumentProvider) { ->
if (task.getClasspath().isEmpty()) {
return ["--module-version", project.version.toString()]
} else {
return []
}
})
task.options.javaModuleVersion.set(provider {
return project.version.toString()
})
}
}
}
class ModularPathsExtension {
Project project
SourceSet sourceSet
Configuration compileModulePathConfiguration
Configuration runtimeModulePathConfiguration
ModularPathsExtension(Project project, SourceSet sourceSet,
Configuration compileModulePathConfiguration,
Configuration runtimeModulePathConfiguration) {
this.project = project
this.sourceSet = sourceSet
this.compileModulePathConfiguration = compileModulePathConfiguration
this.runtimeModulePathConfiguration = runtimeModulePathConfiguration
}
boolean hasModuleDescriptor() {
return sourceSet.allJava.srcDirs.stream()
.map(dir -> new File(dir, "module-info.java"))
.anyMatch(file -> file.exists())
}
}