Skip to content

Commit 7dcf81b

Browse files
committed
initial draft
1 parent a995a12 commit 7dcf81b

File tree

2 files changed

+353
-26
lines changed

2 files changed

+353
-26
lines changed
Lines changed: 241 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,241 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the "Elastic License
4+
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
5+
* Public License v 1"; you may not use this file except in compliance with, at
6+
* your election, the "Elastic License 2.0", the "GNU Affero General Public
7+
* License v3.0 only", or the "Server Side Public License, v 1".
8+
*/
9+
10+
package org.elasticsearch.gradle.internal.transport;
11+
12+
import org.elasticsearch.gradle.Version;
13+
import org.elasticsearch.gradle.VersionProperties;
14+
import org.elasticsearch.gradle.internal.transport.TransportVersionUtils.TransportVersionDefinition;
15+
import org.elasticsearch.gradle.internal.transport.TransportVersionUtils.TransportVersionLatest;
16+
import org.gradle.api.DefaultTask;
17+
import org.gradle.api.Project;
18+
import org.gradle.api.file.DirectoryProperty;
19+
import org.gradle.api.provider.ListProperty;
20+
import org.gradle.api.provider.Property;
21+
import org.gradle.api.tasks.Input;
22+
import org.gradle.api.tasks.InputDirectory;
23+
import org.gradle.api.tasks.Optional;
24+
import org.gradle.api.tasks.PathSensitive;
25+
import org.gradle.api.tasks.PathSensitivity;
26+
import org.gradle.api.tasks.TaskAction;
27+
import org.gradle.api.tasks.options.Option;
28+
import org.gradle.process.ExecOperations;
29+
import org.gradle.process.ExecResult;
30+
31+
import java.io.ByteArrayOutputStream;
32+
import java.io.IOException;
33+
import java.nio.charset.StandardCharsets;
34+
import java.nio.file.Path;
35+
import java.util.ArrayList;
36+
import java.util.Collections;
37+
import java.util.HashSet;
38+
import java.util.List;
39+
import java.util.Objects;
40+
import java.util.Set;
41+
import java.util.function.BiFunction;
42+
import java.util.function.Function;
43+
44+
import javax.inject.Inject;
45+
46+
import static org.elasticsearch.gradle.internal.transport.TransportVersionUtils.IdComponents.PATCH;
47+
import static org.elasticsearch.gradle.internal.transport.TransportVersionUtils.IdComponents.SERVER;
48+
import static org.elasticsearch.gradle.internal.transport.TransportVersionUtils.IdComponents.SUBSIDIARY;
49+
import static org.elasticsearch.gradle.internal.transport.TransportVersionUtils.LATEST_DIR;
50+
import static org.elasticsearch.gradle.internal.transport.TransportVersionUtils.SERVERLESS_BRANCH;
51+
import static org.elasticsearch.gradle.internal.transport.TransportVersionUtils.TransportVersionId;
52+
import static org.elasticsearch.gradle.internal.transport.TransportVersionUtils.latestFilePath;
53+
import static org.elasticsearch.gradle.internal.transport.TransportVersionUtils.readLatestFile;
54+
import static org.elasticsearch.gradle.internal.transport.TransportVersionUtils.writeDefinitionFile;
55+
import static org.elasticsearch.gradle.internal.transport.TransportVersionUtils.writeLatestFile;
56+
57+
/**
58+
* This task generates transport version definition files. These files
59+
* are runtime resources that TransportVersion loads statically.
60+
* They contain a comma separated list of integer ids. Each file is named the same
61+
* as the transport version name itself (with the .csv suffix).
62+
*/
63+
public abstract class GenerateTransportVersionDefinitionTask extends DefaultTask {
64+
65+
/**
66+
* Specifies the directory in which contains all TransportVersionSet data files.
67+
*
68+
* @return
69+
*/
70+
@InputDirectory
71+
// @Optional
72+
@PathSensitive(PathSensitivity.RELATIVE)
73+
public abstract DirectoryProperty getResourcesDirectory();// The plugin should always set this, not optional
74+
75+
// assumption: this task is always run on main, so we can determine the name by diffing with main and looking for new files added in the
76+
// definition directory. (not true: once we generate the file, this will no longer hold true if we then need to update it)
77+
78+
/**
79+
* Used to set the name of the TransportVersionSet for which a data file will be generated.
80+
*/
81+
@Input
82+
// @Optional
83+
@Option(option = "name", description = "TBD")
84+
public abstract Property<String> getTransportVersionName(); // The plugin should always set this, not optional
85+
86+
/**
87+
* Used to set the `major.minor` release version for which the specific TransportVersion ID will be generated.
88+
* E.g.: "9.2", "8.18", etc.
89+
*/
90+
@Optional // This is optional as we will look for labels in an env var if this is not set.
91+
@Input
92+
@Option(option = "branches", description = "The branches for which to generate IDs, e.g. --branch=\"9.2,9.1,SERVERLESS\"")
93+
public abstract ListProperty<String> getBranches();
94+
95+
// @Optional
96+
// @Input
97+
// public abstract Property<Function<String, IdComponents>> getIdIncrementSupplier();
98+
99+
private final Path rootPath;
100+
private final ExecOperations execOperations;
101+
102+
@Inject
103+
public GenerateTransportVersionDefinitionTask(Project project, ExecOperations execOperations) {
104+
this.execOperations = execOperations;
105+
this.rootPath = getProject().getRootProject().getLayout().getProjectDirectory().getAsFile().toPath();
106+
}
107+
108+
@TaskAction
109+
public void generateTransportVersionData() throws IOException {
110+
getLogger().lifecycle("Name: " + getTransportVersionName().get());
111+
getLogger().lifecycle("Versions: " + getBranches().get());
112+
Path resourcesDir = Objects.requireNonNull(getResourcesDirectory().getAsFile().get()).toPath();
113+
String name = getTransportVersionName().isPresent() ? getTransportVersionName().get() : findLocalTransportVersionName();
114+
Set<String> targetMinorVersions = new HashSet<>(
115+
getBranches().isPresent() ? new HashSet<>(getBranches().get()) : findTargetMinorVersions()
116+
);
117+
118+
List<TransportVersionId> ids = new ArrayList<>();
119+
for (String branchName : getKnownBranchNames(resourcesDir)) {
120+
TransportVersionLatest latest = readLatestFile(resourcesDir, branchName);
121+
122+
if (name.equals(latest.name())) {
123+
if (targetMinorVersions.contains(branchName) == false) {
124+
// Regenerate to make this operation idempotent. Need to undo prior updates to the latest files if the list of minor
125+
// versions has changed.
126+
127+
// TODO should we just always regen?
128+
writeLatestFile(resourcesDir, readExistingLatest(branchName));
129+
}
130+
} else {
131+
if (targetMinorVersions.contains(branchName)) {
132+
// increment
133+
var incrementedId = incrementTVId(latest.id(), branchName);
134+
ids.add(incrementedId);
135+
writeLatestFile(resourcesDir, new TransportVersionLatest(branchName, name, incrementedId));
136+
}
137+
}
138+
}
139+
// (Re)write the definition file.
140+
writeDefinitionFile(resourcesDir, new TransportVersionDefinition(name, ids));
141+
}
142+
143+
private TransportVersionId incrementTVId(TransportVersionId id, String branchName) {
144+
// We can only run this task on main, so the ElasticsearchVersion will be for main.
145+
Version mainVersion = VersionProperties.getElasticsearchVersion();
146+
String latestFileNameForMain = mainVersion.getMajor() + "." + mainVersion.getMinor();
147+
148+
final var isMain = branchName.equals(latestFileNameForMain);
149+
if (isMain) {
150+
return id.bumpComponent(SERVER);
151+
} else if (branchName.equals(SERVERLESS_BRANCH)) {
152+
return id.bumpComponent(SUBSIDIARY);
153+
} else {
154+
return id.bumpComponent(PATCH);
155+
}
156+
}
157+
158+
private Set<String> getKnownBranchNames(Path resourcesDir) {
159+
// list files under latest
160+
// TODO add serverless? Otherwise just delete this function if it that doesn't make sense here.
161+
return recordExistingResources(resourcesDir.resolve(LATEST_DIR));
162+
}
163+
164+
private String findLocalTransportVersionName() {
165+
// check for missing
166+
// if none missing, look at git diff against main
167+
return "";
168+
}
169+
170+
private List<String> findTargetMinorVersions() {
171+
// look for env var indicating github PR link from CI
172+
// use github api to find current labels, filter down to version labels
173+
// map version labels to branches
174+
return List.of();
175+
}
176+
177+
// TODO duplicated
178+
private Set<String> recordExistingResources(Path resourcesPath) {
179+
String output = gitCommand("ls-tree", "--name-only", "-r", "main", resourcesPath.toString());
180+
return Set.of(output.split(System.lineSeparator()));
181+
}
182+
183+
// TODO duplicated
184+
private TransportVersionLatest readExistingLatest(String branch) {
185+
return readExistingFile(branch, this::latestRelativePath, TransportVersionLatest::fromString);
186+
}
187+
188+
// TODO duplicated
189+
private <T> T readExistingFile(String name, Function<String, String> pathFunction, BiFunction<String, String, T> parser) {
190+
String relativePath = pathFunction.apply(name);
191+
String content = gitCommand("show", "main:" + relativePath).strip();
192+
return parser.apply(relativePath, content);
193+
}
194+
195+
// TODO duplicated
196+
private String latestRelativePath(String branch) {
197+
return relativePath(latestFilePath(getResourcesDirectory().get(), branch));
198+
}
199+
200+
// TODO duplicated
201+
private Path resourcesDirPath() {
202+
return getResourcesDirectory().get().getAsFile().toPath();
203+
}
204+
205+
// TODO duplicated
206+
private String relativePath(Path file) {
207+
return rootPath.relativize(file).toString();
208+
}
209+
210+
// TODO duplicated
211+
public String gitCommand(String... args) {
212+
final ByteArrayOutputStream stdout = new ByteArrayOutputStream();
213+
214+
List<String> command = new ArrayList<>();
215+
Collections.addAll(command, "git", "-C", rootPath.toAbsolutePath().toString());
216+
Collections.addAll(command, args);
217+
218+
ExecResult result = execOperations.exec(spec -> {
219+
spec.setCommandLine(command);
220+
spec.setStandardOutput(stdout);
221+
spec.setErrorOutput(stdout);
222+
spec.setIgnoreExitValue(true);
223+
});
224+
225+
if (result.getExitValue() != 0) {
226+
throw new RuntimeException(
227+
"git command failed with exit code "
228+
+ result.getExitValue()
229+
+ System.lineSeparator()
230+
+ "command: "
231+
+ String.join(" ", command)
232+
+ System.lineSeparator()
233+
+ "output:"
234+
+ System.lineSeparator()
235+
+ stdout.toString(StandardCharsets.UTF_8)
236+
);
237+
}
238+
239+
return stdout.toString(StandardCharsets.UTF_8);
240+
}
241+
}

0 commit comments

Comments
 (0)