Skip to content

Commit

Permalink
wrapping up
Browse files Browse the repository at this point in the history
  • Loading branch information
Greg Zoller committed May 30, 2024
1 parent 0823f7d commit 8490083
Show file tree
Hide file tree
Showing 25 changed files with 443 additions and 604 deletions.
96 changes: 38 additions & 58 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,66 +17,57 @@ on:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}


concurrency:
group: ${{ github.workflow }} @ ${{ github.ref }}
cancel-in-progress: true

jobs:
build:
name: Build and Test
strategy:
matrix:
os: [ubuntu-latest, windows-latest]
scala: [3.3.0]
java: [zulu@8]
os: [ubuntu-latest]
scala: [3.4.2]
java: [zulu@21]
runs-on: ${{ matrix.os }}
timeout-minutes: 60
steps:
- name: Ignore line ending differences in git
if: contains(runner.os, 'windows')
shell: bash
run: git config --global core.autocrlf false

- name: Checkout current branch (full)
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Setup Java (zulu@8)
if: matrix.java == 'zulu@8'
uses: actions/setup-java@v3
- name: Setup Java (zulu@21)
id: setup-java-zulu-21
if: matrix.java == 'zulu@21'
uses: actions/setup-java@v4
with:
distribution: zulu
java-version: 8
java-version: 21
cache: sbt

- name: Cache sbt
uses: actions/cache@v3
with:
path: |
~/.sbt
~/.ivy2/cache
~/.coursier/cache/v1
~/.cache/coursier/v1
~/AppData/Local/Coursier/Cache/v1
~/Library/Caches/Coursier/v1
key: ${{ runner.os }}-sbt-cache-v2-${{ hashFiles('**/*.sbt') }}-${{ hashFiles('project/build.properties') }}
- name: sbt update
if: matrix.java == 'zulu@21' && steps.setup-java-zulu-21.outputs.cache-hit == 'false'
run: sbt +update

- name: Check that workflows are up to date
shell: bash
run: sbt githubWorkflowCheck

- name: Build project
shell: bash
run: sbt '++${{ matrix.scala }}' test
run: sbt '++ ${{ matrix.scala }}' test

- name: Make target directories
if: github.event_name != 'pull_request' && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v'))
shell: bash
run: mkdir -p target project/target

- name: Compress target directories
if: github.event_name != 'pull_request' && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v'))
shell: bash
run: tar cf targets.tar target project/target

- name: Upload target directories
if: github.event_name != 'pull_request' && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v'))
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: target-${{ matrix.os }}-${{ matrix.java }}-${{ matrix.scala }}
path: targets.tar
Expand All @@ -88,44 +79,33 @@ jobs:
strategy:
matrix:
os: [ubuntu-latest]
scala: [3.3.0]
java: [zulu@8]
java: [zulu@21]
runs-on: ${{ matrix.os }}
steps:
- name: Ignore line ending differences in git
if: contains(runner.os, 'windows')
run: git config --global core.autocrlf false

- name: Checkout current branch (full)
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Setup Java (zulu@8)
if: matrix.java == 'zulu@8'
uses: actions/setup-java@v3
- name: Setup Java (zulu@21)
id: setup-java-zulu-21
if: matrix.java == 'zulu@21'
uses: actions/setup-java@v4
with:
distribution: zulu
java-version: 8
java-version: 21
cache: sbt

- name: Cache sbt
uses: actions/cache@v3
with:
path: |
~/.sbt
~/.ivy2/cache
~/.coursier/cache/v1
~/.cache/coursier/v1
~/AppData/Local/Coursier/Cache/v1
~/Library/Caches/Coursier/v1
key: ${{ runner.os }}-sbt-cache-v2-${{ hashFiles('**/*.sbt') }}-${{ hashFiles('project/build.properties') }}

- name: Download target directories (3.3.0)
uses: actions/download-artifact@v3
- name: sbt update
if: matrix.java == 'zulu@21' && steps.setup-java-zulu-21.outputs.cache-hit == 'false'
run: sbt +update

- name: Download target directories (3.4.2)
uses: actions/download-artifact@v4
with:
name: target-${{ matrix.os }}-${{ matrix.java }}-3.3.0
name: target-${{ matrix.os }}-${{ matrix.java }}-3.4.2

- name: Inflate target directories (3.3.0)
- name: Inflate target directories (3.4.2)
run: |
tar xf targets.tar
rm targets.tar
Expand All @@ -136,4 +116,4 @@ jobs:
SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }}
SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }}
PGP_SECRET: ${{ secrets.PGP_SECRET }}
run: sbt '++${{ matrix.scala }}' ci-release
run: sbt ci-release
59 changes: 59 additions & 0 deletions .github/workflows/coveralls.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
name: Coveralls Publish

on:
push:
branches: [main]
tags: ["v*"]

env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}


concurrency:
group: ${{ github.workflow }} @ ${{ github.ref }}
cancel-in-progress: true

jobs:
build:
name: Build and Test
strategy:
matrix:
os: [ubuntu-latest, windows-latest]
scala: [3.4.2]
java: [zulu@21]
runs-on: ${{ matrix.os }}
timeout-minutes: 60
steps:
- name: Ignore line ending differences in git
if: contains(runner.os, 'windows')
shell: bash
run: git config --global core.autocrlf false

- name: Checkout current branch (full)
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Setup Java (zulu@21)
id: setup-java-zulu-21
if: matrix.java == 'zulu@21'
uses: actions/setup-java@v4
with:
distribution: zulu
java-version: 21
cache: sbt

- name: sbt update
if: matrix.java == 'zulu@21' && steps.setup-java-zulu-21.outputs.cache-hit == 'false'
shell: bash
run: sbt +update

- name: Build project
run: sbt '++ ${{ matrix.scala }}' coverage test

- run: sbt '++ ${{ matrix.scala }}' coverageReport

- name: Coveralls
uses: coverallsapp/github-action@v2
with:
git-branch: main
29 changes: 10 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,11 @@
[![Maven Central](https://maven-badges.herokuapp.com/maven-central/co.blocke/scalajack_3/badge.svg)](https://search.maven.org/artifact/co.blocke/scalajack_3/8.0.0/jar)

ScalaJack 8 is an all-new ScalaJack serializer implemenation built on Scala 3. For Scala 2.13 ScalaJack, please use the frozen version 6.2.0. ScalaJack 8 is built
using Scala 3.4.1 on JDK 21 LTS version.
using Scala 3.4.2 on JDK 21 LTS version. This is done to be as current as possible and also because Scala 3.4.2 provides improvements to code test coverage instrumentation.

ScalaJack is a very fast, seamless serialization engine for unstructured data types designed to require a bare minimum of extra code
to serialize a class. ScalaJack supports JSON in its focus on over-the-wire data and message/event transport. (We looked at offering MsgPack support, but to our surprise benchmarks
showed that MsgPack serialization had about 25% slower write performance and 45% slower read performance than JSON, so for now we're sticking with just JSON support.)

Advanced Features:

- Handles tuples
- 'Any' support
- Handles default values for case class fields
- Rich configuration of trait type hint/value
- Supports value classes
- Sealed trait-style enumerations
ScalaJack is a very fast, seamless serialization engine for non-schema data designed to require a bare minimum of extra code
to serialize a class. ScalaJack currently only supports JSON, however when we looked at adding MsgPack support to our great surprise benchmarks
showed that MsgPack serialization had about 25% slower write performance and 45% slower read performance than JSON, so we're sticking with JSON for the time being.

## Use
ScalaJack is extremely simple to use.
Expand All @@ -27,7 +18,7 @@ Include the following in your build.sbt:
```
libraryDependencies ++= Seq("co.blocke" %% "scalajack" % SJ_VERSION)
```
Now you're good to go! Let's use ScalaJack in your project to serialize/de-serialize a case class object into JSON:
Now you're good to go! Let's use ScalaJack in your project to serialize/deserialize a case class object into JSON:
```scala
// File1.scala
case class Person(name: String, age: Int)
Expand All @@ -44,15 +35,15 @@ sjPerson.fromJson(js) // re-constitutes original Person
Couldn't be simpler!

| **NOTE:** Classes must be defined in a different file from where ScalaJack is called.
| This is a Scala macro requirement, not a ScalaJack "ism"
| This is a Scala macro requirement, not a ScalaJack limitation.

### A word about performance...

Compared to pre-8.0 ScalaJack, which used Scala 2.x runtime reflection, ScalaJack is dramatically faster in almost every case. How's this work? ScalaJack 8 uses macros, that at compile-time generate all the serialization code for you (the codecs). It's very much like writing hand-tooled, field-by-field serialization code yourself, except ScalaJack does it at compile-time. Wherever you see ```sjCodecOf``` is where the compiler will generate all the serialization code. **(That also means try not to use sjCodecOf more than once for any given class or you'll generate a lot of redundant code!)**
Compared to pre-8.0 ScalaJack, which used Scala 2.x runtime reflection, ScalaJack is dramatically faster in almost every case. How does this work? ScalaJack 8 uses compile-time macros to generate all the serialization code for you (the codecs). It's very much like writing hand-tooled, field-by-field serialization code yourself, except ScalaJack does it at compile-time. Wherever you see ```sjCodecOf``` is where the compiler will generate all the serialization code. **(That also means you should try not to use sjCodecOf more than once for any given class or you'll generate a lot of redundant code!)**

### Easy codecs
You only need to worry about generating codecs for your top-most level classes. Some serialization libraries require all nested classes in an object hierarchy to be
specifically called out for code generation, which can get pretty burdensome. ScalaJack doesn't require this. For example:
specifically called out for codec generation, which can get pretty burdensome. ScalaJack doesn't require this. For example:

```scala
case class Dog(name: String, numLegs: Int)
Expand All @@ -78,7 +69,7 @@ val js = sjFoo.fromJson(someJson)

In a non-macro program (e.g. something using Scala 2 runtime reflection) let's say you add a new field to class Foo in File1.scala. You naturally expect sbt to re-compile this file, and anything that depends on Foo, and the changes will be picked up in your program, and all will be well.

That's **not** necessarily what happens with macros! Remember, the macro code is run/expnded at compile-time. File2.scala needs to be re-compiled because the macro that gets expanded at sjCodecOf[Foo] needs to be re-generated to pick up your changes to Foo class in File1.scala. **Unfortunately sbt cna't detect up this dependency!** If you don't know any better you'll just re-run your program after a change to File1.scala, like normal, and you'll get a spectacular exception with exotic errors that won't mean much to you. The simple, but non-intuitive, solution is you need to also recompile File2.scala.
That's **not** necessarily what happens with macros! Remember, the macro code is run/expnded at compile-time. File2.scala needs to be re-compiled because the macro that gets expanded at sjCodecOf[Foo] needs to be re-generated to pick up your changes to Foo class in File1.scala. **Unfortunately sbt can't detect this dependency!** If you don't know any better you'll just re-run your program after a change to File1.scala, like normal, and you'll get a spectacular exception with exotic errors that won't mean much to you. The simple, but non-intuitive, solution is you need to also recompile File2.scala.

This means you will be doing more re-compiling with macro-based code than you would without the macros. It's an unfortunate cost of inconvenience, but the payoff is a *dramatic* gain in speed at runtime, and in the case of reflection in Scala 3, using macros is the only way to accomplish reflection, so there really isn't an alternative.

Expand All @@ -97,7 +88,7 @@ This means you will be doing more re-compiling with macro-based code than you wo

### Notes:

* 8.0.0 -- Rebuild on Scala 3.4.1 and major refactor of ScalaJack 7.0
* 8.0.0 -- Rebuild on Scala 3.4.2 and deep refactor of ScalaJack 7.0
* 7.0.3 -- Rebuild on Scala 3.2.1
* 7.0.1 -- GA release of ScalaJack 7 for Scala 3.
* 7.0.0-M2 -- Initial release for Scala 3
2 changes: 1 addition & 1 deletion benchmark/build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ val compilerOptions = Seq(

val circeVersion = "0.15.0-M1"
val scalaTestVersion = "3.2.11"
ThisBuild / scalaVersion := "3.4.1"
ThisBuild / scalaVersion := "3.4.2"

def priorTo2_13(scalaVersion: String): Boolean =
CrossVersion.partialVersion(scalaVersion) match {
Expand Down
25 changes: 12 additions & 13 deletions benchmark/src/main/scala/co.blocke/Benchmark.scala
Original file line number Diff line number Diff line change
Expand Up @@ -44,21 +44,20 @@ trait HandTooledWritingBenchmark {
@OutputTimeUnit(TimeUnit.SECONDS)
class ReadingBenchmark
extends ScalaJackZ.ScalaJackReadingBenchmark
// with CirceZ.CirceReadingBenchmark
// extends JsoniterZ.JsoniterReadingBenchmark
// with ZIOZ.ZIOJsonReadingBenchmark
// with PlayZ.PlayReadingBenchmark
// with ArgonautZ.ArgonautReadingBenchmark
with CirceZ.CirceReadingBenchmark
with JsoniterZ.JsoniterReadingBenchmark
with ZIOZ.ZIOJsonReadingBenchmark
with PlayZ.PlayReadingBenchmark
with ArgonautZ.ArgonautReadingBenchmark

@State(Scope.Thread)
@BenchmarkMode(Array(Mode.Throughput))
@OutputTimeUnit(TimeUnit.SECONDS)
class WritingBenchmark
// extends HandTooledWritingBenchmark
// extends CirceZ.CirceWritingBenchmark
// extends ScalaJackZ.MsgPackWritingBenchmark
extends ScalaJackZ.ScalaJackWritingBenchmark
// extends JsoniterZ.JsoniterWritingBenchmark
// with ZIOZ.ZIOJsonWritingBenchmark
// with PlayZ.PlayWritingBenchmark
// with ArgonautZ.ArgonautWritingBenchmark
extends HandTooledWritingBenchmark
with CirceZ.CirceWritingBenchmark
with ScalaJackZ.ScalaJackWritingBenchmark
with JsoniterZ.JsoniterWritingBenchmark
with ZIOZ.ZIOJsonWritingBenchmark
with PlayZ.PlayWritingBenchmark
with ArgonautZ.ArgonautWritingBenchmark
17 changes: 13 additions & 4 deletions build.sbt
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import org.typelevel.sbt.gha.JavaSpec.Distribution.Zulu
import scoverage.ScoverageKeys._

lazy val isCI = sys.env.get("CI").contains("true")

inThisBuild(List(
Expand All @@ -19,7 +21,8 @@ inThisBuild(List(

name := "scalajack"
ThisBuild / organization := "co.blocke"
ThisBuild / scalaVersion := "3.4.1"
ThisBuild / scalaVersion := "3.4.2"
ThisBuild / githubWorkflowScalaVersions := Seq("3.4.2")

lazy val root = project
.in(file("."))
Expand All @@ -35,7 +38,7 @@ lazy val root = project
Test / parallelExecution := false,
scalafmtOnCompile := !isCI,
libraryDependencies ++= Seq(
"co.blocke" %% "scala-reflection" % "2.0.6",
"co.blocke" %% "scala-reflection" % "2.0.8",
"org.apache.commons" % "commons-text" % "1.11.0",
"io.github.kitlangton" %% "neotype" % "0.0.9",
"org.scalatest" %% "scalatest" % "3.2.17" % Test,
Expand All @@ -44,7 +47,7 @@ lazy val root = project
)
)

ThisBuild / githubWorkflowJavaVersions := Seq(JavaSpec(Zulu, "8"))
ThisBuild / githubWorkflowJavaVersions := Seq(JavaSpec(Zulu, "21"))
ThisBuild / githubWorkflowOSes := Seq("ubuntu-latest")
ThisBuild / githubWorkflowPublishTargetBranches := Seq(
RefPredicate.Equals(Ref.Branch("main")),
Expand Down Expand Up @@ -77,7 +80,13 @@ lazy val compilerOptions = Seq(
"-feature",
"-language:implicitConversions",
"-deprecation",
// "-explain",
// "-explain",'
"-coverage-exclude-files",
".*SJConfig.*",
"-coverage-exclude-classlikes",
".*internal.*",
"-coverage-exclude-classlikes",
".*AnyWriter",
"-encoding",
"utf8"
)
Expand Down
3 changes: 2 additions & 1 deletion project/plugins.sbt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
addSbtPlugin("co.blocke" % "gitflow-packager" % "0.1.32")
addSbtPlugin("com.github.sbt" % "sbt-ci-release" % "1.5.11")
addSbtPlugin("org.typelevel" % "sbt-typelevel-sonatype-ci-release" % "0.5.0-M6")
addSbtPlugin("org.typelevel" % "sbt-typelevel-github-actions" % "0.4.16")
addSbtPlugin("org.typelevel" % "sbt-typelevel-github-actions" % "0.7.1")
addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.5.2")
addSbtPlugin("org.scoverage" % "sbt-scoverage" % "2.0.12")
2 changes: 0 additions & 2 deletions src/main/scala/co.blocke.scalajack/ScalaJack.scala
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,6 @@ case class ScalaJack[T](jsonCodec: JsonCodec[T]):

object ScalaJack:

def apply[A](implicit a: ScalaJack[A]): ScalaJack[A] = a

// ----- Use default JsonConfig
inline def sjCodecOf[T]: ScalaJack[T] = ${ codecOfImpl[T] }
def codecOfImpl[T: Type](using Quotes): Expr[ScalaJack[T]] =
Expand Down
Loading

0 comments on commit 8490083

Please sign in to comment.