forked from apache/lucene
-
Notifications
You must be signed in to change notification settings - Fork 0
/
modules.gradle
466 lines (393 loc) · 19 KB
/
modules.gradle
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
/*
* 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)
}
// More verbose debugging for paths.
boolean debugPaths = Boolean.parseBoolean(propertyOrDefault('build.debug.paths', 'false'))
//
// Configure modular extensions for each source set.
//
project.sourceSets.all { SourceSet sourceSet ->
// Create and register a source set extension for manipulating classpath/ module-path
ModularPathsExtension modularPaths = new ModularPathsExtension(project, sourceSet)
sourceSet.extensions.add("modularPaths", modularPaths)
// TODO: the tests of these projects currently don't compile or work in
// module-path mode. Make the modular paths extension use class path only.
if (sourceSet.name == "test" && project.path in [
// Circular dependency between artifacts or source set outputs,
// causing package split issues at runtime.
":lucene:core",
":lucene:codecs",
":lucene:test-framework",
]) {
modularPaths.mode = ModularPathsExtension.Mode.CLASSPATH_ONLY
}
// Configure the JavaCompile task associated with this source set.
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 of modules.
task.options.setSourcepath(sourceSet.java.sourceDirectories)
// Add modular dependencies and their transitive dependencies to module path.
task.options.compilerArgumentProviders.add(modularPaths.compilationArguments)
// 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) {
task.classpath = modularPaths.compilationClasspath
}
doFirst {
if (debugPaths) {
logger.lifecycle(modularPaths.compilationPathsToString())
} else {
logger.info(modularPaths.compilationPathsToString())
}
}
})
// For source sets that contain a module descriptor, configure a jar task that combines
// classes and resources into a single module.
if (modularPaths.hasModuleDescriptor()) {
tasks.maybeCreate(sourceSet.getJarTaskName(), org.gradle.jvm.tasks.Jar).configure({
from(sourceSet.output)
if (sourceSet.name != SourceSet.MAIN_SOURCE_SET_NAME) {
archiveClassifier = sourceSet.name
}
})
}
}
// Connect modular configurations between their "test" and "main" source sets, this reflects
// the conventions set by the Java plugin.
project.configurations {
moduleTestApi.extendsFrom moduleApi
moduleTestImplementation.extendsFrom moduleImplementation
moduleTestRuntimeOnly.extendsFrom moduleRuntimeOnly
moduleTestCompileOnly.extendsFrom moduleCompileOnly
}
// Gradle's java plugin sets the compile and runtime classpath to be a combination
// of configuration dependencies and source set's outputs. For source sets with modules,
// this leads to split class and resource folders.
//
// We tweak the default source set path configurations here by assembling jar task outputs
// of the respective source set, instead of their source set output folders. We also attach
// the main source set's jar to the modular test implementation configuration.
SourceSet mainSourceSet = project.sourceSets.getByName(SourceSet.MAIN_SOURCE_SET_NAME)
boolean mainIsModular = mainSourceSet.modularPaths.hasModuleDescriptor()
SourceSet testSourceSet = project.sourceSets.getByName(SourceSet.TEST_SOURCE_SET_NAME)
boolean testIsModular = testSourceSet.modularPaths.hasModuleDescriptor()
if (testIsModular) {
// 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) {
def jarTask = project.tasks.getByName(mainSourceSet.getJarTaskName())
def testJarTask = project.tasks.getByName(testSourceSet.getJarTaskName())
testSourceSet.compileClasspath = project.objects.fileCollection().from(
jarTask.outputs,
project.configurations.getByName(testSourceSet.getCompileClasspathConfigurationName()),
)
testSourceSet.runtimeClasspath = project.objects.fileCollection().from(
jarTask.outputs,
testJarTask.outputs,
project.configurations.getByName(testSourceSet.getRuntimeClasspathConfigurationName()),
)
project.dependencies {
moduleTestImplementation files(jarTask.outputs)
moduleTestRuntimeOnly files(testJarTask.outputs)
}
}
}
//
// Configures a Test task associated with the provided source set 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 {
ModularPathsExtension modularPaths = sourceSet.modularPaths
dependsOn modularPaths.runtimeModulePathConfiguration
// Add modular dependencies and their transitive dependencies to module path.
jvmArgumentProviders.add(modularPaths.runtimeArguments)
// Modify the default classpath.
classpath = modularPaths.runtimeClasspath
doFirst {
if (debugPaths) {
logger.lifecycle(modularPaths.runtimePathsToString())
} else {
logger.info(modularPaths.runtimePathsToString())
}
}
}
}
// Lazily 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()
})
}
}
}
//
// For a 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'
//
class ModularPathsExtension {
/**
* Determines how paths are split between module path and classpath.
*/
enum Mode {
/**
* Dependencies and source set outputs are placed on classpath, even if declared on modular
* configurations. This would be the 'default' backward-compatible mode.
*/
CLASSPATH_ONLY,
/**
* Dependencies from modular configurations are placed on module path. Source set outputs
* are placed on classpath.
*/
DEPENDENCIES_ON_MODULE_PATH
}
Project project
SourceSet sourceSet
Configuration compileModulePathConfiguration
Configuration runtimeModulePathConfiguration
Configuration modulePatchOnlyConfiguration
// The mode of splitting paths for this source set.
Mode mode = Mode.DEPENDENCIES_ON_MODULE_PATH
/**
* A list of module name - path provider entries that will be converted
* into {@code --patch-module} options.
*/
private List<Map.Entry<String, Provider<Path>>> modulePatches = new ArrayList<>()
ModularPathsExtension(Project project, SourceSet sourceSet) {
this.project = project
this.sourceSet = sourceSet
ConfigurationContainer configurations = project.configurations
// Create modular configurations for gradle's java plugin convention configurations.
Configuration moduleApi = createModuleConfigurationForConvention(sourceSet.apiConfigurationName)
Configuration moduleImplementation = createModuleConfigurationForConvention(sourceSet.implementationConfigurationName)
Configuration moduleRuntimeOnly = createModuleConfigurationForConvention(sourceSet.runtimeOnlyConfigurationName)
Configuration moduleCompileOnly = createModuleConfigurationForConvention(sourceSet.compileOnlyConfigurationName)
// Apply hierarchy relationships to modular configurations.
moduleImplementation.extendsFrom(moduleApi)
// Patched modules have to end up in the implementation configuration for IDEs, which
// otherwise get terribly confused.
Configuration modulePatchOnly = createModuleConfigurationForConvention(
SourceSet.isMain(sourceSet) ? "patchOnly" : sourceSet.name + "PatchOnly")
modulePatchOnly.canBeResolved(true)
moduleImplementation.extendsFrom(modulePatchOnly)
this.modulePatchOnlyConfiguration = modulePatchOnly
// This part of convention configurations seems like a very esoteric use case, leave out for now.
// sourceSet.compileOnlyApiConfigurationName
// We have to ensure configurations are using assembled resources and classes (jar variant) as a single
// module can't be expanded into multiple folders.
Closure<Void> ensureJarVariant = { Configuration c ->
c.attributes.attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, project.objects.named(LibraryElements, LibraryElements.JAR))
}
// 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)
ensureJarVariant(moduleConfiguration)
project.logger.info("Created resolvable module configuration for '${conventionConfiguration.name}': ${moduleConfiguration.name}")
return moduleConfiguration
}
ensureJarVariant(configurations.maybeCreate(sourceSet.compileClasspathConfigurationName))
ensureJarVariant(configurations.maybeCreate(sourceSet.runtimeClasspathConfigurationName))
this.compileModulePathConfiguration = createResolvableModuleConfiguration(sourceSet.compileClasspathConfigurationName)
compileModulePathConfiguration.extendsFrom(moduleCompileOnly, moduleImplementation)
this.runtimeModulePathConfiguration = createResolvableModuleConfiguration(sourceSet.runtimeClasspathConfigurationName)
runtimeModulePathConfiguration.extendsFrom(moduleRuntimeOnly, moduleImplementation)
}
/**
* Adds {@code --patch-module} option for the provided module name and the provider of a
* folder or JAR file.
*
* @param moduleName
* @param pathProvider
*/
void patchModule(String moduleName, Provider<Path> pathProvider) {
modulePatches.add(Map.entry(moduleName, pathProvider));
}
private FileCollection getCompilationModulePath() {
if (mode == Mode.CLASSPATH_ONLY) {
return project.files()
}
return compileModulePathConfiguration - modulePatchOnlyConfiguration
}
private FileCollection getRuntimeModulePath() {
if (mode == Mode.CLASSPATH_ONLY) {
if (hasModuleDescriptor()) {
// The source set is itself a module.
throw new GradleException("Source set contains a module but classpath-only" +
" dependencies requested: ${project.path}, source set '${sourceSet.name}'")
}
return project.files()
}
return runtimeModulePathConfiguration - modulePatchOnlyConfiguration
}
FileCollection getCompilationClasspath() {
if (mode == Mode.CLASSPATH_ONLY) {
return sourceSet.compileClasspath
}
// Modify the default classpath by removing anything already placed on module path.
// Use a lazy provider to delay computation.
project.files({ ->
return sourceSet.compileClasspath - compileModulePathConfiguration - modulePatchOnlyConfiguration
})
}
CommandLineArgumentProvider getCompilationArguments() {
return new CommandLineArgumentProvider() {
@Override
Iterable<String> asArguments() {
FileCollection modulePath = ModularPathsExtension.this.compilationModulePath
if (modulePath.isEmpty()) {
return []
}
ArrayList<String> extraArgs = []
extraArgs += ["--module-path", modulePath.join(File.pathSeparator)]
if (!hasModuleDescriptor()) {
// We're compiling what appears to be a non-module source set so we'll
// bring everything on module path in the resolution graph,
// otherwise modular dependencies wouldn't be part of the resolved module graph and this
// would result in class-not-found compilation problems.
extraArgs += ["--add-modules", "ALL-MODULE-PATH"]
}
// Add module-patching.
extraArgs += getPatchModuleArguments(modulePatches)
return extraArgs
}
}
}
FileCollection getRuntimeClasspath() {
if (mode == Mode.CLASSPATH_ONLY) {
return sourceSet.runtimeClasspath
}
// Modify the default classpath by removing anything already placed on module path.
// Use a lazy provider to delay computation.
project.files({ ->
return sourceSet.runtimeClasspath - runtimeModulePath - modulePatchOnlyConfiguration
})
}
CommandLineArgumentProvider getRuntimeArguments() {
return new CommandLineArgumentProvider() {
@Override
Iterable<String> asArguments() {
FileCollection modulePath = ModularPathsExtension.this.runtimeModulePath
if (modulePath.isEmpty()) {
return []
}
def extraArgs = []
// Add source set outputs to module path.
extraArgs += ["--module-path", modulePath.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"]
// Add module-patching.
extraArgs += getPatchModuleArguments(modulePatches)
return extraArgs
}
}
}
boolean hasModuleDescriptor() {
return sourceSet.allJava.srcDirs.stream()
.map(dir -> new File(dir, "module-info.java"))
.anyMatch(file -> file.exists())
}
private List<String> getPatchModuleArguments(List<Map.Entry<String, Provider<Path>>> patches) {
def args = []
patches.each {
args.add("--patch-module");
args.add(it.key + "=" + it.value.get())
}
return args
}
private static String toList(FileCollection files) {
return files.isEmpty() ? " [empty]" : ("\n " + files.sort().join("\n "))
}
private static String toList(List<Map.Entry<String, Provider<Path>>> patches) {
return patches.isEmpty() ? " [empty]" : ("\n " + patches.collect {"${it.key}=${it.value.get()}"}.join("\n "))
}
String compilationPathsToString() {
return "Modular extension, compilation paths, source set=${sourceSet.name}${hasModuleDescriptor() ? " (module)" : ""}, mode=${mode}:\n" +
" Module path:${toList(compilationModulePath)}\n" +
" Class path: ${toList(compilationClasspath)}\n" +
" Patches: ${toList(modulePatches)}"
}
String runtimePathsToString() {
return "Modular extension, runtime paths, source set=${sourceSet.name}${hasModuleDescriptor() ? " (module)" : ""}, mode=${mode}:\n" +
" Module path:${toList(runtimeModulePath)}\n" +
" Class path: ${toList(runtimeClasspath)}\n" +
" Patches : ${toList(modulePatches)}"
}
// Map convention configuration names to "modular" corresponding configurations.
static String moduleConfigurationNameFor(String configurationName) {
return "module" + configurationName.capitalize().replace("Classpath", "Path")
}
// Create module configuration for the corresponding convention configuration.
private Configuration createModuleConfigurationForConvention(String configurationName) {
ConfigurationContainer configurations = project.configurations
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
}
}