Skip to content

Commit 8be14da

Browse files
Merge pull request ESQL-612 from elastic/main
🤖 ESQL: Merge upstream
2 parents 89749bb + 394d4f0 commit 8be14da

File tree

39 files changed

+2247
-191
lines changed

39 files changed

+2247
-191
lines changed

.ci/packer_cache.sh

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,3 @@ fi
4444
## therefore we run main _AFTER_ we run 6.8 which uses an earlier gradle version
4545
export JAVA_HOME="${HOME}"/.java/${ES_BUILD_JAVA}
4646
./gradlew --parallel clean -s resolveAllDependencies -Dorg.gradle.warning.mode=none -Drecurse.bwc=true
47-
48-
## Copy all dependencies into a "read-only" location to be used by nested Gradle builds
49-
mkdir -p ${HOME}/gradle_ro_cache
50-
rsync -r ${HOME}/.gradle/caches/modules-2 ${HOME}/gradle_ro_cache
51-
rm ${HOME}/gradle_ro_cache/modules-2/gc.properties
52-
rm ${HOME}/gradle_ro_cache/modules-2/*.lock
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import org.apache.tools.ant.taskdefs.condition.Os
2+
import org.elasticsearch.gradle.Version
3+
import org.elasticsearch.gradle.VersionProperties
4+
import org.elasticsearch.gradle.internal.BwcVersions
5+
import org.elasticsearch.gradle.internal.JarApiComparisonTask
6+
import org.elasticsearch.gradle.internal.info.BuildParams
7+
8+
import static org.elasticsearch.gradle.internal.InternalDistributionBwcSetupPlugin.buildBwcTaskName
9+
10+
configurations {
11+
newJar
12+
}
13+
14+
dependencies {
15+
newJar project(":libs:${project.name}")
16+
}
17+
18+
BuildParams.bwcVersions.withIndexCompatible({ it.onOrAfter(Version.fromString(ext.stableApiSince))
19+
&& it != VersionProperties.elasticsearchVersion
20+
}) { bwcVersion, baseName ->
21+
22+
BwcVersions.UnreleasedVersionInfo unreleasedVersion = BuildParams.bwcVersions.unreleasedInfo(bwcVersion)
23+
24+
configurations {
25+
"oldJar${baseName}" {
26+
transitive = false
27+
}
28+
}
29+
30+
dependencies {
31+
if (unreleasedVersion) {
32+
// For unreleased snapshot versions, build them from source
33+
"oldJar${baseName}"(files(project(unreleasedVersion.gradleProjectPath).tasks.named(buildBwcTaskName(project.name))))
34+
} else {
35+
// For released versions, download it
36+
"oldJar${baseName}"("org.elasticsearch:${project.name}:${bwcVersion}")
37+
}
38+
}
39+
40+
def jarApiComparisonTask = tasks.register(bwcTaskName(bwcVersion), JarApiComparisonTask) {
41+
oldJar = configurations."oldJar${baseName}"
42+
newJar = configurations.newJar
43+
}
44+
45+
jarApiComparisonTask.configure {
46+
onlyIf {
47+
!Os.isFamily(Os.FAMILY_WINDOWS)
48+
}
49+
}
50+
}

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

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import java.util.ArrayList;
2828
import java.util.List;
2929
import java.util.Locale;
30+
import java.util.Set;
3031
import java.util.stream.Collectors;
3132

3233
import javax.inject.Inject;
@@ -120,6 +121,35 @@ private void configureBwcProject(Project project, BwcVersions.UnreleasedVersionI
120121
buildBwcTaskProvider,
121122
"assemble"
122123
);
124+
125+
// for versions before 8.7.0, we do not need to set up stable API bwc
126+
if (bwcVersion.get().before(Version.fromString("8.7.0"))) {
127+
return;
128+
}
129+
130+
for (Project stableApiProject : resolveStableProjects(project)) {
131+
132+
String relativeDir = project.getRootProject().relativePath(stableApiProject.getProjectDir());
133+
134+
DistributionProjectArtifact stableAnalysisPluginProjectArtifact = new DistributionProjectArtifact(
135+
new File(
136+
checkoutDir.get(),
137+
relativeDir + "/build/distributions/" + stableApiProject.getName() + "-" + bwcVersion.get() + "-SNAPSHOT.jar"
138+
),
139+
null
140+
);
141+
142+
createBuildBwcTask(
143+
bwcSetupExtension,
144+
project,
145+
bwcVersion,
146+
stableApiProject.getName(),
147+
"libs/" + stableApiProject.getName(),
148+
stableAnalysisPluginProjectArtifact,
149+
buildBwcTaskProvider,
150+
"assemble"
151+
);
152+
}
123153
}
124154

125155
private void registerBwcDistributionArtifacts(Project bwcProject, DistributionProject distributionProject) {
@@ -209,7 +239,16 @@ private static List<DistributionProject> resolveArchiveProjects(File checkoutDir
209239
}).collect(Collectors.toList());
210240
}
211241

212-
private static String buildBwcTaskName(String projectName) {
242+
private static List<Project> resolveStableProjects(Project project) {
243+
Set<String> stableProjectNames = Set.of("elasticsearch-logging", "elasticsearch-plugin-api", "elasticsearch-plugin-analysis-api");
244+
return project.findProject(":libs")
245+
.getSubprojects()
246+
.stream()
247+
.filter(subproject -> stableProjectNames.contains(subproject.getName()))
248+
.toList();
249+
}
250+
251+
public static String buildBwcTaskName(String projectName) {
213252
return "buildBwc"
214253
+ stream(projectName.split("-")).map(i -> i.substring(0, 1).toUpperCase(Locale.ROOT) + i.substring(1))
215254
.collect(Collectors.joining());
Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
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;
10+
11+
import org.elasticsearch.gradle.internal.conventions.precommit.PrecommitTask;
12+
import org.gradle.api.file.FileCollection;
13+
import org.gradle.api.provider.Property;
14+
import org.gradle.api.tasks.CacheableTask;
15+
import org.gradle.api.tasks.CompileClasspath;
16+
import org.gradle.api.tasks.TaskAction;
17+
18+
import java.io.BufferedReader;
19+
import java.io.File;
20+
import java.io.IOException;
21+
import java.io.InputStream;
22+
import java.io.InputStreamReader;
23+
import java.util.ArrayList;
24+
import java.util.HashMap;
25+
import java.util.HashSet;
26+
import java.util.List;
27+
import java.util.Map;
28+
import java.util.Set;
29+
import java.util.jar.JarFile;
30+
import java.util.regex.Pattern;
31+
import java.util.stream.Collectors;
32+
import java.util.zip.ZipEntry;
33+
34+
/**
35+
* This implementation of a jar API comparison uses the "javap" tool to compare
36+
* the "signatures" of two different jars. We assume that calling out to javap
37+
* is not too expensive at this stage of the stable API project. We also assume
38+
* that for every public class, method, and field, javap will print a consistent
39+
* single line. This should let us make string comparisons, rather than having
40+
* to parse the output of javap.
41+
* <p>
42+
* While the above assumptions appear to hold, they are not guaranteed, and hence
43+
* brittle. We could overcome these problems with an ASM implementation of the
44+
* Jar Scanner.
45+
* <p>
46+
* We also assume that we will not be comparing multi-version JARs.
47+
* <p>
48+
* This "javap" approach has a few further drawbacks:
49+
* <ol>
50+
* <li>We don't account for class visibility when examining fields and methods.</li>
51+
* <li>We don't consider what is exported from the module. Is a public method from
52+
* a non-exported package considered part of the stable api?</li>
53+
* <li>Changing method types to their superclass or return types to an implementation
54+
* class will be considered a change by this approach, even though that doesn't break
55+
* an API.</li>
56+
* <li>Finally, moving a method up the class hierarchy is not really a breaking change,
57+
* but it will trip this test.</li>
58+
* </ol>
59+
*/
60+
@CacheableTask
61+
public abstract class JarApiComparisonTask extends PrecommitTask {
62+
63+
@TaskAction
64+
public void compare() {
65+
FileCollection fileCollection = getOldJar().get();
66+
File newJarFile = getNewJar().get().getSingleFile();
67+
68+
Set<String> oldJarNames = fileCollection.getFiles().stream().map(File::getName).collect(Collectors.toSet());
69+
if (oldJarNames.size() > 1) {
70+
throw new IllegalStateException("Expected a single original jar, but found: " + oldJarNames);
71+
}
72+
if (oldJarNames.contains(newJarFile.getName())) {
73+
throw new IllegalStateException(
74+
"We should be comparing different jars, but original and new jars were both: " + newJarFile.getAbsolutePath()
75+
);
76+
}
77+
78+
JarScanner oldJS = new JarScanner(getOldJar().get().getSingleFile().getPath());
79+
JarScanner newJS = new JarScanner(newJarFile.getPath());
80+
try {
81+
JarScanner.compareSignatures(oldJS.jarSignature(), newJS.jarSignature());
82+
} catch (IOException e) {
83+
throw new RuntimeException(e);
84+
}
85+
}
86+
87+
@CompileClasspath
88+
public abstract Property<FileCollection> getOldJar();
89+
90+
@CompileClasspath
91+
public abstract Property<FileCollection> getNewJar();
92+
93+
public static class JarScanner {
94+
95+
private final String path;
96+
97+
public JarScanner(String path) {
98+
this.path = path;
99+
}
100+
101+
private String getPath() {
102+
return path;
103+
}
104+
105+
/**
106+
* Get a list of class names contained in this jar by looking for file names
107+
* that end in ".class"
108+
*/
109+
List<String> classNames() throws IOException {
110+
Pattern classEnding = Pattern.compile(".*\\.class$");
111+
try (JarFile jf = new JarFile(this.path)) {
112+
return jf.stream().map(ZipEntry::getName).filter(classEnding.asMatchPredicate()).collect(Collectors.toList());
113+
}
114+
}
115+
116+
/**
117+
* Given a path to a file in the jar, get the output of javap as a list of strings.
118+
*/
119+
public List<String> disassembleFromJar(String fileInJarPath, String classpath) {
120+
String location = "jar:file://" + getPath() + "!/" + fileInJarPath;
121+
return disassemble(location, getPath(), classpath);
122+
}
123+
124+
/**
125+
* Invoke javap on a class file, optionally providing a module path or class path
126+
*/
127+
static List<String> disassemble(String location, String modulePath, String classpath) {
128+
ProcessBuilder pb = new ProcessBuilder();
129+
List<String> command = new ArrayList<>();
130+
command.add("javap");
131+
if (modulePath != null) {
132+
command.add("--module-path");
133+
command.add(modulePath);
134+
}
135+
if (classpath != null) {
136+
command.add("--class-path");
137+
command.add(classpath);
138+
}
139+
command.add(location);
140+
pb.command(command.toArray(new String[] {}));
141+
Process p;
142+
try {
143+
p = pb.start();
144+
p.onExit().get();
145+
} catch (Exception e) {
146+
throw new RuntimeException(e);
147+
}
148+
149+
InputStream streamToRead = p.exitValue() == 0 ? p.getInputStream() : p.getErrorStream();
150+
151+
try (BufferedReader br = new BufferedReader(new InputStreamReader(streamToRead))) {
152+
return br.lines().toList();
153+
} catch (IOException e) {
154+
throw new RuntimeException(e);
155+
}
156+
}
157+
158+
/**
159+
* Given the output of the javap command, that is, the disassembled class file,
160+
* return a set of signatures for all public classes, methods, and fields.
161+
*/
162+
public static Set<String> signaturesSet(List<String> javapOutput) {
163+
return javapOutput.stream().filter(s -> s.matches("^\\s*public.*")).collect(Collectors.toSet());
164+
}
165+
166+
/**
167+
* Given a disassembled module-info.class, return all unqualified exports.
168+
*/
169+
public static Set<String> moduleInfoSignaturesSet(List<String> javapOutput) {
170+
return javapOutput.stream()
171+
.filter(s -> s.matches("^\\s*exports.*"))
172+
.filter(s -> s.matches(".* to$") == false)
173+
.collect(Collectors.toSet());
174+
}
175+
176+
/**
177+
* Iterate over classes and gather signatures.
178+
*/
179+
public Map<String, Set<String>> jarSignature() throws IOException {
180+
return this.classNames().stream().collect(Collectors.toMap(s -> s, s -> {
181+
List<String> disassembled = disassembleFromJar(s, null);
182+
if ("module-info.class".equals(s)) {
183+
return moduleInfoSignaturesSet(disassembled);
184+
}
185+
return signaturesSet(disassembled);
186+
}));
187+
}
188+
189+
/**
190+
* Comparison: The signatures are maps of class names to public class, field, or method
191+
* declarations.
192+
* </p>
193+
* First, we check that the new jar signature contains all the same classes
194+
* as the old jar signature. If not, we return an error.
195+
* </p>
196+
* Second, we iterate over the signature for each class. If a signature from the old
197+
* jar is absent in the new jar, we add it to our list of errors.
198+
* </p>
199+
* Note that it is fine for the new jar to have additional elements, as this
200+
* is backwards compatible.
201+
*/
202+
public static void compareSignatures(Map<String, Set<String>> oldSignature, Map<String, Set<String>> newSignature) {
203+
Set<String> deletedClasses = new HashSet<>(oldSignature.keySet());
204+
deletedClasses.removeAll(newSignature.keySet());
205+
if (deletedClasses.size() > 0) {
206+
throw new IllegalStateException("Classes from a previous version not found: " + deletedClasses);
207+
}
208+
209+
Map<String, Set<String>> deletedMembersMap = new HashMap<>();
210+
for (Map.Entry<String, Set<String>> entry : oldSignature.entrySet()) {
211+
Set<String> deletedMembers = new HashSet<>(entry.getValue());
212+
deletedMembers.removeAll(newSignature.get(entry.getKey()));
213+
if (deletedMembers.size() > 0) {
214+
deletedMembersMap.put(entry.getKey(), Set.copyOf(deletedMembers));
215+
}
216+
}
217+
if (deletedMembersMap.size() > 0) {
218+
throw new IllegalStateException(
219+
"Classes from a previous version have been modified, violating backwards compatibility: " + deletedMembersMap
220+
);
221+
}
222+
}
223+
}
224+
}

docs/reference/docs/reindex.asciidoc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -575,6 +575,9 @@ Valid values: `index`, `create`. Defaults to `index`.
575575
IMPORTANT: To reindex to a data stream destination, this argument must be
576576
`create`.
577577

578+
`pipeline`:::
579+
(Optional, string) the name of the <<reindex-with-an-ingest-pipeline,pipeline>> to use.
580+
578581
`script`::
579582
`source`:::
580583
(Optional, string) The script to run to update the document source or metadata when reindexing.

docs/reference/modules/cluster/shards_allocation.asciidoc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ one of the active allocation ids in the cluster state.
3535
`cluster.routing.allocation.node_concurrent_recoveries`::
3636
(<<dynamic-cluster-setting,Dynamic>>)
3737
A shortcut to set both `cluster.routing.allocation.node_concurrent_incoming_recoveries` and
38-
`cluster.routing.allocation.node_concurrent_outgoing_recoveries`.
38+
`cluster.routing.allocation.node_concurrent_outgoing_recoveries`. Defaults to 2.
3939

4040

4141
`cluster.routing.allocation.node_initial_primaries_recoveries`::

libs/logging/build.gradle

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
apply plugin: 'elasticsearch.publish'
1010
apply plugin: 'elasticsearch.build'
1111

12-
1312
tasks.named("loggerUsageCheck").configure {enabled = false }
1413

1514
dependencies {

libs/plugin-scanner/src/main/java/org/elasticsearch/plugin/scanner/ClassReaders.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,8 @@ public static List<ClassReader> ofDirWithJars(String path) {
4545
return Collections.emptyList();
4646
}
4747
Path dir = Paths.get(path);
48-
try {
49-
return ofPaths(Files.list(dir));
48+
try (var stream = Files.list(dir)) {
49+
return ofPaths(stream);
5050
} catch (IOException e) {
5151
throw new UncheckedIOException(e);
5252
}

0 commit comments

Comments
 (0)