Skip to content

Commit 82ebd0a

Browse files
authored
Update BWC version logic to support multiple bugfix versions (#117943) (#118116) (#118119)
1 parent ad2c631 commit 82ebd0a

File tree

21 files changed

+386
-636
lines changed

21 files changed

+386
-636
lines changed

.buildkite/pipelines/intake.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ steps:
5656
timeout_in_minutes: 300
5757
matrix:
5858
setup:
59-
BWC_VERSION: ["7.17.27", "8.16.2"]
59+
BWC_VERSION: ["7.17.27", "8.15.6", "8.16.2"]
6060
agents:
6161
provider: gcp
6262
image: family/elasticsearch-ubuntu-2004

.buildkite/pipelines/periodic.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -733,7 +733,7 @@ steps:
733733
setup:
734734
ES_RUNTIME_JAVA:
735735
- openjdk17
736-
BWC_VERSION: ["7.17.27", "8.16.2"]
736+
BWC_VERSION: ["7.17.27", "8.15.6", "8.16.2"]
737737
agents:
738738
provider: gcp
739739
image: family/elasticsearch-ubuntu-2004
@@ -781,7 +781,7 @@ steps:
781781
- openjdk21
782782
- openjdk22
783783
- openjdk23
784-
BWC_VERSION: ["7.17.27", "8.16.2"]
784+
BWC_VERSION: ["7.17.27", "8.15.6", "8.16.2"]
785785
agents:
786786
provider: gcp
787787
image: family/elasticsearch-ubuntu-2004

.ci/snapshotBwcVersions

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
BWC_VERSION:
22
- "7.17.27"
3+
- "8.15.6"
34
- "8.16.2"

build-tools-internal/src/integTest/groovy/org/elasticsearch/gradle/internal/InternalDistributionBwcSetupPluginFuncTest.groovy

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,10 @@
99

1010
package org.elasticsearch.gradle.internal
1111

12+
import spock.lang.Unroll
13+
1214
import org.elasticsearch.gradle.fixtures.AbstractGitAwareGradleFuncTest
1315
import org.gradle.testkit.runner.TaskOutcome
14-
import spock.lang.Unroll
1516

1617
class InternalDistributionBwcSetupPluginFuncTest extends AbstractGitAwareGradleFuncTest {
1718

@@ -22,9 +23,11 @@ class InternalDistributionBwcSetupPluginFuncTest extends AbstractGitAwareGradleF
2223
buildFile << """
2324
apply plugin: 'elasticsearch.internal-distribution-bwc-setup'
2425
"""
25-
execute("git branch origin/8.0", file("cloned"))
26+
execute("git branch origin/8.x", file("cloned"))
27+
execute("git branch origin/8.3", file("cloned"))
28+
execute("git branch origin/8.2", file("cloned"))
29+
execute("git branch origin/8.1", file("cloned"))
2630
execute("git branch origin/7.16", file("cloned"))
27-
execute("git branch origin/7.15", file("cloned"))
2831
}
2932

3033
def "builds distribution from branches via archives extractedAssemble"() {
@@ -48,10 +51,11 @@ class InternalDistributionBwcSetupPluginFuncTest extends AbstractGitAwareGradleF
4851
assertOutputContains(result.output, "[$bwcDistVersion] > Task :distribution:archives:darwin-tar:${expectedAssembleTaskName}")
4952

5053
where:
51-
bwcDistVersion | bwcProject | expectedAssembleTaskName
52-
"8.0.0" | "minor" | "extractedAssemble"
53-
"7.16.0" | "staged" | "extractedAssemble"
54-
"7.15.2" | "bugfix" | "extractedAssemble"
54+
bwcDistVersion | bwcProject | expectedAssembleTaskName
55+
"8.4.0" | "minor" | "extractedAssemble"
56+
"8.3.0" | "staged" | "extractedAssemble"
57+
"8.2.1" | "bugfix" | "extractedAssemble"
58+
"8.1.3" | "bugfix2" | "extractedAssemble"
5559
}
5660

5761
@Unroll
@@ -70,8 +74,8 @@ class InternalDistributionBwcSetupPluginFuncTest extends AbstractGitAwareGradleF
7074

7175
where:
7276
bwcDistVersion | platform
73-
"8.0.0" | "darwin"
74-
"8.0.0" | "linux"
77+
"8.4.0" | "darwin"
78+
"8.4.0" | "linux"
7579
}
7680

7781
def "bwc expanded distribution folder can be resolved as bwc project artifact"() {
@@ -107,11 +111,11 @@ class InternalDistributionBwcSetupPluginFuncTest extends AbstractGitAwareGradleF
107111
result.task(":resolveExpandedDistribution").outcome == TaskOutcome.SUCCESS
108112
result.task(":distribution:bwc:minor:buildBwcDarwinTar").outcome == TaskOutcome.SUCCESS
109113
and: "assemble task triggered"
110-
result.output.contains("[8.0.0] > Task :distribution:archives:darwin-tar:extractedAssemble")
111-
result.output.contains("expandedRootPath /distribution/bwc/minor/build/bwc/checkout-8.0/" +
114+
result.output.contains("[8.4.0] > Task :distribution:archives:darwin-tar:extractedAssemble")
115+
result.output.contains("expandedRootPath /distribution/bwc/minor/build/bwc/checkout-8.x/" +
112116
"distribution/archives/darwin-tar/build/install")
113-
result.output.contains("nested folder /distribution/bwc/minor/build/bwc/checkout-8.0/" +
114-
"distribution/archives/darwin-tar/build/install/elasticsearch-8.0.0-SNAPSHOT")
117+
result.output.contains("nested folder /distribution/bwc/minor/build/bwc/checkout-8.x/" +
118+
"distribution/archives/darwin-tar/build/install/elasticsearch-8.4.0-SNAPSHOT")
115119
}
116120

117121
}

build-tools-internal/src/integTest/groovy/org/elasticsearch/gradle/internal/InternalDistributionDownloadPluginFuncTest.groovy

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ class InternalDistributionDownloadPluginFuncTest extends AbstractGradleFuncTest
5757
5858
elasticsearch_distributions {
5959
test_distro {
60-
version = "8.0.0"
60+
version = "8.4.0"
6161
type = "archive"
6262
platform = "linux"
6363
architecture = Architecture.current();
@@ -87,7 +87,7 @@ class InternalDistributionDownloadPluginFuncTest extends AbstractGradleFuncTest
8787
8888
elasticsearch_distributions {
8989
test_distro {
90-
version = "8.0.0"
90+
version = "8.4.0"
9191
type = "archive"
9292
platform = "linux"
9393
architecture = Architecture.current();

build-tools-internal/src/integTest/groovy/org/elasticsearch/gradle/internal/test/rest/LegacyYamlRestCompatTestPluginFuncTest.groovy

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ class LegacyYamlRestCompatTestPluginFuncTest extends AbstractRestResourcesFuncTe
3939

4040
def "yamlRestTestVxCompatTest does nothing when there are no tests"() {
4141
given:
42+
internalBuild()
43+
4244
subProject(":distribution:bwc:maintenance") << """
4345
configurations { checkout }
4446
artifacts {
@@ -47,9 +49,7 @@ class LegacyYamlRestCompatTestPluginFuncTest extends AbstractRestResourcesFuncTe
4749
"""
4850

4951
buildFile << """
50-
plugins {
51-
id 'elasticsearch.legacy-yaml-rest-compat-test'
52-
}
52+
apply plugin: 'elasticsearch.legacy-yaml-rest-compat-test'
5353
"""
5454

5555
when:
@@ -62,7 +62,7 @@ class LegacyYamlRestCompatTestPluginFuncTest extends AbstractRestResourcesFuncTe
6262
result.task(transformTask).outcome == TaskOutcome.NO_SOURCE
6363
}
6464

65-
def "yamlRestTestVxCompatTest executes and copies api and transforms tests from :bwc:maintenance"() {
65+
def "yamlRestCompatTest executes and copies api and transforms tests from :bwc:maintenance"() {
6666
given:
6767
internalBuild()
6868

@@ -144,6 +144,7 @@ class LegacyYamlRestCompatTestPluginFuncTest extends AbstractRestResourcesFuncTe
144144

145145
def "yamlRestTestVxCompatTest is wired into check and checkRestCompat"() {
146146
given:
147+
internalBuild()
147148
withVersionCatalogue()
148149
subProject(":distribution:bwc:maintenance") << """
149150
configurations { checkout }
@@ -153,10 +154,7 @@ class LegacyYamlRestCompatTestPluginFuncTest extends AbstractRestResourcesFuncTe
153154
"""
154155

155156
buildFile << """
156-
plugins {
157-
id 'elasticsearch.legacy-yaml-rest-compat-test'
158-
}
159-
157+
apply plugin: 'elasticsearch.legacy-yaml-rest-compat-test'
160158
"""
161159

162160
when:

build-tools-internal/src/integTest/resources/org/elasticsearch/gradle/internal/fake_git/remote/distribution/bwc/bugfix2/build.gradle

Whitespace-only changes.

build-tools-internal/src/integTest/resources/org/elasticsearch/gradle/internal/fake_git/remote/distribution/bwc/maintenance/build.gradle

Whitespace-only changes.

build-tools-internal/src/integTest/resources/org/elasticsearch/gradle/internal/fake_git/remote/settings.gradle

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,11 @@
1010
rootProject.name = "root"
1111

1212
include ":distribution:bwc:bugfix"
13+
include ":distribution:bwc:bugfix2"
1314
include ":distribution:bwc:minor"
1415
include ":distribution:bwc:major"
1516
include ":distribution:bwc:staged"
17+
include ":distribution:bwc:maintenance"
1618
include ":distribution:archives:darwin-tar"
1719
include ":distribution:archives:oss-darwin-tar"
1820
include ":distribution:archives:darwin-aarch64-tar"

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

Lines changed: 89 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,16 @@
2323
import java.util.Optional;
2424
import java.util.Set;
2525
import java.util.TreeMap;
26-
import java.util.TreeSet;
2726
import java.util.function.BiConsumer;
2827
import java.util.function.Consumer;
2928
import java.util.function.Predicate;
3029
import java.util.regex.Matcher;
3130
import java.util.regex.Pattern;
3231
import java.util.stream.Collectors;
3332

33+
import static java.util.Collections.reverseOrder;
3434
import static java.util.Collections.unmodifiableList;
35+
import static java.util.Comparator.comparing;
3536

3637
/**
3738
* A container for elasticsearch supported version information used in BWC testing.
@@ -70,18 +71,17 @@ public class BwcVersions implements Serializable {
7071
private static final Pattern LINE_PATTERN = Pattern.compile(
7172
"\\W+public static final Version V_(\\d+)_(\\d+)_(\\d+)(_alpha\\d+|_beta\\d+|_rc\\d+)?.*\\);"
7273
);
73-
private static final Version MINIMUM_WIRE_COMPATIBLE_VERSION = Version.fromString("7.17.0");
7474
private static final String GLIBC_VERSION_ENV_VAR = "GLIBC_VERSION";
7575

7676
private final Version currentVersion;
7777
private final transient List<Version> versions;
7878
private final Map<Version, UnreleasedVersionInfo> unreleased;
7979

80-
public BwcVersions(List<String> versionLines) {
81-
this(versionLines, Version.fromString(VersionProperties.getElasticsearch()));
80+
public BwcVersions(List<String> versionLines, List<String> developmentBranches) {
81+
this(versionLines, Version.fromString(VersionProperties.getElasticsearch()), developmentBranches);
8282
}
8383

84-
public BwcVersions(Version currentVersionProperty, List<Version> allVersions) {
84+
public BwcVersions(Version currentVersionProperty, List<Version> allVersions, List<String> developmentBranches) {
8585
if (allVersions.isEmpty()) {
8686
throw new IllegalArgumentException("Could not parse any versions");
8787
}
@@ -90,12 +90,12 @@ public BwcVersions(Version currentVersionProperty, List<Version> allVersions) {
9090
this.currentVersion = allVersions.get(allVersions.size() - 1);
9191
assertCurrentVersionMatchesParsed(currentVersionProperty);
9292

93-
this.unreleased = computeUnreleased();
93+
this.unreleased = computeUnreleased(developmentBranches);
9494
}
9595

9696
// Visible for testing
97-
BwcVersions(List<String> versionLines, Version currentVersionProperty) {
98-
this(currentVersionProperty, parseVersionLines(versionLines));
97+
BwcVersions(List<String> versionLines, Version currentVersionProperty, List<String> developmentBranches) {
98+
this(currentVersionProperty, parseVersionLines(versionLines), developmentBranches);
9999
}
100100

101101
private static List<Version> parseVersionLines(List<String> versionLines) {
@@ -132,51 +132,77 @@ public void forPreviousUnreleased(Consumer<UnreleasedVersionInfo> consumer) {
132132
).stream().map(unreleased::get).forEach(consumer);
133133
}
134134

135-
private String getBranchFor(Version version) {
136-
if (version.equals(currentVersion)) {
137-
// Just assume the current branch is 'main'. It's actually not important, we never check out the current branch.
138-
return "main";
139-
} else {
135+
private String getBranchFor(Version version, List<String> developmentBranches) {
136+
// If the current version matches a specific feature freeze branch, use that
137+
if (developmentBranches.contains(version.getMajor() + "." + version.getMinor())) {
140138
return version.getMajor() + "." + version.getMinor();
139+
} else if (developmentBranches.contains(version.getMajor() + ".x")) { // Otherwise if an n.x branch exists and we are that major
140+
return version.getMajor() + ".x";
141+
} else { // otherwise we're the main branch
142+
return "main";
141143
}
142144
}
143145

144-
private Map<Version, UnreleasedVersionInfo> computeUnreleased() {
145-
Set<Version> unreleased = new TreeSet<>();
146-
// The current version is being worked, is always unreleased
147-
unreleased.add(currentVersion);
148-
// Recurse for all unreleased versions starting from the current version
149-
addUnreleased(unreleased, currentVersion, 0);
146+
private Map<Version, UnreleasedVersionInfo> computeUnreleased(List<String> developmentBranches) {
147+
Map<Version, UnreleasedVersionInfo> result = new TreeMap<>();
150148

151-
// Grab the latest version from the previous major if necessary as well, this is going to be a maintenance release
152-
Version maintenance = versions.stream()
153-
.filter(v -> v.getMajor() == currentVersion.getMajor() - 1)
154-
.max(Comparator.naturalOrder())
155-
.orElseThrow();
156-
// This is considered the maintenance release only if we haven't yet encountered it
157-
boolean hasMaintenanceRelease = unreleased.add(maintenance);
149+
// The current version is always in development
150+
String currentBranch = getBranchFor(currentVersion, developmentBranches);
151+
result.put(currentVersion, new UnreleasedVersionInfo(currentVersion, currentBranch, ":distribution"));
152+
153+
// Check for an n.x branch as well
154+
if (currentBranch.equals("main") && developmentBranches.stream().anyMatch(s -> s.endsWith(".x"))) {
155+
// This should correspond to the latest new minor
156+
Version version = versions.stream()
157+
.sorted(Comparator.reverseOrder())
158+
.filter(v -> v.getMajor() == (currentVersion.getMajor() - 1) && v.getRevision() == 0)
159+
.findFirst()
160+
.orElseThrow(() -> new IllegalStateException("Unable to determine development version for branch"));
161+
String branch = getBranchFor(version, developmentBranches);
162+
assert branch.equals(currentVersion.getMajor() - 1 + ".x") : "Expected branch does not match development branch";
163+
164+
result.put(version, new UnreleasedVersionInfo(version, branch, ":distribution:bwc:minor"));
165+
}
158166

159-
List<Version> unreleasedList = unreleased.stream().sorted(Comparator.reverseOrder()).toList();
160-
Map<Version, UnreleasedVersionInfo> result = new TreeMap<>();
161-
for (int i = 0; i < unreleasedList.size(); i++) {
162-
Version esVersion = unreleasedList.get(i);
163-
// This is either a new minor or staged release
164-
if (currentVersion.equals(esVersion)) {
165-
result.put(esVersion, new UnreleasedVersionInfo(esVersion, getBranchFor(esVersion), ":distribution"));
166-
} else if (esVersion.getRevision() == 0) {
167-
// If there are two upcoming unreleased minors then this one is the new minor
168-
if (unreleasedList.get(i + 1).getRevision() == 0) {
169-
result.put(esVersion, new UnreleasedVersionInfo(esVersion, getBranchFor(esVersion), ":distribution:bwc:minor"));
170-
} else {
171-
result.put(esVersion, new UnreleasedVersionInfo(esVersion, getBranchFor(esVersion), ":distribution:bwc:staged"));
172-
}
173-
} else {
174-
// If this is the oldest unreleased version and we have a maintenance release
175-
if (i == unreleasedList.size() - 1 && hasMaintenanceRelease) {
176-
result.put(esVersion, new UnreleasedVersionInfo(esVersion, getBranchFor(esVersion), ":distribution:bwc:maintenance"));
177-
} else {
178-
result.put(esVersion, new UnreleasedVersionInfo(esVersion, getBranchFor(esVersion), ":distribution:bwc:bugfix"));
179-
}
167+
// Now handle all the feature freeze branches
168+
List<String> featureFreezeBranches = developmentBranches.stream()
169+
.filter(b -> Pattern.matches("[0-9]+\\.[0-9]+", b))
170+
.sorted(reverseOrder(comparing(s -> Version.fromString(s, Version.Mode.RELAXED))))
171+
.toList();
172+
173+
boolean existingBugfix = false;
174+
for (int i = 0; i < featureFreezeBranches.size(); i++) {
175+
String branch = featureFreezeBranches.get(i);
176+
Version version = versions.stream()
177+
.sorted(Comparator.reverseOrder())
178+
.filter(v -> v.toString().startsWith(branch))
179+
.findFirst()
180+
.orElse(null);
181+
182+
// If we don't know about this version we can ignore it
183+
if (version == null) {
184+
continue;
185+
}
186+
187+
// If this is the current version we can ignore as we've already handled it
188+
if (version.equals(currentVersion)) {
189+
continue;
190+
}
191+
192+
// We only maintain compatibility back one major so ignore anything older
193+
if (currentVersion.getMajor() - version.getMajor() > 1) {
194+
continue;
195+
}
196+
197+
// This is the maintenance version
198+
if (i == featureFreezeBranches.size() - 1) {
199+
result.put(version, new UnreleasedVersionInfo(version, branch, ":distribution:bwc:maintenance"));
200+
} else if (version.getRevision() == 0) { // This is the next staged minor
201+
result.put(version, new UnreleasedVersionInfo(version, branch, ":distribution:bwc:staged"));
202+
} else { // This is a bugfix
203+
String project = existingBugfix ? "bugfix2" : "bugfix";
204+
result.put(version, new UnreleasedVersionInfo(version, branch, ":distribution:bwc:" + project));
205+
existingBugfix = true;
180206
}
181207
}
182208

@@ -225,7 +251,10 @@ public void compareToAuthoritative(List<Version> authoritativeReleasedVersions)
225251
}
226252

227253
private List<Version> getReleased() {
228-
return versions.stream().filter(v -> unreleased.containsKey(v) == false).toList();
254+
return versions.stream()
255+
.filter(v -> v.getMajor() >= currentVersion.getMajor() - 1)
256+
.filter(v -> unreleased.containsKey(v) == false)
257+
.toList();
229258
}
230259

231260
/**
@@ -251,7 +280,7 @@ public void withIndexCompatible(Predicate<Version> filter, BiConsumer<Version, S
251280
}
252281

253282
public List<Version> getWireCompatible() {
254-
return filterSupportedVersions(versions.stream().filter(v -> v.compareTo(MINIMUM_WIRE_COMPATIBLE_VERSION) >= 0).toList());
283+
return filterSupportedVersions(versions.stream().filter(v -> v.compareTo(getMinimumWireCompatibleVersion()) >= 0).toList());
255284
}
256285

257286
public void withWireCompatible(BiConsumer<Version, String> versionAction) {
@@ -289,7 +318,17 @@ public List<Version> getUnreleasedWireCompatible() {
289318
}
290319

291320
public Version getMinimumWireCompatibleVersion() {
292-
return MINIMUM_WIRE_COMPATIBLE_VERSION;
321+
// Determine minimum wire compatible version from list of known versions.
322+
// Current BWC policy states the minimum wire compatible version is the last minor release or the previous major version.
323+
return versions.stream()
324+
.filter(v -> v.getRevision() == 0)
325+
.filter(v -> v.getMajor() == currentVersion.getMajor() - 1)
326+
.max(Comparator.naturalOrder())
327+
.orElseThrow(() -> new IllegalStateException("Unable to determine minimum wire compatible version."));
328+
}
329+
330+
public Version getCurrentVersion() {
331+
return currentVersion;
293332
}
294333

295334
public record UnreleasedVersionInfo(Version version, String branch, String gradleProjectPath) {}

0 commit comments

Comments
 (0)