From 45c2e639b4fe900bfc3a5aa78f19a909e6fd9e23 Mon Sep 17 00:00:00 2001 From: Wojciech Mazur Date: Wed, 6 Nov 2024 11:16:02 +0100 Subject: [PATCH 1/7] [build] Improve, document and group versioning code in `Build.scala` (#21837) * Introduce `developedVersion` describing the target for the current release cycle * Adapt `baseVersion` to the effectively revert #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 --- project/Build.scala | 56 ++++++++++++++++++++++++++++----------------- 1 file changed, 35 insertions(+), 21 deletions(-) diff --git a/project/Build.scala b/project/Build.scala index 1174895d8d2e..b91ff73c485c 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -83,7 +83,39 @@ object Build { val referenceVersion = "3.3.5" - val baseVersion = "3.3.6-RC1" + /** Version of the Scala compiler targeted in the current release cycle + * Contains a version without RC/SNAPSHOT/NIGHTLY specific suffixes + * Should be updated ONLY after release or cutoff for previous release cycle. + * + * Should only be referred from `dottyVersion` or settings/tasks requiring simplified version string, + * eg. `compatMode` or Windows native distribution version. + */ + val developedVersion = "3.3.6" + + /** The version of the compiler including the RC prefix. + * Defined as common base before calculating environment specific suffixes in `dottyVersion` + * + * By default, during development cycle defined as `${developedVersion}-RC1`; + * During release candidate cycle incremented by the release officer before publishing a subsequent RC version; + * During final, stable release is set exactly to `developedVersion`. + */ + val baseVersion = s"$developedVersion-RC1" + + /** Final version of Scala compiler, controlled by environment variables. */ + val dottyVersion = { + if (isRelease) baseVersion + else if (isNightly) s"${baseVersion}-bin-${VersionUtil.commitDate}-${VersionUtil.gitHash}-NIGHTLY" + else s"${baseVersion}-bin-SNAPSHOT" + } + def isRelease = sys.env.get("RELEASEBUILD").contains("yes") + def isNightly = sys.env.get("NIGHTLYBUILD").contains("yes") + + /** Version calculate for `nonbootstrapped` projects */ + val dottyNonBootstrappedVersion = { + // Make sure sbt always computes the scalaBinaryVersion correctly + val bin = if (!dottyVersion.contains("-bin")) "-bin" else "" + dottyVersion + bin + "-nonbootstrapped" + } // LTS or Next val versionLine = "LTS" @@ -110,8 +142,8 @@ object Build { } val compatMode = { - val VersionRE = """^\d+\.(\d+).(\d+).*""".r - baseVersion match { + val VersionRE = """^\d+\.(\d+)\.(\d+)""".r + developedVersion match { case VersionRE(_, "0") => CompatMode.BinaryCompatible case _ => CompatMode.SourceAndBinaryCompatible } @@ -132,24 +164,6 @@ object Build { val dottyGithubUrl = "https://github.com/scala/scala3" val dottyGithubRawUserContentUrl = "https://raw.githubusercontent.com/scala/scala3" - - val isRelease = sys.env.get("RELEASEBUILD") == Some("yes") - - val dottyVersion = { - def isNightly = sys.env.get("NIGHTLYBUILD") == Some("yes") - if (isRelease) - baseVersion - else if (isNightly) - baseVersion + "-bin-" + VersionUtil.commitDate + "-" + VersionUtil.gitHash + "-NIGHTLY" - else - baseVersion + "-bin-SNAPSHOT" - } - val dottyNonBootstrappedVersion = { - // Make sure sbt always computes the scalaBinaryVersion correctly - val bin = if (!dottyVersion.contains("-bin")) "-bin" else "" - dottyVersion + bin + "-nonbootstrapped" - } - val sbtCommunityBuildVersion = "0.1.0-SNAPSHOT" val agentOptions = List( From df73cadf09c684fd78c9d397b8dabedcdb7f0837 Mon Sep 17 00:00:00 2001 From: Wojciech Mazur Date: Wed, 19 Mar 2025 10:13:20 +0100 Subject: [PATCH 2/7] Improve mima test for previous version - test against first version in the series --- project/Build.scala | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/project/Build.scala b/project/Build.scala index b91ff73c485c..f072887810a5 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -127,14 +127,13 @@ object Build { val publishedDottyVersion = referenceVersion val sbtDottyVersion = "0.5.5" - /** Version against which we check binary compatibility. + /** LTS version against which we check binary compatibility. * - * This must be the latest published release in the same versioning line. - * For example, if the next version is going to be 3.1.4, then this must be - * set to 3.1.3. If it is going to be 3.1.0, it must be set to the latest - * 3.0.x release. + * This must be the earliest published release in the LTS versioning line. + * For example, if the latest LTS release is be 3.3.4, then this must be + * set to 3.3.0. */ - val previousDottyVersion = "3.3.5" + val mimaPreviousLTSDottyVersion = "3.3.0" object CompatMode { final val BinaryCompatible = 0 @@ -491,7 +490,7 @@ object Build { case cv: Disabled => thisProjectID.name case cv: Binary => s"${thisProjectID.name}_${cv.prefix}3${cv.suffix}" } - (thisProjectID.organization % crossedName % previousDottyVersion) + (thisProjectID.organization % crossedName % mimaPreviousLTSDottyVersion) }, mimaCheckDirection := (compatMode match { From b15b55df30584967c1c50d622a3513bfad5487de Mon Sep 17 00:00:00 2001 From: Wojciech Mazur Date: Wed, 19 Mar 2025 10:16:30 +0100 Subject: [PATCH 3/7] Document referenceVersion --- project/Build.scala | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/project/Build.scala b/project/Build.scala index f072887810a5..82a8845f6fbd 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -81,6 +81,11 @@ object DottyJSPlugin extends AutoPlugin { object Build { import ScaladocConfigs._ + /** Version of the Scala compiler used to build the artifacts. + * Reference version should track the latest version pushed to Maven: + * - In main branch it should be the last RC version + * - In release branch it should be the last stable release + */ val referenceVersion = "3.3.5" /** Version of the Scala compiler targeted in the current release cycle From bf9e225f9cd6c8ee8ed10f7e3df1a652cc68abc2 Mon Sep 17 00:00:00 2001 From: Wojciech Mazur Date: Wed, 19 Mar 2025 10:26:16 +0100 Subject: [PATCH 4/7] Update reference, MiMa previous version and sync TASTy version (#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 --- project/Build.scala | 66 +++++++++++++++++++ .../src/dotty/tools/tasty/TastyVersion.scala | 39 +++++++++++ .../tools/tasty/BuildTastyVersionTest.scala | 26 ++++++++ 3 files changed, 131 insertions(+) create mode 100644 tasty/src/dotty/tools/tasty/TastyVersion.scala create mode 100644 tasty/test/dotty/tools/tasty/BuildTastyVersionTest.scala diff --git a/project/Build.scala b/project/Build.scala index 82a8845f6fbd..03754f32e98b 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -85,6 +85,8 @@ object Build { * Reference version should track the latest version pushed to Maven: * - In main branch it should be the last RC version * - In release branch it should be the last stable release + * + * Warning: Change of this variable needs to be consulted with `expectedTastyVersion` */ val referenceVersion = "3.3.5" @@ -94,6 +96,8 @@ object Build { * * Should only be referred from `dottyVersion` or settings/tasks requiring simplified version string, * eg. `compatMode` or Windows native distribution version. + * + * Warning: Change of this variable might require updating `expectedTastyVersion` */ val developedVersion = "3.3.6" @@ -106,6 +110,25 @@ object Build { */ val baseVersion = s"$developedVersion-RC1" + /** The version of TASTY that should be emitted, checked in runtime test + * For defails on how TASTY version should be set see related discussions: + * - https://github.com/scala/scala3/issues/13447#issuecomment-912447107 + * - https://github.com/scala/scala3/issues/14306#issuecomment-1069333516 + * - https://github.com/scala/scala3/pull/19321 + * + * Simplified rules, given 3.$minor.$patch = $developedVersion + * - Major version is always 28 + * - TASTY minor version: + * - in main (NIGHTLY): {if $patch == 0 || ${referenceVersion.matches(raw"3.$minor.0-RC\d")} then $minor else ${minor + 1}} + * - in release branch is always equal to $minor + * - TASTY experimental version: + * - in main (NIGHTLY) is always experimental + * - in release candidate branch is experimental if {patch == 0} + * - in stable release is always non-experimetnal + */ + val expectedTastyVersion = "28.3" + checkReleasedTastyVersion() + /** Final version of Scala compiler, controlled by environment variables. */ val dottyVersion = { if (isRelease) baseVersion @@ -2043,6 +2066,9 @@ object Build { settings(disableDocSetting). settings( versionScheme := Some("semver-spec"), + Test / envVars ++= Map( + "EXPECTED_TASTY_VERSION" -> expectedTastyVersion, + ), if (mode == Bootstrapped) Def.settings( commonMiMaSettings, mimaBinaryIssueFilters ++= MiMaFilters.TastyCore, @@ -2077,6 +2103,46 @@ object Build { case Bootstrapped => commonBootstrappedSettings }) } + + /* Tests TASTy version invariants during NIGHLY, RC or Stable releases */ + def checkReleasedTastyVersion(): Unit = { + case class ScalaVersion(minor: Int, patch: Int, isRC: Boolean) + def parseScalaVersion(version: String): ScalaVersion = version.split("\\.|-").take(4) match { + case Array("3", minor, patch) => ScalaVersion(minor.toInt, patch.toInt, false) + case Array("3", minor, patch, _) => ScalaVersion(minor.toInt, patch.toInt, true) + case other => sys.error(s"Invalid Scala base version string: $baseVersion") + } + lazy val version = parseScalaVersion(baseVersion) + lazy val referenceV = parseScalaVersion(referenceVersion) + lazy val (tastyMinor, tastyIsExperimental) = expectedTastyVersion.split("\\.|-").take(4) match { + case Array("28", minor) => (minor.toInt, false) + case Array("28", minor, "experimental", _) => (minor.toInt, true) + case other => sys.error(s"Invalid TASTy version string: $expectedTastyVersion") + } + + if(isNightly) { + assert(tastyIsExperimental, "TASTY needs to be experimental in nightly builds") + val expectedTastyMinor = version.patch match { + case 0 => version.minor + case 1 if referenceV.patch == 0 && referenceV.isRC => + // Special case for a period when reference version is a new unstable minor + // Needed for non_bootstrapped tests requiring either stable tasty or the same experimental version produced by both reference and bootstrapped compiler + assert(version.minor == referenceV.minor, "Expected reference and base version to use the same minor") + version.minor + case _ => version.minor + 1 + } + assert(tastyMinor == expectedTastyMinor, "Invalid TASTy minor version") + } + + if(isRelease) { + assert(version.minor == tastyMinor, "Minor versions of TASTY vesion and Scala version should match in release builds") + assert(!referenceV.isRC, "Stable release needs to use stable compiler version") + if (version.isRC && version.patch == 0) + assert(tastyIsExperimental, "TASTy should be experimental when releasing a new minor version RC") + else + assert(!tastyIsExperimental, "Stable version cannot use experimental TASTY") + } + } } object ScaladocConfigs { diff --git a/tasty/src/dotty/tools/tasty/TastyVersion.scala b/tasty/src/dotty/tools/tasty/TastyVersion.scala new file mode 100644 index 000000000000..b6474f7c7934 --- /dev/null +++ b/tasty/src/dotty/tools/tasty/TastyVersion.scala @@ -0,0 +1,39 @@ +package dotty.tools.tasty + +import scala.annotation.internal.sharable + +case class TastyVersion private(major: Int, minor: Int, experimental: Int) { + def isExperimental: Boolean = experimental > 0 + + def nextStable: TastyVersion = copy(experimental = 0) + + def minStable: TastyVersion = copy(minor = 0, experimental = 0) + + def show: String = { + val suffix = if (isExperimental) s"-experimental-$experimental" else "" + s"$major.$minor$suffix" + } + + def kind: String = + if (isExperimental) "experimental TASTy" else "TASTy" + + def validRange: String = { + val min = TastyVersion(major, 0, 0) + val max = if (experimental == 0) this else TastyVersion(major, minor - 1, 0) + val extra = Option.when(experimental > 0)(this) + s"stable TASTy from ${min.show} to ${max.show}${extra.fold("")(e => s", or exactly ${e.show}")}" + } +} + +object TastyVersion { + + @sharable + private val cache: java.util.concurrent.ConcurrentHashMap[TastyVersion, TastyVersion] = + new java.util.concurrent.ConcurrentHashMap() + + def apply(major: Int, minor: Int, experimental: Int): TastyVersion = { + val version = new TastyVersion(major, minor, experimental) + val cachedVersion = cache.putIfAbsent(version, version) + if (cachedVersion == null) version else cachedVersion + } +} diff --git a/tasty/test/dotty/tools/tasty/BuildTastyVersionTest.scala b/tasty/test/dotty/tools/tasty/BuildTastyVersionTest.scala new file mode 100644 index 000000000000..88a48b293d11 --- /dev/null +++ b/tasty/test/dotty/tools/tasty/BuildTastyVersionTest.scala @@ -0,0 +1,26 @@ +package dotty.tools.tasty + +import org.junit.Assert._ +import org.junit.Test + +import TastyBuffer._ + +// Tests ensuring TASTY version emitted by compiler is matching expected TASTY version +class BuildTastyVersionTest { + + val CurrentTastyVersion = TastyVersion(TastyFormat.MajorVersion, TastyFormat.MinorVersion, TastyFormat.ExperimentalVersion) + + // Needs to be defined in build Test/envVars + val ExpectedTastyVersionEnvVar = "EXPECTED_TASTY_VERSION" + + @Test def testBuildTastyVersion(): Unit = { + val expectedVersion = sys.env.get(ExpectedTastyVersionEnvVar) + .getOrElse(fail(s"Env variable $ExpectedTastyVersionEnvVar not defined")) + .match { + case s"$major.$minor-experimental-$experimental" => TastyVersion(major.toInt, minor.toInt, experimental.toInt) + case s"$major.$minor" if minor.forall(_.isDigit) => TastyVersion(major.toInt, minor.toInt, 0) + case other => fail(s"Invalid TASTY version string: $other") + } + assertEquals(expectedVersion, CurrentTastyVersion) + } +} From f7e6c4ca2de24647162cbaa657d91535c057b839 Mon Sep 17 00:00:00 2001 From: Wojciech Mazur Date: Wed, 19 Mar 2025 10:36:35 +0100 Subject: [PATCH 5/7] Adapt `checkReleasedTastyVersion` to LTS series - nightlies of LTS are released using stable TASTy version --- project/Build.scala | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/project/Build.scala b/project/Build.scala index 03754f32e98b..369e3fd2c11b 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -146,7 +146,7 @@ object Build { } // LTS or Next - val versionLine = "LTS" + final val versionLine = "LTS" // Versions used by the vscode extension to create a new project // This should be the latest published releases. @@ -2119,9 +2119,10 @@ object Build { case Array("28", minor, "experimental", _) => (minor.toInt, true) case other => sys.error(s"Invalid TASTy version string: $expectedTastyVersion") } + val isLTS = versionLine == "LTS" if(isNightly) { - assert(tastyIsExperimental, "TASTY needs to be experimental in nightly builds") + assert(tastyIsExperimental || isLTS, "TASTY needs to be experimental in nightly builds") val expectedTastyMinor = version.patch match { case 0 => version.minor case 1 if referenceV.patch == 0 && referenceV.isRC => @@ -2129,9 +2130,11 @@ object Build { // Needed for non_bootstrapped tests requiring either stable tasty or the same experimental version produced by both reference and bootstrapped compiler assert(version.minor == referenceV.minor, "Expected reference and base version to use the same minor") version.minor - case _ => version.minor + 1 + case _ => + if (isLTS) version.minor + else version.minor + 1 } - assert(tastyMinor == expectedTastyMinor, "Invalid TASTy minor version") + assert(tastyMinor == expectedTastyMinor, s"Invalid TASTy minor version, expected $expectedTastyMinor, got $tastyMinor") } if(isRelease) { From b0f75e0b2ea4d44d8eb7a386d603a9bb3c6b2a75 Mon Sep 17 00:00:00 2001 From: Wojciech Mazur Date: Wed, 19 Mar 2025 10:44:43 +0100 Subject: [PATCH 6/7] Adjust expected tasty version comments to LTS versioning --- project/Build.scala | 2 ++ 1 file changed, 2 insertions(+) diff --git a/project/Build.scala b/project/Build.scala index 369e3fd2c11b..47c8ad92ba96 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -120,9 +120,11 @@ object Build { * - Major version is always 28 * - TASTY minor version: * - in main (NIGHTLY): {if $patch == 0 || ${referenceVersion.matches(raw"3.$minor.0-RC\d")} then $minor else ${minor + 1}} + * - in LTS branch (NIGHTLY): always equal to $minor * - in release branch is always equal to $minor * - TASTY experimental version: * - in main (NIGHTLY) is always experimental + * - in LTS branch (NIGHTLY) is always non-experimental * - in release candidate branch is experimental if {patch == 0} * - in stable release is always non-experimetnal */ From 1c8a2f3d3f6745833e2e14a7c8d99049a21369b4 Mon Sep 17 00:00:00 2001 From: Wojciech Mazur Date: Wed, 19 Mar 2025 10:53:24 +0100 Subject: [PATCH 7/7] Adjust MiMaFilter for backported dotty.tools.tasty.TastyVersion class --- project/MiMaFilters.scala | 3 +++ 1 file changed, 3 insertions(+) diff --git a/project/MiMaFilters.scala b/project/MiMaFilters.scala index 112a5601615c..f35b9240ae14 100644 --- a/project/MiMaFilters.scala +++ b/project/MiMaFilters.scala @@ -16,6 +16,9 @@ object MiMaFilters { // end of New experimental features in 3.3.X ) val TastyCore: Seq[ProblemFilter] = Seq( + // Backported in 3.3.6 + ProblemFilters.exclude[MissingClassProblem]("dotty.tools.tasty.TastyVersion"), + ProblemFilters.exclude[MissingClassProblem]("dotty.tools.tasty.TastyVersion$"), ) val Interfaces: Seq[ProblemFilter] = Seq( )