Platform: build numbers: support multi-component build numbers

This commit is contained in:
Anton Makeev
2016-03-31 18:37:19 +02:00
parent 4a9555742c
commit 9f9af42a92
2 changed files with 188 additions and 63 deletions
@@ -23,63 +23,71 @@ import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
/**
* @author max
*/
public class BuildNumber implements Comparable<BuildNumber> {
public enum Format { HISTORIC, BRANCH_BASED, YEAR_BASED }
private static final String BUILD_NUMBER = "__BUILD_NUMBER__";
private static final String STAR = "*";
private static final String SNAPSHOT = "SNAPSHOT";
private static final String FALLBACK_VERSION = "999.SNAPSHOT";
private static final String FALLBACK_VERSION = "2999.1.SNAPSHOT";
private static class Holder {
private static final int TOP_BASELINE_VERSION = fromFile().getBaselineVersion();
private static final BuildNumber CURRENT_VERSION = fromFile();
}
private final String myProductCode;
private final int myBaselineVersion;
private final int myBuildNumber;
private final String myAttemptInfo;
@NotNull private final String myProductCode;
@NotNull private final Format myFormat;
private final int[] myComponents;
public BuildNumber(@NotNull String productCode, int baselineVersion, int buildNumber) {
this(productCode, baselineVersion, buildNumber, null);
this(productCode, Format.BRANCH_BASED, baselineVersion, buildNumber);
}
public BuildNumber(@NotNull String productCode, int baselineVersion, int buildNumber, @Nullable String attemptInfo) {
BuildNumber(@NotNull String productCode, @NotNull Format format, int... components) {
myProductCode = productCode;
myBaselineVersion = baselineVersion;
myBuildNumber = buildNumber;
myAttemptInfo = StringUtil.isEmpty(attemptInfo) ? null : attemptInfo;
myFormat = format;
myComponents = components;
}
public String asString() {
return asString(true, false);
return asString(true, true);
}
public String asStringWithAllDetails() {
return asString(true, true);
}
public String asStringWithoutProductCode() {
return asString(false, true);
}
public String asStringWithoutProductCodeAndSnapshot() {
return asString(false, false);
}
private String asString(boolean includeProductCode, boolean withBuildAttempt) {
private String asString(boolean includeProductCode, boolean withSnapshotMarker) {
StringBuilder builder = new StringBuilder();
if (includeProductCode && !StringUtil.isEmpty(myProductCode)) {
builder.append(myProductCode).append('-');
}
builder.append(myBaselineVersion).append('.');
if (myBuildNumber != Integer.MAX_VALUE) {
builder.append(myBuildNumber);
}
else {
builder.append(SNAPSHOT);
}
if (withBuildAttempt && myAttemptInfo != null) {
builder.append('.').append(myAttemptInfo);
for (int each : myComponents) {
if (each != Integer.MAX_VALUE) {
builder.append(each);
}
else if (withSnapshotMarker) {
builder.append(SNAPSHOT);
}
builder.append('.');
}
if (builder.charAt(builder.length() - 1) == '.') builder.setLength(builder.length() - 1);
return builder.toString();
}
@@ -91,9 +99,9 @@ public class BuildNumber implements Comparable<BuildNumber> {
public static BuildNumber fromString(String version, @Nullable String name) {
if (version == null) return null;
if (BUILD_NUMBER.equals(version)) {
if (BUILD_NUMBER.equals(version) || SNAPSHOT.equals(version)) {
final String productCode = name != null ? name : "";
return new BuildNumber(productCode, Holder.TOP_BASELINE_VERSION, Integer.MAX_VALUE);
return new BuildNumber(productCode, Holder.CURRENT_VERSION.getFormat(), Holder.CURRENT_VERSION.myComponents);
}
String code = version;
@@ -110,38 +118,57 @@ public class BuildNumber implements Comparable<BuildNumber> {
int baselineVersionSeparator = code.indexOf('.');
int baselineVersion;
int buildNumber;
String attemptInfo = null;
if (baselineVersionSeparator > 0) {
String baselineVersionString = code.substring(0, baselineVersionSeparator);
if (baselineVersionString.trim().isEmpty()) return null;
try {
String baselineVersionString = code.substring(0, baselineVersionSeparator);
if (baselineVersionString.trim().isEmpty()) return null;
baselineVersion = Integer.parseInt(baselineVersionString);
code = code.substring(baselineVersionSeparator + 1);
}
catch (NumberFormatException e) {
throw new RuntimeException("Invalid version number: " + version + "; plugin name: " + name);
}
int minorBuildSeparator = code.indexOf('.'); // allow <BuildNumber>.<BuildAttemptNumber> skipping BuildAttemptNumber
if (minorBuildSeparator > 0) {
attemptInfo = code.substring(minorBuildSeparator + 1);
code = code.substring(0, minorBuildSeparator);
if (baselineVersion >= 2016) {
List<String> stringComponents = StringUtil.split(code, ".");
int[] intComponents = new int[stringComponents.size()];
for (int i = 0; i < stringComponents.size(); i++) {
intComponents[i] = parseBuildNumber(version, stringComponents.get(i), name);
}
return new BuildNumber(productCode, Format.YEAR_BASED, intComponents);
}
else {
code = code.substring(baselineVersionSeparator + 1);
int minorBuildSeparator = code.indexOf('.'); // allow <BuildNumber>.<BuildAttemptNumber> skipping BuildAttemptNumber
Integer attemptInfo = null;
if (minorBuildSeparator > 0) {
attemptInfo = parseBuildNumber(version, code.substring(minorBuildSeparator + 1), name);
code = code.substring(0, minorBuildSeparator);
}
buildNumber = parseBuildNumber(version, code, name);
if (attemptInfo != null) {
return new BuildNumber(productCode, Format.BRANCH_BASED, baselineVersion, buildNumber, attemptInfo);
}
else {
return new BuildNumber(productCode, Format.BRANCH_BASED, baselineVersion, buildNumber);
}
}
buildNumber = parseBuildNumber(version, code, name);
}
else {
buildNumber = parseBuildNumber(version, code, name);
if (buildNumber <= 2000) {
// it's probably a baseline, not a build number
return new BuildNumber(productCode, buildNumber, 0, null);
return new BuildNumber(productCode, Format.BRANCH_BASED, buildNumber, 0);
}
baselineVersion = getBaseLineForHistoricBuilds(buildNumber);
return new BuildNumber(productCode, Format.HISTORIC, baselineVersion, buildNumber);
}
return new BuildNumber(productCode, baselineVersion, buildNumber, attemptInfo);
}
private static int parseBuildNumber(String version, String code, String name) {
@@ -181,21 +208,44 @@ public class BuildNumber implements Comparable<BuildNumber> {
@Override
public int compareTo(@NotNull BuildNumber o) {
if (myBaselineVersion == o.myBaselineVersion) return myBuildNumber - o.myBuildNumber;
return myBaselineVersion - o.myBaselineVersion;
for (int i = 0; i < Math.min(myComponents.length, o.myComponents.length); i++) {
int result = myComponents[i] - o.myComponents[i];
if (result != 0) return result;
if (myComponents[i] == Integer.MAX_VALUE) return 0; // anything after first SNAPSHOT doesn't really matter
}
return myComponents.length - o.myComponents.length;
}
public int compareToWithVariableLength(@NotNull BuildNumber o) {
for (int i = 0; i < Math.min(myComponents.length, o.myComponents.length); i++) {
int result = myComponents[i] - o.myComponents[i];
if (result != 0) return result;
if (myComponents[i] == Integer.MAX_VALUE) return 0; // anything after first SNAPSHOT doesn't really matter
}
return myComponents.length - o.myComponents.length;
}
@NotNull
public String getProductCode() {
return myProductCode;
}
public int getBaselineVersion() {
return myBaselineVersion;
return myFormat == Format.YEAR_BASED ? (myComponents[0] * 10 + myComponents[1]) : myComponents[0];
}
@Deprecated
public int getBuildNumber() {
return myBuildNumber;
return myFormat == Format.YEAR_BASED ? -1 : myComponents[1];
}
@NotNull
public Format getFormat() {
return myFormat;
}
@Override
@@ -205,10 +255,9 @@ public class BuildNumber implements Comparable<BuildNumber> {
BuildNumber that = (BuildNumber)o;
if (myBaselineVersion != that.myBaselineVersion) return false;
if (myBuildNumber != that.myBuildNumber) return false;
if (myFormat != that.myFormat) return false;
if (!myProductCode.equals(that.myProductCode)) return false;
if (!Comparing.equal(myAttemptInfo, that.myAttemptInfo)) return false;
if (!Arrays.equals(myComponents, that.myComponents)) return false;
return true;
}
@@ -216,18 +265,13 @@ public class BuildNumber implements Comparable<BuildNumber> {
@Override
public int hashCode() {
int result = myProductCode.hashCode();
result = 31 * result + myBaselineVersion;
result = 31 * result + myBuildNumber;
if (myAttemptInfo != null) result = 31 * result + myAttemptInfo.hashCode();
result = 31 * result + Arrays.hashCode(myComponents);
result = 31 * result + myFormat.hashCode();
return result;
}
// See http://www.jetbrains.net/confluence/display/IDEADEV/Build+Number+Ranges for historic build ranges
private static int getBaseLineForHistoricBuilds(int bn) {
if (bn == Integer.MAX_VALUE) {
return Holder.TOP_BASELINE_VERSION; // SNAPSHOTS
}
if (bn >= 10000) {
return 88; // Maia, 9x builds
}
@@ -276,10 +320,9 @@ public class BuildNumber implements Comparable<BuildNumber> {
}
public boolean isSnapshot() {
return myBuildNumber == Integer.MAX_VALUE;
}
public String asStringWithAllDetails() {
return asString(true, true);
for (int each : myComponents) {
if (each == Integer.MAX_VALUE) return true;
}
return false;
}
}
@@ -19,21 +19,82 @@ import org.junit.Test;
import static org.junit.Assert.*;
/**
* @author max
*/
public class BuildNumberTest {
@Test
public void historicBuild() {
assertEquals(new BuildNumber("", 75, 7512), BuildNumber.fromString("7512"));
assertEquals(new BuildNumber("", BuildNumber.Format.HISTORIC, 75, 7512), BuildNumber.fromString("7512"));
assertEquals("75.7512", BuildNumber.fromString("7512").asString());
}
@Test
public void branchBasedBuild() throws Exception {
assertParsed(BuildNumber.fromString("145"), 145, 0, "145.0");
assertParsed(BuildNumber.fromString("145.1"), 145, 1, "145.1");
assertParsed(BuildNumber.fromString("145.1.2"), 145, 1, "145.1.2");
assertParsed(BuildNumber.fromString("IU-145.1.2"), 145, 1, "IU-145.1.2");
assertParsed(BuildNumber.fromString("IU-145.SNAPSHOT"), 145, Integer.MAX_VALUE, "IU-145.SNAPSHOT");
assertParsed(BuildNumber.fromString("IU-145.1.SNAPSHOT"), 145, 1, "IU-145.1.SNAPSHOT");
}
@Test
public void yearBasedBuild() throws Exception {
assertParsed(BuildNumber.fromString("2016.1.2"), 20161, -1, "2016.1.2");
assertParsed(BuildNumber.fromString("IU-2016.1.2"), 20161, -1, "IU-2016.1.2");
assertParsed(BuildNumber.fromString("2016.1.2.3"), 20161, -1, "2016.1.2.3");
assertParsed(BuildNumber.fromString("IU-2016.1.2.3"), 20161, -1, "IU-2016.1.2.3");
assertParsed(BuildNumber.fromString("2016.1.2.3.4"), 20161, -1, "2016.1.2.3.4");
assertParsed(BuildNumber.fromString("IU-2016.1.2.3.4"), 20161, -1, "IU-2016.1.2.3.4");
assertParsed(BuildNumber.fromString("IU-2016.1.SNAPSHOT"), 20161, -1, "IU-2016.1.SNAPSHOT");
assertParsed(BuildNumber.fromString("IU-2016.1.SNAPSHOT.1"), 20161, -1, "IU-2016.1.SNAPSHOT.1");
}
private static void assertParsed(BuildNumber n, int expectedBaseline, int expectedBuildNumber, String asString) {
assertEquals(expectedBaseline, n.getBaselineVersion());
assertEquals(expectedBuildNumber, n.getBuildNumber());
assertEquals(asString, n.asString());
}
@Test
public void comparingYearBasedVersion() throws Exception {
assertTrue(BuildNumber.fromString("2016.1").compareTo(BuildNumber.fromString("2016.1")) == 0);
assertTrue(BuildNumber.fromString("2016.1.1").compareTo(BuildNumber.fromString("2016.1.1")) == 0);
assertTrue(BuildNumber.fromString("2016.1.1.1").compareTo(BuildNumber.fromString("2016.1.1.1")) == 0);
assertTrue(BuildNumber.fromString("2016.1").compareTo(BuildNumber.fromString("2016.1.1")) < 0);
assertTrue(BuildNumber.fromString("2016.1").compareTo(BuildNumber.fromString("2016.1.1.1")) < 0);
assertTrue(BuildNumber.fromString("2016.1.1").compareTo(BuildNumber.fromString("2016.1.1.1")) < 0);
assertTrue(BuildNumber.fromString("2016.1").compareTo(BuildNumber.fromString("2016.2")) < 0);
assertTrue(BuildNumber.fromString("2016.1").compareTo(BuildNumber.fromString("2016.2.1")) < 0);
assertTrue(BuildNumber.fromString("2016.1.1").compareTo(BuildNumber.fromString("2016.2")) < 0);
assertTrue(BuildNumber.fromString("2016.1.1").compareTo(BuildNumber.fromString("2016.2.1")) < 0);
assertTrue(BuildNumber.fromString("146.1").compareTo(BuildNumber.fromString("2016.1")) < 0);
assertTrue(BuildNumber.fromString("146.9.9").compareTo(BuildNumber.fromString("2016.1")) < 0);
}
@Test
public void isSnapshot() {
assertTrue(BuildNumber.fromString("SNAPSHOT").isSnapshot());
assertTrue(BuildNumber.fromString("__BUILD_NUMBER__").isSnapshot());
assertTrue(BuildNumber.fromString("IU-90.SNAPSHOT").isSnapshot());
assertTrue(BuildNumber.fromString("IC-90.*").isSnapshot());
assertFalse(BuildNumber.fromString("90.9999999").isSnapshot());
assertFalse(BuildNumber.fromString("2016.1").isSnapshot());
assertTrue(BuildNumber.fromString("2016.1.SNAPSHOT").isSnapshot());
assertTrue(BuildNumber.fromString("2016.1.SNAPSHOT.1").isSnapshot());
}
@Test
public void devSnapshotVersion() throws Exception {
BuildNumber b = BuildNumber.fromString("__BUILD_NUMBER__");
assertTrue(b.asString(), b.getBaselineVersion() >= 2016);
assertEquals(b.asString(), -1, b.getBuildNumber());
assertTrue(b.isSnapshot());
assertEquals(BuildNumber.fromString("__BUILD_NUMBER__"), BuildNumber.fromString("SNAPSHOT"));
}
@Test
@@ -43,5 +104,26 @@ public class BuildNumberTest {
assertTrue(BuildNumber.fromString("IU-90.SNAPSHOT").compareTo(BuildNumber.fromString("RM-100.12345")) < 0);
assertTrue(BuildNumber.fromString("IU-90.SNAPSHOT").compareTo(BuildNumber.fromString("RM-100.SNAPSHOT")) < 0);
assertTrue(BuildNumber.fromString("IU-90.SNAPSHOT").compareTo(BuildNumber.fromString("RM-90.SNAPSHOT")) == 0);
assertTrue(BuildNumber.fromString("2016.1.SNAPSHOT").compareTo(BuildNumber.fromString("2016.1.1")) > 0);
assertTrue(BuildNumber.fromString("2016.1.SNAPSHOT").compareTo(BuildNumber.fromString("2016.1.SNAPSHOT")) == 0);
assertTrue(BuildNumber.fromString("2016.1.1.SNAPSHOT").compareTo(BuildNumber.fromString("2016.1.1.1")) > 0);
assertTrue(BuildNumber.fromString("2016.1.1.SNAPSHOT").compareTo(BuildNumber.fromString("2016.1.1.SNAPSHOT")) == 0);
assertTrue(BuildNumber.fromString("2016.1.SNAPSHOT.1").compareTo(BuildNumber.fromString("2016.1.1.1")) > 0);
assertTrue(BuildNumber.fromString("2016.1.SNAPSHOT.1").compareTo(BuildNumber.fromString("2016.1.1.SNAPSHOT")) > 0);
assertTrue(BuildNumber.fromString("2016.1.SNAPSHOT.1").compareTo(BuildNumber.fromString("2016.1.SNAPSHOT.SNAPSHOT")) == 0);
}
@Test
public void fallbackVersion() throws Exception {
assertParsed(BuildNumber.fallback(), 29991, -1, "2999.1.SNAPSHOT");
assertEquals(BuildNumber.Format.YEAR_BASED, BuildNumber.fallback().getFormat());
assertTrue(BuildNumber.fallback().isSnapshot());
assertTrue(BuildNumber.fallback().compareTo(BuildNumber.fromString("7512")) > 0);
assertTrue(BuildNumber.fallback().compareTo(BuildNumber.fromString("145")) > 0);
assertTrue(BuildNumber.fallback().compareTo(BuildNumber.fromString("145.12")) > 0);
assertTrue(BuildNumber.fallback().compareTo(BuildNumber.fromString("2016.1")) > 0);
assertTrue(BuildNumber.fallback().compareTo(BuildNumber.fromString("2016.1.2")) > 0);
}
}