|
| 1 | +/* |
| 2 | + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one |
| 3 | + * or more contributor license agreements. Licensed under the Elastic License |
| 4 | + * 2.0 and the Server Side Public License, v 1; you may not use this file except |
| 5 | + * in compliance with, at your election, the Elastic License 2.0 or the Server |
| 6 | + * Side Public License, v 1. |
| 7 | + */ |
| 8 | + |
| 9 | +package org.elasticsearch.gradle.plugin; |
| 10 | + |
| 11 | +import org.elasticsearch.gradle.Version; |
| 12 | +import org.elasticsearch.gradle.VersionProperties; |
| 13 | +import org.elasticsearch.gradle.dependencies.CompileOnlyResolvePlugin; |
| 14 | +import org.elasticsearch.gradle.jarhell.JarHellPlugin; |
| 15 | +import org.elasticsearch.gradle.test.GradleTestPolicySetupPlugin; |
| 16 | +import org.elasticsearch.gradle.testclusters.ElasticsearchCluster; |
| 17 | +import org.elasticsearch.gradle.testclusters.RunTask; |
| 18 | +import org.elasticsearch.gradle.testclusters.TestClustersPlugin; |
| 19 | +import org.elasticsearch.gradle.util.GradleUtils; |
| 20 | +import org.gradle.api.NamedDomainObjectContainer; |
| 21 | +import org.gradle.api.Plugin; |
| 22 | +import org.gradle.api.Project; |
| 23 | +import org.gradle.api.Task; |
| 24 | +import org.gradle.api.Transformer; |
| 25 | +import org.gradle.api.artifacts.type.ArtifactTypeDefinition; |
| 26 | +import org.gradle.api.file.CopySpec; |
| 27 | +import org.gradle.api.file.FileCollection; |
| 28 | +import org.gradle.api.file.RegularFile; |
| 29 | +import org.gradle.api.plugins.BasePlugin; |
| 30 | +import org.gradle.api.plugins.JavaPlugin; |
| 31 | +import org.gradle.api.plugins.JavaPluginExtension; |
| 32 | +import org.gradle.api.provider.Provider; |
| 33 | +import org.gradle.api.provider.ProviderFactory; |
| 34 | +import org.gradle.api.tasks.SourceSet; |
| 35 | +import org.gradle.api.tasks.SourceSetContainer; |
| 36 | +import org.gradle.api.tasks.Sync; |
| 37 | +import org.gradle.api.tasks.TaskProvider; |
| 38 | +import org.gradle.api.tasks.bundling.Zip; |
| 39 | + |
| 40 | +import java.io.File; |
| 41 | +import java.util.Map; |
| 42 | +import java.util.concurrent.Callable; |
| 43 | + |
| 44 | +import javax.inject.Inject; |
| 45 | + |
| 46 | +/** |
| 47 | + * Common logic for building ES plugins. |
| 48 | + * Requires plugin extension to be created before applying |
| 49 | + */ |
| 50 | +public class BasePluginBuildPlugin implements Plugin<Project> { |
| 51 | + |
| 52 | + public static final String PLUGIN_EXTENSION_NAME = "esplugin"; |
| 53 | + public static final String BUNDLE_PLUGIN_TASK_NAME = "bundlePlugin"; |
| 54 | + public static final String EXPLODED_BUNDLE_PLUGIN_TASK_NAME = "explodedBundlePlugin"; |
| 55 | + public static final String EXPLODED_BUNDLE_CONFIG = "explodedBundleZip"; |
| 56 | + |
| 57 | + protected final ProviderFactory providerFactory; |
| 58 | + |
| 59 | + @Inject |
| 60 | + public BasePluginBuildPlugin(ProviderFactory providerFactory) { |
| 61 | + this.providerFactory = providerFactory; |
| 62 | + } |
| 63 | + |
| 64 | + @Override |
| 65 | + public void apply(final Project project) { |
| 66 | + project.getPluginManager().apply(JavaPlugin.class); |
| 67 | + project.getPluginManager().apply(TestClustersPlugin.class); |
| 68 | + project.getPluginManager().apply(CompileOnlyResolvePlugin.class); |
| 69 | + project.getPluginManager().apply(JarHellPlugin.class); |
| 70 | + project.getPluginManager().apply(GradleTestPolicySetupPlugin.class); |
| 71 | + |
| 72 | + var extension = project.getExtensions() |
| 73 | + .create(BasePluginBuildPlugin.PLUGIN_EXTENSION_NAME, PluginPropertiesExtension.class, project); |
| 74 | + |
| 75 | + final var bundleTask = createBundleTasks(project, extension); |
| 76 | + project.getConfigurations().getByName("default").extendsFrom(project.getConfigurations().getByName("runtimeClasspath")); |
| 77 | + |
| 78 | + // allow running ES with this plugin in the foreground of a build |
| 79 | + var testClusters = testClusters(project, TestClustersPlugin.EXTENSION_NAME); |
| 80 | + var runCluster = testClusters.register("runTask", c -> { |
| 81 | + // TODO: use explodedPlugin here for modules |
| 82 | + if (GradleUtils.isModuleProject(project.getPath())) { |
| 83 | + c.module(bundleTask.flatMap((Transformer<Provider<RegularFile>, Zip>) zip -> zip.getArchiveFile())); |
| 84 | + } else { |
| 85 | + c.plugin(bundleTask.flatMap((Transformer<Provider<RegularFile>, Zip>) zip -> zip.getArchiveFile())); |
| 86 | + } |
| 87 | + }); |
| 88 | + |
| 89 | + project.getTasks().register("run", RunTask.class, r -> { |
| 90 | + r.useCluster(runCluster); |
| 91 | + r.dependsOn(project.getTasks().named(BUNDLE_PLUGIN_TASK_NAME)); |
| 92 | + }); |
| 93 | + } |
| 94 | + |
| 95 | + @SuppressWarnings("unchecked") |
| 96 | + private static NamedDomainObjectContainer<ElasticsearchCluster> testClusters(Project project, String extensionName) { |
| 97 | + return (NamedDomainObjectContainer<ElasticsearchCluster>) project.getExtensions().getByName(extensionName); |
| 98 | + } |
| 99 | + |
| 100 | + /** |
| 101 | + * Adds bundle tasks which builds the dir and zip containing the plugin jars, |
| 102 | + * metadata, properties, and packaging files |
| 103 | + */ |
| 104 | + private TaskProvider<Zip> createBundleTasks(final Project project, PluginPropertiesExtension extension) { |
| 105 | + final var pluginMetadata = project.file("src/main/plugin-metadata"); |
| 106 | + |
| 107 | + final var buildProperties = project.getTasks().register("pluginProperties", GeneratePluginPropertiesTask.class, task -> { |
| 108 | + task.getPluginName().set(providerFactory.provider(extension::getName)); |
| 109 | + task.getPluginDescription().set(providerFactory.provider(extension::getDescription)); |
| 110 | + task.getPluginVersion().set(providerFactory.provider(extension::getVersion)); |
| 111 | + task.getElasticsearchVersion().set(Version.fromString(VersionProperties.getElasticsearch()).toString()); |
| 112 | + var javaExtension = project.getExtensions().getByType(JavaPluginExtension.class); |
| 113 | + task.getJavaVersion().set(providerFactory.provider(() -> javaExtension.getTargetCompatibility().toString())); |
| 114 | + task.getExtendedPlugins().set(providerFactory.provider(extension::getExtendedPlugins)); |
| 115 | + task.getHasNativeController().set(providerFactory.provider(extension::isHasNativeController)); |
| 116 | + task.getRequiresKeystore().set(providerFactory.provider(extension::isRequiresKeystore)); |
| 117 | + task.getIsLicensed().set(providerFactory.provider(extension::isLicensed)); |
| 118 | + |
| 119 | + var mainSourceSet = project.getExtensions().getByType(SourceSetContainer.class).getByName(SourceSet.MAIN_SOURCE_SET_NAME); |
| 120 | + FileCollection moduleInfoFile = mainSourceSet.getOutput().getAsFileTree().matching(p -> p.include("module-info.class")); |
| 121 | + task.getModuleInfoFile().setFrom(moduleInfoFile); |
| 122 | + |
| 123 | + }); |
| 124 | + // add the plugin properties and metadata to test resources, so unit tests can |
| 125 | + // know about the plugin (used by test security code to statically initialize the plugin in unit tests) |
| 126 | + var testSourceSet = project.getExtensions().getByType(SourceSetContainer.class).getByName("test"); |
| 127 | + Map<String, Object> map = Map.of("builtBy", buildProperties); |
| 128 | + testSourceSet.getOutput().dir(map, new File(project.getBuildDir(), "generated-resources")); |
| 129 | + testSourceSet.getResources().srcDir(pluginMetadata); |
| 130 | + |
| 131 | + var bundleSpec = createBundleSpec(project, pluginMetadata, buildProperties); |
| 132 | + extension.setBundleSpec(bundleSpec); |
| 133 | + // create the actual bundle task, which zips up all the files for the plugin |
| 134 | + final var bundle = project.getTasks().register("bundlePlugin", Zip.class, zip -> zip.with(bundleSpec)); |
| 135 | + project.getTasks().named(BasePlugin.ASSEMBLE_TASK_NAME).configure(task -> task.dependsOn(bundle)); |
| 136 | + |
| 137 | + // also make the zip available as a configuration (used when depending on this project) |
| 138 | + var configuration = project.getConfigurations().create("zip"); |
| 139 | + configuration.getAttributes().attribute(ArtifactTypeDefinition.ARTIFACT_TYPE_ATTRIBUTE, ArtifactTypeDefinition.ZIP_TYPE); |
| 140 | + project.getArtifacts().add("zip", bundle); |
| 141 | + |
| 142 | + var explodedBundle = project.getTasks().register(EXPLODED_BUNDLE_PLUGIN_TASK_NAME, Sync.class, sync -> { |
| 143 | + sync.with(bundleSpec); |
| 144 | + sync.into(new File(project.getBuildDir(), "explodedBundle/" + extension.getName())); |
| 145 | + }); |
| 146 | + |
| 147 | + // also make the exploded bundle available as a configuration (used when depending on this project) |
| 148 | + var explodedBundleZip = project.getConfigurations().create(EXPLODED_BUNDLE_CONFIG); |
| 149 | + explodedBundleZip.setCanBeResolved(false); |
| 150 | + explodedBundleZip.setCanBeConsumed(true); |
| 151 | + explodedBundleZip.getAttributes().attribute(ArtifactTypeDefinition.ARTIFACT_TYPE_ATTRIBUTE, ArtifactTypeDefinition.DIRECTORY_TYPE); |
| 152 | + project.getArtifacts().add(EXPLODED_BUNDLE_CONFIG, explodedBundle); |
| 153 | + return bundle; |
| 154 | + } |
| 155 | + |
| 156 | + private static CopySpec createBundleSpec( |
| 157 | + Project project, |
| 158 | + File pluginMetadata, |
| 159 | + TaskProvider<GeneratePluginPropertiesTask> buildProperties |
| 160 | + ) { |
| 161 | + var bundleSpec = project.copySpec(); |
| 162 | + bundleSpec.from(buildProperties); |
| 163 | + bundleSpec.from(pluginMetadata, copySpec -> { |
| 164 | + // metadata (eg custom security policy) |
| 165 | + // the codebases properties file is only for tests and not needed in production |
| 166 | + copySpec.exclude("plugin-security.codebases"); |
| 167 | + }); |
| 168 | + bundleSpec.from( |
| 169 | + (Callable<TaskProvider<Task>>) () -> project.getPluginManager().hasPlugin("com.github.johnrengelman.shadow") |
| 170 | + ? project.getTasks().named("shadowJar") |
| 171 | + : project.getTasks().named("jar") |
| 172 | + ); |
| 173 | + bundleSpec.from( |
| 174 | + project.getConfigurations() |
| 175 | + .getByName("runtimeClasspath") |
| 176 | + .minus(project.getConfigurations().getByName(CompileOnlyResolvePlugin.RESOLVEABLE_COMPILE_ONLY_CONFIGURATION_NAME)) |
| 177 | + ); |
| 178 | + |
| 179 | + // extra files for the plugin to go into the zip |
| 180 | + bundleSpec.from("src/main/packaging");// TODO: move all config/bin/_size/etc into packaging |
| 181 | + bundleSpec.from("src/main", copySpec -> { |
| 182 | + copySpec.include("config/**"); |
| 183 | + copySpec.include("bin/**"); |
| 184 | + }); |
| 185 | + return bundleSpec; |
| 186 | + } |
| 187 | +} |
0 commit comments