diff --git a/.gitignore b/.gitignore index 8b2da4dc0832a..cac5a799012e1 100644 --- a/.gitignore +++ b/.gitignore @@ -69,6 +69,7 @@ testfixtures_shared/ # Generated checkstyle_ide.xml x-pack/plugin/esql/src/main/generated-src/generated/ +server/src/main/resources/transport/defined/manifest.txt # JEnv .java-version diff --git a/build-tools-internal/build.gradle b/build-tools-internal/build.gradle index c04ba9b90d5e7..0b81ab90ceced 100644 --- a/build-tools-internal/build.gradle +++ b/build-tools-internal/build.gradle @@ -220,6 +220,14 @@ gradlePlugin { id = 'elasticsearch.internal-yaml-rest-test' implementationClass = 'org.elasticsearch.gradle.internal.test.rest.InternalYamlRestTestPlugin' } + transportVersionManagementPlugin { + id = 'elasticsearch.transport-version-management' + implementationClass = 'org.elasticsearch.gradle.internal.transport.TransportVersionManagementPlugin' + } + globalTransportVersionManagementPlugin { + id = 'elasticsearch.global-transport-version-management' + implementationClass = 'org.elasticsearch.gradle.internal.transport.GlobalTransportVersionManagementPlugin' + } } } diff --git a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/BaseInternalPluginBuildPlugin.java b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/BaseInternalPluginBuildPlugin.java index b6f4c99e3d0e6..f89eb3131bc7f 100644 --- a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/BaseInternalPluginBuildPlugin.java +++ b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/BaseInternalPluginBuildPlugin.java @@ -15,6 +15,7 @@ import org.elasticsearch.gradle.internal.info.BuildParameterExtension; import org.elasticsearch.gradle.internal.precommit.JarHellPrecommitPlugin; import org.elasticsearch.gradle.internal.test.ClusterFeaturesMetadataPlugin; +import org.elasticsearch.gradle.internal.transport.TransportVersionManagementPlugin; import org.elasticsearch.gradle.plugin.PluginBuildPlugin; import org.elasticsearch.gradle.plugin.PluginPropertiesExtension; import org.elasticsearch.gradle.util.GradleUtils; @@ -36,6 +37,7 @@ public void apply(Project project) { project.getPluginManager().apply(JarHellPrecommitPlugin.class); project.getPluginManager().apply(ElasticsearchJavaPlugin.class); project.getPluginManager().apply(ClusterFeaturesMetadataPlugin.class); + project.getPluginManager().apply(TransportVersionManagementPlugin.class); boolean isCi = project.getRootProject().getExtensions().getByType(BuildParameterExtension.class).getCi(); // Clear default dependencies added by public PluginBuildPlugin as we add our // own project dependencies for internal builds diff --git a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/transport/CollectTransportVersionReferencesTask.java b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/transport/CollectTransportVersionReferencesTask.java new file mode 100644 index 0000000000000..76d0d48f0db1f --- /dev/null +++ b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/transport/CollectTransportVersionReferencesTask.java @@ -0,0 +1,133 @@ +/* + * 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +package org.elasticsearch.gradle.internal.transport; + +import org.gradle.api.DefaultTask; +import org.gradle.api.file.ConfigurableFileCollection; +import org.gradle.api.file.RegularFileProperty; +import org.gradle.api.tasks.CacheableTask; +import org.gradle.api.tasks.Classpath; +import org.gradle.api.tasks.OutputFile; +import org.gradle.api.tasks.TaskAction; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.Label; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.tree.LdcInsnNode; +import org.objectweb.asm.tree.MethodNode; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.HashSet; +import java.util.Set; + +/** + * This task locates all method invocations of org.elasticsearch.TransportVersion#fromName(java.lang.String) in the + * provided directory, and then records the value of string literals passed as arguments. It then records each + * string on a newline along with path and line number in the provided output file. + */ +@CacheableTask +public abstract class CollectTransportVersionReferencesTask extends DefaultTask { + public static final String TRANSPORT_VERSION_SET_CLASS = "org/elasticsearch/TransportVersion"; + public static final String TRANSPORT_VERSION_SET_METHOD_NAME = "fromName"; + public static final String CLASS_EXTENSION = ".class"; + public static final String MODULE_INFO = "module-info.class"; + + /** + * The directory to scan for method invocations. + */ + @Classpath + public abstract ConfigurableFileCollection getClassPath(); + + /** + * The output file, with each newline containing the string literal argument of each method + * invocation. + */ + @OutputFile + public abstract RegularFileProperty getOutputFile(); + + @TaskAction + public void checkTransportVersion() throws IOException { + var results = new HashSet(); + + for (var cpElement : getClassPath()) { + Path file = cpElement.toPath(); + if (Files.isDirectory(file)) { + addNamesFromClassesDirectory(results, file); + } + } + + Path outputFile = getOutputFile().get().getAsFile().toPath(); + Files.writeString(outputFile, String.join("\n", results.stream().map(Object::toString).sorted().toList())); + } + + private void addNamesFromClassesDirectory(Set results, Path file) throws IOException { + Files.walkFileTree(file, new SimpleFileVisitor<>() { + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + String filename = file.getFileName().toString(); + if (filename.endsWith(CLASS_EXTENSION) && filename.endsWith(MODULE_INFO) == false) { + try (var inputStream = Files.newInputStream(file)) { + addNamesFromClass(results, inputStream, classname(file.toString())); + } + } + return FileVisitResult.CONTINUE; + } + }); + } + + private void addNamesFromClass(Set results, InputStream classBytes, String classname) + throws IOException { + ClassVisitor classVisitor = new ClassVisitor(Opcodes.ASM9) { + @Override + public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) { + return new MethodNode(Opcodes.ASM9, access, name, descriptor, signature, exceptions) { + int lineNumber = -1; + + @Override + public void visitLineNumber(int line, Label start) { + lineNumber = line; + } + + @Override + public void visitMethodInsn(int opcode, String owner, String name, String descriptor, boolean isInterface) { + if (owner.equals(TRANSPORT_VERSION_SET_CLASS) && name.equals(TRANSPORT_VERSION_SET_METHOD_NAME)) { + var abstractInstruction = this.instructions.getLast(); + String location = classname + " line " + lineNumber; + if (abstractInstruction instanceof LdcInsnNode ldcInsnNode + && ldcInsnNode.cst instanceof String tvName + && tvName.isEmpty() == false) { + results.add(new TransportVersionUtils.TransportVersionReference(tvName, location)); + } else { + // The instruction is not a LDC with a String constant (or an empty String), which is not allowed. + throw new RuntimeException( + "TransportVersion.fromName must be called with a non-empty String literal. " + "See " + location + "." + ); + } + } + super.visitMethodInsn(opcode, owner, name, descriptor, isInterface); + } + }; + } + }; + ClassReader classReader = new ClassReader(classBytes); + classReader.accept(classVisitor, 0); + } + + private static String classname(String filename) { + return filename.substring(0, filename.length() - CLASS_EXTENSION.length()).replaceAll("[/\\\\]", "."); + } +} diff --git a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/transport/GenerateTransportVersionManifestTask.java b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/transport/GenerateTransportVersionManifestTask.java new file mode 100644 index 0000000000000..5de32bf1835f7 --- /dev/null +++ b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/transport/GenerateTransportVersionManifestTask.java @@ -0,0 +1,58 @@ +/* + * 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +package org.elasticsearch.gradle.internal.transport; + +import org.gradle.api.DefaultTask; +import org.gradle.api.file.DirectoryProperty; +import org.gradle.api.file.RegularFileProperty; +import org.gradle.api.tasks.InputDirectory; +import org.gradle.api.tasks.Optional; +import org.gradle.api.tasks.OutputFile; +import org.gradle.api.tasks.PathSensitive; +import org.gradle.api.tasks.PathSensitivity; +import org.gradle.api.tasks.TaskAction; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; + +public abstract class GenerateTransportVersionManifestTask extends DefaultTask { + @InputDirectory + @Optional + @PathSensitive(PathSensitivity.RELATIVE) + public abstract DirectoryProperty getDefinitionsDirectory(); + + @OutputFile + public abstract RegularFileProperty getManifestFile(); + + @TaskAction + public void generateTransportVersionManifest() throws IOException { + Path manifestFile = getManifestFile().get().getAsFile().toPath(); + if (getDefinitionsDirectory().isPresent() == false) { + // no definitions to capture, remove this leniency once all branches have at least one version + Files.writeString(manifestFile, "", StandardCharsets.UTF_8); + return; + } + Path constantsDir = getDefinitionsDirectory().get().getAsFile().toPath(); + + try (var writer = Files.newBufferedWriter(manifestFile)) { + try (var stream = Files.list(constantsDir)) { + for (String filename : stream.map(p -> p.getFileName().toString()).toList()) { + if (filename.equals(manifestFile.getFileName().toString())) { + // don't list self + continue; + } + writer.write(filename + "\n"); + } + } + } + } +} diff --git a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/transport/GlobalTransportVersionManagementPlugin.java b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/transport/GlobalTransportVersionManagementPlugin.java new file mode 100644 index 0000000000000..b8bf0adbdd8e9 --- /dev/null +++ b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/transport/GlobalTransportVersionManagementPlugin.java @@ -0,0 +1,68 @@ +/* + * 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +package org.elasticsearch.gradle.internal.transport; + +import org.gradle.api.Plugin; +import org.gradle.api.Project; +import org.gradle.api.artifacts.Configuration; +import org.gradle.api.artifacts.dsl.DependencyHandler; +import org.gradle.api.file.Directory; +import org.gradle.api.plugins.JavaPlugin; +import org.gradle.api.tasks.Copy; +import org.gradle.language.base.plugins.LifecycleBasePlugin; + +import java.util.Map; + +public class GlobalTransportVersionManagementPlugin implements Plugin { + + @Override + public void apply(Project project) { + project.getPluginManager().apply(LifecycleBasePlugin.class); + + DependencyHandler depsHandler = project.getDependencies(); + Configuration tvReferencesConfig = project.getConfigurations().create("globalTvReferences"); + tvReferencesConfig.setCanBeConsumed(false); + tvReferencesConfig.setCanBeResolved(true); + tvReferencesConfig.attributes(TransportVersionUtils::addTransportVersionReferencesAttribute); + + // iterate through all projects, and if the management plugin is applied, add that project back as a dep to check + for (Project subProject : project.getRootProject().getSubprojects()) { + subProject.getPlugins().withType(TransportVersionManagementPlugin.class).configureEach(plugin -> { + tvReferencesConfig.getDependencies().add(depsHandler.project(Map.of("path", subProject.getPath()))); + }); + } + + var validateTask = project.getTasks() + .register("validateTransportVersionDefinitions", ValidateTransportVersionDefinitionsTask.class, t -> { + t.setGroup("Transport Versions"); + t.setDescription("Validates that all defined TransportVersion constants are used in at least one project"); + Directory definitionsDir = TransportVersionUtils.getDefinitionsDirectory(project); + if (definitionsDir.getAsFile().exists()) { + t.getDefinitionsDirectory().set(definitionsDir); + } + t.getReferencesFiles().setFrom(tvReferencesConfig); + }); + project.getTasks().named(LifecycleBasePlugin.CHECK_TASK_NAME).configure(t -> t.dependsOn(validateTask)); + + var generateManifestTask = project.getTasks() + .register("generateTransportVersionManifest", GenerateTransportVersionManifestTask.class, t -> { + t.setGroup("Transport Versions"); + t.setDescription("Generate a manifest resource for all the known transport version definitions"); + Directory definitionsDir = TransportVersionUtils.getDefinitionsDirectory(project); + if (definitionsDir.getAsFile().exists()) { + t.getDefinitionsDirectory().set(definitionsDir); + } + t.getManifestFile().set(project.getLayout().getBuildDirectory().file("generated-resources/manifest.txt")); + }); + project.getTasks().named(JavaPlugin.PROCESS_RESOURCES_TASK_NAME, Copy.class).configure(t -> { + t.into("transport/defined", c -> c.from(generateManifestTask)); + }); + } +} diff --git a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/transport/TransportVersionManagementPlugin.java b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/transport/TransportVersionManagementPlugin.java new file mode 100644 index 0000000000000..47af8f288f958 --- /dev/null +++ b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/transport/TransportVersionManagementPlugin.java @@ -0,0 +1,54 @@ +/* + * 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +package org.elasticsearch.gradle.internal.transport; + +import org.elasticsearch.gradle.util.GradleUtils; +import org.gradle.api.Plugin; +import org.gradle.api.Project; +import org.gradle.api.artifacts.Configuration; +import org.gradle.api.file.Directory; +import org.gradle.api.tasks.SourceSet; +import org.gradle.language.base.plugins.LifecycleBasePlugin; + +public class TransportVersionManagementPlugin implements Plugin { + + @Override + public void apply(Project project) { + project.getPluginManager().apply(LifecycleBasePlugin.class); + + var collectTask = project.getTasks() + .register("collectTransportVersionReferences", CollectTransportVersionReferencesTask.class, t -> { + t.setGroup("Transport Versions"); + t.setDescription("Collects all TransportVersion references used throughout the project"); + SourceSet mainSourceSet = GradleUtils.getJavaSourceSets(project).findByName(SourceSet.MAIN_SOURCE_SET_NAME); + t.getClassPath().setFrom(mainSourceSet.getOutput()); + t.getOutputFile().set(project.getLayout().getBuildDirectory().file("transport-version/references.txt")); + }); + + Configuration tvReferencesConfig = project.getConfigurations().create("transportVersionReferences", c -> { + c.setCanBeConsumed(true); + c.setCanBeResolved(false); + c.attributes(TransportVersionUtils::addTransportVersionReferencesAttribute); + }); + project.getArtifacts().add(tvReferencesConfig.getName(), collectTask); + + var validateTask = project.getTasks() + .register("validateTransportVersionReferences", ValidateTransportVersionReferencesTask.class, t -> { + t.setGroup("Transport Versions"); + t.setDescription("Validates that all TransportVersion references used in the project have an associated definition file"); + Directory definitionsDir = TransportVersionUtils.getDefinitionsDirectory(project); + if (definitionsDir.getAsFile().exists()) { + t.getDefinitionsDirectory().set(definitionsDir); + } + t.getReferencesFile().set(collectTask.get().getOutputFile()); + }); + project.getTasks().named(LifecycleBasePlugin.CHECK_TASK_NAME).configure(t -> t.dependsOn(validateTask)); + } +} diff --git a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/transport/TransportVersionUtils.java b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/transport/TransportVersionUtils.java new file mode 100644 index 0000000000000..ca932adadae25 --- /dev/null +++ b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/transport/TransportVersionUtils.java @@ -0,0 +1,89 @@ +/* + * 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +package org.elasticsearch.gradle.internal.transport; + +import com.google.common.collect.Comparators; + +import org.gradle.api.Project; +import org.gradle.api.attributes.Attribute; +import org.gradle.api.attributes.AttributeContainer; +import org.gradle.api.file.Directory; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; + +import static org.gradle.api.artifacts.type.ArtifactTypeDefinition.ARTIFACT_TYPE_ATTRIBUTE; + +class TransportVersionUtils { + + static final Attribute TRANSPORT_VERSION_REFERENCES_ATTRIBUTE = Attribute.of("transport-version-references", Boolean.class); + + record TransportVersionConstant(String name, List ids) {} + + record TransportVersionReference(String name, String location) { + @Override + public String toString() { + return name + " " + location; + } + } + + static TransportVersionConstant readDefinitionFile(Path file) throws IOException { + assert file.endsWith(".csv"); + String rawName = file.getFileName().toString(); + String name = rawName.substring(0, rawName.length() - 4); + List ids = new ArrayList<>(); + + for (String rawId : Files.readString(file, StandardCharsets.UTF_8).split(",")) { + try { + ids.add(Integer.parseInt(rawId.strip())); + } catch (NumberFormatException e) { + throw new IOException("Failed to parse id " + rawId + " in " + file, e); + } + } + + if (Comparators.isInOrder(ids, Comparator.reverseOrder()) == false) { + throw new IOException("invalid transport version data file [" + file + "], ids are not in sorted"); + } + return new TransportVersionConstant(name, ids); + } + + static List readReferencesFile(Path file) throws IOException { + assert file.endsWith(".txt"); + List results = new ArrayList<>(); + for (String line : Files.readAllLines(file, StandardCharsets.UTF_8)) { + String[] parts = line.split(" ", 2); + if (parts.length != 2) { + throw new IOException("Invalid transport version data file [" + file + "]: " + line); + } + results.add(new TransportVersionReference(parts[0], parts[1])); + } + return results; + } + + static Directory getDefinitionsDirectory(Project project) { + var projectName = project.findProperty("org.elasticsearch.transport.definitionsProject"); + if (projectName == null) { + projectName = ":server"; + } + Directory projectDir = project.project(projectName.toString()).getLayout().getProjectDirectory(); + return projectDir.dir("src/main/resources/transport/defined"); + } + + static void addTransportVersionReferencesAttribute(AttributeContainer attributes) { + attributes.attribute(ARTIFACT_TYPE_ATTRIBUTE, "txt"); + attributes.attribute(TransportVersionUtils.TRANSPORT_VERSION_REFERENCES_ATTRIBUTE, true); + } + +} diff --git a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/transport/ValidateTransportVersionDefinitionsTask.java b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/transport/ValidateTransportVersionDefinitionsTask.java new file mode 100644 index 0000000000000..9d2c4dfafceb9 --- /dev/null +++ b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/transport/ValidateTransportVersionDefinitionsTask.java @@ -0,0 +1,69 @@ +/* + * 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +package org.elasticsearch.gradle.internal.transport; + +import org.elasticsearch.gradle.internal.transport.TransportVersionUtils.TransportVersionReference; +import org.gradle.api.DefaultTask; +import org.gradle.api.file.ConfigurableFileCollection; +import org.gradle.api.file.DirectoryProperty; +import org.gradle.api.tasks.CacheableTask; +import org.gradle.api.tasks.InputDirectory; +import org.gradle.api.tasks.InputFiles; +import org.gradle.api.tasks.Optional; +import org.gradle.api.tasks.PathSensitive; +import org.gradle.api.tasks.PathSensitivity; +import org.gradle.api.tasks.TaskAction; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.HashSet; +import java.util.Set; + +import static org.elasticsearch.gradle.internal.transport.TransportVersionUtils.readDefinitionFile; +import static org.elasticsearch.gradle.internal.transport.TransportVersionUtils.readReferencesFile; + +/** + * Validates that each defined transport version constant is referenced by at least one project. + */ +@CacheableTask +public abstract class ValidateTransportVersionDefinitionsTask extends DefaultTask { + + @InputDirectory + @Optional + @PathSensitive(PathSensitivity.RELATIVE) + public abstract DirectoryProperty getDefinitionsDirectory(); + + @InputFiles + @PathSensitive(PathSensitivity.RELATIVE) + public abstract ConfigurableFileCollection getReferencesFiles(); + + @TaskAction + public void validateTransportVersions() throws IOException { + if (getDefinitionsDirectory().isPresent() == false) { + return; // no definitions to validate, remove this leniency once all branches have at least one version + } + Path constantsDir = getDefinitionsDirectory().getAsFile().get().toPath(); + + Set allTvNames = new HashSet<>(); + for (var tvReferencesFile : getReferencesFiles()) { + readReferencesFile(tvReferencesFile.toPath()).stream().map(TransportVersionReference::name).forEach(allTvNames::add); + } + + try (var constantsStream = Files.list(constantsDir)) { + for (var constantsFile : constantsStream.toList()) { + var tv = readDefinitionFile(constantsFile); + if (allTvNames.contains(tv.name()) == false) { + throw new IllegalStateException("Transport version constant " + tv.name() + " is not referenced"); + } + } + } + } +} diff --git a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/transport/ValidateTransportVersionReferencesTask.java b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/transport/ValidateTransportVersionReferencesTask.java new file mode 100644 index 0000000000000..79050f1b171a5 --- /dev/null +++ b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/transport/ValidateTransportVersionReferencesTask.java @@ -0,0 +1,67 @@ +/* + * 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +package org.elasticsearch.gradle.internal.transport; + +import org.gradle.api.DefaultTask; +import org.gradle.api.file.DirectoryProperty; +import org.gradle.api.file.RegularFileProperty; +import org.gradle.api.tasks.CacheableTask; +import org.gradle.api.tasks.InputDirectory; +import org.gradle.api.tasks.InputFile; +import org.gradle.api.tasks.Optional; +import org.gradle.api.tasks.PathSensitive; +import org.gradle.api.tasks.PathSensitivity; +import org.gradle.api.tasks.TaskAction; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.function.Predicate; + +/** + * Validates that each transport version named reference has a constant definition. + */ +@CacheableTask +public abstract class ValidateTransportVersionReferencesTask extends DefaultTask { + + @InputDirectory + @Optional + @PathSensitive(PathSensitivity.RELATIVE) + public abstract DirectoryProperty getDefinitionsDirectory(); + + @InputFile + @PathSensitive(PathSensitivity.RELATIVE) + public abstract RegularFileProperty getReferencesFile(); + + @TaskAction + public void validateTransportVersions() throws IOException { + final Predicate referenceChecker; + if (getDefinitionsDirectory().isPresent()) { + Path definitionsDir = getDefinitionsDirectory().getAsFile().get().toPath(); + referenceChecker = (name) -> Files.exists(definitionsDir.resolve(name + ".csv")); + } else { + referenceChecker = (name) -> false; + } + Path namesFile = getReferencesFile().get().getAsFile().toPath(); + + for (var tvReference : TransportVersionUtils.readReferencesFile(namesFile)) { + if (referenceChecker.test(tvReference.name()) == false) { + throw new RuntimeException( + "TransportVersion.fromName(\"" + + tvReference.name() + + "\") was used at " + + tvReference.location() + + ", but lacks a" + + " transport version definition. This can be generated with the task" // todo + ); + } + } + } +} diff --git a/server/build.gradle b/server/build.gradle index 1afb32a973e02..95d68672ec64c 100644 --- a/server/build.gradle +++ b/server/build.gradle @@ -11,6 +11,8 @@ apply plugin: 'elasticsearch.build' apply plugin: 'elasticsearch.publish' apply plugin: 'elasticsearch.internal-cluster-test' apply plugin: 'elasticsearch.internal-test-artifact' +apply plugin: 'elasticsearch.transport-version-management' +apply plugin: 'elasticsearch.global-transport-version-management' publishing { publications { diff --git a/server/src/main/java/org/elasticsearch/TransportVersion.java b/server/src/main/java/org/elasticsearch/TransportVersion.java index 2ac4c1bf72ab6..d31bb2a5e5495 100644 --- a/server/src/main/java/org/elasticsearch/TransportVersion.java +++ b/server/src/main/java/org/elasticsearch/TransportVersion.java @@ -399,7 +399,7 @@ private static Map loadTransportVersionsByName() { throw new UncheckedIOException("latest transport version file not found at [" + latestLocation + "]", ioe); } - String manifestLocation = "/transport/constant/manifest.txt"; + String manifestLocation = "/transport/defined/manifest.txt"; List versionFileNames = null; if (latestId > -1) { try (InputStream inputStream = TransportVersion.class.getResourceAsStream(manifestLocation)) { @@ -414,7 +414,7 @@ private static Map loadTransportVersionsByName() { if (versionFileNames != null) { for (String name : versionFileNames) { - String versionLocation = "/transport/constant/" + name; + String versionLocation = "/transport/defined/" + name; try (InputStream inputStream = TransportVersion.class.getResourceAsStream(versionLocation)) { if (inputStream == null) { throw new IllegalStateException("transport version file not found at [" + versionLocation + "]"); diff --git a/server/src/test/resources/transport/constant/manifest.txt b/server/src/test/resources/transport/defined/manifest.txt similarity index 100% rename from server/src/test/resources/transport/constant/manifest.txt rename to server/src/test/resources/transport/defined/manifest.txt diff --git a/server/src/test/resources/transport/constant/test_0.csv b/server/src/test/resources/transport/defined/test_0.csv similarity index 100% rename from server/src/test/resources/transport/constant/test_0.csv rename to server/src/test/resources/transport/defined/test_0.csv diff --git a/server/src/test/resources/transport/constant/test_1.csv b/server/src/test/resources/transport/defined/test_1.csv similarity index 100% rename from server/src/test/resources/transport/constant/test_1.csv rename to server/src/test/resources/transport/defined/test_1.csv diff --git a/server/src/test/resources/transport/constant/test_2.csv b/server/src/test/resources/transport/defined/test_2.csv similarity index 100% rename from server/src/test/resources/transport/constant/test_2.csv rename to server/src/test/resources/transport/defined/test_2.csv diff --git a/server/src/test/resources/transport/constant/test_3.csv b/server/src/test/resources/transport/defined/test_3.csv similarity index 100% rename from server/src/test/resources/transport/constant/test_3.csv rename to server/src/test/resources/transport/defined/test_3.csv diff --git a/server/src/test/resources/transport/constant/test_4.csv b/server/src/test/resources/transport/defined/test_4.csv similarity index 100% rename from server/src/test/resources/transport/constant/test_4.csv rename to server/src/test/resources/transport/defined/test_4.csv diff --git a/x-pack/plugin/esql/compute/build.gradle b/x-pack/plugin/esql/compute/build.gradle index f2eca7aee058f..54a5cb215bdb9 100644 --- a/x-pack/plugin/esql/compute/build.gradle +++ b/x-pack/plugin/esql/compute/build.gradle @@ -3,6 +3,7 @@ import org.elasticsearch.gradle.internal.util.SourceDirectoryCommandLineArgument apply plugin: 'elasticsearch.build' apply plugin: 'elasticsearch.string-templates' apply plugin: 'elasticsearch.publish' +apply plugin: 'elasticsearch.transport-version-management' base { archivesName = 'x-pack-esql-compute'