Skip to content

Commit 2b41273

Browse files
Backport build changes and TASTy version tests from main (#185)
* [build] Improve, document and group versioning code in `Build.scala` (scala#21837) * Introduce `developedVersion` describing the target for the current release cycle * Adapt `baseVersion` to the effectively revert scala#21011 changes * Adapt .msi packager to use `developedVersion` as a workaround to MSI ProductInfo limitations (version without RC suffix required) * Group and document versioning related code * Improve mima test for previous version - test against first version in the series * Document referenceVersion * Update reference, MiMa previous version and sync TASTy version (scala#22187) * We now document better how and when tasty version should be set * Add additional runtime test to ensure we don't emit invalid TASTy version during Release / NIGHTLY releases and the expected version set in build matches version defined in TastyFormat * Adapt `checkReleasedTastyVersion` to LTS series - nightlies of LTS are released using stable TASTy version * Adjust expected tasty version comments to LTS versioning * Adjust MiMaFilter for backported dotty.tools.tasty.TastyVersion class
1 parent 22ee747 commit 2b41273

File tree

4 files changed

+186
-29
lines changed

4 files changed

+186
-29
lines changed

project/Build.scala

+118-29
Original file line numberDiff line numberDiff line change
@@ -81,12 +81,74 @@ object DottyJSPlugin extends AutoPlugin {
8181
object Build {
8282
import ScaladocConfigs._
8383

84+
/** Version of the Scala compiler used to build the artifacts.
85+
* Reference version should track the latest version pushed to Maven:
86+
* - In main branch it should be the last RC version
87+
* - In release branch it should be the last stable release
88+
*
89+
* Warning: Change of this variable needs to be consulted with `expectedTastyVersion`
90+
*/
8491
val referenceVersion = "3.3.5"
8592

86-
val baseVersion = "3.3.6-RC1"
93+
/** Version of the Scala compiler targeted in the current release cycle
94+
* Contains a version without RC/SNAPSHOT/NIGHTLY specific suffixes
95+
* Should be updated ONLY after release or cutoff for previous release cycle.
96+
*
97+
* Should only be referred from `dottyVersion` or settings/tasks requiring simplified version string,
98+
* eg. `compatMode` or Windows native distribution version.
99+
*
100+
* Warning: Change of this variable might require updating `expectedTastyVersion`
101+
*/
102+
val developedVersion = "3.3.6"
103+
104+
/** The version of the compiler including the RC prefix.
105+
* Defined as common base before calculating environment specific suffixes in `dottyVersion`
106+
*
107+
* By default, during development cycle defined as `${developedVersion}-RC1`;
108+
* During release candidate cycle incremented by the release officer before publishing a subsequent RC version;
109+
* During final, stable release is set exactly to `developedVersion`.
110+
*/
111+
val baseVersion = s"$developedVersion-RC1"
112+
113+
/** The version of TASTY that should be emitted, checked in runtime test
114+
* For defails on how TASTY version should be set see related discussions:
115+
* - https://github.com/scala/scala3/issues/13447#issuecomment-912447107
116+
* - https://github.com/scala/scala3/issues/14306#issuecomment-1069333516
117+
* - https://github.com/scala/scala3/pull/19321
118+
*
119+
* Simplified rules, given 3.$minor.$patch = $developedVersion
120+
* - Major version is always 28
121+
* - TASTY minor version:
122+
* - in main (NIGHTLY): {if $patch == 0 || ${referenceVersion.matches(raw"3.$minor.0-RC\d")} then $minor else ${minor + 1}}
123+
* - in LTS branch (NIGHTLY): always equal to $minor
124+
* - in release branch is always equal to $minor
125+
* - TASTY experimental version:
126+
* - in main (NIGHTLY) is always experimental
127+
* - in LTS branch (NIGHTLY) is always non-experimental
128+
* - in release candidate branch is experimental if {patch == 0}
129+
* - in stable release is always non-experimetnal
130+
*/
131+
val expectedTastyVersion = "28.3"
132+
checkReleasedTastyVersion()
133+
134+
/** Final version of Scala compiler, controlled by environment variables. */
135+
val dottyVersion = {
136+
if (isRelease) baseVersion
137+
else if (isNightly) s"${baseVersion}-bin-${VersionUtil.commitDate}-${VersionUtil.gitHash}-NIGHTLY"
138+
else s"${baseVersion}-bin-SNAPSHOT"
139+
}
140+
def isRelease = sys.env.get("RELEASEBUILD").contains("yes")
141+
def isNightly = sys.env.get("NIGHTLYBUILD").contains("yes")
142+
143+
/** Version calculate for `nonbootstrapped` projects */
144+
val dottyNonBootstrappedVersion = {
145+
// Make sure sbt always computes the scalaBinaryVersion correctly
146+
val bin = if (!dottyVersion.contains("-bin")) "-bin" else ""
147+
dottyVersion + bin + "-nonbootstrapped"
148+
}
87149

88150
// LTS or Next
89-
val versionLine = "LTS"
151+
final val versionLine = "LTS"
90152

91153
// Versions used by the vscode extension to create a new project
92154
// This should be the latest published releases.
@@ -95,23 +157,22 @@ object Build {
95157
val publishedDottyVersion = referenceVersion
96158
val sbtDottyVersion = "0.5.5"
97159

98-
/** Version against which we check binary compatibility.
160+
/** LTS version against which we check binary compatibility.
99161
*
100-
* This must be the latest published release in the same versioning line.
101-
* For example, if the next version is going to be 3.1.4, then this must be
102-
* set to 3.1.3. If it is going to be 3.1.0, it must be set to the latest
103-
* 3.0.x release.
162+
* This must be the earliest published release in the LTS versioning line.
163+
* For example, if the latest LTS release is be 3.3.4, then this must be
164+
* set to 3.3.0.
104165
*/
105-
val previousDottyVersion = "3.3.5"
166+
val mimaPreviousLTSDottyVersion = "3.3.0"
106167

107168
object CompatMode {
108169
final val BinaryCompatible = 0
109170
final val SourceAndBinaryCompatible = 1
110171
}
111172

112173
val compatMode = {
113-
val VersionRE = """^\d+\.(\d+).(\d+).*""".r
114-
baseVersion match {
174+
val VersionRE = """^\d+\.(\d+)\.(\d+)""".r
175+
developedVersion match {
115176
case VersionRE(_, "0") => CompatMode.BinaryCompatible
116177
case _ => CompatMode.SourceAndBinaryCompatible
117178
}
@@ -132,24 +193,6 @@ object Build {
132193
val dottyGithubUrl = "https://github.com/scala/scala3"
133194
val dottyGithubRawUserContentUrl = "https://raw.githubusercontent.com/scala/scala3"
134195

135-
136-
val isRelease = sys.env.get("RELEASEBUILD") == Some("yes")
137-
138-
val dottyVersion = {
139-
def isNightly = sys.env.get("NIGHTLYBUILD") == Some("yes")
140-
if (isRelease)
141-
baseVersion
142-
else if (isNightly)
143-
baseVersion + "-bin-" + VersionUtil.commitDate + "-" + VersionUtil.gitHash + "-NIGHTLY"
144-
else
145-
baseVersion + "-bin-SNAPSHOT"
146-
}
147-
val dottyNonBootstrappedVersion = {
148-
// Make sure sbt always computes the scalaBinaryVersion correctly
149-
val bin = if (!dottyVersion.contains("-bin")) "-bin" else ""
150-
dottyVersion + bin + "-nonbootstrapped"
151-
}
152-
153196
val sbtCommunityBuildVersion = "0.1.0-SNAPSHOT"
154197

155198
val agentOptions = List(
@@ -477,7 +520,7 @@ object Build {
477520
case cv: Disabled => thisProjectID.name
478521
case cv: Binary => s"${thisProjectID.name}_${cv.prefix}3${cv.suffix}"
479522
}
480-
(thisProjectID.organization % crossedName % previousDottyVersion)
523+
(thisProjectID.organization % crossedName % mimaPreviousLTSDottyVersion)
481524
},
482525

483526
mimaCheckDirection := (compatMode match {
@@ -2032,6 +2075,9 @@ object Build {
20322075
settings(disableDocSetting).
20332076
settings(
20342077
versionScheme := Some("semver-spec"),
2078+
Test / envVars ++= Map(
2079+
"EXPECTED_TASTY_VERSION" -> expectedTastyVersion,
2080+
),
20352081
if (mode == Bootstrapped) Def.settings(
20362082
commonMiMaSettings,
20372083
mimaBinaryIssueFilters ++= MiMaFilters.TastyCore,
@@ -2066,6 +2112,49 @@ object Build {
20662112
case Bootstrapped => commonBootstrappedSettings
20672113
})
20682114
}
2115+
2116+
/* Tests TASTy version invariants during NIGHLY, RC or Stable releases */
2117+
def checkReleasedTastyVersion(): Unit = {
2118+
case class ScalaVersion(minor: Int, patch: Int, isRC: Boolean)
2119+
def parseScalaVersion(version: String): ScalaVersion = version.split("\\.|-").take(4) match {
2120+
case Array("3", minor, patch) => ScalaVersion(minor.toInt, patch.toInt, false)
2121+
case Array("3", minor, patch, _) => ScalaVersion(minor.toInt, patch.toInt, true)
2122+
case other => sys.error(s"Invalid Scala base version string: $baseVersion")
2123+
}
2124+
lazy val version = parseScalaVersion(baseVersion)
2125+
lazy val referenceV = parseScalaVersion(referenceVersion)
2126+
lazy val (tastyMinor, tastyIsExperimental) = expectedTastyVersion.split("\\.|-").take(4) match {
2127+
case Array("28", minor) => (minor.toInt, false)
2128+
case Array("28", minor, "experimental", _) => (minor.toInt, true)
2129+
case other => sys.error(s"Invalid TASTy version string: $expectedTastyVersion")
2130+
}
2131+
val isLTS = versionLine == "LTS"
2132+
2133+
if(isNightly) {
2134+
assert(tastyIsExperimental || isLTS, "TASTY needs to be experimental in nightly builds")
2135+
val expectedTastyMinor = version.patch match {
2136+
case 0 => version.minor
2137+
case 1 if referenceV.patch == 0 && referenceV.isRC =>
2138+
// Special case for a period when reference version is a new unstable minor
2139+
// Needed for non_bootstrapped tests requiring either stable tasty or the same experimental version produced by both reference and bootstrapped compiler
2140+
assert(version.minor == referenceV.minor, "Expected reference and base version to use the same minor")
2141+
version.minor
2142+
case _ =>
2143+
if (isLTS) version.minor
2144+
else version.minor + 1
2145+
}
2146+
assert(tastyMinor == expectedTastyMinor, s"Invalid TASTy minor version, expected $expectedTastyMinor, got $tastyMinor")
2147+
}
2148+
2149+
if(isRelease) {
2150+
assert(version.minor == tastyMinor, "Minor versions of TASTY vesion and Scala version should match in release builds")
2151+
assert(!referenceV.isRC, "Stable release needs to use stable compiler version")
2152+
if (version.isRC && version.patch == 0)
2153+
assert(tastyIsExperimental, "TASTy should be experimental when releasing a new minor version RC")
2154+
else
2155+
assert(!tastyIsExperimental, "Stable version cannot use experimental TASTY")
2156+
}
2157+
}
20692158
}
20702159

20712160
object ScaladocConfigs {

project/MiMaFilters.scala

+3
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ object MiMaFilters {
1616
// end of New experimental features in 3.3.X
1717
)
1818
val TastyCore: Seq[ProblemFilter] = Seq(
19+
// Backported in 3.3.6
20+
ProblemFilters.exclude[MissingClassProblem]("dotty.tools.tasty.TastyVersion"),
21+
ProblemFilters.exclude[MissingClassProblem]("dotty.tools.tasty.TastyVersion$"),
1922
)
2023
val Interfaces: Seq[ProblemFilter] = Seq(
2124
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package dotty.tools.tasty
2+
3+
import scala.annotation.internal.sharable
4+
5+
case class TastyVersion private(major: Int, minor: Int, experimental: Int) {
6+
def isExperimental: Boolean = experimental > 0
7+
8+
def nextStable: TastyVersion = copy(experimental = 0)
9+
10+
def minStable: TastyVersion = copy(minor = 0, experimental = 0)
11+
12+
def show: String = {
13+
val suffix = if (isExperimental) s"-experimental-$experimental" else ""
14+
s"$major.$minor$suffix"
15+
}
16+
17+
def kind: String =
18+
if (isExperimental) "experimental TASTy" else "TASTy"
19+
20+
def validRange: String = {
21+
val min = TastyVersion(major, 0, 0)
22+
val max = if (experimental == 0) this else TastyVersion(major, minor - 1, 0)
23+
val extra = Option.when(experimental > 0)(this)
24+
s"stable TASTy from ${min.show} to ${max.show}${extra.fold("")(e => s", or exactly ${e.show}")}"
25+
}
26+
}
27+
28+
object TastyVersion {
29+
30+
@sharable
31+
private val cache: java.util.concurrent.ConcurrentHashMap[TastyVersion, TastyVersion] =
32+
new java.util.concurrent.ConcurrentHashMap()
33+
34+
def apply(major: Int, minor: Int, experimental: Int): TastyVersion = {
35+
val version = new TastyVersion(major, minor, experimental)
36+
val cachedVersion = cache.putIfAbsent(version, version)
37+
if (cachedVersion == null) version else cachedVersion
38+
}
39+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package dotty.tools.tasty
2+
3+
import org.junit.Assert._
4+
import org.junit.Test
5+
6+
import TastyBuffer._
7+
8+
// Tests ensuring TASTY version emitted by compiler is matching expected TASTY version
9+
class BuildTastyVersionTest {
10+
11+
val CurrentTastyVersion = TastyVersion(TastyFormat.MajorVersion, TastyFormat.MinorVersion, TastyFormat.ExperimentalVersion)
12+
13+
// Needs to be defined in build Test/envVars
14+
val ExpectedTastyVersionEnvVar = "EXPECTED_TASTY_VERSION"
15+
16+
@Test def testBuildTastyVersion(): Unit = {
17+
val expectedVersion = sys.env.get(ExpectedTastyVersionEnvVar)
18+
.getOrElse(fail(s"Env variable $ExpectedTastyVersionEnvVar not defined"))
19+
.match {
20+
case s"$major.$minor-experimental-$experimental" => TastyVersion(major.toInt, minor.toInt, experimental.toInt)
21+
case s"$major.$minor" if minor.forall(_.isDigit) => TastyVersion(major.toInt, minor.toInt, 0)
22+
case other => fail(s"Invalid TASTY version string: $other")
23+
}
24+
assertEquals(expectedVersion, CurrentTastyVersion)
25+
}
26+
}

0 commit comments

Comments
 (0)