Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[proposal] Literal Scala versions #16

Open
wants to merge 17 commits into
base: main
Choose a base branch
from
Open
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
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -90,11 +90,11 @@ jobs:

- name: Make target directories
if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main')
run: mkdir -p target .js/target .jvm/target .native/target lib/.js/target lib/.jvm/target project/target
run: mkdir -p target macros/.jvm/target testkit/.js/target .js/target core/.js/target macros/.js/target core/.jvm/target .jvm/target .native/target lib/.js/target testkit/.jvm/target lib/.jvm/target project/target

- name: Compress target directories
if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main')
run: tar cf targets.tar target .js/target .jvm/target .native/target lib/.js/target lib/.jvm/target project/target
run: tar cf targets.tar target macros/.jvm/target testkit/.js/target .js/target core/.js/target macros/.js/target core/.jvm/target .jvm/target .native/target lib/.js/target testkit/.jvm/target lib/.jvm/target project/target

- name: Upload target directories
if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main')
Expand Down
9 changes: 7 additions & 2 deletions .scalafmt.conf
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
version = 3.6.0

runner.dialect = scala213
runner.dialect = Scala213Source3
fileOverride {
"glob:**/src/{main,test}/scala-3/**" {
runner.dialect = Scala3
}
}

preset = defaultWithAlign

Expand All @@ -10,4 +15,4 @@ indent {
callSite = 2
defnSite = 2
extendSite = 2
}
}
70 changes: 65 additions & 5 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ name := "scalac-options-root"

ThisBuild / tlBaseVersion := "0.1"

// Mima validations against the prevous releases are intentionally disabled
// since the current changes are not (yet) supposed to be binary-compatible.
ThisBuild / tlMimaPreviousVersions := Set.empty

ThisBuild / organization := "org.typelevel"
ThisBuild / organizationName := "Typelevel"
ThisBuild / startYear := Some(2022)
Expand All @@ -24,19 +28,75 @@ ThisBuild / crossScalaVersions := Seq(
Scala3
) // There's no reason not to cross-publish

lazy val root = tlCrossRootProject.aggregate(lib)
lazy val root = tlCrossRootProject.aggregate(core, macros, lib, testkit)

lazy val literallyVersion = "1.1.0"
lazy val munitVersion = "0.7.29"
lazy val scalacheckVersion = "1.17.0"

lazy val core = crossProject(JVMPlatform, JSPlatform)
.crossType(CrossType.Pure)
.in(file("core"))
.enablePlugins(NoPublishPlugin)
.settings(
name := "scalac-options-core"
)

lazy val macros = crossProject(JVMPlatform, JSPlatform)
.crossType(CrossType.Pure)
.in(file("macros"))
.dependsOn(core)
.enablePlugins(NoPublishPlugin)
.settings(
name := "scalac-options-macros",
scalacOptions := {
if (tlIsScala3.value)
scalacOptions.value.filterNot(_ == "-source:3.0-migration") :+ "-source:3.1"
else
scalacOptions.value
},
libraryDependencies ++= Seq(
"org.typelevel" %%% "literally" % literallyVersion
Copy link
Member

@DavidGregory084 DavidGregory084 Oct 6, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a way for us to check that literally doesn't end up on our users' runtime classpath when depending upon this library? Perhaps I'm being paranoid 😛

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm... I like your idea, but is it feasible in principle? I'll try to figure out that.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't necessarily mean an automated way to do this but it would be nice to create a project that depends on this library and check the runtimeClasspath in sbt 😃

) ++ {
if (tlIsScala3.value) Nil
else
List("org.scala-lang" % "scala-reflect" % scalaVersion.value % Provided)
}
)

lazy val lib = crossProject(JVMPlatform, JSPlatform)
.crossType(CrossType.Pure)
.in(file("lib"))
.dependsOn(core, macros)
.settings(
name := "scalac-options"
)
.settings {
def projectMappings(cproj: sbtcrossproject.CrossProject) = Def.taskDyn {
cproj.projects(crossProjectPlatform.value) / Compile / packageBin / mappings
}

Compile / packageBin / mappings ++=
projectMappings(core).value ++ projectMappings(macros).value
}
.settings {
def projectMappings(cproj: sbtcrossproject.CrossProject) = Def.taskDyn {
cproj.projects(crossProjectPlatform.value) / Compile / packageSrc / mappings
}

Compile / packageSrc / mappings ++=
projectMappings(core).value ++ projectMappings(macros).value
}

lazy val testkit = crossProject(JVMPlatform, JSPlatform)
.crossType(CrossType.Pure)
.in(file("testkit"))
.dependsOn(lib)
.settings(
name := "scalac-options",
name := "scalac-options-testkit",
libraryDependencies ++= Seq(
"org.scalameta" %%% "munit" % munitVersion % Test,
"org.scalacheck" %%% "scalacheck" % scalacheckVersion % Test,
"org.scalameta" %%% "munit-scalacheck" % munitVersion % Test
"org.scalacheck" %%% "scalacheck" % scalacheckVersion,
"org.scalameta" %%% "munit" % munitVersion % Test,
"org.scalameta" %%% "munit-scalacheck" % munitVersion % Test
)
)
101 changes: 101 additions & 0 deletions core/src/main/scala/org/typelevel/scalacoptions/ScalaVersion.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/*
* Copyright 2022 Typelevel
*
* Licensed 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.typelevel.scalacoptions

import org.typelevel.scalacoptions.internal.Parser

import scala.Ordering.Implicits._
import scala.collection.immutable

abstract sealed case class ScalaVersion private (major: Long, minor: Long, patch: Long) {

def isBetween(addedVersion: ScalaVersion, removedVersion: ScalaVersion): Boolean =
this >= addedVersion && this < removedVersion
}

object ScalaVersion {
private def apply(major: Long, minor: Long, patch: Long): ScalaVersion =
new ScalaVersion(major, minor, patch) {}

lazy val knownVersions: immutable.SortedSet[ScalaVersion] = {
val bld = immutable.SortedSet.newBuilder[ScalaVersion]

def bldPatches(major: Long, minor: Long, lastPatch: Long): Unit =
for (patch <- 0L to lastPatch)
bld += ScalaVersion(major, minor, patch)

bldPatches(2, 11, 12)
bldPatches(2, 12, 17)
bldPatches(2, 13, 9)
bldPatches(3, 0, 2)
bldPatches(3, 1, 3)
bldPatches(3, 2, 0)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It could cause problems that this list must be maintained by hand.

I wonder if we can come up with some way that Scala Steward could recognise these versions and bump them, e.g. by using a String here instead of major,minor,patch?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd rather not get into a position where we have to release a new version of this library (and all downstream tooling based on it) for every new Scala version.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call out. I tried to avoid enumerating all known versions by hand, but for the sake of to be understood by scala-steward we could create a list of all known versions and put it into a separate file:

private [scalacoptions] KnownScalaVersions {
  protected val knownVersionStrings = Seq(
    "2.12.0",
    "2.12.1",
    ...
    "3.2.0"
  )
}

and then use it from the ScalaVersion object:

object ScalaVersion extends KnownScalaVersions {
  val knownVersions = knownVersionStrings.map(fromString.unsafe)
}

or something like that.
Would it work, wdyt?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we just leave it open ended? So unless a series is known to be EOL it will accept not-yet-released versions?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@armanbilge just to make sure I understand you correctly,

you would prefer to avoid having scala-steward involved into that completely, wouldn't you?
I.e. rather than having scala-steward involved, you're suggesting to make ScalaVersion more flexible and supporting versions like "2.13+", "2.13.9+", etc?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you would prefer to avoid having scala-steward involved into that completely, wouldn't you?

Right, it's not that I care about Scala Steward specifically, but rather that we would have to release a new version of this library (and thus a new version of all the plugins that use it) for every new Scala version.

you're suggesting to make ScalaVersion more flexible and supporting versions like "2.13+", "2.13.9+", etc?

I don't think that's necessary. But I do think it should accept 2.13.20 today even though that's not yet released.


bld.result()
}

def from(major: Long, minor: Long, patch: Long): Option[ScalaVersion] = {
val candidate = ScalaVersion(major, minor, patch)
if (knownVersions.contains(candidate)) Some(candidate) else None
}

def unsafeFrom(major: Long, minor: Long, patch: Long): ScalaVersion =
from(major, minor, patch).getOrElse(throw invalidScalaVersionError(s"$major.$minor.$patch"))

object fromString {
def apply(s: String): Option[ScalaVersion] =
Parser.parseGenericVersion(s).flatMap(Function.tupled(from _))
def unapply(s: String): Option[ScalaVersion] = apply(s)

def unsafe(s: String): ScalaVersion = apply(s).getOrElse(throw invalidScalaVersionError(s))
}

@deprecated("use literal `sver\"2.11.0\"` instead", "<TBD>")
val V2_11_0 = ScalaVersion(2, 11, 0)
@deprecated("use literal `sver\"2.11.11\"` instead", "<TBD>")
val V2_11_11 = ScalaVersion(2, 11, 11)
@deprecated("use literal `sver\"2.12.0\"` instead", "<TBD>")
val V2_12_0 = ScalaVersion(2, 12, 0)
@deprecated("use literal `sver\"2.12.2\"` instead", "<TBD>")
val V2_12_2 = ScalaVersion(2, 12, 2)
@deprecated("use literal `sver\"2.12.5\"` instead", "<TBD>")
val V2_12_5 = ScalaVersion(2, 12, 5)
@deprecated("use literal `sver\"2.12.13\"` instead", "<TBD>")
val V2_12_13 = ScalaVersion(2, 12, 13)
@deprecated("use literal `sver\"2.13.0\"` instead", "<TBD>")
val V2_13_0 = ScalaVersion(2, 13, 0)
@deprecated("use literal `sver\"2.13.2\"` instead", "<TBD>")
val V2_13_2 = ScalaVersion(2, 13, 2)
@deprecated("use literal `sver\"2.13.3\"` instead", "<TBD>")
val V2_13_3 = ScalaVersion(2, 13, 3)
@deprecated("use literal `sver\"2.13.4\"` instead", "<TBD>")
val V2_13_4 = ScalaVersion(2, 13, 4)
@deprecated("use literal `sver\"2.13.5\"` instead", "<TBD>")
val V2_13_5 = ScalaVersion(2, 13, 5)
@deprecated("use literal `sver\"2.13.6\"` instead", "<TBD>")
val V2_13_6 = ScalaVersion(2, 13, 6)
@deprecated("use literal `sver\"3.0.0\"` instead", "<TBD>")
val V3_0_0 = ScalaVersion(3, 0, 0)
@deprecated("use literal `sver\"3.1.0\"` instead", "<TBD>")
val V3_1_0 = ScalaVersion(3, 1, 0)

implicit lazy val scalaVersionOrdering: Ordering[ScalaVersion] =
Ordering.by(version => (version.major, version.minor, version.patch))

private def invalidScalaVersionError(s: String) =
new IllegalArgumentException(s"invalid Scala version: $s")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* Copyright 2022 Typelevel
*
* Licensed 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.typelevel.scalacoptions.internal

private[scalacoptions] object Parser {
private[internal] lazy val genericVersionRe = """^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$""".r

private object asLong {
def unapply(s: String): Option[Long] = {
try {
Some(java.lang.Long.parseUnsignedLong(s))
} catch {
case _: NumberFormatException => None
}
}
}

def parseGenericVersion(s: String): Option[(Long, Long, Long)] = s match {
case genericVersionRe(asLong(major), asLong(minor), asLong(patch)) =>
Some((major, minor, patch))
case _ =>
None
}
}
44 changes: 0 additions & 44 deletions lib/src/main/scala/org/typelevel/scalacoptions/ScalaVersion.scala

This file was deleted.

Loading