From f135b63a72e11df143ca6bdc83d20d5bb1b2f1c5 Mon Sep 17 00:00:00 2001 From: Przemyslaw Gomulka Date: Mon, 26 Sep 2022 15:48:58 +0200 Subject: [PATCH] Create gradle plugin for ES stable plugins New ES stable plugins when built should have a stable-plugin-descriptor.properties file instead of plugin-descriptor.properties. New plugins also do not use classname property in the plugin descriptor relates #88980 --- .../ElasticsearchDistributionExtension.java | 2 +- .../internal/test/RestTestBasePlugin.java | 4 +- build-tools/build.gradle | 4 + .../plugin/PluginBuildPluginFuncTest.groovy | 40 +++- .../gradle/plugin/BasePluginBuildPlugin.java | 186 ++++++++++++++++++ .../plugin/BasePluginPropertiesExtension.java | 151 ++++++++++++++ .../plugin/GeneratePluginPropertiesTask.java | 26 ++- .../gradle/plugin/PluginBuildPlugin.java | 158 ++------------- .../plugin/PluginPropertiesExtension.java | 135 +------------ .../gradle/plugin/StableBuildPlugin.java | 35 ++++ .../gradle/test/JavaRestTestPlugin.java | 2 +- .../gradle/test/YamlRestTestPlugin.java | 4 +- .../testclusters/ElasticsearchNode.java | 2 +- .../stable-plugin-descriptor.properties | 54 +++++ 14 files changed, 509 insertions(+), 294 deletions(-) create mode 100644 build-tools/src/main/java/org/elasticsearch/gradle/plugin/BasePluginBuildPlugin.java create mode 100644 build-tools/src/main/java/org/elasticsearch/gradle/plugin/BasePluginPropertiesExtension.java create mode 100644 build-tools/src/main/java/org/elasticsearch/gradle/plugin/StableBuildPlugin.java create mode 100644 build-tools/src/main/resources/stable-plugin-descriptor.properties diff --git a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/distribution/ElasticsearchDistributionExtension.java b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/distribution/ElasticsearchDistributionExtension.java index a62c7900f4fed..a5cf60f37f553 100644 --- a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/distribution/ElasticsearchDistributionExtension.java +++ b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/distribution/ElasticsearchDistributionExtension.java @@ -19,7 +19,7 @@ import java.util.concurrent.Callable; import java.util.regex.Pattern; -import static org.elasticsearch.gradle.plugin.PluginBuildPlugin.EXPLODED_BUNDLE_CONFIG; +import static org.elasticsearch.gradle.plugin.BasePluginBuildPlugin.EXPLODED_BUNDLE_CONFIG; public class ElasticsearchDistributionExtension { private static final Pattern CONFIG_BIN_REGEX_PATTERN = Pattern.compile("([^\\/]+\\/)?(config|bin)\\/.*"); diff --git a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/test/RestTestBasePlugin.java b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/test/RestTestBasePlugin.java index 0b7338f6d96b3..fc27bfa43798c 100644 --- a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/test/RestTestBasePlugin.java +++ b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/test/RestTestBasePlugin.java @@ -28,8 +28,8 @@ import javax.inject.Inject; -import static org.elasticsearch.gradle.plugin.PluginBuildPlugin.BUNDLE_PLUGIN_TASK_NAME; -import static org.elasticsearch.gradle.plugin.PluginBuildPlugin.EXPLODED_BUNDLE_PLUGIN_TASK_NAME; +import static org.elasticsearch.gradle.plugin.BasePluginBuildPlugin.BUNDLE_PLUGIN_TASK_NAME; +import static org.elasticsearch.gradle.plugin.BasePluginBuildPlugin.EXPLODED_BUNDLE_PLUGIN_TASK_NAME; public class RestTestBasePlugin implements Plugin { private static final String TESTS_REST_CLUSTER = "tests.rest.cluster"; diff --git a/build-tools/build.gradle b/build-tools/build.gradle index 093a12bf97fde..916b7ec26e12d 100644 --- a/build-tools/build.gradle +++ b/build-tools/build.gradle @@ -37,6 +37,10 @@ gradlePlugin { id = 'elasticsearch.esplugin' implementationClass = 'org.elasticsearch.gradle.plugin.PluginBuildPlugin' } + stableEsPlugin { + id = 'elasticsearch.stable_esplugin' + implementationClass = 'org.elasticsearch.gradle.plugin.StableBuildPlugin' + } javaRestTest { id = 'elasticsearch.java-rest-test' implementationClass = 'org.elasticsearch.gradle.test.JavaRestTestPlugin' diff --git a/build-tools/src/integTest/groovy/org/elasticsearch/gradle/plugin/PluginBuildPluginFuncTest.groovy b/build-tools/src/integTest/groovy/org/elasticsearch/gradle/plugin/PluginBuildPluginFuncTest.groovy index d486e109a9307..5642eec729e84 100644 --- a/build-tools/src/integTest/groovy/org/elasticsearch/gradle/plugin/PluginBuildPluginFuncTest.groovy +++ b/build-tools/src/integTest/groovy/org/elasticsearch/gradle/plugin/PluginBuildPluginFuncTest.groovy @@ -75,7 +75,7 @@ class PluginBuildPluginFuncTest extends AbstractGradleFuncTest { } dependencies { - consume project(path:':', configuration:'${PluginBuildPlugin.EXPLODED_BUNDLE_CONFIG}') + consume project(path:':', configuration:'${BasePluginBuildPlugin.EXPLODED_BUNDLE_CONFIG}') } tasks.register("resolveModule", Copy) { @@ -127,6 +127,38 @@ class PluginBuildPluginFuncTest extends AbstractGradleFuncTest { props.size() == 9 } + def "can build stable plugin properties"() { + given: + buildFile << """plugins { + id 'elasticsearch.stable_esplugin' + } + + version = '1.2.3' + + esplugin { + name = 'myplugin' + description = 'test plugin' + } + """ + + when: + def result = gradleRunner(":pluginProperties").build() + def props = getPluginProperties("build/generated-descriptor/stable-plugin-descriptor.properties") + + then: + result.task(":pluginProperties").outcome == TaskOutcome.SUCCESS + props.get("name") == "myplugin" + props.get("version") == "1.2.3" + props.get("description") == "test plugin" + props.get("modulename") == "" + props.get("java.version") == Integer.toString(Runtime.version().feature()) + props.get("elasticsearch.version") == VersionProperties.elasticsearchVersion.toString() + props.get("extended.plugins") == "" + props.get("has.native.controller") == "false" + props.get("stable") == "true" + props.size() == 9 + } + def "module name is inferred by plugin properties"() { given: buildFile << """plugins { @@ -157,7 +189,11 @@ class PluginBuildPluginFuncTest extends AbstractGradleFuncTest { } Map getPluginProperties() { - Path propsFile = file("build/generated-descriptor/plugin-descriptor.properties").toPath(); + return getPluginProperties("build/generated-descriptor/plugin-descriptor.properties") + } + + Map getPluginProperties(String fileName) { + Path propsFile = file(fileName).toPath(); Properties rawProps = new Properties() try (var inputStream = Files.newInputStream(propsFile)) { rawProps.load(inputStream) diff --git a/build-tools/src/main/java/org/elasticsearch/gradle/plugin/BasePluginBuildPlugin.java b/build-tools/src/main/java/org/elasticsearch/gradle/plugin/BasePluginBuildPlugin.java new file mode 100644 index 0000000000000..06d6454a46be7 --- /dev/null +++ b/build-tools/src/main/java/org/elasticsearch/gradle/plugin/BasePluginBuildPlugin.java @@ -0,0 +1,186 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +package org.elasticsearch.gradle.plugin; + +import org.elasticsearch.gradle.Version; +import org.elasticsearch.gradle.VersionProperties; +import org.elasticsearch.gradle.dependencies.CompileOnlyResolvePlugin; +import org.elasticsearch.gradle.jarhell.JarHellPlugin; +import org.elasticsearch.gradle.test.GradleTestPolicySetupPlugin; +import org.elasticsearch.gradle.testclusters.ElasticsearchCluster; +import org.elasticsearch.gradle.testclusters.RunTask; +import org.elasticsearch.gradle.testclusters.TestClustersPlugin; +import org.elasticsearch.gradle.util.GradleUtils; +import org.gradle.api.NamedDomainObjectContainer; +import org.gradle.api.Plugin; +import org.gradle.api.Project; +import org.gradle.api.Task; +import org.gradle.api.Transformer; +import org.gradle.api.artifacts.type.ArtifactTypeDefinition; +import org.gradle.api.file.CopySpec; +import org.gradle.api.file.FileCollection; +import org.gradle.api.file.RegularFile; +import org.gradle.api.plugins.BasePlugin; +import org.gradle.api.plugins.JavaPlugin; +import org.gradle.api.plugins.JavaPluginExtension; +import org.gradle.api.provider.Provider; +import org.gradle.api.provider.ProviderFactory; +import org.gradle.api.tasks.SourceSet; +import org.gradle.api.tasks.SourceSetContainer; +import org.gradle.api.tasks.Sync; +import org.gradle.api.tasks.TaskProvider; +import org.gradle.api.tasks.bundling.Zip; + +import java.io.File; +import java.util.Map; +import java.util.concurrent.Callable; + +import javax.inject.Inject; + +/** + * Common logic for building ES plugins. + * Requires plugin extension to be created before applying + */ +public class BasePluginBuildPlugin implements Plugin { + + public static final String PLUGIN_EXTENSION_NAME = "esplugin"; + public static final String BUNDLE_PLUGIN_TASK_NAME = "bundlePlugin"; + public static final String EXPLODED_BUNDLE_PLUGIN_TASK_NAME = "explodedBundlePlugin"; + public static final String EXPLODED_BUNDLE_CONFIG = "explodedBundleZip"; + + protected final ProviderFactory providerFactory; + + @Inject + public BasePluginBuildPlugin(ProviderFactory providerFactory) { + this.providerFactory = providerFactory; + } + + @Override + public void apply(final Project project) { + project.getPluginManager().apply(JavaPlugin.class); + project.getPluginManager().apply(TestClustersPlugin.class); + project.getPluginManager().apply(CompileOnlyResolvePlugin.class); + project.getPluginManager().apply(JarHellPlugin.class); + project.getPluginManager().apply(GradleTestPolicySetupPlugin.class); + + var extension = (BasePluginPropertiesExtension) project.getExtensions().getByName(BasePluginBuildPlugin.PLUGIN_EXTENSION_NAME); + + final var bundleTask = createBundleTasks(project, extension); + project.getConfigurations().getByName("default").extendsFrom(project.getConfigurations().getByName("runtimeClasspath")); + + // allow running ES with this plugin in the foreground of a build + var testClusters = testClusters(project, TestClustersPlugin.EXTENSION_NAME); + var runCluster = testClusters.register("runTask", c -> { + // TODO: use explodedPlugin here for modules + if (GradleUtils.isModuleProject(project.getPath())) { + c.module(bundleTask.flatMap((Transformer, Zip>) zip -> zip.getArchiveFile())); + } else { + c.plugin(bundleTask.flatMap((Transformer, Zip>) zip -> zip.getArchiveFile())); + } + }); + + project.getTasks().register("run", RunTask.class, r -> { + r.useCluster(runCluster); + r.dependsOn(project.getTasks().named(BUNDLE_PLUGIN_TASK_NAME)); + }); + } + + @SuppressWarnings("unchecked") + private static NamedDomainObjectContainer testClusters(Project project, String extensionName) { + return (NamedDomainObjectContainer) project.getExtensions().getByName(extensionName); + } + + /** + * Adds bundle tasks which builds the dir and zip containing the plugin jars, + * metadata, properties, and packaging files + */ + private TaskProvider createBundleTasks(final Project project, BasePluginPropertiesExtension extension) { + final var pluginMetadata = project.file("src/main/plugin-metadata"); + + final var buildProperties = project.getTasks().register("pluginProperties", GeneratePluginPropertiesTask.class, task -> { + task.getPluginName().set(providerFactory.provider(extension::getName)); + task.getPluginDescription().set(providerFactory.provider(extension::getDescription)); + task.getPluginVersion().set(providerFactory.provider(extension::getVersion)); + task.getElasticsearchVersion().set(Version.fromString(VersionProperties.getElasticsearch()).toString()); + var javaExtension = project.getExtensions().getByType(JavaPluginExtension.class); + task.getJavaVersion().set(providerFactory.provider(() -> javaExtension.getTargetCompatibility().toString())); + task.getExtendedPlugins().set(providerFactory.provider(extension::getExtendedPlugins)); + task.getHasNativeController().set(providerFactory.provider(extension::isHasNativeController)); + task.getRequiresKeystore().set(providerFactory.provider(extension::isRequiresKeystore)); + task.getIsLicensed().set(providerFactory.provider(extension::isLicensed)); + + var mainSourceSet = project.getExtensions().getByType(SourceSetContainer.class).getByName(SourceSet.MAIN_SOURCE_SET_NAME); + FileCollection moduleInfoFile = mainSourceSet.getOutput().getAsFileTree().matching(p -> p.include("module-info.class")); + task.getModuleInfoFile().setFrom(moduleInfoFile); + + }); + // add the plugin properties and metadata to test resources, so unit tests can + // know about the plugin (used by test security code to statically initialize the plugin in unit tests) + var testSourceSet = project.getExtensions().getByType(SourceSetContainer.class).getByName("test"); + Map map = Map.of("builtBy", buildProperties); + testSourceSet.getOutput().dir(map, new File(project.getBuildDir(), "generated-resources")); + testSourceSet.getResources().srcDir(pluginMetadata); + + var bundleSpec = createBundleSpec(project, pluginMetadata, buildProperties); + extension.setBundleSpec(bundleSpec); + // create the actual bundle task, which zips up all the files for the plugin + final var bundle = project.getTasks().register("bundlePlugin", Zip.class, zip -> zip.with(bundleSpec)); + project.getTasks().named(BasePlugin.ASSEMBLE_TASK_NAME).configure(task -> task.dependsOn(bundle)); + + // also make the zip available as a configuration (used when depending on this project) + var configuration = project.getConfigurations().create("zip"); + configuration.getAttributes().attribute(ArtifactTypeDefinition.ARTIFACT_TYPE_ATTRIBUTE, ArtifactTypeDefinition.ZIP_TYPE); + project.getArtifacts().add("zip", bundle); + + var explodedBundle = project.getTasks().register(EXPLODED_BUNDLE_PLUGIN_TASK_NAME, Sync.class, sync -> { + sync.with(bundleSpec); + sync.into(new File(project.getBuildDir(), "explodedBundle/" + extension.getName())); + }); + + // also make the exploded bundle available as a configuration (used when depending on this project) + var explodedBundleZip = project.getConfigurations().create(EXPLODED_BUNDLE_CONFIG); + explodedBundleZip.setCanBeResolved(false); + explodedBundleZip.setCanBeConsumed(true); + explodedBundleZip.getAttributes().attribute(ArtifactTypeDefinition.ARTIFACT_TYPE_ATTRIBUTE, ArtifactTypeDefinition.DIRECTORY_TYPE); + project.getArtifacts().add(EXPLODED_BUNDLE_CONFIG, explodedBundle); + return bundle; + } + + private static CopySpec createBundleSpec( + Project project, + File pluginMetadata, + TaskProvider buildProperties + ) { + var bundleSpec = project.copySpec(); + bundleSpec.from(buildProperties); + bundleSpec.from(pluginMetadata, copySpec -> { + // metadata (eg custom security policy) + // the codebases properties file is only for tests and not needed in production + copySpec.exclude("plugin-security.codebases"); + }); + bundleSpec.from( + (Callable>) () -> project.getPluginManager().hasPlugin("com.github.johnrengelman.shadow") + ? project.getTasks().named("shadowJar") + : project.getTasks().named("jar") + ); + bundleSpec.from( + project.getConfigurations() + .getByName("runtimeClasspath") + .minus(project.getConfigurations().getByName(CompileOnlyResolvePlugin.RESOLVEABLE_COMPILE_ONLY_CONFIGURATION_NAME)) + ); + + // extra files for the plugin to go into the zip + bundleSpec.from("src/main/packaging");// TODO: move all config/bin/_size/etc into packaging + bundleSpec.from("src/main", copySpec -> { + copySpec.include("config/**"); + copySpec.include("bin/**"); + }); + return bundleSpec; + } +} diff --git a/build-tools/src/main/java/org/elasticsearch/gradle/plugin/BasePluginPropertiesExtension.java b/build-tools/src/main/java/org/elasticsearch/gradle/plugin/BasePluginPropertiesExtension.java new file mode 100644 index 0000000000000..ef4257df6855d --- /dev/null +++ b/build-tools/src/main/java/org/elasticsearch/gradle/plugin/BasePluginPropertiesExtension.java @@ -0,0 +1,151 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +package org.elasticsearch.gradle.plugin; + +import org.gradle.api.Project; +import org.gradle.api.file.CopySpec; +import org.gradle.api.file.RegularFileProperty; +import org.gradle.api.plugins.ExtraPropertiesExtension; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +public abstract class BasePluginPropertiesExtension { + + private String name; + + private String version; + + private String description; + + /** Other plugins this plugin extends through SPI */ + private List extendedPlugins = new ArrayList<>(); + + private boolean hasNativeController; + + /** Whether a license agreement must be accepted before this plugin can be installed. */ + private boolean isLicensed = false; + + /** True if the plugin requires the elasticsearch keystore to exist, false otherwise. */ + private boolean requiresKeystore; + + /** A license file that should be included in the built plugin zip. */ + private File licenseFile; + + /** + * A notice file that should be included in the built plugin zip. This will be + * extended with notices from the {@code licenses/} directory. + */ + private File noticeFile; + + private final Project project; + private CopySpec bundleSpec; + + public BasePluginPropertiesExtension(Project project) { + this.project = project; + } + + public String getName() { + return name == null ? project.getName() : name; + } + + public void setName(String name) { + this.project.setProperty("archivesBaseName", name); + this.name = name; + } + + public String getVersion() { + return version == null ? project.getVersion().toString() : version; + } + + public void setVersion(String version) { + this.version = version; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + project.setDescription(description); + this.description = description; + } + + public List getExtendedPlugins() { + return this.extendedPlugins; + } + + public boolean isHasNativeController() { + return hasNativeController; + } + + public void setHasNativeController(boolean hasNativeController) { + this.hasNativeController = hasNativeController; + } + + public boolean isLicensed() { + return isLicensed; + } + + public void setLicensed(boolean licensed) { + isLicensed = licensed; + } + + public boolean isRequiresKeystore() { + return requiresKeystore; + } + + public void setRequiresKeystore(boolean requiresKeystore) { + this.requiresKeystore = requiresKeystore; + } + + public File getLicenseFile() { + return licenseFile; + } + + public void setLicenseFile(File licenseFile) { + ExtraPropertiesExtension extraProperties = this.project.getExtensions().getExtraProperties(); + RegularFileProperty regularFileProperty = extraProperties.has("licenseFile") + ? (RegularFileProperty) extraProperties.get("licenseFile") + : project.getObjects().fileProperty(); + regularFileProperty.set(licenseFile); + this.licenseFile = licenseFile; + } + + public File getNoticeFile() { + return noticeFile; + } + + public void setNoticeFile(File noticeFile) { + ExtraPropertiesExtension extraProperties = this.project.getExtensions().getExtraProperties(); + RegularFileProperty regularFileProperty = extraProperties.has("noticeFile") + ? (RegularFileProperty) extraProperties.get("noticeFile") + : project.getObjects().fileProperty(); + regularFileProperty.set(noticeFile); + this.noticeFile = noticeFile; + } + + public Project getProject() { + return project; + } + + public void setExtendedPlugins(List extendedPlugins) { + this.extendedPlugins = extendedPlugins; + } + + public void setBundleSpec(CopySpec bundleSpec) { + this.bundleSpec = bundleSpec; + } + + public CopySpec getBundleSpec() { + return bundleSpec; + } + +} diff --git a/build-tools/src/main/java/org/elasticsearch/gradle/plugin/GeneratePluginPropertiesTask.java b/build-tools/src/main/java/org/elasticsearch/gradle/plugin/GeneratePluginPropertiesTask.java index 2880d4ec3c988..aee376a7d8472 100644 --- a/build-tools/src/main/java/org/elasticsearch/gradle/plugin/GeneratePluginPropertiesTask.java +++ b/build-tools/src/main/java/org/elasticsearch/gradle/plugin/GeneratePluginPropertiesTask.java @@ -20,6 +20,7 @@ import org.gradle.api.provider.Property; import org.gradle.api.tasks.Input; import org.gradle.api.tasks.InputFiles; +import org.gradle.api.tasks.Optional; import org.gradle.api.tasks.OutputFile; import org.gradle.api.tasks.TaskAction; import org.objectweb.asm.ClassReader; @@ -39,12 +40,13 @@ public abstract class GeneratePluginPropertiesTask extends DefaultTask { - private static final String PROPERTIES_FILENAME = "plugin-descriptor.properties"; + public static final String PROPERTIES_FILENAME = "plugin-descriptor.properties"; + public static final String STABLE_PROPERTIES_FILENAME = "stable-plugin-descriptor.properties"; @Inject public GeneratePluginPropertiesTask(ProjectLayout projectLayout) { - setDescription("Generate " + PROPERTIES_FILENAME); - getOutputFile().convention(projectLayout.getBuildDirectory().file("generated-descriptor/" + PROPERTIES_FILENAME)); + // TODO I cannot tell if this is stable or not.. + setDescription("Generate " + PROPERTIES_FILENAME + " or " + STABLE_PROPERTIES_FILENAME); } @Input @@ -63,6 +65,7 @@ public GeneratePluginPropertiesTask(ProjectLayout projectLayout) { public abstract Property getJavaVersion(); @Input + @Optional public abstract Property getClassname(); @Input @@ -83,10 +86,17 @@ public GeneratePluginPropertiesTask(ProjectLayout projectLayout) { @OutputFile public abstract RegularFileProperty getOutputFile(); + @Input + public abstract Property getTemplateFileName(); + + @Input + public abstract Property getIsStable(); + @TaskAction public void generatePropertiesFile() throws IOException { String classname = getClassname().getOrElse(""); - if (classname.isEmpty()) { + boolean stablePlugin = getIsStable().getOrElse(false); + if (stablePlugin == false && classname.isEmpty()) { throw new InvalidUserDataException("classname is a required setting for esplugin"); } @@ -96,7 +106,9 @@ public void generatePropertiesFile() throws IOException { props.put("version", getPluginVersion().get()); props.put("elasticsearchVersion", getElasticsearchVersion().get()); props.put("javaVersion", getJavaVersion().get()); - props.put("classname", classname); + if (classname.isEmpty() == false) { + props.put("classname", classname); + } props.put("extendedPlugins", String.join(",", getExtendedPlugins().get())); props.put("hasNativeController", getHasNativeController().get()); props.put("requiresKeystore", getRequiresKeystore().get()); @@ -106,8 +118,9 @@ public void generatePropertiesFile() throws IOException { SimpleTemplateEngine engine = new SimpleTemplateEngine(); Path outputFile = getOutputFile().get().getAsFile().toPath(); Files.createDirectories(outputFile.getParent()); + String name = "/" + getTemplateFileName().get(); try ( - var inputStream = GeneratePluginPropertiesTask.class.getResourceAsStream("/" + PROPERTIES_FILENAME); + var inputStream = GeneratePluginPropertiesTask.class.getResourceAsStream(name); var reader = new BufferedReader(new InputStreamReader(inputStream)); var writer = Files.newBufferedWriter(outputFile, StandardCharsets.UTF_8) ) { @@ -129,4 +142,5 @@ private String findModuleName() { } return visitor.module.name; } + } diff --git a/build-tools/src/main/java/org/elasticsearch/gradle/plugin/PluginBuildPlugin.java b/build-tools/src/main/java/org/elasticsearch/gradle/plugin/PluginBuildPlugin.java index 908b409aaac3b..37a60ee3bff31 100644 --- a/build-tools/src/main/java/org/elasticsearch/gradle/plugin/PluginBuildPlugin.java +++ b/build-tools/src/main/java/org/elasticsearch/gradle/plugin/PluginBuildPlugin.java @@ -8,38 +8,12 @@ package org.elasticsearch.gradle.plugin; -import org.elasticsearch.gradle.Version; import org.elasticsearch.gradle.VersionProperties; -import org.elasticsearch.gradle.dependencies.CompileOnlyResolvePlugin; -import org.elasticsearch.gradle.jarhell.JarHellPlugin; -import org.elasticsearch.gradle.test.GradleTestPolicySetupPlugin; -import org.elasticsearch.gradle.testclusters.ElasticsearchCluster; -import org.elasticsearch.gradle.testclusters.RunTask; -import org.elasticsearch.gradle.testclusters.TestClustersPlugin; -import org.elasticsearch.gradle.util.GradleUtils; -import org.gradle.api.NamedDomainObjectContainer; import org.gradle.api.Plugin; import org.gradle.api.Project; -import org.gradle.api.Task; -import org.gradle.api.Transformer; -import org.gradle.api.artifacts.type.ArtifactTypeDefinition; -import org.gradle.api.file.CopySpec; -import org.gradle.api.file.FileCollection; import org.gradle.api.file.RegularFile; -import org.gradle.api.plugins.BasePlugin; -import org.gradle.api.plugins.JavaPlugin; -import org.gradle.api.plugins.JavaPluginExtension; import org.gradle.api.provider.Provider; import org.gradle.api.provider.ProviderFactory; -import org.gradle.api.tasks.SourceSet; -import org.gradle.api.tasks.SourceSetContainer; -import org.gradle.api.tasks.Sync; -import org.gradle.api.tasks.TaskProvider; -import org.gradle.api.tasks.bundling.Zip; - -import java.io.File; -import java.util.Map; -import java.util.concurrent.Callable; import javax.inject.Inject; @@ -48,12 +22,7 @@ */ public class PluginBuildPlugin implements Plugin { - public static final String PLUGIN_EXTENSION_NAME = "esplugin"; - public static final String BUNDLE_PLUGIN_TASK_NAME = "bundlePlugin"; - public static final String EXPLODED_BUNDLE_PLUGIN_TASK_NAME = "explodedBundlePlugin"; - public static final String EXPLODED_BUNDLE_CONFIG = "explodedBundleZip"; - - private final ProviderFactory providerFactory; + protected final ProviderFactory providerFactory; @Inject public PluginBuildPlugin(ProviderFactory providerFactory) { @@ -62,131 +31,28 @@ public PluginBuildPlugin(ProviderFactory providerFactory) { @Override public void apply(final Project project) { - project.getPluginManager().apply(JavaPlugin.class); - project.getPluginManager().apply(TestClustersPlugin.class); - project.getPluginManager().apply(CompileOnlyResolvePlugin.class); - project.getPluginManager().apply(JarHellPlugin.class); - project.getPluginManager().apply(GradleTestPolicySetupPlugin.class); - - var extension = project.getExtensions().create(PLUGIN_EXTENSION_NAME, PluginPropertiesExtension.class, project); - configureDependencies(project); - - final var bundleTask = createBundleTasks(project, extension); - project.getConfigurations().getByName("default").extendsFrom(project.getConfigurations().getByName("runtimeClasspath")); - - // allow running ES with this plugin in the foreground of a build - var testClusters = testClusters(project, TestClustersPlugin.EXTENSION_NAME); - var runCluster = testClusters.register("runTask", c -> { - // TODO: use explodedPlugin here for modules - if (GradleUtils.isModuleProject(project.getPath())) { - c.module(bundleTask.flatMap((Transformer, Zip>) zip -> zip.getArchiveFile())); - } else { - c.plugin(bundleTask.flatMap((Transformer, Zip>) zip -> zip.getArchiveFile())); - } - }); + var extension = project.getExtensions() + .create(BasePluginBuildPlugin.PLUGIN_EXTENSION_NAME, PluginPropertiesExtension.class, project); - project.getTasks().register("run", RunTask.class, r -> { - r.useCluster(runCluster); - r.dependsOn(project.getTasks().named(BUNDLE_PLUGIN_TASK_NAME)); - }); - } - - @SuppressWarnings("unchecked") - private static NamedDomainObjectContainer testClusters(Project project, String extensionName) { - return (NamedDomainObjectContainer) project.getExtensions().getByName(extensionName); - } + project.getPluginManager().apply(BasePluginBuildPlugin.class); - private static void configureDependencies(final Project project) { var dependencies = project.getDependencies(); dependencies.add("compileOnly", "org.elasticsearch:elasticsearch:" + VersionProperties.getElasticsearch()); dependencies.add("testImplementation", "org.elasticsearch.test:framework:" + VersionProperties.getElasticsearch()); - } - - /** - * Adds bundle tasks which builds the dir and zip containing the plugin jars, - * metadata, properties, and packaging files - */ - private TaskProvider createBundleTasks(final Project project, PluginPropertiesExtension extension) { - final var pluginMetadata = project.file("src/main/plugin-metadata"); - final var buildProperties = project.getTasks().register("pluginProperties", GeneratePluginPropertiesTask.class, task -> { - task.getPluginName().set(providerFactory.provider(extension::getName)); - task.getPluginDescription().set(providerFactory.provider(extension::getDescription)); - task.getPluginVersion().set(providerFactory.provider(extension::getVersion)); - task.getElasticsearchVersion().set(Version.fromString(VersionProperties.getElasticsearch()).toString()); - var javaExtension = project.getExtensions().getByType(JavaPluginExtension.class); - task.getJavaVersion().set(providerFactory.provider(() -> javaExtension.getTargetCompatibility().toString())); - task.getClassname().set(providerFactory.provider(extension::getClassname)); - task.getExtendedPlugins().set(providerFactory.provider(extension::getExtendedPlugins)); - task.getHasNativeController().set(providerFactory.provider(extension::isHasNativeController)); - task.getRequiresKeystore().set(providerFactory.provider(extension::isRequiresKeystore)); - task.getIsLicensed().set(providerFactory.provider(extension::isLicensed)); - - var mainSourceSet = project.getExtensions().getByType(SourceSetContainer.class).getByName(SourceSet.MAIN_SOURCE_SET_NAME); - FileCollection moduleInfoFile = mainSourceSet.getOutput().getAsFileTree().matching(p -> p.include("module-info.class")); - task.getModuleInfoFile().setFrom(moduleInfoFile); - }); - // add the plugin properties and metadata to test resources, so unit tests can - // know about the plugin (used by test security code to statically initialize the plugin in unit tests) - var testSourceSet = project.getExtensions().getByType(SourceSetContainer.class).getByName("test"); - Map map = Map.of("builtBy", buildProperties); - testSourceSet.getOutput().dir(map, new File(project.getBuildDir(), "generated-resources")); - testSourceSet.getResources().srcDir(pluginMetadata); + project.getTasks().withType(GeneratePluginPropertiesTask.class).named("pluginProperties").configure(task -> { + task.getIsStable().set(false); - var bundleSpec = createBundleSpec(project, pluginMetadata, buildProperties); - extension.setBundleSpec(bundleSpec); - // create the actual bundle task, which zips up all the files for the plugin - final var bundle = project.getTasks().register("bundlePlugin", Zip.class, zip -> zip.with(bundleSpec)); - project.getTasks().named(BasePlugin.ASSEMBLE_TASK_NAME).configure(task -> task.dependsOn(bundle)); + task.getTemplateFileName().set(GeneratePluginPropertiesTask.PROPERTIES_FILENAME); - // also make the zip available as a configuration (used when depending on this project) - var configuration = project.getConfigurations().create("zip"); - configuration.getAttributes().attribute(ArtifactTypeDefinition.ARTIFACT_TYPE_ATTRIBUTE, ArtifactTypeDefinition.ZIP_TYPE); - project.getArtifacts().add("zip", bundle); + task.getClassname().set(providerFactory.provider(extension::getClassname)); - var explodedBundle = project.getTasks().register(EXPLODED_BUNDLE_PLUGIN_TASK_NAME, Sync.class, sync -> { - sync.with(bundleSpec); - sync.into(new File(project.getBuildDir(), "explodedBundle/" + extension.getName())); + Provider file = project.getLayout() + .getBuildDirectory() + .file("generated-descriptor/" + GeneratePluginPropertiesTask.PROPERTIES_FILENAME); + task.getOutputFile().set(file); }); - // also make the exploded bundle available as a configuration (used when depending on this project) - var explodedBundleZip = project.getConfigurations().create(EXPLODED_BUNDLE_CONFIG); - explodedBundleZip.setCanBeResolved(false); - explodedBundleZip.setCanBeConsumed(true); - explodedBundleZip.getAttributes().attribute(ArtifactTypeDefinition.ARTIFACT_TYPE_ATTRIBUTE, ArtifactTypeDefinition.DIRECTORY_TYPE); - project.getArtifacts().add(EXPLODED_BUNDLE_CONFIG, explodedBundle); - return bundle; } - private static CopySpec createBundleSpec( - Project project, - File pluginMetadata, - TaskProvider buildProperties - ) { - var bundleSpec = project.copySpec(); - bundleSpec.from(buildProperties); - bundleSpec.from(pluginMetadata, copySpec -> { - // metadata (eg custom security policy) - // the codebases properties file is only for tests and not needed in production - copySpec.exclude("plugin-security.codebases"); - }); - bundleSpec.from( - (Callable>) () -> project.getPluginManager().hasPlugin("com.github.johnrengelman.shadow") - ? project.getTasks().named("shadowJar") - : project.getTasks().named("jar") - ); - bundleSpec.from( - project.getConfigurations() - .getByName("runtimeClasspath") - .minus(project.getConfigurations().getByName(CompileOnlyResolvePlugin.RESOLVEABLE_COMPILE_ONLY_CONFIGURATION_NAME)) - ); - - // extra files for the plugin to go into the zip - bundleSpec.from("src/main/packaging");// TODO: move all config/bin/_size/etc into packaging - bundleSpec.from("src/main", copySpec -> { - copySpec.include("config/**"); - copySpec.include("bin/**"); - }); - return bundleSpec; - } } diff --git a/build-tools/src/main/java/org/elasticsearch/gradle/plugin/PluginPropertiesExtension.java b/build-tools/src/main/java/org/elasticsearch/gradle/plugin/PluginPropertiesExtension.java index dae8b5fa514f2..40aeab87d0f2f 100644 --- a/build-tools/src/main/java/org/elasticsearch/gradle/plugin/PluginPropertiesExtension.java +++ b/build-tools/src/main/java/org/elasticsearch/gradle/plugin/PluginPropertiesExtension.java @@ -9,78 +9,17 @@ package org.elasticsearch.gradle.plugin; import org.gradle.api.Project; -import org.gradle.api.file.CopySpec; -import org.gradle.api.file.RegularFileProperty; -import org.gradle.api.plugins.ExtraPropertiesExtension; - -import java.io.File; -import java.util.ArrayList; -import java.util.List; /** * A container for plugin properties that will be written to the plugin descriptor, for easy * manipulation in the gradle DSL. */ -public class PluginPropertiesExtension { - private String name; - - private String version; - - private String description; +public class PluginPropertiesExtension extends BasePluginPropertiesExtension { private String classname; - /** Other plugins this plugin extends through SPI */ - private List extendedPlugins = new ArrayList<>(); - - private boolean hasNativeController; - - /** Whether a license agreement must be accepted before this plugin can be installed. */ - private boolean isLicensed = false; - - /** True if the plugin requires the elasticsearch keystore to exist, false otherwise. */ - private boolean requiresKeystore; - - /** A license file that should be included in the built plugin zip. */ - private File licenseFile; - - /** - * A notice file that should be included in the built plugin zip. This will be - * extended with notices from the {@code licenses/} directory. - */ - private File noticeFile; - - private final Project project; - private CopySpec bundleSpec; - public PluginPropertiesExtension(Project project) { - this.project = project; - } - - public String getName() { - return name == null ? project.getName() : name; - } - - public void setName(String name) { - this.project.setProperty("archivesBaseName", name); - this.name = name; - } - - public String getVersion() { - return version == null ? project.getVersion().toString() : version; - } - - public void setVersion(String version) { - this.version = version; - } - - public String getDescription() { - return description; - } - - public void setDescription(String description) { - project.setDescription(description); - this.description = description; + super(project); } public String getClassname() { @@ -90,74 +29,4 @@ public String getClassname() { public void setClassname(String classname) { this.classname = classname; } - - public List getExtendedPlugins() { - return this.extendedPlugins; - } - - public boolean isHasNativeController() { - return hasNativeController; - } - - public void setHasNativeController(boolean hasNativeController) { - this.hasNativeController = hasNativeController; - } - - public boolean isLicensed() { - return isLicensed; - } - - public void setLicensed(boolean licensed) { - isLicensed = licensed; - } - - public boolean isRequiresKeystore() { - return requiresKeystore; - } - - public void setRequiresKeystore(boolean requiresKeystore) { - this.requiresKeystore = requiresKeystore; - } - - public File getLicenseFile() { - return licenseFile; - } - - public void setLicenseFile(File licenseFile) { - ExtraPropertiesExtension extraProperties = this.project.getExtensions().getExtraProperties(); - RegularFileProperty regularFileProperty = extraProperties.has("licenseFile") - ? (RegularFileProperty) extraProperties.get("licenseFile") - : project.getObjects().fileProperty(); - regularFileProperty.set(licenseFile); - this.licenseFile = licenseFile; - } - - public File getNoticeFile() { - return noticeFile; - } - - public void setNoticeFile(File noticeFile) { - ExtraPropertiesExtension extraProperties = this.project.getExtensions().getExtraProperties(); - RegularFileProperty regularFileProperty = extraProperties.has("noticeFile") - ? (RegularFileProperty) extraProperties.get("noticeFile") - : project.getObjects().fileProperty(); - regularFileProperty.set(noticeFile); - this.noticeFile = noticeFile; - } - - public Project getProject() { - return project; - } - - public void setExtendedPlugins(List extendedPlugins) { - this.extendedPlugins = extendedPlugins; - } - - public void setBundleSpec(CopySpec bundleSpec) { - this.bundleSpec = bundleSpec; - } - - public CopySpec getBundleSpec() { - return bundleSpec; - } } diff --git a/build-tools/src/main/java/org/elasticsearch/gradle/plugin/StableBuildPlugin.java b/build-tools/src/main/java/org/elasticsearch/gradle/plugin/StableBuildPlugin.java new file mode 100644 index 0000000000000..ed1a07d33adc5 --- /dev/null +++ b/build-tools/src/main/java/org/elasticsearch/gradle/plugin/StableBuildPlugin.java @@ -0,0 +1,35 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +package org.elasticsearch.gradle.plugin; + +import org.gradle.api.Plugin; +import org.gradle.api.Project; +import org.gradle.api.file.RegularFile; +import org.gradle.api.provider.Provider; + +public class StableBuildPlugin implements Plugin { + + @Override + public void apply(Project project) { + var extension = project.getExtensions() + .create(BasePluginBuildPlugin.PLUGIN_EXTENSION_NAME, BasePluginPropertiesExtension.class, project); + + project.getPluginManager().apply(BasePluginBuildPlugin.class); + + project.getTasks().withType(GeneratePluginPropertiesTask.class).named("pluginProperties").configure(task -> { + task.getIsStable().set(true); + task.getTemplateFileName().set("stable-" + GeneratePluginPropertiesTask.PROPERTIES_FILENAME); + + Provider file = project.getLayout() + .getBuildDirectory() + .file("generated-descriptor/" + GeneratePluginPropertiesTask.STABLE_PROPERTIES_FILENAME); + task.getOutputFile().set(file); + }); + } +} diff --git a/build-tools/src/main/java/org/elasticsearch/gradle/test/JavaRestTestPlugin.java b/build-tools/src/main/java/org/elasticsearch/gradle/test/JavaRestTestPlugin.java index 9e0cbf88eb7b8..7ed7d669c6a2d 100644 --- a/build-tools/src/main/java/org/elasticsearch/gradle/test/JavaRestTestPlugin.java +++ b/build-tools/src/main/java/org/elasticsearch/gradle/test/JavaRestTestPlugin.java @@ -22,7 +22,7 @@ import org.gradle.api.tasks.bundling.Zip; import org.gradle.language.base.plugins.LifecycleBasePlugin; -import static org.elasticsearch.gradle.plugin.PluginBuildPlugin.BUNDLE_PLUGIN_TASK_NAME; +import static org.elasticsearch.gradle.plugin.BasePluginBuildPlugin.BUNDLE_PLUGIN_TASK_NAME; public class JavaRestTestPlugin implements Plugin { diff --git a/build-tools/src/main/java/org/elasticsearch/gradle/test/YamlRestTestPlugin.java b/build-tools/src/main/java/org/elasticsearch/gradle/test/YamlRestTestPlugin.java index 9c95d788636ac..93449aa0b767f 100644 --- a/build-tools/src/main/java/org/elasticsearch/gradle/test/YamlRestTestPlugin.java +++ b/build-tools/src/main/java/org/elasticsearch/gradle/test/YamlRestTestPlugin.java @@ -36,8 +36,8 @@ import java.io.File; -import static org.elasticsearch.gradle.plugin.PluginBuildPlugin.BUNDLE_PLUGIN_TASK_NAME; -import static org.elasticsearch.gradle.plugin.PluginBuildPlugin.EXPLODED_BUNDLE_PLUGIN_TASK_NAME; +import static org.elasticsearch.gradle.plugin.BasePluginBuildPlugin.BUNDLE_PLUGIN_TASK_NAME; +import static org.elasticsearch.gradle.plugin.BasePluginBuildPlugin.EXPLODED_BUNDLE_PLUGIN_TASK_NAME; public class YamlRestTestPlugin implements Plugin { diff --git a/build-tools/src/main/java/org/elasticsearch/gradle/testclusters/ElasticsearchNode.java b/build-tools/src/main/java/org/elasticsearch/gradle/testclusters/ElasticsearchNode.java index 238331f04372b..66482c64e8c18 100644 --- a/build-tools/src/main/java/org/elasticsearch/gradle/testclusters/ElasticsearchNode.java +++ b/build-tools/src/main/java/org/elasticsearch/gradle/testclusters/ElasticsearchNode.java @@ -98,7 +98,7 @@ import static java.util.Objects.requireNonNull; import static java.util.Optional.ofNullable; -import static org.elasticsearch.gradle.plugin.PluginBuildPlugin.EXPLODED_BUNDLE_CONFIG; +import static org.elasticsearch.gradle.plugin.BasePluginBuildPlugin.EXPLODED_BUNDLE_CONFIG; public class ElasticsearchNode implements TestClusterConfiguration { diff --git a/build-tools/src/main/resources/stable-plugin-descriptor.properties b/build-tools/src/main/resources/stable-plugin-descriptor.properties new file mode 100644 index 0000000000000..e53d7a6f85417 --- /dev/null +++ b/build-tools/src/main/resources/stable-plugin-descriptor.properties @@ -0,0 +1,54 @@ +# Elasticsearch plugin descriptor file +# This file must exist as 'plugin-descriptor.properties' inside a plugin. +# +### example plugin for "foo" +# +# foo.zip <-- zip file for the plugin, with this structure: +# |____ .jar <-- classes, resources, dependencies +# |____ .jar <-- any number of jars +# |____ plugin-descriptor.properties <-- example contents below: +# +# classname=foo.bar.BazPlugin +# description=My cool plugin +# version=6.0 +# elasticsearch.version=6.0 +# java.version=1.8 +# +### mandatory elements for all plugins: +# +# 'description': simple summary of the plugin +description=${description} +# +# 'version': plugin's version +version=${version} +# +# 'name': the plugin name +name=${name} + +# +# 'modulename': the name of the module to load classname from. Only applies to +# "isolated" plugins. This is optional. Specifying it causes the plugin +# to be loaded as a module. +modulename=${modulename} +# +# 'java.version': version of java the code is built against +# use the system property java.specification.version +# version string must be a sequence of nonnegative decimal integers +# separated by "."'s and may have leading zeros +java.version=${javaVersion} +# +# 'elasticsearch.version': version of elasticsearch compiled against +elasticsearch.version=${elasticsearchVersion} +### optional elements for plugins: +# +# 'extended.plugins': other plugins this plugin extends through SPI +extended.plugins=${extendedPlugins} +# +# 'has.native.controller': whether or not the plugin has a native controller +has.native.controller=${hasNativeController} +<% if (licensed) { %> +# This plugin requires that a license agreement be accepted before installation +licensed=${licensed} +<% } %> + +stable=${stable}