Skip to content

Commit c827ff1

Browse files
committed
oops
1 parent 4f77cc0 commit c827ff1

File tree

5 files changed

+354
-354
lines changed

5 files changed

+354
-354
lines changed

build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/transport/GlobalTransportVersionManagementPlugin.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ public void apply(Project project) {
4343
}
4444

4545
var validateTask = project.getTasks()
46-
.register("validateTransportVersionDefinitions", ValidateTransportVersionResourcesTask.class, t -> {
46+
.register("validateTransportVersionDefinitions", ValidateTransportVersionDefinitionsTask.class, t -> {
4747
t.setGroup("Transport Versions");
4848
t.setDescription("Validates that all defined TransportVersion constants are used in at least one project");
4949
Directory resourcesDir = getResourcesDirectory(project);

build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/transport/TransportVersionManagementPlugin.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ public void apply(Project project) {
4343
project.getArtifacts().add(tvReferencesConfig.getName(), collectTask);
4444

4545
var validateTask = project.getTasks()
46-
.register("validateTransportVersionReferences", ValidateTransportVersionDefinitionsTask.class, t -> {
46+
.register("validateTransportVersionReferences", ValidateTransportVersionReferencesTask.class, t -> {
4747
t.setGroup("Transport Versions");
4848
t.setDescription("Validates that all TransportVersion references used in the project have an associated definition file");
4949
Directory definitionsDir = getDefinitionsDirectory(getResourcesDirectory(project));

build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/transport/ValidateTransportVersionDefinitionsTask.java

Lines changed: 285 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -9,59 +9,319 @@
99

1010
package org.elasticsearch.gradle.internal.transport;
1111

12+
import com.google.common.collect.Comparators;
13+
1214
import org.gradle.api.DefaultTask;
15+
import org.gradle.api.file.ConfigurableFileCollection;
1316
import org.gradle.api.file.DirectoryProperty;
14-
import org.gradle.api.file.RegularFileProperty;
1517
import org.gradle.api.tasks.CacheableTask;
1618
import org.gradle.api.tasks.InputDirectory;
17-
import org.gradle.api.tasks.InputFile;
19+
import org.gradle.api.tasks.InputFiles;
1820
import org.gradle.api.tasks.Optional;
1921
import org.gradle.api.tasks.PathSensitive;
2022
import org.gradle.api.tasks.PathSensitivity;
2123
import org.gradle.api.tasks.TaskAction;
24+
import org.gradle.process.ExecOperations;
25+
import org.gradle.process.ExecResult;
2226

27+
import java.io.ByteArrayOutputStream;
2328
import java.io.IOException;
29+
import java.nio.charset.StandardCharsets;
2430
import java.nio.file.Files;
2531
import java.nio.file.Path;
26-
import java.util.function.Predicate;
32+
import java.util.ArrayList;
33+
import java.util.Collections;
34+
import java.util.Comparator;
35+
import java.util.HashMap;
36+
import java.util.HashSet;
37+
import java.util.List;
38+
import java.util.Map;
39+
import java.util.Set;
40+
import java.util.function.BiFunction;
41+
import java.util.function.Function;
42+
import java.util.regex.Pattern;
43+
44+
import javax.inject.Inject;
45+
46+
import static org.elasticsearch.gradle.internal.transport.TransportVersionReference.listFromFile;
47+
import static org.elasticsearch.gradle.internal.transport.TransportVersionUtils.definitionFilePath;
48+
import static org.elasticsearch.gradle.internal.transport.TransportVersionUtils.latestFilePath;
49+
import static org.elasticsearch.gradle.internal.transport.TransportVersionUtils.readDefinitionFile;
50+
import static org.elasticsearch.gradle.internal.transport.TransportVersionUtils.readLatestFile;
2751

2852
/**
29-
* Validates that each transport version named reference has a constant definition.
53+
* Validates that each defined transport version constant is referenced by at least one project.
3054
*/
3155
@CacheableTask
3256
public abstract class ValidateTransportVersionDefinitionsTask extends DefaultTask {
3357

3458
@InputDirectory
3559
@Optional
3660
@PathSensitive(PathSensitivity.RELATIVE)
37-
public abstract DirectoryProperty getDefinitionsDirectory();
61+
public abstract DirectoryProperty getResourcesDirectory();
3862

39-
@InputFile
63+
@InputFiles
4064
@PathSensitive(PathSensitivity.RELATIVE)
41-
public abstract RegularFileProperty getReferencesFile();
65+
public abstract ConfigurableFileCollection getReferencesFiles();
66+
67+
private record IdAndDefinition(TransportVersionId id, TransportVersionDefinition definition) {}
68+
69+
private static final Pattern NAME_FORMAT = Pattern.compile("[a-z0-9_]+");
70+
71+
private final Path rootPath;
72+
private final ExecOperations execOperations;
73+
74+
// all transport version names referenced
75+
private final Set<String> allNames = new HashSet<>();
76+
// direct lookup of definition by name
77+
private final Map<String, TransportVersionDefinition> definitions = new HashMap<>();
78+
// which resource files already existed
79+
private final Set<String> existingResources = new HashSet<>();
80+
// reverse lookup of ids back to name
81+
private final Map<Integer, String> definedIds = new HashMap<>();
82+
// lookup of base ids back to definition
83+
private final Map<Integer, List<IdAndDefinition>> idsByBase = new HashMap<>();
84+
// direct lookup of latest for each branch
85+
Map<String, TransportVersionLatest> latestByBranch = new HashMap<>();
86+
87+
@Inject
88+
public ValidateTransportVersionDefinitionsTask(ExecOperations execOperations) {
89+
this.execOperations = execOperations;
90+
this.rootPath = getProject().getRootProject().getLayout().getProjectDirectory().getAsFile().toPath();
91+
}
4292

4393
@TaskAction
4494
public void validateTransportVersions() throws IOException {
45-
final Predicate<String> referenceChecker;
46-
if (getDefinitionsDirectory().isPresent()) {
47-
Path definitionsDir = getDefinitionsDirectory().getAsFile().get().toPath();
48-
referenceChecker = (name) -> Files.exists(definitionsDir.resolve(name + ".csv"));
49-
} else {
50-
referenceChecker = (name) -> false;
51-
}
52-
Path namesFile = getReferencesFile().get().getAsFile().toPath();
53-
54-
for (var tvReference : TransportVersionReference.listFromFile(namesFile)) {
55-
if (referenceChecker.test(tvReference.name()) == false) {
56-
throw new RuntimeException(
57-
"TransportVersion.fromName(\""
58-
+ tvReference.name()
59-
+ "\") was used at "
60-
+ tvReference.location()
61-
+ ", but lacks a"
62-
+ " transport version definition. This can be generated with the <TODO> task" // todo
95+
Path resourcesDir = getResourcesDirectory().getAsFile().get().toPath();
96+
Path definitionsDir = resourcesDir.resolve("defined");
97+
Path latestDir = resourcesDir.resolve("latest");
98+
99+
// first check which resource files already exist in main
100+
recordExistingResources();
101+
102+
// then collect all names referenced in the codebase
103+
for (var referencesFile : getReferencesFiles()) {
104+
listFromFile(referencesFile.toPath()).stream().map(TransportVersionReference::name).forEach(allNames::add);
105+
}
106+
107+
// now load all definitions, do some validation and record them by various keys for later quick lookup
108+
// NOTE: this must run after loading referenced names and existing definitions
109+
// NOTE: this is sorted so that the order of cross validation is deterministic
110+
try (var definitionsStream = Files.list(definitionsDir).sorted()) {
111+
for (var definitionFile : definitionsStream.toList()) {
112+
recordAndValidateDefinition(readDefinitionFile(definitionFile));
113+
}
114+
}
115+
116+
// cleanup base lookup so we can check ids
117+
// NOTE: this must run after definition recording
118+
for (var entry : idsByBase.entrySet()) {
119+
cleanupAndValidateBase(entry.getKey(), entry.getValue());
120+
}
121+
122+
// now load all latest versions and do validation
123+
// NOTE: this must run after definition recording and idsByBase cleanup
124+
try (var latestStream = Files.list(latestDir)) {
125+
for (var latestFile : latestStream.toList()) {
126+
recordAndValidateLatest(readLatestFile(latestFile));
127+
}
128+
}
129+
}
130+
131+
private String gitCommand(String... args) {
132+
final ByteArrayOutputStream stdout = new ByteArrayOutputStream();
133+
134+
List<String> command = new ArrayList<>();
135+
Collections.addAll(command, "git", "-C", rootPath.toAbsolutePath().toString());
136+
Collections.addAll(command, args);
137+
138+
ExecResult result = execOperations.exec(spec -> {
139+
spec.setCommandLine(command);
140+
spec.setStandardOutput(stdout);
141+
spec.setErrorOutput(stdout);
142+
spec.setIgnoreExitValue(true);
143+
});
144+
145+
if (result.getExitValue() != 0) {
146+
throw new RuntimeException(
147+
"git command failed with exit code "
148+
+ result.getExitValue()
149+
+ System.lineSeparator()
150+
+ "command: "
151+
+ String.join(" ", command)
152+
+ System.lineSeparator()
153+
+ "output:"
154+
+ System.lineSeparator()
155+
+ stdout.toString(StandardCharsets.UTF_8)
156+
);
157+
}
158+
159+
return stdout.toString(StandardCharsets.UTF_8);
160+
}
161+
162+
private void recordExistingResources() {
163+
String resourcesPath = relativePath(getResourcesDirectory().getAsFile().get().toPath());
164+
String output = gitCommand("ls-tree", "--name-only", "-r", "main", resourcesPath);
165+
Collections.addAll(existingResources, output.split(System.lineSeparator()));
166+
}
167+
168+
private void recordAndValidateDefinition(TransportVersionDefinition definition) {
169+
definitions.put(definition.name(), definition);
170+
// record the ids for each base id so we can ensure compactness later
171+
for (TransportVersionId id : definition.ids()) {
172+
idsByBase.computeIfAbsent(id.base(), k -> new ArrayList<>()).add(new IdAndDefinition(id, definition));
173+
}
174+
175+
// validate any modifications
176+
Map<Integer, TransportVersionId> existingIdsByBase = new HashMap<>();
177+
TransportVersionDefinition originalDefinition = readExistingDefinition(definition.name());
178+
if (originalDefinition != null) {
179+
180+
int primaryId = definition.ids().get(0).complete();
181+
int originalPrimaryId = originalDefinition.ids().get(0).complete();
182+
if (primaryId != originalPrimaryId) {
183+
throwDefinitionFailure(definition.name(), "has modified primary id from " + originalPrimaryId + " to " + primaryId);
184+
}
185+
186+
originalDefinition.ids().forEach(id -> existingIdsByBase.put(id.base(), id));
187+
}
188+
189+
if (allNames.contains(definition.name()) == false && definition.name().startsWith("initial_") == false) {
190+
throwDefinitionFailure(definition.name(), "is not referenced");
191+
}
192+
if (NAME_FORMAT.matcher(definition.name()).matches() == false) {
193+
throwDefinitionFailure(definition.name(), "does not have a valid name, must be lowercase alphanumeric and underscore");
194+
}
195+
if (definition.ids().isEmpty()) {
196+
throwDefinitionFailure(definition.name(), "does not contain any ids");
197+
}
198+
if (Comparators.isInOrder(definition.ids(), Comparator.reverseOrder()) == false) {
199+
throwDefinitionFailure(definition.name(), "does not have ordered ids");
200+
}
201+
for (int ndx = 0; ndx < definition.ids().size(); ++ndx) {
202+
TransportVersionId id = definition.ids().get(ndx);
203+
204+
String existing = definedIds.put(id.complete(), definition.name());
205+
if (existing != null) {
206+
throwDefinitionFailure(
207+
definition.name(),
208+
"contains id " + id + " already defined in [" + definitionRelativePath(existing) + "]"
209+
);
210+
}
211+
212+
if (ndx == 0) {
213+
// TODO: initial versions will only be applicable to a release branch, so they won't have an associated
214+
// main version. They will also be loaded differently in the future, but until they are separate, we ignore them here.
215+
if (id.patch() != 0 && definition.name().startsWith("initial_") == false) {
216+
throwDefinitionFailure(definition.name(), "has patch version " + id.complete() + " as primary id");
217+
}
218+
} else {
219+
if (id.patch() == 0) {
220+
throwDefinitionFailure(definition.name(), "contains bwc id [" + id + "] with a patch part of 0");
221+
}
222+
}
223+
224+
// check modifications of ids on same branch, ie sharing same base
225+
TransportVersionId maybeModifiedId = existingIdsByBase.get(id.base());
226+
if (maybeModifiedId != null && maybeModifiedId.complete() != id.complete()) {
227+
throwDefinitionFailure(definition.name(), "modifies existing patch id from " + maybeModifiedId + " to " + id);
228+
}
229+
}
230+
}
231+
232+
private TransportVersionDefinition readExistingDefinition(String name) {
233+
return readExistingFile(name, this::definitionRelativePath, TransportVersionDefinition::fromString);
234+
}
235+
236+
private TransportVersionLatest readExistingLatest(String branch) {
237+
return readExistingFile(branch, this::latestRelativePath, TransportVersionLatest::fromString);
238+
}
239+
240+
private <T> T readExistingFile(String name, Function<String, String> pathFunction, BiFunction<String, String, T> parser) {
241+
String relativePath = pathFunction.apply(name);
242+
if (existingResources.contains(relativePath) == false) {
243+
return null;
244+
}
245+
String content = gitCommand("show", "main:" + relativePath).strip();
246+
return parser.apply(relativePath, content);
247+
}
248+
249+
private void recordAndValidateLatest(TransportVersionLatest latest) {
250+
latestByBranch.put(latest.branch(), latest);
251+
252+
TransportVersionDefinition latestDefinition = definitions.get(latest.name());
253+
if (latestDefinition == null) {
254+
throwLatestFailure(latest.branch(), "contains transport version name [" + latest.name() + "] which is not defined");
255+
}
256+
if (latestDefinition.ids().contains(latest.id()) == false) {
257+
throwLatestFailure(
258+
latest.branch(),
259+
"has id " + latest.id() + " which is not in definition [" + definitionRelativePath(latest.name()) + "]"
260+
);
261+
}
262+
263+
List<IdAndDefinition> baseIds = idsByBase.get(latest.id().base());
264+
IdAndDefinition lastId = baseIds.getLast();
265+
if (lastId.id().complete() != latest.id().complete()) {
266+
throwLatestFailure(
267+
latest.branch(),
268+
"has id "
269+
+ latest.id()
270+
+ " from ["
271+
+ latest.name()
272+
+ "] with base "
273+
+ latest.id().base()
274+
+ " but another id "
275+
+ lastId.id().complete()
276+
+ " from ["
277+
+ lastId.definition().name()
278+
+ "] is later for that base"
279+
);
280+
}
281+
282+
TransportVersionLatest existingLatest = readExistingLatest(latest.branch());
283+
if (existingLatest != null) {
284+
if (latest.id().patch() != 0 && latest.id().base() != existingLatest.id().base()) {
285+
throwLatestFailure(latest.branch(), "modifies base id from " + existingLatest.id().base() + " to " + latest.id().base());
286+
}
287+
}
288+
}
289+
290+
private void cleanupAndValidateBase(int base, List<IdAndDefinition> ids) {
291+
// first sort the ids list so we can check compactness and quickly lookup the highest id later
292+
ids.sort(Comparator.comparingInt(a -> a.id().complete()));
293+
294+
// TODO: switch this to a fully dense check once all existing transport versions have been migrated
295+
IdAndDefinition previous = ids.getLast();
296+
for (int ndx = ids.size() - 2; ndx >= 0; --ndx) {
297+
IdAndDefinition next = ids.get(ndx);
298+
// note that next and previous are reversed here because we are iterating in reverse order
299+
if (previous.id().complete() - 1 != next.id().complete()) {
300+
throw new IllegalStateException(
301+
"Transport version base id " + base + " is missing patch ids between " + next.id() + " and " + previous.id()
63302
);
64303
}
304+
previous = next;
65305
}
66306
}
307+
308+
private void throwDefinitionFailure(String name, String message) {
309+
throw new IllegalStateException("Transport version definition file [" + definitionRelativePath(name) + "] " + message);
310+
}
311+
312+
private void throwLatestFailure(String branch, String message) {
313+
throw new IllegalStateException("Latest transport version file [" + latestRelativePath(branch) + "] " + message);
314+
}
315+
316+
private String definitionRelativePath(String name) {
317+
return relativePath(definitionFilePath(getResourcesDirectory().get(), name));
318+
}
319+
320+
private String latestRelativePath(String branch) {
321+
return relativePath(latestFilePath(getResourcesDirectory().get(), branch));
322+
}
323+
324+
private String relativePath(Path file) {
325+
return rootPath.relativize(file).toString();
326+
}
67327
}

0 commit comments

Comments
 (0)