diff --git a/build.gradle b/build.gradle index a2b79d31bad..358895a6078 100644 --- a/build.gradle +++ b/build.gradle @@ -103,10 +103,6 @@ subprojects { * in a branch if there are only betas and rcs in the branch so we have * *something* to test against. */ VersionCollection versions = new VersionCollection(file('server/src/main/java/org/elasticsearch/Version.java').readLines('UTF-8')) -if (versions.currentVersion != VersionProperties.elasticsearch) { - throw new GradleException("The last version in Versions.java [${versions.currentVersion}] does not match " + - "VersionProperties.elasticsearch [${VersionProperties.elasticsearch}]") -} // build metadata from previous build, contains eg hashes for bwc builds String buildMetadataValue = System.getenv('BUILD_METADATA') @@ -140,26 +136,16 @@ task verifyVersions { if (gradle.startParameter.isOffline()) { throw new GradleException("Must run in online mode to verify versions") } - // Read the list from maven central - Node xml + // Read the list from maven central. + // Fetch the metadata an parse the xml into Version instances because it's more straight forward here + // rather than bwcVersion ( VersionCollection ). new URL('https://repo1.maven.org/maven2/org/elasticsearch/elasticsearch/maven-metadata.xml').openStream().withStream { s -> - xml = new XmlParser().parse(s) - } - Set knownVersions = new TreeSet<>(xml.versioning.versions.version.collect { it.text() }.findAll { it ==~ /\d+\.\d+\.\d+/ }.collect { Version.fromString(it) }) - - // Limit the known versions to those that should be index compatible, and are not future versions - knownVersions = knownVersions.findAll { it.major >= bwcVersions.currentVersion.major - 1 && it.before(VersionProperties.elasticsearch) } - - /* Limit the listed versions to those that have been marked as released. - * Versions not marked as released don't get the same testing and we want - * to make sure that we flip all unreleased versions to released as soon - * as possible after release. */ - Set actualVersions = new TreeSet<>(bwcVersions.indexCompatible.findAll { false == it.snapshot }) - - // Finally, compare! - if (knownVersions.equals(actualVersions) == false) { - throw new GradleException("out-of-date released versions\nActual :" + actualVersions + "\nExpected:" + knownVersions + - "\nUpdate Version.java. Note that Version.CURRENT doesn't count because it is not released.") + bwcVersions.compareToAuthoritative( + new XmlParser().parse(s) + .versioning.versions.version + .collect { it.text() }.findAll { it ==~ /\d+\.\d+\.\d+/ } + .collect { Version.fromString(it) } + ) } } } @@ -251,20 +237,17 @@ subprojects { "org.elasticsearch.plugin:percolator-client:${version}": ':modules:percolator', "org.elasticsearch.plugin:rank-eval-client:${version}": ':modules:rank-eval', ] - - bwcVersions.snapshotProjectNames.each { snapshotName -> - Version snapshot = bwcVersions.getSnapshotForProject(snapshotName) - if (snapshot != null ) { - String snapshotProject = ":distribution:bwc:${snapshotName}" - project(snapshotProject).ext.bwcVersion = snapshot - ext.projectSubstitutions["org.elasticsearch.distribution.deb:elasticsearch:${snapshot}"] = snapshotProject - ext.projectSubstitutions["org.elasticsearch.distribution.rpm:elasticsearch:${snapshot}"] = snapshotProject - ext.projectSubstitutions["org.elasticsearch.distribution.zip:elasticsearch:${snapshot}"] = snapshotProject - if (snapshot.onOrAfter('6.3.0')) { - ext.projectSubstitutions["org.elasticsearch.distribution.deb:elasticsearch-oss:${snapshot}"] = snapshotProject - ext.projectSubstitutions["org.elasticsearch.distribution.rpm:elasticsearch-oss:${snapshot}"] = snapshotProject - ext.projectSubstitutions["org.elasticsearch.distribution.zip:elasticsearch-oss:${snapshot}"] = snapshotProject - } + // substitute unreleased versions with projects that check out and build locally + bwcVersions.forPreviousUnreleased { VersionCollection.UnreleasedVersionInfo unreleasedVersion -> + Version unreleased = unreleasedVersion.version + String snapshotProject = ":distribution:bwc:${unreleasedVersion.gradleProjectName}" + ext.projectSubstitutions["org.elasticsearch.distribution.deb:elasticsearch:${unreleased}"] = snapshotProject + ext.projectSubstitutions["org.elasticsearch.distribution.rpm:elasticsearch:${unreleased}"] = snapshotProject + ext.projectSubstitutions["org.elasticsearch.distribution.zip:elasticsearch:${unreleased}"] = snapshotProject + if (unreleased.onOrAfter('6.3.0')) { + ext.projectSubstitutions["org.elasticsearch.distribution.deb:elasticsearch-oss:${unreleased}"] = snapshotProject + ext.projectSubstitutions["org.elasticsearch.distribution.rpm:elasticsearch-oss:${unreleased}"] = snapshotProject + ext.projectSubstitutions["org.elasticsearch.distribution.zip:elasticsearch-oss:${unreleased}"] = snapshotProject } } diff --git a/buildSrc/src/main/groovy/org/elasticsearch/gradle/VersionCollection.groovy b/buildSrc/src/main/groovy/org/elasticsearch/gradle/VersionCollection.groovy deleted file mode 100644 index 063dcf7d3bb..00000000000 --- a/buildSrc/src/main/groovy/org/elasticsearch/gradle/VersionCollection.groovy +++ /dev/null @@ -1,353 +0,0 @@ -/* - * Licensed to Elasticsearch under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -package org.elasticsearch.gradle - -import org.gradle.api.GradleException -import org.gradle.api.InvalidUserDataException - -import java.util.regex.Matcher - -/** - * The collection of version constants declared in Version.java, for use in BWC testing. - * - * if major+1 released: released artifacts from $version down to major-1.highestMinor.highestPatch, none of these should be snapshots, period. - * if major+1 unreleased: - * - if released: - * -- caveat 0: snapshot for the major-1.highestMinor.highestPatch - * - if unreleased: - * -- caveat 0: snapshot for the major-1.highestMinor.highestPatch - * -- caveat 1: every same major lower minor branch should also be tested if its released, and if not, its a snapshot. There should only be max 2 of these. - * -- caveat 2: the largest released minor branch before the unreleased minor should also be a snapshot - * -- caveat 3: if the current version is a different major than the previous rules apply to major - 1 of the current version - * - * Please note that the caveat's also correspond with the 4 types of snapshots. - * - Caveat 0 - always maintenanceBugfixSnapshot. - * - Caveat 1 - This is tricky. If caveat 3 applies, the highest matching value is nextMinorSnapshot, if there is another it is the stagedMinorSnapshot. - * If caveat 3 does not apply then the only possible value is the stagedMinorSnapshot. - * - Caveat 2 - always nextBugfixSnapshot - * - Caveat 3 - this only changes the applicability of Caveat 1 - * - * Notes on terminology: - * - The case for major+1 being released is accomplished through the isReleasableBranch value. If this is false, then the branch is no longer - * releasable, meaning not to test against any snapshots. - * - Released is defined as having > 1 suffix-free version in a major.minor series. For instance, only 6.2.0 means unreleased, but a - * 6.2.0 and 6.2.1 mean that 6.2.0 was released already. - */ -class VersionCollection { - - private final List versions - Version nextMinorSnapshot - Version stagedMinorSnapshot - Version nextBugfixSnapshot - Version maintenanceBugfixSnapshot - final Version currentVersion - private final TreeSet versionSet = new TreeSet<>() - final List snapshotProjectNames = ['next-minor-snapshot', - 'staged-minor-snapshot', - 'next-bugfix-snapshot', - 'maintenance-bugfix-snapshot'] - - // When we roll 8.0 its very likely these will need to be extracted from this class - private final boolean isReleasableBranch = true - - /** - * Construct a VersionCollection from the lines of the Version.java file. The basic logic for the following is pretty straight forward. - - * @param versionLines The lines of the Version.java file. - */ - VersionCollection(List versionLines) { - final boolean buildSnapshot = System.getProperty("build.snapshot", "true") == "true" - - List versions = [] - // This class should be converted wholesale to use the treeset - - for (final String line : versionLines) { - final Matcher match = line =~ /\W+public static final Version V_(\d+)_(\d+)_(\d+)(_alpha\d+|_beta\d+|_rc\d+)? .*/ - if (match.matches()) { - final Version foundVersion = new Version( - Integer.parseInt(match.group(1)), Integer.parseInt(match.group(2)), - Integer.parseInt(match.group(3)), (match.group(4) ?: '').replace('_', '-'), false) - safeAddToSet(foundVersion) - } - } - - if (versionSet.empty) { - throw new GradleException("Unexpectedly found no version constants in Versions.java") - } - - // If the major version has been released, then remove all of the alpha/beta/rc versions that exist in the set - versionSet.removeAll { it.suffix.isEmpty() == false && isMajorReleased(it, versionSet) } - - // set currentVersion - Version lastVersion = versionSet.last() - currentVersion = new Version(lastVersion.major, lastVersion.minor, lastVersion.revision, lastVersion.suffix, buildSnapshot) - - // remove all of the potential alpha/beta/rc from the currentVersion - versionSet.removeAll { - it.suffix.isEmpty() == false && - it.major == currentVersion.major && - it.minor == currentVersion.minor && - it.revision == currentVersion.revision } - - // re-add the currentVersion to the set - versionSet.add(currentVersion) - - if (isReleasableBranch) { - if (isReleased(currentVersion)) { - // caveat 0 - if the minor has been released then it only has a maintenance version - // go back 1 version to get the last supported snapshot version of the line, which is a maint bugfix - Version highestMinor = getHighestPreviousMinor(currentVersion.major) - maintenanceBugfixSnapshot = replaceAsSnapshot(highestMinor) - } else { - // caveat 3 - if our currentVersion is a X.0.0, we need to check X-1 minors to see if they are released - if (currentVersion.minor == 0) { - for (Version version: getMinorTips(currentVersion.major - 1)) { - if (isReleased(version) == false) { - // caveat 1 - This should only ever contain 2 non released branches in flight. An example is 6.x is frozen, - // and 6.2 is cut but not yet released there is some simple logic to make sure that in the case of more than 2, - // it will bail. The order is that the minor snapshot is fulfilled first, and then the staged minor snapshot - if (nextMinorSnapshot == null) { - // it has not been set yet - nextMinorSnapshot = replaceAsSnapshot(version) - } else if (stagedMinorSnapshot == null) { - stagedMinorSnapshot = replaceAsSnapshot(version) - } else { - throw new GradleException("More than 2 snapshot version existed for the next minor and staged (frozen) minors.") - } - } else { - // caveat 2 - this is the last minor snap for this major, so replace the highest (last) one of these and break - nextBugfixSnapshot = replaceAsSnapshot(version) - // we only care about the largest minor here, so in the case of 6.1 and 6.0, it will only get 6.1 - break - } - } - // caveat 0 - the last supported snapshot of the line is on a version that we don't support (N-2) - maintenanceBugfixSnapshot = null - } else { - // caveat 3 did not apply. version is not a X.0.0, so we are somewhere on a X.Y line - // only check till minor == 0 of the major - for (Version version: getMinorTips(currentVersion.major)) { - if (isReleased(version) == false) { - // caveat 1 - This should only ever contain 0 or 1 branch in flight. An example is 6.x is frozen, and 6.2 is cut - // but not yet released there is some simple logic to make sure that in the case of more than 1, it will bail - if (stagedMinorSnapshot == null) { - stagedMinorSnapshot = replaceAsSnapshot(version) - } else { - throw new GradleException("More than 1 snapshot version existed for the staged (frozen) minors.") - } - } else { - // caveat 2 - this is the last minor snap for this major, so replace the highest (last) one of these and break - nextBugfixSnapshot = replaceAsSnapshot(version) - // we only care about the largest minor here, so in the case of 6.1 and 6.0, it will only get 6.1 - break - } - } - // caveat 0 - now dip back 1 version to get the last supported snapshot version of the line - Version highestMinor = getHighestPreviousMinor(currentVersion.major) - maintenanceBugfixSnapshot = replaceAsSnapshot(highestMinor) - } - } - } - - this.versions = Collections.unmodifiableList(versionSet.toList()) - } - - /** - * @return The list of versions read from the Version.java file - */ - List getVersions() { - return versions - } - - /** - * Index compat supports 1 previous entire major version. For instance, any 6.x test for this would test all of 5 up to that 6.x version - * - * @return All earlier versions that should be tested for index BWC with the current version. - */ - List getIndexCompatible() { - int actualMajor = (currentVersion.major == 5 ? 2 : currentVersion.major - 1) - return versionSet - .tailSet(Version.fromString("${actualMajor}.0.0")) - .headSet(currentVersion) - .asList() - } - - /** - * Ensures the types of snapshot are not null and are also in the index compat list - */ - List getSnapshotsIndexCompatible() { - List compatSnapshots = [] - List allCompatVersions = getIndexCompatible() - if (allCompatVersions.contains(nextMinorSnapshot)) { - compatSnapshots.add(nextMinorSnapshot) - } - if (allCompatVersions.contains(stagedMinorSnapshot)) { - compatSnapshots.add(stagedMinorSnapshot) - } - if (allCompatVersions.contains(nextBugfixSnapshot)) { - compatSnapshots.add(nextBugfixSnapshot) - } - if (allCompatVersions.contains(maintenanceBugfixSnapshot)) { - compatSnapshots.add(maintenanceBugfixSnapshot) - } - - return compatSnapshots - } - - /** - * Wire compat supports the last minor of the previous major. For instance, any 6.x test would test 5.6 up to that 6.x version - * - * @return All earlier versions that should be tested for wire BWC with the current version. - */ - List getWireCompatible() { - // Get the last minor of the previous major - Version lowerBound = getHighestPreviousMinor(currentVersion.major) - return versionSet - .tailSet(Version.fromString("${lowerBound.major}.${lowerBound.minor}.0")) - .headSet(currentVersion) - .toList() - } - - /** - * Ensures the types of snapshot are not null and are also in the wire compat list - */ - List getSnapshotsWireCompatible() { - List compatSnapshots = [] - List allCompatVersions = getWireCompatible() - if (allCompatVersions.contains(nextMinorSnapshot)) { - compatSnapshots.add(nextMinorSnapshot) - } - if (allCompatVersions.contains(stagedMinorSnapshot)) { - compatSnapshots.add(stagedMinorSnapshot) - } - if (allCompatVersions.contains(nextBugfixSnapshot)) { - compatSnapshots.add(nextBugfixSnapshot) - } - if (allCompatVersions.contains(maintenanceBugfixSnapshot)) { - compatSnapshots.add(maintenanceBugfixSnapshot) - } - // There was no wire compat for the 2.x line - compatSnapshots.removeAll {it.major == 2} - - return compatSnapshots - } - - /** - * Grabs the proper snapshot based on the name passed in. These names should correspond with gradle project names under bwc. If you - * are editing this if/else it is only because you added another project under :distribution:bwc. Do not modify this method or its - * reasoning for throwing the exception unless you are sure that it will not harm :distribution:bwc. - */ - Version getSnapshotForProject(String snapshotProjectName) { - if (snapshotProjectName == 'next-minor-snapshot') { - return nextMinorSnapshot - } else if (snapshotProjectName == 'staged-minor-snapshot') { - return stagedMinorSnapshot - } else if (snapshotProjectName == 'maintenance-bugfix-snapshot') { - return maintenanceBugfixSnapshot - } else if (snapshotProjectName == 'next-bugfix-snapshot') { - return nextBugfixSnapshot - } else { - throw new InvalidUserDataException("Unsupported project name ${snapshotProjectName}") - } - } - - /** - * Uses basic logic about our releases to determine if this version has been previously released - */ - private boolean isReleased(Version version) { - return version.revision > 0 - } - - /** - * Validates that the count of non suffixed (alpha/beta/rc) versions in a given major to major+1 is greater than 1. - * This means that there is more than just a major.0.0 or major.0.0-alpha in a branch to signify it has been prevously released. - */ - private boolean isMajorReleased(Version version, TreeSet items) { - return items - .tailSet(Version.fromString("${version.major}.0.0")) - .headSet(Version.fromString("${version.major + 1}.0.0")) - .count { it.suffix.isEmpty() } // count only non suffix'd versions as actual versions that may be released - .intValue() > 1 - } - - /** - * Gets the largest version previous major version based on the nextMajorVersion passed in. - * If you have a list [5.0.2, 5.1.2, 6.0.1, 6.1.1] and pass in 6 for the nextMajorVersion, it will return you 5.1.2 - */ - private Version getHighestPreviousMinor(Integer nextMajorVersion) { - SortedSet result = versionSet.headSet(Version.fromString("${nextMajorVersion}.0.0")) - return result.isEmpty() ? null : result.last() - } - - /** - * Helper function for turning a version into a snapshot version, removing and readding it to the tree - */ - private Version replaceAsSnapshot(Version version) { - versionSet.remove(version) - Version snapshotVersion = new Version(version.major, version.minor, version.revision, version.suffix, true) - safeAddToSet(snapshotVersion) - return snapshotVersion - } - - /** - * Safely adds a value to the treeset, or bails if the value already exists. - * @param version - */ - private void safeAddToSet(Version version) { - if (versionSet.add(version) == false) { - throw new GradleException("Versions.java contains duplicate entries for ${version}") - } - } - - /** - * Gets the entire set of major.minor.* given those parameters. - */ - private SortedSet getMinorSetForMajor(Integer major, Integer minor) { - return versionSet - .tailSet(Version.fromString("${major}.${minor}.0")) - .headSet(Version.fromString("${major}.${minor + 1}.0")) - } - - /** - * Gets the entire set of major.* to the currentVersion - */ - private SortedSet getMajorSet(Integer major) { - return versionSet - .tailSet(Version.fromString("${major}.0.0")) - .headSet(currentVersion) - } - - /** - * Gets the tip of each minor set and puts it in a list. - * - * examples: - * [1.0.0, 1.1.0, 1.1.1, 1.2.0, 1.3.1] will return [1.0.0, 1.1.1, 1.2.0, 1.3.1] - * [1.0.0, 1.0.1, 1.0.2, 1.0.3, 1.0.4] will return [1.0.4] - */ - private List getMinorTips(Integer major) { - TreeSet majorSet = getMajorSet(major) - List minorList = new ArrayList<>() - for (int minor = majorSet.last().minor; minor >= 0; minor--) { - TreeSet minorSetInMajor = getMinorSetForMajor(major, minor) - minorList.add(minorSetInMajor.last()) - } - return minorList - } -} diff --git a/buildSrc/src/main/java/org/elasticsearch/gradle/Version.java b/buildSrc/src/main/java/org/elasticsearch/gradle/Version.java index 53855716840..04e884d8818 100644 --- a/buildSrc/src/main/java/org/elasticsearch/gradle/Version.java +++ b/buildSrc/src/main/java/org/elasticsearch/gradle/Version.java @@ -21,6 +21,10 @@ public final class Version implements Comparable { private static final Pattern pattern = Pattern.compile("(\\d)+\\.(\\d+)\\.(\\d+)(-alpha\\d+|-beta\\d+|-rc\\d+)?(-SNAPSHOT)?"); + public Version(int major, int minor, int revision) { + this(major, minor, revision, "", false); + } + public Version(int major, int minor, int revision, String suffix, boolean snapshot) { Objects.requireNonNull(major, "major version can't be null"); Objects.requireNonNull(minor, "minor version can't be null"); @@ -31,25 +35,8 @@ public final class Version implements Comparable { this.snapshot = snapshot; this.suffix = suffix == null ? "" : suffix; - int suffixOffset = 0; - if (this.suffix.isEmpty()) { - // no suffix will be considered smaller, uncomment to change that - // suffixOffset = 100; - } else { - if (this.suffix.contains("alpha")) { - suffixOffset += parseSuffixNumber(this.suffix.substring(6)); - } else if (this.suffix.contains("beta")) { - suffixOffset += 25 + parseSuffixNumber(this.suffix.substring(5)); - } else if (this.suffix.contains("rc")) { - suffixOffset += 50 + parseSuffixNumber(this.suffix.substring(3)); - } - else { - throw new IllegalArgumentException("Suffix must contain one of: alpha, beta or rc"); - } - } - // currently snapshot is not taken into account - this.id = major * 10000000 + minor * 100000 + revision * 1000 + suffixOffset * 10 /*+ (snapshot ? 1 : 0)*/; + this.id = major * 10000000 + minor * 100000 + revision * 1000; } private static int parseSuffixNumber(String substring) { @@ -136,10 +123,7 @@ public final class Version implements Comparable { Version version = (Version) o; return major == version.major && minor == version.minor && - revision == version.revision && - id == version.id && - snapshot == version.snapshot && - Objects.equals(suffix, version.suffix); + revision == version.revision; } @Override @@ -176,4 +160,5 @@ public final class Version implements Comparable { public int compareTo(Version other) { return Integer.compare(getId(), other.getId()); } + } diff --git a/buildSrc/src/main/java/org/elasticsearch/gradle/VersionCollection.java b/buildSrc/src/main/java/org/elasticsearch/gradle/VersionCollection.java new file mode 100644 index 00000000000..5b82e0d942b --- /dev/null +++ b/buildSrc/src/main/java/org/elasticsearch/gradle/VersionCollection.java @@ -0,0 +1,341 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.elasticsearch.gradle; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.Consumer; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static java.util.Collections.emptyList; +import static java.util.Collections.unmodifiableList; + +/** + * A container for elasticsearch supported version information used in BWC testing. + * + * Parse the Java source file containing the versions declarations and use the known rules to figure out which are all + * the version the current one is wire and index compatible with. + * On top of this, figure out which of these are unreleased and provide the branch they can be built from. + * + * Note that in this context, currentVersion is the unreleased version this build operates on. + * At any point in time there will surely be four such unreleased versions being worked on, + * thus currentVersion will be one of these. + * + * Considering: + *
+ *
M, M > 0
+ *
last released major
+ *
N, N > 0
+ *
last released minor
+ *
+ * + *
    + *
  • the unreleased major, M+1.0.0 on the `master` branch
  • + *
  • the unreleased minor, M.N.0 on the `M.x` (x is literal) branch
  • + *
  • the unreleased bugfix, M.N.c (c > 0) on the `M.b` branch
  • + *
  • the unreleased maintenance, M-1.d.e ( d > 0, e > 0) on the `(M-1).d` branch
  • + *
+ * In addition to these, there will be a fifth one when a minor reaches feature freeze, we call this the staged + * version: + *
    + *
  • the unreleased staged, M.N-2.0 (N > 2) on the `M.(N-2)` branch
  • + *
+ * + * Each build is only concerned with versions before it, as those are the ones that need to be tested + * for backwards compatibility. We never look forward, and don't add forward facing version number to branches of previous + * version. + * + * Each branch has a current version, and expected compatible versions are parsed from the server code's Version` class. + * We can reliably figure out which the unreleased versions are due to the convention of always adding the next unreleased + * version number to server in all branches when a version is released. + * E.x when M.N.c is released M.N.c+1 is added to the Version class mentioned above in all the following branches: + * `M.b`, `M.x` and `master` so we can reliably assume that the leafs of the version tree are unreleased. + * This convention is enforced by checking the versions we consider to be unreleased against an + * authoritative source (maven central). + * We are then able to map the unreleased version to branches in git and Gradle projects that are capable of checking + * out and building them, so we can include these in the testing plan as well. + */ +public class VersionCollection { + + private static final Pattern LINE_PATTERN = Pattern.compile( + "\\W+public static final Version V_(\\d+)_(\\d+)_(\\d+)(_alpha\\d+|_beta\\d+|_rc\\d+)? .*" + ); + + private final Version currentVersion; + private final Map> groupByMajor; + + public class UnreleasedVersionInfo { + public final Version version; + public final String branch; + public final String gradleProjectName; + + UnreleasedVersionInfo(Version version, String branch, String gradleProjectName) { + this.version = version; + this.branch = branch; + this.gradleProjectName = gradleProjectName; + } + } + + public VersionCollection(List versionLines) { + this(versionLines, VersionProperties.getElasticsearch()); + } + + protected VersionCollection(List versionLines, Version currentVersionProperty) { + groupByMajor = versionLines.stream() + .map(LINE_PATTERN::matcher) + .filter(Matcher::matches) + .map(match -> new Version( + Integer.parseInt(match.group(1)), + Integer.parseInt(match.group(2)), + Integer.parseInt(match.group(3)), + (match.group(4) == null ? "" : match.group(4)).replace('_', '-'), + false + )) + .sorted() + .filter(version -> version.getSuffix().isEmpty() || version.equals(currentVersionProperty)) + .collect(Collectors.groupingBy(Version::getMajor, Collectors.toList())); + + if (groupByMajor.isEmpty()) { + throw new IllegalArgumentException("Could not parse any versions"); + } + + currentVersion = getLatestVersionByKey( + groupByMajor, + groupByMajor.keySet().stream().max(Integer::compareTo) + .orElseThrow(() -> new IllegalStateException("Unexpected number of versions in collection")) + ); + + assertCurrentVersionMatchesParsed(currentVersionProperty); + + assertNoOlderThanTwoMajors(); + + markUnreleasedAsSnapshot(); + } + + private void markUnreleasedAsSnapshot() { + getUnreleased().forEach(uv -> + groupByMajor.get(uv.getMajor()).set( + groupByMajor.get(uv.getMajor()).indexOf(uv), + new Version(uv.getMajor(), uv.getMinor(), uv.getRevision(),uv.getSuffix(), true) + ) + ); + } + + private void assertNoOlderThanTwoMajors() { + Set majors = groupByMajor.keySet(); + if (majors.size() != 2 && currentVersion.getMinor() != 0 && currentVersion.getMajor() != 0) { + throw new IllegalStateException( + "Expected exactly 2 majors in parsed versions but found: " + majors + ); + } + } + + private void assertCurrentVersionMatchesParsed(Version currentVersionProperty) { + if (currentVersionProperty.equals(currentVersion) == false) { + throw new IllegalStateException( + "Parsed versions latest version does not match the one configured in build properties. " + + "Parsed latest version is " + currentVersion + " but the build has " + + currentVersionProperty + ); + } + } + + public void forPreviousUnreleased(Consumer consumer) { + getUnreleased().stream() + .filter(version -> version.equals(currentVersion) == false) + .forEach(version -> consumer.accept( + new UnreleasedVersionInfo( + version, + getBranchFor(version), + getGradleProjectNameFor(version) + ) + )); + } + + private String getGradleProjectNameFor(Version version) { + if (version.equals(currentVersion)) { + throw new IllegalArgumentException("The Gradle project to build " + version + " is the current build."); + } + Map> releasedMajorGroupedByMinor = getReleasedMajorGroupedByMinor(); + + if (version.getRevision() == 0) { + if (releasedMajorGroupedByMinor + .get(releasedMajorGroupedByMinor.keySet().stream().max(Integer::compareTo).orElse(0)) + .contains(version)) { + return "minor"; + } else { + return "staged"; + } + } else { + if (releasedMajorGroupedByMinor + .getOrDefault(version.getMinor(), emptyList()) + .contains(version)) { + return "bugfix"; + } else { + return "maintenance"; + } + } + } + + private String getBranchFor(Version version) { + switch (getGradleProjectNameFor(version)) { + case "minor": + return version.getMajor() + ".x"; + case "staged": + case "maintenance": + case "bugfix": + return version.getMajor() + "." + version.getMinor(); + default: + throw new IllegalStateException("Unexpected Gradle project name"); + } + } + + public List getUnreleased() { + List unreleased = new ArrayList<>(); + // The current version is being worked, is always unreleased + unreleased.add(currentVersion); + + // the tip of the previous major is unreleased for sure, be it a minor or a bugfix + unreleased.add(getLatestVersionByKey(this.groupByMajor, currentVersion.getMajor() - 1)); + + final Map> groupByMinor = getReleasedMajorGroupedByMinor(); + int greatestMinor = groupByMinor.keySet().stream().max(Integer::compareTo).orElse(0); + + // the last bugfix for this minor series is always unreleased + unreleased.add(getLatestVersionByKey(groupByMinor, greatestMinor)); + + if (groupByMinor.get(greatestMinor).size() == 1) { + // we found an unreleased minor + unreleased.add(getLatestVersionByKey(groupByMinor, greatestMinor - 1)); + if (groupByMinor.getOrDefault(greatestMinor - 1, emptyList()).size() == 1) { + // we found that the previous minor is staged but not yet released + // in this case, the minor before that has a bugfix + unreleased.add(getLatestVersionByKey(groupByMinor, greatestMinor - 2)); + } + } + + return unmodifiableList( + unreleased.stream() + .sorted() + .distinct() + .collect(Collectors.toList()) + ); + } + + private Version getLatestVersionByKey(Map> groupByMajor, int key) { + return groupByMajor.getOrDefault(key, emptyList()).stream() + .max(Version::compareTo) + .orElseThrow(() -> new IllegalStateException("Unexpected number of versions in collection")); + } + + private Map> getReleasedMajorGroupedByMinor() { + List currentMajorVersions = groupByMajor.get(currentVersion.getMajor()); + List previousMajorVersions = groupByMajor.get(currentVersion.getMajor() - 1); + + final Map> groupByMinor; + if (currentMajorVersions.size() == 1) { + // Current is an unreleased major: x.0.0 so we have to look for other unreleased versions in the previous major + groupByMinor = previousMajorVersions.stream() + .collect(Collectors.groupingBy(Version::getMinor, Collectors.toList())); + } else { + groupByMinor = currentMajorVersions.stream() + .collect(Collectors.groupingBy(Version::getMinor, Collectors.toList())); + } + return groupByMinor; + } + + public void compareToAuthoritative(List authoritativeReleasedVersions) { + Set notReallyReleased = new HashSet<>(getReleased()); + notReallyReleased.removeAll(authoritativeReleasedVersions); + if (notReallyReleased.isEmpty() == false) { + throw new IllegalStateException( + "out-of-date released versions" + + "\nFollowing versions are not really released, but the build thinks they are: " + notReallyReleased + ); + } + + Set incorrectlyConsideredUnreleased = new HashSet<>(authoritativeReleasedVersions); + incorrectlyConsideredUnreleased.retainAll(getUnreleased()); + if (incorrectlyConsideredUnreleased.isEmpty() == false) { + throw new IllegalStateException( + "out-of-date released versions" + + "\nBuild considers versions unreleased, " + + "but they are released according to an authoritative source: " + incorrectlyConsideredUnreleased + + "\nThe next versions probably needs to be added to Version.java (CURRENT doesn't count)." + ); + } + } + + private List getReleased() { + List unreleased = getUnreleased(); + return groupByMajor.values().stream() + .flatMap(Collection::stream) + .filter(each -> unreleased.contains(each) == false) + .collect(Collectors.toList()); + } + + public List getIndexCompatible() { + return unmodifiableList( + Stream.concat( + groupByMajor.get(currentVersion.getMajor() - 1).stream(), + groupByMajor.get(currentVersion.getMajor()).stream() + ) + .filter(version -> version.equals(currentVersion) == false) + .collect(Collectors.toList()) + ); + } + + public List getWireCompatible() { + List wireCompat = new ArrayList<>(); + + List prevMajors = groupByMajor.get(currentVersion.getMajor() - 1); + int minor = prevMajors.get(prevMajors.size() - 1).getMinor(); + for (int i = prevMajors.size() - 1; + i > 0 && prevMajors.get(i).getMinor() == minor; + i-- + ) { + wireCompat.add(prevMajors.get(i)); + } + wireCompat.addAll(groupByMajor.get(currentVersion.getMajor())); + wireCompat.remove(currentVersion); + wireCompat.sort(Version::compareTo); + + return unmodifiableList(wireCompat); + } + + public List getUnreleasedIndexCompatible() { + List unreleasedIndexCompatible = new ArrayList<>(getIndexCompatible()); + unreleasedIndexCompatible.retainAll(getUnreleased()); + return unmodifiableList(unreleasedIndexCompatible); + } + + public List getUnreleasedWireCompatible() { + List unreleasedWireCompatible = new ArrayList<>(getWireCompatible()); + unreleasedWireCompatible.retainAll(getUnreleased()); + return unmodifiableList(unreleasedWireCompatible); + } + +} diff --git a/buildSrc/src/test/groovy/org/elasticsearch/gradle/VersionCollectionTests.groovy b/buildSrc/src/test/groovy/org/elasticsearch/gradle/VersionCollectionTests.groovy deleted file mode 100644 index f6b9cb5fc95..00000000000 --- a/buildSrc/src/test/groovy/org/elasticsearch/gradle/VersionCollectionTests.groovy +++ /dev/null @@ -1,236 +0,0 @@ -package org.elasticsearch.gradle - -import org.elasticsearch.gradle.test.GradleUnitTestCase -import org.junit.Test - -class VersionCollectionTests extends GradleUnitTestCase { - - String formatVersion(String version) { - return " public static final Version V_${version.replaceAll("\\.", "_")} " - } - List allVersions = [formatVersion('5.0.0'), formatVersion('5.0.0_alpha1'), formatVersion('5.0.0_alpha2'), formatVersion('5.0.0_beta1'), - formatVersion('5.0.0_rc1'),formatVersion('5.0.0_rc2'),formatVersion('5.0.1'), formatVersion('5.0.2'), - formatVersion('5.1.1'), formatVersion('5.1.2'), formatVersion('5.2.0'), formatVersion('5.2.1'), formatVersion('6.0.0'), - formatVersion('6.0.1'), formatVersion('6.1.0'), formatVersion('6.1.1'), formatVersion('6.2.0'), formatVersion('6.3.0'), - formatVersion('7.0.0_alpha1'), formatVersion('7.0.0_alpha2')] - - /** - * This validates the logic of being on a unreleased major branch with a staged major-1.minor sibling. This case happens when a version is - * branched from Major-1.x At the time of this writing 6.2 is unreleased and 6.3 is the 6.x branch. This test simulates the behavior - * from 7.0 perspective, or master at the time of this writing. - */ - @Test - void testAgainstMajorUnreleasedWithExistingStagedMinorRelease() { - VersionCollection vc = new VersionCollection(allVersions) - assertNotNull(vc) - assertEquals(vc.nextMinorSnapshot, Version.fromString("6.3.0-SNAPSHOT")) - assertEquals(vc.stagedMinorSnapshot, Version.fromString("6.2.0-SNAPSHOT")) - assertEquals(vc.nextBugfixSnapshot, Version.fromString("6.1.1-SNAPSHOT")) - assertNull(vc.maintenanceBugfixSnapshot) - - vc.indexCompatible.containsAll(vc.versions) - - // This should contain the same list sans the current version - List indexCompatList = [Version.fromString("6.0.0"), Version.fromString("6.0.1"), - Version.fromString("6.1.0"), Version.fromString("6.1.1-SNAPSHOT"), - Version.fromString("6.2.0-SNAPSHOT"), Version.fromString("6.3.0-SNAPSHOT")] - assertTrue(indexCompatList.containsAll(vc.indexCompatible)) - assertTrue(vc.indexCompatible.containsAll(indexCompatList)) - - List wireCompatList = [Version.fromString("6.3.0-SNAPSHOT")] - assertTrue(wireCompatList.containsAll(vc.wireCompatible)) - assertTrue(vc.wireCompatible.containsAll(wireCompatList)) - - assertEquals(vc.snapshotsIndexCompatible.size(), 3) - assertTrue(vc.snapshotsIndexCompatible.contains(Version.fromString("6.3.0-SNAPSHOT"))) - assertTrue(vc.snapshotsIndexCompatible.contains(Version.fromString("6.2.0-SNAPSHOT"))) - assertTrue(vc.snapshotsIndexCompatible.contains(Version.fromString("6.1.1-SNAPSHOT"))) - - assertEquals(vc.snapshotsWireCompatible.size(), 1) - assertEquals(vc.snapshotsWireCompatible.first(), Version.fromString("6.3.0-SNAPSHOT")) - } - - /** - * This validates the logic of being on a unreleased major branch without a staged major-1.minor sibling. This case happens once a staged, - * unreleased minor is released. At the time of this writing 6.2 is unreleased, so adding a 6.2.1 simulates a 6.2 release. This test - * simulates the behavior from 7.0 perspective, or master at the time of this writing. - */ - @Test - void testAgainstMajorUnreleasedWithoutStagedMinorRelease() { - List localVersion = allVersions.clone() - localVersion.add(formatVersion('6.2.1')) // release 6.2 - - VersionCollection vc = new VersionCollection(localVersion) - assertNotNull(vc) - assertEquals(vc.nextMinorSnapshot, Version.fromString("6.3.0-SNAPSHOT")) - assertEquals(vc.stagedMinorSnapshot, null) - assertEquals(vc.nextBugfixSnapshot, Version.fromString("6.2.1-SNAPSHOT")) - assertNull(vc.maintenanceBugfixSnapshot) - - vc.indexCompatible.containsAll(vc.versions) - - // This should contain the same list sans the current version - List indexCompatList = [Version.fromString("6.0.0"), Version.fromString("6.0.1"), - Version.fromString("6.1.0"), Version.fromString("6.1.1"), - Version.fromString("6.2.0"), Version.fromString("6.2.1-SNAPSHOT"), - Version.fromString("6.3.0-SNAPSHOT")] - assertTrue(indexCompatList.containsAll(vc.indexCompatible)) - assertTrue(vc.indexCompatible.containsAll(indexCompatList)) - - List wireCompatList = [Version.fromString("6.3.0-SNAPSHOT")] - assertTrue(wireCompatList.containsAll(vc.wireCompatible)) - assertTrue(vc.wireCompatible.containsAll(wireCompatList)) - - assertEquals(vc.snapshotsIndexCompatible.size(), 2) - assertTrue(vc.snapshotsIndexCompatible.contains(Version.fromString("6.3.0-SNAPSHOT"))) - assertTrue(vc.snapshotsIndexCompatible.contains(Version.fromString("6.2.1-SNAPSHOT"))) - - assertEquals(vc.snapshotsWireCompatible.size(), 1) - assertEquals(vc.snapshotsWireCompatible.first(), Version.fromString("6.3.0-SNAPSHOT")) - } - - /** - * This validates the logic of being on a unreleased minor branch with a staged minor sibling. This case happens when a version is - * branched from Major.x At the time of this writing 6.2 is unreleased and 6.3 is the 6.x branch. This test simulates the behavior - * from 6.3 perspective. - */ - @Test - void testAgainstMinorReleasedBranch() { - List localVersion = allVersions.clone() - localVersion.removeAll { it.toString().contains('7_0_0')} // remove all the 7.x so that the actual version is 6.3 (6.x) - VersionCollection vc = new VersionCollection(localVersion) - assertNotNull(vc) - assertEquals(vc.nextMinorSnapshot, null) - assertEquals(vc.stagedMinorSnapshot, Version.fromString("6.2.0-SNAPSHOT")) - assertEquals(vc.nextBugfixSnapshot, Version.fromString("6.1.1-SNAPSHOT")) - assertEquals(vc.maintenanceBugfixSnapshot, Version.fromString("5.2.1-SNAPSHOT")) - - // This should contain the same list sans the current version - List indexCompatList = vc.versions.subList(0, vc.versions.size() - 1) - assertTrue(indexCompatList.containsAll(vc.indexCompatible)) - assertTrue(vc.indexCompatible.containsAll(indexCompatList)) - - List wireCompatList = [Version.fromString("5.2.0"), Version.fromString("5.2.1-SNAPSHOT"), Version.fromString("6.0.0"), - Version.fromString("6.0.1"), Version.fromString("6.1.0"), Version.fromString("6.1.1-SNAPSHOT"), - Version.fromString("6.2.0-SNAPSHOT")] - assertTrue(wireCompatList.containsAll(vc.wireCompatible)) - assertTrue(vc.wireCompatible.containsAll(wireCompatList)) - - assertEquals(vc.snapshotsIndexCompatible.size(), 3) - assertTrue(vc.snapshotsIndexCompatible.contains(Version.fromString("6.2.0-SNAPSHOT"))) - assertTrue(vc.snapshotsIndexCompatible.contains(Version.fromString("6.1.1-SNAPSHOT"))) - assertTrue(vc.snapshotsIndexCompatible.contains(Version.fromString("5.2.1-SNAPSHOT"))) - - assertEquals(vc.snapshotsWireCompatible.size(), 3) - assertTrue(vc.snapshotsWireCompatible.contains(Version.fromString("6.2.0-SNAPSHOT"))) - assertTrue(vc.snapshotsWireCompatible.contains(Version.fromString("6.1.1-SNAPSHOT"))) - assertTrue(vc.snapshotsWireCompatible.contains(Version.fromString("5.2.1-SNAPSHOT"))) - } - - /** - * This validates the logic of being on a unreleased minor branch without a staged minor sibling. This case happens once a staged, - * unreleased minor is released. At the time of this writing 6.2 is unreleased, so adding a 6.2.1 simulates a 6.2 release. This test - * simulates the behavior from 6.3 perspective. - */ - @Test - void testAgainstMinorReleasedBranchNoStagedMinor() { - List localVersion = allVersions.clone() - // remove all the 7.x and add a 6.2.1 which means 6.2 was released - localVersion.removeAll { it.toString().contains('7_0_0')} - localVersion.add(formatVersion('6.2.1')) - VersionCollection vc = new VersionCollection(localVersion) - assertNotNull(vc) - assertEquals(vc.nextMinorSnapshot, null) - assertEquals(vc.stagedMinorSnapshot, null) - assertEquals(vc.nextBugfixSnapshot, Version.fromString("6.2.1-SNAPSHOT")) - assertEquals(vc.maintenanceBugfixSnapshot, Version.fromString("5.2.1-SNAPSHOT")) - - // This should contain the same list sans the current version - List indexCompatList = vc.versions.subList(0, vc.versions.size() - 1) - assertTrue(indexCompatList.containsAll(vc.indexCompatible)) - assertTrue(vc.indexCompatible.containsAll(indexCompatList)) - - List wireCompatList = [Version.fromString("5.2.0"), Version.fromString("5.2.1-SNAPSHOT"), Version.fromString("6.0.0"), - Version.fromString("6.0.1"), Version.fromString("6.1.0"), Version.fromString("6.1.1"), - Version.fromString("6.2.0"), Version.fromString("6.2.1-SNAPSHOT")] - assertTrue(wireCompatList.containsAll(vc.wireCompatible)) - assertTrue(vc.wireCompatible.containsAll(wireCompatList)) - - assertEquals(vc.snapshotsIndexCompatible.size(), 2) - assertTrue(vc.snapshotsIndexCompatible.contains(Version.fromString("6.2.1-SNAPSHOT"))) - assertTrue(vc.snapshotsIndexCompatible.contains(Version.fromString("5.2.1-SNAPSHOT"))) - - assertEquals(vc.snapshotsWireCompatible.size(), 2) - assertTrue(vc.snapshotsWireCompatible.contains(Version.fromString("6.2.1-SNAPSHOT"))) - assertTrue(vc.snapshotsWireCompatible.contains(Version.fromString("5.2.1-SNAPSHOT"))) - } - - /** - * This validates the logic of being on a released minor branch. At the time of writing, 6.2 is unreleased, so this is equivalent of being - * on 6.1. - */ - @Test - void testAgainstOldMinor() { - - List localVersion = allVersions.clone() - // remove the 7 alphas and the ones greater than 6.1 - localVersion.removeAll { it.toString().contains('7_0_0') || it.toString().contains('V_6_2') || it.toString().contains('V_6_3') } - VersionCollection vc = new VersionCollection(localVersion) - assertNotNull(vc) - assertEquals(vc.nextMinorSnapshot, null) - assertEquals(vc.stagedMinorSnapshot, null) - assertEquals(vc.nextBugfixSnapshot, null) - assertEquals(vc.maintenanceBugfixSnapshot, Version.fromString("5.2.1-SNAPSHOT")) - - // This should contain the same list sans the current version - List indexCompatList = vc.versions.subList(0, vc.versions.size() - 1) - assertTrue(indexCompatList.containsAll(vc.indexCompatible)) - assertTrue(vc.indexCompatible.containsAll(indexCompatList)) - - List wireCompatList = [Version.fromString("5.2.0"), Version.fromString("5.2.1-SNAPSHOT"), Version.fromString("6.0.0"), - Version.fromString("6.0.1"), Version.fromString("6.1.0")] - assertTrue(wireCompatList.containsAll(vc.wireCompatible)) - assertTrue(vc.wireCompatible.containsAll(wireCompatList)) - - assertEquals(vc.snapshotsIndexCompatible.size(), 1) - assertTrue(vc.snapshotsIndexCompatible.contains(Version.fromString("5.2.1-SNAPSHOT"))) - - assertEquals(vc.snapshotsWireCompatible.size(), 1) - assertTrue(vc.snapshotsWireCompatible.contains(Version.fromString("5.2.1-SNAPSHOT"))) - } - - /** - * This validates the lower bound of wire compat, which is 5.0. It also validates that the span of 2.x to 5.x if it is decided to port - * this fix all the way to the maint 5.6 release. - */ - @Test - void testFloorOfWireCompatVersions() { - List localVersion = [formatVersion('2.0.0'), formatVersion('2.0.1'), formatVersion('2.1.0'), formatVersion('2.1.1'), - formatVersion('5.0.0'), formatVersion('5.0.1'), formatVersion('5.1.0'), formatVersion('5.1.1'), - formatVersion('5.2.0'),formatVersion('5.2.1'),formatVersion('5.3.0'),formatVersion('5.3.1'), - formatVersion('5.3.2')] - VersionCollection vc = new VersionCollection(localVersion) - assertNotNull(vc) - assertEquals(vc.maintenanceBugfixSnapshot, Version.fromString("2.1.1-SNAPSHOT")) - - // This should contain the same list sans the current version - List indexCompatList = vc.versions.subList(0, vc.versions.size() - 1) - assertTrue(indexCompatList.containsAll(vc.indexCompatible)) - assertTrue(vc.indexCompatible.containsAll(indexCompatList)) - - List wireCompatList = [Version.fromString("2.1.0"), Version.fromString("2.1.1-SNAPSHOT"), Version.fromString("5.0.0"), - Version.fromString("5.0.1"), Version.fromString("5.1.0"), - Version.fromString("5.1.1"), Version.fromString("5.2.0"), Version.fromString("5.2.1"), - Version.fromString("5.3.0"), Version.fromString("5.3.1")] - - List compatible = vc.wireCompatible - assertTrue(wireCompatList.containsAll(compatible)) - assertTrue(vc.wireCompatible.containsAll(wireCompatList)) - - assertEquals(vc.snapshotsIndexCompatible.size(), 1) - assertTrue(vc.snapshotsIndexCompatible.contains(Version.fromString("2.1.1-SNAPSHOT"))) - - // ensure none of the 2.x snapshots appear here, as this is the floor of bwc for wire compat - assertEquals(vc.snapshotsWireCompatible.size(), 0) - } -} diff --git a/buildSrc/src/test/java/org/elasticsearch/gradle/VersionCollectionTests.java b/buildSrc/src/test/java/org/elasticsearch/gradle/VersionCollectionTests.java new file mode 100644 index 00000000000..5e83462cf5c --- /dev/null +++ b/buildSrc/src/test/java/org/elasticsearch/gradle/VersionCollectionTests.java @@ -0,0 +1,406 @@ +package org.elasticsearch.gradle; + +import org.elasticsearch.gradle.test.GradleUnitTestCase; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static java.util.Arrays.asList; +import static java.util.Collections.singletonList; + +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +public class VersionCollectionTests extends GradleUnitTestCase { + + private static final Map> sampleVersions = new HashMap<>(); + + @Rule + public ExpectedException expectedEx = ExpectedException.none(); + + static { + // unreleased major and two unreleased minors ( minor in feature freeze ) + sampleVersions.put("8.0.0", asList( + "7_0_0", "7_0_1", "7_1_0", "7_1_1", "7_2_0", "7_3_0", "8.0.0" + )); + sampleVersions.put("7.0.0-alpha1", asList( + "6_0_0_alpha1", "6_0_0_alpha2", "6_0_0_beta1", "6_0_0_beta2", "6_0_0_rc1", "6_0_0_rc2", + "6_0_0", "6_0_1", "6_1_0", "6_1_1", "6_1_2", "6_1_3", "6_1_4", + "6_2_0", "6_2_1", "6_2_2", "6_2_3", "6_2_4", + "6_3_0", "6_3_1", "6_3_2", + "6_4_0", "6_4_1", "6_4_2", + "6_5_0", "7_0_0_alpha1" + )); + sampleVersions.put("6.5.0", asList( + "5_0_0_alpha1", "5_0_0_alpha2", "5_0_0_alpha3", "5_0_0_alpha4", "5_0_0_alpha5", "5_0_0_beta1", "5_0_0_rc1", + "5_0_0", "5_0_1", "5_0_2", "5_1_1", "5_1_2", "5_2_0", "5_2_1", "5_2_2", "5_3_0", "5_3_1", "5_3_2", "5_3_3", + "5_4_0", "5_4_1", "5_4_2", "5_4_3", "5_5_0", "5_5_1", "5_5_2", "5_5_3", "5_6_0", "5_6_1", "5_6_2", "5_6_3", + "5_6_4", "5_6_5", "5_6_6", "5_6_7", "5_6_8", "5_6_9", "5_6_10", "5_6_11", "5_6_12", "5_6_13", + "6_0_0_alpha1", "6_0_0_alpha2", "6_0_0_beta1", "6_0_0_beta2", "6_0_0_rc1", "6_0_0_rc2", "6_0_0", "6_0_1", + "6_1_0", "6_1_1", "6_1_2", "6_1_3", "6_1_4", "6_2_0", "6_2_1", "6_2_2", "6_2_3", "6_2_4", "6_3_0", "6_3_1", + "6_3_2", "6_4_0", "6_4_1", "6_4_2", "6_5_0" + )); + sampleVersions.put("6.6.0", asList( + "5_0_0_alpha1", "5_0_0_alpha2", "5_0_0_alpha3", "5_0_0_alpha4", "5_0_0_alpha5", "5_0_0_beta1", "5_0_0_rc1", + "5_0_0", "5_0_1", "5_0_2", "5_1_1", "5_1_2", "5_2_0", "5_2_1", "5_2_2", "5_3_0", "5_3_1", "5_3_2", "5_3_3", + "5_4_0", "5_4_1", "5_4_2", "5_4_3", "5_5_0", "5_5_1", "5_5_2", "5_5_3", "5_6_0", "5_6_1", "5_6_2", "5_6_3", + "5_6_4", "5_6_5", "5_6_6", "5_6_7", "5_6_8", "5_6_9", "5_6_10", "5_6_11", "5_6_12", "5_6_13", + "6_0_0_alpha1", "6_0_0_alpha2", "6_0_0_beta1", "6_0_0_beta2", "6_0_0_rc1", "6_0_0_rc2", "6_0_0", "6_0_1", + "6_1_0", "6_1_1", "6_1_2", "6_1_3", "6_1_4", "6_2_0", "6_2_1", "6_2_2", "6_2_3", "6_2_4", "6_3_0", "6_3_1", + "6_3_2", "6_4_0", "6_4_1", "6_4_2", "6_5_0", "6_6_0" + )); + sampleVersions.put("6.4.2", asList( + "5_0_0_alpha1", "5_0_0_alpha2", "5_0_0_alpha3", "5_0_0_alpha4", "5_0_0_alpha5", "5_0_0_beta1", "5_0_0_rc1", + "5_0_0", "5_0_1", "5_0_2", "5_1_1", "5_1_2", "5_2_0", "5_2_1", "5_2_2", "5_3_0", + "5_3_1", "5_3_2", "5_3_3", "5_4_0", "5_4_1", "5_4_2", "5_4_3", "5_5_0", "5_5_1", "5_5_2", "5_5_3", + "5_6_0", "5_6_1", "5_6_2", "5_6_3", "5_6_4", "5_6_5", "5_6_6", "5_6_7", "5_6_8", "5_6_9", "5_6_10", + "5_6_11", "5_6_12", "5_6_13", + "6_0_0_alpha1", "6_0_0_alpha2", "6_0_0_beta1", "6_0_0_beta2", "6_0_0_rc1", "6_0_0_rc2", + "6_0_0", "6_0_1", "6_1_0", "6_1_1", "6_1_2", "6_1_3", "6_1_4", "6_2_0", "6_2_1", "6_2_2", "6_2_3", + "6_2_4", "6_3_0", "6_3_1", "6_3_2", "6_4_0", "6_4_1", "6_4_2" + )); + } + + @Test(expected = IllegalArgumentException.class) + public void testExceptionOnEmpty() { + new VersionCollection(asList("foo", "bar"), Version.fromString("7.0.0")); + } + + @Test(expected = IllegalStateException.class) + public void testExceptionOnNonCurrent() { + new VersionCollection(singletonList(formatVersionToLine("6.5.0")), Version.fromString("7.0.0")); + } + + @Test(expected = IllegalStateException.class) + public void testExceptionOnTooManyMajors() { + new VersionCollection( + asList( + formatVersionToLine("5.6.12"), + formatVersionToLine("6.5.0"), + formatVersionToLine("7.0.0") + ), + Version.fromString("7.0.0") + ); + } + + public void testWireCompatible() { + assertVersionsEquals( + singletonList("6.5.0-SNAPSHOT"), + getVersionCollection("7.0.0-alpha1").getWireCompatible() + ); + assertVersionsEquals( + asList( + "5.6.0", "5.6.1", "5.6.2", "5.6.3", "5.6.4", "5.6.5", "5.6.6", "5.6.7", "5.6.8", "5.6.9", "5.6.10", + "5.6.11", "5.6.12", "5.6.13-SNAPSHOT", + "6.0.0", "6.0.1", "6.1.0", "6.1.1", "6.1.2", "6.1.3", "6.1.4", + "6.2.0", "6.2.1", "6.2.2", "6.2.3", "6.2.4", + "6.3.0", "6.3.1", "6.3.2", "6.4.0", "6.4.1", "6.4.2-SNAPSHOT" + ), + getVersionCollection("6.5.0").getWireCompatible() + ); + + assertVersionsEquals( + asList( + "5.6.0", "5.6.1", "5.6.2", "5.6.3", "5.6.4", "5.6.5", "5.6.6", "5.6.7", "5.6.8", "5.6.9", "5.6.10", + "5.6.11", "5.6.12", "5.6.13-SNAPSHOT", "6.0.0", "6.0.1", "6.1.0", "6.1.1", "6.1.2", "6.1.3", "6.1.4", + "6.2.0", "6.2.1", "6.2.2", "6.2.3", "6.2.4", "6.3.0", "6.3.1", "6.3.2", "6.4.0", "6.4.1" + ), + getVersionCollection("6.4.2").getWireCompatible() + ); + + assertVersionsEquals( + asList( + "5.6.0", "5.6.1", "5.6.2", "5.6.3", "5.6.4", "5.6.5", "5.6.6", "5.6.7", "5.6.8", "5.6.9", "5.6.10", + "5.6.11", "5.6.12", "5.6.13-SNAPSHOT", + "6.0.0", "6.0.1", "6.1.0", "6.1.1", "6.1.2", "6.1.3", "6.1.4", + "6.2.0", "6.2.1", "6.2.2", "6.2.3", "6.2.4", + "6.3.0", "6.3.1", "6.3.2", "6.4.0", "6.4.1", "6.4.2-SNAPSHOT", "6.5.0-SNAPSHOT" + ), + getVersionCollection("6.6.0").getWireCompatible() + ); + + assertVersionsEquals( + singletonList("7.3.0"), + getVersionCollection("8.0.0").getWireCompatible() + ); + } + + public void testWireCompatibleUnreleased() { + assertVersionsEquals( + singletonList("6.5.0-SNAPSHOT"), + getVersionCollection("7.0.0-alpha1").getUnreleasedWireCompatible() + ); + assertVersionsEquals( + asList("5.6.13-SNAPSHOT", "6.4.2-SNAPSHOT"), + getVersionCollection("6.5.0").getUnreleasedWireCompatible() + ); + + assertVersionsEquals( + singletonList("5.6.13-SNAPSHOT"), + getVersionCollection("6.4.2").getUnreleasedWireCompatible() + ); + + assertVersionsEquals( + asList("5.6.13-SNAPSHOT", "6.4.2-SNAPSHOT", "6.5.0-SNAPSHOT"), + getVersionCollection("6.6.0").getUnreleasedWireCompatible() + ); + + assertVersionsEquals( + singletonList("7.3.0"), + getVersionCollection("8.0.0").getUnreleasedWireCompatible() + ); + } + + public void testIndexCompatible() { + assertVersionsEquals( + asList( + "6.0.0", "6.0.1", "6.1.0", "6.1.1", "6.1.2", "6.1.3", "6.1.4", + "6.2.0", "6.2.1", "6.2.2", "6.2.3", "6.2.4", "6.3.0", "6.3.1", + "6.3.2", "6.4.0", "6.4.1", "6.4.2-SNAPSHOT", "6.5.0-SNAPSHOT" + ), + getVersionCollection("7.0.0-alpha1").getIndexCompatible() + ); + + assertVersionsEquals( + asList( + "5.0.0", "5.0.1", "5.0.2", "5.1.1", "5.1.2", "5.2.0", "5.2.1", "5.2.2", "5.3.0", "5.3.1", "5.3.2", "5.3.3", + "5.4.0", "5.4.1", "5.4.2", "5.4.3", "5.5.0", "5.5.1", "5.5.2", "5.5.3", "5.6.0", "5.6.1", "5.6.2", "5.6.3", + "5.6.4", "5.6.5", "5.6.6", "5.6.7", "5.6.8", "5.6.9", "5.6.10", "5.6.11", "5.6.12", "5.6.13-SNAPSHOT", + "6.0.0", "6.0.1", "6.1.0", "6.1.1", "6.1.2", "6.1.3", "6.1.4", "6.2.0", "6.2.1", "6.2.2", "6.2.3", "6.2.4", + "6.3.0", "6.3.1", "6.3.2", "6.4.0", "6.4.1", "6.4.2-SNAPSHOT" + ), + getVersionCollection("6.5.0").getIndexCompatible() + ); + + assertVersionsEquals( + asList( + "5.0.0", "5.0.1", "5.0.2", "5.1.1", "5.1.2", "5.2.0", "5.2.1", "5.2.2", "5.3.0", "5.3.1", "5.3.2", "5.3.3", + "5.4.0", "5.4.1", "5.4.2", "5.4.3", "5.5.0", "5.5.1", "5.5.2", "5.5.3", "5.6.0", "5.6.1", "5.6.2", "5.6.3", + "5.6.4", "5.6.5", "5.6.6", "5.6.7", "5.6.8", "5.6.9", "5.6.10", "5.6.11", "5.6.12", "5.6.13-SNAPSHOT", + "6.0.0", "6.0.1", "6.1.0", "6.1.1", "6.1.2", "6.1.3", "6.1.4", "6.2.0", "6.2.1", "6.2.2", "6.2.3", "6.2.4", + "6.3.0", "6.3.1", "6.3.2", "6.4.0", "6.4.1" + ), + getVersionCollection("6.4.2").getIndexCompatible() + ); + + assertVersionsEquals( + asList( + "5.0.0", "5.0.1", "5.0.2", "5.1.1", "5.1.2", "5.2.0", "5.2.1", "5.2.2", "5.3.0", "5.3.1", "5.3.2", "5.3.3", + "5.4.0", "5.4.1", "5.4.2", "5.4.3", "5.5.0", "5.5.1", "5.5.2", "5.5.3", "5.6.0", "5.6.1", "5.6.2", "5.6.3", + "5.6.4", "5.6.5", "5.6.6", "5.6.7", "5.6.8", "5.6.9", "5.6.10", "5.6.11", "5.6.12", "5.6.13-SNAPSHOT", + "6.0.0", "6.0.1", "6.1.0", "6.1.1", "6.1.2", "6.1.3", "6.1.4", "6.2.0", "6.2.1", "6.2.2", "6.2.3", "6.2.4", + "6.3.0", "6.3.1", "6.3.2", "6.4.0", "6.4.1", "6.4.2-SNAPSHOT", "6.5.0-SNAPSHOT" + ), + getVersionCollection("6.6.0").getIndexCompatible() + ); + + assertVersionsEquals( + asList("7.0.0", "7.0.1", "7.1.0", "7.1.1", "7.2.0", "7.3.0"), + getVersionCollection("8.0.0").getIndexCompatible() + ); + } + + public void testIndexCompatibleUnreleased() { + assertVersionsEquals( + asList("6.4.2-SNAPSHOT", "6.5.0-SNAPSHOT"), + getVersionCollection("7.0.0-alpha1").getUnreleasedIndexCompatible() + ); + + assertVersionsEquals( + asList("5.6.13-SNAPSHOT", "6.4.2-SNAPSHOT"), + getVersionCollection("6.5.0").getUnreleasedIndexCompatible() + ); + + assertVersionsEquals( + singletonList("5.6.13-SNAPSHOT"), + getVersionCollection("6.4.2").getUnreleasedIndexCompatible() + ); + + assertVersionsEquals( + asList("5.6.13-SNAPSHOT", "6.4.2-SNAPSHOT", "6.5.0-SNAPSHOT"), + getVersionCollection("6.6.0").getUnreleasedIndexCompatible() + ); + + assertVersionsEquals( + asList("7.1.1", "7.2.0", "7.3.0"), + getVersionCollection("8.0.0").getUnreleasedIndexCompatible() + ); + } + + public void testGetUnreleased() { + assertVersionsEquals( + asList("6.4.2", "6.5.0", "7.0.0-alpha1"), + getVersionCollection("7.0.0-alpha1").getUnreleased() + ); + assertVersionsEquals( + asList("5.6.13", "6.4.2", "6.5.0"), + getVersionCollection("6.5.0").getUnreleased() + ); + assertVersionsEquals( + asList("5.6.13", "6.4.2"), + getVersionCollection("6.4.2").getUnreleased() + ); + assertVersionsEquals( + asList("5.6.13", "6.4.2", "6.5.0", "6.6.0"), + getVersionCollection("6.6.0").getUnreleased() + ); + assertVersionsEquals( + asList("7.1.1", "7.2.0", "7.3.0", "8.0.0"), + getVersionCollection("8.0.0").getUnreleased() + ); + } + + public void testGetBranch() { + assertUnreleasedBranchNames( + asList("6.4", "6.x"), + getVersionCollection("7.0.0-alpha1") + ); + assertUnreleasedBranchNames( + asList("5.6", "6.4"), + getVersionCollection("6.5.0") + ); + assertUnreleasedBranchNames( + singletonList("5.6"), + getVersionCollection("6.4.2") + ); + assertUnreleasedBranchNames( + asList("5.6", "6.4", "6.5"), + getVersionCollection("6.6.0") + ); + assertUnreleasedBranchNames( + asList("7.1", "7.2", "7.x"), + getVersionCollection("8.0.0") + ); + } + + public void testGetGradleProjectName() { + assertUnreleasedGradleProjectNames( + asList("bugfix", "minor"), + getVersionCollection("7.0.0-alpha1") + ); + assertUnreleasedGradleProjectNames( + asList("maintenance", "bugfix"), + getVersionCollection("6.5.0") + ); + assertUnreleasedGradleProjectNames( + singletonList("maintenance"), + getVersionCollection("6.4.2") + ); + assertUnreleasedGradleProjectNames( + asList("maintenance", "bugfix", "staged"), + getVersionCollection("6.6.0") + ); + assertUnreleasedGradleProjectNames( + asList("bugfix", "staged", "minor"), + getVersionCollection("8.0.0") + ); + } + + public void testCompareToAuthoritative() { + List listOfVersions = asList("7.0.0", "7.0.1", "7.1.0", "7.1.1", "7.2.0", "7.3.0", "8.0.0"); + List authoritativeReleasedVersions = Stream.of("7.0.0", "7.0.1", "7.1.0") + .map(Version::fromString) + .collect(Collectors.toList()); + + VersionCollection vc = new VersionCollection( + listOfVersions.stream() + .map(this::formatVersionToLine) + .collect(Collectors.toList()), + Version.fromString("8.0.0") + ); + vc.compareToAuthoritative(authoritativeReleasedVersions); + } + + public void testCompareToAuthoritativeUnreleasedActuallyReleased() { + List listOfVersions = asList("7.0.0", "7.0.1", "7.1.0", "7.1.1", "7.2.0", "7.3.0", "8.0.0"); + List authoritativeReleasedVersions = Stream.of("7.0.0", "7.0.1", "7.1.0", "7.1.1", "8.0.0") + .map(Version::fromString) + .collect(Collectors.toList()); + + VersionCollection vc = new VersionCollection( + listOfVersions.stream() + .map(this::formatVersionToLine) + .collect(Collectors.toList()), + Version.fromString("8.0.0") + ); + expectedEx.expect(IllegalStateException.class); + expectedEx.expectMessage("but they are released"); + vc.compareToAuthoritative(authoritativeReleasedVersions); + } + + public void testCompareToAuthoritativeNotReallyRelesed() { + List listOfVersions = asList("7.0.0", "7.0.1", "7.1.0", "7.1.1", "7.2.0", "7.3.0", "8.0.0"); + List authoritativeReleasedVersions = Stream.of("7.0.0", "7.0.1") + .map(Version::fromString) + .collect(Collectors.toList()); + VersionCollection vc = new VersionCollection( + listOfVersions.stream() + .map(this::formatVersionToLine) + .collect(Collectors.toList()), + Version.fromString("8.0.0") + ); + expectedEx.expect(IllegalStateException.class); + expectedEx.expectMessage("not really released"); + vc.compareToAuthoritative(authoritativeReleasedVersions); + } + + private void assertUnreleasedGradleProjectNames(List expectedNAmes, VersionCollection versionCollection) { + List actualNames = new ArrayList<>(); + versionCollection.forPreviousUnreleased(unreleasedVersion -> + actualNames.add(unreleasedVersion.gradleProjectName) + ); + assertEquals(expectedNAmes, actualNames); + } + + private void assertUnreleasedBranchNames(List expectedBranches, VersionCollection versionCollection) { + List actualBranches = new ArrayList<>(); + versionCollection.forPreviousUnreleased(unreleasedVersionInfo -> + actualBranches.add(unreleasedVersionInfo.branch) + ); + assertEquals(expectedBranches, actualBranches); + } + + private String formatVersionToLine(final String version) { + return " public static final Version V_" + version.replaceAll("\\.", "_") + " "; + } + + private void assertVersionsEquals(List expected, List actual) { + assertEquals( + expected.stream() + .map(Version::fromString) + .collect(Collectors.toList()), + actual + ); + } + + private VersionCollection getVersionCollection(String currentVersion) { + return new VersionCollection( + sampleVersions.get(currentVersion).stream() + .map(this::formatVersionToLine) + .collect(Collectors.toList()), + Version.fromString(currentVersion) + ); + } +} diff --git a/buildSrc/src/test/java/org/elasticsearch/gradle/VersionTests.java b/buildSrc/src/test/java/org/elasticsearch/gradle/VersionTests.java index d3c3b4a43cb..fb4e9d70e3a 100644 --- a/buildSrc/src/test/java/org/elasticsearch/gradle/VersionTests.java +++ b/buildSrc/src/test/java/org/elasticsearch/gradle/VersionTests.java @@ -44,16 +44,12 @@ public class VersionTests extends GradleUnitTestCase { assertTrue("1.10.20 is not interpreted as before 2.0.0", Version.fromString("1.10.20").before("2.0.0") ); - assertTrue("7.0.0-alpha1 is not interpreted as before 7.0.0-alpha2", - Version.fromString("7.0.0-alpha1").before("7.0.0-alpha2") - ); assertTrue("7.0.0-alpha1 should be equal to 7.0.0-alpha1", Version.fromString("7.0.0-alpha1").equals(Version.fromString("7.0.0-alpha1")) ); assertTrue("7.0.0-SNAPSHOT should be equal to 7.0.0-SNAPSHOT", Version.fromString("7.0.0-SNAPSHOT").equals(Version.fromString("7.0.0-SNAPSHOT")) ); - assertEquals(Version.fromString("5.2.1-SNAPSHOT"), Version.fromString("5.2.1-SNAPSHOT")); } public void testCollections() { @@ -89,51 +85,10 @@ public class VersionTests extends GradleUnitTestCase { new Version(7, 0, 0, "", true) )); - // snapshot is not taken into account TODO inconsistent with equals assertEquals( 0, - new Version(7, 0, 0, "", false).compareTo( - new Version(7, 0, 0, null, true)) - ); - // without sufix is smaller than with TODO - assertOrder( - new Version(7, 0, 0, null, false), - new Version(7, 0, 0, "-alpha1", false) - ); - // numbered sufix - assertOrder( - new Version(7, 0, 0, "-alpha1", false), - new Version(7, 0, 0, "-alpha2", false) - ); - // ranked sufix - assertOrder( - new Version(7, 0, 0, "-alpha8", false), - new Version(7, 0, 0, "-rc1", false) - ); - // ranked sufix - assertOrder( - new Version(7, 0, 0, "-alpha8", false), - new Version(7, 0, 0, "-beta1", false) - ); - // ranked sufix - assertOrder( - new Version(7, 0, 0, "-beta8", false), - new Version(7, 0, 0, "-rc1", false) - ); - // major takes precedence - assertOrder( - new Version(6, 10, 10, "-alpha8", true), - new Version(7, 0, 0, "-alpha2", false) - ); - // then minor - assertOrder( - new Version(7, 0, 10, "-alpha8", true), - new Version(7, 1, 0, "-alpha2", false) - ); - // then revision - assertOrder( - new Version(7, 1, 0, "-alpha8", true), - new Version(7, 1, 10, "-alpha2", false) + new Version(7, 0, 0, "-alpha1", false).compareTo( + new Version(7, 0, 0, "", true)) ); } @@ -149,18 +104,6 @@ public class VersionTests extends GradleUnitTestCase { Version.fromString("foo.bar.baz"); } - public void testExceptionSuffixNumber() { - expectedEx.expect(IllegalArgumentException.class); - expectedEx.expectMessage("Invalid suffix"); - new Version(7, 1, 1, "-alpha", true); - } - - public void testExceptionSuffix() { - expectedEx.expect(IllegalArgumentException.class); - expectedEx.expectMessage("Suffix must contain one of:"); - new Version(7, 1, 1, "foo1", true); - } - private void assertOrder(Version smaller, Version bigger) { assertEquals(smaller + " should be smaller than " + bigger, -1, smaller.compareTo(bigger)); } diff --git a/distribution/bwc/maintenance-bugfix-snapshot/build.gradle b/distribution/bwc/bugfix/build.gradle similarity index 100% rename from distribution/bwc/maintenance-bugfix-snapshot/build.gradle rename to distribution/bwc/bugfix/build.gradle diff --git a/distribution/bwc/build.gradle b/distribution/bwc/build.gradle index a44e670542b..43ebe53e03a 100644 --- a/distribution/bwc/build.gradle +++ b/distribution/bwc/build.gradle @@ -17,236 +17,225 @@ * under the License. */ - - import org.apache.tools.ant.taskdefs.condition.Os import org.elasticsearch.gradle.LoggedExec import org.elasticsearch.gradle.Version +import org.elasticsearch.gradle.VersionCollection import java.nio.charset.StandardCharsets import static org.elasticsearch.gradle.BuildPlugin.getJavaHome + /** - * This is a dummy project which does a local checkout of the previous - * wire compat version's branch, and builds a snapshot. This allows backcompat - * tests to test against the next unreleased version, closest to this version, - * without relying on snapshots. + * We want to be able to do BWC tests for unreleased versions without relying on and waiting for snapshots. + * For this we need to check out and build the unreleased versions. + * Since These depend on the current version, we can't name the Gradle projects statically, and don't know what the + * unreleased versions are when Gradle projects are set up, so we use "build-unreleased-version-*" as placeholders + * and configure them to build various versions here. */ -subprojects { +bwcVersions.forPreviousUnreleased { VersionCollection.UnreleasedVersionInfo unreleasedVersion -> project("${project.path}:${unreleasedVersion.gradleProjectName}") { + Version bwcVersion = unreleasedVersion.version + String bwcBranch = unreleasedVersion.branch + apply plugin: 'distribution' + // Not published so no need to assemble + assemble.enabled = false + assemble.dependsOn.remove('buildBwcVersion') - Version bwcVersion = bwcVersions.getSnapshotForProject(project.name) - if (bwcVersion == null) { - // this project wont do anything - return - } + File checkoutDir = file("${buildDir}/bwc/checkout-${bwcBranch}") - String bwcBranch - if (project.name == 'next-minor-snapshot') { - // this is always a .x series - bwcBranch = "${bwcVersion.major}.x" - } else { - bwcBranch = "${bwcVersion.major}.${bwcVersion.minor}" - } + final String remote = System.getProperty("tests.bwc.remote", "elastic") - apply plugin: 'distribution' - // Not published so no need to assemble - assemble.enabled = false - assemble.dependsOn.remove('buildBwcVersion') - - File checkoutDir = file("${buildDir}/bwc/checkout-${bwcBranch}") - - final String remote = System.getProperty("tests.bwc.remote", "elastic") - - final boolean gitFetchLatest - final String gitFetchLatestProperty = System.getProperty("tests.bwc.git_fetch_latest", "true") - if ("true".equals(gitFetchLatestProperty)) { - gitFetchLatest = true - } else if ("false".equals(gitFetchLatestProperty)) { - gitFetchLatest = false - } else { - throw new GradleException("tests.bwc.git_fetch_latest must be [true] or [false] but was [" + gitFetchLatestProperty + "]") - } - - task createClone(type: LoggedExec) { - onlyIf { checkoutDir.exists() == false } - commandLine = ['git', 'clone', rootDir, checkoutDir] - } - - task findRemote(type: LoggedExec) { - dependsOn createClone - workingDir = checkoutDir - commandLine = ['git', 'remote', '-v'] - ByteArrayOutputStream output = new ByteArrayOutputStream() - standardOutput = output - doLast { - project.ext.remoteExists = false - output.toString('UTF-8').eachLine { - if (it.contains("${remote}\t")) { - project.ext.remoteExists = true - } - } - } - } - - task addRemote(type: LoggedExec) { - dependsOn findRemote - onlyIf { project.ext.remoteExists == false } - workingDir = checkoutDir - commandLine = ['git', 'remote', 'add', "${remote}", "https://github.com/${remote}/elasticsearch.git"] - } - - task fetchLatest(type: LoggedExec) { - onlyIf { project.gradle.startParameter.isOffline() == false && gitFetchLatest } - dependsOn addRemote - workingDir = checkoutDir - commandLine = ['git', 'fetch', '--all'] - } - - String buildMetadataKey = "bwc_refspec_${project.path.substring(1)}" - task checkoutBwcBranch(type: LoggedExec) { - String refspec = System.getProperty("tests.bwc.refspec.${bwcBranch}", buildMetadata.get(buildMetadataKey, "${remote}/${bwcBranch}")) - dependsOn fetchLatest - workingDir = checkoutDir - commandLine = ['git', 'checkout', refspec] - doFirst { - println "Checking out elasticsearch ${refspec} for branch ${bwcBranch}" - } - } - - File buildMetadataFile = project.file("build/${project.name}/build_metadata") - task writeBuildMetadata(type: LoggedExec) { - dependsOn checkoutBwcBranch - workingDir = checkoutDir - commandLine = ['git', 'rev-parse', 'HEAD'] - ignoreExitValue = true - ByteArrayOutputStream output = new ByteArrayOutputStream() - standardOutput = output - doLast { - if (execResult.exitValue != 0) { - output.toString('UTF-8').eachLine { line -> logger.error(line) } - execResult.assertNormalExitValue() - } - project.mkdir(buildMetadataFile.parent) - String commit = output.toString('UTF-8') - buildMetadataFile.setText("${buildMetadataKey}=${commit}", 'UTF-8') - println "Checked out elasticsearch commit ${commit}" - } - } - - List artifactFiles = [] - List projectDirs = [] - for (String project : ['zip', 'deb', 'rpm']) { - String baseDir = "distribution" - if (bwcVersion.onOrAfter('6.3.0')) { - baseDir += project == 'zip' ? '/archives' : '/packages' - // add oss variant first - projectDirs.add("${baseDir}/oss-${project}") - artifactFiles.add(file("${checkoutDir}/${baseDir}/oss-${project}/build/distributions/elasticsearch-oss-${bwcVersion}.${project}")) - } - projectDirs.add("${baseDir}/${project}") - artifactFiles.add(file("${checkoutDir}/${baseDir}/${project}/build/distributions/elasticsearch-${bwcVersion}.${project}")) - } - - task buildBwcVersion(type: Exec) { - dependsOn checkoutBwcBranch, writeBuildMetadata - workingDir = checkoutDir - doFirst { - // Execution time so that the checkouts are available - List lines = file("$checkoutDir/.ci/java-versions.properties").readLines() - environment( - 'JAVA_HOME', - getJavaHome(it, Integer.parseInt( - lines - .findAll({ it.startsWith("ES_BUILD_JAVA=java") }) - .collect({ it.replace("ES_BUILD_JAVA=java", "").trim() }) - .join("!!") - )) - ) - environment( - 'RUNTIME_JAVA_HOME', - getJavaHome(it, Integer.parseInt( - lines - .findAll({ it.startsWith("ES_RUNTIME_JAVA=java") }) - .collect({ it.replace("ES_RUNTIME_JAVA=java", "").trim() }) - .join("!!") - )) - ) - } - - if (Os.isFamily(Os.FAMILY_WINDOWS)) { - executable 'cmd' - args '/C', 'call', new File(checkoutDir, 'gradlew').toString() + final boolean gitFetchLatest + final String gitFetchLatestProperty = System.getProperty("tests.bwc.git_fetch_latest", "true") + if ("true".equals(gitFetchLatestProperty)) { + gitFetchLatest = true + } else if ("false".equals(gitFetchLatestProperty)) { + gitFetchLatest = false } else { - executable new File(checkoutDir, 'gradlew').toString() + throw new GradleException("tests.bwc.git_fetch_latest must be [true] or [false] but was [" + gitFetchLatestProperty + "]") } - if (gradle.startParameter.isOffline()) { - args "--offline" - } - for (String dir : projectDirs) { - args ":${dir.replace('/', ':')}:assemble" - } - args "-Dbuild.snapshot=true" - final LogLevel logLevel = gradle.startParameter.logLevel - if ([LogLevel.QUIET, LogLevel.WARN, LogLevel.INFO, LogLevel.DEBUG].contains(logLevel)) { - args "--${logLevel.name().toLowerCase(Locale.ENGLISH)}" - } - final String showStacktraceName = gradle.startParameter.showStacktrace.name() - assert ["INTERNAL_EXCEPTIONS", "ALWAYS", "ALWAYS_FULL"].contains(showStacktraceName) - if (showStacktraceName.equals("ALWAYS")) { - args "--stacktrace" - } else if (showStacktraceName.equals("ALWAYS_FULL")) { - args "--full-stacktrace" - } - standardOutput = new IndentingOutputStream(System.out) - errorOutput = new IndentingOutputStream(System.err) - doLast { - List missing = artifactFiles.grep { file -> - false == file.exists() - } - if (false == missing.empty) { - throw new InvalidUserDataException( - "Building bwc version didn't generate expected files ${missing}") - } - } - } - if (gradle.startParameter.taskNames == ["assemble"]) { - // Gradle needs the `artifacts` declaration, including `builtBy` bellow to make projects dependencies on this - // project work, but it will also trigger the build of these for the `assemble` task. - // Since these are only used for testing, we don't want to assemble them if `assemble` is the single command being - // ran. - logger.info("Skipping BWC builds since `assemble` is the only task name provided on the command line") - } else { - artifacts { - for (File artifactFile : artifactFiles) { - String artifactName = artifactFile.name.contains('oss') ? 'elasticsearch-oss' : 'elasticsearch' - String suffix = artifactFile.toString()[-3..-1] - 'default' file: artifactFile, name: artifactName, type: suffix, builtBy: buildBwcVersion - } + task createClone(type: LoggedExec) { + onlyIf { checkoutDir.exists() == false } + commandLine = ['git', 'clone', rootDir, checkoutDir] } - } -} + + task findRemote(type: LoggedExec) { + dependsOn createClone + workingDir = checkoutDir + commandLine = ['git', 'remote', '-v'] + ByteArrayOutputStream output = new ByteArrayOutputStream() + standardOutput = output + doLast { + project.ext.remoteExists = false + output.toString('UTF-8').eachLine { + if (it.contains("${remote}\t")) { + project.ext.remoteExists = true + } + } + } + } + + task addRemote(type: LoggedExec) { + dependsOn findRemote + onlyIf { project.ext.remoteExists == false } + workingDir = checkoutDir + commandLine = ['git', 'remote', 'add', "${remote}", "https://github.com/${remote}/elasticsearch.git"] + } + + task fetchLatest(type: LoggedExec) { + onlyIf { project.gradle.startParameter.isOffline() == false && gitFetchLatest } + dependsOn addRemote + workingDir = checkoutDir + commandLine = ['git', 'fetch', '--all'] + } + + String buildMetadataKey = "bwc_refspec_${project.path.substring(1)}" + task checkoutBwcBranch(type: LoggedExec) { + String refspec = System.getProperty("tests.bwc.refspec.${bwcBranch}", buildMetadata.get(buildMetadataKey, "${remote}/${bwcBranch}")) + dependsOn fetchLatest + workingDir = checkoutDir + commandLine = ['git', 'checkout', refspec] + doFirst { + println "Checking out elasticsearch ${refspec} for branch ${bwcBranch}" + } + } + + File buildMetadataFile = project.file("build/${project.name}/build_metadata") + task writeBuildMetadata(type: LoggedExec) { + dependsOn checkoutBwcBranch + workingDir = checkoutDir + commandLine = ['git', 'rev-parse', 'HEAD'] + ignoreExitValue = true + ByteArrayOutputStream output = new ByteArrayOutputStream() + standardOutput = output + doLast { + if (execResult.exitValue != 0) { + output.toString('UTF-8').eachLine { line -> logger.error(line) } + execResult.assertNormalExitValue() + } + project.mkdir(buildMetadataFile.parent) + String commit = output.toString('UTF-8') + buildMetadataFile.setText("${buildMetadataKey}=${commit}", 'UTF-8') + println "Checked out elasticsearch commit ${commit}" + } + } + + List artifactFiles = [] + List projectDirs = [] + for (String project : ['zip', 'deb', 'rpm']) { + String baseDir = "distribution" + if (bwcVersion.onOrAfter('6.3.0')) { + baseDir += project == 'zip' ? '/archives' : '/packages' + // add oss variant first + projectDirs.add("${baseDir}/oss-${project}") + artifactFiles.add(file("${checkoutDir}/${baseDir}/oss-${project}/build/distributions/elasticsearch-oss-${bwcVersion}.${project}")) + } + projectDirs.add("${baseDir}/${project}") + artifactFiles.add(file("${checkoutDir}/${baseDir}/${project}/build/distributions/elasticsearch-${bwcVersion}.${project}")) + } + + task buildBwcVersion(type: Exec) { + dependsOn checkoutBwcBranch, writeBuildMetadata + workingDir = checkoutDir + doFirst { + // Execution time so that the checkouts are available + List lines = file("${checkoutDir}/.ci/java-versions.properties").readLines() + environment( + 'JAVA_HOME', + getJavaHome(it, Integer.parseInt( + lines + .findAll({ it.startsWith("ES_BUILD_JAVA=java") }) + .collect({ it.replace("ES_BUILD_JAVA=java", "").trim() }) + .join("!!") + )) + ) + environment( + 'RUNTIME_JAVA_HOME', + getJavaHome(it, Integer.parseInt( + lines + .findAll({ it.startsWith("ES_RUNTIME_JAVA=java") }) + .collect({ it.replace("ES_RUNTIME_JAVA=java", "").trim() }) + .join("!!") + )) + ) + } + + if (Os.isFamily(Os.FAMILY_WINDOWS)) { + executable 'cmd' + args '/C', 'call', new File(checkoutDir, 'gradlew').toString() + } else { + executable new File(checkoutDir, 'gradlew').toString() + } + if (gradle.startParameter.isOffline()) { + args "--offline" + } + for (String dir : projectDirs) { + args ":${dir.replace('/', ':')}:assemble" + } + args "-Dbuild.snapshot=true" + final LogLevel logLevel = gradle.startParameter.logLevel + if ([LogLevel.QUIET, LogLevel.WARN, LogLevel.INFO, LogLevel.DEBUG].contains(logLevel)) { + args "--${logLevel.name().toLowerCase(Locale.ENGLISH)}" + } + final String showStacktraceName = gradle.startParameter.showStacktrace.name() + assert ["INTERNAL_EXCEPTIONS", "ALWAYS", "ALWAYS_FULL"].contains(showStacktraceName) + if (showStacktraceName.equals("ALWAYS")) { + args "--stacktrace" + } else if (showStacktraceName.equals("ALWAYS_FULL")) { + args "--full-stacktrace" + } + standardOutput = new IndentingOutputStream(System.out, bwcVersion) + errorOutput = new IndentingOutputStream(System.err, bwcVersion) + doLast { + List missing = artifactFiles.grep { file -> + false == file.exists() + } + if (false == missing.empty) { + throw new InvalidUserDataException( + "Building ${bwcVersion} didn't generate expected files ${missing}") + } + } + } + + if (gradle.startParameter.taskNames == ["assemble"]) { + // Gradle needs the `artifacts` declaration, including `builtBy` bellow to make projects dependencies on this + // project work, but it will also trigger the build of these for the `assemble` task. + // Since these are only used for testing, we don't want to assemble them if `assemble` is the single command being + // ran. + logger.info("Skipping BWC builds since `assemble` is the only task name provided on the command line") + } else { + artifacts { + for (File artifactFile : artifactFiles) { + String artifactName = artifactFile.name.contains('oss') ? 'elasticsearch-oss' : 'elasticsearch' + String suffix = artifactFile.toString()[-3..-1] + 'default' file: artifactFile, name: artifactName, type: suffix, builtBy: buildBwcVersion + } + } + } +}} class IndentingOutputStream extends OutputStream { - public static final byte[] INDENT = " [bwc] ".getBytes(StandardCharsets.UTF_8) - private final OutputStream delegate + public final byte[] indent + private final OutputStream delegate - public IndentingOutputStream(OutputStream delegate) { - this.delegate = delegate - } - - @Override - public void write(int b) { - write([b] as int[], 0, 1) - } - - public void write(int[] bytes, int offset, int length) { - for (int i = 0; i < bytes.length; i++) { - delegate.write(bytes[i]) - if (bytes[i] == '\n') { - delegate.write(INDENT) - } + public IndentingOutputStream(OutputStream delegate, Object version) { + this.delegate = delegate + indent = " [${version}] ".getBytes(StandardCharsets.UTF_8) } - } -} + + @Override + public void write(int b) { + write([b] as int[], 0, 1) + } + + public void write(int[] bytes, int offset, int length) { + for (int i = 0; i < bytes.length; i++) { + delegate.write(bytes[i]) + if (bytes[i] == '\n') { + delegate.write(indent) + } + } + } +} \ No newline at end of file diff --git a/distribution/bwc/next-bugfix-snapshot/build.gradle b/distribution/bwc/maintenance/build.gradle similarity index 100% rename from distribution/bwc/next-bugfix-snapshot/build.gradle rename to distribution/bwc/maintenance/build.gradle diff --git a/distribution/bwc/next-minor-snapshot/build.gradle b/distribution/bwc/minor/build.gradle similarity index 100% rename from distribution/bwc/next-minor-snapshot/build.gradle rename to distribution/bwc/minor/build.gradle diff --git a/distribution/bwc/staged-minor-snapshot/build.gradle b/distribution/bwc/staged/build.gradle similarity index 100% rename from distribution/bwc/staged-minor-snapshot/build.gradle rename to distribution/bwc/staged/build.gradle diff --git a/qa/full-cluster-restart/build.gradle b/qa/full-cluster-restart/build.gradle index 8b305462e4d..e812af9522b 100644 --- a/qa/full-cluster-restart/build.gradle +++ b/qa/full-cluster-restart/build.gradle @@ -98,7 +98,7 @@ test.enabled = false // no unit tests for rolling upgrades, only the rest integr // basic integ tests includes testing bwc against the most recent version task integTest { if (project.bwc_tests_enabled) { - for (final def version : bwcVersions.snapshotsIndexCompatible) { + for (final def version : bwcVersions.unreleasedIndexCompatible) { dependsOn "v${version}#bwcTest" } } diff --git a/qa/mixed-cluster/build.gradle b/qa/mixed-cluster/build.gradle index ac57d51def7..62de043b5cc 100644 --- a/qa/mixed-cluster/build.gradle +++ b/qa/mixed-cluster/build.gradle @@ -65,7 +65,7 @@ test.enabled = false // no unit tests for rolling upgrades, only the rest integr // basic integ tests includes testing bwc against the most recent version task integTest { if (project.bwc_tests_enabled) { - for (final def version : bwcVersions.snapshotsWireCompatible) { + for (final def version : bwcVersions.unreleasedWireCompatible) { dependsOn "v${version}#bwcTest" } } diff --git a/qa/rolling-upgrade/build.gradle b/qa/rolling-upgrade/build.gradle index bfd37863cc2..4e27511fe04 100644 --- a/qa/rolling-upgrade/build.gradle +++ b/qa/rolling-upgrade/build.gradle @@ -145,7 +145,7 @@ test.enabled = false // no unit tests for rolling upgrades, only the rest integr // basic integ tests includes testing bwc against the most recent version task integTest { if (project.bwc_tests_enabled) { - for (final def version : bwcVersions.snapshotsWireCompatible) { + for (final def version : bwcVersions.unreleasedWireCompatible) { dependsOn "v${version}#bwcTest" } } diff --git a/qa/verify-version-constants/build.gradle b/qa/verify-version-constants/build.gradle index 30c879ec614..46b5df8ae0c 100644 --- a/qa/verify-version-constants/build.gradle +++ b/qa/verify-version-constants/build.gradle @@ -57,7 +57,7 @@ test.enabled = false task integTest { if (project.bwc_tests_enabled) { - final def version = bwcVersions.snapshotsIndexCompatible.first() + final def version = bwcVersions.unreleasedIndexCompatible.first() dependsOn "v${version}#bwcTest" } } diff --git a/settings.gradle b/settings.gradle index dedf3520bbb..c5acf583000 100644 --- a/settings.gradle +++ b/settings.gradle @@ -24,10 +24,10 @@ List projects = [ 'distribution:packages:deb', 'distribution:packages:oss-rpm', 'distribution:packages:rpm', - 'distribution:bwc:next-minor-snapshot', - 'distribution:bwc:staged-minor-snapshot', - 'distribution:bwc:next-bugfix-snapshot', - 'distribution:bwc:maintenance-bugfix-snapshot', + 'distribution:bwc:bugfix', + 'distribution:bwc:maintenance', + 'distribution:bwc:minor', + 'distribution:bwc:staged', 'distribution:tools:java-version-checker', 'distribution:tools:launchers', 'distribution:tools:plugin-cli', diff --git a/x-pack/qa/full-cluster-restart/build.gradle b/x-pack/qa/full-cluster-restart/build.gradle index c0fb7eb2b77..58d420836f9 100644 --- a/x-pack/qa/full-cluster-restart/build.gradle +++ b/x-pack/qa/full-cluster-restart/build.gradle @@ -237,7 +237,7 @@ subprojects { // basic integ tests includes testing bwc against the most recent version task integTest { if (project.bwc_tests_enabled) { - for (final def version : bwcVersions.snapshotsIndexCompatible) { + for (final def version : bwcVersions.unreleasedIndexCompatible) { dependsOn "v${version}#bwcTest" } } diff --git a/x-pack/qa/rolling-upgrade-basic/build.gradle b/x-pack/qa/rolling-upgrade-basic/build.gradle index 5774e5d7856..0c232e6b10b 100644 --- a/x-pack/qa/rolling-upgrade-basic/build.gradle +++ b/x-pack/qa/rolling-upgrade-basic/build.gradle @@ -125,7 +125,7 @@ test.enabled = false // no unit tests for rolling upgrades, only the rest integr // basic integ tests includes testing bwc against the most recent version task integTest { if (project.bwc_tests_enabled) { - for (final def version : bwcVersions.snapshotsWireCompatible) { + for (final def version : bwcVersions.unreleasedWireCompatible) { dependsOn "v${version}#bwcTest" } } diff --git a/x-pack/qa/rolling-upgrade/build.gradle b/x-pack/qa/rolling-upgrade/build.gradle index 90da6cf4e58..0fe80c49a94 100644 --- a/x-pack/qa/rolling-upgrade/build.gradle +++ b/x-pack/qa/rolling-upgrade/build.gradle @@ -281,7 +281,7 @@ subprojects { // basic integ tests includes testing bwc against the most recent version task integTest { if (project.bwc_tests_enabled) { - for (final def version : bwcVersions.snapshotsWireCompatible) { + for (final def version : bwcVersions.unreleasedWireCompatible) { dependsOn "v${version}#bwcTest" } }