Skip to content
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
f135b63
Create gradle plugin for ES stable plugins
pgomulka Sep 26, 2022
673350d
use only one template
pgomulka Sep 26, 2022
1a82005
Merge remote-tracking branch 'upstream/main' into stable_plugin_clean
pgomulka Sep 26, 2022
9f6c7f6
partial tests
pgomulka Sep 27, 2022
8d2212c
remove additional extension
pgomulka Sep 27, 2022
cb9f184
tests as spec
pgomulka Sep 28, 2022
7717d3b
code review follow up
pgomulka Sep 28, 2022
c44feaf
Merge branch 'scanner_gradle_of_clean' into stable_plugin_clean
pgomulka Sep 28, 2022
0cea1a2
named components
pgomulka Sep 28, 2022
91e55f3
scanning
pgomulka Sep 29, 2022
ad116a7
more tests
pgomulka Sep 29, 2022
bab1859
specs and groovy tasks
pgomulka Oct 3, 2022
c43a91f
gradle test
pgomulka Oct 3, 2022
a0e73e8
Merge remote-tracking branch 'upstream/main' into stable_plugin_clean
pgomulka Oct 3, 2022
e23ae57
unused imports
pgomulka Oct 4, 2022
7c4e61c
cleanup
pgomulka Oct 4, 2022
bb41a7e
Merge branch 'main' into stable_plugin_clean
elasticmachine Oct 5, 2022
2709ff5
cleanup test classes
pgomulka Oct 5, 2022
92f0d28
Merge remote-tracking branch 'upstream/main' into stable_plugin_clean
pgomulka Oct 5, 2022
69eceda
remove stream supplier and two class properties
pgomulka Oct 5, 2022
2f87873
Merge remote-tracking branch 'pgomulka/stable_plugin_clean' into stab…
pgomulka Oct 5, 2022
410b21e
test fix after move of testclasses
pgomulka Oct 5, 2022
5c1065a
fix tests
pgomulka Oct 5, 2022
d5e4d6a
code review follow up
pgomulka Oct 6, 2022
e0a43c2
spotless
pgomulka Oct 6, 2022
3acd65d
Merge remote-tracking branch 'upstream/main' into stable_plugin_clean
pgomulka Oct 6, 2022
681aa02
remove unused methods
pgomulka Oct 6, 2022
c16ae04
revert import ordering
pgomulka Oct 6, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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)\\/.*");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<Project> {
private static final String TESTS_REST_CLUSTER = "tests.rest.cluster";
Expand Down
11 changes: 10 additions & 1 deletion build-tools/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ gradlePlugin {
id = 'elasticsearch.esplugin'
implementationClass = 'org.elasticsearch.gradle.plugin.PluginBuildPlugin'
}
stableEsPlugin {
id = 'elasticsearch.stable-esplugin'
Copy link
Contributor

@ChrisHegarty ChrisHegarty Oct 6, 2022

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.

implementationClass = 'org.elasticsearch.gradle.plugin.StablePluginBuildPlugin'
}
javaRestTest {
id = 'elasticsearch.java-rest-test'
implementationClass = 'org.elasticsearch.gradle.test.JavaRestTestPlugin'
Expand Down Expand Up @@ -119,6 +123,8 @@ dependencies {
api buildLibs.commmons.io
implementation buildLibs.asm.tree
implementation buildLibs.asm
implementation buildLibs.jackson.core
implementation buildLibs.jackson.databind

testFixturesApi gradleApi()
testFixturesApi gradleTestKit()
Expand All @@ -128,7 +134,9 @@ dependencies {
testFixturesApi(buildLibs.spock.core) {
exclude module: "groovy"
}

testFixturesApi(buildLibs.bytebuddy) {
because 'Generating dynamic plugin apis'
}
testImplementation(buildLibs.spock.junit4) {
because 'required as we rely on junit4 rules'
}
Expand All @@ -144,6 +152,7 @@ dependencies {
testRuntimeOnly(buildLibs.junit5.platform.launcher) {
because 'allows tests to run from IDEs that bundle older version of launcher'
}

}

tasks.named('test').configure {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ class PluginBuildPluginFuncTest extends AbstractGradleFuncTest {
}
Copy link
Contributor

Choose a reason for hiding this comment

The 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 BasePluginBuildPluginFunc test

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but I cannot really test basePluginBuildPlugin without applying 'elasticsearch.esplugin' or elasticsearch.stable-esplugin`.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you can apply the BasePluginBuildPlugin just by using the classname

        plugins.apply(org.elasticsearch.gradle.plugin. BasePluginBuildPlugin)

We do something similar to test internal plugins. see AbstractGradleInternalPluginFuncTest


dependencies {
consume project(path:':', configuration:'${PluginBuildPlugin.EXPLODED_BUNDLE_CONFIG}')
consume project(path:':', configuration:'${BasePluginBuildPlugin.EXPLODED_BUNDLE_CONFIG}')
}

tasks.register("resolveModule", Copy) {
Expand Down
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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

debug output can be removed

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();
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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

guess this debug output can be removed

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;
}
}
Loading