-
Notifications
You must be signed in to change notification settings - Fork 1.5k
Added Scala 3 support #1904
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
Added Scala 3 support #1904
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -58,7 +58,12 @@ require(!scalaVersions.isNullOrEmpty()) { | |
| } | ||
|
|
||
| scalaVersions?.forEach { version -> | ||
| require(version.matches(Regex("\\d\\.\\d{2}"))) { "Scala version '$version' must be in the format X.YY" } | ||
| require(version.matches(Regex("^[23].*"))) { "Scala version '$version' not supported." } | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is to ensure the bom is correctly configured. Where as Scala 2.11, 2.12, 2.13 are all major releases. Scala 3 has gone for the traditional Major.Minor.Patch formatting. This means the postfix for artifacts for scala 3 is just |
||
| if (version.startsWith("3")) { | ||
| require(version.matches(Regex("^3$"))) { "Scala version '$version' must be in the format X" } | ||
| } else { | ||
| require(version.matches(Regex("\\d\\.\\d{2}"))) { "Scala version '$version' must be in the format X.YY" } | ||
| } | ||
| } | ||
| /* | ||
| * Apply the Java Platform plugin to create the BOM | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,72 @@ | ||
| # Scala Bson library | ||
|
|
||
| The `bson-scala` project provides Scala-idiomatic wrappers for the Java Bson library. | ||
| It currently supports: **Scala 2.11**, **Scala 2.12**, **Scala 2.13**, and **Scala 3**. | ||
|
|
||
| ## Scala Versions | ||
|
|
||
| Supported Scala versions and their exact version numbers are defined in [`gradle.properties`](../gradle.properties): | ||
|
|
||
| - `supportedScalaVersions` — the list of supported Scala versions | ||
| - `defaultScalaVersion` — the version used when no `-PscalaVersion` flag is provided (currently `2.13`) | ||
|
|
||
| ## Build Configuration | ||
|
|
||
| The Scala source set configuration, compiler options, and dependency wiring are all handled in [`buildSrc/src/main/kotlin/project/scala.gradle.kts`](../buildSrc/src/main/kotlin/project/scala.gradle.kts). | ||
|
|
||
| ## Library Dependencies | ||
|
|
||
| Scala library and test dependencies for each version are defined in [`gradle/libs.versions.toml`](../gradle/libs.versions.toml). Look for entries prefixed with `scala-` in the `[versions]`, `[libraries]`, and `[bundles]` sections. | ||
|
|
||
| ## Directory Layout | ||
|
|
||
| Source code is organized into version-specific directories. | ||
| Shared code goes in the common `scala` directory, while version-specific code goes in the appropriate directory: | ||
|
|
||
| ``` | ||
| src/main/ | ||
| ├── scala/ # Shared code (all Scala versions) | ||
| ├── scala-2/ # Scala 2 only (2.11, 2.12 and 2.13) | ||
| ├── scala-2.13/ # Scala 2.13 only | ||
| ├── scala-2.13-/ # Scala 2.12 & 2.11 | ||
| ├── scala-3/ # Scala 3 only | ||
| ``` | ||
|
|
||
| Test code also supports the same directory structure. | ||
| The source sets for each Scala version are configured in [`buildSrc/src/main/kotlin/project/scala.gradle.kts`](../buildSrc/src/main/kotlin/project/scala.gradle.kts). | ||
| When adding new code, place it in the most general directory that applies. Only use a version-specific directory when the code requires syntax or APIs unique to that version. | ||
|
|
||
| ## Code Formatting (Spotless) | ||
|
|
||
| Spotless defaults to **Scala 2.13** formatting rules. This means code in shared directories (`scala/`, `scala-2/`) is formatted with the 2.13 scalafmt configuration. | ||
|
|
||
| For **Scala 3 specific code**, the `bson-scala/build.gradle.kts` shows how to configure Spotless to use a Scala 3 scalafmt config. It targets only files in `**/scala-3/**` and uses a separate `config/scala/scalafmt-3.conf`: | ||
|
|
||
| ```kotlin | ||
| if (scalaVersion.equals("3")) { | ||
| spotless { | ||
| scala { | ||
| clearSteps() | ||
| target("**/scala-3/**") | ||
| scalafmt("3.10.7").configFile(rootProject.file("config/scala/scalafmt-3.conf")) | ||
| } | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| Use this pattern in other `build.gradle.kts` files if they also contain Scala 3 specific code. | ||
|
|
||
| ## Testing | ||
|
|
||
| By default, tests run against Scala 2.13. To test against a specific Scala version, pass the `-PscalaVersion` flag: | ||
|
|
||
| ```bash | ||
| # Test bson-scala with Scala 3 | ||
| ./gradlew :bson-scala:scalaCheck -PscalaVersion=3 | ||
|
|
||
| # Test bson-scala with Scala 2.12 | ||
| ./gradlew :bson-scala:scalaCheck -PscalaVersion=2.12 | ||
|
|
||
| # Test bson-scala with the default (2.13) | ||
| ./gradlew :bson-scala:scalaCheck | ||
| ``` |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -15,9 +15,12 @@ | |
| */ | ||
| import ProjectExtensions.configureJarManifest | ||
| import ProjectExtensions.configureMavenPublication | ||
| import ProjectExtensions.scalaVersion | ||
|
|
||
| plugins { id("project.scala") } | ||
|
|
||
| val scalaVersion: String = project.scalaVersion() | ||
|
|
||
| base.archivesName.set("mongo-scala-bson") | ||
|
|
||
| dependencies { api(project(path = ":bson", configuration = "default")) } | ||
|
|
@@ -35,3 +38,13 @@ configureJarManifest { | |
| attributes["Bundle-SymbolicName"] = "org.mongodb.scala.mongo-scala-bson" | ||
| attributes["Import-Package"] = "!scala.*,*" | ||
| } | ||
|
|
||
| if (scalaVersion.equals("3")) { | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As you cannot have two different spotless scala tasks with different config. This change clears the default steps for scala 3 and only formats the Scala3 code. |
||
| spotless { | ||
| scala { | ||
| clearSteps() | ||
| target("**/scala-3/**") | ||
| scalafmt("3.10.7").configFile(rootProject.file("config/scala/scalafmt-3.conf")) | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -187,7 +187,7 @@ trait MacroCodec[T] extends Codec[T] { | |
| currentType match { | ||
| case BsonType.DOCUMENT => readDocument(reader, decoderContext, clazz, typeArgs) | ||
| case BsonType.ARRAY => readArray(reader, decoderContext, clazz, typeArgs) | ||
| case BsonType.NULL => | ||
| case BsonType.NULL => | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Scalafmt changes only and a move to |
||
| reader.readNull() | ||
| null.asInstanceOf[V] // scalastyle:ignore | ||
| case _ => registry.get(clazz).decode(reader, decoderContext) | ||
|
|
@@ -239,12 +239,13 @@ trait MacroCodec[T] extends Codec[T] { | |
| if (typeArgs.isEmpty) { | ||
| reader.skipValue() | ||
| } else { | ||
| map += (name -> readValue( | ||
| reader, | ||
| decoderContext, | ||
| typeArgs.head, | ||
| typeArgs.tail | ||
| )) | ||
| map += | ||
| (name -> readValue( | ||
| reader, | ||
| decoderContext, | ||
| typeArgs.head, | ||
| typeArgs.tail | ||
| )) | ||
| } | ||
| } | ||
| reader.readEndDocument() | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,140 @@ | ||
| /* | ||
| * Copyright 2008-present MongoDB, Inc. | ||
| * | ||
| * 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.mongodb.scala.bson.codecs | ||
|
|
||
| import scala.annotation.compileTimeOnly | ||
| import scala.language.experimental.macros | ||
| import scala.language.implicitConversions | ||
|
|
||
| import org.bson.codecs.Codec | ||
| import org.bson.codecs.configuration.{ CodecProvider, CodecRegistry } | ||
|
|
||
| import org.mongodb.scala.bson.codecs.macrocodecs.{ CaseClassCodec, CaseClassProvider } | ||
|
|
||
| /** | ||
| * Macro based Codecs | ||
| * | ||
| * Allows the compile time creation of Codecs for case classes. | ||
| * | ||
| * The recommended approach is to use the implicit [[Macros.createCodecProvider[T](clazz:Class[T])*]] method to help build a codecRegistry: | ||
| * ``` | ||
| * import org.mongodb.scala.bson.codecs.Macros.createCodecProvider | ||
| * import org.bson.codecs.configuration.CodecRegistries.{fromRegistries, fromProviders} | ||
| * | ||
| * case class Contact(phone: String) | ||
| * case class User(_id: Int, username: String, age: Int, hobbies: List[String], contacts: List[Contact]) | ||
| * | ||
| * val codecRegistry = fromRegistries(fromProviders(classOf[User], classOf[Contact]), MongoClient.DEFAULT_CODEC_REGISTRY) | ||
| * ``` | ||
| * | ||
| * @since 2.0 | ||
| */ | ||
| object Macros { | ||
|
|
||
| /** | ||
| * Creates a CodecProvider for a case class. | ||
| * | ||
| * `Option[T]` fields set to `None` are encoded as BSON null. Use [[createCodecProviderIgnoreNone]] to omit them instead. | ||
| * | ||
| * @tparam T the case class to create a Codec from | ||
| * @return the CodecProvider for the case class | ||
| */ | ||
| inline def createCodecProvider[T](): CodecProvider = | ||
| ${ CaseClassProvider.createCodecProviderEncodeNone[T] } | ||
|
|
||
| /** | ||
| * Creates a CodecProvider for a case class using the given class to represent the case class. | ||
| * | ||
| * `Option[T]` fields set to `None` are encoded as BSON null. Use [[createCodecProviderIgnoreNone]] to omit them instead. | ||
| * | ||
| * @param clazz the clazz that is the case class | ||
| * @tparam T the case class to create a Codec from | ||
| * @return the CodecProvider for the case class | ||
| */ | ||
| inline implicit def createCodecProvider[T](clazz: Class[T]): CodecProvider = | ||
| ${ CaseClassProvider.createCodecProviderEncodeNone[T] } | ||
|
|
||
| /** | ||
| * Creates a CodecProvider for a case class where `Option[T]` fields set to `None` are omitted from the BSON output | ||
| * entirely (the field is not written). Use [[createCodecProvider]] to encode `None` as BSON null instead. | ||
| * | ||
| * @tparam T the case class to create a Codec from | ||
| * @return the CodecProvider for the case class | ||
| * @since 2.1 | ||
| */ | ||
| inline def createCodecProviderIgnoreNone[T](): CodecProvider = | ||
| ${ CaseClassProvider.createCodecProviderIgnoreNone[T] } | ||
|
|
||
| /** | ||
| * Creates a CodecProvider for a case class where `Option[T]` fields set to `None` are omitted from the BSON output | ||
| * entirely (the field is not written). Use [[createCodecProvider]] to encode `None` as BSON null instead. | ||
| * | ||
| * @param clazz the clazz that is the case class | ||
| * @tparam T the case class to create a Codec from | ||
| * @return the CodecProvider for the case class | ||
| * @since 2.1 | ||
| */ | ||
| inline def createCodecProviderIgnoreNone[T](clazz: Class[T]): CodecProvider = | ||
| ${ CaseClassProvider.createCodecProviderIgnoreNone[T] } | ||
|
|
||
| /** | ||
| * Creates a Codec for a case class using a default `CodecRegistry`. | ||
| * | ||
| * `Option[T]` fields set to `None` are encoded as BSON null. Use [[createCodecIgnoreNone]] to omit them instead. | ||
| * | ||
| * @tparam T the case class to create a Codec from | ||
| * @return the Codec for the case class | ||
| */ | ||
| inline def createCodec[T](): Codec[T] = | ||
| ${ CaseClassCodec.createCodecBasicCodecRegistryEncodeNone[T] } | ||
|
|
||
| /** | ||
| * Creates a Codec for a case class using the provided `CodecRegistry`. | ||
| * | ||
| * `Option[T]` fields set to `None` are encoded as BSON null. Use [[createCodecIgnoreNone]] to omit them instead. | ||
| * | ||
| * @param codecRegistry the Codec Registry to use | ||
| * @tparam T the case class to create a codec from | ||
| * @return the Codec for the case class | ||
| */ | ||
| inline def createCodec[T](codecRegistry: CodecRegistry): Codec[T] = | ||
| ${ CaseClassCodec.createCodecEncodeNone[T]('codecRegistry) } | ||
|
|
||
| /** | ||
| * Creates a Codec for a case class using a default `CodecRegistry`, where `Option[T]` fields set to `None` are | ||
| * omitted from the BSON output entirely. Use [[createCodec]] to encode `None` as BSON null instead. | ||
| * | ||
| * @tparam T the case class to create a Codec from | ||
| * @return the Codec for the case class | ||
| * @since 2.1 | ||
| */ | ||
| inline def createCodecIgnoreNone[T](): Codec[T] = | ||
| ${ CaseClassCodec.createCodecBasicCodecRegistryIgnoreNone[T] } | ||
|
|
||
| /** | ||
| * Creates a Codec for a case class using the provided `CodecRegistry`, where `Option[T]` fields set to `None` are | ||
| * omitted from the BSON output entirely. Use [[createCodec]] to encode `None` as BSON null instead. | ||
| * | ||
| * @param codecRegistry the Codec Registry to use | ||
| * @tparam T the case class to create a codec from | ||
| * @return the Codec for the case class | ||
| * @since 2.1 | ||
| */ | ||
| inline def createCodecIgnoreNone[T](codecRegistry: CodecRegistry): Codec[T] = | ||
| ${ CaseClassCodec.createCodecIgnoreNone[T]('codecRegistry) } | ||
|
|
||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Scala 3 currently supports Java 8.
However, with Java 8 some of the test Models caused scalatest-Junit to fail with invalid class name.
Unfortunately, the fix is to upgrade scalatest but doing that limits the base version of the SDK - so I opted not test against Java 8, rather than set a hard base of 17.