Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [0.9.16] - 2026-01-20
- feat: Scala 3.8.0;
- feat: Gradle 9.3.0;
- cleanup;

## [0.9.15] - 2026-01-16
- chore: Scala 3.7.4;
- chore: Scala 2.13.18;
Expand Down
24 changes: 16 additions & 8 deletions README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
:toc: preamble
:icons: font
// INCLUDED ATTRIBUTES
:version-plugin: 0.9.15
:version-gradle: 9.2.1
:version-scala: 3.7.4
:version-plugin: 0.9.16
:version-gradle: 9.3.0
:version-scala: 3.8.0
:version-scala-213: 2.13.18
:version-scala-212: 2.12.21
:version-sbt-test-interface: 1.0
Expand All @@ -32,7 +32,7 @@
:version-framework-weaver: 0.11.3
:version-framework-zio-test: 2.1.24
:attribute-pluginScalaBackendProperty: org.podval.tools.backend
:attribute-gradleVersionForBadge: 9.2.1
:attribute-gradleVersionForBadge: 9.3.0
// INCLUDED ATTRIBUTES

image:https://github.com/dubinsky/scalajs-gradle/actions/workflows/CI.yaml/badge.svg[]
Expand Down Expand Up @@ -153,6 +153,14 @@ $ ./graldew -PscalaVersion={version-scala}

Plugin assumes that the project uses the explicit approach; no assumptions are made about the name of the property.

Note: Starting with Scala 3.8.0, the following has to be configured in the
Gradle build file:

[source,groovy]
----
tasks.withType(ScalaCompile).configureEach {scalaCompileOptions.additionalParameters = ['-release:17'] }
----

=== Building for Specific Scala Version
Plugin does not support building for multiple Scala versions
_at the same time_ using only Gradle
Expand Down Expand Up @@ -2165,7 +2173,7 @@ It would have been nice if this fact was documented somewhere :(
I coded an event queue with its own thread, but then discovered that:

- Gradle provides a mechanism that ensures that all the calls are made from the same thread: `Actor.createActor.getProxy`;
- when tests are forked, `MaxNParallelTestClassProcessor` is used, which already does that, so I do not need to;
- when tests are forked, `MaxNParallelTestDefinitionProcessor` is used, which already does that, so I do not need to;
- when running on `Scala.js` everything is single-threaded anyway.

=== Test Ids
Expand Down Expand Up @@ -2193,7 +2201,7 @@ Gradle uses test ids that are composite of two Longs.
AbstractTestTask installs `StateTrackingTestResultProcessor`
which keeps track of all tests that are executing in any `TestWorker`.
That means that test ids must be scoped per `TestWorker`.
Each `TestWorker` has an `idGenerator` which it uses to generate `WorkerTestClassProcessor.workerSuiteId`;
Each `TestWorker` has an `idGenerator` which it uses to generate `WorkerTestDefinitionProcessor.workerSuiteId`;
that same `idGenerator` can be used to generate sequential ids
for the tests in the worker,
satisfying the uniqueness requirements - and resulting in the test ids always being
Expand All @@ -2204,7 +2212,7 @@ Because tests are scoped by the workers, it does not seem possible to group test
Since I can not use the real `rootTestSuiteId` that `DefaultTestExecuter`
supplies to the `TestMainAction` - because it is a `String` -
and I am not keen on second-guessing what it is anyway,
I use a `RunTestClassProcessor.rootTestSuiteIdPlaceholder`
I use a `RunTestDefinitionProcessor.rootTestSuiteIdPlaceholder`
and change it to the real one in `FixRootTestSuiteOutputTestResultProcessor`.

=== Test Output
Expand Down Expand Up @@ -2372,7 +2380,7 @@ https://github.com/scala-native/scala-native/blob/main/sbt-scala-native/src/main
`TestExecuter` makes sure that the tests are not forked,
and `TestTask` overrides
`org.gradle.api.tasks.testing.Test.getMaxParallelForks()`
to return `1` on `Scala.js` to prevent `MaxNParallelTestClassProcessor`
to return `1` on `Scala.js` to prevent `MaxNParallelTestDefinitionProcessor`
from forking.

On JVM, exceptions are serialized in Gradle's `org.gradle.internal.serialize.ExceptionPlaceholder`, which contains lots of details;
Expand Down
3 changes: 2 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ plugins {
id 'com.github.ben-manes.versions' version '0.53.0'
id 'scala'
id 'signing'
id 'org.podval.tools.scalajs' version '0.9.14'
id 'org.podval.tools.scalajs' version '0.9.16'
id 'com.gradle.plugin-publish' version '2.0.0'
}

Expand Down Expand Up @@ -63,6 +63,7 @@ tasks.named("dependencyUpdates").configure {
tasks.withType(ScalaCompile).configureEach {
scalaCompileOptions.with {
additionalParameters = [
'-release:17',
'-new-syntax',
'-feature',
'-language:strictEquality',
Expand Down
6 changes: 3 additions & 3 deletions gradle.properties
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
org.podval.tools.backend = JVM
version_plugin = 0.9.15
version_gradle = 9.2.1
version_scala = 3.7.4
version_plugin = 0.9.16
version_gradle = 9.3.0
version_scala = 3.8.0
version_scala_213 = 2.13.18
version_scala_212 = 2.12.21
version_sbt_test_interface = 1.0
Expand Down
2 changes: 1 addition & 1 deletion gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.0-all.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,16 +28,16 @@ abstract class BackendExtension @Inject(

final def isScala3: Boolean = getScalaLibrary.isScala3
final def getScalaVersion: String = getScalaLibrary.scalaVersion.toString
final def getScalaBinaryVersion: Version = getScalaLibrary.scalaVersion.binaryVersionSuffix
final def getScala2BinaryVersion: Version = getScalaLibrary.scala2.binaryVersionSuffix
final def getScalaBinaryVersion: Version = getScalaLibrary.scalaBinaryVersionSuffix
final def getScala2BinaryVersion: Version = getScalaLibrary.scala2BinaryVersionSuffix

final lazy val getScalaLibrary: ScalaLibrary =
val result: ScalaLibrary = ScalaLibrary.fromImplementationConfiguration(project)
require(result.scalaVersion == getScalaVersionFromScalaExtension)
result

def getPluginScalaBinaryVersion: Version = getPluginScalaLibrary.scalaVersion.binaryVersionSuffix
def getPluginScala2BinaryVersion: Version = getPluginScalaLibrary.scala2.binaryVersionSuffix
final def getPluginScalaBinaryVersion: Version = getPluginScalaLibrary.scalaBinaryVersionSuffix
final def getPluginScala2BinaryVersion: Version = getPluginScalaLibrary.scala2BinaryVersionSuffix
final lazy val getPluginScalaLibrary: ScalaLibrary = ScalaLibrary.fromAmbientClasspath(project)

final def testFramework(frameworkClass: Class[? <: Framework]): String =
Expand Down
4 changes: 2 additions & 2 deletions src/main/scala/org/podval/tools/backend/VersionsWriter.scala
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ import java.io.File
// This writes versions of everything into an AsciiDoc file that the documentation uses;
// this way, the versions are guaranteed to be consistent - if this was run ;)
object VersionsWriter:
private val gradleVersion: Version = Version("9.2.1")
private val gradleVersion: Version = Version("9.3.0")

private val versions: Seq[(String, Version)] = Seq(
"plugin" -> Version("0.9.15"),
"plugin" -> Version("0.9.16"),

"gradle" -> gradleVersion,

Expand Down
57 changes: 12 additions & 45 deletions src/main/scala/org/podval/tools/build/ScalaBinaryVersion.scala
Original file line number Diff line number Diff line change
@@ -1,68 +1,35 @@
package org.podval.tools.build

sealed trait ScalaBinaryVersion extends JavaDependency derives CanEqual:
final override def toString: String = versionSuffix.toString
final override def group: String = ScalaBinaryVersion.group
final override def toString: String = prefix.toString
def prefix: Version
final def is(version: Version): Boolean = version.startsWith(prefix)
final override def versionDefault: Version = scalaVersionDefault.version
final def versionSuffix: Version = versionDefault.take(versionSuffixLength)

def versionMajor: Int
def versionSuffixLength: Int
def scalaVersionDefault: ScalaVersion

def isScala3 : Boolean
def isScala2 : Boolean
def isScala213: Boolean
def isScala212: Boolean

object ScalaBinaryVersion:
val group: String = "org.scala-lang"

def forVersion(version: Version): ScalaBinaryVersion =
if version.major == Scala3.versionMajor
then Scala3
else
require(version.major == Scala2.versionMajor)
if version.minor == Scala2_13.versionMinor
then
Scala2_13
else
require(version.minor == Scala2_12.versionMinor)
Scala2_12

def all: Seq[ScalaBinaryVersion] = Seq(Scala3, Scala2_13, Scala2_12)

object Scala3 extends ScalaBinaryVersion:
override def versionMajor: Int = 3
override def versionSuffixLength: Int = 1
override def artifact: String = "scala3-library_3"
override def description: String = "Scala 3 Library."
override val scalaVersionDefault: ScalaVersion = ScalaVersion("3.7.4")
override def isScala3: Boolean = true
override def isScala2: Boolean = false
override def isScala213: Boolean = false
override def isScala212: Boolean = false

private object Scala2:
def versionMajor: Int = 2
override val prefix: Version = Version("3")
override val scalaVersionDefault: ScalaVersion = ScalaVersion("3.8.0")
def scala2VersionDefault: ScalaVersion = Scala2_13.scalaVersionDefault
private val versionLibraryCompiledWithScala3: Version = Version("3.8.0")
def libraryCompiledWithScala2(scalaVersion: ScalaVersion): Boolean = versionLibraryCompiledWithScala3.after(scalaVersion.version)

sealed trait Scala2 extends ScalaBinaryVersion:
final override def versionMajor: Int = Scala2.versionMajor
final override def versionSuffixLength: Int = 2
override def artifact: String = "scala-library"
override def description: String = "Scala 2 Library."
def versionMinor: Int
override def isScala3: Boolean = false
override def isScala2: Boolean = true
final override def artifact: String = "scala-library"
final override def description: String = "Scala 2 Library."

object Scala2_13 extends Scala2:
override def versionMinor: Int = 13
override def isScala213: Boolean = true
override def isScala212: Boolean = false
override val prefix: Version = Version("2.13")
override val scalaVersionDefault: ScalaVersion = ScalaVersion("2.13.18")

object Scala2_12 extends Scala2:
override def versionMinor: Int = 12
override def isScala213: Boolean = false
override def isScala212: Boolean = true
override val prefix: Version = Version("2.12")
override val scalaVersionDefault: ScalaVersion = ScalaVersion("2.12.21")
3 changes: 1 addition & 2 deletions src/main/scala/org/podval/tools/build/ScalaDependency.scala
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,7 @@ trait ScalaDependency extends Dependency:
scalaBackend(backendOverride).artifactNameSuffix(scalaVersion.versionSuffix(isScalaVersionFull))

final override def withScalaVersion(scalaLibrary: ScalaLibrary): ScalaDependency.WithScalaVersion = withScalaVersion(
scalaLibrary.scala3.filter(_ => isPublishedForScala3)
.orElse(Some(scalaLibrary.scala2).filter(_ => isPublishedForScala2))
scalaLibrary.scalaVersion(isPublishedForScala3, isPublishedForScala2)
.getOrElse(throw GradleException(s"Dependency $this is not published for $scalaLibrary."))
)

Expand Down
109 changes: 68 additions & 41 deletions src/main/scala/org/podval/tools/build/ScalaLibrary.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,51 +6,64 @@ import org.podval.tools.gradle.{Artifact, Configurations, GradleClasspath}
import scala.jdk.CollectionConverters.IterableHasAsScala
import java.io.File

final class ScalaLibrary private(
val scala3: Option[ScalaVersion],
val scala2: ScalaVersion
):
override def toString: String = s"ScalaLibrary(scala3=$scala3, scala2=$scala2)"
sealed abstract class ScalaLibrary(val scalaVersion: ScalaVersion):
final def scalaBinaryVersionSuffix: Version = scalaVersion.binaryVersionSuffix
def scala2BinaryVersionSuffix: Version
def isScala3: Boolean

def isScala3: Boolean = scala3.isDefined
def scalaVersion(
isPublishedForScala3: Boolean,
isPublishedForScala2: Boolean
): Option[ScalaVersion]

def scalaVersion: ScalaVersion = scala3.getOrElse(scala2)

def verify(project: Project): Unit =
final def verify(project: Project): Unit =
val runtimeClasspath: Configuration = Configurations.runtimeClasspath(project)
val other: ScalaLibrary = ScalaLibrary.fromClasspath(
project,
source = s"'${runtimeClasspath.getName}'",
classPath = runtimeClasspath.asScala
)
require(
other.scala3.nonEmpty == scala3.nonEmpty,
s"Scala 3 presence changed from ${scala3.nonEmpty} to ${other.scala3.nonEmpty}."
)
if scala3.nonEmpty
then require(
other.scala3.get == scala3.get,
s"Scala 3 version changed from ${scala3.get} to ${other.scala3.get}."
)
else require(
other.scala2 == scala2,
s"Scala 2 version changed from $scala2 to $other.scala2."
)
require(this.toString == other.toString, s"Scala library changed from $this to $other")

object ScalaLibrary:
def fromScalaVersion(scalaVersion: ScalaVersion): ScalaLibrary =
val isScala3: Boolean = scalaVersion.isScala3
new ScalaLibrary(
scala3 = Option.when(isScala3)(scalaVersion),
scala2 = if !isScala3 then scalaVersion else
// For Scala 3, we *approximate* Scala 2;
// this is safe, since the full Scala 2 version
// matters only for dependencies `withScalaVersionFull`,
// and ScalaLibrary constructed here is only used with `FrameworkDescriptor` dependencies,
// which are not `withScalaVersionFull`.
// Scala 2 version used by Scala 3 from 3.0.0 to the current is 2.13:
ScalaBinaryVersion.Scala2_13.scalaVersionDefault
)
final class Scala2(scalaVersion: ScalaVersion /* TODO .Scala2*/) extends ScalaLibrary(scalaVersion):
override def toString: String = s"Scala2Library($scalaVersion)"
override def isScala3: Boolean = false
override def scala2BinaryVersionSuffix: Version = scalaBinaryVersionSuffix

override def scalaVersion(
isPublishedForScala3: Boolean,
isPublishedForScala2: Boolean
): Option[ScalaVersion] = Option.when(isPublishedForScala2)(scalaVersion)

// For Scala 3, we *approximate* Scala 2;
// this is safe, since the full Scala 2 version
// matters only for dependencies `withScalaVersionFull`,
// and ScalaLibrary constructed here is only used with `FrameworkDescriptor` dependencies,
// which are not `withScalaVersionFull`.
// Scala 2 version used by Scala 3 from 3.0.0 to 3.8.0 is 2.13;
// starting with 3.8.0, the version is the same as that of the Scala 3, with the major of 3.

sealed abstract class Scala3(scalaVersion: ScalaVersion/* TODO .Scala3*/) extends ScalaLibrary(scalaVersion):
override def toString: String = s"Scala3Library($scalaVersion, $scala2Version)"
def scala2Version: ScalaVersion /* TODO .Scala2*/
final override def isScala3: Boolean = true
final override def scala2BinaryVersionSuffix: Version = scala2Version.binaryVersionSuffix

final override def scalaVersion(
isPublishedForScala3: Boolean,
isPublishedForScala2: Boolean
): Option[ScalaVersion] =
Option.when(isPublishedForScala3)(scalaVersion)
.orElse(Option.when(isPublishedForScala2)(scala2Version))

final class Scala3LibraryCompiledWithScala2(
scalaVersion: ScalaVersion,
override val scala2Version: ScalaVersion
) extends Scala3(scalaVersion)

final class Scala3LibraryCompiledWithScala3(scalaVersion: ScalaVersion /* TODO .Scala3*/) extends Scala3(scalaVersion):
override def scala2Version: ScalaVersion = ScalaBinaryVersion.Scala3.scala2VersionDefault

def fromImplementationConfiguration(project: Project): ScalaLibrary =
val implementation: Configuration = Configurations.implementation(project)
Expand Down Expand Up @@ -78,26 +91,40 @@ object ScalaLibrary:
find = _.findInClasspath(classPath)
)

def fromScalaVersion(scalaVersion: ScalaVersion): ScalaLibrary =
if scalaVersion.isScala2
then Scala2(scalaVersion)
else
if ScalaBinaryVersion.Scala3.libraryCompiledWithScala2(scalaVersion)
then Scala3LibraryCompiledWithScala2(scalaVersion, ScalaBinaryVersion.Scala3.scala2VersionDefault)
else Scala3LibraryCompiledWithScala3(scalaVersion)

private def apply(
project: Project,
source: String,
isFromClasspath: Boolean,
find: JavaDependency => Option[Dependency.WithVersion]
): ScalaLibrary =
val scala3: Option[Dependency.WithVersion] = find(ScalaBinaryVersion.Scala3 )
// Note: this finds any Scala 2 library, even if it is 2.12, not 2.13:
// Note: this finds any Scala 2 library, even if it is 2.12, not 2.13 - and even if it is 3.8.0 :)
val scala2: Option[Dependency.WithVersion] = find(ScalaBinaryVersion.Scala2_13)

require(scala3.nonEmpty || scala2.nonEmpty, s"No Scala library $source.")
if isFromClasspath then require(scala2.nonEmpty, s"No Scala 2 library $source.")
if !isFromClasspath then require(scala3.isEmpty || scala2.isEmpty, s"Both Scala 3 and Scala 2 library present $source.")

if scala2.isDefined then
def toScalaVersion(withVersion: Dependency.WithVersion): ScalaVersion = ScalaVersion(withVersion.version.version)
new ScalaLibrary(
scala3.map(toScalaVersion),
toScalaVersion(scala2.get)
)
def toScalaVersion(withVersion: Option[Dependency.WithVersion]): ScalaVersion = ScalaVersion(withVersion.get.version.version)
val scala2Version: ScalaVersion = toScalaVersion(scala2)
if scala3.isEmpty
then Scala2(scala2Version)
else
val scala3Version: ScalaVersion = toScalaVersion(scala3)
if ScalaBinaryVersion.Scala3.libraryCompiledWithScala2(scala3Version)
then Scala3LibraryCompiledWithScala2(scala3Version, scala2Version)
else
require(scala2Version == scala3Version)
Scala3LibraryCompiledWithScala3(scala3Version)
else
// When constructing Scala 3 ScalaLibrary `fromImplementationConfiguration`,
// we do not get Scala 2 library dependency - and we need it for dependencies `withScalaVersionFull`;
Expand Down
Loading