-
Notifications
You must be signed in to change notification settings - Fork 25.6k
Create gradle plugin for ES stable plugins #90355
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 14 commits
f135b63
673350d
1a82005
9f6c7f6
8d2212c
cb9f184
7717d3b
c44feaf
0cea1a2
91e55f3
ad116a7
bab1859
c43a91f
a0e73e8
e23ae57
7c4e61c
bb41a7e
2709ff5
92f0d28
69eceda
2f87873
410b21e
5c1065a
d5e4d6a
e0a43c2
3acd65d
681aa02
c16ae04
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -75,7 +75,7 @@ class PluginBuildPluginFuncTest extends AbstractGradleFuncTest { | |
| } | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In general the test that test functionality that moved into the BasePlugin should be moved into a
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. but I cannot really test basePluginBuildPlugin without applying
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. you can apply the We do something similar to test internal plugins. see |
||
|
|
||
| dependencies { | ||
| consume project(path:':', configuration:'${PluginBuildPlugin.EXPLODED_BUNDLE_CONFIG}') | ||
| consume project(path:':', configuration:'${BasePluginBuildPlugin.EXPLODED_BUNDLE_CONFIG}') | ||
| } | ||
|
|
||
| tasks.register("resolveModule", Copy) { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,129 @@ | ||
| /* | ||
| * 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 com.fasterxml.jackson.core.type.TypeReference | ||
| import com.fasterxml.jackson.databind.ObjectMapper | ||
|
|
||
| import org.elasticsearch.gradle.VersionProperties | ||
| import org.elasticsearch.gradle.fixtures.AbstractGradleFuncTest | ||
| import org.gradle.testkit.runner.TaskOutcome | ||
| import org.junit.Assert | ||
|
|
||
| import java.nio.file.Files | ||
| import java.nio.file.Path | ||
| import java.util.stream.Collectors | ||
|
|
||
| class StableBuildPluginPluginFuncTest extends AbstractGradleFuncTest { | ||
|
|
||
| def setup() { | ||
| // underlaying TestClusterPlugin and StandaloneRestIntegTestTask are not cc compatible | ||
| configurationCacheCompatible = false | ||
| } | ||
|
|
||
| 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() | ||
| // Path propsFile = file("build/generated-named-components/named_components.json").toPath(); | ||
|
|
||
| then: | ||
| result.task(":pluginProperties").outcome == TaskOutcome.SUCCESS | ||
| props.get("classname") == null | ||
|
|
||
| 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.size() == 8 | ||
|
|
||
| } | ||
|
|
||
| def "can scan and create named components file"() { | ||
| given: | ||
| println testProjectDir.root | ||
|
||
| File jarFolder = new File(testProjectDir.root, "jars") | ||
| jarFolder.mkdirs() | ||
|
|
||
| buildFile << """plugins { | ||
| id 'elasticsearch.stable-esplugin' | ||
| } | ||
|
|
||
| version = '1.2.3' | ||
|
|
||
| esplugin { | ||
| name = 'myplugin' | ||
| description = 'test plugin' | ||
| } | ||
|
|
||
| dependencies { | ||
| implementation files('${pluginApiJar(jarFolder).absolutePath}') | ||
| implementation files('${extensibleApiJar(jarFolder).absolutePath}') | ||
| } | ||
|
|
||
| """ | ||
|
|
||
| file("src/main/java/org/acme/A.java") << """ | ||
| package org.acme; | ||
|
|
||
| import org.elasticsearch.plugin.api.NamedComponent; | ||
| import org.elasticsearch.extensible.ExtensibleClass; | ||
|
|
||
| @NamedComponent(name = "componentA") | ||
| public class A extends ExtensibleClass { | ||
| } | ||
| """ | ||
|
|
||
|
|
||
| when: | ||
| def result = gradleRunner(":assemble").build() | ||
| Path namedComponents = file("build/generated-named-components/named_components.json").toPath(); | ||
breskeby marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| ObjectMapper mapper = new ObjectMapper() | ||
| TypeReference<Map<String,Map<String,String>>> typeRef | ||
| = new TypeReference<HashMap<String,Object>>() {} | ||
|
|
||
| Map<String,Map<String,String>> map = mapper.readValue(namedComponents.toFile(), typeRef); | ||
|
|
||
|
|
||
| then: | ||
| result.task(":assemble").outcome == TaskOutcome.SUCCESS | ||
|
|
||
| println Files.readString(namedComponents) | ||
|
||
| println namedComponents.toFile().exists() | ||
|
|
||
| map == ["org.elasticsearch.extensible.ExtensibleClass" : (["componentA" : "org.acme.A"]) ] | ||
| } | ||
|
|
||
|
|
||
| Map<String, String> getPluginProperties() { | ||
| Path propsFile = file("build/generated-descriptor/stable-plugin-descriptor.properties").toPath(); | ||
| Properties rawProps = new Properties() | ||
| try (var inputStream = Files.newInputStream(propsFile)) { | ||
| rawProps.load(inputStream) | ||
| } | ||
| return rawProps.entrySet().stream().collect(Collectors.toMap(e -> e.getKey().toString(), e -> e.getValue().toString())) | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,187 @@ | ||
| /* | ||
| * 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<Project> { | ||
|
|
||
| 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 = project.getExtensions() | ||
| .create(BasePluginBuildPlugin.PLUGIN_EXTENSION_NAME, PluginPropertiesExtension.class, 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<Provider<RegularFile>, Zip>) zip -> zip.getArchiveFile())); | ||
| } else { | ||
| c.plugin(bundleTask.flatMap((Transformer<Provider<RegularFile>, 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<ElasticsearchCluster> testClusters(Project project, String extensionName) { | ||
| return (NamedDomainObjectContainer<ElasticsearchCluster>) project.getExtensions().getByName(extensionName); | ||
| } | ||
|
|
||
| /** | ||
| * Adds bundle tasks which builds the dir and zip containing the plugin jars, | ||
| * metadata, properties, and packaging files | ||
| */ | ||
| private TaskProvider<Zip> 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.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<String, Object> 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<GeneratePluginPropertiesTask> 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<TaskProvider<Task>>) () -> 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; | ||
| } | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's go with this name for now, but reserve the right to change it.