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

Configure shared and test modules to cross-build scala 3 #676

Merged
merged 10 commits into from
Nov 7, 2023
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
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
Copy link
Contributor

Choose a reason for hiding this comment

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

What exactly targets.tar will contain?

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 @@
/*
Copy link
Contributor

Choose a reason for hiding this comment

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

all code in scala-2 was just copied?

* 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
Loading