diff --git a/build-tools/build-infra/src/main/groovy/lucene.java.modules.gradle b/build-tools/build-infra/src/main/groovy/lucene.java.modules.gradle deleted file mode 100644 index 0b5b7f4fa3a8..000000000000 --- a/build-tools/build-infra/src/main/groovy/lucene.java.modules.gradle +++ /dev/null @@ -1,612 +0,0 @@ -/* - * 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. - */ - - -import java.nio.charset.StandardCharsets -import java.nio.file.Files -import java.nio.file.Path -import org.apache.lucene.gradle.plugins.java.ModularPathsExtensionApi - -// Configures miscellaneous aspects required to support the java module system layer. - -allprojects { - plugins.withType(JavaPlugin).configureEach { - // We won't be using gradle's built-in automatic module finder. - java { - modularity.inferModulePath.set(false) - } - - // - // Configure modular extensions for each source set. - // - project.sourceSets.configureEach { 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) - - // LUCENE-10344: We have to provide a special-case extension for ECJ because it does not - // support all of the module-specific javac options. - ModularPathsExtension modularPathsForEcj = modularPaths - if (sourceSet.name == SourceSet.TEST_SOURCE_SET_NAME && project.path in [ - ":lucene:spatial-extras", - ":lucene:spatial3d", - ]) { - modularPathsForEcj = modularPaths.cloneWithMode(ModularPathsExtension.Mode.CLASSPATH_ONLY) - } - sourceSet.extensions.add("modularPathsForEcj", modularPathsForEcj) - - // 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 == SourceSet.TEST_SOURCE_SET_NAME && 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 - - // GH-12742: add the modular path as inputs so that if anything changes, the task - // is not up to date and is re-run. I [dw] believe this should be a @Classpath parameter - // on the task itself... but I don't know how to implement this on an existing class. - // this is a workaround but should work just fine though. - task.inputs.files(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 (!buildGlobals.intellijIdea.get().isIdeaSync) { - task.classpath = modularPaths.compilationClasspath - } - - doFirst { - modularPaths.logCompilationPaths(logger) - } - }) - - // For source sets that contain a module descriptor, configure a jar task that combines - // classes and resources into a single module. - if (sourceSet.name != SourceSet.MAIN_SOURCE_SET_NAME) { - tasks.maybeCreate(sourceSet.getJarTaskName(), org.gradle.jvm.tasks.Jar).configure({ - archiveClassifier = sourceSet.name - from(sourceSet.output) - }) - } - } - - // 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() - boolean mainIsEmpty = mainSourceSet.allJava.isEmpty() - SourceSet testSourceSet = project.sourceSets.getByName(SourceSet.TEST_SOURCE_SET_NAME) - boolean testIsModular = testSourceSet.modularPaths.hasModuleDescriptor() - - // LUCENE-10304: if we modify the classpath here, IntelliJ no longer sees the dependencies as compile-time - // dependencies, don't know why. - if (!buildGlobals.intellijIdea.get().isIdeaSync) { - def jarTask = project.tasks.getByName(mainSourceSet.getJarTaskName()) - def testJarTask = project.tasks.getByName(testSourceSet.getJarTaskName()) - - // Consider various combinations of module/classpath configuration between the main and test source set. - if (testIsModular) { - if (mainIsModular || mainIsEmpty) { - // If the main source set is empty, skip the jar task. - def jarTaskOutputs = mainIsEmpty ? [] : jarTask.outputs - - // Fully modular tests - must have no split packages, proper access, etc. - // Work around the split classes/resources problem by adjusting classpaths to - // rely on JARs rather than source set output folders. - testSourceSet.compileClasspath = project.objects.fileCollection().from( - jarTaskOutputs, - project.configurations.getByName(testSourceSet.getCompileClasspathConfigurationName()), - ) - testSourceSet.runtimeClasspath = project.objects.fileCollection().from( - jarTaskOutputs, - testJarTask.outputs, - project.configurations.getByName(testSourceSet.getRuntimeClasspathConfigurationName()), - ) - - project.dependencies { - moduleTestImplementation files(jarTaskOutputs) - moduleTestRuntimeOnly files(testJarTask.outputs) - } - } else { - // This combination simply does not make any sense (in my opinion). - throw new GradleException("Test source set is modular and main source set is class-based, this makes no sense: " + project.path) - } - } else { - if (mainIsModular) { - // This combination is a potential candidate for patching the main sourceset's module with test classes. I could - // not resolve all the difficulties that arise when you try to do it though: - // - either a separate module descriptor is needed that opens test packages, adds dependencies via requires clauses - // or a series of jvm arguments (--add-reads, --add-opens, etc.) has to be generated and maintained. This is - // very low-level (ECJ doesn't support a full set of these instructions, for example). - // - // Fall back to classpath mode. - } else { - // This is the 'plain old classpath' mode: neither the main source set nor the test set are modular. - } - } - } - - // - // 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 configureTestTaskForSourceSet = { Test task, SourceSet sourceSet -> - task.configure { - def forkProperties = file("${task.temporaryDir}/jvm-forking.properties") - - ModularPathsExtension modularPaths = sourceSet.modularPaths - - dependsOn modularPaths - - // Add modular dependencies and their transitive dependencies to module path. - jvmArgumentProviders.add(modularPaths.runtimeArguments) - - // Modify the default classpath. - classpath = modularPaths.testRuntimeClasspath - - doFirst { - modularPaths.logRuntimePaths(logger) - } - - // Pass all the required properties for tests which fork the JVM. We don't use - // regular system properties here because this could affect task up-to-date checks. - jvmArgumentProviders.add(new CommandLineArgumentProvider() { - @Override - Iterable asArguments() { - return [ - "-Dtests.jvmForkArgsFile=" + forkProperties.absolutePath - ] - } - }) - doFirst { - List args = new ArrayList<>(modularPaths.runtimeArguments.asArguments().collect()) - def cp = modularPaths.runtimeClasspath.asPath - if (!cp.isBlank()) { - args.addAll(["-cp", cp]) - } - - // Sanity check. - args.forEach({s -> - if (s.contains("\n")) { - throw new RuntimeException("LF in forked jvm property?: " + s) - } - }) - - Files.createDirectories(forkProperties.toPath().getParent()) - Files.writeString(forkProperties.toPath(), String.join("\n", args), StandardCharsets.UTF_8) - } - } - } - - // Configure (tasks.test, sourceSets.test) - tasks.matching { it.name ==~ /test(_[0-9]+)?/ }.configureEach { 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. - String projectVersion = project.version.toString() - task.options.compilerArgumentProviders.add((CommandLineArgumentProvider) { - -> - if (task.getClasspath().isEmpty()) { - return [ - "--module-version", - projectVersion - ] - } else { - return [] - } - }) - - task.options.javaModuleVersion.set(projectVersion) - } - } -} - - -// -// 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 implements Cloneable, Iterable, ModularPathsExtensionApi { - /** - * 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 - - // More verbose debugging for paths. - private boolean debugPaths - - /** - * A list of module name - path provider entries that will be converted - * into {@code --patch-module} options. - */ - private List>> modulePatches = new ArrayList<>() - - ModularPathsExtension(Project project, SourceSet sourceSet) { - this.project = project - this.sourceSet = sourceSet - - // enable to debug paths. - debugPaths = false - - 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 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 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 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 removeNonExisting(FileCollection fc) { - return fc.filter { f -> f.exists() } - } - - FileCollection getCompilationClasspath() { - if (mode == Mode.CLASSPATH_ONLY) { - return removeNonExisting(sourceSet.compileClasspath) - } - - // Modify the default classpath by removing anything already placed on module path. - // Use a lazy provider to delay computation. - return removeNonExisting(project.files({ - -> - return sourceSet.compileClasspath - compileModulePathConfiguration - modulePatchOnlyConfiguration - })) - } - - CommandLineArgumentProvider getCompilationArguments() { - return new CommandLineArgumentProvider() { - @Override - Iterable asArguments() { - FileCollection modulePath = ModularPathsExtension.this.compilationModulePath - - if (modulePath.isEmpty()) { - return [] - } - - ArrayList 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 - }) - } - - /** - * Returns the runtime classpath for test tasks. - * - * The plain runtimeClassspath would also filter out junit dependencies, which must - * be on the Test.classpath as the gradle test worker depends on it. - * Not having junit explicitly on the classpath would result in a deprecation warning - * about junit not being on the classpath and indirectly added by gradle internal logic. - */ - FileCollection getTestRuntimeClasspath() { - 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 + - sourceSet.runtimeClasspath.filter {file -> file.name.contains("junit")} - }) - } - - CommandLineArgumentProvider getRuntimeArguments() { - return new CommandLineArgumentProvider() { - @Override - Iterable 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 getPatchModuleArguments(List>> 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>> patches) { - return patches.isEmpty() ? " [empty]" : ("\n " + patches.collect {"${it.key}=${it.value.get()}"}.join("\n ")) - } - - public void logCompilationPaths(Logger logger) { - def value = "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)}" - - if (debugPaths) { - logger.lifecycle(value) - } else { - logger.info(value) - } - } - - public void logRuntimePaths(Logger logger) { - def value = "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)}" - - if (debugPaths) { - logger.lifecycle(value) - } else { - logger.info(value) - } - } - - public ModularPathsExtension clone() { - return (ModularPathsExtension) super.clone() - } - - ModularPathsExtension cloneWithMode(Mode newMode) { - def cloned = this.clone() - cloned.mode = newMode - return cloned - } - - // 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 - } - - /** - * Provide internal dependencies for tasks willing to depend on this modular paths object. - */ - @Override - Iterator iterator() { - return [ - compileModulePathConfiguration, - runtimeModulePathConfiguration - ].iterator() - } -} diff --git a/build-tools/build-infra/src/main/groovy/lucene.misc.pylucene.gradle b/build-tools/build-infra/src/main/groovy/lucene.misc.pylucene.gradle deleted file mode 100644 index 23253e5aecd4..000000000000 --- a/build-tools/build-infra/src/main/groovy/lucene.misc.pylucene.gradle +++ /dev/null @@ -1,40 +0,0 @@ -/* - * 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. - */ - -/* - * This script configures some helper tasks used by the sibling PyLucene - * project. - */ - -configure(rootProject.ext.mavenProjects, { - plugins.withType(JavaPlugin).configureEach { - /** - * Collect the module's primary artifact and any runtime JARs it requires. - */ - tasks.register("collectRuntimeJars", Sync, { - // Collect our own artifact. - from jar.outputs - - // Collect all dependencies, excluding cross-module deps. - from(configurations.runtimeClasspath, { - exclude "lucene-*" - }) - - into project.layout.buildDirectory.file("runtimeJars") - }) - } -}) diff --git a/build-tools/build-infra/src/main/groovy/lucene.publications.maven-to-local-m2.gradle b/build-tools/build-infra/src/main/groovy/lucene.publications.maven-to-local-m2.gradle deleted file mode 100644 index 44efa9d63459..000000000000 --- a/build-tools/build-infra/src/main/groovy/lucene.publications.maven-to-local-m2.gradle +++ /dev/null @@ -1,30 +0,0 @@ -/* - * 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 on-demand maven publishing into ~/.m2 for developers' convenience. - -configure(rootProject) { - tasks.register("mavenToLocal", { - group = "Distribution" - description = "Publish Lucene Maven artifacts to ~/.m2 repository." - - dependsOn rootProject.ext.mavenProjects.collect { - it.tasks.matching { it.name == "publishJarsPublicationToMavenLocal" } - } - }) -} diff --git a/build-tools/build-infra/src/main/groovy/lucene.publications.maven-to-nexus-releases.gradle b/build-tools/build-infra/src/main/groovy/lucene.publications.maven-to-nexus-releases.gradle deleted file mode 100644 index f4204e8f7fce..000000000000 --- a/build-tools/build-infra/src/main/groovy/lucene.publications.maven-to-nexus-releases.gradle +++ /dev/null @@ -1,82 +0,0 @@ -/* - * 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. - */ - -import org.apache.lucene.gradle.plugins.globals.LuceneBuildGlobalsExtension - -// Configure artifact push to apache nexus (releases repository). -def apacheNexusReleasesRepository = "https://repository.apache.org/service/local/staging/deploy/maven2" - -// These access credentials must be passed by the release manager -// (either on command-line, via the environment or via ~/.gradle.properties). -Provider asfNexusUsername = providers.gradleProperty("asfNexusUsername") - .orElse(providers.systemProperty("asfNexusUsername")) - .orElse(providers.environmentVariable("ASF_NEXUS_USERNAME")) - -Provider asfNexusPassword = providers.gradleProperty("asfNexusPassword") - .orElse(providers.systemProperty("asfNexusPassword")) - .orElse(providers.environmentVariable("ASF_NEXUS_PASSWORD")) - -tasks.register("mavenToApacheReleases", { - group = "Distribution" - description = "Publish Lucene Maven artifacts to Apache Releases repository: ${apacheNexusReleasesRepository}" - - dependsOn rootProject.ext.mavenProjects.collect { - it.tasks.matching { it.name == "publishSignedJarsPublicationToApacheReleasesRepository" } - } -}) - -LuceneBuildGlobalsExtension buildGlobals = rootProject.extensions.getByType(LuceneBuildGlobalsExtension) - -def checkReleasesRepositoryPushPreconditions = tasks.register("checkReleasesRepositoryPushPreconditions", { - doFirst { - // Make sure we're pushing a release version. The release repository - // does not accept snapshots and returns cryptic errors upon trying. - if (buildGlobals.snapshotBuild) { - throw new GradleException("ASF releases repository will not accept a snapshot version: ${rootProject.version}") - } - - // Make sure access credentials have been passed. - if (!asfNexusUsername.isPresent() || !asfNexusPassword.isPresent()) { - throw new GradleException("asfNexusUsername or asfNexusPassword is empty: these are required to publish to " + - " ASF Nexus.") - } - } -}) - -configure(rootProject.ext.mavenProjects) { Project project -> - // Make sure any actual publication task is preceded by precondition checks. - tasks.matching { it.name ==~ /publish.+ToApacheReleasesRepository/ }.configureEach { - dependsOn checkReleasesRepositoryPushPreconditions - } - - // Configure the release repository. - plugins.withType(PublishingPlugin).configureEach { - publishing { - repositories { - maven { - name = "ApacheReleases" - url = apacheNexusReleasesRepository - - credentials { - username = asfNexusUsername.getOrElse(null) - password = asfNexusPassword.getOrElse(null) - } - } - } - } - } -} diff --git a/build-tools/build-infra/src/main/groovy/lucene.publications.maven-to-nexus-snapshots.gradle b/build-tools/build-infra/src/main/groovy/lucene.publications.maven-to-nexus-snapshots.gradle deleted file mode 100644 index 9fbeb5772849..000000000000 --- a/build-tools/build-infra/src/main/groovy/lucene.publications.maven-to-nexus-snapshots.gradle +++ /dev/null @@ -1,80 +0,0 @@ -/* - * 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. - */ - -import org.apache.lucene.gradle.plugins.globals.LuceneBuildGlobalsExtension - -// Configure artifact push to apache nexus (snapshots repository, CI job) -def apacheNexusSnapshotsRepository = "https://repository.apache.org/content/repositories/snapshots" - -// These access credentials must be passed by the release manager -// (either on command-line, via the environment or via ~/.gradle.properties). -Provider asfNexusUsername = providers.gradleProperty("asfNexusUsername") - .orElse(providers.systemProperty("asfNexusUsername")) - .orElse(providers.environmentVariable("ASF_NEXUS_USERNAME")) - -Provider asfNexusPassword = providers.gradleProperty("asfNexusPassword") - .orElse(providers.systemProperty("asfNexusPassword")) - .orElse(providers.environmentVariable("ASF_NEXUS_PASSWORD")) - -tasks.register("mavenToApacheSnapshots", { - group = "Distribution" - description = "Publish Lucene Maven artifacts to Apache Snapshots repository: ${apacheNexusSnapshotsRepository}" - - dependsOn rootProject.ext.mavenProjects.collect { - it.tasks.matching { it.name == "publishJarsPublicationToApacheSnapshotsRepository" } - } -}) - -LuceneBuildGlobalsExtension buildGlobals = rootProject.extensions.getByType(LuceneBuildGlobalsExtension) - -def checkSnapshotsRepositoryPushPreconditions = tasks.register("checkSnapshotsRepositoryPushPreconditions", { - doFirst { - // Make sure we're pushing a snapshot version. - if (!buildGlobals.snapshotBuild) { - throw new GradleException("ASF snapshots repository will not accept a non-snapshot version: ${rootProject.version}") - } - - // Make sure access credentials have been passed. - if (!asfNexusUsername.isPresent() || !asfNexusPassword.isPresent()) { - throw new GradleException("asfNexusUsername or asfNexusPassword is empty: these are required to publish to " + - " ASF Nexus.") - } - } -}) - -configure(rootProject.ext.mavenProjects) { Project project -> - // Make sure any actual publication task is preceded by precondition checks. - tasks.matching { it.name ==~ /publish.+ToApacheSnapshotsRepository/ }.configureEach { - dependsOn checkSnapshotsRepositoryPushPreconditions - } - - plugins.withType(PublishingPlugin).configureEach { - publishing { - repositories { - maven { - name = "ApacheSnapshots" - url = apacheNexusSnapshotsRepository - - credentials { - username = asfNexusUsername.getOrElse(null) - password = asfNexusPassword.getOrElse(null) - } - } - } - } - } -} diff --git a/build-tools/build-infra/src/main/groovy/lucene.publications.maven.gradle b/build-tools/build-infra/src/main/groovy/lucene.publications.maven.gradle deleted file mode 100644 index c9cd7360ba1f..000000000000 --- a/build-tools/build-infra/src/main/groovy/lucene.publications.maven.gradle +++ /dev/null @@ -1,214 +0,0 @@ -/* - * 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. - */ - -/* - * This script configures aspects related to all project publications, - * this includes: - * - * - configuring maven artifacts, - * - setting up target maven repositories for publications to end up on, - * - configuring binary and source release artifacts, - * - other concerns related to publishing artifacts (signing, checksums). - */ - -if (project != project.rootProject) { - throw new GradleException("Applicable to rootProject only: " + project.path) -} - -// -// An explicit list of projects to publish as Maven artifacts. -// -def mavenProjects = project(":lucene").subprojects.findAll {subproject -> - def excluded = [ - // Exclude distribution assembly, tests & documentation. - ":lucene:distribution", - ":lucene:documentation", - // Exclude the parent container project for analysis modules (no artifacts). - ":lucene:analysis", - // Exclude the native module. - ":lucene:misc:native", - // Exclude test fixtures. - ":lucene:spatial-test-fixtures", - // Exclude JMH benchmarks. - ":lucene:benchmarks-jmh", - ] - - // Exclude all subprojects that are modular test projects and those explicitly - // excluded above. - return !(subproject.path.endsWith(".tests") - || subproject.path.startsWith(":build-tools:") - || subproject.path in excluded) -} - -// Make projects available to other scripts. Perhaps we should think of another way to do it. -ext.set("mavenProjects", mavenProjects) - - -// Optionally, switch to using an external GPG command, using it's configured gpg-agent for key management -Provider useGpgOption = buildOptions.addBooleanOption("useGpg", "Use GPG for signing artifacts.", false) -if (useGpgOption.get()) { - // Do this check before 'useGpgCmd()' (and once), otherwise gradle will fail with a confusing error about 'signatory.keyId' - // - // 'signatory.keyId' is an implementation detail of the SigningPlugin that it populates from 'signing.gnupg.keyName' when useGpgCmd() - // is used -- but does not explain in the error produced if 'signing.gnupg.keyName' is not set. - def propName = 'signing.gnupg.keyName' - if (!project.hasProperty(propName)) { - throw new GradleException("'$propName' project property must be set when using external GPG via 'useGpg', please see help/publishing.txt") - } - - rootProject.allprojects { - plugins.withType(SigningPlugin).configureEach { - signing { - useGpgCmd() - } - } - } -} - -configure(mavenProjects) { Project project -> - project.apply plugin: 'maven-publish' - project.apply plugin: 'signing' - - plugins.withType(JavaPlugin).configureEach { - // We have two types of publications: jars and signed jars. - publishing { - publications { - jars(MavenPublication) - signedJars(MavenPublication) - } - } - - // signedJars publication is always signed. - signing { - sign publishing.publications.signedJars - } - - // Each publication consists of the java components, source and javadoc artifacts. - // Add tasks to assemble source and javadoc JARs. - def sourcesJar = tasks.register("sourcesJar", Jar, { - dependsOn "classes" - archiveClassifier = 'sources' - from sourceSets.main.allJava - }) - - def javadocJar = tasks.register("javadocJar", Jar, { - dependsOn "javadoc" - archiveClassifier = 'javadoc' - from javadoc.destinationDir - }) - - publishing { - publications.each { publication -> - configure(publication) { - from components.java - groupId = project.group - artifactId = project.base.archivesName.get() - - artifact sourcesJar - artifact javadocJar - } - } - } - - // Configure pom defaults for all publications. - publishing { - publications.each { publication -> - configure(publication) { - pom { - name = provider { -> "Apache Lucene (module: ${project.name})" } - description = provider { -> "Apache Lucene (module: ${project.name})" } - url = "https://lucene.apache.org/" - - licenses { - license { - name = 'Apache 2' - url = 'https://www.apache.org/licenses/LICENSE-2.0.txt' - } - } - - inceptionYear = "2000" - - issueManagement { - system = "github" - url = "https://github.com/apache/lucene/issues" - } - - ciManagement { - system = "Jenkins" - url = "https://builds.apache.org/job/Lucene/" - } - - mailingLists { - mailingList { - name = "Java User List" - subscribe = "java-user-subscribe@lucene.apache.org" - unsubscribe = "java-user-unsubscribe@lucene.apache.org" - archive = "https://mail-archives.apache.org/mod_mbox/lucene-java-user/" - } - - mailingList { - name = "Java Developer List" - subscribe = "dev-subscribe@lucene.apache.org" - unsubscribe = "dev-unsubscribe@lucene.apache.org" - archive = "https://mail-archives.apache.org/mod_mbox/lucene-dev/" - } - - mailingList { - name = "Java Commits List" - subscribe = "commits-subscribe@lucene.apache.org" - unsubscribe = "commits-unsubscribe@lucene.apache.org" - archive = "https://mail-archives.apache.org/mod_mbox/lucene-java-commits/" - } - } - - scm { - connection = 'scm:git:https://gitbox.apache.org/repos/asf/lucene.git' - developerConnection = 'scm:git:https://gitbox.apache.org/repos/asf/lucene.git' - url = 'https://gitbox.apache.org/repos/asf?p=lucene.git' - } - } - } - } - } - } - - // Hack: prevent any test fixture JARs from being published. - afterEvaluate { - configurations.matching { configuration -> - configuration.name in [ - "testFixturesApiElements", - "testFixturesRuntimeElements" - ] - }.configureEach { - project.components.java.withVariantsFromConfiguration(it) { variant -> - variant.skip() - } - } - } - - // Hack: do not generate or publish gradle metadata files. - tasks.withType(GenerateModuleMetadata).configureEach { - enabled = false - } -} - -// Configure on-demand maven publishing into ~/.m2 for developers' convenience. -plugins.apply('lucene.publications.maven-to-local-m2') - -// Configure ASF Nexus publications. -plugins.apply('lucene.publications.maven-to-nexus-releases') -plugins.apply('lucene.publications.maven-to-nexus-snapshots') diff --git a/build-tools/build-infra/src/main/java/org/apache/lucene/gradle/plugins/java/EcjLintPlugin.java b/build-tools/build-infra/src/main/java/org/apache/lucene/gradle/plugins/java/EcjLintPlugin.java index 556146201a90..84879168dc7b 100644 --- a/build-tools/build-infra/src/main/java/org/apache/lucene/gradle/plugins/java/EcjLintPlugin.java +++ b/build-tools/build-infra/src/main/java/org/apache/lucene/gradle/plugins/java/EcjLintPlugin.java @@ -123,8 +123,11 @@ private void configureEcjTask( args.addAll(List.of("-properties", javadocPrefsPath.toAbsolutePath().toString())); // We depend on modular paths. - ModularPathsExtensionApi modularPaths = - (ModularPathsExtensionApi) sourceSet.getExtensions().getByName("modularPathsForEcj"); + ModularPathsExtension modularPaths = + (ModularPathsExtension) + sourceSet + .getExtensions() + .getByName(ModularPathsPlugin.MODULAR_PATHS_EXTENSION_ECJ_NAME); task.dependsOn(modularPaths); // Collect modular dependencies and their transitive dependencies to module path. diff --git a/build-tools/build-infra/src/main/java/org/apache/lucene/gradle/plugins/java/JavaProjectConventionsPlugin.java b/build-tools/build-infra/src/main/java/org/apache/lucene/gradle/plugins/java/JavaProjectConventionsPlugin.java index 78b513d590ad..cb1ff9753f56 100644 --- a/build-tools/build-infra/src/main/java/org/apache/lucene/gradle/plugins/java/JavaProjectConventionsPlugin.java +++ b/build-tools/build-infra/src/main/java/org/apache/lucene/gradle/plugins/java/JavaProjectConventionsPlugin.java @@ -18,6 +18,7 @@ import java.util.stream.Stream; import org.apache.lucene.gradle.plugins.LuceneGradlePlugin; +import org.apache.lucene.gradle.plugins.misc.PyLuceneHelperPlugin; import org.apache.lucene.gradle.plugins.spotless.GoogleJavaFormatPlugin; import org.gradle.api.Project; import org.gradle.api.plugins.JavaLibraryPlugin; @@ -45,6 +46,7 @@ private void applyJavaPlugins(Project project) { plugins.apply(JavaFolderLayoutPlugin.class); plugins.apply(JavacConfigurationPlugin.class); plugins.apply(JarManifestConfigurationPlugin.class); + plugins.apply(ModularPathsPlugin.class); plugins.apply(TestsAndRandomizationPlugin.class); plugins.apply(TestsBeastingPlugin.class); plugins.apply(TestsSummaryPlugin.class); @@ -58,5 +60,6 @@ private void applyJavaPlugins(Project project) { plugins.apply(ShowFailedTestsAtEndPlugin.class); plugins.apply(ErrorPronePlugin.class); plugins.apply(RenderJavadocPlugin.class); + plugins.apply(PyLuceneHelperPlugin.class); } } diff --git a/build-tools/build-infra/src/main/java/org/apache/lucene/gradle/plugins/java/ModularPathsExtension.java b/build-tools/build-infra/src/main/java/org/apache/lucene/gradle/plugins/java/ModularPathsExtension.java new file mode 100644 index 000000000000..a664201c6663 --- /dev/null +++ b/build-tools/build-infra/src/main/java/org/apache/lucene/gradle/plugins/java/ModularPathsExtension.java @@ -0,0 +1,455 @@ +/* + * 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. + */ +package org.apache.lucene.gradle.plugins.java; + +import java.io.File; +import java.nio.file.Path; +import java.util.AbstractMap; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.concurrent.Callable; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.stream.Collectors; +import org.gradle.api.GradleException; +import org.gradle.api.Project; +import org.gradle.api.artifacts.Configuration; +import org.gradle.api.artifacts.ConfigurationContainer; +import org.gradle.api.attributes.LibraryElements; +import org.gradle.api.file.FileCollection; +import org.gradle.api.logging.Logger; +import org.gradle.api.provider.Provider; +import org.gradle.api.tasks.SourceSet; +import org.gradle.process.CommandLineArgumentProvider; + +/** + * @see ModularPathsPlugin + */ +public class ModularPathsExtension implements Cloneable, Iterable { + /** Determines how paths are split between module path and classpath. */ + public enum Mode { + /** + * Dependencies and source set outputs are placed on classpath, even if declared on modular + * configurations. + */ + CLASSPATH_ONLY, + + /** + * Dependencies from modular configurations are placed on module path. Source set outputs are + * placed on classpath. + */ + DEPENDENCIES_ON_MODULE_PATH + } + + private final Project project; + private final SourceSet sourceSet; + + private final Configuration compileModulePathConfiguration; + private final Configuration runtimeModulePathConfiguration; + private final Configuration modulePatchOnlyConfiguration; + + /** The mode of splitting paths for this source set. */ + private Mode mode = Mode.DEPENDENCIES_ON_MODULE_PATH; + + /** More verbose debugging for paths. */ + private boolean debugPaths; + + /** + * A list of module name - path provider entries that will be converted into {@code + * --patch-module} options. + */ + private final List>> modulePatches = new ArrayList<>(); + + public ModularPathsExtension(Project project, SourceSet sourceSet) { + this.project = project; + this.sourceSet = sourceSet; + + // enable to debug paths. + this.debugPaths = false; + + ConfigurationContainer configurations = project.getConfigurations(); + + // Create modular configurations for gradle's java plugin convention configurations. + Configuration moduleApi = + createModuleConfigurationForConvention(sourceSet.getApiConfigurationName()); + Configuration moduleImplementation = + createModuleConfigurationForConvention(sourceSet.getImplementationConfigurationName()); + Configuration moduleRuntimeOnly = + createModuleConfigurationForConvention(sourceSet.getRuntimeOnlyConfigurationName()); + Configuration moduleCompileOnly = + createModuleConfigurationForConvention(sourceSet.getCompileOnlyConfigurationName()); + + // Apply hierarchy relationships to modular configurations. + moduleImplementation.extendsFrom(moduleApi); + + // Patched modules have to end up in the implementation configuration for IDEs. + String patchOnlyName = + SourceSet.isMain(sourceSet) ? "patchOnly" : (sourceSet.getName() + "PatchOnly"); + Configuration modulePatchOnly = createModuleConfigurationForConvention(patchOnlyName); + modulePatchOnly.setCanBeResolved(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. + Consumer ensureJarVariant = + (Configuration c) -> + c.getAttributes() + .attribute( + LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, + project.getObjects().named(LibraryElements.class, LibraryElements.JAR)); + + // Set up compilation/runtime classpath configurations to prefer JAR variant as well. + ensureJarVariant.accept( + configurations.maybeCreate(sourceSet.getCompileClasspathConfigurationName())); + ensureJarVariant.accept( + configurations.maybeCreate(sourceSet.getRuntimeClasspathConfigurationName())); + + // A factory that creates resolvable module configurations mirroring the convention ones. + Function createResolvableModuleConfiguration = + (String configurationName) -> { + Configuration conventionConfiguration = configurations.maybeCreate(configurationName); + Configuration moduleConfiguration = + configurations.maybeCreate( + moduleConfigurationNameFor(conventionConfiguration.getName())); + moduleConfiguration.setCanBeConsumed(false); + moduleConfiguration.setCanBeResolved(true); + ensureJarVariant.accept(moduleConfiguration); + project + .getLogger() + .info( + "Created resolvable module configuration for '{}': {}", + conventionConfiguration.getName(), + moduleConfiguration.getName()); + return moduleConfiguration; + }; + + this.compileModulePathConfiguration = + createResolvableModuleConfiguration.apply(sourceSet.getCompileClasspathConfigurationName()); + this.compileModulePathConfiguration.extendsFrom(moduleCompileOnly, moduleImplementation); + + this.runtimeModulePathConfiguration = + createResolvableModuleConfiguration.apply(sourceSet.getRuntimeClasspathConfigurationName()); + this.runtimeModulePathConfiguration.extendsFrom(moduleRuntimeOnly, moduleImplementation); + } + + /** + * Adds {@code --patch-module} option for the provided module name and the provider of a folder or + * JAR file. + */ + public void patchModule(String moduleName, Provider pathProvider) { + modulePatches.add(new AbstractMap.SimpleImmutableEntry<>(moduleName, pathProvider)); + } + + private FileCollection getCompilationModulePath() { + if (mode == Mode.CLASSPATH_ONLY) { + return project.files(); + } + return compileModulePathConfiguration.minus(modulePatchOnlyConfiguration); + } + + private FileCollection getRuntimeModulePath() { + if (mode == Mode.CLASSPATH_ONLY) { + if (hasModuleDescriptor()) { + throw new GradleException( + "Source set contains a module but classpath-only dependencies requested: " + + project.getPath() + + ", source set '" + + sourceSet.getName() + + "'"); + } + return project.files(); + } + return runtimeModulePathConfiguration.minus(modulePatchOnlyConfiguration); + } + + private FileCollection removeNonExisting(FileCollection fc) { + return fc.filter(File::exists); + } + + public FileCollection getCompilationClasspath() { + if (mode == Mode.CLASSPATH_ONLY) { + return removeNonExisting(sourceSet.getCompileClasspath()); + } + + // Lazy computation to subtract module-path artifacts (and patches) from compile classpath. + return removeNonExisting( + project.files( + (Callable) + () -> + sourceSet + .getCompileClasspath() + .minus(compileModulePathConfiguration) + .minus(modulePatchOnlyConfiguration))); + } + + public CommandLineArgumentProvider getCompilationArguments() { + return () -> { + FileCollection modulePath = getCompilationModulePath(); + if (modulePath.isEmpty()) { + return List.of(); + } + + List extraArgs = new ArrayList<>(); + extraArgs.add("--module-path"); + extraArgs.add(joinPaths(modulePath)); + + 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("--add-modules"); + extraArgs.add("ALL-MODULE-PATH"); + } + + // Add module-patching. + extraArgs.addAll(getPatchModuleArguments(modulePatches)); + + return extraArgs; + }; + } + + public FileCollection getRuntimeClasspath() { + if (mode == Mode.CLASSPATH_ONLY) { + return sourceSet.getRuntimeClasspath(); + } + return project.files( + (Callable) + () -> + sourceSet + .getRuntimeClasspath() + .minus(getRuntimeModulePath()) + .minus(modulePatchOnlyConfiguration)); + } + + /** + * Returns the runtime classpath for test tasks. + * + *

Ensures junit remains on the classpath for the Gradle test worker. Explanation: the plain + * runtimeClasspath would also filter out junit dependencies, which must be on the Test.classpath + * as the gradle test worker depends on it. Not having junit explicitly on the classpath would + * result in a deprecation warning about junit not being on the classpath and junit being + * indirectly added by gradle internal logic. + */ + public FileCollection getTestRuntimeClasspath() { + if (mode == Mode.CLASSPATH_ONLY) { + return sourceSet.getRuntimeClasspath(); + } + return project.files( + (Callable) + () -> + sourceSet + .getRuntimeClasspath() + .minus(getRuntimeModulePath()) + .minus(modulePatchOnlyConfiguration) + .plus( + sourceSet + .getRuntimeClasspath() + .filter(file -> file.getName().contains("junit")))); + } + + public CommandLineArgumentProvider getRuntimeArguments() { + return () -> { + FileCollection modulePath = getRuntimeModulePath(); + if (modulePath.isEmpty()) { + return Collections.emptyList(); + } + + List extraArgs = new ArrayList<>(); + + // Add source set outputs to module path. + extraArgs.add("--module-path"); + extraArgs.add(joinPaths(modulePath)); + + // 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("--add-modules"); + extraArgs.add("ALL-MODULE-PATH"); + + // Add module-patching. + extraArgs.addAll(getPatchModuleArguments(modulePatches)); + + return extraArgs; + }; + } + + public boolean hasModuleDescriptor() { + return sourceSet.getAllJava().getSrcDirs().stream() + .map(dir -> new File(dir, "module-info.java")) + .anyMatch(File::exists); + } + + private static List getPatchModuleArguments( + List>> patches) { + List args = new ArrayList<>(); + for (Map.Entry> e : patches) { + args.add("--patch-module"); + args.add(e.getKey() + "=" + e.getValue().get()); + } + return args; + } + + private static String toList(FileCollection files) { + if (files.isEmpty()) { + return " [empty]"; + } + + return files.getFiles().stream() + .map(f -> " " + f.getPath()) + .sorted() + .collect(Collectors.joining("\n")); + } + + private static String toList(List>> patches) { + if (patches.isEmpty()) { + return " [empty]"; + } + return patches.stream() + .map(e -> " " + e.getKey() + "=" + e.getValue().get()) + .collect(Collectors.joining("\n")); + } + + public void logCompilationPaths(Logger logger) { + String value = + "Modular extension, compilation paths, source set=" + + (sourceSet.getName() + (hasModuleDescriptor() ? " (module)" : "")) + + (", mode=" + mode + ":\n") + + (" Module path:" + toList(getCompilationModulePath()) + "\n") + + (" Class path: " + toList(getCompilationClasspath()) + "\n") + + (" Patches: " + toList(modulePatches)); + if (debugPaths) { + logger.lifecycle(value); + } else { + logger.info(value); + } + } + + public void logRuntimePaths(Logger logger) { + String value = + "Modular extension, runtime paths, source set=" + + (sourceSet.getName() + (hasModuleDescriptor() ? " (module)" : "")) + + (", mode=" + mode + ":\n") + + (" Module path:" + toList(getRuntimeModulePath()) + "\n") + + (" Class path: " + toList(getRuntimeClasspath()) + "\n") + + (" Patches : " + toList(modulePatches)); + if (debugPaths) { + logger.lifecycle(value); + } else { + logger.info(value); + } + } + + @Override + public ModularPathsExtension clone() { + try { + return (ModularPathsExtension) super.clone(); + } catch (CloneNotSupportedException e) { + throw new AssertionError(e); + } + } + + public ModularPathsExtension cloneWithMode(Mode newMode) { + ModularPathsExtension cloned = this.clone(); + cloned.mode = newMode; + return cloned; + } + + /** Map convention configuration names to "modular" corresponding configurations. */ + public static String moduleConfigurationNameFor(String configurationName) { + return "module" + capitalizeFirst(configurationName).replace("Classpath", "Path"); + } + + /** Create module configuration for the corresponding convention configuration. */ + private Configuration createModuleConfigurationForConvention(String configurationName) { + ConfigurationContainer configurations = project.getConfigurations(); + Configuration conventionConfiguration = configurations.maybeCreate(configurationName); + Configuration moduleConfiguration = + configurations.maybeCreate(moduleConfigurationNameFor(configurationName)); + moduleConfiguration.setCanBeConsumed(false); + moduleConfiguration.setCanBeResolved(false); + conventionConfiguration.extendsFrom(moduleConfiguration); + + project + .getLogger() + .info( + "Created module configuration for '{}': {}", + conventionConfiguration.getName(), + moduleConfiguration.getName()); + + return moduleConfiguration; + } + + /** Provide internal dependencies for tasks willing to depend on this modular paths object. */ + @Override + public Iterator iterator() { + return Arrays.asList(compileModulePathConfiguration, runtimeModulePathConfiguration).iterator(); + } + + // --- getters/setters/extras --- + + public Mode getMode() { + return mode; + } + + public void setMode(Mode mode) { + this.mode = mode; + } + + public boolean isDebugPaths() { + return debugPaths; + } + + public void setDebugPaths(boolean debugPaths) { + this.debugPaths = debugPaths; + } + + public Configuration getCompileModulePathConfiguration() { + return compileModulePathConfiguration; + } + + public Configuration getRuntimeModulePathConfiguration() { + return runtimeModulePathConfiguration; + } + + public Configuration getModulePatchOnlyConfiguration() { + return modulePatchOnlyConfiguration; + } + + private static String capitalizeFirst(String s) { + if (s == null || s.isEmpty()) return s; + if (Character.isUpperCase(s.charAt(0))) return s; + return Character.toUpperCase(s.charAt(0)) + s.substring(1); + } + + private static String joinPaths(FileCollection fc) { + return fc.getFiles().stream() + .map(File::getPath) + .collect(Collectors.joining(File.pathSeparator)); + } +} diff --git a/build-tools/build-infra/src/main/java/org/apache/lucene/gradle/plugins/java/ModularPathsExtensionApi.java b/build-tools/build-infra/src/main/java/org/apache/lucene/gradle/plugins/java/ModularPathsExtensionApi.java deleted file mode 100644 index c338b4640f85..000000000000 --- a/build-tools/build-infra/src/main/java/org/apache/lucene/gradle/plugins/java/ModularPathsExtensionApi.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * 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. - */ -package org.apache.lucene.gradle.plugins.java; - -import org.gradle.api.file.FileCollection; -import org.gradle.process.CommandLineArgumentProvider; - -/** - * This interface should be replaced with ModularPathsExtension, once lucene.java.modules plugin is - * converted to Java. - */ -public interface ModularPathsExtensionApi { - CommandLineArgumentProvider getCompilationArguments(); - - FileCollection getCompilationClasspath(); -} diff --git a/build-tools/build-infra/src/main/java/org/apache/lucene/gradle/plugins/java/ModularPathsPlugin.java b/build-tools/build-infra/src/main/java/org/apache/lucene/gradle/plugins/java/ModularPathsPlugin.java new file mode 100644 index 000000000000..23f5a5556137 --- /dev/null +++ b/build-tools/build-infra/src/main/java/org/apache/lucene/gradle/plugins/java/ModularPathsPlugin.java @@ -0,0 +1,329 @@ +/* + * 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. + */ +package org.apache.lucene.gradle.plugins.java; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Set; +import org.apache.lucene.gradle.plugins.LuceneGradlePlugin; +import org.gradle.api.Action; +import org.gradle.api.GradleException; +import org.gradle.api.Project; +import org.gradle.api.artifacts.ConfigurationContainer; +import org.gradle.api.file.FileCollection; +import org.gradle.api.plugins.JavaPlugin; +import org.gradle.api.plugins.JavaPluginExtension; +import org.gradle.api.tasks.SourceSet; +import org.gradle.api.tasks.SourceSetContainer; +import org.gradle.api.tasks.compile.JavaCompile; +import org.gradle.api.tasks.testing.Test; +import org.gradle.jvm.tasks.Jar; + +/** Configures miscellaneous aspects required to support the java module system layer. */ +public class ModularPathsPlugin extends LuceneGradlePlugin { + public static final String MODULAR_PATHS_EXTENSION_NAME = "modularPaths"; + public static final String MODULAR_PATHS_EXTENSION_ECJ_NAME = "modularPathsForEcj"; + + private static final Set ECJ_CLASSPATH_ONLY_PROJECTS = + Set.of(":lucene:spatial-extras", ":lucene:spatial3d"); + + private static final Set TEST_CLASSPATH_ONLY_PROJECTS = + Set.of(":lucene:core", ":lucene:codecs", ":lucene:test-framework"); + + @Override + public void apply(Project project) { + requiresAppliedPlugin(project, JavaPlugin.class); + + // We won't be using gradle's built-in automatic module finder. + var javaPluginExtension = project.getExtensions().getByType(JavaPluginExtension.class); + javaPluginExtension.getModularity().getInferModulePath().set(false); + + var ideaSync = getLuceneBuildGlobals(project).getIntellijIdea().get().isIdeaSync(); + + // + // Configure modular extensions for each source set. + // + SourceSetContainer sourceSets = javaPluginExtension.getSourceSets(); + sourceSets.configureEach( + sourceSet -> { + // Create and register a source set extension for manipulating classpath/ module-path + ModularPathsExtension modularPaths = new ModularPathsExtension(project, sourceSet); + sourceSet.getExtensions().add(MODULAR_PATHS_EXTENSION_NAME, modularPaths); + + // LUCENE-10344: We have to provide a special-case extension for ECJ because it does + // not support all the module-specific javac options. + ModularPathsExtension modularPathsForEcj = modularPaths; + if (SourceSet.TEST_SOURCE_SET_NAME.equals(sourceSet.getName()) + && ECJ_CLASSPATH_ONLY_PROJECTS.contains(project.getPath())) { + modularPathsForEcj = + modularPaths.cloneWithMode(ModularPathsExtension.Mode.CLASSPATH_ONLY); + } + sourceSet.getExtensions().add(MODULAR_PATHS_EXTENSION_ECJ_NAME, modularPathsForEcj); + + // 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.TEST_SOURCE_SET_NAME.equals(sourceSet.getName()) + && TEST_CLASSPATH_ONLY_PROJECTS.contains(project.getPath())) { + modularPaths.setMode(ModularPathsExtension.Mode.CLASSPATH_ONLY); + } + + // Configure the JavaCompile task associated with this source set. + project + .getTasks() + .named(sourceSet.getCompileJavaTaskName(), JavaCompile.class) + .configure( + task -> { + task.dependsOn(modularPaths.getCompileModulePathConfiguration()); + + // GH-12742: add the modular path as inputs so that if anything changes, the + // task + // is not up to date and is re-run. I [dw] believe this should be a + // @Classpath parameter + // on the task itself... but I don't know how to implement this on an + // existing class. + // this is a workaround but should work just fine though. + task.getInputs().files(modularPaths.getCompileModulePathConfiguration()); + + // LUCENE-10327: don't allow gradle to emit an empty sourcepath as it would + // break + // compilation of modules. + task.getOptions().setSourcepath(sourceSet.getJava().getSourceDirectories()); + + // Add modular dependencies and their transitive dependencies to module + // path. + task.getOptions() + .getCompilerArgumentProviders() + .add(modularPaths.getCompilationArguments()); + + // LUCENE-10304: if we modify the classpath here, IntelliJ no longer sees + // the dependencies as compile-time + // dependencies, don't know why. + if (!ideaSync) { + task.setClasspath(modularPaths.getCompilationClasspath()); + } + + task.doFirst(t -> modularPaths.logCompilationPaths(t.getLogger())); + }); + + // For source sets that contain a module descriptor, configure a jar task that + // combines classes and resources into a single module. + if (!SourceSet.MAIN_SOURCE_SET_NAME.equals(sourceSet.getName())) { + project + .getTasks() + .register( + sourceSet.getJarTaskName(), + Jar.class, + jar -> { + jar.getArchiveClassifier().set(sourceSet.getName()); + jar.from(sourceSet.getOutput()); + }); + } + }); + + // Connect modular configurations between their "test" and "main" source sets, this reflects + // the conventions set by the Java plugin. + ConfigurationContainer configs = project.getConfigurations(); + configs.getByName("moduleTestApi").extendsFrom(configs.getByName("moduleApi")); + configs + .getByName("moduleTestImplementation") + .extendsFrom(configs.getByName("moduleImplementation")); + configs.getByName("moduleTestRuntimeOnly").extendsFrom(configs.getByName("moduleRuntimeOnly")); + configs.getByName("moduleTestCompileOnly").extendsFrom(configs.getByName("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 main = sourceSets.getByName(SourceSet.MAIN_SOURCE_SET_NAME); + SourceSet test = sourceSets.getByName(SourceSet.TEST_SOURCE_SET_NAME); + + boolean mainIsEmpty = main.getAllJava().isEmpty(); + boolean mainIsModular = + ((ModularPathsExtension) main.getExtensions().getByName(MODULAR_PATHS_EXTENSION_NAME)) + .hasModuleDescriptor(); + boolean testIsModular = + ((ModularPathsExtension) test.getExtensions().getByName(MODULAR_PATHS_EXTENSION_NAME)) + .hasModuleDescriptor(); + + // LUCENE-10304: if we modify the classpath here, IntelliJ no longer sees the dependencies as + // compile-time dependencies, don't know why. + if (!ideaSync) { + // Consider combinations of modular/classpath between main and test. + if (testIsModular) { + if (mainIsModular || mainIsEmpty) { + // Task providers for jar outputs (using task providers so we depend on their outputs + // lazily). + var mainJarTask = project.getTasks().named(main.getJarTaskName(), Jar.class); + var testJarTask = project.getTasks().named(test.getJarTaskName(), Jar.class); + + // If main is empty, omit its jar outputs. + FileCollection mainJarOutputs = + mainIsEmpty + ? project.files() + : project.getObjects().fileCollection().from(mainJarTask); + + FileCollection testJarOutputs = project.getObjects().fileCollection().from(testJarTask); + + // Attach jars to modular test configurations. + project.getDependencies().add("moduleTestImplementation", mainJarOutputs); + project.getDependencies().add("moduleTestRuntimeOnly", testJarOutputs); + + // Fully modular tests - must have no split packages, proper access, etc. + // Work around the split classes/resources problem by adjusting classpaths to + // rely on JARs rather than source set output folders. + test.setCompileClasspath( + project + .getObjects() + .fileCollection() + .from( + mainJarOutputs, + configs.getByName(test.getCompileClasspathConfigurationName()))); + + test.setRuntimeClasspath( + project + .getObjects() + .fileCollection() + .from( + mainJarOutputs, + testJarOutputs, + configs.getByName(test.getRuntimeClasspathConfigurationName()))); + + } else { + // This combination of options simply does not make any sense (in my opinion). + throw new GradleException( + "Test source set is modular and main source set is class-based, this makes no sense: " + + project.getPath()); + } + } else { + // if (mainIsModular) { + // This combination is a potential candidate for patching the main sourceset's module with + // test classes. I could not resolve all the difficulties that arise when you try to do it + // though - either a separate module descriptor is needed that opens test packages, adds + // dependencies via requires clauses or a series of jvm arguments (--add-reads, + // --add-opens, etc.) has to be generated and maintained. This is very low-level (ECJ + // doesn't support a full set of these instructions, for example). + // + // Fall back to classpath mode. + // } else { + // This is the 'plain old classpath' mode: neither the main source set nor the test set + // are modular. + // } + } + } + + // + // 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. + Action configureTestTaskForSourceSet = + task -> { + // Default: configure for SourceSet 'test'. You can call this for other pairs if needed. + SourceSet testSourceSet = sourceSets.getByName(SourceSet.TEST_SOURCE_SET_NAME); + + File forkProperties = new File(task.getTemporaryDir(), "jvm-forking.properties"); + + ModularPathsExtension modularPaths = + ((ModularPathsExtension) + testSourceSet.getExtensions().getByName(MODULAR_PATHS_EXTENSION_NAME)); + task.dependsOn(modularPaths); + + // Add modular dependencies (and transitives) to module path. + task.getJvmArgumentProviders().add(modularPaths.getRuntimeArguments()); + + // Modify default classpath for tests. + task.setClasspath(modularPaths.getTestRuntimeClasspath()); + + task.doFirst(t -> modularPaths.logRuntimePaths(t.getLogger())); + + // Pass all the required properties for tests which fork the JVM. We don't use + // regular system properties here because this could affect task up-to-date checks. + task.getJvmArgumentProviders() + .add(() -> List.of("-Dtests.jvmForkArgsFile=" + forkProperties.getAbsolutePath())); + + // Before execution: compute full JVM args to be used by forked processes + // and persist to file. + task.doFirst( + _ -> { + // Start with runtime module-path args + List args = new ArrayList<>(); + modularPaths.getRuntimeArguments().asArguments().forEach(args::add); + + String cp = modularPaths.getRuntimeClasspath().getAsPath(); + if (!cp.isBlank()) { + args.add("-cp"); + args.add(cp); + } + + // Sanity check: no newlines in individual args + for (String s : args) { + if (s.contains("\n")) { + throw new GradleException("LF in a forked jvm property?: " + s); + } + } + + try { + Files.createDirectories(forkProperties.toPath().getParent()); + Files.writeString(forkProperties.toPath(), String.join("\n", args)); + } catch (IOException e) { + throw new GradleException( + "Failed to write fork properties file: " + forkProperties, e); + } + }); + }; + + project + .getTasks() + .withType(Test.class) + .matching(it -> it.getName().matches("test(_[0-9]+)?")) + .configureEach(configureTestTaskForSourceSet); + + // Configure module versions. + project + .getTasks() + .withType(JavaCompile.class) + .configureEach( + task -> { + // TODO: LUCENE-10267: Gradle bug workaround. Remove when upstream is fixed. + String projectVersion = String.valueOf(project.getVersion()); + + task.getOptions() + .getCompilerArgumentProviders() + .add( + () -> { + if (task.getClasspath().isEmpty()) { + return Arrays.asList("--module-version", projectVersion); + } else { + return List.of(); + } + }); + + task.getOptions().getJavaModuleVersion().set(projectVersion); + }); + } +} diff --git a/build-tools/build-infra/src/main/java/org/apache/lucene/gradle/plugins/misc/PyLuceneHelperPlugin.java b/build-tools/build-infra/src/main/java/org/apache/lucene/gradle/plugins/misc/PyLuceneHelperPlugin.java new file mode 100644 index 000000000000..5d3234e81baf --- /dev/null +++ b/build-tools/build-infra/src/main/java/org/apache/lucene/gradle/plugins/misc/PyLuceneHelperPlugin.java @@ -0,0 +1,50 @@ +/* + * 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. + */ +package org.apache.lucene.gradle.plugins.misc; + +import org.apache.lucene.gradle.plugins.LuceneGradlePlugin; +import org.gradle.api.Project; +import org.gradle.api.plugins.JavaPlugin; +import org.gradle.api.tasks.Sync; +import org.gradle.jvm.tasks.Jar; + +public class PyLuceneHelperPlugin extends LuceneGradlePlugin { + @Override + public void apply(Project project) { + requiresAppliedPlugin(project, JavaPlugin.class); + + var publishedProjects = getLuceneBuildGlobals(project).getPublishedProjects(); + if (publishedProjects.contains(project)) { + project + .getTasks() + .register( + "collectRuntimeJars", + Sync.class, + task -> { + // Collect our own artifact. + task.from(project.getTasks().withType(Jar.class).getByName("jar").getOutputs()); + + // Collect all dependencies, excluding cross-module dependencies. + task.from( + project.getConfigurations().getByName("runtimeClasspath"), + spec -> spec.exclude("lucene-*")); + + task.into(project.getLayout().getBuildDirectory().dir("runtimeJars")); + }); + } + } +} diff --git a/build-tools/build-infra/src/main/java/org/apache/lucene/gradle/plugins/publishing/ConfigureMavenPublishingPlugin.java b/build-tools/build-infra/src/main/java/org/apache/lucene/gradle/plugins/publishing/ConfigureMavenPublishingPlugin.java new file mode 100644 index 000000000000..4278c430f767 --- /dev/null +++ b/build-tools/build-infra/src/main/java/org/apache/lucene/gradle/plugins/publishing/ConfigureMavenPublishingPlugin.java @@ -0,0 +1,447 @@ +/* + * 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. + */ +package org.apache.lucene.gradle.plugins.publishing; + +import java.util.List; +import org.apache.lucene.gradle.plugins.LuceneGradlePlugin; +import org.gradle.api.GradleException; +import org.gradle.api.Project; +import org.gradle.api.component.AdhocComponentWithVariants; +import org.gradle.api.component.ConfigurationVariantDetails; +import org.gradle.api.plugins.BasePluginExtension; +import org.gradle.api.provider.Provider; +import org.gradle.api.publish.PublishingExtension; +import org.gradle.api.publish.maven.MavenPom; +import org.gradle.api.publish.maven.MavenPublication; +import org.gradle.api.publish.maven.plugins.MavenPublishPlugin; +import org.gradle.api.publish.tasks.GenerateModuleMetadata; +import org.gradle.api.tasks.Delete; +import org.gradle.jvm.tasks.Jar; +import org.gradle.plugins.signing.SigningExtension; +import org.gradle.plugins.signing.SigningPlugin; + +/** + * This plugin configures aspects related to all project publications, including: + * + *
    + *
  • configuring maven artifacts + *
  • setting up target maven repositories for publications to end up on + *
  • configuring binary and source release artifacts + *
  • other concerns related to publishing + *
+ */ +public class ConfigureMavenPublishingPlugin extends LuceneGradlePlugin { + public static final String OPT_SIGN = "sign"; + public static final String MAVEN_ARTIFACTS_CONFIGURATION = "mavenArtifacts"; + + @Override + public void apply(Project project) { + applicableToRootProjectOnly(project); + + getBuildOptions(project) + .addBooleanOption(OPT_SIGN, "Sign assembled distribution artifacts.", false); + + configureGpgSigning(project); + getLuceneBuildGlobals(project).getPublishedProjects().forEach(this::configureMavenPublications); + + configurePublicationsToLocalMavenRepository(project); + configurePublicationsToLocalBuildDirectory(project); + configurePublicationsToApacheNexus(project, true); + configurePublicationsToApacheNexus(project, false); + } + + /** Configure artifact push to apache nexus (releases or snapshots repository). */ + private void configurePublicationsToApacheNexus( + Project rootProject, boolean useReleaseRepository) { + var apacheNexusRepositoryUrl = + useReleaseRepository + ? "https://repository.apache.org/service/local/staging/deploy/maven2" + : "https://repository.apache.org/content/repositories/snapshots"; + + var type = useReleaseRepository ? "Releases" : "Snapshots"; + + var tasks = rootProject.getTasks(); + tasks.register( + "mavenToApache" + type, + task -> { + task.setGroup("Distribution"); + task.setDescription( + "Publish Lucene Maven artifacts to Apache " + + type + + " repository: " + + apacheNexusRepositoryUrl); + + for (var p : getLuceneBuildGlobals(rootProject).getPublishedProjects()) { + var matchingTasks = + p.getTasks() + .matching( + t -> + t.getName() + .equals( + "publishSignedJarsPublicationToApache" + type + "Repository")); + task.dependsOn(matchingTasks); + } + }); + + // These access credentials must be passed by the release manager + // (either on command-line, via the environment or via ~/.gradle.properties). + var providers = rootProject.getProviders(); + Provider asfNexusUsername = + providers + .gradleProperty("asfNexusUsername") + .orElse(providers.systemProperty("asfNexusUsername")) + .orElse(providers.environmentVariable("ASF_NEXUS_USERNAME")); + + Provider asfNexusPassword = + providers + .gradleProperty("asfNexusPassword") + .orElse(providers.systemProperty("asfNexusPassword")) + .orElse(providers.environmentVariable("ASF_NEXUS_PASSWORD")); + + var buildGlobals = getLuceneBuildGlobals(rootProject); + var checkRepositoryPushPreconditions = + tasks.register( + "check" + type + "RepositoryPushPreconditions", + task -> { + task.doFirst( + _ -> { + // Make sure we're pushing a release version to release repo and a snapshot + // to a snapshot repo. + if (buildGlobals.snapshotBuild == useReleaseRepository) { + throw new GradleException( + type + + " repository will not accept this version: " + + rootProject.getVersion()); + } + + // Make sure access credentials have been passed. + if (!asfNexusUsername.isPresent() || !asfNexusPassword.isPresent()) { + throw new GradleException( + "asfNexusUsername or asfNexusPassword is empty: these are required to publish to " + + " ASF Nexus."); + } + }); + }); + + buildGlobals + .getPublishedProjects() + .forEach( + project -> { + // Add the publishing repository + var publishingExtension = + project.getExtensions().getByType(PublishingExtension.class); + publishingExtension + .getRepositories() + .maven( + repo -> { + repo.setName("Apache" + type); + repo.setUrl(apacheNexusRepositoryUrl); + repo.credentials( + creds -> { + creds.setUsername(asfNexusUsername.getOrNull()); + creds.setPassword(asfNexusPassword.getOrNull()); + }); + }); + + // Make sure any actual publication task is preceded by precondition checks. + project + .getTasks() + .matching( + task -> task.getName().matches("publish.+ToApache" + type + "Repository")) + .configureEach( + t -> { + t.dependsOn(checkRepositoryPushPreconditions); + }); + }); + } + + /** + * Configure maven publishing task that copies artifacts into the root project's {@code + * build/maven-artifacts}. This is used in smoke-testing script runs. + */ + private void configurePublicationsToLocalBuildDirectory(Project project) { + var mavenRepositoryDir = project.getLayout().getBuildDirectory().dir("maven-artifacts"); + var publishedProjects = getLuceneBuildGlobals(project).getPublishedProjects(); + + var tasks = project.getTasks(); + var mavenToBuildTask = + tasks.register( + "mavenToBuild", + task -> { + task.getOutputs().dir(mavenRepositoryDir); + + // In signed mode, collect signed artifacts. Otherwise, collect + // unsigned JARs (and their checksums). + boolean withSignedArtifacts = + getBuildOptions(project).getOption(OPT_SIGN).asBooleanProvider().get(); + var mavenConventionTask = + withSignedArtifacts + ? "publishSignedJarsPublicationToBuildRepository" + : "publishJarsPublicationToBuildRepository"; + + task.dependsOn( + publishedProjects.stream() + .map(p -> p.getTasks().matching(t -> t.getName().equals(mavenConventionTask))) + .toList()); + }); + + var cleanBuildTask = + tasks.register( + "cleanMavenBuildRepository", + Delete.class, + task -> { + task.delete(mavenRepositoryDir); + }); + + for (var p : publishedProjects) { + // Clean the build repository prior to publishing anything. Ensures we don't + // have multiple artifacts there. + p.getTasks() + .matching(t -> t.getName().matches("publish.+ToBuildRepository")) + .configureEach( + t -> { + t.dependsOn(cleanBuildTask); + }); + + var publishingExtension = p.getExtensions().getByType(PublishingExtension.class); + publishingExtension + .getRepositories() + .maven( + repo -> { + repo.setName("Build"); + repo.setUrl(mavenRepositoryDir); + }); + } + + project.getConfigurations().create(MAVEN_ARTIFACTS_CONFIGURATION); + + project + .getArtifacts() + .add( + MAVEN_ARTIFACTS_CONFIGURATION, + mavenRepositoryDir, + configurablePublishArtifact -> { + configurablePublishArtifact.builtBy(mavenToBuildTask); + }); + } + + /** + * Configure maven publishing task that copies artifacts into {@code ~/.m2} (this is to allow + * local testing and review, mostly). + */ + private void configurePublicationsToLocalMavenRepository(Project project) { + project + .getTasks() + .register( + "mavenToLocal", + task -> { + task.setGroup("Distribution"); + task.setDescription("Publish Lucene Maven artifacts to ~/.m2 repository."); + + task.dependsOn( + getLuceneBuildGlobals(project).getPublishedProjects().stream() + .map(p -> p.getTasks().named("publishJarsPublicationToMavenLocal")) + .toList()); + }); + } + + private void configureGpgSigning(Project project) { + Provider useGpgOption = + getBuildOptions(project) + .addBooleanOption("useGpg", "Use GPG for signing artifacts.", false); + + if (useGpgOption.get()) { + // Do this check before 'useGpgCmd()' (and once), otherwise gradle will fail + // with a confusing error about 'signatory.keyId' + // + // 'signatory.keyId' is an implementation detail of the SigningPlugin that it populates + // from 'signing.gnupg.keyName' when useGpgCmd() + // is used -- but does not explain in the error produced if 'signing.gnupg.keyName' is not + // set. + var propName = "signing.gnupg.keyName"; + if (!project.hasProperty(propName)) { + throw new GradleException( + "'" + + propName + + "' project property must be set when using external GPG via 'useGpg', please see help/publishing.txt"); + } + + for (var p : project.getAllprojects()) { + p.getPlugins() + .withType(SigningPlugin.class) + .configureEach( + _ -> { + p.getExtensions().configure(SigningExtension.class, SigningExtension::useGpgCmd); + }); + } + } + } + + private void configureMavenPublications(Project project) { + project.getPlugins().apply(MavenPublishPlugin.class); + project.getPlugins().apply(SigningPlugin.class); + + var signingExtension = project.getExtensions().getByType(SigningExtension.class); + var publishingExtension = project.getExtensions().getByType(PublishingExtension.class); + var publications = publishingExtension.getPublications(); + + // We have two types of publications: jars and signed jars. + var jarsPublication = publications.create("jars", MavenPublication.class); + var signedJarsPublication = publications.create("signedJars", MavenPublication.class); + + // signedJars publication is always signed. + signingExtension.sign(signedJarsPublication); + + // Each publication consists of the java components, source and javadoc artifacts. + // Add tasks to assemble source and javadoc JARs. + var sourcesJarTask = + project + .getTasks() + .register( + "sourcesJar", + Jar.class, + sourcesJar -> { + sourcesJar.dependsOn("classes"); + sourcesJar.getArchiveClassifier().set("sources"); + sourcesJar.from( + project + .getExtensions() + .getByType(org.gradle.api.plugins.JavaPluginExtension.class) + .getSourceSets() + .getByName("main") + .getAllJava()); + }); + + var javadocJarTask = + project + .getTasks() + .register( + "javadocJar", + Jar.class, + javadocJar -> { + javadocJar.dependsOn("javadoc"); + javadocJar.getArchiveClassifier().set("javadoc"); + javadocJar.from( + project.getTasks().named("javadoc").get().getOutputs().getFiles()); + }); + + for (MavenPublication pub : List.of(jarsPublication, signedJarsPublication)) { + pub.setGroupId(project.getGroup().toString()); + pub.setArtifactId( + project.getExtensions().getByType(BasePluginExtension.class).getArchivesName().get()); + + pub.from(project.getComponents().getByName("java")); + + pub.artifact(sourcesJarTask); + pub.artifact(javadocJarTask); + + configurePom(project, pub); + } + + // Hack: prevent any test fixture JARs from being published. + project + .getConfigurations() + .matching( + cfg -> + cfg.getName().equals("testFixturesApiElements") + || cfg.getName().equals("testFixturesRuntimeElements")) + .configureEach( + cfg -> { + project.getComponents().stream() + .filter(c -> "java".equals(c.getName())) + .filter(c -> c instanceof AdhocComponentWithVariants) + .map(c -> (AdhocComponentWithVariants) c) + .forEach( + comp -> + comp.withVariantsFromConfiguration( + cfg, ConfigurationVariantDetails::skip)); + }); + + // Hack: do not generate or publish Gradle module metadata (*.module) + project + .getTasks() + .withType(GenerateModuleMetadata.class) + .configureEach(t -> t.setEnabled(false)); + } + + private void configurePom(Project project, MavenPublication pub) { + MavenPom pom = pub.getPom(); + + Provider prov = + project.getProviders().provider(() -> "Apache Lucene (module: " + project.getName() + ")"); + + pom.getName().set(prov); + pom.getDescription().set(prov); + pom.getUrl().set("https://lucene.apache.org/"); + + pom.licenses( + licenses -> + licenses.license( + lic -> { + lic.getName().set("Apache 2"); + lic.getUrl().set("https://www.apache.org/licenses/LICENSE-2.0.txt"); + })); + + pom.getInceptionYear().set("2000"); + + pom.issueManagement( + im -> { + im.getSystem().set("github"); + im.getUrl().set("https://github.com/apache/lucene/issues"); + }); + + pom.ciManagement( + ci -> { + ci.getSystem().set("Jenkins"); + ci.getUrl().set("https://builds.apache.org/job/Lucene/"); + }); + + pom.mailingLists( + mls -> { + mls.mailingList( + ml -> { + ml.getName().set("Java User List"); + ml.getSubscribe().set("java-user-subscribe@lucene.apache.org"); + ml.getUnsubscribe().set("java-user-unsubscribe@lucene.apache.org"); + ml.getArchive().set("https://mail-archives.apache.org/mod_mbox/lucene-java-user/"); + }); + mls.mailingList( + ml -> { + ml.getName().set("Java Developer List"); + ml.getSubscribe().set("dev-subscribe@lucene.apache.org"); + ml.getUnsubscribe().set("dev-unsubscribe@lucene.apache.org"); + ml.getArchive().set("https://mail-archives.apache.org/mod_mbox/lucene-dev/"); + }); + mls.mailingList( + ml -> { + ml.getName().set("Java Commits List"); + ml.getSubscribe().set("commits-subscribe@lucene.apache.org"); + ml.getUnsubscribe().set("commits-unsubscribe@lucene.apache.org"); + ml.getArchive() + .set("https://mail-archives.apache.org/mod_mbox/lucene-java-commits/"); + }); + }); + + pom.scm( + scm -> { + scm.getConnection().set("scm:git:https://gitbox.apache.org/repos/asf/lucene.git"); + scm.getDeveloperConnection() + .set("scm:git:https://gitbox.apache.org/repos/asf/lucene.git"); + scm.getUrl().set("https://gitbox.apache.org/repos/asf?p=lucene.git"); + }); + } +} diff --git a/build.gradle b/build.gradle index ef40c760964c..0782624e0b62 100644 --- a/build.gradle +++ b/build.gradle @@ -1,3 +1,5 @@ +import org.apache.lucene.gradle.plugins.publishing.ConfigureMavenPublishingPlugin + /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with @@ -24,15 +26,8 @@ plugins { id "lucene.root-project.setup" id "lucene.java-projects.conventions" - id "lucene.java.modules" id "lucene.validation.jar-checks" - id "lucene.publications.maven" - id "lucene.publications.maven-to-nexus-releases" - id "lucene.publications.maven-to-nexus-snapshots" - - id "lucene.misc.pylucene" - id "lucene.regenerate" id "lucene.regenerate.jflex" id "lucene.regenerate.forUtil" @@ -46,4 +41,7 @@ plugins { id "lucene.regenerate.snowball" } +// configure publishing defaults. +plugins.apply(ConfigureMavenPublishingPlugin.class); + description = 'Grandparent project for Apache Lucene Core' diff --git a/lucene/distribution/binary-release.gradle b/lucene/distribution/binary-release.gradle deleted file mode 100644 index 971111956bd5..000000000000 --- a/lucene/distribution/binary-release.gradle +++ /dev/null @@ -1,153 +0,0 @@ -/* - * 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 Lucene's binary release. This is a bit convoluted so is placed -// in a separate script. - -configure(project(":lucene:distribution")) { - def packageBaseName = "${buildDir}/packages/lucene-${version}" - - // Prepare site documentation dependency for inclusion. - configurations { - docs - - jars - jarsTestFramework - jarsThirdParty - - binaryDirForTests - } - - dependencies { DependencyHandler handler -> - docs project(path: ':lucene:documentation', configuration: 'site') - - // Maven-published submodule JARs are part of the binary distribution. - // We don't copy their transitive dependencies. - def binaryModules = rootProject.ext.mavenProjects.findAll { p -> !(p.path in [ - // Placed in a separate folder (module layer conflicts). - ":lucene:test-framework", - ]) } - for (Project module : binaryModules) { - jars(module, { - transitive = false - }) - } - - // It's placed in a separate modules folder since it has service providers that are consumed by lucene-core for tests. - // We won't resolve it when running the Luke app. - jarsTestFramework(project(":lucene:test-framework"), { - transitive = false - }) - - // The third-party JARs consist of all the transitive dependencies from a subset of - // all Lucene modules. We only include the demos and Luke. Everything else has to be downloaded - // manually or via maven POMs. - for (Project module : [ - project(":lucene:luke"), - project(":lucene:demo") - ]) { - jarsThirdParty(module, { - transitive = true - }) - } - } - - task assembleBinaryTgz(type: Tar) { - description = "Assemble binary Lucene artifact as a .tgz file." - - archiveFileName = packageBaseName + ".tgz" - destinationDirectory = file(archiveFileName).parentFile - - reproducibleFileOrder = true - compression = Compression.GZIP - - // Internal archive folder for all files. - into "lucene-${rootProject.version}/" - } - - task assembleBinaryDirForTests(type: Sync) { - description = "Assemble a subset of the binary Lucene distribution as an expanded directory for tests." - destinationDir = file("${packageBaseName}-itests") - } - - artifacts { - binaryDirForTests tasks.assembleBinaryDirForTests.destinationDir, { - builtBy tasks.assembleBinaryDirForTests - } - } - - // Configure distribution content for archives and stand-alone directories. - // This is split into binaries and other artifacts to speed up distribution - // tests. - Closure distributionBinaryContent = { AbstractCopyTask task -> - // Manually correct posix permissions (matters when assembling archives on Windows). - filesMatching(["**/*.sh", "**/*.bat"]) { copy -> - copy.permissions { - unix("755") - } - } - - // Attach binary release - only files. - from(file("src/binary-release"), { - filteringCharset = 'UTF-8' - }) - - // Binary modules (Lucene). - from(configurations.jars, { - into 'modules' - }) - from(configurations.jarsTestFramework, { - into 'modules-test-framework' - }) - - // Binary modules (with dependencies). Don't duplicate project artifacts. - from((configurations.jarsThirdParty - configurations.jars), { - into 'modules-thirdparty' - }) - } - - Closure distributionOtherContent = { AbstractCopyTask task -> - // Cherry-pick certain files from the root. - from(project(':').projectDir, { - include "LICENSE.txt" - include "NOTICE.txt" - }) - - // Cherry-pick certain files from the lucene module. - from(project(':lucene').projectDir, { - include "CHANGES.txt" - include "JRE_VERSION_MIGRATION.md" - include "MIGRATE.md" - include "SYSTEM_REQUIREMENTS.md" - include "licenses/*" - }) - - // The documentation. - from(configurations.docs, { - into 'docs' - }) - } - - [tasks.assembleBinaryTgz].each { Task task -> - task.configure distributionBinaryContent - task.configure distributionOtherContent - } - - [tasks.assembleBinaryDirForTests].each { Task task -> - task.configure distributionBinaryContent - } -} diff --git a/lucene/distribution/build.gradle b/lucene/distribution/build.gradle index e2e8de998f96..9495078d2ff4 100644 --- a/lucene/distribution/build.gradle +++ b/lucene/distribution/build.gradle @@ -15,12 +15,11 @@ * limitations under the License. */ - -import org.apache.lucene.gradle.plugins.gitinfo.GitInfoExtension - import java.nio.charset.StandardCharsets import java.nio.file.Files +import org.apache.lucene.gradle.plugins.gitinfo.GitInfoExtension import org.apache.lucene.gradle.plugins.misc.QuietExec +import org.apache.lucene.gradle.plugins.publishing.ConfigureMavenPublishingPlugin plugins { id 'signing' @@ -29,19 +28,16 @@ plugins { // This project puts together the Lucene "distribution", assembling bits and pieces // from across the project structure into release artifacts. -Provider signOption = buildOptions.addBooleanOption("sign", "Sign assembled distribution artifacts.", false) - -ext { - releaseDir = file("${buildDir}/release") - withSignedArtifacts = signOption.get() -} +def releaseDir = project.layout.buildDirectory.dir("release") // Collect git revision. GitInfoExtension gitInfoExt = rootProject.extensions.getByName("gitinfo") MapProperty gitStatus = gitInfoExt.getGitInfo() Provider gitRev = gitStatus.getting("git.commit") +// // Prepare the "source" distribution artifact. We use raw git export, no additional complexity needed. +// Provider sourceTgzFile = project.layout.buildDirectory.file("packages/lucene-${version}-src.tgz") tasks.register("assembleSourceTgz", QuietExec, { description = "Assemble source Lucene artifact as a .tgz file." @@ -74,11 +70,139 @@ tasks.register("assembleSourceTgz", QuietExec, { }) +// // Prepare the "binary" distribution artifact. -apply from: buildscript.sourceFile.toPath().resolveSibling("binary-release.gradle") +// +def packageBaseName = project.layout.buildDirectory.dir("packages/lucene-${version}").get().asFile.toString() + +// Prepare site documentation dependency for inclusion. +configurations { + docs + + jars + jarsTestFramework + jarsThirdParty + + binaryDirForTests +} + +dependencies { + docs project(path: ':lucene:documentation', configuration: 'site') + + // Maven-published submodule JARs are part of the binary distribution. + // We don't copy their transitive dependencies. + def binaryModules = buildGlobals.publishedProjects.findAll {p -> + !(p.path in [ + // Placed in a separate folder (module layer conflicts). + ":lucene:test-framework", + ]) + } + for (Project module : binaryModules) { + jars(module, { + transitive = false + }) + } + + // It's placed in a separate modules folder since it has service providers that are consumed by lucene-core for tests. + // We won't resolve it when running the Luke app. + jarsTestFramework(project(":lucene:test-framework"), { + transitive = false + }) + + // The third-party JARs consist of all the transitive dependencies from a subset of + // all Lucene modules. We only include the demos and Luke. Everything else has to be downloaded + // manually or via maven POMs. + for (Project module : [ + project(":lucene:luke"), + project(":lucene:demo") + ]) { + jarsThirdParty(module, { + transitive = true + }) + } +} + +def assembleBinaryTgz = tasks.register("assembleBinaryTgz", Tar, { + description = "Assemble binary Lucene artifact as a .tgz file." + + archiveFileName = packageBaseName + ".tgz" + destinationDirectory = file(archiveFileName).parentFile + + reproducibleFileOrder = true + compression = Compression.GZIP + + // Internal archive folder for all files. + into("lucene-${rootProject.version}/") +}) + +def binaryDestinationDir = file("${packageBaseName}-itests") +def assembleBinaryDirForTests = tasks.register("assembleBinaryDirForTests", Sync, { + description = "Assemble a subset of the binary Lucene distribution as an expanded directory for tests." + destinationDir = binaryDestinationDir +}) + +artifacts { + binaryDirForTests binaryDestinationDir, { + builtBy assembleBinaryDirForTests + } +} + +// Configure distribution content for archives and stand-alone directories. +// This is split into binaries and other artifacts to speed up distribution +// tests. +Action distributionBinaryContent = { AbstractCopyTask task -> + // Manually correct posix permissions (matters when assembling archives on Windows). + task.filesMatching(["**/*.sh", "**/*.bat"]) {copy -> + copy.permissions { + unix("755") + } + } + + // Attach binary release - only files. + task.from(file("src/binary-release"), { + filteringCharset = 'UTF-8' + }) + + // Binary modules (Lucene). + task.from(configurations.jars, { + into 'modules' + }) + task.from(configurations.jarsTestFramework, { + into 'modules-test-framework' + }) + + // Binary modules (with dependencies). Don't duplicate project artifacts. + task.from((configurations.jarsThirdParty - configurations.jars), { + into 'modules-thirdparty' + }) +} + +Action distributionOtherContent = { Tar task -> + // Cherry-pick certain files from the root. + task.from(project(':').projectDir, { + include "LICENSE.txt" + include "NOTICE.txt" + }) + + // Cherry-pick certain files from the lucene module. + task.from(project(':lucene').projectDir, { + include "CHANGES.txt" + include "JRE_VERSION_MIGRATION.md" + include "MIGRATE.md" + include "SYSTEM_REQUIREMENTS.md" + include "licenses/*" + }) + + // The documentation. + task.from(configurations.docs, { + into 'docs' + }) +} + +assembleBinaryTgz.configure(distributionBinaryContent) +assembleBinaryTgz.configure(distributionOtherContent) +assembleBinaryDirForTests.configure(distributionBinaryContent) -// Configure maven artifact collection to a local build folder (required to collect artifacts for the release). -apply from: buildscript.sourceFile.toPath().resolveSibling("collect-maven-artifacts.gradle") // Configure release artifact signing. tasks.register("signReleaseArchives", Sign, { @@ -90,32 +214,20 @@ tasks.register("signReleaseArchives", Sign, { sign sourceTgzFile.get().asFile }) - -// Set up the HTML-rendered "changes" distribution artifact by linking to documentation's output. -configurations { - changesHtml -} - -dependencies { - changesHtml project(path: ":lucene:documentation", configuration: "changesHtml") -} - - // Compute checksums for release archives. tasks.register("computeChecksums", org.apache.lucene.gradle.Checksum, { files = objects.fileCollection() [ tasks.assembleSourceTgz, tasks.assembleBinaryTgz, - ].each { dep -> + ].each {dep -> dependsOn dep files += dep.outputs.files } - outputDir = file("${buildDir}/checksums") + outputDir = project.layout.buildDirectory.dir("checksums").get().asFile }) - tasks.register("prepareGitRev", { Provider outputFile = project.layout.buildDirectory.file(".gitrev") @@ -127,6 +239,16 @@ tasks.register("prepareGitRev", { } }) +// Set up the HTML-rendered "changes" distribution artifact by linking to documentation's output. +configurations { + changesHtml + mavenArtifacts +} + +dependencies { + changesHtml project(path: ":lucene:documentation", configuration: "changesHtml") + mavenArtifacts project(path: ":", configuration: ConfigureMavenPublishingPlugin.MAVEN_ARTIFACTS_CONFIGURATION) +} // Assemble everything needed in the release folder structure. tasks.register("assembleRelease", Sync, { @@ -136,7 +258,7 @@ tasks.register("assembleRelease", Sync, { into "changes" }) - from(tasks.mavenToBuild, { + from(configurations.mavenArtifacts, { into "maven" }) @@ -147,14 +269,15 @@ tasks.register("assembleRelease", Sync, { from tasks.computeChecksums // Conditionally, attach signatures of all the release archives. - if (project.ext.withSignedArtifacts) { + Provider signOption = rootProject.buildOptions.getOption(ConfigureMavenPublishingPlugin.OPT_SIGN) + .asBooleanProvider() + if (signOption.get()) { from tasks.signReleaseArchives } into releaseDir }) - // Add the description and task group to some of the tasks that make // sense at the user-level help. tasks.matching { diff --git a/lucene/distribution/collect-maven-artifacts.gradle b/lucene/distribution/collect-maven-artifacts.gradle deleted file mode 100644 index 16dde08c8723..000000000000 --- a/lucene/distribution/collect-maven-artifacts.gradle +++ /dev/null @@ -1,64 +0,0 @@ -/* - * 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 artifact publishing into a build-local Maven repository. This is technically not required -// at all - maven artifacts can go directly to ASF Nexus - but smoke-testing scripts run checks on these -// artifacts. -// - -configure(project(":lucene:distribution")) { - def mavenRepositoryDir = file("${buildDir}/maven-artifacts") - - task mavenToBuild() { - outputs.dir(mavenRepositoryDir) - - // In signed mode, collect signed artifacts. Otherwise collect - // unsigned JARs (and their checksums). - def mavenConventionTask = project.ext.withSignedArtifacts ? - "publishSignedJarsPublicationToBuildRepository" : - "publishJarsPublicationToBuildRepository" - - dependsOn rootProject.ext.mavenProjects.collect { - it.tasks.matching { it.name == mavenConventionTask } - } - } - - def cleanBuildTask = tasks.register("cleanMavenBuildRepository", Delete, { - delete mavenRepositoryDir - }) - - configure(rootProject.ext.mavenProjects) { Project project -> - // Clean the build repository prior to publishing anything. Ensures we don't - // have multiple artifacts there. - tasks.matching { it.name ==~ /publish.+ToBuildRepository/ }.configureEach { - dependsOn cleanBuildTask - } - - plugins.withType(PublishingPlugin).configureEach { - publishing { - repositories { - maven { - name = "Build" - url = mavenRepositoryDir - } - } - } - } - } -}