Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -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
8 changes: 8 additions & 0 deletions build-tools-internal/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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'
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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<TransportVersionUtils.TransportVersionReference>();

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<TransportVersionUtils.TransportVersionReference> 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<TransportVersionUtils.TransportVersionReference> 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("[/\\\\]", ".");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* 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.OutputFile;
import org.gradle.api.tasks.TaskAction;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;

public abstract class GenerateTransportVersionManifestTask extends DefaultTask {
@InputDirectory
public abstract DirectoryProperty getDefinitionsDirectory();

@OutputFile
public abstract RegularFileProperty getManifestFile();

@TaskAction
public void generateTransportVersionManifest() throws IOException {
Path constantsDir = getDefinitionsDirectory().get().getAsFile().toPath();
Path manifestFile = getManifestFile().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");
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/*
* 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<Project> {

@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");
t.getDefinitionsDirectory().set(TransportVersionUtils.getDefinitionsDirectory(project));
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));
});
}
}
Original file line number Diff line number Diff line change
@@ -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<Project> {

@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));
}
}
Loading