Skip to content

Commit 6e3a6ea

Browse files
authored
Avoid overhead of zip / unzip modules when packaging distribution (#84660) (#84971)
This removes the overhead of zipping up modules that are immediately unzipped again when packaging the elasticsearch distribution. We also move some logic for packaging the elasticsearch distribution into a plugin and remove some outdated overhead dealing with 'meta plugins' when copying modules into a distribution. Another follow up and related optimization out of scope of this PR is, to also not zip unzip modules declared for usage in our test cluster setups. This partially addresses #76726.
1 parent 2c54c8d commit 6e3a6ea

File tree

15 files changed

+478
-268
lines changed

15 files changed

+478
-268
lines changed

build-tools-internal/build.gradle

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@ gradlePlugin {
3535
id = 'elasticsearch.build'
3636
implementationClass = 'org.elasticsearch.gradle.internal.BuildPlugin'
3737
}
38+
distro {
39+
id = 'elasticsearch.distro'
40+
implementationClass = 'org.elasticsearch.gradle.internal.distribution.ElasticsearchDistributionPlugin'
41+
}
3842
distroTest {
3943
id = 'elasticsearch.distro-test'
4044
implementationClass = 'org.elasticsearch.gradle.internal.test.DistroTestPlugin'
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
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.internal.distibution
10+
11+
import org.elasticsearch.gradle.fixtures.AbstractGradleFuncTest
12+
import org.gradle.testkit.runner.TaskOutcome
13+
14+
class ElasticsearchDistributionPluginFuncTest extends AbstractGradleFuncTest {
15+
16+
def "copied modules are resolved from explodedBundleZip"() {
17+
given:
18+
moduleSubProject()
19+
20+
buildFile << """plugins {
21+
id 'elasticsearch.distro'
22+
}
23+
24+
def someCopy = tasks.register('someCopy', Sync) {
25+
into 'build/targetDir'
26+
}
27+
28+
distro.copyModule(someCopy, project(":module"))
29+
"""
30+
when:
31+
def result = gradleRunner("someCopy").build()
32+
33+
then:
34+
result.task(":someCopy").outcome == TaskOutcome.SUCCESS
35+
file('build/targetDir/modules/some-test-module/some-test-module.jar').exists()
36+
file('build/targetDir/modules/some-test-module/plugin-descriptor.properties').exists()
37+
}
38+
39+
private File moduleSubProject() {
40+
settingsFile << "include 'module'"
41+
file('module/build.gradle') << """
42+
plugins {
43+
id 'elasticsearch.esplugin'
44+
}
45+
46+
esplugin {
47+
name = 'some-test-module'
48+
classname = 'org.acme.never.used.TestPluginClass'
49+
description = 'some plugin description'
50+
}
51+
52+
// for testing purposes only
53+
configurations.compileOnly.dependencies.clear()
54+
"""
55+
}
56+
}

build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/BaseInternalPluginBuildPlugin.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020
import org.gradle.api.NamedDomainObjectContainer;
2121
import org.gradle.api.Plugin;
2222
import org.gradle.api.Project;
23-
import org.gradle.api.tasks.bundling.Zip;
2423

2524
import java.util.Optional;
2625

@@ -106,11 +105,12 @@ Optional<String> findModulePath(Project project, String pluginName) {
106105
protected static void addNoticeGeneration(final Project project, PluginPropertiesExtension extension) {
107106
final var licenseFile = extension.getLicenseFile();
108107
var tasks = project.getTasks();
108+
var bundleSpec = extension.getBundleSpec();
109109
if (licenseFile != null) {
110-
tasks.withType(Zip.class).named("bundlePlugin").configure(zip -> zip.from(licenseFile.getParentFile(), copySpec -> {
111-
copySpec.include(licenseFile.getName());
112-
copySpec.rename(s -> "LICENSE.txt");
113-
}));
110+
bundleSpec.from(licenseFile.getParentFile(), s -> {
111+
s.include(licenseFile.getName());
112+
s.rename(f -> "LICENSE.txt");
113+
});
114114
}
115115

116116
final var noticeFile = extension.getNoticeFile();
@@ -119,7 +119,7 @@ protected static void addNoticeGeneration(final Project project, PluginPropertie
119119
noticeTask.setInputFile(noticeFile);
120120
noticeTask.source(Util.getJavaMainSourceSet(project).get().getAllJava());
121121
});
122-
tasks.withType(Zip.class).named("bundlePlugin").configure(task -> task.from(generateNotice));
122+
bundleSpec.from(generateNotice);
123123
}
124124
}
125125
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
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.internal.distribution;
10+
11+
import org.elasticsearch.gradle.plugin.PluginPropertiesExtension;
12+
import org.gradle.api.Project;
13+
import org.gradle.api.artifacts.Configuration;
14+
import org.gradle.api.tasks.AbstractCopyTask;
15+
import org.gradle.api.tasks.TaskProvider;
16+
17+
import java.io.File;
18+
import java.util.Map;
19+
import java.util.concurrent.Callable;
20+
import java.util.regex.Pattern;
21+
22+
import static org.elasticsearch.gradle.plugin.PluginBuildPlugin.EXPLODED_BUNDLE_CONFIG;
23+
24+
public class ElasticsearchDistributionExtension {
25+
private static final Pattern CONFIG_BIN_REGEX_PATTERN = Pattern.compile("([^\\/]+\\/)?(config|bin)\\/.*");
26+
27+
private final Project project;
28+
29+
public ElasticsearchDistributionExtension(Project project) {
30+
this.project = project;
31+
}
32+
33+
private Configuration moduleZip(Project module) {
34+
var moduleConfigurationCoords = Map.of("path", module.getPath(), "configuration", EXPLODED_BUNDLE_CONFIG);
35+
var dep = project.getDependencies().project(moduleConfigurationCoords);
36+
return project.getConfigurations().detachedConfiguration(dep);
37+
}
38+
39+
public void copyModule(TaskProvider<AbstractCopyTask> copyTask, Project module) {
40+
copyTask.configure(sync -> {
41+
var moduleConfig = moduleZip(module);
42+
sync.dependsOn(moduleConfig);
43+
Callable<File> callableSingleFile = () -> moduleConfig.getSingleFile();
44+
sync.from(callableSingleFile, spec -> {
45+
spec.setIncludeEmptyDirs(false);
46+
// these are handled separately in the log4j config tasks in the :distribution plugin
47+
spec.exclude("*/config/log4j2.properties");
48+
spec.exclude("config/log4j2.properties");
49+
50+
// This adds a implicit dependency for 'module' to PluginBuildPlugin which is fine as we just fail
51+
// in case an invalid 'module' not applying this plugin is passed here
52+
var moduleName = module.getExtensions().getByType(PluginPropertiesExtension.class).getName();
53+
54+
spec.eachFile(d -> {
55+
if (CONFIG_BIN_REGEX_PATTERN.matcher(d.getRelativePath().getPathString()).matches() == false) {
56+
d.setRelativePath(d.getRelativePath().prepend("modules", moduleName));
57+
}
58+
});
59+
});
60+
});
61+
}
62+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
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.internal.distribution;
10+
11+
import org.gradle.api.Plugin;
12+
import org.gradle.api.Project;
13+
14+
/**
15+
* Provides an extension named 'distro' to provide common operations used when setting up and bundling
16+
* elasticsearch distributions. Having those here instead of in the build scripts makes testing and maintaining
17+
* this common functionality easier
18+
* */
19+
public class ElasticsearchDistributionPlugin implements Plugin<Project> {
20+
21+
@Override
22+
public void apply(Project project) {
23+
project.getExtensions().create("distro", ElasticsearchDistributionExtension.class, project);
24+
}
25+
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
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.fixtures.AbstractGradleFuncTest
12+
import org.gradle.testkit.runner.TaskOutcome
13+
14+
class PluginBuildPluginFuncTest extends AbstractGradleFuncTest {
15+
16+
def "can assemble plugin via #taskName"() {
17+
given:
18+
buildFile << """plugins {
19+
id 'elasticsearch.esplugin'
20+
}
21+
22+
esplugin {
23+
description = 'test plugin'
24+
classname = 'com.acme.plugin.TestPlugin'
25+
}
26+
27+
// for testing purposes only
28+
configurations.compileOnly.dependencies.clear()
29+
"""
30+
31+
when:
32+
def result = gradleRunner(taskName).build()
33+
34+
then:
35+
result.task(taskName).outcome == TaskOutcome.SUCCESS
36+
file(expectedOutputPath).exists()
37+
38+
where:
39+
expectedOutputPath | taskName
40+
"build/distributions/hello-world.zip" | ":bundlePlugin"
41+
"build/explodedBundle/" | ":explodedBundlePlugin"
42+
43+
}
44+
45+
def "can resolve plugin as directory without intermediate zipping "() {
46+
given:
47+
buildFile << """plugins {
48+
id 'elasticsearch.esplugin'
49+
}
50+
51+
esplugin {
52+
name = 'sample-plugin'
53+
description = 'test plugin'
54+
classname = 'com.acme.plugin.TestPlugin'
55+
}
56+
57+
// for testing purposes only
58+
configurations.compileOnly.dependencies.clear()
59+
"""
60+
61+
file('settings.gradle') << "include 'module-consumer'"
62+
file('module-consumer/build.gradle') << """
63+
configurations {
64+
consume
65+
}
66+
67+
dependencies {
68+
consume project(path:':', configuration:'${PluginBuildPlugin.EXPLODED_BUNDLE_CONFIG}')
69+
}
70+
71+
tasks.register("resolveModule", Copy) {
72+
from configurations.consume
73+
into "build/resolved"
74+
}
75+
"""
76+
when:
77+
def result = gradleRunner(":module-consumer:resolveModule").build()
78+
79+
then:
80+
result.task(":module-consumer:resolveModule").outcome == TaskOutcome.SUCCESS
81+
result.task(":explodedBundlePlugin").outcome == TaskOutcome.SUCCESS
82+
file("module-consumer/build/resolved/sample-plugin.jar").exists()
83+
file("module-consumer/build/resolved/plugin-descriptor.properties").exists()
84+
}
85+
}

0 commit comments

Comments
 (0)