Skip to content

Commit

Permalink
Configure shared and test modules to cross-build scala 3 (#676)
Browse files Browse the repository at this point in the history
* Update build setup

* setup scala3 on shared + test

* FIx build update

* Fix enum macros

* Update CI script

* Fix CI cross building

* Add back runtime check for scala 3

* Restrict false EnumType generation in scala 3

* Update sbt-scoverage to 2.0.9

* Add comment
  • Loading branch information
RustedBones authored Nov 7, 2023
1 parent ca4c0d6 commit e3eec4a
Show file tree
Hide file tree
Showing 15 changed files with 535 additions and 174 deletions.
45 changes: 31 additions & 14 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,12 @@ jobs:
strategy:
matrix:
os: [ubuntu-latest]
scala: [2.13, 2.12]
scala: [3, 2.13, 2.12]
java: [corretto@17, corretto@11]
project: [rootJVM]
exclude:
- scala: 3
java: corretto@11
- scala: 2.12
java: corretto@11
runs-on: ${{ matrix.os }}
Expand Down Expand Up @@ -71,16 +74,20 @@ jobs:
run: sbt githubWorkflowCheck

- name: Build project
if: matrix.scala == '2.13.12' && matrix.java == 'corretto@11'
run: sbt '++ ${{ matrix.scala }}' coverage test coverageAggregate
if: matrix.scala == '2.13' && matrix.java == 'corretto@11'
run: sbt 'project ${{ matrix.project }}' '++ ${{ matrix.scala }}' coverage test coverageAggregate

- name: Upload coverage report
if: matrix.scala == '2.13.12' && matrix.java == 'corretto@11'
if: matrix.scala == '2.13' && matrix.java == 'corretto@11'
run: 'bash <(curl -s https://codecov.io/bash)'

- name: Build project
if: '!(matrix.scala == ''2.13.12'' && matrix.java == ''corretto@11'')'
run: sbt '++ ${{ matrix.scala }}' test
if: '!(matrix.scala == ''2.13'' && matrix.java == ''corretto@11'' || matrix.scala == ''3'')'
run: sbt 'project ${{ matrix.project }}' '++ ${{ matrix.scala }}' test

- name: Build project
if: matrix.scala == '3'
run: sbt 'project ${{ matrix.project }}' '++ ${{ matrix.scala }}' shared/test test/test

- name: Make target directories
if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main')
Expand All @@ -94,7 +101,7 @@ jobs:
if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main')
uses: actions/upload-artifact@v3
with:
name: target-${{ matrix.os }}-${{ matrix.java }}-${{ matrix.scala }}
name: target-${{ matrix.os }}-${{ matrix.java }}-${{ matrix.scala }}-${{ matrix.project }}
path: targets.tar

publish:
Expand Down Expand Up @@ -138,22 +145,32 @@ jobs:
if: matrix.java == 'corretto@11' && steps.setup-java-corretto-11.outputs.cache-hit == 'false'
run: sbt +update

- name: Download target directories (2.13)
- name: Download target directories (3, rootJVM)
uses: actions/download-artifact@v3
with:
name: target-${{ matrix.os }}-${{ matrix.java }}-3-rootJVM

- name: Inflate target directories (3, rootJVM)
run: |
tar xf targets.tar
rm targets.tar
- name: Download target directories (2.13, rootJVM)
uses: actions/download-artifact@v3
with:
name: target-${{ matrix.os }}-${{ matrix.java }}-2.13
name: target-${{ matrix.os }}-${{ matrix.java }}-2.13-rootJVM

- name: Inflate target directories (2.13)
- name: Inflate target directories (2.13, rootJVM)
run: |
tar xf targets.tar
rm targets.tar
- name: Download target directories (2.12)
- name: Download target directories (2.12, rootJVM)
uses: actions/download-artifact@v3
with:
name: target-${{ matrix.os }}-${{ matrix.java }}-2.12
name: target-${{ matrix.os }}-${{ matrix.java }}-2.12-rootJVM

- name: Inflate target directories (2.12)
- name: Inflate target directories (2.12, rootJVM)
run: |
tar xf targets.tar
rm targets.tar
Expand Down Expand Up @@ -225,7 +242,7 @@ jobs:
- name: Submit Dependencies
uses: scalacenter/sbt-dependency-submission@v2
with:
modules-ignore: test_2.13 test_2.12 magnolify_2.13 magnolify_2.12
modules-ignore: test_3 test_2.13 test_2.12 magnolify_3 magnolify_2.13 magnolify_2.12 magnolify_3 magnolify_2.13 magnolify_2.12 magnolify_3 magnolify_2.13 magnolify_2.12
configs-ignore: test scala-tool scala-doc-tool test-internal

validate-steward:
Expand Down
51 changes: 34 additions & 17 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import sbtprotoc.ProtocPlugin.ProtobufConfig
import com.typesafe.tools.mima.core._

val magnoliaScala2Version = "1.1.6"
val magnoliaScala3Version = "1.1.4"
val magnoliaScala3Version = "1.3.4"

val algebirdVersion = "0.13.10"
val avroVersion = Option(sys.props("avro.version")).getOrElse("1.11.2")
Expand Down Expand Up @@ -103,19 +103,23 @@ ThisBuild / developers := List(
val scala3 = "3.3.0"
val scala213 = "2.13.12"
val scala212 = "2.12.18"
val defaultScala = scala213
val scalaDefault = scala213

// github actions
val java17 = JavaSpec.corretto("17")
val java11 = JavaSpec.corretto("11")
val defaultJava = java11
val javaDefault = java11
val coverageCond = Seq(
s"matrix.scala == '$defaultScala'",
s"matrix.java == '${defaultJava.render}'"
s"matrix.scala == '${CrossVersion.binaryScalaVersion(scalaDefault)}'",
s"matrix.java == '${javaDefault.render}'"
).mkString(" && ")

ThisBuild / scalaVersion := defaultScala
ThisBuild / crossScalaVersions := Seq(scala213, scala212)
val scala3Cond = "matrix.scala == '3'"
val scala3Projects = List(
"shared",
"test"
)
ThisBuild / scalaVersion := scalaDefault
ThisBuild / crossScalaVersions := Seq(scala3, scala213, scala212)
ThisBuild / githubWorkflowTargetBranches := Seq("main")
ThisBuild / githubWorkflowJavaVersions := Seq(java17, java11)
ThisBuild / githubWorkflowBuild := Seq(
Expand All @@ -129,7 +133,16 @@ ThisBuild / githubWorkflowBuild := Seq(
name = Some("Upload coverage report"),
cond = Some(coverageCond)
),
WorkflowStep.Sbt(List("test"), name = Some("Build project"), cond = Some(s"!($coverageCond)"))
WorkflowStep.Sbt(
List("test"),
name = Some("Build project"),
cond = Some(s"!($coverageCond || $scala3Cond)")
),
WorkflowStep.Sbt(
scala3Projects.map(p => s"$p/test"),
name = Some("Build project"),
cond = Some(scala3Cond)
)
)
ThisBuild / githubWorkflowAddedJobs ++= Seq(
WorkflowJob(
Expand All @@ -142,8 +155,8 @@ ThisBuild / githubWorkflowAddedJobs ++= Seq(
name = Some("Build project")
)
),
scalas = List(defaultScala),
javas = List(defaultJava)
scalas = List(scalaDefault),
javas = List(javaDefault)
)
)

Expand Down Expand Up @@ -179,13 +192,16 @@ lazy val keepExistingHeader =
val commonSettings = Seq(
tlFatalWarnings := false,
tlJdkRelease := Some(8),
// So far most projects do no support scala 3
crossScalaVersions := Seq(scala213, scala212),
scalaVersion := scalaDefault,
scalacOptions ++= (CrossVersion.partialVersion(scalaVersion.value) match {
case Some((3, _)) =>
Seq(
// required by magnolia for accessing default values
"-Xretain-trees",
"-Yretain-trees",
// tolerate some nested macro expansion
"-Ymax-inlines",
"-Xmax-inlines",
"64"
)
case Some((2, 13)) =>
Expand Down Expand Up @@ -232,11 +248,9 @@ val commonSettings = Seq(
Test / classLoaderLayeringStrategy := ClassLoaderLayeringStrategy.Flat
)

lazy val root = project
.in(file("."))
lazy val root = tlCrossRootProject
.enablePlugins(NoPublishPlugin)
.settings(
commonSettings,
name := "magnolify",
description := "A collection of Magnolia add-on modules"
)
Expand All @@ -262,6 +276,7 @@ lazy val shared = project
.in(file("shared"))
.settings(
commonSettings,
crossScalaVersions := Seq(scala3, scala213, scala212),
moduleName := "magnolify-shared",
description := "Shared code for Magnolify"
)
Expand All @@ -271,8 +286,9 @@ lazy val test = project
.in(file("test"))
.enablePlugins(NoPublishPlugin)
.dependsOn(shared)
.settings(commonSettings)
.settings(
commonSettings,
crossScalaVersions := Seq(scala3, scala213, scala212),
libraryDependencies ++= Seq(
"org.scalameta" %% "munit-scalacheck" % munitVersion % Test,
"org.typelevel" %% "cats-core" % catsVersion % Test
Expand Down Expand Up @@ -557,6 +573,7 @@ lazy val jmh: Project = project
)
.settings(
commonSettings,
crossScalaVersions := Seq(scalaDefault),
Jmh / classDirectory := (Test / classDirectory).value,
Jmh / dependencyClasspath := (Test / dependencyClasspath).value,
// rewire tasks, so that 'jmh:run' automatically invokes 'jmh:compile'
Expand Down
2 changes: 1 addition & 1 deletion project/plugins.sbt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
addSbtPlugin("org.typelevel" % "sbt-typelevel" % "0.6.0")
addSbtPlugin("com.thesamet" % "sbt-protoc" % "1.0.6")
addSbtPlugin("org.scoverage" % "sbt-scoverage" % "2.0.8")
addSbtPlugin("org.scoverage" % "sbt-scoverage" % "2.0.9")
addSbtPlugin("pl.project13.scala" % "sbt-jmh" % "0.4.4")
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* Copyright 2023 Spotify AB
*
* 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 magnolify.shared

import scala.reflect.macros.whitebox

object AnnotationTypeMacros {
def annotationTypeMacro[T: c.WeakTypeTag](c: whitebox.Context): c.Tree = {
import c.universe._
val wtt = weakTypeTag[T]
val pre = wtt.tpe.asInstanceOf[TypeRef].pre

// Scala 2.12 & 2.13 macros seem to handle annotations differently
// Scala annotation works in both but Java annotations only works in 2.13
val saType = typeOf[scala.annotation.StaticAnnotation]
val jaType = typeOf[java.lang.annotation.Annotation]
// Annotation for Scala enumerations are on the outer object
val annotated = if (pre <:< typeOf[scala.Enumeration]) pre else wtt.tpe
val trees = annotated.typeSymbol.annotations.map(_.tree).collect {
case t @ q"new $n(..$args)" if t.tpe <:< saType && !(t.tpe <:< jaType) =>
// FIXME `t.tree` should work but somehow crashes the compiler
q"new $n(..$args)"
}

// Get Java annotations via reflection
val j = q"classOf[${annotated.typeSymbol.asClass}].getAnnotations.toList"
val annotations = q"_root_.scala.List(..$trees) ++ $j"

q"_root_.magnolify.shared.AnnotationType[$wtt]($annotations)"
}
}

trait AnnotationTypeCompanionMacros {
implicit def gen[T]: AnnotationType[T] = macro AnnotationTypeMacros.annotationTypeMacro[T]
}
64 changes: 64 additions & 0 deletions shared/src/main/scala-2/magnolify/shared/EnumTypeDerivation.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/*
* Copyright 2023 Spotify AB
*
* 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 magnolify.shared

import magnolia1.{CaseClass, SealedTrait}

import scala.annotation.{implicitNotFound, nowarn}

trait EnumTypeDerivation {
type Typeclass[T] = EnumType[T]

// EnumType can only be split into objects with fixed name
// Avoid invalid ADT derivation involving products by requiring
// implicit EnumValue type-class in magnolia join
// see https://github.com/softwaremill/magnolia/issues/267
@implicitNotFound("Cannot derive EnumType.EnumValue. EnumType only works for sum types")
trait EnumValue[T]

implicit def genEnumValue[T]: EnumValue[T] = macro EnumTypeMacros.genEnumValueMacro[T]

@nowarn
def join[T: EnumValue](caseClass: CaseClass[Typeclass, T]): Typeclass[T] = {
val n = caseClass.typeName.short
val ns = caseClass.typeName.owner
EnumType.create(
n,
ns,
List(n),
caseClass.annotations.toList,
_ => caseClass.rawConstruct(Nil)
)
}

def split[T](sealedTrait: SealedTrait[Typeclass, T]): Typeclass[T] = {
val n = sealedTrait.typeName.short
val ns = sealedTrait.typeName.owner
val subs = sealedTrait.subtypes.map(_.typeclass)
val values = subs.flatMap(_.values).sorted.toList
val annotations = (sealedTrait.annotations ++ subs.flatMap(_.annotations)).toList
EnumType.create(
n,
ns,
values,
annotations,
// it is ok to use the inefficient find here because it will be called only once
// and cached inside an instance of EnumType
v => subs.find(_.name == v).get.from(v)
)
}
}
Loading

0 comments on commit e3eec4a

Please sign in to comment.