Skip to content

Commit aed04c0

Browse files
authored
Backport update versions task to 7.17 (#104303)
Copy update version gradle task from #103335 to 7.17 for future 7.17 releases
1 parent 20b46d1 commit aed04c0

File tree

4 files changed

+495
-0
lines changed

4 files changed

+495
-0
lines changed

build-tools-internal/build.gradle

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,9 @@ dependencies {
262262
api buildLibs.asm.tree
263263

264264
compileOnly buildLibs.checkstyle
265+
266+
implementation 'com.github.javaparser:javaparser-core:3.18.0'
267+
265268
runtimeOnly "org.elasticsearch.gradle:reaper:$version"
266269
testImplementation buildLibs.checkstyle
267270
testImplementation buildLibs.wiremock

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,9 @@ public void apply(Project project) {
4545

4646
final Version version = VersionProperties.getElasticsearchVersion();
4747

48+
project.getTasks()
49+
.register("updateVersions", UpdateVersionsTask.class, t -> project.getTasks().named("spotlessApply").get().mustRunAfter(t));
50+
4851
final FileTree yamlFiles = projectDirectory.dir("docs/changelog")
4952
.getAsFileTree()
5053
.matching(new PatternSet().include("**/*.yml", "**/*.yaml"));
Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
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.release;
10+
11+
import com.github.javaparser.StaticJavaParser;
12+
import com.github.javaparser.ast.CompilationUnit;
13+
import com.github.javaparser.ast.NodeList;
14+
import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration;
15+
import com.github.javaparser.ast.body.FieldDeclaration;
16+
import com.github.javaparser.ast.body.VariableDeclarator;
17+
import com.github.javaparser.ast.expr.FieldAccessExpr;
18+
import com.github.javaparser.ast.expr.NameExpr;
19+
import com.github.javaparser.printer.lexicalpreservation.LexicalPreservingPrinter;
20+
import com.google.common.annotations.VisibleForTesting;
21+
22+
import org.elasticsearch.gradle.Version;
23+
import org.gradle.api.DefaultTask;
24+
import org.gradle.api.logging.Logger;
25+
import org.gradle.api.logging.Logging;
26+
import org.gradle.api.tasks.TaskAction;
27+
import org.gradle.api.tasks.options.Option;
28+
import org.gradle.initialization.layout.BuildLayout;
29+
30+
import java.io.IOException;
31+
import java.nio.file.Files;
32+
import java.nio.file.Path;
33+
import java.nio.file.StandardOpenOption;
34+
import java.util.Map;
35+
import java.util.NavigableMap;
36+
import java.util.Objects;
37+
import java.util.Optional;
38+
import java.util.TreeMap;
39+
import java.util.regex.Matcher;
40+
import java.util.regex.Pattern;
41+
import java.util.stream.Collectors;
42+
43+
import javax.annotation.Nullable;
44+
import javax.inject.Inject;
45+
46+
public class UpdateVersionsTask extends DefaultTask {
47+
private static final Logger LOGGER = Logging.getLogger(UpdateVersionsTask.class);
48+
49+
static final String SERVER_MODULE_PATH = "server/src/main/java/";
50+
static final String VERSION_FILE_PATH = SERVER_MODULE_PATH + "org/elasticsearch/Version.java";
51+
52+
static final Pattern VERSION_FIELD = Pattern.compile("V_(\\d+)_(\\d+)_(\\d+)(?:_(\\w+))?");
53+
54+
final Path rootDir;
55+
56+
@Nullable
57+
private Version addVersion;
58+
private boolean setCurrent;
59+
@Nullable
60+
private Version removeVersion;
61+
62+
@Inject
63+
public UpdateVersionsTask(BuildLayout layout) {
64+
rootDir = layout.getRootDirectory().toPath();
65+
}
66+
67+
@Option(option = "add-version", description = "Specifies the version to add")
68+
public void addVersion(String version) {
69+
this.addVersion = Version.fromString(version);
70+
}
71+
72+
@Option(option = "set-current", description = "Set the 'current' constant to the new version")
73+
public void setCurrent(boolean setCurrent) {
74+
this.setCurrent = setCurrent;
75+
}
76+
77+
@Option(option = "remove-version", description = "Specifies the version to remove")
78+
public void removeVersion(String version) {
79+
this.removeVersion = Version.fromString(version);
80+
}
81+
82+
static String toVersionField(Version version) {
83+
return String.format("V_%d_%d_%d", version.getMajor(), version.getMinor(), version.getRevision());
84+
}
85+
86+
static Optional<Version> parseVersionField(CharSequence field) {
87+
Matcher m = VERSION_FIELD.matcher(field);
88+
if (m.find() == false) return Optional.empty();
89+
90+
// if it has a qualifier, ignore it for now, just use the official release. We're not using qualifiers anymore.
91+
if (m.group(4) != null) return Optional.empty();
92+
93+
return Optional.of(
94+
new Version(Integer.parseInt(m.group(1)), Integer.parseInt(m.group(2)), Integer.parseInt(m.group(3)), m.group(4))
95+
);
96+
}
97+
98+
@TaskAction
99+
public void executeTask() throws IOException {
100+
if (addVersion == null && removeVersion == null) {
101+
throw new IllegalArgumentException("No versions to add or remove specified");
102+
}
103+
if (setCurrent && addVersion == null) {
104+
throw new IllegalArgumentException("No new version added to set as the current version");
105+
}
106+
if (Objects.equals(addVersion, removeVersion)) {
107+
throw new IllegalArgumentException("Same version specified to add and remove");
108+
}
109+
110+
Path versionJava = rootDir.resolve(VERSION_FILE_PATH);
111+
CompilationUnit file = LexicalPreservingPrinter.setup(StaticJavaParser.parse(versionJava));
112+
113+
Optional<CompilationUnit> modifiedFile = Optional.empty();
114+
if (addVersion != null) {
115+
LOGGER.lifecycle("Adding new version [{}] to [{}]", addVersion, versionJava);
116+
var added = addVersionConstant(modifiedFile.orElse(file), addVersion, setCurrent);
117+
if (added.isPresent()) {
118+
modifiedFile = added;
119+
}
120+
}
121+
if (removeVersion != null) {
122+
LOGGER.lifecycle("Removing version [{}] from [{}]", removeVersion, versionJava);
123+
var removed = removeVersionConstant(modifiedFile.orElse(file), removeVersion);
124+
if (removed.isPresent()) {
125+
modifiedFile = removed;
126+
}
127+
}
128+
129+
if (modifiedFile.isPresent()) {
130+
writeOutNewContents(versionJava, modifiedFile.get());
131+
}
132+
}
133+
134+
@VisibleForTesting
135+
static Optional<CompilationUnit> addVersionConstant(CompilationUnit versionJava, Version version, boolean updateCurrent) {
136+
String newFieldName = toVersionField(version);
137+
138+
ClassOrInterfaceDeclaration versionClass = versionJava.getClassByName("Version").get();
139+
if (versionClass.getFieldByName(newFieldName).isPresent()) {
140+
LOGGER.lifecycle("New version constant [{}] already present, skipping", newFieldName);
141+
return Optional.empty();
142+
}
143+
144+
NavigableMap<Version, FieldDeclaration> versions = versionClass.getFields()
145+
.stream()
146+
.map(f -> Map.entry(f, parseVersionField(f.getVariable(0).getNameAsString())))
147+
.filter(e -> e.getValue().isPresent())
148+
.collect(
149+
Collectors.toMap(
150+
e -> e.getValue().get(),
151+
Map.Entry::getKey,
152+
(v1, v2) -> { throw new IllegalArgumentException("Duplicate version constants " + v1); },
153+
TreeMap::new
154+
)
155+
);
156+
157+
// find the version this should be inserted after
158+
var previousVersion = versions.lowerEntry(version);
159+
if (previousVersion == null) {
160+
throw new IllegalStateException(String.format("Could not find previous version to [%s]", version));
161+
}
162+
// copy the lucene version argument from the previous version
163+
String luceneFieldRef = previousVersion.getValue()
164+
.getVariable(0)
165+
.getInitializer()
166+
.get()
167+
.findFirst(FieldAccessExpr.class)
168+
.orElseThrow(
169+
() -> new IllegalStateException(
170+
String.format("Could not find lucene version from previous version declaration [%s]", previousVersion.getValue())
171+
)
172+
)
173+
.toString();
174+
175+
FieldDeclaration newVersion = createNewVersionConstant(
176+
previousVersion.getValue(),
177+
newFieldName,
178+
String.format("%d_%02d_%02d_99, %s", version.getMajor(), version.getMinor(), version.getRevision(), luceneFieldRef)
179+
);
180+
versionClass.getMembers().addAfter(newVersion, previousVersion.getValue());
181+
182+
if (updateCurrent) {
183+
versionClass.getFieldByName("CURRENT")
184+
.orElseThrow(() -> new IllegalArgumentException("Could not find CURRENT constant"))
185+
.getVariable(0)
186+
.setInitializer(new NameExpr(newFieldName));
187+
}
188+
189+
return Optional.of(versionJava);
190+
}
191+
192+
private static FieldDeclaration createNewVersionConstant(FieldDeclaration lastVersion, String newName, String newExpr) {
193+
return new FieldDeclaration(
194+
new NodeList<>(lastVersion.getModifiers()),
195+
new VariableDeclarator(
196+
lastVersion.getCommonType(),
197+
newName,
198+
StaticJavaParser.parseExpression(String.format("new Version(%s)", newExpr))
199+
)
200+
);
201+
}
202+
203+
@VisibleForTesting
204+
static Optional<CompilationUnit> removeVersionConstant(CompilationUnit versionJava, Version version) {
205+
String removeFieldName = toVersionField(version);
206+
207+
ClassOrInterfaceDeclaration versionClass = versionJava.getClassByName("Version").get();
208+
var declaration = versionClass.getFieldByName(removeFieldName);
209+
if (declaration.isEmpty()) {
210+
LOGGER.lifecycle("Version constant [{}] not found, skipping", removeFieldName);
211+
return Optional.empty();
212+
}
213+
214+
// check if this is referenced by CURRENT
215+
String currentReference = versionClass.getFieldByName("CURRENT")
216+
.orElseThrow(() -> new IllegalArgumentException("Could not find CURRENT constant"))
217+
.getVariable(0)
218+
.getInitializer()
219+
.get()
220+
.asNameExpr()
221+
.getNameAsString();
222+
if (currentReference.equals(removeFieldName)) {
223+
throw new IllegalArgumentException(String.format("Cannot remove version [%s], it is referenced by CURRENT", version));
224+
}
225+
226+
declaration.get().remove();
227+
228+
return Optional.of(versionJava);
229+
}
230+
231+
static void writeOutNewContents(Path file, CompilationUnit unit) throws IOException {
232+
if (unit.containsData(LexicalPreservingPrinter.NODE_TEXT_DATA) == false) {
233+
throw new IllegalArgumentException("CompilationUnit has no lexical information for output");
234+
}
235+
Files.writeString(file, LexicalPreservingPrinter.print(unit), StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING);
236+
}
237+
}

0 commit comments

Comments
 (0)