Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
4df06c3
Add ReleaseVersions class for reading version record files
thecoop Dec 21, 2023
9507f7c
Add a task to tag new versions in the version records
thecoop Dec 21, 2023
a0a98ff
Merge remote-tracking branch 'upstream/main' into version-records
thecoop Jan 10, 2024
4f8e744
Add some comments to versions
thecoop Jan 10, 2024
241d685
Merge branch 'main' into version-records
thecoop Jan 11, 2024
7920370
Change version map to load on startup
thecoop Jan 11, 2024
4203945
Update docs/changelog/103627.yaml
thecoop Jan 15, 2024
69c01cd
Merge remote-tracking branch 'upstream/main' into version-records
thecoop Jan 15, 2024
be1cee5
Delete docs/changelog/103627.yaml
thecoop Jan 15, 2024
085238f
Separate out extract & tag functionality into separate gradle tasks
thecoop Jan 15, 2024
3f1399a
Use constants for string constants
thecoop Jan 15, 2024
e6087e9
Fix setting outputfile parameter
thecoop Jan 15, 2024
a9aae42
Add some tests
thecoop Jan 15, 2024
ac44aca
More tests
thecoop Jan 15, 2024
67e961d
Update docs/changelog/103627.yaml
thecoop Jan 15, 2024
5027972
Only sort on output
thecoop Jan 15, 2024
5ce4286
Create file if it does not exist already
thecoop Jan 17, 2024
33510bc
Merge branch 'main' into version-records
thecoop Jan 17, 2024
1865d54
Merge branch 'main' into version-records
elasticmachine Jan 17, 2024
16c983c
Merge branch 'main' into version-records
thecoop Jan 18, 2024
3dc09d9
Use lists rather than sorted sets for version lists
thecoop Jan 18, 2024
dbf0f90
Add extra checks to options
thecoop Jan 18, 2024
80fef71
Support processing Version from 7.17 releases
thecoop Jan 18, 2024
8cb7350
Merge remote-tracking branch 'upstream/main' into version-records
thecoop Jan 22, 2024
108749d
Make it clear raw ids are from snapshot builds
thecoop Jan 22, 2024
702329b
Some tweaks
thecoop Jan 22, 2024
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
@@ -0,0 +1,35 @@
/*
* 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.internal.release;

import org.gradle.api.DefaultTask;
import org.gradle.initialization.layout.BuildLayout;

import java.nio.file.Path;

public abstract class AbstractVersionsTask extends DefaultTask {

static final String TRANSPORT_VERSION_TYPE = "TransportVersion";
static final String INDEX_VERSION_TYPE = "IndexVersion";

static final String SERVER_MODULE_PATH = "server/src/main/java/";
static final String TRANSPORT_VERSION_FILE_PATH = SERVER_MODULE_PATH + "org/elasticsearch/TransportVersions.java";
static final String INDEX_VERSION_FILE_PATH = SERVER_MODULE_PATH + "org/elasticsearch/index/IndexVersions.java";

static final String SERVER_RESOURCES_PATH = "server/src/main/resources/";
static final String TRANSPORT_VERSIONS_RECORD = SERVER_RESOURCES_PATH + "org/elasticsearch/TransportVersions.csv";
static final String INDEX_VERSIONS_RECORD = SERVER_RESOURCES_PATH + "org/elasticsearch/index/IndexVersions.csv";

final Path rootDir;

protected AbstractVersionsTask(BuildLayout layout) {
rootDir = layout.getRootDirectory().toPath();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
/*
* 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.internal.release;

import com.github.javaparser.StaticJavaParser;
import com.github.javaparser.ast.CompilationUnit;
import com.github.javaparser.ast.body.FieldDeclaration;
import com.github.javaparser.ast.expr.IntegerLiteralExpr;

import org.gradle.api.logging.Logger;
import org.gradle.api.logging.Logging;
import org.gradle.api.tasks.TaskAction;
import org.gradle.api.tasks.options.Option;
import org.gradle.initialization.layout.BuildLayout;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;

import javax.inject.Inject;

public class ExtractCurrentVersionsTask extends AbstractVersionsTask {
private static final Logger LOGGER = Logging.getLogger(ExtractCurrentVersionsTask.class);

private Path outputFile;

@Inject
public ExtractCurrentVersionsTask(BuildLayout layout) {
super(layout);
}

@Option(option = "output-file", description = "File to output tag information to")
public void outputFile(String file) {
this.outputFile = Path.of(file);
}

@TaskAction
public void executeTask() throws IOException {
if (outputFile == null) {
throw new IllegalArgumentException("Output file not specified");
}

LOGGER.lifecycle("Extracting latest version information");

List<String> output = new ArrayList<>();
int transportVersion = readLatestVersion(rootDir.resolve(TRANSPORT_VERSION_FILE_PATH));
LOGGER.lifecycle("Transport version: {}", transportVersion);
output.add(TRANSPORT_VERSION_TYPE + ":" + transportVersion);

int indexVersion = readLatestVersion(rootDir.resolve(INDEX_VERSION_FILE_PATH));
LOGGER.lifecycle("Index version: {}", indexVersion);
output.add(INDEX_VERSION_TYPE + ":" + indexVersion);

LOGGER.lifecycle("Writing version information to {}", outputFile);
Files.write(outputFile, output, StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING);
}

static class FieldIdExtractor implements Consumer<FieldDeclaration> {
private Integer highestVersionId;

Integer highestVersionId() {
return highestVersionId;
}

@Override
public void accept(FieldDeclaration fieldDeclaration) {
var ints = fieldDeclaration.findAll(IntegerLiteralExpr.class);
switch (ints.size()) {
case 0 -> {
// No ints in the field declaration, ignore
}
case 1 -> {
int id = ints.get(0).asNumber().intValue();
if (highestVersionId != null && highestVersionId > id) {
LOGGER.warn("Version ids [{}, {}] out of order", highestVersionId, id);
} else {
highestVersionId = id;
}
}
default -> LOGGER.warn("Multiple integers found in version field declaration [{}]", fieldDeclaration); // and ignore it
}
}
}

private static int readLatestVersion(Path javaVersionsFile) throws IOException {
CompilationUnit java = StaticJavaParser.parse(javaVersionsFile);

FieldIdExtractor extractor = new FieldIdExtractor();
java.walk(FieldDeclaration.class, extractor); // walks in code file order
if (extractor.highestVersionId == null) {
throw new IllegalArgumentException("No version ids found in " + javaVersionsFile);
}
return extractor.highestVersionId;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ public void apply(Project project) {
project.getTasks()
.register("updateVersions", UpdateVersionsTask.class, t -> project.getTasks().named("spotlessApply").get().mustRunAfter(t));

project.getTasks().register("extractCurrentVersions", ExtractCurrentVersionsTask.class);
project.getTasks().register("tagVersions", TagVersionsTask.class);

final FileTree yamlFiles = projectDirectory.dir("docs/changelog")
.getAsFileTree()
.matching(new PatternSet().include("**/*.yml", "**/*.yaml"));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
/*
* 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.internal.release;

import org.elasticsearch.gradle.Version;
import org.gradle.api.logging.Logger;
import org.gradle.api.logging.Logging;
import org.gradle.api.tasks.TaskAction;
import org.gradle.api.tasks.options.Option;
import org.gradle.initialization.layout.BuildLayout;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import javax.inject.Inject;

public class TagVersionsTask extends AbstractVersionsTask {
private static final Logger LOGGER = Logging.getLogger(TagVersionsTask.class);

private Version releaseVersion;

private Map<String, Integer> tagVersions = Map.of();

@Inject
public TagVersionsTask(BuildLayout layout) {
super(layout);
}

@Option(option = "release", description = "The release version to be tagged")
public void release(String version) {
releaseVersion = Version.fromString(version);
}

@Option(option = "tag-version", description = "Version id to tag. Of the form <VersionType>:<id>.")
public void tagVersions(List<String> version) {
this.tagVersions = version.stream().map(l -> {
var split = l.split(":");
if (split.length != 2) throw new IllegalArgumentException("Invalid tag format [" + l + "]");
return split;
}).collect(Collectors.toMap(l -> l[0], l -> Integer.parseInt(l[1])));
}

@TaskAction
public void executeTask() throws IOException {
if (releaseVersion == null) {
throw new IllegalArgumentException("Release version not specified");
}
if (tagVersions.isEmpty()) {
throw new IllegalArgumentException("No version tags specified");
}

LOGGER.lifecycle("Tagging version {} component ids", releaseVersion);

var versions = expandV7Version(tagVersions);

for (var v : versions.entrySet()) {
Path recordFile = switch (v.getKey()) {
case TRANSPORT_VERSION_TYPE -> rootDir.resolve(TRANSPORT_VERSIONS_RECORD);
case INDEX_VERSION_TYPE -> rootDir.resolve(INDEX_VERSIONS_RECORD);
default -> throw new IllegalArgumentException("Unknown version type " + v.getKey());
};

LOGGER.lifecycle("Adding version record for {} to [{}]: [{},{}]", v.getKey(), recordFile, releaseVersion, v.getValue());

Path file = rootDir.resolve(recordFile);
List<String> versionRecords = Files.readAllLines(file);
var modified = addVersionRecord(versionRecords, releaseVersion, v.getValue());
if (modified.isPresent()) {
Files.write(
file,
modified.get(),
StandardOpenOption.CREATE,
StandardOpenOption.WRITE,
StandardOpenOption.TRUNCATE_EXISTING
);
}
}
}

/*
* V7 just extracts a single Version. If so, this version needs to be applied to transport and index versions.
*/
private static Map<String, Integer> expandV7Version(Map<String, Integer> tagVersions) {
Integer v7Version = tagVersions.get("Version");
if (v7Version == null) return tagVersions;

return Map.of(TRANSPORT_VERSION_TYPE, v7Version, INDEX_VERSION_TYPE, v7Version);
}

private static final Pattern VERSION_LINE = Pattern.compile("(\\d+\\.\\d+\\.\\d+),(\\d+)");

static Optional<List<String>> addVersionRecord(List<String> versionRecordLines, Version release, int id) {
Map<Version, Integer> versions = versionRecordLines.stream().map(l -> {
Matcher m = VERSION_LINE.matcher(l);
if (m.matches() == false) throw new IllegalArgumentException(String.format("Incorrect format for line [%s]", l));
return m;
}).collect(Collectors.toMap(m -> Version.fromString(m.group(1)), m -> Integer.parseInt(m.group(2))));

Integer existing = versions.putIfAbsent(release, id);
if (existing != null) {
if (existing.equals(id)) {
LOGGER.lifecycle("Version id [{}] for release [{}] already recorded", id, release);
return Optional.empty();
} else {
throw new IllegalArgumentException(
String.format(
"Release [%s] already recorded with version id [%s], cannot update to version [%s]",
release,
existing,
id
)
);
}
}

return Optional.of(
versions.entrySet().stream().sorted(Map.Entry.comparingByKey()).map(e -> e.getKey() + "," + e.getValue()).toList()
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* 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.internal.release;

import com.github.javaparser.StaticJavaParser;
import com.github.javaparser.ast.body.FieldDeclaration;

import org.elasticsearch.gradle.internal.release.ExtractCurrentVersionsTask.FieldIdExtractor;
import org.junit.Test;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;

public class ExtractCurrentVersionsTaskTests {

@Test
public void testFieldExtractor() {
var unit = StaticJavaParser.parse("""
public class Version {
public static final Version V_1 = def(1);
public static final Version V_2 = def(2);
public static final Version V_3 = def(3);

// ignore fields with no or more than one int
public static final Version REF = V_3;
public static final Version COMPUTED = compute(100, 200);
}""");

FieldIdExtractor extractor = new FieldIdExtractor();
unit.walk(FieldDeclaration.class, extractor);
assertThat(extractor.highestVersionId(), is(3));
}
}
Loading