-
Notifications
You must be signed in to change notification settings - Fork 25.4k
Build Tasks to Find and Verify TransportVersion Definitions #131782
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
rjernst
merged 27 commits into
elastic:main
from
JVerwolf:new-tv-logic-locate-declaration
Aug 1, 2025
Merged
Changes from all commits
Commits
Show all changes
27 commits
Select commit
Hold shift + click to select a range
57193ca
Add task to scan for TV instantiations
JVerwolf de27ca3
Initial draft
JVerwolf 68dbac8
wip
rjernst 6071cdf
Merge branch 'main' into new-tv-logic-locate-declaration
rjernst 312235e
add example
rjernst 818bda4
[CI] Auto commit changes from spotless
1721d2e
iter
rjernst 8c8c7c4
iter and add generate manifest
rjernst 3d30062
wired manifest task
rjernst d945947
done
rjernst c6251da
spotless and rename
rjernst 97503ab
moved to defined
rjernst 508aec1
fix task name
rjernst 1e3fb65
address review comments
rjernst 49dca33
iter
rjernst a4c62df
more feedback
rjernst 5aab19c
[CI] Auto commit changes from spotless
1cfa412
wip
rjernst f192388
Merge branch 'main' into new-tv-logic-locate-declaration
rjernst 3dd41ff
remove constants
rjernst 252f9c7
Merge branch 'main' into new-tv-logic-locate-declaration
rjernst df07e02
Merge branch 'main' into new-tv-logic-locate-declaration
rjernst 3c465d4
make definitions dir optional for validation
rjernst 6821e70
better reference checker
rjernst c184989
Merge branch 'main' into new-tv-logic-locate-declaration
rjernst aa14965
try again
rjernst 1e9a4ec
guard global plugin too
rjernst File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
133 changes: 133 additions & 0 deletions
133
...va/org/elasticsearch/gradle/internal/transport/CollectTransportVersionReferencesTask.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
rjernst marked this conversation as resolved.
Show resolved
Hide resolved
|
||
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("[/\\\\]", "."); | ||
} | ||
} |
46 changes: 46 additions & 0 deletions
46
...ava/org/elasticsearch/gradle/internal/transport/GenerateTransportVersionManifestTask.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"); | ||
} | ||
} | ||
} | ||
} | ||
} |
65 changes: 65 additions & 0 deletions
65
...a/org/elasticsearch/gradle/internal/transport/GlobalTransportVersionManagementPlugin.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) { | ||
rjernst marked this conversation as resolved.
Show resolved
Hide resolved
|
||
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")); | ||
rjernst marked this conversation as resolved.
Show resolved
Hide resolved
|
||
}); | ||
project.getTasks().named(JavaPlugin.PROCESS_RESOURCES_TASK_NAME, Copy.class).configure(t -> { | ||
t.into("transport/defined", c -> c.from(generateManifestTask)); | ||
}); | ||
} | ||
} |
54 changes: 54 additions & 0 deletions
54
...in/java/org/elasticsearch/gradle/internal/transport/TransportVersionManagementPlugin.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)); | ||
} | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.