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

Cross-build to Scala 3 #557

Merged
merged 11 commits into from
Jan 21, 2021
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
9 changes: 0 additions & 9 deletions .scala-steward.conf
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,6 @@ updates.pin = [
// https://github.com/akka/akka/blob/master/project/Dependencies.scala#L24
{ groupId = "com.fasterxml.jackson.core", version = "2.10." }
{ groupId = "com.fasterxml.jackson.datatype", version = "2.10." }
// "It is not forward binary compatible with 1.0.x: libraries compiled with 1.1.0 cannot be used with 1.0.x."
// https://users.scala-lang.org/t/announcing-scala-js-1-1-0/6053/3?u=sjrd
{ groupId = "org.scala-js", artifactId = "sbt-scalajs", version = "1.0." }
{ groupId = "org.scala-js", artifactId = "scalajs-compiler", version = "1.0." }
{ groupId = "org.scala-js", artifactId = "scalajs-library", version = "1.0." }
{ groupId = "org.scala-js", artifactId = "scalajs-test-bridge", version = "1.0." }
// Scalatest 3.2.x pulls in ScalaJs 1.1
{ groupId = "org.scalatest", artifactId = "scalatest", version = "3.1." }
{ groupId = "org.scalatestplus", artifactId = "scalacheck-1-14", version = "3.1." }
]

commits.message = "${artifactName} ${nextVersion} (was ${currentVersion})"
5 changes: 3 additions & 2 deletions .scalafmt.conf
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ assumeStandardLibraryStripMargin = true
danglingParentheses = true
docstrings = JavaDoc
maxColumn = 120
project.excludeFilters += core/play/src/main/scala/play/core/hidden/ObjectMappings.scala
#scalafmt does not support Scala 3 syntax yet (`inline`)
project.excludeFilters += scala-3
project.git = true
rewrite.rules = [ AvoidInfix, ExpandImportSelectors, RedundantParens, SortModifiers, PreferCurlyFors ]
rewrite.neverInfix.excludeFilters = [
Expand All @@ -19,4 +20,4 @@ version = 2.3.2

literals.long=Upper
literals.float=Upper
literals.double=Upper
literals.double=Upper
10 changes: 8 additions & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ jobs:
- name: "Run tests with Scala 2.13 and AdoptOpenJDK 11"
script: scripts/test-code.sh
env:
- SCALA_VERSION=2.13.2
- SCALA_VERSION=2.13.4
- TRAVIS_JDK=11

- name: "Run tests with Scala 2.12 and AdoptOpenJDK 8"
Expand All @@ -38,7 +38,13 @@ jobs:
- name: "Run tests with Scala 2.13 and AdoptOpenJDK 8"
script: scripts/test-code.sh
env:
- SCALA_VERSION=2.13.2
- SCALA_VERSION=2.13.4
- TRAVIS_JDK=8

- name: "Run tests with Scala 3 and AdoptOpenJDK 8"
script: scripts/test-code.sh
env:
- SCALA_VERSION=3.0.0-M3
- TRAVIS_JDK=8

- stage: publish
Expand Down
154 changes: 99 additions & 55 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,18 @@ import sbt._
import sbt.util._
import sbt.io.Path._

import com.typesafe.tools.mima.plugin.MimaKeys.mimaPreviousArtifacts
import com.typesafe.tools.mima.core._

import sbtcrossproject.CrossPlugin.autoImport.crossProject
import sbtcrossproject.CrossType

resolvers ++= DefaultOptions.resolvers(snapshot = true)

val specs2 = Seq(
"org.specs2" %% "specs2-core" % "4.10.5" % Test,
"org.specs2" %% "specs2-junit" % "4.10.5" % Test,
)
def specs2(scalaVersion: String) =
Seq(
"org.specs2" %% "specs2-core" % "4.10.5" % Test,
"org.specs2" %% "specs2-junit" % "4.10.5" % Test,
).map(_.withDottyCompat(scalaVersion))

val jacksonDatabindVersion = "2.10.5.1"
val jacksonDatabind = Seq(
Expand All @@ -34,7 +35,7 @@ val joda = Seq(
"joda-time" % "joda-time" % "2.10.8"
)

def jsonDependencies(scalaVersion: String) = Seq(
def scalaReflect(scalaVersion: String) = Seq(
"org.scala-lang" % "scala-reflect" % scalaVersion
)

Expand All @@ -43,10 +44,22 @@ def jsonDependencies(scalaVersion: String) = Seq(
// Do not check for previous JS artifacts for upgrade to Scala.js 1.0 because no sjs1 artifacts exist
def playJsonMimaSettings = Seq(
mimaPreviousArtifacts := ((crossProjectPlatform.?.value, previousStableVersion.value) match {
case _ if isDotty.value => Set.empty // no releases for Scala 3 yet
case (Some(JSPlatform), Some("2.8.1")) => Set.empty
case (_, Some(previousVersion)) => Set(organization.value %%% moduleName.value % previousVersion)
case _ => throw new Error("Unable to determine previous version")
})
}),
mimaBinaryIssueFilters ++= Seq(
// MergedOWrites is private
ProblemFilters.exclude[Problem]("play.api.libs.json.OWrites#MergedOWrites*"),
// [error] * method unapply(play.api.libs.json.JsBoolean)scala.Option in object play.api.libs.json.JsBoolean has a different result type in current version, where it is scala.Some rather than scala.Option
// [error] * static method unapply(play.api.libs.json.JsBoolean)scala.Option in class play.api.libs.json.JsBoolean has a different result type in current version, where it is scala.Some rather than scala.Option
// Some is a subtype, which is safe because return types are covariant.
ProblemFilters.exclude[IncompatibleResultTypeProblem]("play.api.libs.json.JsBoolean.unapply"),
// [error] * in current version, classes mixing play.api.libs.json.DefaultWrites need be recompiled to wire to the new static mixin forwarder method all super calls to method enumNameWrites()play.api.libs.json.Writes
// Despite not being `sealed` or documented, I don't think DefaultWrites was intended to be extended by users.
ProblemFilters.exclude[NewMixinForwarderProblem]("play.api.libs.json.DefaultWrites.enumNameWrites"),
),
)

// Workaround for https://github.com/scala-js/scala-js/issues/2378
Expand All @@ -64,6 +77,7 @@ val javacSettings = Seq(
)

val scalacOpts = Seq(
"-language:higherKinds",
"-target:jvm-1.8",
"-Ywarn-unused:imports",
"-Xlint:nullary-unit",
Expand All @@ -74,10 +88,14 @@ val scalacOpts = Seq(

val silencerVersion = "1.7.1"

libraryDependencies in ThisBuild ++= Seq(
compilerPlugin(("com.github.ghik" % "silencer-plugin" % silencerVersion).cross(CrossVersion.full)),
("com.github.ghik" % "silencer-lib" % silencerVersion % Provided).cross(CrossVersion.full)
)
libraryDependencies in ThisBuild ++= {
if (isDotty.value) Nil
else
Seq(
compilerPlugin(("com.github.ghik" % "silencer-plugin" % silencerVersion).cross(CrossVersion.full)),
("com.github.ghik" % "silencer-lib" % silencerVersion % Provided).cross(CrossVersion.full)
)
}

// Customise sbt-dynver's behaviour to make it work with tags which aren't v-prefixed
dynverVTagPrefix in ThisBuild := false
Expand Down Expand Up @@ -105,11 +123,11 @@ lazy val commonSettings = Def.settings(
),
headerLicense := Some(HeaderLicense.Custom(s"Copyright (C) 2009-2020 Lightbend Inc. <https://www.lightbend.com>")),
scalaVersion := Dependencies.Scala212,
crossScalaVersions := Seq(Dependencies.Scala212, Dependencies.Scala213),
crossScalaVersions := Seq(Dependencies.Scala212, Dependencies.Scala213, Dependencies.Scala3),
javacOptions in Compile ++= javacSettings,
javacOptions in Test ++= javacSettings,
javacOptions in (Compile, compile) ++= Seq("-target", "1.8"), // sbt #1785, avoids passing to javadoc
scalacOptions ++= scalacOpts,
scalacOptions ++= (if (isDotty.value) Nil else scalacOpts),
scalacOptions in (Compile, doc) ++= Seq(
// Work around 2.12 bug which prevents javadoc in nested java classes from compiling.
"-no-java-comments",
Expand All @@ -136,28 +154,46 @@ lazy val `play-json` = crossProject(JVMPlatform, JSPlatform)
.enablePlugins(Omnidoc, Publish, Playdoc)
.configs(Docs)
.settings(
commonSettings ++ playJsonMimaSettings ++ Seq(
libraryDependencies ++= jsonDependencies(scalaVersion.value) ++ Seq(
"org.scalatest" %%% "scalatest" % "3.1.2" % Test, // 3.1.2 is the last version that uses Scala.js 1.0.x
"org.scalatestplus" %%% "scalacheck-1-14" % "3.1.2.0" % Test,
"org.scalacheck" %%% "scalacheck" % "1.14.3" % Test,
"com.chuusai" %% "shapeless" % "2.3.3" % Test,
"org.scala-lang" % "scala-compiler" % scalaVersion.value % "provided"
),
commonSettings ++ playJsonMimaSettings ++ Def.settings(
libraryDependencies ++= (if (isDotty.value) Nil else scalaReflect(scalaVersion.value)),
libraryDependencies ++= Seq(
"org.scalatest" %%% "scalatest" % "3.2.3" % Test,
"org.scalatestplus" %%% "scalacheck-1-14" % "3.2.2.0" % Test,
"org.scalacheck" %%% "scalacheck" % "1.14.3" % Test,
"com.chuusai" %% "shapeless" % "2.3.3" % Test,
).map(_.withDottyCompat(scalaVersion.value)),
libraryDependencies += {
if (isDotty.value)
"org.scala-lang" %% "scala3-compiler" % scalaVersion.value % Provided
else
"org.scala-lang" % "scala-compiler" % scalaVersion.value % Provided
},
libraryDependencies ++=
(CrossVersion.partialVersion(scalaVersion.value) match {
case Some((2, 13)) => Seq()
case Some((3, _)) => Nil
case _ => Seq(compilerPlugin(("org.scalamacros" % "paradise" % "2.1.1").cross(CrossVersion.full)))
}),
unmanagedSourceDirectories in Compile += {
//val sourceDir = (sourceDirectory in Compile).value
// ^ gives jvm/src/main, for some reason
val sourceDir = baseDirectory.value.getParentFile / "shared/src/main"
CrossVersion.partialVersion(scalaVersion.value) match {
case Some((2, n)) if n >= 13 => sourceDir / "scala-2.13+"
case _ => sourceDir / "scala-2.13-"
case Some((2, n)) if n < 13 => sourceDir / "scala-2.13-"
case _ => sourceDir / "scala-2.13+"
}
},
Seq((Compile, "main"), (Test, "test")).map {
case (conf, dir) =>
conf / unmanagedSourceDirectories ++= {
val sourceDir = baseDirectory.value.getParentFile / s"shared/src/$dir"
CrossVersion.partialVersion(scalaVersion.value) match {
case Some((2, _)) => List(sourceDir / "scala-2")
case Some((3, _)) => List(sourceDir / "scala-3")
case _ => Nil
}
},
},
sourceGenerators in Compile += Def.task {
val dir = (sourceManaged in Compile).value

Expand All @@ -167,45 +203,46 @@ lazy val `play-json` = crossProject(JVMPlatform, JSPlatform)
.map {
i =>
def commaSeparated(s: Int => String) = 1.to(i).map(s).mkString(", ")
def newlineSeparated(s: Int => String) = 1.to(i).map(s).mkString("\n")
def newlineSeparated(s: Int => String) = 1.to(i).map(s).mkString("\n ")
dwijnand marked this conversation as resolved.
Show resolved Hide resolved
val writerTypes = commaSeparated(j => s"T$j: Writes")
val readerTypes = commaSeparated(j => s"T$j: Reads")
val typeTuple = commaSeparated(j => s"T$j")
val written = commaSeparated(j => s"implicitly[Writes[T$j]].writes(x._$j)")
val readValues = commaSeparated(j => s"t$j")
val readGenerators = newlineSeparated(j => s"t$j <- implicitly[Reads[T$j]].reads(arr(${j - 1}))")

(s"""
implicit def Tuple${i}W[$writerTypes]: Writes[Tuple${i}[$typeTuple]] = Writes[Tuple${i}[$typeTuple]](
x => JsArray(Array($written))
)
""", s"""
implicit def Tuple${i}R[$readerTypes]: Reads[Tuple${i}[$typeTuple]] = Reads[Tuple${i}[$typeTuple]]{
case JsArray(arr) if arr.size == $i =>
for{
$readGenerators
} yield Tuple$i($readValues)

case _ =>
JsError(Seq(JsPath() -> Seq(JsonValidationError("Expected array of $i elements"))))
}
""")
val writes =
dwijnand marked this conversation as resolved.
Show resolved Hide resolved
s""" implicit def Tuple${i}W[$writerTypes]: Writes[Tuple$i[$typeTuple]] = Writes[Tuple${i}[$typeTuple]](
| x => JsArray(Array($written))
| )""".stripMargin

val reads =
s""" implicit def Tuple${i}R[$readerTypes]: Reads[Tuple$i[$typeTuple]] = Reads[Tuple${i}[$typeTuple]] {
| case JsArray(arr) if arr.size == $i =>
| for {
| $readGenerators
| } yield Tuple$i($readValues)
|
| case _ =>
| JsError(Seq(JsPath() -> Seq(JsonValidationError("Expected array of $i elements"))))
| }""".stripMargin

(writes, reads)
}
.unzip

IO.write(
file,
s"""
package play.api.libs.json

trait GeneratedReads {
${reads.mkString("\n")}
}

trait GeneratedWrites{
${writes.mkString("\n")}
}
"""
s"""package play.api.libs.json
|
|trait GeneratedReads {
|${reads.mkString("\n")}
|}
|
|trait GeneratedWrites{
|${writes.mkString("\n")}
|}
|""".stripMargin
)
Seq(file)
}.taskValue
Expand All @@ -217,18 +254,18 @@ lazy val `play-jsonJS` = `play-json`.js

lazy val `play-jsonJVM` = `play-json`.jvm.settings(
libraryDependencies ++=
jacksons ++ specs2 :+ (
jacksons ++ specs2(scalaVersion.value) :+ (
"ch.qos.logback" % "logback-classic" % "1.2.3" % Test
),
unmanagedSourceDirectories in Test ++= (baseDirectory.value.getParentFile.getParentFile / "docs/manual/working/scalaGuide" ** "code").get
unmanagedSourceDirectories in Test ++= (docsP / PlayDocsKeys.scalaManualSourceDirectories).value,
dwijnand marked this conversation as resolved.
Show resolved Hide resolved
)

lazy val `play-json-joda` = project
.in(file("play-json-joda"))
.enablePlugins(Omnidoc, Publish)
.settings(
commonSettings ++ playJsonMimaSettings ++ Seq(
libraryDependencies ++= joda ++ specs2
libraryDependencies ++= joda ++ specs2(scalaVersion.value),
)
)
.dependsOn(`play-jsonJVM`)
Expand All @@ -252,15 +289,22 @@ lazy val benchmarks = project
.settings(publish / skip := true)
.dependsOn(`play-jsonJVM`)

val docsP = LocalProject("docs")
cchantep marked this conversation as resolved.
Show resolved Hide resolved
lazy val docs = project
.in(file("docs"))
.enablePlugins(PlayDocsPlugin)
.disablePlugins(MimaPlugin)
.configs(Docs)
.settings(
publish / skip := true,
libraryDependencies ++= specs2,
PlayDocsKeys.scalaManualSourceDirectories := (baseDirectory.value / "manual" / "working" / "scalaGuide" ** "code").get,
libraryDependencies ++= specs2(scalaVersion.value),
PlayDocsKeys.validateDocs := (if (isDotty.value) () else PlayDocsKeys.validateDocs.value),
PlayDocsKeys.scalaManualSourceDirectories := {
val base = baseDirectory.value / "manual" / "working" / "scalaGuide"
val code = (base ** "code").get
if (isDotty.value) code
else code ++ (base ** "code-2").get
},
PlayDocsKeys.resources += {
val apiDocs = (doc in (`play-jsonJVM`, Compile)).value
// Copy the docs to a place so they have the correct api/scala prefix
Expand Down
14 changes: 7 additions & 7 deletions docs/manual/working/scalaGuide/main/json/ScalaJsonAutomated.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,16 +30,16 @@ And a complete example of automatically parsing JSON to a case class is:

The [value classes](https://docs.scala-lang.org/overviews/core/value-classes.html) are also supported. Given the following value class, based on a `String` value:

@[valueClass](code/ScalaJsonAutomatedSpec.scala)
@[valueClass](code-2/Scala2JsonAutomatedSpec.scala)

Then it's also possible to generate a `Reads[IdText]` using the following macro (as `String` is already supported):

@[value-reads](code/ScalaJsonAutomatedSpec.scala)
@[value-reads](code-2/Scala2JsonAutomatedSpec.scala)

As for case classes, similar macros exists for a `Writes[T]` or a `Format[T]`:

@[value-writes](code/ScalaJsonAutomatedSpec.scala)
@[value-format](code/ScalaJsonAutomatedSpec.scala)
@[value-writes](code-2/Scala2JsonAutomatedSpec.scala)
@[value-format](code-2/Scala2JsonAutomatedSpec.scala)

> Note: To be able to access JSON from `request.body.asJson`, the request must have a `Content-Type` header of `application/json`. You can relax this constraint by using the [[`tolerantJson` body parser|ScalaBodyParsers#Choosing-an-explicit-body-parser]].

Expand All @@ -57,15 +57,15 @@ Case classes automatically meet these requirements. For custom classes or traits

A trait can also supported, if and only if it's a sealed one and if the sub-types comply with the previous requirements:

@[model3](code/ScalaJsonAutomatedSpec.scala)
@[model1](code-2/Scala2JsonAutomatedSpec.scala)

The JSON representation for instances of a sealed family includes a discriminator field, which specify the effective sub-type (a text field, with default name `_type`).

@[trait-representation](code/ScalaJsonAutomatedSpec.scala)
@[trait-representation](code-2/Scala2JsonAutomatedSpec.scala)

Then the macros are able generate `Reads[T]`, `OWrites[T]` or `OFormat[T]`.

@[auto-JSON-sealed-trait](code/ScalaJsonAutomatedSpec.scala)
@[auto-JSON-sealed-trait](code-2/Scala2JsonAutomatedSpec.scala)

## Custom Naming Strategies

Expand Down
Loading