/* * 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. 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 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 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 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 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" }.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()) } }