diff --git a/mill-iw-support/.mill-version b/mill-iw-support/.mill-version new file mode 100644 index 0000000..54dbed4 --- /dev/null +++ b/mill-iw-support/.mill-version @@ -0,0 +1 @@ +0.12.10 diff --git a/mill-iw-support/.mill-version b/mill-iw-support/.mill-version new file mode 100644 index 0000000..54dbed4 --- /dev/null +++ b/mill-iw-support/.mill-version @@ -0,0 +1 @@ +0.12.10 diff --git a/mill-iw-support/.scalafmt.conf b/mill-iw-support/.scalafmt.conf new file mode 100644 index 0000000..ee7753a --- /dev/null +++ b/mill-iw-support/.scalafmt.conf @@ -0,0 +1,2 @@ +version = "3.7.15" +runner.dialect = scala213 \ No newline at end of file diff --git a/mill-iw-support/.mill-version b/mill-iw-support/.mill-version new file mode 100644 index 0000000..54dbed4 --- /dev/null +++ b/mill-iw-support/.mill-version @@ -0,0 +1 @@ +0.12.10 diff --git a/mill-iw-support/.scalafmt.conf b/mill-iw-support/.scalafmt.conf new file mode 100644 index 0000000..ee7753a --- /dev/null +++ b/mill-iw-support/.scalafmt.conf @@ -0,0 +1,2 @@ +version = "3.7.15" +runner.dialect = scala213 \ No newline at end of file diff --git a/mill-iw-support/README.md b/mill-iw-support/README.md index 4986e5c..9bb1ba8 100644 --- a/mill-iw-support/README.md +++ b/mill-iw-support/README.md @@ -28,8 +28,45 @@ IWMillDeps.tapirCore // ivy"com.softwaremill.sttp.tapir::tapir-core:1.11.16" // Use dependency groups -IWMillDeps.useZIO() // Core ZIO dependencies with test framework -IWMillDeps.useZIOAll() // Comprehensive ZIO stack +IWMillDeps.zioCore // List of core ZIO dependencies +IWMillDeps.zioAll // List of all ZIO dependencies +``` + +### IWBomModule + +Bill of Materials (BOM) module for centralized dependency management: + +```scala +import mill._ +import works.iterative.mill._ + +// Define a BOM module +object bom extends IWBomModule { + def publishVersion = "0.1.0-SNAPSHOT" + + def pomSettings = PomSettings( + // Standard POM settings + ) + + // Override with custom dependencies if needed + override def depManagement = T { + super.depManagement() ++ Agg( + ivy"com.example::custom-lib:1.0.0" + ) + } +} + +// Use the BOM in modules +object myModule extends IWScalaModule { + // Reference the BOM module + override def bomModuleDeps = Seq(bom) + + // No need to specify versions - they come from the BOM + def ivyDeps = super.ivyDeps() ++ Agg( + ivy"dev.zio::zio", + ivy"dev.zio::zio-json" + ) +} ``` ### IWScalaModule @@ -44,10 +81,18 @@ // Use default Scala version (3.6.3) // and standard compiler options with SemanticDB enabled - def ivyDeps = super.ivyDeps() ++ IWMillDeps.useZIO() + def ivyDeps = super.ivyDeps() ++ Agg( + ivy"${IWMillDeps.zio}", + ivy"${IWMillDeps.zioJson}" + ) // Standard test module configuration - object test extends Tests with IWTests + object test extends ScalaTests with TestModule.ZioTest { + def ivyDeps = super.ivyDeps() ++ Agg( + ivy"${IWMillDeps.zioTest}", + ivy"${IWMillDeps.zioTestSbt}" + ) + } } ``` @@ -96,6 +141,51 @@ } ``` +## Dependency Management Approaches + +This library provides two approaches for dependency management: + +### 1. IWMillDeps (String Constants) + +Use the IWMillDeps object to access dependency strings with correct versions: + +```scala +def ivyDeps = super.ivyDeps() ++ Agg( + ivy"${IWMillDeps.zio}", + ivy"${IWMillDeps.zioJson}" +) +``` + +### 2. BOM (Bill of Materials) + +Use the IWBomModule to centralize dependency versions: + +```scala +// Define a BOM module +object bom extends IWBomModule { + def publishVersion = "0.1.0-SNAPSHOT" + def pomSettings = PomSettings(...) +} + +// Use the BOM in modules +object myModule extends IWScalaModule { + override def bomModuleDeps = Seq(bom) + + // No need to specify versions + def ivyDeps = super.ivyDeps() ++ Agg( + ivy"dev.zio::zio", + ivy"dev.zio::zio-json" + ) +} +``` + +The BOM approach offers these advantages: +- Enforces consistent versions across modules +- Simplifies version updates +- Controls transitive dependency versions +- Makes build files cleaner (no version strings) +- Can be published as a separate artifact + ## Examples See the `example` directory for a complete working example. diff --git a/mill-iw-support/.mill-version b/mill-iw-support/.mill-version new file mode 100644 index 0000000..54dbed4 --- /dev/null +++ b/mill-iw-support/.mill-version @@ -0,0 +1 @@ +0.12.10 diff --git a/mill-iw-support/.scalafmt.conf b/mill-iw-support/.scalafmt.conf new file mode 100644 index 0000000..ee7753a --- /dev/null +++ b/mill-iw-support/.scalafmt.conf @@ -0,0 +1,2 @@ +version = "3.7.15" +runner.dialect = scala213 \ No newline at end of file diff --git a/mill-iw-support/README.md b/mill-iw-support/README.md index 4986e5c..9bb1ba8 100644 --- a/mill-iw-support/README.md +++ b/mill-iw-support/README.md @@ -28,8 +28,45 @@ IWMillDeps.tapirCore // ivy"com.softwaremill.sttp.tapir::tapir-core:1.11.16" // Use dependency groups -IWMillDeps.useZIO() // Core ZIO dependencies with test framework -IWMillDeps.useZIOAll() // Comprehensive ZIO stack +IWMillDeps.zioCore // List of core ZIO dependencies +IWMillDeps.zioAll // List of all ZIO dependencies +``` + +### IWBomModule + +Bill of Materials (BOM) module for centralized dependency management: + +```scala +import mill._ +import works.iterative.mill._ + +// Define a BOM module +object bom extends IWBomModule { + def publishVersion = "0.1.0-SNAPSHOT" + + def pomSettings = PomSettings( + // Standard POM settings + ) + + // Override with custom dependencies if needed + override def depManagement = T { + super.depManagement() ++ Agg( + ivy"com.example::custom-lib:1.0.0" + ) + } +} + +// Use the BOM in modules +object myModule extends IWScalaModule { + // Reference the BOM module + override def bomModuleDeps = Seq(bom) + + // No need to specify versions - they come from the BOM + def ivyDeps = super.ivyDeps() ++ Agg( + ivy"dev.zio::zio", + ivy"dev.zio::zio-json" + ) +} ``` ### IWScalaModule @@ -44,10 +81,18 @@ // Use default Scala version (3.6.3) // and standard compiler options with SemanticDB enabled - def ivyDeps = super.ivyDeps() ++ IWMillDeps.useZIO() + def ivyDeps = super.ivyDeps() ++ Agg( + ivy"${IWMillDeps.zio}", + ivy"${IWMillDeps.zioJson}" + ) // Standard test module configuration - object test extends Tests with IWTests + object test extends ScalaTests with TestModule.ZioTest { + def ivyDeps = super.ivyDeps() ++ Agg( + ivy"${IWMillDeps.zioTest}", + ivy"${IWMillDeps.zioTestSbt}" + ) + } } ``` @@ -96,6 +141,51 @@ } ``` +## Dependency Management Approaches + +This library provides two approaches for dependency management: + +### 1. IWMillDeps (String Constants) + +Use the IWMillDeps object to access dependency strings with correct versions: + +```scala +def ivyDeps = super.ivyDeps() ++ Agg( + ivy"${IWMillDeps.zio}", + ivy"${IWMillDeps.zioJson}" +) +``` + +### 2. BOM (Bill of Materials) + +Use the IWBomModule to centralize dependency versions: + +```scala +// Define a BOM module +object bom extends IWBomModule { + def publishVersion = "0.1.0-SNAPSHOT" + def pomSettings = PomSettings(...) +} + +// Use the BOM in modules +object myModule extends IWScalaModule { + override def bomModuleDeps = Seq(bom) + + // No need to specify versions + def ivyDeps = super.ivyDeps() ++ Agg( + ivy"dev.zio::zio", + ivy"dev.zio::zio-json" + ) +} +``` + +The BOM approach offers these advantages: +- Enforces consistent versions across modules +- Simplifies version updates +- Controls transitive dependency versions +- Makes build files cleaner (no version strings) +- Can be published as a separate artifact + ## Examples See the `example` directory for a complete working example. diff --git a/mill-iw-support/build.sc b/mill-iw-support/build.sc index 5df80ae..2ed832c 100644 --- a/mill-iw-support/build.sc +++ b/mill-iw-support/build.sc @@ -2,20 +2,33 @@ import mill.scalalib._ import mill.scalalib.publish._ import mill.scalalib.scalafmt._ +import mill.scalalib.TestModule.Utest -object `mill-iw-support` extends Cross[IWMillSupportModule]("2.13.16", "3.6.3") +/** Main module for the mill-iw-support library. Built for Scala 2.13 which is + * compatible with Mill. + */ +object core extends ScalaModule with ScalafmtModule with PublishModule { + val millVersion = "0.12.10" -class IWMillSupportModule(val crossScalaVersion: String) extends CrossScalaModule with ScalafmtModule with PublishModule { + def scalaVersion = "2.13.16" + + def artifactName = "mill-iw-support" + def publishVersion = "0.1.0-SNAPSHOT" def pomSettings = PomSettings( description = "Iterative Works Mill Support Library", organization = "works.iterative", - url = "https://github.com/iterative-works/mill-iw-support", + url = "https://github.com/iterative-works/iw-project-support", licenses = Seq(License.MIT), - versionControl = VersionControl.github("iterative-works", "mill-iw-support"), + versionControl = + VersionControl.github("iterative-works", "iw-project-support"), developers = Seq( - Developer("mph", "Michal Přihoda", "https://github.com/iterative-works") + Developer( + "mprihoda", + "Michal Příhoda", + "https://github.com/iterative-works" + ) ) ) @@ -23,10 +36,15 @@ ivy"com.lihaoyi::os-lib:0.9.3" ) - object test extends Tests with ScalafmtModule { + // Mill's own dependencies needed for compilation + def compileIvyDeps = Agg( + ivy"com.lihaoyi::mill-main:${millVersion}", + ivy"com.lihaoyi::mill-scalalib:${millVersion}" + ) + + object test extends ScalaTests with Utest with ScalafmtModule { def ivyDeps = Agg( ivy"com.lihaoyi::utest:0.8.2" ) - def testFramework = "utest.runner.Framework" } -} \ No newline at end of file +} diff --git a/mill-iw-support/.mill-version b/mill-iw-support/.mill-version new file mode 100644 index 0000000..54dbed4 --- /dev/null +++ b/mill-iw-support/.mill-version @@ -0,0 +1 @@ +0.12.10 diff --git a/mill-iw-support/.scalafmt.conf b/mill-iw-support/.scalafmt.conf new file mode 100644 index 0000000..ee7753a --- /dev/null +++ b/mill-iw-support/.scalafmt.conf @@ -0,0 +1,2 @@ +version = "3.7.15" +runner.dialect = scala213 \ No newline at end of file diff --git a/mill-iw-support/README.md b/mill-iw-support/README.md index 4986e5c..9bb1ba8 100644 --- a/mill-iw-support/README.md +++ b/mill-iw-support/README.md @@ -28,8 +28,45 @@ IWMillDeps.tapirCore // ivy"com.softwaremill.sttp.tapir::tapir-core:1.11.16" // Use dependency groups -IWMillDeps.useZIO() // Core ZIO dependencies with test framework -IWMillDeps.useZIOAll() // Comprehensive ZIO stack +IWMillDeps.zioCore // List of core ZIO dependencies +IWMillDeps.zioAll // List of all ZIO dependencies +``` + +### IWBomModule + +Bill of Materials (BOM) module for centralized dependency management: + +```scala +import mill._ +import works.iterative.mill._ + +// Define a BOM module +object bom extends IWBomModule { + def publishVersion = "0.1.0-SNAPSHOT" + + def pomSettings = PomSettings( + // Standard POM settings + ) + + // Override with custom dependencies if needed + override def depManagement = T { + super.depManagement() ++ Agg( + ivy"com.example::custom-lib:1.0.0" + ) + } +} + +// Use the BOM in modules +object myModule extends IWScalaModule { + // Reference the BOM module + override def bomModuleDeps = Seq(bom) + + // No need to specify versions - they come from the BOM + def ivyDeps = super.ivyDeps() ++ Agg( + ivy"dev.zio::zio", + ivy"dev.zio::zio-json" + ) +} ``` ### IWScalaModule @@ -44,10 +81,18 @@ // Use default Scala version (3.6.3) // and standard compiler options with SemanticDB enabled - def ivyDeps = super.ivyDeps() ++ IWMillDeps.useZIO() + def ivyDeps = super.ivyDeps() ++ Agg( + ivy"${IWMillDeps.zio}", + ivy"${IWMillDeps.zioJson}" + ) // Standard test module configuration - object test extends Tests with IWTests + object test extends ScalaTests with TestModule.ZioTest { + def ivyDeps = super.ivyDeps() ++ Agg( + ivy"${IWMillDeps.zioTest}", + ivy"${IWMillDeps.zioTestSbt}" + ) + } } ``` @@ -96,6 +141,51 @@ } ``` +## Dependency Management Approaches + +This library provides two approaches for dependency management: + +### 1. IWMillDeps (String Constants) + +Use the IWMillDeps object to access dependency strings with correct versions: + +```scala +def ivyDeps = super.ivyDeps() ++ Agg( + ivy"${IWMillDeps.zio}", + ivy"${IWMillDeps.zioJson}" +) +``` + +### 2. BOM (Bill of Materials) + +Use the IWBomModule to centralize dependency versions: + +```scala +// Define a BOM module +object bom extends IWBomModule { + def publishVersion = "0.1.0-SNAPSHOT" + def pomSettings = PomSettings(...) +} + +// Use the BOM in modules +object myModule extends IWScalaModule { + override def bomModuleDeps = Seq(bom) + + // No need to specify versions + def ivyDeps = super.ivyDeps() ++ Agg( + ivy"dev.zio::zio", + ivy"dev.zio::zio-json" + ) +} +``` + +The BOM approach offers these advantages: +- Enforces consistent versions across modules +- Simplifies version updates +- Controls transitive dependency versions +- Makes build files cleaner (no version strings) +- Can be published as a separate artifact + ## Examples See the `example` directory for a complete working example. diff --git a/mill-iw-support/build.sc b/mill-iw-support/build.sc index 5df80ae..2ed832c 100644 --- a/mill-iw-support/build.sc +++ b/mill-iw-support/build.sc @@ -2,20 +2,33 @@ import mill.scalalib._ import mill.scalalib.publish._ import mill.scalalib.scalafmt._ +import mill.scalalib.TestModule.Utest -object `mill-iw-support` extends Cross[IWMillSupportModule]("2.13.16", "3.6.3") +/** Main module for the mill-iw-support library. Built for Scala 2.13 which is + * compatible with Mill. + */ +object core extends ScalaModule with ScalafmtModule with PublishModule { + val millVersion = "0.12.10" -class IWMillSupportModule(val crossScalaVersion: String) extends CrossScalaModule with ScalafmtModule with PublishModule { + def scalaVersion = "2.13.16" + + def artifactName = "mill-iw-support" + def publishVersion = "0.1.0-SNAPSHOT" def pomSettings = PomSettings( description = "Iterative Works Mill Support Library", organization = "works.iterative", - url = "https://github.com/iterative-works/mill-iw-support", + url = "https://github.com/iterative-works/iw-project-support", licenses = Seq(License.MIT), - versionControl = VersionControl.github("iterative-works", "mill-iw-support"), + versionControl = + VersionControl.github("iterative-works", "iw-project-support"), developers = Seq( - Developer("mph", "Michal Přihoda", "https://github.com/iterative-works") + Developer( + "mprihoda", + "Michal Příhoda", + "https://github.com/iterative-works" + ) ) ) @@ -23,10 +36,15 @@ ivy"com.lihaoyi::os-lib:0.9.3" ) - object test extends Tests with ScalafmtModule { + // Mill's own dependencies needed for compilation + def compileIvyDeps = Agg( + ivy"com.lihaoyi::mill-main:${millVersion}", + ivy"com.lihaoyi::mill-scalalib:${millVersion}" + ) + + object test extends ScalaTests with Utest with ScalafmtModule { def ivyDeps = Agg( ivy"com.lihaoyi::utest:0.8.2" ) - def testFramework = "utest.runner.Framework" } -} \ No newline at end of file +} diff --git a/mill-iw-support/core/src/IWBomModule.scala b/mill-iw-support/core/src/IWBomModule.scala new file mode 100644 index 0000000..d8e70bb --- /dev/null +++ b/mill-iw-support/core/src/IWBomModule.scala @@ -0,0 +1,97 @@ +package works.iterative.mill + +import mill._ +import mill.scalalib._ +import mill.scalalib.publish._ + +/** Standard Bill of Materials (BOM) module for Iterative Works projects using + * Mill. This module centralizes dependency versioning across projects. + * + * A BOM module allows: + * 1. Centralized version management for dependencies 2. Enforcing specific + * versions of transitive dependencies 3. Applying uniform dependency + * management across multiple modules 4. Publishing a BOM artifact that + * other projects can consume + * + * Usage: + * 1. Create a BOM module in your build: + * ```scala + * object bom extends IWBomModule { + * def publishVersion = "0.1.0-SNAPSHOT" + * + * // Add your organization-specific customizations here + * def pomSettings = PomSettings(...) + * } + * ``` + * + * 2. Use the BOM in your modules: + * ```scala + * object myModule extends IWScalaModule { + * def bomModuleDeps = Seq(bom) + * + * def ivyDeps = Agg( + * // No need to specify versions as they come from the BOM + * ivy"dev.zio::zio", + * ivy"dev.zio::zio-json" + * ) + * } + * ``` + */ +trait IWBomModule extends BomModule with ScalaModule with IWScalaVersions { + + override def scalaVersion: T[String] = scala3Version + + /** The default implementation includes all standard IW dependencies from + * IWMillVersions and IWMillDeps. + */ + override def depManagement = T { + val V = IWMillVersions + + Agg( + // ZIO Core + ivy"dev.zio::zio:${V.zio}", + ivy"dev.zio::zio-test:${V.zio}", + ivy"dev.zio::zio-test-sbt:${V.zio}", + ivy"dev.zio::zio-streams:${V.zio}", + + // ZIO Extensions + ivy"dev.zio::zio-config:${V.zioConfig}", + ivy"dev.zio::zio-config-typesafe:${V.zioConfig}", + ivy"dev.zio::zio-config-magnolia:${V.zioConfig}", + ivy"dev.zio::zio-json:${V.zioJson}", + ivy"dev.zio::zio-logging:${V.zioLogging}", + ivy"dev.zio::zio-logging-slf4j:${V.zioLogging}", + ivy"dev.zio::zio-prelude:${V.zioPrelude}", + ivy"dev.zio::zio-interop-cats:${V.zioInteropCats}", + ivy"dev.zio::zio-interop-reactivestreams:${V.zioInteropReactiveStreams}", + ivy"dev.zio::zio-nio:${V.zioNIO}", + ivy"dev.zio::zio-cli:${V.zioCli}", + ivy"dev.zio::zio-schema:${V.zioSchema}", + ivy"dev.zio::zio-schema-derivation:${V.zioSchema}", + + // Database + ivy"io.getquill::quill-jdbc-zio:${V.quill}", + ivy"com.augustnagro::magnum:${V.magnum}", + ivy"com.augustnagro::magnumzio:${V.magnum}", + ivy"com.augustnagro::magnumpg:${V.magnum}", + + // HTTP + ivy"com.softwaremill.sttp.tapir::tapir-core:${V.tapir}", + ivy"com.softwaremill.sttp.tapir::tapir-zio:${V.tapir}", + ivy"com.softwaremill.sttp.tapir::tapir-json-zio:${V.tapir}", + + // UI/Frontend + ivy"com.lihaoyi::scalatags:${V.scalatags}", + ivy"com.raquo::laminar:${V.laminar}", + ivy"com.raquo::waypoint:${V.waypoint}", + ivy"be.doeraene::url-dsl:${V.urlDsl}", + + // Testing + ivy"org.scalatest::scalatest:${V.scalaTest}", + ivy"com.lihaoyi::utest:${V.utest}", + + // Logging + ivy"ch.qos.logback:logback-classic:${V.logbackClassic}" + ) + } +} diff --git a/mill-iw-support/.mill-version b/mill-iw-support/.mill-version new file mode 100644 index 0000000..54dbed4 --- /dev/null +++ b/mill-iw-support/.mill-version @@ -0,0 +1 @@ +0.12.10 diff --git a/mill-iw-support/.scalafmt.conf b/mill-iw-support/.scalafmt.conf new file mode 100644 index 0000000..ee7753a --- /dev/null +++ b/mill-iw-support/.scalafmt.conf @@ -0,0 +1,2 @@ +version = "3.7.15" +runner.dialect = scala213 \ No newline at end of file diff --git a/mill-iw-support/README.md b/mill-iw-support/README.md index 4986e5c..9bb1ba8 100644 --- a/mill-iw-support/README.md +++ b/mill-iw-support/README.md @@ -28,8 +28,45 @@ IWMillDeps.tapirCore // ivy"com.softwaremill.sttp.tapir::tapir-core:1.11.16" // Use dependency groups -IWMillDeps.useZIO() // Core ZIO dependencies with test framework -IWMillDeps.useZIOAll() // Comprehensive ZIO stack +IWMillDeps.zioCore // List of core ZIO dependencies +IWMillDeps.zioAll // List of all ZIO dependencies +``` + +### IWBomModule + +Bill of Materials (BOM) module for centralized dependency management: + +```scala +import mill._ +import works.iterative.mill._ + +// Define a BOM module +object bom extends IWBomModule { + def publishVersion = "0.1.0-SNAPSHOT" + + def pomSettings = PomSettings( + // Standard POM settings + ) + + // Override with custom dependencies if needed + override def depManagement = T { + super.depManagement() ++ Agg( + ivy"com.example::custom-lib:1.0.0" + ) + } +} + +// Use the BOM in modules +object myModule extends IWScalaModule { + // Reference the BOM module + override def bomModuleDeps = Seq(bom) + + // No need to specify versions - they come from the BOM + def ivyDeps = super.ivyDeps() ++ Agg( + ivy"dev.zio::zio", + ivy"dev.zio::zio-json" + ) +} ``` ### IWScalaModule @@ -44,10 +81,18 @@ // Use default Scala version (3.6.3) // and standard compiler options with SemanticDB enabled - def ivyDeps = super.ivyDeps() ++ IWMillDeps.useZIO() + def ivyDeps = super.ivyDeps() ++ Agg( + ivy"${IWMillDeps.zio}", + ivy"${IWMillDeps.zioJson}" + ) // Standard test module configuration - object test extends Tests with IWTests + object test extends ScalaTests with TestModule.ZioTest { + def ivyDeps = super.ivyDeps() ++ Agg( + ivy"${IWMillDeps.zioTest}", + ivy"${IWMillDeps.zioTestSbt}" + ) + } } ``` @@ -96,6 +141,51 @@ } ``` +## Dependency Management Approaches + +This library provides two approaches for dependency management: + +### 1. IWMillDeps (String Constants) + +Use the IWMillDeps object to access dependency strings with correct versions: + +```scala +def ivyDeps = super.ivyDeps() ++ Agg( + ivy"${IWMillDeps.zio}", + ivy"${IWMillDeps.zioJson}" +) +``` + +### 2. BOM (Bill of Materials) + +Use the IWBomModule to centralize dependency versions: + +```scala +// Define a BOM module +object bom extends IWBomModule { + def publishVersion = "0.1.0-SNAPSHOT" + def pomSettings = PomSettings(...) +} + +// Use the BOM in modules +object myModule extends IWScalaModule { + override def bomModuleDeps = Seq(bom) + + // No need to specify versions + def ivyDeps = super.ivyDeps() ++ Agg( + ivy"dev.zio::zio", + ivy"dev.zio::zio-json" + ) +} +``` + +The BOM approach offers these advantages: +- Enforces consistent versions across modules +- Simplifies version updates +- Controls transitive dependency versions +- Makes build files cleaner (no version strings) +- Can be published as a separate artifact + ## Examples See the `example` directory for a complete working example. diff --git a/mill-iw-support/build.sc b/mill-iw-support/build.sc index 5df80ae..2ed832c 100644 --- a/mill-iw-support/build.sc +++ b/mill-iw-support/build.sc @@ -2,20 +2,33 @@ import mill.scalalib._ import mill.scalalib.publish._ import mill.scalalib.scalafmt._ +import mill.scalalib.TestModule.Utest -object `mill-iw-support` extends Cross[IWMillSupportModule]("2.13.16", "3.6.3") +/** Main module for the mill-iw-support library. Built for Scala 2.13 which is + * compatible with Mill. + */ +object core extends ScalaModule with ScalafmtModule with PublishModule { + val millVersion = "0.12.10" -class IWMillSupportModule(val crossScalaVersion: String) extends CrossScalaModule with ScalafmtModule with PublishModule { + def scalaVersion = "2.13.16" + + def artifactName = "mill-iw-support" + def publishVersion = "0.1.0-SNAPSHOT" def pomSettings = PomSettings( description = "Iterative Works Mill Support Library", organization = "works.iterative", - url = "https://github.com/iterative-works/mill-iw-support", + url = "https://github.com/iterative-works/iw-project-support", licenses = Seq(License.MIT), - versionControl = VersionControl.github("iterative-works", "mill-iw-support"), + versionControl = + VersionControl.github("iterative-works", "iw-project-support"), developers = Seq( - Developer("mph", "Michal Přihoda", "https://github.com/iterative-works") + Developer( + "mprihoda", + "Michal Příhoda", + "https://github.com/iterative-works" + ) ) ) @@ -23,10 +36,15 @@ ivy"com.lihaoyi::os-lib:0.9.3" ) - object test extends Tests with ScalafmtModule { + // Mill's own dependencies needed for compilation + def compileIvyDeps = Agg( + ivy"com.lihaoyi::mill-main:${millVersion}", + ivy"com.lihaoyi::mill-scalalib:${millVersion}" + ) + + object test extends ScalaTests with Utest with ScalafmtModule { def ivyDeps = Agg( ivy"com.lihaoyi::utest:0.8.2" ) - def testFramework = "utest.runner.Framework" } -} \ No newline at end of file +} diff --git a/mill-iw-support/core/src/IWBomModule.scala b/mill-iw-support/core/src/IWBomModule.scala new file mode 100644 index 0000000..d8e70bb --- /dev/null +++ b/mill-iw-support/core/src/IWBomModule.scala @@ -0,0 +1,97 @@ +package works.iterative.mill + +import mill._ +import mill.scalalib._ +import mill.scalalib.publish._ + +/** Standard Bill of Materials (BOM) module for Iterative Works projects using + * Mill. This module centralizes dependency versioning across projects. + * + * A BOM module allows: + * 1. Centralized version management for dependencies 2. Enforcing specific + * versions of transitive dependencies 3. Applying uniform dependency + * management across multiple modules 4. Publishing a BOM artifact that + * other projects can consume + * + * Usage: + * 1. Create a BOM module in your build: + * ```scala + * object bom extends IWBomModule { + * def publishVersion = "0.1.0-SNAPSHOT" + * + * // Add your organization-specific customizations here + * def pomSettings = PomSettings(...) + * } + * ``` + * + * 2. Use the BOM in your modules: + * ```scala + * object myModule extends IWScalaModule { + * def bomModuleDeps = Seq(bom) + * + * def ivyDeps = Agg( + * // No need to specify versions as they come from the BOM + * ivy"dev.zio::zio", + * ivy"dev.zio::zio-json" + * ) + * } + * ``` + */ +trait IWBomModule extends BomModule with ScalaModule with IWScalaVersions { + + override def scalaVersion: T[String] = scala3Version + + /** The default implementation includes all standard IW dependencies from + * IWMillVersions and IWMillDeps. + */ + override def depManagement = T { + val V = IWMillVersions + + Agg( + // ZIO Core + ivy"dev.zio::zio:${V.zio}", + ivy"dev.zio::zio-test:${V.zio}", + ivy"dev.zio::zio-test-sbt:${V.zio}", + ivy"dev.zio::zio-streams:${V.zio}", + + // ZIO Extensions + ivy"dev.zio::zio-config:${V.zioConfig}", + ivy"dev.zio::zio-config-typesafe:${V.zioConfig}", + ivy"dev.zio::zio-config-magnolia:${V.zioConfig}", + ivy"dev.zio::zio-json:${V.zioJson}", + ivy"dev.zio::zio-logging:${V.zioLogging}", + ivy"dev.zio::zio-logging-slf4j:${V.zioLogging}", + ivy"dev.zio::zio-prelude:${V.zioPrelude}", + ivy"dev.zio::zio-interop-cats:${V.zioInteropCats}", + ivy"dev.zio::zio-interop-reactivestreams:${V.zioInteropReactiveStreams}", + ivy"dev.zio::zio-nio:${V.zioNIO}", + ivy"dev.zio::zio-cli:${V.zioCli}", + ivy"dev.zio::zio-schema:${V.zioSchema}", + ivy"dev.zio::zio-schema-derivation:${V.zioSchema}", + + // Database + ivy"io.getquill::quill-jdbc-zio:${V.quill}", + ivy"com.augustnagro::magnum:${V.magnum}", + ivy"com.augustnagro::magnumzio:${V.magnum}", + ivy"com.augustnagro::magnumpg:${V.magnum}", + + // HTTP + ivy"com.softwaremill.sttp.tapir::tapir-core:${V.tapir}", + ivy"com.softwaremill.sttp.tapir::tapir-zio:${V.tapir}", + ivy"com.softwaremill.sttp.tapir::tapir-json-zio:${V.tapir}", + + // UI/Frontend + ivy"com.lihaoyi::scalatags:${V.scalatags}", + ivy"com.raquo::laminar:${V.laminar}", + ivy"com.raquo::waypoint:${V.waypoint}", + ivy"be.doeraene::url-dsl:${V.urlDsl}", + + // Testing + ivy"org.scalatest::scalatest:${V.scalaTest}", + ivy"com.lihaoyi::utest:${V.utest}", + + // Logging + ivy"ch.qos.logback:logback-classic:${V.logbackClassic}" + ) + } +} diff --git a/mill-iw-support/core/src/IWMillDeps.scala b/mill-iw-support/core/src/IWMillDeps.scala new file mode 100644 index 0000000..d4cac1c --- /dev/null +++ b/mill-iw-support/core/src/IWMillDeps.scala @@ -0,0 +1,80 @@ +package works.iterative.mill + +import mill.api.Loose + +/** Standardized dependency management for Iterative Works projects using Mill. + * This object provides methods to get dependency strings with proper + * versioning. + * + * All versions are centralized in [[IWMillVersions]]. + * + * This object is designed to be used in build.sc files where the ivy string + * interpolator is available, like: + * + * {{{ + * def ivyDeps = Agg( + * ivy"$${IWMillDeps.zio}", + * ivy"$${IWMillDeps.zioJson}" + * ) + * }}} + */ +object IWMillDeps { + val V = IWMillVersions + + // ZIO Core + val zio = s"dev.zio::zio:${V.zio}" + val zioTest = s"dev.zio::zio-test:${V.zio}" + val zioTestSbt = s"dev.zio::zio-test-sbt:${V.zio}" + val zioStreams = s"dev.zio::zio-streams:${V.zio}" + + // ZIO Extensions + val zioConfig = s"dev.zio::zio-config:${V.zioConfig}" + val zioConfigTypesafe = s"dev.zio::zio-config-typesafe:${V.zioConfig}" + val zioConfigMagnolia = s"dev.zio::zio-config-magnolia:${V.zioConfig}" + val zioJson = s"dev.zio::zio-json:${V.zioJson}" + val zioLogging = s"dev.zio::zio-logging:${V.zioLogging}" + val zioLoggingSlf4j = s"dev.zio::zio-logging-slf4j:${V.zioLogging}" + val zioPrelude = s"dev.zio::zio-prelude:${V.zioPrelude}" + val zioInteropCats = s"dev.zio::zio-interop-cats:${V.zioInteropCats}" + val zioInteropReactiveStreams = + s"dev.zio::zio-interop-reactivestreams:${V.zioInteropReactiveStreams}" + val zioNIO = s"dev.zio::zio-nio:${V.zioNIO}" + val zioCli = s"dev.zio::zio-cli:${V.zioCli}" + val zioSchema = s"dev.zio::zio-schema:${V.zioSchema}" + val zioSchemaDerivation = s"dev.zio::zio-schema-derivation:${V.zioSchema}" + + // Collection of ZIO dependencies + val zioCore = List(zio, zioTest, zioTestSbt) + val zioJson_ = List(zioJson) + val zioAll = zioCore ++ List( + zioStreams, + zioConfig, + zioConfigTypesafe, + zioConfigMagnolia, + zioLogging, + zioPrelude + ) ++ zioJson_ + + // Database + val quill = s"io.getquill::quill-jdbc-zio:${V.quill}" + val magnum = s"com.augustnagro::magnum_3:${V.magnum}" + val magnumZIO = s"com.augustnagro::magnumzio_3:${V.magnum}" + val magnumPG = s"com.augustnagro::magnumpg_3:${V.magnum}" + + // HTTP + val tapirCore = s"com.softwaremill.sttp.tapir::tapir-core:${V.tapir}" + val tapirZIO = s"com.softwaremill.sttp.tapir::tapir-zio:${V.tapir}" + val tapirZIOJson = s"com.softwaremill.sttp.tapir::tapir-json-zio:${V.tapir}" + + // UI/Frontend + val scalatags = s"com.lihaoyi::scalatags:${V.scalatags}" + val laminar = s"com.raquo::laminar:${V.laminar}" + val waypoint = s"com.raquo::waypoint:${V.waypoint}" + val urlDsl = s"be.doeraene::url-dsl:${V.urlDsl}" + + // Testing + val scalaTest = s"org.scalatest::scalatest:${V.scalaTest}" + + // Logging + val logbackClassic = s"ch.qos.logback:logback-classic:${V.logbackClassic}" +} diff --git a/mill-iw-support/.mill-version b/mill-iw-support/.mill-version new file mode 100644 index 0000000..54dbed4 --- /dev/null +++ b/mill-iw-support/.mill-version @@ -0,0 +1 @@ +0.12.10 diff --git a/mill-iw-support/.scalafmt.conf b/mill-iw-support/.scalafmt.conf new file mode 100644 index 0000000..ee7753a --- /dev/null +++ b/mill-iw-support/.scalafmt.conf @@ -0,0 +1,2 @@ +version = "3.7.15" +runner.dialect = scala213 \ No newline at end of file diff --git a/mill-iw-support/README.md b/mill-iw-support/README.md index 4986e5c..9bb1ba8 100644 --- a/mill-iw-support/README.md +++ b/mill-iw-support/README.md @@ -28,8 +28,45 @@ IWMillDeps.tapirCore // ivy"com.softwaremill.sttp.tapir::tapir-core:1.11.16" // Use dependency groups -IWMillDeps.useZIO() // Core ZIO dependencies with test framework -IWMillDeps.useZIOAll() // Comprehensive ZIO stack +IWMillDeps.zioCore // List of core ZIO dependencies +IWMillDeps.zioAll // List of all ZIO dependencies +``` + +### IWBomModule + +Bill of Materials (BOM) module for centralized dependency management: + +```scala +import mill._ +import works.iterative.mill._ + +// Define a BOM module +object bom extends IWBomModule { + def publishVersion = "0.1.0-SNAPSHOT" + + def pomSettings = PomSettings( + // Standard POM settings + ) + + // Override with custom dependencies if needed + override def depManagement = T { + super.depManagement() ++ Agg( + ivy"com.example::custom-lib:1.0.0" + ) + } +} + +// Use the BOM in modules +object myModule extends IWScalaModule { + // Reference the BOM module + override def bomModuleDeps = Seq(bom) + + // No need to specify versions - they come from the BOM + def ivyDeps = super.ivyDeps() ++ Agg( + ivy"dev.zio::zio", + ivy"dev.zio::zio-json" + ) +} ``` ### IWScalaModule @@ -44,10 +81,18 @@ // Use default Scala version (3.6.3) // and standard compiler options with SemanticDB enabled - def ivyDeps = super.ivyDeps() ++ IWMillDeps.useZIO() + def ivyDeps = super.ivyDeps() ++ Agg( + ivy"${IWMillDeps.zio}", + ivy"${IWMillDeps.zioJson}" + ) // Standard test module configuration - object test extends Tests with IWTests + object test extends ScalaTests with TestModule.ZioTest { + def ivyDeps = super.ivyDeps() ++ Agg( + ivy"${IWMillDeps.zioTest}", + ivy"${IWMillDeps.zioTestSbt}" + ) + } } ``` @@ -96,6 +141,51 @@ } ``` +## Dependency Management Approaches + +This library provides two approaches for dependency management: + +### 1. IWMillDeps (String Constants) + +Use the IWMillDeps object to access dependency strings with correct versions: + +```scala +def ivyDeps = super.ivyDeps() ++ Agg( + ivy"${IWMillDeps.zio}", + ivy"${IWMillDeps.zioJson}" +) +``` + +### 2. BOM (Bill of Materials) + +Use the IWBomModule to centralize dependency versions: + +```scala +// Define a BOM module +object bom extends IWBomModule { + def publishVersion = "0.1.0-SNAPSHOT" + def pomSettings = PomSettings(...) +} + +// Use the BOM in modules +object myModule extends IWScalaModule { + override def bomModuleDeps = Seq(bom) + + // No need to specify versions + def ivyDeps = super.ivyDeps() ++ Agg( + ivy"dev.zio::zio", + ivy"dev.zio::zio-json" + ) +} +``` + +The BOM approach offers these advantages: +- Enforces consistent versions across modules +- Simplifies version updates +- Controls transitive dependency versions +- Makes build files cleaner (no version strings) +- Can be published as a separate artifact + ## Examples See the `example` directory for a complete working example. diff --git a/mill-iw-support/build.sc b/mill-iw-support/build.sc index 5df80ae..2ed832c 100644 --- a/mill-iw-support/build.sc +++ b/mill-iw-support/build.sc @@ -2,20 +2,33 @@ import mill.scalalib._ import mill.scalalib.publish._ import mill.scalalib.scalafmt._ +import mill.scalalib.TestModule.Utest -object `mill-iw-support` extends Cross[IWMillSupportModule]("2.13.16", "3.6.3") +/** Main module for the mill-iw-support library. Built for Scala 2.13 which is + * compatible with Mill. + */ +object core extends ScalaModule with ScalafmtModule with PublishModule { + val millVersion = "0.12.10" -class IWMillSupportModule(val crossScalaVersion: String) extends CrossScalaModule with ScalafmtModule with PublishModule { + def scalaVersion = "2.13.16" + + def artifactName = "mill-iw-support" + def publishVersion = "0.1.0-SNAPSHOT" def pomSettings = PomSettings( description = "Iterative Works Mill Support Library", organization = "works.iterative", - url = "https://github.com/iterative-works/mill-iw-support", + url = "https://github.com/iterative-works/iw-project-support", licenses = Seq(License.MIT), - versionControl = VersionControl.github("iterative-works", "mill-iw-support"), + versionControl = + VersionControl.github("iterative-works", "iw-project-support"), developers = Seq( - Developer("mph", "Michal Přihoda", "https://github.com/iterative-works") + Developer( + "mprihoda", + "Michal Příhoda", + "https://github.com/iterative-works" + ) ) ) @@ -23,10 +36,15 @@ ivy"com.lihaoyi::os-lib:0.9.3" ) - object test extends Tests with ScalafmtModule { + // Mill's own dependencies needed for compilation + def compileIvyDeps = Agg( + ivy"com.lihaoyi::mill-main:${millVersion}", + ivy"com.lihaoyi::mill-scalalib:${millVersion}" + ) + + object test extends ScalaTests with Utest with ScalafmtModule { def ivyDeps = Agg( ivy"com.lihaoyi::utest:0.8.2" ) - def testFramework = "utest.runner.Framework" } -} \ No newline at end of file +} diff --git a/mill-iw-support/core/src/IWBomModule.scala b/mill-iw-support/core/src/IWBomModule.scala new file mode 100644 index 0000000..d8e70bb --- /dev/null +++ b/mill-iw-support/core/src/IWBomModule.scala @@ -0,0 +1,97 @@ +package works.iterative.mill + +import mill._ +import mill.scalalib._ +import mill.scalalib.publish._ + +/** Standard Bill of Materials (BOM) module for Iterative Works projects using + * Mill. This module centralizes dependency versioning across projects. + * + * A BOM module allows: + * 1. Centralized version management for dependencies 2. Enforcing specific + * versions of transitive dependencies 3. Applying uniform dependency + * management across multiple modules 4. Publishing a BOM artifact that + * other projects can consume + * + * Usage: + * 1. Create a BOM module in your build: + * ```scala + * object bom extends IWBomModule { + * def publishVersion = "0.1.0-SNAPSHOT" + * + * // Add your organization-specific customizations here + * def pomSettings = PomSettings(...) + * } + * ``` + * + * 2. Use the BOM in your modules: + * ```scala + * object myModule extends IWScalaModule { + * def bomModuleDeps = Seq(bom) + * + * def ivyDeps = Agg( + * // No need to specify versions as they come from the BOM + * ivy"dev.zio::zio", + * ivy"dev.zio::zio-json" + * ) + * } + * ``` + */ +trait IWBomModule extends BomModule with ScalaModule with IWScalaVersions { + + override def scalaVersion: T[String] = scala3Version + + /** The default implementation includes all standard IW dependencies from + * IWMillVersions and IWMillDeps. + */ + override def depManagement = T { + val V = IWMillVersions + + Agg( + // ZIO Core + ivy"dev.zio::zio:${V.zio}", + ivy"dev.zio::zio-test:${V.zio}", + ivy"dev.zio::zio-test-sbt:${V.zio}", + ivy"dev.zio::zio-streams:${V.zio}", + + // ZIO Extensions + ivy"dev.zio::zio-config:${V.zioConfig}", + ivy"dev.zio::zio-config-typesafe:${V.zioConfig}", + ivy"dev.zio::zio-config-magnolia:${V.zioConfig}", + ivy"dev.zio::zio-json:${V.zioJson}", + ivy"dev.zio::zio-logging:${V.zioLogging}", + ivy"dev.zio::zio-logging-slf4j:${V.zioLogging}", + ivy"dev.zio::zio-prelude:${V.zioPrelude}", + ivy"dev.zio::zio-interop-cats:${V.zioInteropCats}", + ivy"dev.zio::zio-interop-reactivestreams:${V.zioInteropReactiveStreams}", + ivy"dev.zio::zio-nio:${V.zioNIO}", + ivy"dev.zio::zio-cli:${V.zioCli}", + ivy"dev.zio::zio-schema:${V.zioSchema}", + ivy"dev.zio::zio-schema-derivation:${V.zioSchema}", + + // Database + ivy"io.getquill::quill-jdbc-zio:${V.quill}", + ivy"com.augustnagro::magnum:${V.magnum}", + ivy"com.augustnagro::magnumzio:${V.magnum}", + ivy"com.augustnagro::magnumpg:${V.magnum}", + + // HTTP + ivy"com.softwaremill.sttp.tapir::tapir-core:${V.tapir}", + ivy"com.softwaremill.sttp.tapir::tapir-zio:${V.tapir}", + ivy"com.softwaremill.sttp.tapir::tapir-json-zio:${V.tapir}", + + // UI/Frontend + ivy"com.lihaoyi::scalatags:${V.scalatags}", + ivy"com.raquo::laminar:${V.laminar}", + ivy"com.raquo::waypoint:${V.waypoint}", + ivy"be.doeraene::url-dsl:${V.urlDsl}", + + // Testing + ivy"org.scalatest::scalatest:${V.scalaTest}", + ivy"com.lihaoyi::utest:${V.utest}", + + // Logging + ivy"ch.qos.logback:logback-classic:${V.logbackClassic}" + ) + } +} diff --git a/mill-iw-support/core/src/IWMillDeps.scala b/mill-iw-support/core/src/IWMillDeps.scala new file mode 100644 index 0000000..d4cac1c --- /dev/null +++ b/mill-iw-support/core/src/IWMillDeps.scala @@ -0,0 +1,80 @@ +package works.iterative.mill + +import mill.api.Loose + +/** Standardized dependency management for Iterative Works projects using Mill. + * This object provides methods to get dependency strings with proper + * versioning. + * + * All versions are centralized in [[IWMillVersions]]. + * + * This object is designed to be used in build.sc files where the ivy string + * interpolator is available, like: + * + * {{{ + * def ivyDeps = Agg( + * ivy"$${IWMillDeps.zio}", + * ivy"$${IWMillDeps.zioJson}" + * ) + * }}} + */ +object IWMillDeps { + val V = IWMillVersions + + // ZIO Core + val zio = s"dev.zio::zio:${V.zio}" + val zioTest = s"dev.zio::zio-test:${V.zio}" + val zioTestSbt = s"dev.zio::zio-test-sbt:${V.zio}" + val zioStreams = s"dev.zio::zio-streams:${V.zio}" + + // ZIO Extensions + val zioConfig = s"dev.zio::zio-config:${V.zioConfig}" + val zioConfigTypesafe = s"dev.zio::zio-config-typesafe:${V.zioConfig}" + val zioConfigMagnolia = s"dev.zio::zio-config-magnolia:${V.zioConfig}" + val zioJson = s"dev.zio::zio-json:${V.zioJson}" + val zioLogging = s"dev.zio::zio-logging:${V.zioLogging}" + val zioLoggingSlf4j = s"dev.zio::zio-logging-slf4j:${V.zioLogging}" + val zioPrelude = s"dev.zio::zio-prelude:${V.zioPrelude}" + val zioInteropCats = s"dev.zio::zio-interop-cats:${V.zioInteropCats}" + val zioInteropReactiveStreams = + s"dev.zio::zio-interop-reactivestreams:${V.zioInteropReactiveStreams}" + val zioNIO = s"dev.zio::zio-nio:${V.zioNIO}" + val zioCli = s"dev.zio::zio-cli:${V.zioCli}" + val zioSchema = s"dev.zio::zio-schema:${V.zioSchema}" + val zioSchemaDerivation = s"dev.zio::zio-schema-derivation:${V.zioSchema}" + + // Collection of ZIO dependencies + val zioCore = List(zio, zioTest, zioTestSbt) + val zioJson_ = List(zioJson) + val zioAll = zioCore ++ List( + zioStreams, + zioConfig, + zioConfigTypesafe, + zioConfigMagnolia, + zioLogging, + zioPrelude + ) ++ zioJson_ + + // Database + val quill = s"io.getquill::quill-jdbc-zio:${V.quill}" + val magnum = s"com.augustnagro::magnum_3:${V.magnum}" + val magnumZIO = s"com.augustnagro::magnumzio_3:${V.magnum}" + val magnumPG = s"com.augustnagro::magnumpg_3:${V.magnum}" + + // HTTP + val tapirCore = s"com.softwaremill.sttp.tapir::tapir-core:${V.tapir}" + val tapirZIO = s"com.softwaremill.sttp.tapir::tapir-zio:${V.tapir}" + val tapirZIOJson = s"com.softwaremill.sttp.tapir::tapir-json-zio:${V.tapir}" + + // UI/Frontend + val scalatags = s"com.lihaoyi::scalatags:${V.scalatags}" + val laminar = s"com.raquo::laminar:${V.laminar}" + val waypoint = s"com.raquo::waypoint:${V.waypoint}" + val urlDsl = s"be.doeraene::url-dsl:${V.urlDsl}" + + // Testing + val scalaTest = s"org.scalatest::scalatest:${V.scalaTest}" + + // Logging + val logbackClassic = s"ch.qos.logback:logback-classic:${V.logbackClassic}" +} diff --git a/mill-iw-support/core/src/IWMillVersions.scala b/mill-iw-support/core/src/IWMillVersions.scala new file mode 100644 index 0000000..0ad9bd2 --- /dev/null +++ b/mill-iw-support/core/src/IWMillVersions.scala @@ -0,0 +1,56 @@ +package works.iterative.mill + +/** + * Centralized version management for Iterative Works projects using Mill. + * This object contains all library versions used across projects. + * + * Update this object to bump versions for all projects. + */ +object IWMillVersions { + // ZIO Ecosystem + val zio = "2.1.16" + val zioConfig = "4.0.3" + val zioCli = "0.7.0" + val zioInteropCats = "23.1.0.3" + val zioInteropReactiveStreams = "2.0.2" + val zioJson = "0.7.36" + val zioLogging = "2.5.0" + val zioNIO = "2.0.2" + val zioPrelude = "1.0.0-RC39" + val zioQuery = "0.7.6" + val zioSchema = "1.6.3" + + // Akka Ecosystem + val akka = "2.6.18" + val akkaHttp = "10.4.0" + + // Other Libraries + val cats = "2.13.0" + val utest = "0.8.2" + val caliban = "2.9.2" + val chimney = "1.7.3" + val ducktape = "0.2.7" + val elastic4s = "7.12.4" + val http4s = "0.23.30" + val http4sBlaze = "0.23.17" + val http4sPac4J = "5.0.0" + val laminar = "17.2.0" + val laminext = "0.17.0" + val logbackClassic = "1.5.17" + val magnum = "2.0.0-M1" + val pac4j = "6.0.3" + val play = "2.8.18" + val playJson = "2.9.3" + val quill = "4.8.6" + val scalaJsMacroTaskExecutor = "1.1.1" + val scalaJsJavaSecureRandom = "1.0.0" + val scalatags = "0.13.1" + val scalaTest = "3.2.19" + val slick = "3.3.3" + val sttpClient3 = "3.10.3" + val sttpClient4 = "4.0.0-RC1" + val support = "1.7.0" + val tapir = "1.11.16" + val urlDsl = "0.7.0" + val waypoint = "9.0.0" +} \ No newline at end of file diff --git a/mill-iw-support/.mill-version b/mill-iw-support/.mill-version new file mode 100644 index 0000000..54dbed4 --- /dev/null +++ b/mill-iw-support/.mill-version @@ -0,0 +1 @@ +0.12.10 diff --git a/mill-iw-support/.scalafmt.conf b/mill-iw-support/.scalafmt.conf new file mode 100644 index 0000000..ee7753a --- /dev/null +++ b/mill-iw-support/.scalafmt.conf @@ -0,0 +1,2 @@ +version = "3.7.15" +runner.dialect = scala213 \ No newline at end of file diff --git a/mill-iw-support/README.md b/mill-iw-support/README.md index 4986e5c..9bb1ba8 100644 --- a/mill-iw-support/README.md +++ b/mill-iw-support/README.md @@ -28,8 +28,45 @@ IWMillDeps.tapirCore // ivy"com.softwaremill.sttp.tapir::tapir-core:1.11.16" // Use dependency groups -IWMillDeps.useZIO() // Core ZIO dependencies with test framework -IWMillDeps.useZIOAll() // Comprehensive ZIO stack +IWMillDeps.zioCore // List of core ZIO dependencies +IWMillDeps.zioAll // List of all ZIO dependencies +``` + +### IWBomModule + +Bill of Materials (BOM) module for centralized dependency management: + +```scala +import mill._ +import works.iterative.mill._ + +// Define a BOM module +object bom extends IWBomModule { + def publishVersion = "0.1.0-SNAPSHOT" + + def pomSettings = PomSettings( + // Standard POM settings + ) + + // Override with custom dependencies if needed + override def depManagement = T { + super.depManagement() ++ Agg( + ivy"com.example::custom-lib:1.0.0" + ) + } +} + +// Use the BOM in modules +object myModule extends IWScalaModule { + // Reference the BOM module + override def bomModuleDeps = Seq(bom) + + // No need to specify versions - they come from the BOM + def ivyDeps = super.ivyDeps() ++ Agg( + ivy"dev.zio::zio", + ivy"dev.zio::zio-json" + ) +} ``` ### IWScalaModule @@ -44,10 +81,18 @@ // Use default Scala version (3.6.3) // and standard compiler options with SemanticDB enabled - def ivyDeps = super.ivyDeps() ++ IWMillDeps.useZIO() + def ivyDeps = super.ivyDeps() ++ Agg( + ivy"${IWMillDeps.zio}", + ivy"${IWMillDeps.zioJson}" + ) // Standard test module configuration - object test extends Tests with IWTests + object test extends ScalaTests with TestModule.ZioTest { + def ivyDeps = super.ivyDeps() ++ Agg( + ivy"${IWMillDeps.zioTest}", + ivy"${IWMillDeps.zioTestSbt}" + ) + } } ``` @@ -96,6 +141,51 @@ } ``` +## Dependency Management Approaches + +This library provides two approaches for dependency management: + +### 1. IWMillDeps (String Constants) + +Use the IWMillDeps object to access dependency strings with correct versions: + +```scala +def ivyDeps = super.ivyDeps() ++ Agg( + ivy"${IWMillDeps.zio}", + ivy"${IWMillDeps.zioJson}" +) +``` + +### 2. BOM (Bill of Materials) + +Use the IWBomModule to centralize dependency versions: + +```scala +// Define a BOM module +object bom extends IWBomModule { + def publishVersion = "0.1.0-SNAPSHOT" + def pomSettings = PomSettings(...) +} + +// Use the BOM in modules +object myModule extends IWScalaModule { + override def bomModuleDeps = Seq(bom) + + // No need to specify versions + def ivyDeps = super.ivyDeps() ++ Agg( + ivy"dev.zio::zio", + ivy"dev.zio::zio-json" + ) +} +``` + +The BOM approach offers these advantages: +- Enforces consistent versions across modules +- Simplifies version updates +- Controls transitive dependency versions +- Makes build files cleaner (no version strings) +- Can be published as a separate artifact + ## Examples See the `example` directory for a complete working example. diff --git a/mill-iw-support/build.sc b/mill-iw-support/build.sc index 5df80ae..2ed832c 100644 --- a/mill-iw-support/build.sc +++ b/mill-iw-support/build.sc @@ -2,20 +2,33 @@ import mill.scalalib._ import mill.scalalib.publish._ import mill.scalalib.scalafmt._ +import mill.scalalib.TestModule.Utest -object `mill-iw-support` extends Cross[IWMillSupportModule]("2.13.16", "3.6.3") +/** Main module for the mill-iw-support library. Built for Scala 2.13 which is + * compatible with Mill. + */ +object core extends ScalaModule with ScalafmtModule with PublishModule { + val millVersion = "0.12.10" -class IWMillSupportModule(val crossScalaVersion: String) extends CrossScalaModule with ScalafmtModule with PublishModule { + def scalaVersion = "2.13.16" + + def artifactName = "mill-iw-support" + def publishVersion = "0.1.0-SNAPSHOT" def pomSettings = PomSettings( description = "Iterative Works Mill Support Library", organization = "works.iterative", - url = "https://github.com/iterative-works/mill-iw-support", + url = "https://github.com/iterative-works/iw-project-support", licenses = Seq(License.MIT), - versionControl = VersionControl.github("iterative-works", "mill-iw-support"), + versionControl = + VersionControl.github("iterative-works", "iw-project-support"), developers = Seq( - Developer("mph", "Michal Přihoda", "https://github.com/iterative-works") + Developer( + "mprihoda", + "Michal Příhoda", + "https://github.com/iterative-works" + ) ) ) @@ -23,10 +36,15 @@ ivy"com.lihaoyi::os-lib:0.9.3" ) - object test extends Tests with ScalafmtModule { + // Mill's own dependencies needed for compilation + def compileIvyDeps = Agg( + ivy"com.lihaoyi::mill-main:${millVersion}", + ivy"com.lihaoyi::mill-scalalib:${millVersion}" + ) + + object test extends ScalaTests with Utest with ScalafmtModule { def ivyDeps = Agg( ivy"com.lihaoyi::utest:0.8.2" ) - def testFramework = "utest.runner.Framework" } -} \ No newline at end of file +} diff --git a/mill-iw-support/core/src/IWBomModule.scala b/mill-iw-support/core/src/IWBomModule.scala new file mode 100644 index 0000000..d8e70bb --- /dev/null +++ b/mill-iw-support/core/src/IWBomModule.scala @@ -0,0 +1,97 @@ +package works.iterative.mill + +import mill._ +import mill.scalalib._ +import mill.scalalib.publish._ + +/** Standard Bill of Materials (BOM) module for Iterative Works projects using + * Mill. This module centralizes dependency versioning across projects. + * + * A BOM module allows: + * 1. Centralized version management for dependencies 2. Enforcing specific + * versions of transitive dependencies 3. Applying uniform dependency + * management across multiple modules 4. Publishing a BOM artifact that + * other projects can consume + * + * Usage: + * 1. Create a BOM module in your build: + * ```scala + * object bom extends IWBomModule { + * def publishVersion = "0.1.0-SNAPSHOT" + * + * // Add your organization-specific customizations here + * def pomSettings = PomSettings(...) + * } + * ``` + * + * 2. Use the BOM in your modules: + * ```scala + * object myModule extends IWScalaModule { + * def bomModuleDeps = Seq(bom) + * + * def ivyDeps = Agg( + * // No need to specify versions as they come from the BOM + * ivy"dev.zio::zio", + * ivy"dev.zio::zio-json" + * ) + * } + * ``` + */ +trait IWBomModule extends BomModule with ScalaModule with IWScalaVersions { + + override def scalaVersion: T[String] = scala3Version + + /** The default implementation includes all standard IW dependencies from + * IWMillVersions and IWMillDeps. + */ + override def depManagement = T { + val V = IWMillVersions + + Agg( + // ZIO Core + ivy"dev.zio::zio:${V.zio}", + ivy"dev.zio::zio-test:${V.zio}", + ivy"dev.zio::zio-test-sbt:${V.zio}", + ivy"dev.zio::zio-streams:${V.zio}", + + // ZIO Extensions + ivy"dev.zio::zio-config:${V.zioConfig}", + ivy"dev.zio::zio-config-typesafe:${V.zioConfig}", + ivy"dev.zio::zio-config-magnolia:${V.zioConfig}", + ivy"dev.zio::zio-json:${V.zioJson}", + ivy"dev.zio::zio-logging:${V.zioLogging}", + ivy"dev.zio::zio-logging-slf4j:${V.zioLogging}", + ivy"dev.zio::zio-prelude:${V.zioPrelude}", + ivy"dev.zio::zio-interop-cats:${V.zioInteropCats}", + ivy"dev.zio::zio-interop-reactivestreams:${V.zioInteropReactiveStreams}", + ivy"dev.zio::zio-nio:${V.zioNIO}", + ivy"dev.zio::zio-cli:${V.zioCli}", + ivy"dev.zio::zio-schema:${V.zioSchema}", + ivy"dev.zio::zio-schema-derivation:${V.zioSchema}", + + // Database + ivy"io.getquill::quill-jdbc-zio:${V.quill}", + ivy"com.augustnagro::magnum:${V.magnum}", + ivy"com.augustnagro::magnumzio:${V.magnum}", + ivy"com.augustnagro::magnumpg:${V.magnum}", + + // HTTP + ivy"com.softwaremill.sttp.tapir::tapir-core:${V.tapir}", + ivy"com.softwaremill.sttp.tapir::tapir-zio:${V.tapir}", + ivy"com.softwaremill.sttp.tapir::tapir-json-zio:${V.tapir}", + + // UI/Frontend + ivy"com.lihaoyi::scalatags:${V.scalatags}", + ivy"com.raquo::laminar:${V.laminar}", + ivy"com.raquo::waypoint:${V.waypoint}", + ivy"be.doeraene::url-dsl:${V.urlDsl}", + + // Testing + ivy"org.scalatest::scalatest:${V.scalaTest}", + ivy"com.lihaoyi::utest:${V.utest}", + + // Logging + ivy"ch.qos.logback:logback-classic:${V.logbackClassic}" + ) + } +} diff --git a/mill-iw-support/core/src/IWMillDeps.scala b/mill-iw-support/core/src/IWMillDeps.scala new file mode 100644 index 0000000..d4cac1c --- /dev/null +++ b/mill-iw-support/core/src/IWMillDeps.scala @@ -0,0 +1,80 @@ +package works.iterative.mill + +import mill.api.Loose + +/** Standardized dependency management for Iterative Works projects using Mill. + * This object provides methods to get dependency strings with proper + * versioning. + * + * All versions are centralized in [[IWMillVersions]]. + * + * This object is designed to be used in build.sc files where the ivy string + * interpolator is available, like: + * + * {{{ + * def ivyDeps = Agg( + * ivy"$${IWMillDeps.zio}", + * ivy"$${IWMillDeps.zioJson}" + * ) + * }}} + */ +object IWMillDeps { + val V = IWMillVersions + + // ZIO Core + val zio = s"dev.zio::zio:${V.zio}" + val zioTest = s"dev.zio::zio-test:${V.zio}" + val zioTestSbt = s"dev.zio::zio-test-sbt:${V.zio}" + val zioStreams = s"dev.zio::zio-streams:${V.zio}" + + // ZIO Extensions + val zioConfig = s"dev.zio::zio-config:${V.zioConfig}" + val zioConfigTypesafe = s"dev.zio::zio-config-typesafe:${V.zioConfig}" + val zioConfigMagnolia = s"dev.zio::zio-config-magnolia:${V.zioConfig}" + val zioJson = s"dev.zio::zio-json:${V.zioJson}" + val zioLogging = s"dev.zio::zio-logging:${V.zioLogging}" + val zioLoggingSlf4j = s"dev.zio::zio-logging-slf4j:${V.zioLogging}" + val zioPrelude = s"dev.zio::zio-prelude:${V.zioPrelude}" + val zioInteropCats = s"dev.zio::zio-interop-cats:${V.zioInteropCats}" + val zioInteropReactiveStreams = + s"dev.zio::zio-interop-reactivestreams:${V.zioInteropReactiveStreams}" + val zioNIO = s"dev.zio::zio-nio:${V.zioNIO}" + val zioCli = s"dev.zio::zio-cli:${V.zioCli}" + val zioSchema = s"dev.zio::zio-schema:${V.zioSchema}" + val zioSchemaDerivation = s"dev.zio::zio-schema-derivation:${V.zioSchema}" + + // Collection of ZIO dependencies + val zioCore = List(zio, zioTest, zioTestSbt) + val zioJson_ = List(zioJson) + val zioAll = zioCore ++ List( + zioStreams, + zioConfig, + zioConfigTypesafe, + zioConfigMagnolia, + zioLogging, + zioPrelude + ) ++ zioJson_ + + // Database + val quill = s"io.getquill::quill-jdbc-zio:${V.quill}" + val magnum = s"com.augustnagro::magnum_3:${V.magnum}" + val magnumZIO = s"com.augustnagro::magnumzio_3:${V.magnum}" + val magnumPG = s"com.augustnagro::magnumpg_3:${V.magnum}" + + // HTTP + val tapirCore = s"com.softwaremill.sttp.tapir::tapir-core:${V.tapir}" + val tapirZIO = s"com.softwaremill.sttp.tapir::tapir-zio:${V.tapir}" + val tapirZIOJson = s"com.softwaremill.sttp.tapir::tapir-json-zio:${V.tapir}" + + // UI/Frontend + val scalatags = s"com.lihaoyi::scalatags:${V.scalatags}" + val laminar = s"com.raquo::laminar:${V.laminar}" + val waypoint = s"com.raquo::waypoint:${V.waypoint}" + val urlDsl = s"be.doeraene::url-dsl:${V.urlDsl}" + + // Testing + val scalaTest = s"org.scalatest::scalatest:${V.scalaTest}" + + // Logging + val logbackClassic = s"ch.qos.logback:logback-classic:${V.logbackClassic}" +} diff --git a/mill-iw-support/core/src/IWMillVersions.scala b/mill-iw-support/core/src/IWMillVersions.scala new file mode 100644 index 0000000..0ad9bd2 --- /dev/null +++ b/mill-iw-support/core/src/IWMillVersions.scala @@ -0,0 +1,56 @@ +package works.iterative.mill + +/** + * Centralized version management for Iterative Works projects using Mill. + * This object contains all library versions used across projects. + * + * Update this object to bump versions for all projects. + */ +object IWMillVersions { + // ZIO Ecosystem + val zio = "2.1.16" + val zioConfig = "4.0.3" + val zioCli = "0.7.0" + val zioInteropCats = "23.1.0.3" + val zioInteropReactiveStreams = "2.0.2" + val zioJson = "0.7.36" + val zioLogging = "2.5.0" + val zioNIO = "2.0.2" + val zioPrelude = "1.0.0-RC39" + val zioQuery = "0.7.6" + val zioSchema = "1.6.3" + + // Akka Ecosystem + val akka = "2.6.18" + val akkaHttp = "10.4.0" + + // Other Libraries + val cats = "2.13.0" + val utest = "0.8.2" + val caliban = "2.9.2" + val chimney = "1.7.3" + val ducktape = "0.2.7" + val elastic4s = "7.12.4" + val http4s = "0.23.30" + val http4sBlaze = "0.23.17" + val http4sPac4J = "5.0.0" + val laminar = "17.2.0" + val laminext = "0.17.0" + val logbackClassic = "1.5.17" + val magnum = "2.0.0-M1" + val pac4j = "6.0.3" + val play = "2.8.18" + val playJson = "2.9.3" + val quill = "4.8.6" + val scalaJsMacroTaskExecutor = "1.1.1" + val scalaJsJavaSecureRandom = "1.0.0" + val scalatags = "0.13.1" + val scalaTest = "3.2.19" + val slick = "3.3.3" + val sttpClient3 = "3.10.3" + val sttpClient4 = "4.0.0-RC1" + val support = "1.7.0" + val tapir = "1.11.16" + val urlDsl = "0.7.0" + val waypoint = "9.0.0" +} \ No newline at end of file diff --git a/mill-iw-support/core/src/IWPublishModule.scala b/mill-iw-support/core/src/IWPublishModule.scala new file mode 100644 index 0000000..5ae0ade --- /dev/null +++ b/mill-iw-support/core/src/IWPublishModule.scala @@ -0,0 +1,22 @@ +package works.iterative.mill + +import mill._ +import mill.scalalib._ +import mill.scalalib.publish._ + +/** Standard publish module trait for Iterative Works projects using Mill. This + * trait sets up standard publishing configuration for IW Maven repositories. + * + * To use this trait, simply extend it in your module and define: + * - publishVersion: The version to publish + * - pomSettings: The POM settings for the publication + * + * Use IW_USERNAME and IW_PASSWORD environment variables for authentication. + */ +trait IWPublishModule extends PublishModule { + + override def sonatypeUri = "https://dig.iterative.works/maven/releases" + override def sonatypeSnapshotUri = + "https://dig.iterative.works/maven/snapshots" + +} diff --git a/mill-iw-support/.mill-version b/mill-iw-support/.mill-version new file mode 100644 index 0000000..54dbed4 --- /dev/null +++ b/mill-iw-support/.mill-version @@ -0,0 +1 @@ +0.12.10 diff --git a/mill-iw-support/.scalafmt.conf b/mill-iw-support/.scalafmt.conf new file mode 100644 index 0000000..ee7753a --- /dev/null +++ b/mill-iw-support/.scalafmt.conf @@ -0,0 +1,2 @@ +version = "3.7.15" +runner.dialect = scala213 \ No newline at end of file diff --git a/mill-iw-support/README.md b/mill-iw-support/README.md index 4986e5c..9bb1ba8 100644 --- a/mill-iw-support/README.md +++ b/mill-iw-support/README.md @@ -28,8 +28,45 @@ IWMillDeps.tapirCore // ivy"com.softwaremill.sttp.tapir::tapir-core:1.11.16" // Use dependency groups -IWMillDeps.useZIO() // Core ZIO dependencies with test framework -IWMillDeps.useZIOAll() // Comprehensive ZIO stack +IWMillDeps.zioCore // List of core ZIO dependencies +IWMillDeps.zioAll // List of all ZIO dependencies +``` + +### IWBomModule + +Bill of Materials (BOM) module for centralized dependency management: + +```scala +import mill._ +import works.iterative.mill._ + +// Define a BOM module +object bom extends IWBomModule { + def publishVersion = "0.1.0-SNAPSHOT" + + def pomSettings = PomSettings( + // Standard POM settings + ) + + // Override with custom dependencies if needed + override def depManagement = T { + super.depManagement() ++ Agg( + ivy"com.example::custom-lib:1.0.0" + ) + } +} + +// Use the BOM in modules +object myModule extends IWScalaModule { + // Reference the BOM module + override def bomModuleDeps = Seq(bom) + + // No need to specify versions - they come from the BOM + def ivyDeps = super.ivyDeps() ++ Agg( + ivy"dev.zio::zio", + ivy"dev.zio::zio-json" + ) +} ``` ### IWScalaModule @@ -44,10 +81,18 @@ // Use default Scala version (3.6.3) // and standard compiler options with SemanticDB enabled - def ivyDeps = super.ivyDeps() ++ IWMillDeps.useZIO() + def ivyDeps = super.ivyDeps() ++ Agg( + ivy"${IWMillDeps.zio}", + ivy"${IWMillDeps.zioJson}" + ) // Standard test module configuration - object test extends Tests with IWTests + object test extends ScalaTests with TestModule.ZioTest { + def ivyDeps = super.ivyDeps() ++ Agg( + ivy"${IWMillDeps.zioTest}", + ivy"${IWMillDeps.zioTestSbt}" + ) + } } ``` @@ -96,6 +141,51 @@ } ``` +## Dependency Management Approaches + +This library provides two approaches for dependency management: + +### 1. IWMillDeps (String Constants) + +Use the IWMillDeps object to access dependency strings with correct versions: + +```scala +def ivyDeps = super.ivyDeps() ++ Agg( + ivy"${IWMillDeps.zio}", + ivy"${IWMillDeps.zioJson}" +) +``` + +### 2. BOM (Bill of Materials) + +Use the IWBomModule to centralize dependency versions: + +```scala +// Define a BOM module +object bom extends IWBomModule { + def publishVersion = "0.1.0-SNAPSHOT" + def pomSettings = PomSettings(...) +} + +// Use the BOM in modules +object myModule extends IWScalaModule { + override def bomModuleDeps = Seq(bom) + + // No need to specify versions + def ivyDeps = super.ivyDeps() ++ Agg( + ivy"dev.zio::zio", + ivy"dev.zio::zio-json" + ) +} +``` + +The BOM approach offers these advantages: +- Enforces consistent versions across modules +- Simplifies version updates +- Controls transitive dependency versions +- Makes build files cleaner (no version strings) +- Can be published as a separate artifact + ## Examples See the `example` directory for a complete working example. diff --git a/mill-iw-support/build.sc b/mill-iw-support/build.sc index 5df80ae..2ed832c 100644 --- a/mill-iw-support/build.sc +++ b/mill-iw-support/build.sc @@ -2,20 +2,33 @@ import mill.scalalib._ import mill.scalalib.publish._ import mill.scalalib.scalafmt._ +import mill.scalalib.TestModule.Utest -object `mill-iw-support` extends Cross[IWMillSupportModule]("2.13.16", "3.6.3") +/** Main module for the mill-iw-support library. Built for Scala 2.13 which is + * compatible with Mill. + */ +object core extends ScalaModule with ScalafmtModule with PublishModule { + val millVersion = "0.12.10" -class IWMillSupportModule(val crossScalaVersion: String) extends CrossScalaModule with ScalafmtModule with PublishModule { + def scalaVersion = "2.13.16" + + def artifactName = "mill-iw-support" + def publishVersion = "0.1.0-SNAPSHOT" def pomSettings = PomSettings( description = "Iterative Works Mill Support Library", organization = "works.iterative", - url = "https://github.com/iterative-works/mill-iw-support", + url = "https://github.com/iterative-works/iw-project-support", licenses = Seq(License.MIT), - versionControl = VersionControl.github("iterative-works", "mill-iw-support"), + versionControl = + VersionControl.github("iterative-works", "iw-project-support"), developers = Seq( - Developer("mph", "Michal Přihoda", "https://github.com/iterative-works") + Developer( + "mprihoda", + "Michal Příhoda", + "https://github.com/iterative-works" + ) ) ) @@ -23,10 +36,15 @@ ivy"com.lihaoyi::os-lib:0.9.3" ) - object test extends Tests with ScalafmtModule { + // Mill's own dependencies needed for compilation + def compileIvyDeps = Agg( + ivy"com.lihaoyi::mill-main:${millVersion}", + ivy"com.lihaoyi::mill-scalalib:${millVersion}" + ) + + object test extends ScalaTests with Utest with ScalafmtModule { def ivyDeps = Agg( ivy"com.lihaoyi::utest:0.8.2" ) - def testFramework = "utest.runner.Framework" } -} \ No newline at end of file +} diff --git a/mill-iw-support/core/src/IWBomModule.scala b/mill-iw-support/core/src/IWBomModule.scala new file mode 100644 index 0000000..d8e70bb --- /dev/null +++ b/mill-iw-support/core/src/IWBomModule.scala @@ -0,0 +1,97 @@ +package works.iterative.mill + +import mill._ +import mill.scalalib._ +import mill.scalalib.publish._ + +/** Standard Bill of Materials (BOM) module for Iterative Works projects using + * Mill. This module centralizes dependency versioning across projects. + * + * A BOM module allows: + * 1. Centralized version management for dependencies 2. Enforcing specific + * versions of transitive dependencies 3. Applying uniform dependency + * management across multiple modules 4. Publishing a BOM artifact that + * other projects can consume + * + * Usage: + * 1. Create a BOM module in your build: + * ```scala + * object bom extends IWBomModule { + * def publishVersion = "0.1.0-SNAPSHOT" + * + * // Add your organization-specific customizations here + * def pomSettings = PomSettings(...) + * } + * ``` + * + * 2. Use the BOM in your modules: + * ```scala + * object myModule extends IWScalaModule { + * def bomModuleDeps = Seq(bom) + * + * def ivyDeps = Agg( + * // No need to specify versions as they come from the BOM + * ivy"dev.zio::zio", + * ivy"dev.zio::zio-json" + * ) + * } + * ``` + */ +trait IWBomModule extends BomModule with ScalaModule with IWScalaVersions { + + override def scalaVersion: T[String] = scala3Version + + /** The default implementation includes all standard IW dependencies from + * IWMillVersions and IWMillDeps. + */ + override def depManagement = T { + val V = IWMillVersions + + Agg( + // ZIO Core + ivy"dev.zio::zio:${V.zio}", + ivy"dev.zio::zio-test:${V.zio}", + ivy"dev.zio::zio-test-sbt:${V.zio}", + ivy"dev.zio::zio-streams:${V.zio}", + + // ZIO Extensions + ivy"dev.zio::zio-config:${V.zioConfig}", + ivy"dev.zio::zio-config-typesafe:${V.zioConfig}", + ivy"dev.zio::zio-config-magnolia:${V.zioConfig}", + ivy"dev.zio::zio-json:${V.zioJson}", + ivy"dev.zio::zio-logging:${V.zioLogging}", + ivy"dev.zio::zio-logging-slf4j:${V.zioLogging}", + ivy"dev.zio::zio-prelude:${V.zioPrelude}", + ivy"dev.zio::zio-interop-cats:${V.zioInteropCats}", + ivy"dev.zio::zio-interop-reactivestreams:${V.zioInteropReactiveStreams}", + ivy"dev.zio::zio-nio:${V.zioNIO}", + ivy"dev.zio::zio-cli:${V.zioCli}", + ivy"dev.zio::zio-schema:${V.zioSchema}", + ivy"dev.zio::zio-schema-derivation:${V.zioSchema}", + + // Database + ivy"io.getquill::quill-jdbc-zio:${V.quill}", + ivy"com.augustnagro::magnum:${V.magnum}", + ivy"com.augustnagro::magnumzio:${V.magnum}", + ivy"com.augustnagro::magnumpg:${V.magnum}", + + // HTTP + ivy"com.softwaremill.sttp.tapir::tapir-core:${V.tapir}", + ivy"com.softwaremill.sttp.tapir::tapir-zio:${V.tapir}", + ivy"com.softwaremill.sttp.tapir::tapir-json-zio:${V.tapir}", + + // UI/Frontend + ivy"com.lihaoyi::scalatags:${V.scalatags}", + ivy"com.raquo::laminar:${V.laminar}", + ivy"com.raquo::waypoint:${V.waypoint}", + ivy"be.doeraene::url-dsl:${V.urlDsl}", + + // Testing + ivy"org.scalatest::scalatest:${V.scalaTest}", + ivy"com.lihaoyi::utest:${V.utest}", + + // Logging + ivy"ch.qos.logback:logback-classic:${V.logbackClassic}" + ) + } +} diff --git a/mill-iw-support/core/src/IWMillDeps.scala b/mill-iw-support/core/src/IWMillDeps.scala new file mode 100644 index 0000000..d4cac1c --- /dev/null +++ b/mill-iw-support/core/src/IWMillDeps.scala @@ -0,0 +1,80 @@ +package works.iterative.mill + +import mill.api.Loose + +/** Standardized dependency management for Iterative Works projects using Mill. + * This object provides methods to get dependency strings with proper + * versioning. + * + * All versions are centralized in [[IWMillVersions]]. + * + * This object is designed to be used in build.sc files where the ivy string + * interpolator is available, like: + * + * {{{ + * def ivyDeps = Agg( + * ivy"$${IWMillDeps.zio}", + * ivy"$${IWMillDeps.zioJson}" + * ) + * }}} + */ +object IWMillDeps { + val V = IWMillVersions + + // ZIO Core + val zio = s"dev.zio::zio:${V.zio}" + val zioTest = s"dev.zio::zio-test:${V.zio}" + val zioTestSbt = s"dev.zio::zio-test-sbt:${V.zio}" + val zioStreams = s"dev.zio::zio-streams:${V.zio}" + + // ZIO Extensions + val zioConfig = s"dev.zio::zio-config:${V.zioConfig}" + val zioConfigTypesafe = s"dev.zio::zio-config-typesafe:${V.zioConfig}" + val zioConfigMagnolia = s"dev.zio::zio-config-magnolia:${V.zioConfig}" + val zioJson = s"dev.zio::zio-json:${V.zioJson}" + val zioLogging = s"dev.zio::zio-logging:${V.zioLogging}" + val zioLoggingSlf4j = s"dev.zio::zio-logging-slf4j:${V.zioLogging}" + val zioPrelude = s"dev.zio::zio-prelude:${V.zioPrelude}" + val zioInteropCats = s"dev.zio::zio-interop-cats:${V.zioInteropCats}" + val zioInteropReactiveStreams = + s"dev.zio::zio-interop-reactivestreams:${V.zioInteropReactiveStreams}" + val zioNIO = s"dev.zio::zio-nio:${V.zioNIO}" + val zioCli = s"dev.zio::zio-cli:${V.zioCli}" + val zioSchema = s"dev.zio::zio-schema:${V.zioSchema}" + val zioSchemaDerivation = s"dev.zio::zio-schema-derivation:${V.zioSchema}" + + // Collection of ZIO dependencies + val zioCore = List(zio, zioTest, zioTestSbt) + val zioJson_ = List(zioJson) + val zioAll = zioCore ++ List( + zioStreams, + zioConfig, + zioConfigTypesafe, + zioConfigMagnolia, + zioLogging, + zioPrelude + ) ++ zioJson_ + + // Database + val quill = s"io.getquill::quill-jdbc-zio:${V.quill}" + val magnum = s"com.augustnagro::magnum_3:${V.magnum}" + val magnumZIO = s"com.augustnagro::magnumzio_3:${V.magnum}" + val magnumPG = s"com.augustnagro::magnumpg_3:${V.magnum}" + + // HTTP + val tapirCore = s"com.softwaremill.sttp.tapir::tapir-core:${V.tapir}" + val tapirZIO = s"com.softwaremill.sttp.tapir::tapir-zio:${V.tapir}" + val tapirZIOJson = s"com.softwaremill.sttp.tapir::tapir-json-zio:${V.tapir}" + + // UI/Frontend + val scalatags = s"com.lihaoyi::scalatags:${V.scalatags}" + val laminar = s"com.raquo::laminar:${V.laminar}" + val waypoint = s"com.raquo::waypoint:${V.waypoint}" + val urlDsl = s"be.doeraene::url-dsl:${V.urlDsl}" + + // Testing + val scalaTest = s"org.scalatest::scalatest:${V.scalaTest}" + + // Logging + val logbackClassic = s"ch.qos.logback:logback-classic:${V.logbackClassic}" +} diff --git a/mill-iw-support/core/src/IWMillVersions.scala b/mill-iw-support/core/src/IWMillVersions.scala new file mode 100644 index 0000000..0ad9bd2 --- /dev/null +++ b/mill-iw-support/core/src/IWMillVersions.scala @@ -0,0 +1,56 @@ +package works.iterative.mill + +/** + * Centralized version management for Iterative Works projects using Mill. + * This object contains all library versions used across projects. + * + * Update this object to bump versions for all projects. + */ +object IWMillVersions { + // ZIO Ecosystem + val zio = "2.1.16" + val zioConfig = "4.0.3" + val zioCli = "0.7.0" + val zioInteropCats = "23.1.0.3" + val zioInteropReactiveStreams = "2.0.2" + val zioJson = "0.7.36" + val zioLogging = "2.5.0" + val zioNIO = "2.0.2" + val zioPrelude = "1.0.0-RC39" + val zioQuery = "0.7.6" + val zioSchema = "1.6.3" + + // Akka Ecosystem + val akka = "2.6.18" + val akkaHttp = "10.4.0" + + // Other Libraries + val cats = "2.13.0" + val utest = "0.8.2" + val caliban = "2.9.2" + val chimney = "1.7.3" + val ducktape = "0.2.7" + val elastic4s = "7.12.4" + val http4s = "0.23.30" + val http4sBlaze = "0.23.17" + val http4sPac4J = "5.0.0" + val laminar = "17.2.0" + val laminext = "0.17.0" + val logbackClassic = "1.5.17" + val magnum = "2.0.0-M1" + val pac4j = "6.0.3" + val play = "2.8.18" + val playJson = "2.9.3" + val quill = "4.8.6" + val scalaJsMacroTaskExecutor = "1.1.1" + val scalaJsJavaSecureRandom = "1.0.0" + val scalatags = "0.13.1" + val scalaTest = "3.2.19" + val slick = "3.3.3" + val sttpClient3 = "3.10.3" + val sttpClient4 = "4.0.0-RC1" + val support = "1.7.0" + val tapir = "1.11.16" + val urlDsl = "0.7.0" + val waypoint = "9.0.0" +} \ No newline at end of file diff --git a/mill-iw-support/core/src/IWPublishModule.scala b/mill-iw-support/core/src/IWPublishModule.scala new file mode 100644 index 0000000..5ae0ade --- /dev/null +++ b/mill-iw-support/core/src/IWPublishModule.scala @@ -0,0 +1,22 @@ +package works.iterative.mill + +import mill._ +import mill.scalalib._ +import mill.scalalib.publish._ + +/** Standard publish module trait for Iterative Works projects using Mill. This + * trait sets up standard publishing configuration for IW Maven repositories. + * + * To use this trait, simply extend it in your module and define: + * - publishVersion: The version to publish + * - pomSettings: The POM settings for the publication + * + * Use IW_USERNAME and IW_PASSWORD environment variables for authentication. + */ +trait IWPublishModule extends PublishModule { + + override def sonatypeUri = "https://dig.iterative.works/maven/releases" + override def sonatypeSnapshotUri = + "https://dig.iterative.works/maven/snapshots" + +} diff --git a/mill-iw-support/core/src/IWScalaModule.scala b/mill-iw-support/core/src/IWScalaModule.scala new file mode 100644 index 0000000..021bccc --- /dev/null +++ b/mill-iw-support/core/src/IWScalaModule.scala @@ -0,0 +1,97 @@ +package works.iterative.mill + +import mill._ +import mill.scalalib._ +import mill.scalalib.scalafmt._ + +/** Standard Scala module trait for Iterative Works projects using Mill. This + * trait sets up standard compiler options and configuration. + */ +trait IWScalaModule + extends ScalaModule + with ScalafmtModule + with IWScalaVersions { + + override def scalaVersion = scala3Version + + // Define base compiler options based on Scala version + def baseScalacOptions = T { + if (scalaVersion().startsWith("2.")) { + Seq( + "-encoding", + "utf8", + "-feature", + "-unchecked", + "-language:existentials", + "-language:experimental.macros", + "-language:higherKinds", + "-language:implicitConversions", + "-Xlint:adapted-args", + "-Xlint:constant", + "-Xlint:delayedinit-select", + "-Xlint:deprecation", + "-Xlint:doc-detached", + "-Xlint:implicit-recursion", + "-Xlint:implicit-not-found", + "-Xlint:inaccessible", + "-Xlint:infer-any", + "-Xlint:missing-interpolator", + "-Xlint:nullary-unit", + "-Xlint:option-implicit", + "-Xlint:package-object-classes", + "-Xlint:poly-implicit-overload", + "-Xlint:private-shadow", + "-Xlint:stars-align", + "-Xlint:strict-unsealed-patmat", + "-Xlint:type-parameter-shadow", + "-Xlint:-byname-implicit", + "-Wdead-code", + "-Wextra-implicit", + "-Wnumeric-widen", + "-Wvalue-discard", + "-Wnonunit-statement", + "-Wunused:nowarn", + "-Wunused:implicits", + "-Wunused:explicits", + "-Wunused:imports", + "-Wunused:locals", + "-Wunused:params", + "-Wunused:patvars", + "-Wunused:privates" + ) + } else { + Seq( + "-encoding", + "utf8", + "-deprecation", + "-feature", + "-unchecked", + "-language:experimental.macros", + "-language:higherKinds", + "-language:implicitConversions", + "-Xkind-projector", + "-Wvalue-discard", + "-Wnonunit-statement", + "-Wunused:implicits", + "-Wunused:explicits", + "-Wunused:imports", + "-Wunused:locals", + "-Wunused:params", + "-Wunused:privates" + ) + } + } + + // Enable SemanticDB for tooling + def semanticDbOptions = Seq( + "-Xsemanticdb" + ) + + // Override scalacOptions to include both base options and SemanticDB options + override def scalacOptions = T { + super.scalacOptions() ++ baseScalacOptions() ++ semanticDbOptions + } + + // Base test configuration + trait IWTests extends ScalaTests with ScalafmtModule +} diff --git a/mill-iw-support/.mill-version b/mill-iw-support/.mill-version new file mode 100644 index 0000000..54dbed4 --- /dev/null +++ b/mill-iw-support/.mill-version @@ -0,0 +1 @@ +0.12.10 diff --git a/mill-iw-support/.scalafmt.conf b/mill-iw-support/.scalafmt.conf new file mode 100644 index 0000000..ee7753a --- /dev/null +++ b/mill-iw-support/.scalafmt.conf @@ -0,0 +1,2 @@ +version = "3.7.15" +runner.dialect = scala213 \ No newline at end of file diff --git a/mill-iw-support/README.md b/mill-iw-support/README.md index 4986e5c..9bb1ba8 100644 --- a/mill-iw-support/README.md +++ b/mill-iw-support/README.md @@ -28,8 +28,45 @@ IWMillDeps.tapirCore // ivy"com.softwaremill.sttp.tapir::tapir-core:1.11.16" // Use dependency groups -IWMillDeps.useZIO() // Core ZIO dependencies with test framework -IWMillDeps.useZIOAll() // Comprehensive ZIO stack +IWMillDeps.zioCore // List of core ZIO dependencies +IWMillDeps.zioAll // List of all ZIO dependencies +``` + +### IWBomModule + +Bill of Materials (BOM) module for centralized dependency management: + +```scala +import mill._ +import works.iterative.mill._ + +// Define a BOM module +object bom extends IWBomModule { + def publishVersion = "0.1.0-SNAPSHOT" + + def pomSettings = PomSettings( + // Standard POM settings + ) + + // Override with custom dependencies if needed + override def depManagement = T { + super.depManagement() ++ Agg( + ivy"com.example::custom-lib:1.0.0" + ) + } +} + +// Use the BOM in modules +object myModule extends IWScalaModule { + // Reference the BOM module + override def bomModuleDeps = Seq(bom) + + // No need to specify versions - they come from the BOM + def ivyDeps = super.ivyDeps() ++ Agg( + ivy"dev.zio::zio", + ivy"dev.zio::zio-json" + ) +} ``` ### IWScalaModule @@ -44,10 +81,18 @@ // Use default Scala version (3.6.3) // and standard compiler options with SemanticDB enabled - def ivyDeps = super.ivyDeps() ++ IWMillDeps.useZIO() + def ivyDeps = super.ivyDeps() ++ Agg( + ivy"${IWMillDeps.zio}", + ivy"${IWMillDeps.zioJson}" + ) // Standard test module configuration - object test extends Tests with IWTests + object test extends ScalaTests with TestModule.ZioTest { + def ivyDeps = super.ivyDeps() ++ Agg( + ivy"${IWMillDeps.zioTest}", + ivy"${IWMillDeps.zioTestSbt}" + ) + } } ``` @@ -96,6 +141,51 @@ } ``` +## Dependency Management Approaches + +This library provides two approaches for dependency management: + +### 1. IWMillDeps (String Constants) + +Use the IWMillDeps object to access dependency strings with correct versions: + +```scala +def ivyDeps = super.ivyDeps() ++ Agg( + ivy"${IWMillDeps.zio}", + ivy"${IWMillDeps.zioJson}" +) +``` + +### 2. BOM (Bill of Materials) + +Use the IWBomModule to centralize dependency versions: + +```scala +// Define a BOM module +object bom extends IWBomModule { + def publishVersion = "0.1.0-SNAPSHOT" + def pomSettings = PomSettings(...) +} + +// Use the BOM in modules +object myModule extends IWScalaModule { + override def bomModuleDeps = Seq(bom) + + // No need to specify versions + def ivyDeps = super.ivyDeps() ++ Agg( + ivy"dev.zio::zio", + ivy"dev.zio::zio-json" + ) +} +``` + +The BOM approach offers these advantages: +- Enforces consistent versions across modules +- Simplifies version updates +- Controls transitive dependency versions +- Makes build files cleaner (no version strings) +- Can be published as a separate artifact + ## Examples See the `example` directory for a complete working example. diff --git a/mill-iw-support/build.sc b/mill-iw-support/build.sc index 5df80ae..2ed832c 100644 --- a/mill-iw-support/build.sc +++ b/mill-iw-support/build.sc @@ -2,20 +2,33 @@ import mill.scalalib._ import mill.scalalib.publish._ import mill.scalalib.scalafmt._ +import mill.scalalib.TestModule.Utest -object `mill-iw-support` extends Cross[IWMillSupportModule]("2.13.16", "3.6.3") +/** Main module for the mill-iw-support library. Built for Scala 2.13 which is + * compatible with Mill. + */ +object core extends ScalaModule with ScalafmtModule with PublishModule { + val millVersion = "0.12.10" -class IWMillSupportModule(val crossScalaVersion: String) extends CrossScalaModule with ScalafmtModule with PublishModule { + def scalaVersion = "2.13.16" + + def artifactName = "mill-iw-support" + def publishVersion = "0.1.0-SNAPSHOT" def pomSettings = PomSettings( description = "Iterative Works Mill Support Library", organization = "works.iterative", - url = "https://github.com/iterative-works/mill-iw-support", + url = "https://github.com/iterative-works/iw-project-support", licenses = Seq(License.MIT), - versionControl = VersionControl.github("iterative-works", "mill-iw-support"), + versionControl = + VersionControl.github("iterative-works", "iw-project-support"), developers = Seq( - Developer("mph", "Michal Přihoda", "https://github.com/iterative-works") + Developer( + "mprihoda", + "Michal Příhoda", + "https://github.com/iterative-works" + ) ) ) @@ -23,10 +36,15 @@ ivy"com.lihaoyi::os-lib:0.9.3" ) - object test extends Tests with ScalafmtModule { + // Mill's own dependencies needed for compilation + def compileIvyDeps = Agg( + ivy"com.lihaoyi::mill-main:${millVersion}", + ivy"com.lihaoyi::mill-scalalib:${millVersion}" + ) + + object test extends ScalaTests with Utest with ScalafmtModule { def ivyDeps = Agg( ivy"com.lihaoyi::utest:0.8.2" ) - def testFramework = "utest.runner.Framework" } -} \ No newline at end of file +} diff --git a/mill-iw-support/core/src/IWBomModule.scala b/mill-iw-support/core/src/IWBomModule.scala new file mode 100644 index 0000000..d8e70bb --- /dev/null +++ b/mill-iw-support/core/src/IWBomModule.scala @@ -0,0 +1,97 @@ +package works.iterative.mill + +import mill._ +import mill.scalalib._ +import mill.scalalib.publish._ + +/** Standard Bill of Materials (BOM) module for Iterative Works projects using + * Mill. This module centralizes dependency versioning across projects. + * + * A BOM module allows: + * 1. Centralized version management for dependencies 2. Enforcing specific + * versions of transitive dependencies 3. Applying uniform dependency + * management across multiple modules 4. Publishing a BOM artifact that + * other projects can consume + * + * Usage: + * 1. Create a BOM module in your build: + * ```scala + * object bom extends IWBomModule { + * def publishVersion = "0.1.0-SNAPSHOT" + * + * // Add your organization-specific customizations here + * def pomSettings = PomSettings(...) + * } + * ``` + * + * 2. Use the BOM in your modules: + * ```scala + * object myModule extends IWScalaModule { + * def bomModuleDeps = Seq(bom) + * + * def ivyDeps = Agg( + * // No need to specify versions as they come from the BOM + * ivy"dev.zio::zio", + * ivy"dev.zio::zio-json" + * ) + * } + * ``` + */ +trait IWBomModule extends BomModule with ScalaModule with IWScalaVersions { + + override def scalaVersion: T[String] = scala3Version + + /** The default implementation includes all standard IW dependencies from + * IWMillVersions and IWMillDeps. + */ + override def depManagement = T { + val V = IWMillVersions + + Agg( + // ZIO Core + ivy"dev.zio::zio:${V.zio}", + ivy"dev.zio::zio-test:${V.zio}", + ivy"dev.zio::zio-test-sbt:${V.zio}", + ivy"dev.zio::zio-streams:${V.zio}", + + // ZIO Extensions + ivy"dev.zio::zio-config:${V.zioConfig}", + ivy"dev.zio::zio-config-typesafe:${V.zioConfig}", + ivy"dev.zio::zio-config-magnolia:${V.zioConfig}", + ivy"dev.zio::zio-json:${V.zioJson}", + ivy"dev.zio::zio-logging:${V.zioLogging}", + ivy"dev.zio::zio-logging-slf4j:${V.zioLogging}", + ivy"dev.zio::zio-prelude:${V.zioPrelude}", + ivy"dev.zio::zio-interop-cats:${V.zioInteropCats}", + ivy"dev.zio::zio-interop-reactivestreams:${V.zioInteropReactiveStreams}", + ivy"dev.zio::zio-nio:${V.zioNIO}", + ivy"dev.zio::zio-cli:${V.zioCli}", + ivy"dev.zio::zio-schema:${V.zioSchema}", + ivy"dev.zio::zio-schema-derivation:${V.zioSchema}", + + // Database + ivy"io.getquill::quill-jdbc-zio:${V.quill}", + ivy"com.augustnagro::magnum:${V.magnum}", + ivy"com.augustnagro::magnumzio:${V.magnum}", + ivy"com.augustnagro::magnumpg:${V.magnum}", + + // HTTP + ivy"com.softwaremill.sttp.tapir::tapir-core:${V.tapir}", + ivy"com.softwaremill.sttp.tapir::tapir-zio:${V.tapir}", + ivy"com.softwaremill.sttp.tapir::tapir-json-zio:${V.tapir}", + + // UI/Frontend + ivy"com.lihaoyi::scalatags:${V.scalatags}", + ivy"com.raquo::laminar:${V.laminar}", + ivy"com.raquo::waypoint:${V.waypoint}", + ivy"be.doeraene::url-dsl:${V.urlDsl}", + + // Testing + ivy"org.scalatest::scalatest:${V.scalaTest}", + ivy"com.lihaoyi::utest:${V.utest}", + + // Logging + ivy"ch.qos.logback:logback-classic:${V.logbackClassic}" + ) + } +} diff --git a/mill-iw-support/core/src/IWMillDeps.scala b/mill-iw-support/core/src/IWMillDeps.scala new file mode 100644 index 0000000..d4cac1c --- /dev/null +++ b/mill-iw-support/core/src/IWMillDeps.scala @@ -0,0 +1,80 @@ +package works.iterative.mill + +import mill.api.Loose + +/** Standardized dependency management for Iterative Works projects using Mill. + * This object provides methods to get dependency strings with proper + * versioning. + * + * All versions are centralized in [[IWMillVersions]]. + * + * This object is designed to be used in build.sc files where the ivy string + * interpolator is available, like: + * + * {{{ + * def ivyDeps = Agg( + * ivy"$${IWMillDeps.zio}", + * ivy"$${IWMillDeps.zioJson}" + * ) + * }}} + */ +object IWMillDeps { + val V = IWMillVersions + + // ZIO Core + val zio = s"dev.zio::zio:${V.zio}" + val zioTest = s"dev.zio::zio-test:${V.zio}" + val zioTestSbt = s"dev.zio::zio-test-sbt:${V.zio}" + val zioStreams = s"dev.zio::zio-streams:${V.zio}" + + // ZIO Extensions + val zioConfig = s"dev.zio::zio-config:${V.zioConfig}" + val zioConfigTypesafe = s"dev.zio::zio-config-typesafe:${V.zioConfig}" + val zioConfigMagnolia = s"dev.zio::zio-config-magnolia:${V.zioConfig}" + val zioJson = s"dev.zio::zio-json:${V.zioJson}" + val zioLogging = s"dev.zio::zio-logging:${V.zioLogging}" + val zioLoggingSlf4j = s"dev.zio::zio-logging-slf4j:${V.zioLogging}" + val zioPrelude = s"dev.zio::zio-prelude:${V.zioPrelude}" + val zioInteropCats = s"dev.zio::zio-interop-cats:${V.zioInteropCats}" + val zioInteropReactiveStreams = + s"dev.zio::zio-interop-reactivestreams:${V.zioInteropReactiveStreams}" + val zioNIO = s"dev.zio::zio-nio:${V.zioNIO}" + val zioCli = s"dev.zio::zio-cli:${V.zioCli}" + val zioSchema = s"dev.zio::zio-schema:${V.zioSchema}" + val zioSchemaDerivation = s"dev.zio::zio-schema-derivation:${V.zioSchema}" + + // Collection of ZIO dependencies + val zioCore = List(zio, zioTest, zioTestSbt) + val zioJson_ = List(zioJson) + val zioAll = zioCore ++ List( + zioStreams, + zioConfig, + zioConfigTypesafe, + zioConfigMagnolia, + zioLogging, + zioPrelude + ) ++ zioJson_ + + // Database + val quill = s"io.getquill::quill-jdbc-zio:${V.quill}" + val magnum = s"com.augustnagro::magnum_3:${V.magnum}" + val magnumZIO = s"com.augustnagro::magnumzio_3:${V.magnum}" + val magnumPG = s"com.augustnagro::magnumpg_3:${V.magnum}" + + // HTTP + val tapirCore = s"com.softwaremill.sttp.tapir::tapir-core:${V.tapir}" + val tapirZIO = s"com.softwaremill.sttp.tapir::tapir-zio:${V.tapir}" + val tapirZIOJson = s"com.softwaremill.sttp.tapir::tapir-json-zio:${V.tapir}" + + // UI/Frontend + val scalatags = s"com.lihaoyi::scalatags:${V.scalatags}" + val laminar = s"com.raquo::laminar:${V.laminar}" + val waypoint = s"com.raquo::waypoint:${V.waypoint}" + val urlDsl = s"be.doeraene::url-dsl:${V.urlDsl}" + + // Testing + val scalaTest = s"org.scalatest::scalatest:${V.scalaTest}" + + // Logging + val logbackClassic = s"ch.qos.logback:logback-classic:${V.logbackClassic}" +} diff --git a/mill-iw-support/core/src/IWMillVersions.scala b/mill-iw-support/core/src/IWMillVersions.scala new file mode 100644 index 0000000..0ad9bd2 --- /dev/null +++ b/mill-iw-support/core/src/IWMillVersions.scala @@ -0,0 +1,56 @@ +package works.iterative.mill + +/** + * Centralized version management for Iterative Works projects using Mill. + * This object contains all library versions used across projects. + * + * Update this object to bump versions for all projects. + */ +object IWMillVersions { + // ZIO Ecosystem + val zio = "2.1.16" + val zioConfig = "4.0.3" + val zioCli = "0.7.0" + val zioInteropCats = "23.1.0.3" + val zioInteropReactiveStreams = "2.0.2" + val zioJson = "0.7.36" + val zioLogging = "2.5.0" + val zioNIO = "2.0.2" + val zioPrelude = "1.0.0-RC39" + val zioQuery = "0.7.6" + val zioSchema = "1.6.3" + + // Akka Ecosystem + val akka = "2.6.18" + val akkaHttp = "10.4.0" + + // Other Libraries + val cats = "2.13.0" + val utest = "0.8.2" + val caliban = "2.9.2" + val chimney = "1.7.3" + val ducktape = "0.2.7" + val elastic4s = "7.12.4" + val http4s = "0.23.30" + val http4sBlaze = "0.23.17" + val http4sPac4J = "5.0.0" + val laminar = "17.2.0" + val laminext = "0.17.0" + val logbackClassic = "1.5.17" + val magnum = "2.0.0-M1" + val pac4j = "6.0.3" + val play = "2.8.18" + val playJson = "2.9.3" + val quill = "4.8.6" + val scalaJsMacroTaskExecutor = "1.1.1" + val scalaJsJavaSecureRandom = "1.0.0" + val scalatags = "0.13.1" + val scalaTest = "3.2.19" + val slick = "3.3.3" + val sttpClient3 = "3.10.3" + val sttpClient4 = "4.0.0-RC1" + val support = "1.7.0" + val tapir = "1.11.16" + val urlDsl = "0.7.0" + val waypoint = "9.0.0" +} \ No newline at end of file diff --git a/mill-iw-support/core/src/IWPublishModule.scala b/mill-iw-support/core/src/IWPublishModule.scala new file mode 100644 index 0000000..5ae0ade --- /dev/null +++ b/mill-iw-support/core/src/IWPublishModule.scala @@ -0,0 +1,22 @@ +package works.iterative.mill + +import mill._ +import mill.scalalib._ +import mill.scalalib.publish._ + +/** Standard publish module trait for Iterative Works projects using Mill. This + * trait sets up standard publishing configuration for IW Maven repositories. + * + * To use this trait, simply extend it in your module and define: + * - publishVersion: The version to publish + * - pomSettings: The POM settings for the publication + * + * Use IW_USERNAME and IW_PASSWORD environment variables for authentication. + */ +trait IWPublishModule extends PublishModule { + + override def sonatypeUri = "https://dig.iterative.works/maven/releases" + override def sonatypeSnapshotUri = + "https://dig.iterative.works/maven/snapshots" + +} diff --git a/mill-iw-support/core/src/IWScalaModule.scala b/mill-iw-support/core/src/IWScalaModule.scala new file mode 100644 index 0000000..021bccc --- /dev/null +++ b/mill-iw-support/core/src/IWScalaModule.scala @@ -0,0 +1,97 @@ +package works.iterative.mill + +import mill._ +import mill.scalalib._ +import mill.scalalib.scalafmt._ + +/** Standard Scala module trait for Iterative Works projects using Mill. This + * trait sets up standard compiler options and configuration. + */ +trait IWScalaModule + extends ScalaModule + with ScalafmtModule + with IWScalaVersions { + + override def scalaVersion = scala3Version + + // Define base compiler options based on Scala version + def baseScalacOptions = T { + if (scalaVersion().startsWith("2.")) { + Seq( + "-encoding", + "utf8", + "-feature", + "-unchecked", + "-language:existentials", + "-language:experimental.macros", + "-language:higherKinds", + "-language:implicitConversions", + "-Xlint:adapted-args", + "-Xlint:constant", + "-Xlint:delayedinit-select", + "-Xlint:deprecation", + "-Xlint:doc-detached", + "-Xlint:implicit-recursion", + "-Xlint:implicit-not-found", + "-Xlint:inaccessible", + "-Xlint:infer-any", + "-Xlint:missing-interpolator", + "-Xlint:nullary-unit", + "-Xlint:option-implicit", + "-Xlint:package-object-classes", + "-Xlint:poly-implicit-overload", + "-Xlint:private-shadow", + "-Xlint:stars-align", + "-Xlint:strict-unsealed-patmat", + "-Xlint:type-parameter-shadow", + "-Xlint:-byname-implicit", + "-Wdead-code", + "-Wextra-implicit", + "-Wnumeric-widen", + "-Wvalue-discard", + "-Wnonunit-statement", + "-Wunused:nowarn", + "-Wunused:implicits", + "-Wunused:explicits", + "-Wunused:imports", + "-Wunused:locals", + "-Wunused:params", + "-Wunused:patvars", + "-Wunused:privates" + ) + } else { + Seq( + "-encoding", + "utf8", + "-deprecation", + "-feature", + "-unchecked", + "-language:experimental.macros", + "-language:higherKinds", + "-language:implicitConversions", + "-Xkind-projector", + "-Wvalue-discard", + "-Wnonunit-statement", + "-Wunused:implicits", + "-Wunused:explicits", + "-Wunused:imports", + "-Wunused:locals", + "-Wunused:params", + "-Wunused:privates" + ) + } + } + + // Enable SemanticDB for tooling + def semanticDbOptions = Seq( + "-Xsemanticdb" + ) + + // Override scalacOptions to include both base options and SemanticDB options + override def scalacOptions = T { + super.scalacOptions() ++ baseScalacOptions() ++ semanticDbOptions + } + + // Base test configuration + trait IWTests extends ScalaTests with ScalafmtModule +} diff --git a/mill-iw-support/core/src/IWScalaVersions.scala b/mill-iw-support/core/src/IWScalaVersions.scala new file mode 100644 index 0000000..aa70cb6 --- /dev/null +++ b/mill-iw-support/core/src/IWScalaVersions.scala @@ -0,0 +1,9 @@ +package works.iterative.mill + +trait IWScalaVersions { + val scala2Version = "2.13.16" + val scala3Version = "3.6.4" + val scala3LTSVersion = "3.3.5" +} + +object IWScalaVersions extends IWScalaVersions diff --git a/mill-iw-support/.mill-version b/mill-iw-support/.mill-version new file mode 100644 index 0000000..54dbed4 --- /dev/null +++ b/mill-iw-support/.mill-version @@ -0,0 +1 @@ +0.12.10 diff --git a/mill-iw-support/.scalafmt.conf b/mill-iw-support/.scalafmt.conf new file mode 100644 index 0000000..ee7753a --- /dev/null +++ b/mill-iw-support/.scalafmt.conf @@ -0,0 +1,2 @@ +version = "3.7.15" +runner.dialect = scala213 \ No newline at end of file diff --git a/mill-iw-support/README.md b/mill-iw-support/README.md index 4986e5c..9bb1ba8 100644 --- a/mill-iw-support/README.md +++ b/mill-iw-support/README.md @@ -28,8 +28,45 @@ IWMillDeps.tapirCore // ivy"com.softwaremill.sttp.tapir::tapir-core:1.11.16" // Use dependency groups -IWMillDeps.useZIO() // Core ZIO dependencies with test framework -IWMillDeps.useZIOAll() // Comprehensive ZIO stack +IWMillDeps.zioCore // List of core ZIO dependencies +IWMillDeps.zioAll // List of all ZIO dependencies +``` + +### IWBomModule + +Bill of Materials (BOM) module for centralized dependency management: + +```scala +import mill._ +import works.iterative.mill._ + +// Define a BOM module +object bom extends IWBomModule { + def publishVersion = "0.1.0-SNAPSHOT" + + def pomSettings = PomSettings( + // Standard POM settings + ) + + // Override with custom dependencies if needed + override def depManagement = T { + super.depManagement() ++ Agg( + ivy"com.example::custom-lib:1.0.0" + ) + } +} + +// Use the BOM in modules +object myModule extends IWScalaModule { + // Reference the BOM module + override def bomModuleDeps = Seq(bom) + + // No need to specify versions - they come from the BOM + def ivyDeps = super.ivyDeps() ++ Agg( + ivy"dev.zio::zio", + ivy"dev.zio::zio-json" + ) +} ``` ### IWScalaModule @@ -44,10 +81,18 @@ // Use default Scala version (3.6.3) // and standard compiler options with SemanticDB enabled - def ivyDeps = super.ivyDeps() ++ IWMillDeps.useZIO() + def ivyDeps = super.ivyDeps() ++ Agg( + ivy"${IWMillDeps.zio}", + ivy"${IWMillDeps.zioJson}" + ) // Standard test module configuration - object test extends Tests with IWTests + object test extends ScalaTests with TestModule.ZioTest { + def ivyDeps = super.ivyDeps() ++ Agg( + ivy"${IWMillDeps.zioTest}", + ivy"${IWMillDeps.zioTestSbt}" + ) + } } ``` @@ -96,6 +141,51 @@ } ``` +## Dependency Management Approaches + +This library provides two approaches for dependency management: + +### 1. IWMillDeps (String Constants) + +Use the IWMillDeps object to access dependency strings with correct versions: + +```scala +def ivyDeps = super.ivyDeps() ++ Agg( + ivy"${IWMillDeps.zio}", + ivy"${IWMillDeps.zioJson}" +) +``` + +### 2. BOM (Bill of Materials) + +Use the IWBomModule to centralize dependency versions: + +```scala +// Define a BOM module +object bom extends IWBomModule { + def publishVersion = "0.1.0-SNAPSHOT" + def pomSettings = PomSettings(...) +} + +// Use the BOM in modules +object myModule extends IWScalaModule { + override def bomModuleDeps = Seq(bom) + + // No need to specify versions + def ivyDeps = super.ivyDeps() ++ Agg( + ivy"dev.zio::zio", + ivy"dev.zio::zio-json" + ) +} +``` + +The BOM approach offers these advantages: +- Enforces consistent versions across modules +- Simplifies version updates +- Controls transitive dependency versions +- Makes build files cleaner (no version strings) +- Can be published as a separate artifact + ## Examples See the `example` directory for a complete working example. diff --git a/mill-iw-support/build.sc b/mill-iw-support/build.sc index 5df80ae..2ed832c 100644 --- a/mill-iw-support/build.sc +++ b/mill-iw-support/build.sc @@ -2,20 +2,33 @@ import mill.scalalib._ import mill.scalalib.publish._ import mill.scalalib.scalafmt._ +import mill.scalalib.TestModule.Utest -object `mill-iw-support` extends Cross[IWMillSupportModule]("2.13.16", "3.6.3") +/** Main module for the mill-iw-support library. Built for Scala 2.13 which is + * compatible with Mill. + */ +object core extends ScalaModule with ScalafmtModule with PublishModule { + val millVersion = "0.12.10" -class IWMillSupportModule(val crossScalaVersion: String) extends CrossScalaModule with ScalafmtModule with PublishModule { + def scalaVersion = "2.13.16" + + def artifactName = "mill-iw-support" + def publishVersion = "0.1.0-SNAPSHOT" def pomSettings = PomSettings( description = "Iterative Works Mill Support Library", organization = "works.iterative", - url = "https://github.com/iterative-works/mill-iw-support", + url = "https://github.com/iterative-works/iw-project-support", licenses = Seq(License.MIT), - versionControl = VersionControl.github("iterative-works", "mill-iw-support"), + versionControl = + VersionControl.github("iterative-works", "iw-project-support"), developers = Seq( - Developer("mph", "Michal Přihoda", "https://github.com/iterative-works") + Developer( + "mprihoda", + "Michal Příhoda", + "https://github.com/iterative-works" + ) ) ) @@ -23,10 +36,15 @@ ivy"com.lihaoyi::os-lib:0.9.3" ) - object test extends Tests with ScalafmtModule { + // Mill's own dependencies needed for compilation + def compileIvyDeps = Agg( + ivy"com.lihaoyi::mill-main:${millVersion}", + ivy"com.lihaoyi::mill-scalalib:${millVersion}" + ) + + object test extends ScalaTests with Utest with ScalafmtModule { def ivyDeps = Agg( ivy"com.lihaoyi::utest:0.8.2" ) - def testFramework = "utest.runner.Framework" } -} \ No newline at end of file +} diff --git a/mill-iw-support/core/src/IWBomModule.scala b/mill-iw-support/core/src/IWBomModule.scala new file mode 100644 index 0000000..d8e70bb --- /dev/null +++ b/mill-iw-support/core/src/IWBomModule.scala @@ -0,0 +1,97 @@ +package works.iterative.mill + +import mill._ +import mill.scalalib._ +import mill.scalalib.publish._ + +/** Standard Bill of Materials (BOM) module for Iterative Works projects using + * Mill. This module centralizes dependency versioning across projects. + * + * A BOM module allows: + * 1. Centralized version management for dependencies 2. Enforcing specific + * versions of transitive dependencies 3. Applying uniform dependency + * management across multiple modules 4. Publishing a BOM artifact that + * other projects can consume + * + * Usage: + * 1. Create a BOM module in your build: + * ```scala + * object bom extends IWBomModule { + * def publishVersion = "0.1.0-SNAPSHOT" + * + * // Add your organization-specific customizations here + * def pomSettings = PomSettings(...) + * } + * ``` + * + * 2. Use the BOM in your modules: + * ```scala + * object myModule extends IWScalaModule { + * def bomModuleDeps = Seq(bom) + * + * def ivyDeps = Agg( + * // No need to specify versions as they come from the BOM + * ivy"dev.zio::zio", + * ivy"dev.zio::zio-json" + * ) + * } + * ``` + */ +trait IWBomModule extends BomModule with ScalaModule with IWScalaVersions { + + override def scalaVersion: T[String] = scala3Version + + /** The default implementation includes all standard IW dependencies from + * IWMillVersions and IWMillDeps. + */ + override def depManagement = T { + val V = IWMillVersions + + Agg( + // ZIO Core + ivy"dev.zio::zio:${V.zio}", + ivy"dev.zio::zio-test:${V.zio}", + ivy"dev.zio::zio-test-sbt:${V.zio}", + ivy"dev.zio::zio-streams:${V.zio}", + + // ZIO Extensions + ivy"dev.zio::zio-config:${V.zioConfig}", + ivy"dev.zio::zio-config-typesafe:${V.zioConfig}", + ivy"dev.zio::zio-config-magnolia:${V.zioConfig}", + ivy"dev.zio::zio-json:${V.zioJson}", + ivy"dev.zio::zio-logging:${V.zioLogging}", + ivy"dev.zio::zio-logging-slf4j:${V.zioLogging}", + ivy"dev.zio::zio-prelude:${V.zioPrelude}", + ivy"dev.zio::zio-interop-cats:${V.zioInteropCats}", + ivy"dev.zio::zio-interop-reactivestreams:${V.zioInteropReactiveStreams}", + ivy"dev.zio::zio-nio:${V.zioNIO}", + ivy"dev.zio::zio-cli:${V.zioCli}", + ivy"dev.zio::zio-schema:${V.zioSchema}", + ivy"dev.zio::zio-schema-derivation:${V.zioSchema}", + + // Database + ivy"io.getquill::quill-jdbc-zio:${V.quill}", + ivy"com.augustnagro::magnum:${V.magnum}", + ivy"com.augustnagro::magnumzio:${V.magnum}", + ivy"com.augustnagro::magnumpg:${V.magnum}", + + // HTTP + ivy"com.softwaremill.sttp.tapir::tapir-core:${V.tapir}", + ivy"com.softwaremill.sttp.tapir::tapir-zio:${V.tapir}", + ivy"com.softwaremill.sttp.tapir::tapir-json-zio:${V.tapir}", + + // UI/Frontend + ivy"com.lihaoyi::scalatags:${V.scalatags}", + ivy"com.raquo::laminar:${V.laminar}", + ivy"com.raquo::waypoint:${V.waypoint}", + ivy"be.doeraene::url-dsl:${V.urlDsl}", + + // Testing + ivy"org.scalatest::scalatest:${V.scalaTest}", + ivy"com.lihaoyi::utest:${V.utest}", + + // Logging + ivy"ch.qos.logback:logback-classic:${V.logbackClassic}" + ) + } +} diff --git a/mill-iw-support/core/src/IWMillDeps.scala b/mill-iw-support/core/src/IWMillDeps.scala new file mode 100644 index 0000000..d4cac1c --- /dev/null +++ b/mill-iw-support/core/src/IWMillDeps.scala @@ -0,0 +1,80 @@ +package works.iterative.mill + +import mill.api.Loose + +/** Standardized dependency management for Iterative Works projects using Mill. + * This object provides methods to get dependency strings with proper + * versioning. + * + * All versions are centralized in [[IWMillVersions]]. + * + * This object is designed to be used in build.sc files where the ivy string + * interpolator is available, like: + * + * {{{ + * def ivyDeps = Agg( + * ivy"$${IWMillDeps.zio}", + * ivy"$${IWMillDeps.zioJson}" + * ) + * }}} + */ +object IWMillDeps { + val V = IWMillVersions + + // ZIO Core + val zio = s"dev.zio::zio:${V.zio}" + val zioTest = s"dev.zio::zio-test:${V.zio}" + val zioTestSbt = s"dev.zio::zio-test-sbt:${V.zio}" + val zioStreams = s"dev.zio::zio-streams:${V.zio}" + + // ZIO Extensions + val zioConfig = s"dev.zio::zio-config:${V.zioConfig}" + val zioConfigTypesafe = s"dev.zio::zio-config-typesafe:${V.zioConfig}" + val zioConfigMagnolia = s"dev.zio::zio-config-magnolia:${V.zioConfig}" + val zioJson = s"dev.zio::zio-json:${V.zioJson}" + val zioLogging = s"dev.zio::zio-logging:${V.zioLogging}" + val zioLoggingSlf4j = s"dev.zio::zio-logging-slf4j:${V.zioLogging}" + val zioPrelude = s"dev.zio::zio-prelude:${V.zioPrelude}" + val zioInteropCats = s"dev.zio::zio-interop-cats:${V.zioInteropCats}" + val zioInteropReactiveStreams = + s"dev.zio::zio-interop-reactivestreams:${V.zioInteropReactiveStreams}" + val zioNIO = s"dev.zio::zio-nio:${V.zioNIO}" + val zioCli = s"dev.zio::zio-cli:${V.zioCli}" + val zioSchema = s"dev.zio::zio-schema:${V.zioSchema}" + val zioSchemaDerivation = s"dev.zio::zio-schema-derivation:${V.zioSchema}" + + // Collection of ZIO dependencies + val zioCore = List(zio, zioTest, zioTestSbt) + val zioJson_ = List(zioJson) + val zioAll = zioCore ++ List( + zioStreams, + zioConfig, + zioConfigTypesafe, + zioConfigMagnolia, + zioLogging, + zioPrelude + ) ++ zioJson_ + + // Database + val quill = s"io.getquill::quill-jdbc-zio:${V.quill}" + val magnum = s"com.augustnagro::magnum_3:${V.magnum}" + val magnumZIO = s"com.augustnagro::magnumzio_3:${V.magnum}" + val magnumPG = s"com.augustnagro::magnumpg_3:${V.magnum}" + + // HTTP + val tapirCore = s"com.softwaremill.sttp.tapir::tapir-core:${V.tapir}" + val tapirZIO = s"com.softwaremill.sttp.tapir::tapir-zio:${V.tapir}" + val tapirZIOJson = s"com.softwaremill.sttp.tapir::tapir-json-zio:${V.tapir}" + + // UI/Frontend + val scalatags = s"com.lihaoyi::scalatags:${V.scalatags}" + val laminar = s"com.raquo::laminar:${V.laminar}" + val waypoint = s"com.raquo::waypoint:${V.waypoint}" + val urlDsl = s"be.doeraene::url-dsl:${V.urlDsl}" + + // Testing + val scalaTest = s"org.scalatest::scalatest:${V.scalaTest}" + + // Logging + val logbackClassic = s"ch.qos.logback:logback-classic:${V.logbackClassic}" +} diff --git a/mill-iw-support/core/src/IWMillVersions.scala b/mill-iw-support/core/src/IWMillVersions.scala new file mode 100644 index 0000000..0ad9bd2 --- /dev/null +++ b/mill-iw-support/core/src/IWMillVersions.scala @@ -0,0 +1,56 @@ +package works.iterative.mill + +/** + * Centralized version management for Iterative Works projects using Mill. + * This object contains all library versions used across projects. + * + * Update this object to bump versions for all projects. + */ +object IWMillVersions { + // ZIO Ecosystem + val zio = "2.1.16" + val zioConfig = "4.0.3" + val zioCli = "0.7.0" + val zioInteropCats = "23.1.0.3" + val zioInteropReactiveStreams = "2.0.2" + val zioJson = "0.7.36" + val zioLogging = "2.5.0" + val zioNIO = "2.0.2" + val zioPrelude = "1.0.0-RC39" + val zioQuery = "0.7.6" + val zioSchema = "1.6.3" + + // Akka Ecosystem + val akka = "2.6.18" + val akkaHttp = "10.4.0" + + // Other Libraries + val cats = "2.13.0" + val utest = "0.8.2" + val caliban = "2.9.2" + val chimney = "1.7.3" + val ducktape = "0.2.7" + val elastic4s = "7.12.4" + val http4s = "0.23.30" + val http4sBlaze = "0.23.17" + val http4sPac4J = "5.0.0" + val laminar = "17.2.0" + val laminext = "0.17.0" + val logbackClassic = "1.5.17" + val magnum = "2.0.0-M1" + val pac4j = "6.0.3" + val play = "2.8.18" + val playJson = "2.9.3" + val quill = "4.8.6" + val scalaJsMacroTaskExecutor = "1.1.1" + val scalaJsJavaSecureRandom = "1.0.0" + val scalatags = "0.13.1" + val scalaTest = "3.2.19" + val slick = "3.3.3" + val sttpClient3 = "3.10.3" + val sttpClient4 = "4.0.0-RC1" + val support = "1.7.0" + val tapir = "1.11.16" + val urlDsl = "0.7.0" + val waypoint = "9.0.0" +} \ No newline at end of file diff --git a/mill-iw-support/core/src/IWPublishModule.scala b/mill-iw-support/core/src/IWPublishModule.scala new file mode 100644 index 0000000..5ae0ade --- /dev/null +++ b/mill-iw-support/core/src/IWPublishModule.scala @@ -0,0 +1,22 @@ +package works.iterative.mill + +import mill._ +import mill.scalalib._ +import mill.scalalib.publish._ + +/** Standard publish module trait for Iterative Works projects using Mill. This + * trait sets up standard publishing configuration for IW Maven repositories. + * + * To use this trait, simply extend it in your module and define: + * - publishVersion: The version to publish + * - pomSettings: The POM settings for the publication + * + * Use IW_USERNAME and IW_PASSWORD environment variables for authentication. + */ +trait IWPublishModule extends PublishModule { + + override def sonatypeUri = "https://dig.iterative.works/maven/releases" + override def sonatypeSnapshotUri = + "https://dig.iterative.works/maven/snapshots" + +} diff --git a/mill-iw-support/core/src/IWScalaModule.scala b/mill-iw-support/core/src/IWScalaModule.scala new file mode 100644 index 0000000..021bccc --- /dev/null +++ b/mill-iw-support/core/src/IWScalaModule.scala @@ -0,0 +1,97 @@ +package works.iterative.mill + +import mill._ +import mill.scalalib._ +import mill.scalalib.scalafmt._ + +/** Standard Scala module trait for Iterative Works projects using Mill. This + * trait sets up standard compiler options and configuration. + */ +trait IWScalaModule + extends ScalaModule + with ScalafmtModule + with IWScalaVersions { + + override def scalaVersion = scala3Version + + // Define base compiler options based on Scala version + def baseScalacOptions = T { + if (scalaVersion().startsWith("2.")) { + Seq( + "-encoding", + "utf8", + "-feature", + "-unchecked", + "-language:existentials", + "-language:experimental.macros", + "-language:higherKinds", + "-language:implicitConversions", + "-Xlint:adapted-args", + "-Xlint:constant", + "-Xlint:delayedinit-select", + "-Xlint:deprecation", + "-Xlint:doc-detached", + "-Xlint:implicit-recursion", + "-Xlint:implicit-not-found", + "-Xlint:inaccessible", + "-Xlint:infer-any", + "-Xlint:missing-interpolator", + "-Xlint:nullary-unit", + "-Xlint:option-implicit", + "-Xlint:package-object-classes", + "-Xlint:poly-implicit-overload", + "-Xlint:private-shadow", + "-Xlint:stars-align", + "-Xlint:strict-unsealed-patmat", + "-Xlint:type-parameter-shadow", + "-Xlint:-byname-implicit", + "-Wdead-code", + "-Wextra-implicit", + "-Wnumeric-widen", + "-Wvalue-discard", + "-Wnonunit-statement", + "-Wunused:nowarn", + "-Wunused:implicits", + "-Wunused:explicits", + "-Wunused:imports", + "-Wunused:locals", + "-Wunused:params", + "-Wunused:patvars", + "-Wunused:privates" + ) + } else { + Seq( + "-encoding", + "utf8", + "-deprecation", + "-feature", + "-unchecked", + "-language:experimental.macros", + "-language:higherKinds", + "-language:implicitConversions", + "-Xkind-projector", + "-Wvalue-discard", + "-Wnonunit-statement", + "-Wunused:implicits", + "-Wunused:explicits", + "-Wunused:imports", + "-Wunused:locals", + "-Wunused:params", + "-Wunused:privates" + ) + } + } + + // Enable SemanticDB for tooling + def semanticDbOptions = Seq( + "-Xsemanticdb" + ) + + // Override scalacOptions to include both base options and SemanticDB options + override def scalacOptions = T { + super.scalacOptions() ++ baseScalacOptions() ++ semanticDbOptions + } + + // Base test configuration + trait IWTests extends ScalaTests with ScalafmtModule +} diff --git a/mill-iw-support/core/src/IWScalaVersions.scala b/mill-iw-support/core/src/IWScalaVersions.scala new file mode 100644 index 0000000..aa70cb6 --- /dev/null +++ b/mill-iw-support/core/src/IWScalaVersions.scala @@ -0,0 +1,9 @@ +package works.iterative.mill + +trait IWScalaVersions { + val scala2Version = "2.13.16" + val scala3Version = "3.6.4" + val scala3LTSVersion = "3.3.5" +} + +object IWScalaVersions extends IWScalaVersions diff --git a/mill-iw-support/core/src/package.scala b/mill-iw-support/core/src/package.scala new file mode 100644 index 0000000..1ba4556 --- /dev/null +++ b/mill-iw-support/core/src/package.scala @@ -0,0 +1,6 @@ +package works.iterative + +package object mill { + val IWVersions = IWMillVersions + val IWDeps = IWMillDeps +} diff --git a/mill-iw-support/.mill-version b/mill-iw-support/.mill-version new file mode 100644 index 0000000..54dbed4 --- /dev/null +++ b/mill-iw-support/.mill-version @@ -0,0 +1 @@ +0.12.10 diff --git a/mill-iw-support/.scalafmt.conf b/mill-iw-support/.scalafmt.conf new file mode 100644 index 0000000..ee7753a --- /dev/null +++ b/mill-iw-support/.scalafmt.conf @@ -0,0 +1,2 @@ +version = "3.7.15" +runner.dialect = scala213 \ No newline at end of file diff --git a/mill-iw-support/README.md b/mill-iw-support/README.md index 4986e5c..9bb1ba8 100644 --- a/mill-iw-support/README.md +++ b/mill-iw-support/README.md @@ -28,8 +28,45 @@ IWMillDeps.tapirCore // ivy"com.softwaremill.sttp.tapir::tapir-core:1.11.16" // Use dependency groups -IWMillDeps.useZIO() // Core ZIO dependencies with test framework -IWMillDeps.useZIOAll() // Comprehensive ZIO stack +IWMillDeps.zioCore // List of core ZIO dependencies +IWMillDeps.zioAll // List of all ZIO dependencies +``` + +### IWBomModule + +Bill of Materials (BOM) module for centralized dependency management: + +```scala +import mill._ +import works.iterative.mill._ + +// Define a BOM module +object bom extends IWBomModule { + def publishVersion = "0.1.0-SNAPSHOT" + + def pomSettings = PomSettings( + // Standard POM settings + ) + + // Override with custom dependencies if needed + override def depManagement = T { + super.depManagement() ++ Agg( + ivy"com.example::custom-lib:1.0.0" + ) + } +} + +// Use the BOM in modules +object myModule extends IWScalaModule { + // Reference the BOM module + override def bomModuleDeps = Seq(bom) + + // No need to specify versions - they come from the BOM + def ivyDeps = super.ivyDeps() ++ Agg( + ivy"dev.zio::zio", + ivy"dev.zio::zio-json" + ) +} ``` ### IWScalaModule @@ -44,10 +81,18 @@ // Use default Scala version (3.6.3) // and standard compiler options with SemanticDB enabled - def ivyDeps = super.ivyDeps() ++ IWMillDeps.useZIO() + def ivyDeps = super.ivyDeps() ++ Agg( + ivy"${IWMillDeps.zio}", + ivy"${IWMillDeps.zioJson}" + ) // Standard test module configuration - object test extends Tests with IWTests + object test extends ScalaTests with TestModule.ZioTest { + def ivyDeps = super.ivyDeps() ++ Agg( + ivy"${IWMillDeps.zioTest}", + ivy"${IWMillDeps.zioTestSbt}" + ) + } } ``` @@ -96,6 +141,51 @@ } ``` +## Dependency Management Approaches + +This library provides two approaches for dependency management: + +### 1. IWMillDeps (String Constants) + +Use the IWMillDeps object to access dependency strings with correct versions: + +```scala +def ivyDeps = super.ivyDeps() ++ Agg( + ivy"${IWMillDeps.zio}", + ivy"${IWMillDeps.zioJson}" +) +``` + +### 2. BOM (Bill of Materials) + +Use the IWBomModule to centralize dependency versions: + +```scala +// Define a BOM module +object bom extends IWBomModule { + def publishVersion = "0.1.0-SNAPSHOT" + def pomSettings = PomSettings(...) +} + +// Use the BOM in modules +object myModule extends IWScalaModule { + override def bomModuleDeps = Seq(bom) + + // No need to specify versions + def ivyDeps = super.ivyDeps() ++ Agg( + ivy"dev.zio::zio", + ivy"dev.zio::zio-json" + ) +} +``` + +The BOM approach offers these advantages: +- Enforces consistent versions across modules +- Simplifies version updates +- Controls transitive dependency versions +- Makes build files cleaner (no version strings) +- Can be published as a separate artifact + ## Examples See the `example` directory for a complete working example. diff --git a/mill-iw-support/build.sc b/mill-iw-support/build.sc index 5df80ae..2ed832c 100644 --- a/mill-iw-support/build.sc +++ b/mill-iw-support/build.sc @@ -2,20 +2,33 @@ import mill.scalalib._ import mill.scalalib.publish._ import mill.scalalib.scalafmt._ +import mill.scalalib.TestModule.Utest -object `mill-iw-support` extends Cross[IWMillSupportModule]("2.13.16", "3.6.3") +/** Main module for the mill-iw-support library. Built for Scala 2.13 which is + * compatible with Mill. + */ +object core extends ScalaModule with ScalafmtModule with PublishModule { + val millVersion = "0.12.10" -class IWMillSupportModule(val crossScalaVersion: String) extends CrossScalaModule with ScalafmtModule with PublishModule { + def scalaVersion = "2.13.16" + + def artifactName = "mill-iw-support" + def publishVersion = "0.1.0-SNAPSHOT" def pomSettings = PomSettings( description = "Iterative Works Mill Support Library", organization = "works.iterative", - url = "https://github.com/iterative-works/mill-iw-support", + url = "https://github.com/iterative-works/iw-project-support", licenses = Seq(License.MIT), - versionControl = VersionControl.github("iterative-works", "mill-iw-support"), + versionControl = + VersionControl.github("iterative-works", "iw-project-support"), developers = Seq( - Developer("mph", "Michal Přihoda", "https://github.com/iterative-works") + Developer( + "mprihoda", + "Michal Příhoda", + "https://github.com/iterative-works" + ) ) ) @@ -23,10 +36,15 @@ ivy"com.lihaoyi::os-lib:0.9.3" ) - object test extends Tests with ScalafmtModule { + // Mill's own dependencies needed for compilation + def compileIvyDeps = Agg( + ivy"com.lihaoyi::mill-main:${millVersion}", + ivy"com.lihaoyi::mill-scalalib:${millVersion}" + ) + + object test extends ScalaTests with Utest with ScalafmtModule { def ivyDeps = Agg( ivy"com.lihaoyi::utest:0.8.2" ) - def testFramework = "utest.runner.Framework" } -} \ No newline at end of file +} diff --git a/mill-iw-support/core/src/IWBomModule.scala b/mill-iw-support/core/src/IWBomModule.scala new file mode 100644 index 0000000..d8e70bb --- /dev/null +++ b/mill-iw-support/core/src/IWBomModule.scala @@ -0,0 +1,97 @@ +package works.iterative.mill + +import mill._ +import mill.scalalib._ +import mill.scalalib.publish._ + +/** Standard Bill of Materials (BOM) module for Iterative Works projects using + * Mill. This module centralizes dependency versioning across projects. + * + * A BOM module allows: + * 1. Centralized version management for dependencies 2. Enforcing specific + * versions of transitive dependencies 3. Applying uniform dependency + * management across multiple modules 4. Publishing a BOM artifact that + * other projects can consume + * + * Usage: + * 1. Create a BOM module in your build: + * ```scala + * object bom extends IWBomModule { + * def publishVersion = "0.1.0-SNAPSHOT" + * + * // Add your organization-specific customizations here + * def pomSettings = PomSettings(...) + * } + * ``` + * + * 2. Use the BOM in your modules: + * ```scala + * object myModule extends IWScalaModule { + * def bomModuleDeps = Seq(bom) + * + * def ivyDeps = Agg( + * // No need to specify versions as they come from the BOM + * ivy"dev.zio::zio", + * ivy"dev.zio::zio-json" + * ) + * } + * ``` + */ +trait IWBomModule extends BomModule with ScalaModule with IWScalaVersions { + + override def scalaVersion: T[String] = scala3Version + + /** The default implementation includes all standard IW dependencies from + * IWMillVersions and IWMillDeps. + */ + override def depManagement = T { + val V = IWMillVersions + + Agg( + // ZIO Core + ivy"dev.zio::zio:${V.zio}", + ivy"dev.zio::zio-test:${V.zio}", + ivy"dev.zio::zio-test-sbt:${V.zio}", + ivy"dev.zio::zio-streams:${V.zio}", + + // ZIO Extensions + ivy"dev.zio::zio-config:${V.zioConfig}", + ivy"dev.zio::zio-config-typesafe:${V.zioConfig}", + ivy"dev.zio::zio-config-magnolia:${V.zioConfig}", + ivy"dev.zio::zio-json:${V.zioJson}", + ivy"dev.zio::zio-logging:${V.zioLogging}", + ivy"dev.zio::zio-logging-slf4j:${V.zioLogging}", + ivy"dev.zio::zio-prelude:${V.zioPrelude}", + ivy"dev.zio::zio-interop-cats:${V.zioInteropCats}", + ivy"dev.zio::zio-interop-reactivestreams:${V.zioInteropReactiveStreams}", + ivy"dev.zio::zio-nio:${V.zioNIO}", + ivy"dev.zio::zio-cli:${V.zioCli}", + ivy"dev.zio::zio-schema:${V.zioSchema}", + ivy"dev.zio::zio-schema-derivation:${V.zioSchema}", + + // Database + ivy"io.getquill::quill-jdbc-zio:${V.quill}", + ivy"com.augustnagro::magnum:${V.magnum}", + ivy"com.augustnagro::magnumzio:${V.magnum}", + ivy"com.augustnagro::magnumpg:${V.magnum}", + + // HTTP + ivy"com.softwaremill.sttp.tapir::tapir-core:${V.tapir}", + ivy"com.softwaremill.sttp.tapir::tapir-zio:${V.tapir}", + ivy"com.softwaremill.sttp.tapir::tapir-json-zio:${V.tapir}", + + // UI/Frontend + ivy"com.lihaoyi::scalatags:${V.scalatags}", + ivy"com.raquo::laminar:${V.laminar}", + ivy"com.raquo::waypoint:${V.waypoint}", + ivy"be.doeraene::url-dsl:${V.urlDsl}", + + // Testing + ivy"org.scalatest::scalatest:${V.scalaTest}", + ivy"com.lihaoyi::utest:${V.utest}", + + // Logging + ivy"ch.qos.logback:logback-classic:${V.logbackClassic}" + ) + } +} diff --git a/mill-iw-support/core/src/IWMillDeps.scala b/mill-iw-support/core/src/IWMillDeps.scala new file mode 100644 index 0000000..d4cac1c --- /dev/null +++ b/mill-iw-support/core/src/IWMillDeps.scala @@ -0,0 +1,80 @@ +package works.iterative.mill + +import mill.api.Loose + +/** Standardized dependency management for Iterative Works projects using Mill. + * This object provides methods to get dependency strings with proper + * versioning. + * + * All versions are centralized in [[IWMillVersions]]. + * + * This object is designed to be used in build.sc files where the ivy string + * interpolator is available, like: + * + * {{{ + * def ivyDeps = Agg( + * ivy"$${IWMillDeps.zio}", + * ivy"$${IWMillDeps.zioJson}" + * ) + * }}} + */ +object IWMillDeps { + val V = IWMillVersions + + // ZIO Core + val zio = s"dev.zio::zio:${V.zio}" + val zioTest = s"dev.zio::zio-test:${V.zio}" + val zioTestSbt = s"dev.zio::zio-test-sbt:${V.zio}" + val zioStreams = s"dev.zio::zio-streams:${V.zio}" + + // ZIO Extensions + val zioConfig = s"dev.zio::zio-config:${V.zioConfig}" + val zioConfigTypesafe = s"dev.zio::zio-config-typesafe:${V.zioConfig}" + val zioConfigMagnolia = s"dev.zio::zio-config-magnolia:${V.zioConfig}" + val zioJson = s"dev.zio::zio-json:${V.zioJson}" + val zioLogging = s"dev.zio::zio-logging:${V.zioLogging}" + val zioLoggingSlf4j = s"dev.zio::zio-logging-slf4j:${V.zioLogging}" + val zioPrelude = s"dev.zio::zio-prelude:${V.zioPrelude}" + val zioInteropCats = s"dev.zio::zio-interop-cats:${V.zioInteropCats}" + val zioInteropReactiveStreams = + s"dev.zio::zio-interop-reactivestreams:${V.zioInteropReactiveStreams}" + val zioNIO = s"dev.zio::zio-nio:${V.zioNIO}" + val zioCli = s"dev.zio::zio-cli:${V.zioCli}" + val zioSchema = s"dev.zio::zio-schema:${V.zioSchema}" + val zioSchemaDerivation = s"dev.zio::zio-schema-derivation:${V.zioSchema}" + + // Collection of ZIO dependencies + val zioCore = List(zio, zioTest, zioTestSbt) + val zioJson_ = List(zioJson) + val zioAll = zioCore ++ List( + zioStreams, + zioConfig, + zioConfigTypesafe, + zioConfigMagnolia, + zioLogging, + zioPrelude + ) ++ zioJson_ + + // Database + val quill = s"io.getquill::quill-jdbc-zio:${V.quill}" + val magnum = s"com.augustnagro::magnum_3:${V.magnum}" + val magnumZIO = s"com.augustnagro::magnumzio_3:${V.magnum}" + val magnumPG = s"com.augustnagro::magnumpg_3:${V.magnum}" + + // HTTP + val tapirCore = s"com.softwaremill.sttp.tapir::tapir-core:${V.tapir}" + val tapirZIO = s"com.softwaremill.sttp.tapir::tapir-zio:${V.tapir}" + val tapirZIOJson = s"com.softwaremill.sttp.tapir::tapir-json-zio:${V.tapir}" + + // UI/Frontend + val scalatags = s"com.lihaoyi::scalatags:${V.scalatags}" + val laminar = s"com.raquo::laminar:${V.laminar}" + val waypoint = s"com.raquo::waypoint:${V.waypoint}" + val urlDsl = s"be.doeraene::url-dsl:${V.urlDsl}" + + // Testing + val scalaTest = s"org.scalatest::scalatest:${V.scalaTest}" + + // Logging + val logbackClassic = s"ch.qos.logback:logback-classic:${V.logbackClassic}" +} diff --git a/mill-iw-support/core/src/IWMillVersions.scala b/mill-iw-support/core/src/IWMillVersions.scala new file mode 100644 index 0000000..0ad9bd2 --- /dev/null +++ b/mill-iw-support/core/src/IWMillVersions.scala @@ -0,0 +1,56 @@ +package works.iterative.mill + +/** + * Centralized version management for Iterative Works projects using Mill. + * This object contains all library versions used across projects. + * + * Update this object to bump versions for all projects. + */ +object IWMillVersions { + // ZIO Ecosystem + val zio = "2.1.16" + val zioConfig = "4.0.3" + val zioCli = "0.7.0" + val zioInteropCats = "23.1.0.3" + val zioInteropReactiveStreams = "2.0.2" + val zioJson = "0.7.36" + val zioLogging = "2.5.0" + val zioNIO = "2.0.2" + val zioPrelude = "1.0.0-RC39" + val zioQuery = "0.7.6" + val zioSchema = "1.6.3" + + // Akka Ecosystem + val akka = "2.6.18" + val akkaHttp = "10.4.0" + + // Other Libraries + val cats = "2.13.0" + val utest = "0.8.2" + val caliban = "2.9.2" + val chimney = "1.7.3" + val ducktape = "0.2.7" + val elastic4s = "7.12.4" + val http4s = "0.23.30" + val http4sBlaze = "0.23.17" + val http4sPac4J = "5.0.0" + val laminar = "17.2.0" + val laminext = "0.17.0" + val logbackClassic = "1.5.17" + val magnum = "2.0.0-M1" + val pac4j = "6.0.3" + val play = "2.8.18" + val playJson = "2.9.3" + val quill = "4.8.6" + val scalaJsMacroTaskExecutor = "1.1.1" + val scalaJsJavaSecureRandom = "1.0.0" + val scalatags = "0.13.1" + val scalaTest = "3.2.19" + val slick = "3.3.3" + val sttpClient3 = "3.10.3" + val sttpClient4 = "4.0.0-RC1" + val support = "1.7.0" + val tapir = "1.11.16" + val urlDsl = "0.7.0" + val waypoint = "9.0.0" +} \ No newline at end of file diff --git a/mill-iw-support/core/src/IWPublishModule.scala b/mill-iw-support/core/src/IWPublishModule.scala new file mode 100644 index 0000000..5ae0ade --- /dev/null +++ b/mill-iw-support/core/src/IWPublishModule.scala @@ -0,0 +1,22 @@ +package works.iterative.mill + +import mill._ +import mill.scalalib._ +import mill.scalalib.publish._ + +/** Standard publish module trait for Iterative Works projects using Mill. This + * trait sets up standard publishing configuration for IW Maven repositories. + * + * To use this trait, simply extend it in your module and define: + * - publishVersion: The version to publish + * - pomSettings: The POM settings for the publication + * + * Use IW_USERNAME and IW_PASSWORD environment variables for authentication. + */ +trait IWPublishModule extends PublishModule { + + override def sonatypeUri = "https://dig.iterative.works/maven/releases" + override def sonatypeSnapshotUri = + "https://dig.iterative.works/maven/snapshots" + +} diff --git a/mill-iw-support/core/src/IWScalaModule.scala b/mill-iw-support/core/src/IWScalaModule.scala new file mode 100644 index 0000000..021bccc --- /dev/null +++ b/mill-iw-support/core/src/IWScalaModule.scala @@ -0,0 +1,97 @@ +package works.iterative.mill + +import mill._ +import mill.scalalib._ +import mill.scalalib.scalafmt._ + +/** Standard Scala module trait for Iterative Works projects using Mill. This + * trait sets up standard compiler options and configuration. + */ +trait IWScalaModule + extends ScalaModule + with ScalafmtModule + with IWScalaVersions { + + override def scalaVersion = scala3Version + + // Define base compiler options based on Scala version + def baseScalacOptions = T { + if (scalaVersion().startsWith("2.")) { + Seq( + "-encoding", + "utf8", + "-feature", + "-unchecked", + "-language:existentials", + "-language:experimental.macros", + "-language:higherKinds", + "-language:implicitConversions", + "-Xlint:adapted-args", + "-Xlint:constant", + "-Xlint:delayedinit-select", + "-Xlint:deprecation", + "-Xlint:doc-detached", + "-Xlint:implicit-recursion", + "-Xlint:implicit-not-found", + "-Xlint:inaccessible", + "-Xlint:infer-any", + "-Xlint:missing-interpolator", + "-Xlint:nullary-unit", + "-Xlint:option-implicit", + "-Xlint:package-object-classes", + "-Xlint:poly-implicit-overload", + "-Xlint:private-shadow", + "-Xlint:stars-align", + "-Xlint:strict-unsealed-patmat", + "-Xlint:type-parameter-shadow", + "-Xlint:-byname-implicit", + "-Wdead-code", + "-Wextra-implicit", + "-Wnumeric-widen", + "-Wvalue-discard", + "-Wnonunit-statement", + "-Wunused:nowarn", + "-Wunused:implicits", + "-Wunused:explicits", + "-Wunused:imports", + "-Wunused:locals", + "-Wunused:params", + "-Wunused:patvars", + "-Wunused:privates" + ) + } else { + Seq( + "-encoding", + "utf8", + "-deprecation", + "-feature", + "-unchecked", + "-language:experimental.macros", + "-language:higherKinds", + "-language:implicitConversions", + "-Xkind-projector", + "-Wvalue-discard", + "-Wnonunit-statement", + "-Wunused:implicits", + "-Wunused:explicits", + "-Wunused:imports", + "-Wunused:locals", + "-Wunused:params", + "-Wunused:privates" + ) + } + } + + // Enable SemanticDB for tooling + def semanticDbOptions = Seq( + "-Xsemanticdb" + ) + + // Override scalacOptions to include both base options and SemanticDB options + override def scalacOptions = T { + super.scalacOptions() ++ baseScalacOptions() ++ semanticDbOptions + } + + // Base test configuration + trait IWTests extends ScalaTests with ScalafmtModule +} diff --git a/mill-iw-support/core/src/IWScalaVersions.scala b/mill-iw-support/core/src/IWScalaVersions.scala new file mode 100644 index 0000000..aa70cb6 --- /dev/null +++ b/mill-iw-support/core/src/IWScalaVersions.scala @@ -0,0 +1,9 @@ +package works.iterative.mill + +trait IWScalaVersions { + val scala2Version = "2.13.16" + val scala3Version = "3.6.4" + val scala3LTSVersion = "3.3.5" +} + +object IWScalaVersions extends IWScalaVersions diff --git a/mill-iw-support/core/src/package.scala b/mill-iw-support/core/src/package.scala new file mode 100644 index 0000000..1ba4556 --- /dev/null +++ b/mill-iw-support/core/src/package.scala @@ -0,0 +1,6 @@ +package works.iterative + +package object mill { + val IWVersions = IWMillVersions + val IWDeps = IWMillDeps +} diff --git a/mill-iw-support/example/.mill-version b/mill-iw-support/example/.mill-version new file mode 100644 index 0000000..54dbed4 --- /dev/null +++ b/mill-iw-support/example/.mill-version @@ -0,0 +1 @@ +0.12.10 diff --git a/mill-iw-support/.mill-version b/mill-iw-support/.mill-version new file mode 100644 index 0000000..54dbed4 --- /dev/null +++ b/mill-iw-support/.mill-version @@ -0,0 +1 @@ +0.12.10 diff --git a/mill-iw-support/.scalafmt.conf b/mill-iw-support/.scalafmt.conf new file mode 100644 index 0000000..ee7753a --- /dev/null +++ b/mill-iw-support/.scalafmt.conf @@ -0,0 +1,2 @@ +version = "3.7.15" +runner.dialect = scala213 \ No newline at end of file diff --git a/mill-iw-support/README.md b/mill-iw-support/README.md index 4986e5c..9bb1ba8 100644 --- a/mill-iw-support/README.md +++ b/mill-iw-support/README.md @@ -28,8 +28,45 @@ IWMillDeps.tapirCore // ivy"com.softwaremill.sttp.tapir::tapir-core:1.11.16" // Use dependency groups -IWMillDeps.useZIO() // Core ZIO dependencies with test framework -IWMillDeps.useZIOAll() // Comprehensive ZIO stack +IWMillDeps.zioCore // List of core ZIO dependencies +IWMillDeps.zioAll // List of all ZIO dependencies +``` + +### IWBomModule + +Bill of Materials (BOM) module for centralized dependency management: + +```scala +import mill._ +import works.iterative.mill._ + +// Define a BOM module +object bom extends IWBomModule { + def publishVersion = "0.1.0-SNAPSHOT" + + def pomSettings = PomSettings( + // Standard POM settings + ) + + // Override with custom dependencies if needed + override def depManagement = T { + super.depManagement() ++ Agg( + ivy"com.example::custom-lib:1.0.0" + ) + } +} + +// Use the BOM in modules +object myModule extends IWScalaModule { + // Reference the BOM module + override def bomModuleDeps = Seq(bom) + + // No need to specify versions - they come from the BOM + def ivyDeps = super.ivyDeps() ++ Agg( + ivy"dev.zio::zio", + ivy"dev.zio::zio-json" + ) +} ``` ### IWScalaModule @@ -44,10 +81,18 @@ // Use default Scala version (3.6.3) // and standard compiler options with SemanticDB enabled - def ivyDeps = super.ivyDeps() ++ IWMillDeps.useZIO() + def ivyDeps = super.ivyDeps() ++ Agg( + ivy"${IWMillDeps.zio}", + ivy"${IWMillDeps.zioJson}" + ) // Standard test module configuration - object test extends Tests with IWTests + object test extends ScalaTests with TestModule.ZioTest { + def ivyDeps = super.ivyDeps() ++ Agg( + ivy"${IWMillDeps.zioTest}", + ivy"${IWMillDeps.zioTestSbt}" + ) + } } ``` @@ -96,6 +141,51 @@ } ``` +## Dependency Management Approaches + +This library provides two approaches for dependency management: + +### 1. IWMillDeps (String Constants) + +Use the IWMillDeps object to access dependency strings with correct versions: + +```scala +def ivyDeps = super.ivyDeps() ++ Agg( + ivy"${IWMillDeps.zio}", + ivy"${IWMillDeps.zioJson}" +) +``` + +### 2. BOM (Bill of Materials) + +Use the IWBomModule to centralize dependency versions: + +```scala +// Define a BOM module +object bom extends IWBomModule { + def publishVersion = "0.1.0-SNAPSHOT" + def pomSettings = PomSettings(...) +} + +// Use the BOM in modules +object myModule extends IWScalaModule { + override def bomModuleDeps = Seq(bom) + + // No need to specify versions + def ivyDeps = super.ivyDeps() ++ Agg( + ivy"dev.zio::zio", + ivy"dev.zio::zio-json" + ) +} +``` + +The BOM approach offers these advantages: +- Enforces consistent versions across modules +- Simplifies version updates +- Controls transitive dependency versions +- Makes build files cleaner (no version strings) +- Can be published as a separate artifact + ## Examples See the `example` directory for a complete working example. diff --git a/mill-iw-support/build.sc b/mill-iw-support/build.sc index 5df80ae..2ed832c 100644 --- a/mill-iw-support/build.sc +++ b/mill-iw-support/build.sc @@ -2,20 +2,33 @@ import mill.scalalib._ import mill.scalalib.publish._ import mill.scalalib.scalafmt._ +import mill.scalalib.TestModule.Utest -object `mill-iw-support` extends Cross[IWMillSupportModule]("2.13.16", "3.6.3") +/** Main module for the mill-iw-support library. Built for Scala 2.13 which is + * compatible with Mill. + */ +object core extends ScalaModule with ScalafmtModule with PublishModule { + val millVersion = "0.12.10" -class IWMillSupportModule(val crossScalaVersion: String) extends CrossScalaModule with ScalafmtModule with PublishModule { + def scalaVersion = "2.13.16" + + def artifactName = "mill-iw-support" + def publishVersion = "0.1.0-SNAPSHOT" def pomSettings = PomSettings( description = "Iterative Works Mill Support Library", organization = "works.iterative", - url = "https://github.com/iterative-works/mill-iw-support", + url = "https://github.com/iterative-works/iw-project-support", licenses = Seq(License.MIT), - versionControl = VersionControl.github("iterative-works", "mill-iw-support"), + versionControl = + VersionControl.github("iterative-works", "iw-project-support"), developers = Seq( - Developer("mph", "Michal Přihoda", "https://github.com/iterative-works") + Developer( + "mprihoda", + "Michal Příhoda", + "https://github.com/iterative-works" + ) ) ) @@ -23,10 +36,15 @@ ivy"com.lihaoyi::os-lib:0.9.3" ) - object test extends Tests with ScalafmtModule { + // Mill's own dependencies needed for compilation + def compileIvyDeps = Agg( + ivy"com.lihaoyi::mill-main:${millVersion}", + ivy"com.lihaoyi::mill-scalalib:${millVersion}" + ) + + object test extends ScalaTests with Utest with ScalafmtModule { def ivyDeps = Agg( ivy"com.lihaoyi::utest:0.8.2" ) - def testFramework = "utest.runner.Framework" } -} \ No newline at end of file +} diff --git a/mill-iw-support/core/src/IWBomModule.scala b/mill-iw-support/core/src/IWBomModule.scala new file mode 100644 index 0000000..d8e70bb --- /dev/null +++ b/mill-iw-support/core/src/IWBomModule.scala @@ -0,0 +1,97 @@ +package works.iterative.mill + +import mill._ +import mill.scalalib._ +import mill.scalalib.publish._ + +/** Standard Bill of Materials (BOM) module for Iterative Works projects using + * Mill. This module centralizes dependency versioning across projects. + * + * A BOM module allows: + * 1. Centralized version management for dependencies 2. Enforcing specific + * versions of transitive dependencies 3. Applying uniform dependency + * management across multiple modules 4. Publishing a BOM artifact that + * other projects can consume + * + * Usage: + * 1. Create a BOM module in your build: + * ```scala + * object bom extends IWBomModule { + * def publishVersion = "0.1.0-SNAPSHOT" + * + * // Add your organization-specific customizations here + * def pomSettings = PomSettings(...) + * } + * ``` + * + * 2. Use the BOM in your modules: + * ```scala + * object myModule extends IWScalaModule { + * def bomModuleDeps = Seq(bom) + * + * def ivyDeps = Agg( + * // No need to specify versions as they come from the BOM + * ivy"dev.zio::zio", + * ivy"dev.zio::zio-json" + * ) + * } + * ``` + */ +trait IWBomModule extends BomModule with ScalaModule with IWScalaVersions { + + override def scalaVersion: T[String] = scala3Version + + /** The default implementation includes all standard IW dependencies from + * IWMillVersions and IWMillDeps. + */ + override def depManagement = T { + val V = IWMillVersions + + Agg( + // ZIO Core + ivy"dev.zio::zio:${V.zio}", + ivy"dev.zio::zio-test:${V.zio}", + ivy"dev.zio::zio-test-sbt:${V.zio}", + ivy"dev.zio::zio-streams:${V.zio}", + + // ZIO Extensions + ivy"dev.zio::zio-config:${V.zioConfig}", + ivy"dev.zio::zio-config-typesafe:${V.zioConfig}", + ivy"dev.zio::zio-config-magnolia:${V.zioConfig}", + ivy"dev.zio::zio-json:${V.zioJson}", + ivy"dev.zio::zio-logging:${V.zioLogging}", + ivy"dev.zio::zio-logging-slf4j:${V.zioLogging}", + ivy"dev.zio::zio-prelude:${V.zioPrelude}", + ivy"dev.zio::zio-interop-cats:${V.zioInteropCats}", + ivy"dev.zio::zio-interop-reactivestreams:${V.zioInteropReactiveStreams}", + ivy"dev.zio::zio-nio:${V.zioNIO}", + ivy"dev.zio::zio-cli:${V.zioCli}", + ivy"dev.zio::zio-schema:${V.zioSchema}", + ivy"dev.zio::zio-schema-derivation:${V.zioSchema}", + + // Database + ivy"io.getquill::quill-jdbc-zio:${V.quill}", + ivy"com.augustnagro::magnum:${V.magnum}", + ivy"com.augustnagro::magnumzio:${V.magnum}", + ivy"com.augustnagro::magnumpg:${V.magnum}", + + // HTTP + ivy"com.softwaremill.sttp.tapir::tapir-core:${V.tapir}", + ivy"com.softwaremill.sttp.tapir::tapir-zio:${V.tapir}", + ivy"com.softwaremill.sttp.tapir::tapir-json-zio:${V.tapir}", + + // UI/Frontend + ivy"com.lihaoyi::scalatags:${V.scalatags}", + ivy"com.raquo::laminar:${V.laminar}", + ivy"com.raquo::waypoint:${V.waypoint}", + ivy"be.doeraene::url-dsl:${V.urlDsl}", + + // Testing + ivy"org.scalatest::scalatest:${V.scalaTest}", + ivy"com.lihaoyi::utest:${V.utest}", + + // Logging + ivy"ch.qos.logback:logback-classic:${V.logbackClassic}" + ) + } +} diff --git a/mill-iw-support/core/src/IWMillDeps.scala b/mill-iw-support/core/src/IWMillDeps.scala new file mode 100644 index 0000000..d4cac1c --- /dev/null +++ b/mill-iw-support/core/src/IWMillDeps.scala @@ -0,0 +1,80 @@ +package works.iterative.mill + +import mill.api.Loose + +/** Standardized dependency management for Iterative Works projects using Mill. + * This object provides methods to get dependency strings with proper + * versioning. + * + * All versions are centralized in [[IWMillVersions]]. + * + * This object is designed to be used in build.sc files where the ivy string + * interpolator is available, like: + * + * {{{ + * def ivyDeps = Agg( + * ivy"$${IWMillDeps.zio}", + * ivy"$${IWMillDeps.zioJson}" + * ) + * }}} + */ +object IWMillDeps { + val V = IWMillVersions + + // ZIO Core + val zio = s"dev.zio::zio:${V.zio}" + val zioTest = s"dev.zio::zio-test:${V.zio}" + val zioTestSbt = s"dev.zio::zio-test-sbt:${V.zio}" + val zioStreams = s"dev.zio::zio-streams:${V.zio}" + + // ZIO Extensions + val zioConfig = s"dev.zio::zio-config:${V.zioConfig}" + val zioConfigTypesafe = s"dev.zio::zio-config-typesafe:${V.zioConfig}" + val zioConfigMagnolia = s"dev.zio::zio-config-magnolia:${V.zioConfig}" + val zioJson = s"dev.zio::zio-json:${V.zioJson}" + val zioLogging = s"dev.zio::zio-logging:${V.zioLogging}" + val zioLoggingSlf4j = s"dev.zio::zio-logging-slf4j:${V.zioLogging}" + val zioPrelude = s"dev.zio::zio-prelude:${V.zioPrelude}" + val zioInteropCats = s"dev.zio::zio-interop-cats:${V.zioInteropCats}" + val zioInteropReactiveStreams = + s"dev.zio::zio-interop-reactivestreams:${V.zioInteropReactiveStreams}" + val zioNIO = s"dev.zio::zio-nio:${V.zioNIO}" + val zioCli = s"dev.zio::zio-cli:${V.zioCli}" + val zioSchema = s"dev.zio::zio-schema:${V.zioSchema}" + val zioSchemaDerivation = s"dev.zio::zio-schema-derivation:${V.zioSchema}" + + // Collection of ZIO dependencies + val zioCore = List(zio, zioTest, zioTestSbt) + val zioJson_ = List(zioJson) + val zioAll = zioCore ++ List( + zioStreams, + zioConfig, + zioConfigTypesafe, + zioConfigMagnolia, + zioLogging, + zioPrelude + ) ++ zioJson_ + + // Database + val quill = s"io.getquill::quill-jdbc-zio:${V.quill}" + val magnum = s"com.augustnagro::magnum_3:${V.magnum}" + val magnumZIO = s"com.augustnagro::magnumzio_3:${V.magnum}" + val magnumPG = s"com.augustnagro::magnumpg_3:${V.magnum}" + + // HTTP + val tapirCore = s"com.softwaremill.sttp.tapir::tapir-core:${V.tapir}" + val tapirZIO = s"com.softwaremill.sttp.tapir::tapir-zio:${V.tapir}" + val tapirZIOJson = s"com.softwaremill.sttp.tapir::tapir-json-zio:${V.tapir}" + + // UI/Frontend + val scalatags = s"com.lihaoyi::scalatags:${V.scalatags}" + val laminar = s"com.raquo::laminar:${V.laminar}" + val waypoint = s"com.raquo::waypoint:${V.waypoint}" + val urlDsl = s"be.doeraene::url-dsl:${V.urlDsl}" + + // Testing + val scalaTest = s"org.scalatest::scalatest:${V.scalaTest}" + + // Logging + val logbackClassic = s"ch.qos.logback:logback-classic:${V.logbackClassic}" +} diff --git a/mill-iw-support/core/src/IWMillVersions.scala b/mill-iw-support/core/src/IWMillVersions.scala new file mode 100644 index 0000000..0ad9bd2 --- /dev/null +++ b/mill-iw-support/core/src/IWMillVersions.scala @@ -0,0 +1,56 @@ +package works.iterative.mill + +/** + * Centralized version management for Iterative Works projects using Mill. + * This object contains all library versions used across projects. + * + * Update this object to bump versions for all projects. + */ +object IWMillVersions { + // ZIO Ecosystem + val zio = "2.1.16" + val zioConfig = "4.0.3" + val zioCli = "0.7.0" + val zioInteropCats = "23.1.0.3" + val zioInteropReactiveStreams = "2.0.2" + val zioJson = "0.7.36" + val zioLogging = "2.5.0" + val zioNIO = "2.0.2" + val zioPrelude = "1.0.0-RC39" + val zioQuery = "0.7.6" + val zioSchema = "1.6.3" + + // Akka Ecosystem + val akka = "2.6.18" + val akkaHttp = "10.4.0" + + // Other Libraries + val cats = "2.13.0" + val utest = "0.8.2" + val caliban = "2.9.2" + val chimney = "1.7.3" + val ducktape = "0.2.7" + val elastic4s = "7.12.4" + val http4s = "0.23.30" + val http4sBlaze = "0.23.17" + val http4sPac4J = "5.0.0" + val laminar = "17.2.0" + val laminext = "0.17.0" + val logbackClassic = "1.5.17" + val magnum = "2.0.0-M1" + val pac4j = "6.0.3" + val play = "2.8.18" + val playJson = "2.9.3" + val quill = "4.8.6" + val scalaJsMacroTaskExecutor = "1.1.1" + val scalaJsJavaSecureRandom = "1.0.0" + val scalatags = "0.13.1" + val scalaTest = "3.2.19" + val slick = "3.3.3" + val sttpClient3 = "3.10.3" + val sttpClient4 = "4.0.0-RC1" + val support = "1.7.0" + val tapir = "1.11.16" + val urlDsl = "0.7.0" + val waypoint = "9.0.0" +} \ No newline at end of file diff --git a/mill-iw-support/core/src/IWPublishModule.scala b/mill-iw-support/core/src/IWPublishModule.scala new file mode 100644 index 0000000..5ae0ade --- /dev/null +++ b/mill-iw-support/core/src/IWPublishModule.scala @@ -0,0 +1,22 @@ +package works.iterative.mill + +import mill._ +import mill.scalalib._ +import mill.scalalib.publish._ + +/** Standard publish module trait for Iterative Works projects using Mill. This + * trait sets up standard publishing configuration for IW Maven repositories. + * + * To use this trait, simply extend it in your module and define: + * - publishVersion: The version to publish + * - pomSettings: The POM settings for the publication + * + * Use IW_USERNAME and IW_PASSWORD environment variables for authentication. + */ +trait IWPublishModule extends PublishModule { + + override def sonatypeUri = "https://dig.iterative.works/maven/releases" + override def sonatypeSnapshotUri = + "https://dig.iterative.works/maven/snapshots" + +} diff --git a/mill-iw-support/core/src/IWScalaModule.scala b/mill-iw-support/core/src/IWScalaModule.scala new file mode 100644 index 0000000..021bccc --- /dev/null +++ b/mill-iw-support/core/src/IWScalaModule.scala @@ -0,0 +1,97 @@ +package works.iterative.mill + +import mill._ +import mill.scalalib._ +import mill.scalalib.scalafmt._ + +/** Standard Scala module trait for Iterative Works projects using Mill. This + * trait sets up standard compiler options and configuration. + */ +trait IWScalaModule + extends ScalaModule + with ScalafmtModule + with IWScalaVersions { + + override def scalaVersion = scala3Version + + // Define base compiler options based on Scala version + def baseScalacOptions = T { + if (scalaVersion().startsWith("2.")) { + Seq( + "-encoding", + "utf8", + "-feature", + "-unchecked", + "-language:existentials", + "-language:experimental.macros", + "-language:higherKinds", + "-language:implicitConversions", + "-Xlint:adapted-args", + "-Xlint:constant", + "-Xlint:delayedinit-select", + "-Xlint:deprecation", + "-Xlint:doc-detached", + "-Xlint:implicit-recursion", + "-Xlint:implicit-not-found", + "-Xlint:inaccessible", + "-Xlint:infer-any", + "-Xlint:missing-interpolator", + "-Xlint:nullary-unit", + "-Xlint:option-implicit", + "-Xlint:package-object-classes", + "-Xlint:poly-implicit-overload", + "-Xlint:private-shadow", + "-Xlint:stars-align", + "-Xlint:strict-unsealed-patmat", + "-Xlint:type-parameter-shadow", + "-Xlint:-byname-implicit", + "-Wdead-code", + "-Wextra-implicit", + "-Wnumeric-widen", + "-Wvalue-discard", + "-Wnonunit-statement", + "-Wunused:nowarn", + "-Wunused:implicits", + "-Wunused:explicits", + "-Wunused:imports", + "-Wunused:locals", + "-Wunused:params", + "-Wunused:patvars", + "-Wunused:privates" + ) + } else { + Seq( + "-encoding", + "utf8", + "-deprecation", + "-feature", + "-unchecked", + "-language:experimental.macros", + "-language:higherKinds", + "-language:implicitConversions", + "-Xkind-projector", + "-Wvalue-discard", + "-Wnonunit-statement", + "-Wunused:implicits", + "-Wunused:explicits", + "-Wunused:imports", + "-Wunused:locals", + "-Wunused:params", + "-Wunused:privates" + ) + } + } + + // Enable SemanticDB for tooling + def semanticDbOptions = Seq( + "-Xsemanticdb" + ) + + // Override scalacOptions to include both base options and SemanticDB options + override def scalacOptions = T { + super.scalacOptions() ++ baseScalacOptions() ++ semanticDbOptions + } + + // Base test configuration + trait IWTests extends ScalaTests with ScalafmtModule +} diff --git a/mill-iw-support/core/src/IWScalaVersions.scala b/mill-iw-support/core/src/IWScalaVersions.scala new file mode 100644 index 0000000..aa70cb6 --- /dev/null +++ b/mill-iw-support/core/src/IWScalaVersions.scala @@ -0,0 +1,9 @@ +package works.iterative.mill + +trait IWScalaVersions { + val scala2Version = "2.13.16" + val scala3Version = "3.6.4" + val scala3LTSVersion = "3.3.5" +} + +object IWScalaVersions extends IWScalaVersions diff --git a/mill-iw-support/core/src/package.scala b/mill-iw-support/core/src/package.scala new file mode 100644 index 0000000..1ba4556 --- /dev/null +++ b/mill-iw-support/core/src/package.scala @@ -0,0 +1,6 @@ +package works.iterative + +package object mill { + val IWVersions = IWMillVersions + val IWDeps = IWMillDeps +} diff --git a/mill-iw-support/example/.mill-version b/mill-iw-support/example/.mill-version new file mode 100644 index 0000000..54dbed4 --- /dev/null +++ b/mill-iw-support/example/.mill-version @@ -0,0 +1 @@ +0.12.10 diff --git a/mill-iw-support/example/build.sc b/mill-iw-support/example/build.sc index 5da77ab..cae5a6a 100644 --- a/mill-iw-support/example/build.sc +++ b/mill-iw-support/example/build.sc @@ -1,11 +1,22 @@ import mill._ import mill.scalalib._ +import mill.scalalib.publish._ +import mill.scalalib.scalafmt._ + +// Import our IW Mill Support library import $ivy.`works.iterative::mill-iw-support:0.1.0-SNAPSHOT` import works.iterative.mill._ +// Define a BOM (Bill of Materials) module +object bom extends IWBomModule + +// Example module using the IW Mill support with BOM object example extends IWScalaModule with IWPublishModule { def publishVersion = "0.1.0-SNAPSHOT" - + + // Use the BOM module for dependency management + override def bomModuleDeps = Seq(bom) + def pomSettings = PomSettings( description = "Example Mill Project using IW Mill Support", organization = "com.example", @@ -16,15 +27,25 @@ Developer("developer", "Example Developer", "https://github.com/example") ) ) - - // Use centralized dependency versions and definitions - def ivyDeps = super.ivyDeps() ++ IWMillDeps.useZIO() ++ Agg( - IWMillDeps.zioJson, - IWMillDeps.tapirCore, - IWMillDeps.tapirZIO, - IWMillDeps.tapirZIOJson + + // With BOM, we don't need to specify versions + // They're managed by the BOM module + def ivyDeps = super.ivyDeps() ++ Agg( + ivy"dev.zio::zio", + ivy"dev.zio::zio-json", + ivy"com.softwaremill.sttp.tapir::tapir-core", + ivy"com.softwaremill.sttp.tapir::tapir-zio", + ivy"com.softwaremill.sttp.tapir::tapir-json-zio" ) - - // Define test module extending IWTests - object test extends Tests with IWTests -} \ No newline at end of file + + // Define test module using standard Mill approach + object test extends ScalaTests with TestModule.ZioTest { + // Use the BOM module for dependency management + override def bomModuleDeps = Seq(bom) + + def ivyDeps = super.ivyDeps() ++ Agg( + ivy"dev.zio::zio-test", // Version comes from BOM + ivy"dev.zio::zio-test-sbt" // Version comes from BOM + ) + } +} diff --git a/mill-iw-support/.mill-version b/mill-iw-support/.mill-version new file mode 100644 index 0000000..54dbed4 --- /dev/null +++ b/mill-iw-support/.mill-version @@ -0,0 +1 @@ +0.12.10 diff --git a/mill-iw-support/.scalafmt.conf b/mill-iw-support/.scalafmt.conf new file mode 100644 index 0000000..ee7753a --- /dev/null +++ b/mill-iw-support/.scalafmt.conf @@ -0,0 +1,2 @@ +version = "3.7.15" +runner.dialect = scala213 \ No newline at end of file diff --git a/mill-iw-support/README.md b/mill-iw-support/README.md index 4986e5c..9bb1ba8 100644 --- a/mill-iw-support/README.md +++ b/mill-iw-support/README.md @@ -28,8 +28,45 @@ IWMillDeps.tapirCore // ivy"com.softwaremill.sttp.tapir::tapir-core:1.11.16" // Use dependency groups -IWMillDeps.useZIO() // Core ZIO dependencies with test framework -IWMillDeps.useZIOAll() // Comprehensive ZIO stack +IWMillDeps.zioCore // List of core ZIO dependencies +IWMillDeps.zioAll // List of all ZIO dependencies +``` + +### IWBomModule + +Bill of Materials (BOM) module for centralized dependency management: + +```scala +import mill._ +import works.iterative.mill._ + +// Define a BOM module +object bom extends IWBomModule { + def publishVersion = "0.1.0-SNAPSHOT" + + def pomSettings = PomSettings( + // Standard POM settings + ) + + // Override with custom dependencies if needed + override def depManagement = T { + super.depManagement() ++ Agg( + ivy"com.example::custom-lib:1.0.0" + ) + } +} + +// Use the BOM in modules +object myModule extends IWScalaModule { + // Reference the BOM module + override def bomModuleDeps = Seq(bom) + + // No need to specify versions - they come from the BOM + def ivyDeps = super.ivyDeps() ++ Agg( + ivy"dev.zio::zio", + ivy"dev.zio::zio-json" + ) +} ``` ### IWScalaModule @@ -44,10 +81,18 @@ // Use default Scala version (3.6.3) // and standard compiler options with SemanticDB enabled - def ivyDeps = super.ivyDeps() ++ IWMillDeps.useZIO() + def ivyDeps = super.ivyDeps() ++ Agg( + ivy"${IWMillDeps.zio}", + ivy"${IWMillDeps.zioJson}" + ) // Standard test module configuration - object test extends Tests with IWTests + object test extends ScalaTests with TestModule.ZioTest { + def ivyDeps = super.ivyDeps() ++ Agg( + ivy"${IWMillDeps.zioTest}", + ivy"${IWMillDeps.zioTestSbt}" + ) + } } ``` @@ -96,6 +141,51 @@ } ``` +## Dependency Management Approaches + +This library provides two approaches for dependency management: + +### 1. IWMillDeps (String Constants) + +Use the IWMillDeps object to access dependency strings with correct versions: + +```scala +def ivyDeps = super.ivyDeps() ++ Agg( + ivy"${IWMillDeps.zio}", + ivy"${IWMillDeps.zioJson}" +) +``` + +### 2. BOM (Bill of Materials) + +Use the IWBomModule to centralize dependency versions: + +```scala +// Define a BOM module +object bom extends IWBomModule { + def publishVersion = "0.1.0-SNAPSHOT" + def pomSettings = PomSettings(...) +} + +// Use the BOM in modules +object myModule extends IWScalaModule { + override def bomModuleDeps = Seq(bom) + + // No need to specify versions + def ivyDeps = super.ivyDeps() ++ Agg( + ivy"dev.zio::zio", + ivy"dev.zio::zio-json" + ) +} +``` + +The BOM approach offers these advantages: +- Enforces consistent versions across modules +- Simplifies version updates +- Controls transitive dependency versions +- Makes build files cleaner (no version strings) +- Can be published as a separate artifact + ## Examples See the `example` directory for a complete working example. diff --git a/mill-iw-support/build.sc b/mill-iw-support/build.sc index 5df80ae..2ed832c 100644 --- a/mill-iw-support/build.sc +++ b/mill-iw-support/build.sc @@ -2,20 +2,33 @@ import mill.scalalib._ import mill.scalalib.publish._ import mill.scalalib.scalafmt._ +import mill.scalalib.TestModule.Utest -object `mill-iw-support` extends Cross[IWMillSupportModule]("2.13.16", "3.6.3") +/** Main module for the mill-iw-support library. Built for Scala 2.13 which is + * compatible with Mill. + */ +object core extends ScalaModule with ScalafmtModule with PublishModule { + val millVersion = "0.12.10" -class IWMillSupportModule(val crossScalaVersion: String) extends CrossScalaModule with ScalafmtModule with PublishModule { + def scalaVersion = "2.13.16" + + def artifactName = "mill-iw-support" + def publishVersion = "0.1.0-SNAPSHOT" def pomSettings = PomSettings( description = "Iterative Works Mill Support Library", organization = "works.iterative", - url = "https://github.com/iterative-works/mill-iw-support", + url = "https://github.com/iterative-works/iw-project-support", licenses = Seq(License.MIT), - versionControl = VersionControl.github("iterative-works", "mill-iw-support"), + versionControl = + VersionControl.github("iterative-works", "iw-project-support"), developers = Seq( - Developer("mph", "Michal Přihoda", "https://github.com/iterative-works") + Developer( + "mprihoda", + "Michal Příhoda", + "https://github.com/iterative-works" + ) ) ) @@ -23,10 +36,15 @@ ivy"com.lihaoyi::os-lib:0.9.3" ) - object test extends Tests with ScalafmtModule { + // Mill's own dependencies needed for compilation + def compileIvyDeps = Agg( + ivy"com.lihaoyi::mill-main:${millVersion}", + ivy"com.lihaoyi::mill-scalalib:${millVersion}" + ) + + object test extends ScalaTests with Utest with ScalafmtModule { def ivyDeps = Agg( ivy"com.lihaoyi::utest:0.8.2" ) - def testFramework = "utest.runner.Framework" } -} \ No newline at end of file +} diff --git a/mill-iw-support/core/src/IWBomModule.scala b/mill-iw-support/core/src/IWBomModule.scala new file mode 100644 index 0000000..d8e70bb --- /dev/null +++ b/mill-iw-support/core/src/IWBomModule.scala @@ -0,0 +1,97 @@ +package works.iterative.mill + +import mill._ +import mill.scalalib._ +import mill.scalalib.publish._ + +/** Standard Bill of Materials (BOM) module for Iterative Works projects using + * Mill. This module centralizes dependency versioning across projects. + * + * A BOM module allows: + * 1. Centralized version management for dependencies 2. Enforcing specific + * versions of transitive dependencies 3. Applying uniform dependency + * management across multiple modules 4. Publishing a BOM artifact that + * other projects can consume + * + * Usage: + * 1. Create a BOM module in your build: + * ```scala + * object bom extends IWBomModule { + * def publishVersion = "0.1.0-SNAPSHOT" + * + * // Add your organization-specific customizations here + * def pomSettings = PomSettings(...) + * } + * ``` + * + * 2. Use the BOM in your modules: + * ```scala + * object myModule extends IWScalaModule { + * def bomModuleDeps = Seq(bom) + * + * def ivyDeps = Agg( + * // No need to specify versions as they come from the BOM + * ivy"dev.zio::zio", + * ivy"dev.zio::zio-json" + * ) + * } + * ``` + */ +trait IWBomModule extends BomModule with ScalaModule with IWScalaVersions { + + override def scalaVersion: T[String] = scala3Version + + /** The default implementation includes all standard IW dependencies from + * IWMillVersions and IWMillDeps. + */ + override def depManagement = T { + val V = IWMillVersions + + Agg( + // ZIO Core + ivy"dev.zio::zio:${V.zio}", + ivy"dev.zio::zio-test:${V.zio}", + ivy"dev.zio::zio-test-sbt:${V.zio}", + ivy"dev.zio::zio-streams:${V.zio}", + + // ZIO Extensions + ivy"dev.zio::zio-config:${V.zioConfig}", + ivy"dev.zio::zio-config-typesafe:${V.zioConfig}", + ivy"dev.zio::zio-config-magnolia:${V.zioConfig}", + ivy"dev.zio::zio-json:${V.zioJson}", + ivy"dev.zio::zio-logging:${V.zioLogging}", + ivy"dev.zio::zio-logging-slf4j:${V.zioLogging}", + ivy"dev.zio::zio-prelude:${V.zioPrelude}", + ivy"dev.zio::zio-interop-cats:${V.zioInteropCats}", + ivy"dev.zio::zio-interop-reactivestreams:${V.zioInteropReactiveStreams}", + ivy"dev.zio::zio-nio:${V.zioNIO}", + ivy"dev.zio::zio-cli:${V.zioCli}", + ivy"dev.zio::zio-schema:${V.zioSchema}", + ivy"dev.zio::zio-schema-derivation:${V.zioSchema}", + + // Database + ivy"io.getquill::quill-jdbc-zio:${V.quill}", + ivy"com.augustnagro::magnum:${V.magnum}", + ivy"com.augustnagro::magnumzio:${V.magnum}", + ivy"com.augustnagro::magnumpg:${V.magnum}", + + // HTTP + ivy"com.softwaremill.sttp.tapir::tapir-core:${V.tapir}", + ivy"com.softwaremill.sttp.tapir::tapir-zio:${V.tapir}", + ivy"com.softwaremill.sttp.tapir::tapir-json-zio:${V.tapir}", + + // UI/Frontend + ivy"com.lihaoyi::scalatags:${V.scalatags}", + ivy"com.raquo::laminar:${V.laminar}", + ivy"com.raquo::waypoint:${V.waypoint}", + ivy"be.doeraene::url-dsl:${V.urlDsl}", + + // Testing + ivy"org.scalatest::scalatest:${V.scalaTest}", + ivy"com.lihaoyi::utest:${V.utest}", + + // Logging + ivy"ch.qos.logback:logback-classic:${V.logbackClassic}" + ) + } +} diff --git a/mill-iw-support/core/src/IWMillDeps.scala b/mill-iw-support/core/src/IWMillDeps.scala new file mode 100644 index 0000000..d4cac1c --- /dev/null +++ b/mill-iw-support/core/src/IWMillDeps.scala @@ -0,0 +1,80 @@ +package works.iterative.mill + +import mill.api.Loose + +/** Standardized dependency management for Iterative Works projects using Mill. + * This object provides methods to get dependency strings with proper + * versioning. + * + * All versions are centralized in [[IWMillVersions]]. + * + * This object is designed to be used in build.sc files where the ivy string + * interpolator is available, like: + * + * {{{ + * def ivyDeps = Agg( + * ivy"$${IWMillDeps.zio}", + * ivy"$${IWMillDeps.zioJson}" + * ) + * }}} + */ +object IWMillDeps { + val V = IWMillVersions + + // ZIO Core + val zio = s"dev.zio::zio:${V.zio}" + val zioTest = s"dev.zio::zio-test:${V.zio}" + val zioTestSbt = s"dev.zio::zio-test-sbt:${V.zio}" + val zioStreams = s"dev.zio::zio-streams:${V.zio}" + + // ZIO Extensions + val zioConfig = s"dev.zio::zio-config:${V.zioConfig}" + val zioConfigTypesafe = s"dev.zio::zio-config-typesafe:${V.zioConfig}" + val zioConfigMagnolia = s"dev.zio::zio-config-magnolia:${V.zioConfig}" + val zioJson = s"dev.zio::zio-json:${V.zioJson}" + val zioLogging = s"dev.zio::zio-logging:${V.zioLogging}" + val zioLoggingSlf4j = s"dev.zio::zio-logging-slf4j:${V.zioLogging}" + val zioPrelude = s"dev.zio::zio-prelude:${V.zioPrelude}" + val zioInteropCats = s"dev.zio::zio-interop-cats:${V.zioInteropCats}" + val zioInteropReactiveStreams = + s"dev.zio::zio-interop-reactivestreams:${V.zioInteropReactiveStreams}" + val zioNIO = s"dev.zio::zio-nio:${V.zioNIO}" + val zioCli = s"dev.zio::zio-cli:${V.zioCli}" + val zioSchema = s"dev.zio::zio-schema:${V.zioSchema}" + val zioSchemaDerivation = s"dev.zio::zio-schema-derivation:${V.zioSchema}" + + // Collection of ZIO dependencies + val zioCore = List(zio, zioTest, zioTestSbt) + val zioJson_ = List(zioJson) + val zioAll = zioCore ++ List( + zioStreams, + zioConfig, + zioConfigTypesafe, + zioConfigMagnolia, + zioLogging, + zioPrelude + ) ++ zioJson_ + + // Database + val quill = s"io.getquill::quill-jdbc-zio:${V.quill}" + val magnum = s"com.augustnagro::magnum_3:${V.magnum}" + val magnumZIO = s"com.augustnagro::magnumzio_3:${V.magnum}" + val magnumPG = s"com.augustnagro::magnumpg_3:${V.magnum}" + + // HTTP + val tapirCore = s"com.softwaremill.sttp.tapir::tapir-core:${V.tapir}" + val tapirZIO = s"com.softwaremill.sttp.tapir::tapir-zio:${V.tapir}" + val tapirZIOJson = s"com.softwaremill.sttp.tapir::tapir-json-zio:${V.tapir}" + + // UI/Frontend + val scalatags = s"com.lihaoyi::scalatags:${V.scalatags}" + val laminar = s"com.raquo::laminar:${V.laminar}" + val waypoint = s"com.raquo::waypoint:${V.waypoint}" + val urlDsl = s"be.doeraene::url-dsl:${V.urlDsl}" + + // Testing + val scalaTest = s"org.scalatest::scalatest:${V.scalaTest}" + + // Logging + val logbackClassic = s"ch.qos.logback:logback-classic:${V.logbackClassic}" +} diff --git a/mill-iw-support/core/src/IWMillVersions.scala b/mill-iw-support/core/src/IWMillVersions.scala new file mode 100644 index 0000000..0ad9bd2 --- /dev/null +++ b/mill-iw-support/core/src/IWMillVersions.scala @@ -0,0 +1,56 @@ +package works.iterative.mill + +/** + * Centralized version management for Iterative Works projects using Mill. + * This object contains all library versions used across projects. + * + * Update this object to bump versions for all projects. + */ +object IWMillVersions { + // ZIO Ecosystem + val zio = "2.1.16" + val zioConfig = "4.0.3" + val zioCli = "0.7.0" + val zioInteropCats = "23.1.0.3" + val zioInteropReactiveStreams = "2.0.2" + val zioJson = "0.7.36" + val zioLogging = "2.5.0" + val zioNIO = "2.0.2" + val zioPrelude = "1.0.0-RC39" + val zioQuery = "0.7.6" + val zioSchema = "1.6.3" + + // Akka Ecosystem + val akka = "2.6.18" + val akkaHttp = "10.4.0" + + // Other Libraries + val cats = "2.13.0" + val utest = "0.8.2" + val caliban = "2.9.2" + val chimney = "1.7.3" + val ducktape = "0.2.7" + val elastic4s = "7.12.4" + val http4s = "0.23.30" + val http4sBlaze = "0.23.17" + val http4sPac4J = "5.0.0" + val laminar = "17.2.0" + val laminext = "0.17.0" + val logbackClassic = "1.5.17" + val magnum = "2.0.0-M1" + val pac4j = "6.0.3" + val play = "2.8.18" + val playJson = "2.9.3" + val quill = "4.8.6" + val scalaJsMacroTaskExecutor = "1.1.1" + val scalaJsJavaSecureRandom = "1.0.0" + val scalatags = "0.13.1" + val scalaTest = "3.2.19" + val slick = "3.3.3" + val sttpClient3 = "3.10.3" + val sttpClient4 = "4.0.0-RC1" + val support = "1.7.0" + val tapir = "1.11.16" + val urlDsl = "0.7.0" + val waypoint = "9.0.0" +} \ No newline at end of file diff --git a/mill-iw-support/core/src/IWPublishModule.scala b/mill-iw-support/core/src/IWPublishModule.scala new file mode 100644 index 0000000..5ae0ade --- /dev/null +++ b/mill-iw-support/core/src/IWPublishModule.scala @@ -0,0 +1,22 @@ +package works.iterative.mill + +import mill._ +import mill.scalalib._ +import mill.scalalib.publish._ + +/** Standard publish module trait for Iterative Works projects using Mill. This + * trait sets up standard publishing configuration for IW Maven repositories. + * + * To use this trait, simply extend it in your module and define: + * - publishVersion: The version to publish + * - pomSettings: The POM settings for the publication + * + * Use IW_USERNAME and IW_PASSWORD environment variables for authentication. + */ +trait IWPublishModule extends PublishModule { + + override def sonatypeUri = "https://dig.iterative.works/maven/releases" + override def sonatypeSnapshotUri = + "https://dig.iterative.works/maven/snapshots" + +} diff --git a/mill-iw-support/core/src/IWScalaModule.scala b/mill-iw-support/core/src/IWScalaModule.scala new file mode 100644 index 0000000..021bccc --- /dev/null +++ b/mill-iw-support/core/src/IWScalaModule.scala @@ -0,0 +1,97 @@ +package works.iterative.mill + +import mill._ +import mill.scalalib._ +import mill.scalalib.scalafmt._ + +/** Standard Scala module trait for Iterative Works projects using Mill. This + * trait sets up standard compiler options and configuration. + */ +trait IWScalaModule + extends ScalaModule + with ScalafmtModule + with IWScalaVersions { + + override def scalaVersion = scala3Version + + // Define base compiler options based on Scala version + def baseScalacOptions = T { + if (scalaVersion().startsWith("2.")) { + Seq( + "-encoding", + "utf8", + "-feature", + "-unchecked", + "-language:existentials", + "-language:experimental.macros", + "-language:higherKinds", + "-language:implicitConversions", + "-Xlint:adapted-args", + "-Xlint:constant", + "-Xlint:delayedinit-select", + "-Xlint:deprecation", + "-Xlint:doc-detached", + "-Xlint:implicit-recursion", + "-Xlint:implicit-not-found", + "-Xlint:inaccessible", + "-Xlint:infer-any", + "-Xlint:missing-interpolator", + "-Xlint:nullary-unit", + "-Xlint:option-implicit", + "-Xlint:package-object-classes", + "-Xlint:poly-implicit-overload", + "-Xlint:private-shadow", + "-Xlint:stars-align", + "-Xlint:strict-unsealed-patmat", + "-Xlint:type-parameter-shadow", + "-Xlint:-byname-implicit", + "-Wdead-code", + "-Wextra-implicit", + "-Wnumeric-widen", + "-Wvalue-discard", + "-Wnonunit-statement", + "-Wunused:nowarn", + "-Wunused:implicits", + "-Wunused:explicits", + "-Wunused:imports", + "-Wunused:locals", + "-Wunused:params", + "-Wunused:patvars", + "-Wunused:privates" + ) + } else { + Seq( + "-encoding", + "utf8", + "-deprecation", + "-feature", + "-unchecked", + "-language:experimental.macros", + "-language:higherKinds", + "-language:implicitConversions", + "-Xkind-projector", + "-Wvalue-discard", + "-Wnonunit-statement", + "-Wunused:implicits", + "-Wunused:explicits", + "-Wunused:imports", + "-Wunused:locals", + "-Wunused:params", + "-Wunused:privates" + ) + } + } + + // Enable SemanticDB for tooling + def semanticDbOptions = Seq( + "-Xsemanticdb" + ) + + // Override scalacOptions to include both base options and SemanticDB options + override def scalacOptions = T { + super.scalacOptions() ++ baseScalacOptions() ++ semanticDbOptions + } + + // Base test configuration + trait IWTests extends ScalaTests with ScalafmtModule +} diff --git a/mill-iw-support/core/src/IWScalaVersions.scala b/mill-iw-support/core/src/IWScalaVersions.scala new file mode 100644 index 0000000..aa70cb6 --- /dev/null +++ b/mill-iw-support/core/src/IWScalaVersions.scala @@ -0,0 +1,9 @@ +package works.iterative.mill + +trait IWScalaVersions { + val scala2Version = "2.13.16" + val scala3Version = "3.6.4" + val scala3LTSVersion = "3.3.5" +} + +object IWScalaVersions extends IWScalaVersions diff --git a/mill-iw-support/core/src/package.scala b/mill-iw-support/core/src/package.scala new file mode 100644 index 0000000..1ba4556 --- /dev/null +++ b/mill-iw-support/core/src/package.scala @@ -0,0 +1,6 @@ +package works.iterative + +package object mill { + val IWVersions = IWMillVersions + val IWDeps = IWMillDeps +} diff --git a/mill-iw-support/example/.mill-version b/mill-iw-support/example/.mill-version new file mode 100644 index 0000000..54dbed4 --- /dev/null +++ b/mill-iw-support/example/.mill-version @@ -0,0 +1 @@ +0.12.10 diff --git a/mill-iw-support/example/build.sc b/mill-iw-support/example/build.sc index 5da77ab..cae5a6a 100644 --- a/mill-iw-support/example/build.sc +++ b/mill-iw-support/example/build.sc @@ -1,11 +1,22 @@ import mill._ import mill.scalalib._ +import mill.scalalib.publish._ +import mill.scalalib.scalafmt._ + +// Import our IW Mill Support library import $ivy.`works.iterative::mill-iw-support:0.1.0-SNAPSHOT` import works.iterative.mill._ +// Define a BOM (Bill of Materials) module +object bom extends IWBomModule + +// Example module using the IW Mill support with BOM object example extends IWScalaModule with IWPublishModule { def publishVersion = "0.1.0-SNAPSHOT" - + + // Use the BOM module for dependency management + override def bomModuleDeps = Seq(bom) + def pomSettings = PomSettings( description = "Example Mill Project using IW Mill Support", organization = "com.example", @@ -16,15 +27,25 @@ Developer("developer", "Example Developer", "https://github.com/example") ) ) - - // Use centralized dependency versions and definitions - def ivyDeps = super.ivyDeps() ++ IWMillDeps.useZIO() ++ Agg( - IWMillDeps.zioJson, - IWMillDeps.tapirCore, - IWMillDeps.tapirZIO, - IWMillDeps.tapirZIOJson + + // With BOM, we don't need to specify versions + // They're managed by the BOM module + def ivyDeps = super.ivyDeps() ++ Agg( + ivy"dev.zio::zio", + ivy"dev.zio::zio-json", + ivy"com.softwaremill.sttp.tapir::tapir-core", + ivy"com.softwaremill.sttp.tapir::tapir-zio", + ivy"com.softwaremill.sttp.tapir::tapir-json-zio" ) - - // Define test module extending IWTests - object test extends Tests with IWTests -} \ No newline at end of file + + // Define test module using standard Mill approach + object test extends ScalaTests with TestModule.ZioTest { + // Use the BOM module for dependency management + override def bomModuleDeps = Seq(bom) + + def ivyDeps = super.ivyDeps() ++ Agg( + ivy"dev.zio::zio-test", // Version comes from BOM + ivy"dev.zio::zio-test-sbt" // Version comes from BOM + ) + } +} diff --git a/mill-iw-support/example/example/src/Example.scala b/mill-iw-support/example/example/src/Example.scala new file mode 100644 index 0000000..aa28980 --- /dev/null +++ b/mill-iw-support/example/example/src/Example.scala @@ -0,0 +1,27 @@ +package example + +import zio._ +import zio.json._ + +// Simple example class +case class User(id: String, name: String, email: String) + +object User { + // JSON codecs using ZIO JSON + implicit val encoder: JsonEncoder[User] = DeriveJsonEncoder.gen[User] + implicit val decoder: JsonDecoder[User] = DeriveJsonDecoder.gen[User] +} + +object Example extends ZIOAppDefault { + + val program = for { + _ <- Console.printLine("Starting example application") + user = User("1", "John Doe", "john@example.com") + json <- ZIO.succeed(user.toJson) + _ <- Console.printLine(s"User as JSON: $json") + parsed <- ZIO.fromEither(json.fromJson[User]) + _ <- Console.printLine(s"Parsed user: $parsed") + } yield () + + def run = program +} \ No newline at end of file diff --git a/mill-iw-support/.mill-version b/mill-iw-support/.mill-version new file mode 100644 index 0000000..54dbed4 --- /dev/null +++ b/mill-iw-support/.mill-version @@ -0,0 +1 @@ +0.12.10 diff --git a/mill-iw-support/.scalafmt.conf b/mill-iw-support/.scalafmt.conf new file mode 100644 index 0000000..ee7753a --- /dev/null +++ b/mill-iw-support/.scalafmt.conf @@ -0,0 +1,2 @@ +version = "3.7.15" +runner.dialect = scala213 \ No newline at end of file diff --git a/mill-iw-support/README.md b/mill-iw-support/README.md index 4986e5c..9bb1ba8 100644 --- a/mill-iw-support/README.md +++ b/mill-iw-support/README.md @@ -28,8 +28,45 @@ IWMillDeps.tapirCore // ivy"com.softwaremill.sttp.tapir::tapir-core:1.11.16" // Use dependency groups -IWMillDeps.useZIO() // Core ZIO dependencies with test framework -IWMillDeps.useZIOAll() // Comprehensive ZIO stack +IWMillDeps.zioCore // List of core ZIO dependencies +IWMillDeps.zioAll // List of all ZIO dependencies +``` + +### IWBomModule + +Bill of Materials (BOM) module for centralized dependency management: + +```scala +import mill._ +import works.iterative.mill._ + +// Define a BOM module +object bom extends IWBomModule { + def publishVersion = "0.1.0-SNAPSHOT" + + def pomSettings = PomSettings( + // Standard POM settings + ) + + // Override with custom dependencies if needed + override def depManagement = T { + super.depManagement() ++ Agg( + ivy"com.example::custom-lib:1.0.0" + ) + } +} + +// Use the BOM in modules +object myModule extends IWScalaModule { + // Reference the BOM module + override def bomModuleDeps = Seq(bom) + + // No need to specify versions - they come from the BOM + def ivyDeps = super.ivyDeps() ++ Agg( + ivy"dev.zio::zio", + ivy"dev.zio::zio-json" + ) +} ``` ### IWScalaModule @@ -44,10 +81,18 @@ // Use default Scala version (3.6.3) // and standard compiler options with SemanticDB enabled - def ivyDeps = super.ivyDeps() ++ IWMillDeps.useZIO() + def ivyDeps = super.ivyDeps() ++ Agg( + ivy"${IWMillDeps.zio}", + ivy"${IWMillDeps.zioJson}" + ) // Standard test module configuration - object test extends Tests with IWTests + object test extends ScalaTests with TestModule.ZioTest { + def ivyDeps = super.ivyDeps() ++ Agg( + ivy"${IWMillDeps.zioTest}", + ivy"${IWMillDeps.zioTestSbt}" + ) + } } ``` @@ -96,6 +141,51 @@ } ``` +## Dependency Management Approaches + +This library provides two approaches for dependency management: + +### 1. IWMillDeps (String Constants) + +Use the IWMillDeps object to access dependency strings with correct versions: + +```scala +def ivyDeps = super.ivyDeps() ++ Agg( + ivy"${IWMillDeps.zio}", + ivy"${IWMillDeps.zioJson}" +) +``` + +### 2. BOM (Bill of Materials) + +Use the IWBomModule to centralize dependency versions: + +```scala +// Define a BOM module +object bom extends IWBomModule { + def publishVersion = "0.1.0-SNAPSHOT" + def pomSettings = PomSettings(...) +} + +// Use the BOM in modules +object myModule extends IWScalaModule { + override def bomModuleDeps = Seq(bom) + + // No need to specify versions + def ivyDeps = super.ivyDeps() ++ Agg( + ivy"dev.zio::zio", + ivy"dev.zio::zio-json" + ) +} +``` + +The BOM approach offers these advantages: +- Enforces consistent versions across modules +- Simplifies version updates +- Controls transitive dependency versions +- Makes build files cleaner (no version strings) +- Can be published as a separate artifact + ## Examples See the `example` directory for a complete working example. diff --git a/mill-iw-support/build.sc b/mill-iw-support/build.sc index 5df80ae..2ed832c 100644 --- a/mill-iw-support/build.sc +++ b/mill-iw-support/build.sc @@ -2,20 +2,33 @@ import mill.scalalib._ import mill.scalalib.publish._ import mill.scalalib.scalafmt._ +import mill.scalalib.TestModule.Utest -object `mill-iw-support` extends Cross[IWMillSupportModule]("2.13.16", "3.6.3") +/** Main module for the mill-iw-support library. Built for Scala 2.13 which is + * compatible with Mill. + */ +object core extends ScalaModule with ScalafmtModule with PublishModule { + val millVersion = "0.12.10" -class IWMillSupportModule(val crossScalaVersion: String) extends CrossScalaModule with ScalafmtModule with PublishModule { + def scalaVersion = "2.13.16" + + def artifactName = "mill-iw-support" + def publishVersion = "0.1.0-SNAPSHOT" def pomSettings = PomSettings( description = "Iterative Works Mill Support Library", organization = "works.iterative", - url = "https://github.com/iterative-works/mill-iw-support", + url = "https://github.com/iterative-works/iw-project-support", licenses = Seq(License.MIT), - versionControl = VersionControl.github("iterative-works", "mill-iw-support"), + versionControl = + VersionControl.github("iterative-works", "iw-project-support"), developers = Seq( - Developer("mph", "Michal Přihoda", "https://github.com/iterative-works") + Developer( + "mprihoda", + "Michal Příhoda", + "https://github.com/iterative-works" + ) ) ) @@ -23,10 +36,15 @@ ivy"com.lihaoyi::os-lib:0.9.3" ) - object test extends Tests with ScalafmtModule { + // Mill's own dependencies needed for compilation + def compileIvyDeps = Agg( + ivy"com.lihaoyi::mill-main:${millVersion}", + ivy"com.lihaoyi::mill-scalalib:${millVersion}" + ) + + object test extends ScalaTests with Utest with ScalafmtModule { def ivyDeps = Agg( ivy"com.lihaoyi::utest:0.8.2" ) - def testFramework = "utest.runner.Framework" } -} \ No newline at end of file +} diff --git a/mill-iw-support/core/src/IWBomModule.scala b/mill-iw-support/core/src/IWBomModule.scala new file mode 100644 index 0000000..d8e70bb --- /dev/null +++ b/mill-iw-support/core/src/IWBomModule.scala @@ -0,0 +1,97 @@ +package works.iterative.mill + +import mill._ +import mill.scalalib._ +import mill.scalalib.publish._ + +/** Standard Bill of Materials (BOM) module for Iterative Works projects using + * Mill. This module centralizes dependency versioning across projects. + * + * A BOM module allows: + * 1. Centralized version management for dependencies 2. Enforcing specific + * versions of transitive dependencies 3. Applying uniform dependency + * management across multiple modules 4. Publishing a BOM artifact that + * other projects can consume + * + * Usage: + * 1. Create a BOM module in your build: + * ```scala + * object bom extends IWBomModule { + * def publishVersion = "0.1.0-SNAPSHOT" + * + * // Add your organization-specific customizations here + * def pomSettings = PomSettings(...) + * } + * ``` + * + * 2. Use the BOM in your modules: + * ```scala + * object myModule extends IWScalaModule { + * def bomModuleDeps = Seq(bom) + * + * def ivyDeps = Agg( + * // No need to specify versions as they come from the BOM + * ivy"dev.zio::zio", + * ivy"dev.zio::zio-json" + * ) + * } + * ``` + */ +trait IWBomModule extends BomModule with ScalaModule with IWScalaVersions { + + override def scalaVersion: T[String] = scala3Version + + /** The default implementation includes all standard IW dependencies from + * IWMillVersions and IWMillDeps. + */ + override def depManagement = T { + val V = IWMillVersions + + Agg( + // ZIO Core + ivy"dev.zio::zio:${V.zio}", + ivy"dev.zio::zio-test:${V.zio}", + ivy"dev.zio::zio-test-sbt:${V.zio}", + ivy"dev.zio::zio-streams:${V.zio}", + + // ZIO Extensions + ivy"dev.zio::zio-config:${V.zioConfig}", + ivy"dev.zio::zio-config-typesafe:${V.zioConfig}", + ivy"dev.zio::zio-config-magnolia:${V.zioConfig}", + ivy"dev.zio::zio-json:${V.zioJson}", + ivy"dev.zio::zio-logging:${V.zioLogging}", + ivy"dev.zio::zio-logging-slf4j:${V.zioLogging}", + ivy"dev.zio::zio-prelude:${V.zioPrelude}", + ivy"dev.zio::zio-interop-cats:${V.zioInteropCats}", + ivy"dev.zio::zio-interop-reactivestreams:${V.zioInteropReactiveStreams}", + ivy"dev.zio::zio-nio:${V.zioNIO}", + ivy"dev.zio::zio-cli:${V.zioCli}", + ivy"dev.zio::zio-schema:${V.zioSchema}", + ivy"dev.zio::zio-schema-derivation:${V.zioSchema}", + + // Database + ivy"io.getquill::quill-jdbc-zio:${V.quill}", + ivy"com.augustnagro::magnum:${V.magnum}", + ivy"com.augustnagro::magnumzio:${V.magnum}", + ivy"com.augustnagro::magnumpg:${V.magnum}", + + // HTTP + ivy"com.softwaremill.sttp.tapir::tapir-core:${V.tapir}", + ivy"com.softwaremill.sttp.tapir::tapir-zio:${V.tapir}", + ivy"com.softwaremill.sttp.tapir::tapir-json-zio:${V.tapir}", + + // UI/Frontend + ivy"com.lihaoyi::scalatags:${V.scalatags}", + ivy"com.raquo::laminar:${V.laminar}", + ivy"com.raquo::waypoint:${V.waypoint}", + ivy"be.doeraene::url-dsl:${V.urlDsl}", + + // Testing + ivy"org.scalatest::scalatest:${V.scalaTest}", + ivy"com.lihaoyi::utest:${V.utest}", + + // Logging + ivy"ch.qos.logback:logback-classic:${V.logbackClassic}" + ) + } +} diff --git a/mill-iw-support/core/src/IWMillDeps.scala b/mill-iw-support/core/src/IWMillDeps.scala new file mode 100644 index 0000000..d4cac1c --- /dev/null +++ b/mill-iw-support/core/src/IWMillDeps.scala @@ -0,0 +1,80 @@ +package works.iterative.mill + +import mill.api.Loose + +/** Standardized dependency management for Iterative Works projects using Mill. + * This object provides methods to get dependency strings with proper + * versioning. + * + * All versions are centralized in [[IWMillVersions]]. + * + * This object is designed to be used in build.sc files where the ivy string + * interpolator is available, like: + * + * {{{ + * def ivyDeps = Agg( + * ivy"$${IWMillDeps.zio}", + * ivy"$${IWMillDeps.zioJson}" + * ) + * }}} + */ +object IWMillDeps { + val V = IWMillVersions + + // ZIO Core + val zio = s"dev.zio::zio:${V.zio}" + val zioTest = s"dev.zio::zio-test:${V.zio}" + val zioTestSbt = s"dev.zio::zio-test-sbt:${V.zio}" + val zioStreams = s"dev.zio::zio-streams:${V.zio}" + + // ZIO Extensions + val zioConfig = s"dev.zio::zio-config:${V.zioConfig}" + val zioConfigTypesafe = s"dev.zio::zio-config-typesafe:${V.zioConfig}" + val zioConfigMagnolia = s"dev.zio::zio-config-magnolia:${V.zioConfig}" + val zioJson = s"dev.zio::zio-json:${V.zioJson}" + val zioLogging = s"dev.zio::zio-logging:${V.zioLogging}" + val zioLoggingSlf4j = s"dev.zio::zio-logging-slf4j:${V.zioLogging}" + val zioPrelude = s"dev.zio::zio-prelude:${V.zioPrelude}" + val zioInteropCats = s"dev.zio::zio-interop-cats:${V.zioInteropCats}" + val zioInteropReactiveStreams = + s"dev.zio::zio-interop-reactivestreams:${V.zioInteropReactiveStreams}" + val zioNIO = s"dev.zio::zio-nio:${V.zioNIO}" + val zioCli = s"dev.zio::zio-cli:${V.zioCli}" + val zioSchema = s"dev.zio::zio-schema:${V.zioSchema}" + val zioSchemaDerivation = s"dev.zio::zio-schema-derivation:${V.zioSchema}" + + // Collection of ZIO dependencies + val zioCore = List(zio, zioTest, zioTestSbt) + val zioJson_ = List(zioJson) + val zioAll = zioCore ++ List( + zioStreams, + zioConfig, + zioConfigTypesafe, + zioConfigMagnolia, + zioLogging, + zioPrelude + ) ++ zioJson_ + + // Database + val quill = s"io.getquill::quill-jdbc-zio:${V.quill}" + val magnum = s"com.augustnagro::magnum_3:${V.magnum}" + val magnumZIO = s"com.augustnagro::magnumzio_3:${V.magnum}" + val magnumPG = s"com.augustnagro::magnumpg_3:${V.magnum}" + + // HTTP + val tapirCore = s"com.softwaremill.sttp.tapir::tapir-core:${V.tapir}" + val tapirZIO = s"com.softwaremill.sttp.tapir::tapir-zio:${V.tapir}" + val tapirZIOJson = s"com.softwaremill.sttp.tapir::tapir-json-zio:${V.tapir}" + + // UI/Frontend + val scalatags = s"com.lihaoyi::scalatags:${V.scalatags}" + val laminar = s"com.raquo::laminar:${V.laminar}" + val waypoint = s"com.raquo::waypoint:${V.waypoint}" + val urlDsl = s"be.doeraene::url-dsl:${V.urlDsl}" + + // Testing + val scalaTest = s"org.scalatest::scalatest:${V.scalaTest}" + + // Logging + val logbackClassic = s"ch.qos.logback:logback-classic:${V.logbackClassic}" +} diff --git a/mill-iw-support/core/src/IWMillVersions.scala b/mill-iw-support/core/src/IWMillVersions.scala new file mode 100644 index 0000000..0ad9bd2 --- /dev/null +++ b/mill-iw-support/core/src/IWMillVersions.scala @@ -0,0 +1,56 @@ +package works.iterative.mill + +/** + * Centralized version management for Iterative Works projects using Mill. + * This object contains all library versions used across projects. + * + * Update this object to bump versions for all projects. + */ +object IWMillVersions { + // ZIO Ecosystem + val zio = "2.1.16" + val zioConfig = "4.0.3" + val zioCli = "0.7.0" + val zioInteropCats = "23.1.0.3" + val zioInteropReactiveStreams = "2.0.2" + val zioJson = "0.7.36" + val zioLogging = "2.5.0" + val zioNIO = "2.0.2" + val zioPrelude = "1.0.0-RC39" + val zioQuery = "0.7.6" + val zioSchema = "1.6.3" + + // Akka Ecosystem + val akka = "2.6.18" + val akkaHttp = "10.4.0" + + // Other Libraries + val cats = "2.13.0" + val utest = "0.8.2" + val caliban = "2.9.2" + val chimney = "1.7.3" + val ducktape = "0.2.7" + val elastic4s = "7.12.4" + val http4s = "0.23.30" + val http4sBlaze = "0.23.17" + val http4sPac4J = "5.0.0" + val laminar = "17.2.0" + val laminext = "0.17.0" + val logbackClassic = "1.5.17" + val magnum = "2.0.0-M1" + val pac4j = "6.0.3" + val play = "2.8.18" + val playJson = "2.9.3" + val quill = "4.8.6" + val scalaJsMacroTaskExecutor = "1.1.1" + val scalaJsJavaSecureRandom = "1.0.0" + val scalatags = "0.13.1" + val scalaTest = "3.2.19" + val slick = "3.3.3" + val sttpClient3 = "3.10.3" + val sttpClient4 = "4.0.0-RC1" + val support = "1.7.0" + val tapir = "1.11.16" + val urlDsl = "0.7.0" + val waypoint = "9.0.0" +} \ No newline at end of file diff --git a/mill-iw-support/core/src/IWPublishModule.scala b/mill-iw-support/core/src/IWPublishModule.scala new file mode 100644 index 0000000..5ae0ade --- /dev/null +++ b/mill-iw-support/core/src/IWPublishModule.scala @@ -0,0 +1,22 @@ +package works.iterative.mill + +import mill._ +import mill.scalalib._ +import mill.scalalib.publish._ + +/** Standard publish module trait for Iterative Works projects using Mill. This + * trait sets up standard publishing configuration for IW Maven repositories. + * + * To use this trait, simply extend it in your module and define: + * - publishVersion: The version to publish + * - pomSettings: The POM settings for the publication + * + * Use IW_USERNAME and IW_PASSWORD environment variables for authentication. + */ +trait IWPublishModule extends PublishModule { + + override def sonatypeUri = "https://dig.iterative.works/maven/releases" + override def sonatypeSnapshotUri = + "https://dig.iterative.works/maven/snapshots" + +} diff --git a/mill-iw-support/core/src/IWScalaModule.scala b/mill-iw-support/core/src/IWScalaModule.scala new file mode 100644 index 0000000..021bccc --- /dev/null +++ b/mill-iw-support/core/src/IWScalaModule.scala @@ -0,0 +1,97 @@ +package works.iterative.mill + +import mill._ +import mill.scalalib._ +import mill.scalalib.scalafmt._ + +/** Standard Scala module trait for Iterative Works projects using Mill. This + * trait sets up standard compiler options and configuration. + */ +trait IWScalaModule + extends ScalaModule + with ScalafmtModule + with IWScalaVersions { + + override def scalaVersion = scala3Version + + // Define base compiler options based on Scala version + def baseScalacOptions = T { + if (scalaVersion().startsWith("2.")) { + Seq( + "-encoding", + "utf8", + "-feature", + "-unchecked", + "-language:existentials", + "-language:experimental.macros", + "-language:higherKinds", + "-language:implicitConversions", + "-Xlint:adapted-args", + "-Xlint:constant", + "-Xlint:delayedinit-select", + "-Xlint:deprecation", + "-Xlint:doc-detached", + "-Xlint:implicit-recursion", + "-Xlint:implicit-not-found", + "-Xlint:inaccessible", + "-Xlint:infer-any", + "-Xlint:missing-interpolator", + "-Xlint:nullary-unit", + "-Xlint:option-implicit", + "-Xlint:package-object-classes", + "-Xlint:poly-implicit-overload", + "-Xlint:private-shadow", + "-Xlint:stars-align", + "-Xlint:strict-unsealed-patmat", + "-Xlint:type-parameter-shadow", + "-Xlint:-byname-implicit", + "-Wdead-code", + "-Wextra-implicit", + "-Wnumeric-widen", + "-Wvalue-discard", + "-Wnonunit-statement", + "-Wunused:nowarn", + "-Wunused:implicits", + "-Wunused:explicits", + "-Wunused:imports", + "-Wunused:locals", + "-Wunused:params", + "-Wunused:patvars", + "-Wunused:privates" + ) + } else { + Seq( + "-encoding", + "utf8", + "-deprecation", + "-feature", + "-unchecked", + "-language:experimental.macros", + "-language:higherKinds", + "-language:implicitConversions", + "-Xkind-projector", + "-Wvalue-discard", + "-Wnonunit-statement", + "-Wunused:implicits", + "-Wunused:explicits", + "-Wunused:imports", + "-Wunused:locals", + "-Wunused:params", + "-Wunused:privates" + ) + } + } + + // Enable SemanticDB for tooling + def semanticDbOptions = Seq( + "-Xsemanticdb" + ) + + // Override scalacOptions to include both base options and SemanticDB options + override def scalacOptions = T { + super.scalacOptions() ++ baseScalacOptions() ++ semanticDbOptions + } + + // Base test configuration + trait IWTests extends ScalaTests with ScalafmtModule +} diff --git a/mill-iw-support/core/src/IWScalaVersions.scala b/mill-iw-support/core/src/IWScalaVersions.scala new file mode 100644 index 0000000..aa70cb6 --- /dev/null +++ b/mill-iw-support/core/src/IWScalaVersions.scala @@ -0,0 +1,9 @@ +package works.iterative.mill + +trait IWScalaVersions { + val scala2Version = "2.13.16" + val scala3Version = "3.6.4" + val scala3LTSVersion = "3.3.5" +} + +object IWScalaVersions extends IWScalaVersions diff --git a/mill-iw-support/core/src/package.scala b/mill-iw-support/core/src/package.scala new file mode 100644 index 0000000..1ba4556 --- /dev/null +++ b/mill-iw-support/core/src/package.scala @@ -0,0 +1,6 @@ +package works.iterative + +package object mill { + val IWVersions = IWMillVersions + val IWDeps = IWMillDeps +} diff --git a/mill-iw-support/example/.mill-version b/mill-iw-support/example/.mill-version new file mode 100644 index 0000000..54dbed4 --- /dev/null +++ b/mill-iw-support/example/.mill-version @@ -0,0 +1 @@ +0.12.10 diff --git a/mill-iw-support/example/build.sc b/mill-iw-support/example/build.sc index 5da77ab..cae5a6a 100644 --- a/mill-iw-support/example/build.sc +++ b/mill-iw-support/example/build.sc @@ -1,11 +1,22 @@ import mill._ import mill.scalalib._ +import mill.scalalib.publish._ +import mill.scalalib.scalafmt._ + +// Import our IW Mill Support library import $ivy.`works.iterative::mill-iw-support:0.1.0-SNAPSHOT` import works.iterative.mill._ +// Define a BOM (Bill of Materials) module +object bom extends IWBomModule + +// Example module using the IW Mill support with BOM object example extends IWScalaModule with IWPublishModule { def publishVersion = "0.1.0-SNAPSHOT" - + + // Use the BOM module for dependency management + override def bomModuleDeps = Seq(bom) + def pomSettings = PomSettings( description = "Example Mill Project using IW Mill Support", organization = "com.example", @@ -16,15 +27,25 @@ Developer("developer", "Example Developer", "https://github.com/example") ) ) - - // Use centralized dependency versions and definitions - def ivyDeps = super.ivyDeps() ++ IWMillDeps.useZIO() ++ Agg( - IWMillDeps.zioJson, - IWMillDeps.tapirCore, - IWMillDeps.tapirZIO, - IWMillDeps.tapirZIOJson + + // With BOM, we don't need to specify versions + // They're managed by the BOM module + def ivyDeps = super.ivyDeps() ++ Agg( + ivy"dev.zio::zio", + ivy"dev.zio::zio-json", + ivy"com.softwaremill.sttp.tapir::tapir-core", + ivy"com.softwaremill.sttp.tapir::tapir-zio", + ivy"com.softwaremill.sttp.tapir::tapir-json-zio" ) - - // Define test module extending IWTests - object test extends Tests with IWTests -} \ No newline at end of file + + // Define test module using standard Mill approach + object test extends ScalaTests with TestModule.ZioTest { + // Use the BOM module for dependency management + override def bomModuleDeps = Seq(bom) + + def ivyDeps = super.ivyDeps() ++ Agg( + ivy"dev.zio::zio-test", // Version comes from BOM + ivy"dev.zio::zio-test-sbt" // Version comes from BOM + ) + } +} diff --git a/mill-iw-support/example/example/src/Example.scala b/mill-iw-support/example/example/src/Example.scala new file mode 100644 index 0000000..aa28980 --- /dev/null +++ b/mill-iw-support/example/example/src/Example.scala @@ -0,0 +1,27 @@ +package example + +import zio._ +import zio.json._ + +// Simple example class +case class User(id: String, name: String, email: String) + +object User { + // JSON codecs using ZIO JSON + implicit val encoder: JsonEncoder[User] = DeriveJsonEncoder.gen[User] + implicit val decoder: JsonDecoder[User] = DeriveJsonDecoder.gen[User] +} + +object Example extends ZIOAppDefault { + + val program = for { + _ <- Console.printLine("Starting example application") + user = User("1", "John Doe", "john@example.com") + json <- ZIO.succeed(user.toJson) + _ <- Console.printLine(s"User as JSON: $json") + parsed <- ZIO.fromEither(json.fromJson[User]) + _ <- Console.printLine(s"Parsed user: $parsed") + } yield () + + def run = program +} \ No newline at end of file diff --git a/mill-iw-support/example/example/test/src/ExampleTest.scala b/mill-iw-support/example/example/test/src/ExampleTest.scala new file mode 100644 index 0000000..951c2d8 --- /dev/null +++ b/mill-iw-support/example/example/test/src/ExampleTest.scala @@ -0,0 +1,26 @@ +package example + +import zio._ +import zio.json._ +import zio.test._ +import zio.test.Assertion._ + +object ExampleTest extends ZIOSpecDefault { + override def spec = suite("Example suite")( + test("User JSON serialization") { + val user = User("1", "John Doe", "john@example.com") + val json = user.toJson + val expected = + """{"id":"1","name":"John Doe","email":"john@example.com"}""" + + assertTrue(json == expected) + }, + test("User JSON deserialization") { + val json = """{"id":"1","name":"John Doe","email":"john@example.com"}""" + val parsed = json.fromJson[User] + val expected = User("1", "John Doe", "john@example.com") + + assertTrue(parsed == Right(expected)) + } + ) +} diff --git a/mill-iw-support/.mill-version b/mill-iw-support/.mill-version new file mode 100644 index 0000000..54dbed4 --- /dev/null +++ b/mill-iw-support/.mill-version @@ -0,0 +1 @@ +0.12.10 diff --git a/mill-iw-support/.scalafmt.conf b/mill-iw-support/.scalafmt.conf new file mode 100644 index 0000000..ee7753a --- /dev/null +++ b/mill-iw-support/.scalafmt.conf @@ -0,0 +1,2 @@ +version = "3.7.15" +runner.dialect = scala213 \ No newline at end of file diff --git a/mill-iw-support/README.md b/mill-iw-support/README.md index 4986e5c..9bb1ba8 100644 --- a/mill-iw-support/README.md +++ b/mill-iw-support/README.md @@ -28,8 +28,45 @@ IWMillDeps.tapirCore // ivy"com.softwaremill.sttp.tapir::tapir-core:1.11.16" // Use dependency groups -IWMillDeps.useZIO() // Core ZIO dependencies with test framework -IWMillDeps.useZIOAll() // Comprehensive ZIO stack +IWMillDeps.zioCore // List of core ZIO dependencies +IWMillDeps.zioAll // List of all ZIO dependencies +``` + +### IWBomModule + +Bill of Materials (BOM) module for centralized dependency management: + +```scala +import mill._ +import works.iterative.mill._ + +// Define a BOM module +object bom extends IWBomModule { + def publishVersion = "0.1.0-SNAPSHOT" + + def pomSettings = PomSettings( + // Standard POM settings + ) + + // Override with custom dependencies if needed + override def depManagement = T { + super.depManagement() ++ Agg( + ivy"com.example::custom-lib:1.0.0" + ) + } +} + +// Use the BOM in modules +object myModule extends IWScalaModule { + // Reference the BOM module + override def bomModuleDeps = Seq(bom) + + // No need to specify versions - they come from the BOM + def ivyDeps = super.ivyDeps() ++ Agg( + ivy"dev.zio::zio", + ivy"dev.zio::zio-json" + ) +} ``` ### IWScalaModule @@ -44,10 +81,18 @@ // Use default Scala version (3.6.3) // and standard compiler options with SemanticDB enabled - def ivyDeps = super.ivyDeps() ++ IWMillDeps.useZIO() + def ivyDeps = super.ivyDeps() ++ Agg( + ivy"${IWMillDeps.zio}", + ivy"${IWMillDeps.zioJson}" + ) // Standard test module configuration - object test extends Tests with IWTests + object test extends ScalaTests with TestModule.ZioTest { + def ivyDeps = super.ivyDeps() ++ Agg( + ivy"${IWMillDeps.zioTest}", + ivy"${IWMillDeps.zioTestSbt}" + ) + } } ``` @@ -96,6 +141,51 @@ } ``` +## Dependency Management Approaches + +This library provides two approaches for dependency management: + +### 1. IWMillDeps (String Constants) + +Use the IWMillDeps object to access dependency strings with correct versions: + +```scala +def ivyDeps = super.ivyDeps() ++ Agg( + ivy"${IWMillDeps.zio}", + ivy"${IWMillDeps.zioJson}" +) +``` + +### 2. BOM (Bill of Materials) + +Use the IWBomModule to centralize dependency versions: + +```scala +// Define a BOM module +object bom extends IWBomModule { + def publishVersion = "0.1.0-SNAPSHOT" + def pomSettings = PomSettings(...) +} + +// Use the BOM in modules +object myModule extends IWScalaModule { + override def bomModuleDeps = Seq(bom) + + // No need to specify versions + def ivyDeps = super.ivyDeps() ++ Agg( + ivy"dev.zio::zio", + ivy"dev.zio::zio-json" + ) +} +``` + +The BOM approach offers these advantages: +- Enforces consistent versions across modules +- Simplifies version updates +- Controls transitive dependency versions +- Makes build files cleaner (no version strings) +- Can be published as a separate artifact + ## Examples See the `example` directory for a complete working example. diff --git a/mill-iw-support/build.sc b/mill-iw-support/build.sc index 5df80ae..2ed832c 100644 --- a/mill-iw-support/build.sc +++ b/mill-iw-support/build.sc @@ -2,20 +2,33 @@ import mill.scalalib._ import mill.scalalib.publish._ import mill.scalalib.scalafmt._ +import mill.scalalib.TestModule.Utest -object `mill-iw-support` extends Cross[IWMillSupportModule]("2.13.16", "3.6.3") +/** Main module for the mill-iw-support library. Built for Scala 2.13 which is + * compatible with Mill. + */ +object core extends ScalaModule with ScalafmtModule with PublishModule { + val millVersion = "0.12.10" -class IWMillSupportModule(val crossScalaVersion: String) extends CrossScalaModule with ScalafmtModule with PublishModule { + def scalaVersion = "2.13.16" + + def artifactName = "mill-iw-support" + def publishVersion = "0.1.0-SNAPSHOT" def pomSettings = PomSettings( description = "Iterative Works Mill Support Library", organization = "works.iterative", - url = "https://github.com/iterative-works/mill-iw-support", + url = "https://github.com/iterative-works/iw-project-support", licenses = Seq(License.MIT), - versionControl = VersionControl.github("iterative-works", "mill-iw-support"), + versionControl = + VersionControl.github("iterative-works", "iw-project-support"), developers = Seq( - Developer("mph", "Michal Přihoda", "https://github.com/iterative-works") + Developer( + "mprihoda", + "Michal Příhoda", + "https://github.com/iterative-works" + ) ) ) @@ -23,10 +36,15 @@ ivy"com.lihaoyi::os-lib:0.9.3" ) - object test extends Tests with ScalafmtModule { + // Mill's own dependencies needed for compilation + def compileIvyDeps = Agg( + ivy"com.lihaoyi::mill-main:${millVersion}", + ivy"com.lihaoyi::mill-scalalib:${millVersion}" + ) + + object test extends ScalaTests with Utest with ScalafmtModule { def ivyDeps = Agg( ivy"com.lihaoyi::utest:0.8.2" ) - def testFramework = "utest.runner.Framework" } -} \ No newline at end of file +} diff --git a/mill-iw-support/core/src/IWBomModule.scala b/mill-iw-support/core/src/IWBomModule.scala new file mode 100644 index 0000000..d8e70bb --- /dev/null +++ b/mill-iw-support/core/src/IWBomModule.scala @@ -0,0 +1,97 @@ +package works.iterative.mill + +import mill._ +import mill.scalalib._ +import mill.scalalib.publish._ + +/** Standard Bill of Materials (BOM) module for Iterative Works projects using + * Mill. This module centralizes dependency versioning across projects. + * + * A BOM module allows: + * 1. Centralized version management for dependencies 2. Enforcing specific + * versions of transitive dependencies 3. Applying uniform dependency + * management across multiple modules 4. Publishing a BOM artifact that + * other projects can consume + * + * Usage: + * 1. Create a BOM module in your build: + * ```scala + * object bom extends IWBomModule { + * def publishVersion = "0.1.0-SNAPSHOT" + * + * // Add your organization-specific customizations here + * def pomSettings = PomSettings(...) + * } + * ``` + * + * 2. Use the BOM in your modules: + * ```scala + * object myModule extends IWScalaModule { + * def bomModuleDeps = Seq(bom) + * + * def ivyDeps = Agg( + * // No need to specify versions as they come from the BOM + * ivy"dev.zio::zio", + * ivy"dev.zio::zio-json" + * ) + * } + * ``` + */ +trait IWBomModule extends BomModule with ScalaModule with IWScalaVersions { + + override def scalaVersion: T[String] = scala3Version + + /** The default implementation includes all standard IW dependencies from + * IWMillVersions and IWMillDeps. + */ + override def depManagement = T { + val V = IWMillVersions + + Agg( + // ZIO Core + ivy"dev.zio::zio:${V.zio}", + ivy"dev.zio::zio-test:${V.zio}", + ivy"dev.zio::zio-test-sbt:${V.zio}", + ivy"dev.zio::zio-streams:${V.zio}", + + // ZIO Extensions + ivy"dev.zio::zio-config:${V.zioConfig}", + ivy"dev.zio::zio-config-typesafe:${V.zioConfig}", + ivy"dev.zio::zio-config-magnolia:${V.zioConfig}", + ivy"dev.zio::zio-json:${V.zioJson}", + ivy"dev.zio::zio-logging:${V.zioLogging}", + ivy"dev.zio::zio-logging-slf4j:${V.zioLogging}", + ivy"dev.zio::zio-prelude:${V.zioPrelude}", + ivy"dev.zio::zio-interop-cats:${V.zioInteropCats}", + ivy"dev.zio::zio-interop-reactivestreams:${V.zioInteropReactiveStreams}", + ivy"dev.zio::zio-nio:${V.zioNIO}", + ivy"dev.zio::zio-cli:${V.zioCli}", + ivy"dev.zio::zio-schema:${V.zioSchema}", + ivy"dev.zio::zio-schema-derivation:${V.zioSchema}", + + // Database + ivy"io.getquill::quill-jdbc-zio:${V.quill}", + ivy"com.augustnagro::magnum:${V.magnum}", + ivy"com.augustnagro::magnumzio:${V.magnum}", + ivy"com.augustnagro::magnumpg:${V.magnum}", + + // HTTP + ivy"com.softwaremill.sttp.tapir::tapir-core:${V.tapir}", + ivy"com.softwaremill.sttp.tapir::tapir-zio:${V.tapir}", + ivy"com.softwaremill.sttp.tapir::tapir-json-zio:${V.tapir}", + + // UI/Frontend + ivy"com.lihaoyi::scalatags:${V.scalatags}", + ivy"com.raquo::laminar:${V.laminar}", + ivy"com.raquo::waypoint:${V.waypoint}", + ivy"be.doeraene::url-dsl:${V.urlDsl}", + + // Testing + ivy"org.scalatest::scalatest:${V.scalaTest}", + ivy"com.lihaoyi::utest:${V.utest}", + + // Logging + ivy"ch.qos.logback:logback-classic:${V.logbackClassic}" + ) + } +} diff --git a/mill-iw-support/core/src/IWMillDeps.scala b/mill-iw-support/core/src/IWMillDeps.scala new file mode 100644 index 0000000..d4cac1c --- /dev/null +++ b/mill-iw-support/core/src/IWMillDeps.scala @@ -0,0 +1,80 @@ +package works.iterative.mill + +import mill.api.Loose + +/** Standardized dependency management for Iterative Works projects using Mill. + * This object provides methods to get dependency strings with proper + * versioning. + * + * All versions are centralized in [[IWMillVersions]]. + * + * This object is designed to be used in build.sc files where the ivy string + * interpolator is available, like: + * + * {{{ + * def ivyDeps = Agg( + * ivy"$${IWMillDeps.zio}", + * ivy"$${IWMillDeps.zioJson}" + * ) + * }}} + */ +object IWMillDeps { + val V = IWMillVersions + + // ZIO Core + val zio = s"dev.zio::zio:${V.zio}" + val zioTest = s"dev.zio::zio-test:${V.zio}" + val zioTestSbt = s"dev.zio::zio-test-sbt:${V.zio}" + val zioStreams = s"dev.zio::zio-streams:${V.zio}" + + // ZIO Extensions + val zioConfig = s"dev.zio::zio-config:${V.zioConfig}" + val zioConfigTypesafe = s"dev.zio::zio-config-typesafe:${V.zioConfig}" + val zioConfigMagnolia = s"dev.zio::zio-config-magnolia:${V.zioConfig}" + val zioJson = s"dev.zio::zio-json:${V.zioJson}" + val zioLogging = s"dev.zio::zio-logging:${V.zioLogging}" + val zioLoggingSlf4j = s"dev.zio::zio-logging-slf4j:${V.zioLogging}" + val zioPrelude = s"dev.zio::zio-prelude:${V.zioPrelude}" + val zioInteropCats = s"dev.zio::zio-interop-cats:${V.zioInteropCats}" + val zioInteropReactiveStreams = + s"dev.zio::zio-interop-reactivestreams:${V.zioInteropReactiveStreams}" + val zioNIO = s"dev.zio::zio-nio:${V.zioNIO}" + val zioCli = s"dev.zio::zio-cli:${V.zioCli}" + val zioSchema = s"dev.zio::zio-schema:${V.zioSchema}" + val zioSchemaDerivation = s"dev.zio::zio-schema-derivation:${V.zioSchema}" + + // Collection of ZIO dependencies + val zioCore = List(zio, zioTest, zioTestSbt) + val zioJson_ = List(zioJson) + val zioAll = zioCore ++ List( + zioStreams, + zioConfig, + zioConfigTypesafe, + zioConfigMagnolia, + zioLogging, + zioPrelude + ) ++ zioJson_ + + // Database + val quill = s"io.getquill::quill-jdbc-zio:${V.quill}" + val magnum = s"com.augustnagro::magnum_3:${V.magnum}" + val magnumZIO = s"com.augustnagro::magnumzio_3:${V.magnum}" + val magnumPG = s"com.augustnagro::magnumpg_3:${V.magnum}" + + // HTTP + val tapirCore = s"com.softwaremill.sttp.tapir::tapir-core:${V.tapir}" + val tapirZIO = s"com.softwaremill.sttp.tapir::tapir-zio:${V.tapir}" + val tapirZIOJson = s"com.softwaremill.sttp.tapir::tapir-json-zio:${V.tapir}" + + // UI/Frontend + val scalatags = s"com.lihaoyi::scalatags:${V.scalatags}" + val laminar = s"com.raquo::laminar:${V.laminar}" + val waypoint = s"com.raquo::waypoint:${V.waypoint}" + val urlDsl = s"be.doeraene::url-dsl:${V.urlDsl}" + + // Testing + val scalaTest = s"org.scalatest::scalatest:${V.scalaTest}" + + // Logging + val logbackClassic = s"ch.qos.logback:logback-classic:${V.logbackClassic}" +} diff --git a/mill-iw-support/core/src/IWMillVersions.scala b/mill-iw-support/core/src/IWMillVersions.scala new file mode 100644 index 0000000..0ad9bd2 --- /dev/null +++ b/mill-iw-support/core/src/IWMillVersions.scala @@ -0,0 +1,56 @@ +package works.iterative.mill + +/** + * Centralized version management for Iterative Works projects using Mill. + * This object contains all library versions used across projects. + * + * Update this object to bump versions for all projects. + */ +object IWMillVersions { + // ZIO Ecosystem + val zio = "2.1.16" + val zioConfig = "4.0.3" + val zioCli = "0.7.0" + val zioInteropCats = "23.1.0.3" + val zioInteropReactiveStreams = "2.0.2" + val zioJson = "0.7.36" + val zioLogging = "2.5.0" + val zioNIO = "2.0.2" + val zioPrelude = "1.0.0-RC39" + val zioQuery = "0.7.6" + val zioSchema = "1.6.3" + + // Akka Ecosystem + val akka = "2.6.18" + val akkaHttp = "10.4.0" + + // Other Libraries + val cats = "2.13.0" + val utest = "0.8.2" + val caliban = "2.9.2" + val chimney = "1.7.3" + val ducktape = "0.2.7" + val elastic4s = "7.12.4" + val http4s = "0.23.30" + val http4sBlaze = "0.23.17" + val http4sPac4J = "5.0.0" + val laminar = "17.2.0" + val laminext = "0.17.0" + val logbackClassic = "1.5.17" + val magnum = "2.0.0-M1" + val pac4j = "6.0.3" + val play = "2.8.18" + val playJson = "2.9.3" + val quill = "4.8.6" + val scalaJsMacroTaskExecutor = "1.1.1" + val scalaJsJavaSecureRandom = "1.0.0" + val scalatags = "0.13.1" + val scalaTest = "3.2.19" + val slick = "3.3.3" + val sttpClient3 = "3.10.3" + val sttpClient4 = "4.0.0-RC1" + val support = "1.7.0" + val tapir = "1.11.16" + val urlDsl = "0.7.0" + val waypoint = "9.0.0" +} \ No newline at end of file diff --git a/mill-iw-support/core/src/IWPublishModule.scala b/mill-iw-support/core/src/IWPublishModule.scala new file mode 100644 index 0000000..5ae0ade --- /dev/null +++ b/mill-iw-support/core/src/IWPublishModule.scala @@ -0,0 +1,22 @@ +package works.iterative.mill + +import mill._ +import mill.scalalib._ +import mill.scalalib.publish._ + +/** Standard publish module trait for Iterative Works projects using Mill. This + * trait sets up standard publishing configuration for IW Maven repositories. + * + * To use this trait, simply extend it in your module and define: + * - publishVersion: The version to publish + * - pomSettings: The POM settings for the publication + * + * Use IW_USERNAME and IW_PASSWORD environment variables for authentication. + */ +trait IWPublishModule extends PublishModule { + + override def sonatypeUri = "https://dig.iterative.works/maven/releases" + override def sonatypeSnapshotUri = + "https://dig.iterative.works/maven/snapshots" + +} diff --git a/mill-iw-support/core/src/IWScalaModule.scala b/mill-iw-support/core/src/IWScalaModule.scala new file mode 100644 index 0000000..021bccc --- /dev/null +++ b/mill-iw-support/core/src/IWScalaModule.scala @@ -0,0 +1,97 @@ +package works.iterative.mill + +import mill._ +import mill.scalalib._ +import mill.scalalib.scalafmt._ + +/** Standard Scala module trait for Iterative Works projects using Mill. This + * trait sets up standard compiler options and configuration. + */ +trait IWScalaModule + extends ScalaModule + with ScalafmtModule + with IWScalaVersions { + + override def scalaVersion = scala3Version + + // Define base compiler options based on Scala version + def baseScalacOptions = T { + if (scalaVersion().startsWith("2.")) { + Seq( + "-encoding", + "utf8", + "-feature", + "-unchecked", + "-language:existentials", + "-language:experimental.macros", + "-language:higherKinds", + "-language:implicitConversions", + "-Xlint:adapted-args", + "-Xlint:constant", + "-Xlint:delayedinit-select", + "-Xlint:deprecation", + "-Xlint:doc-detached", + "-Xlint:implicit-recursion", + "-Xlint:implicit-not-found", + "-Xlint:inaccessible", + "-Xlint:infer-any", + "-Xlint:missing-interpolator", + "-Xlint:nullary-unit", + "-Xlint:option-implicit", + "-Xlint:package-object-classes", + "-Xlint:poly-implicit-overload", + "-Xlint:private-shadow", + "-Xlint:stars-align", + "-Xlint:strict-unsealed-patmat", + "-Xlint:type-parameter-shadow", + "-Xlint:-byname-implicit", + "-Wdead-code", + "-Wextra-implicit", + "-Wnumeric-widen", + "-Wvalue-discard", + "-Wnonunit-statement", + "-Wunused:nowarn", + "-Wunused:implicits", + "-Wunused:explicits", + "-Wunused:imports", + "-Wunused:locals", + "-Wunused:params", + "-Wunused:patvars", + "-Wunused:privates" + ) + } else { + Seq( + "-encoding", + "utf8", + "-deprecation", + "-feature", + "-unchecked", + "-language:experimental.macros", + "-language:higherKinds", + "-language:implicitConversions", + "-Xkind-projector", + "-Wvalue-discard", + "-Wnonunit-statement", + "-Wunused:implicits", + "-Wunused:explicits", + "-Wunused:imports", + "-Wunused:locals", + "-Wunused:params", + "-Wunused:privates" + ) + } + } + + // Enable SemanticDB for tooling + def semanticDbOptions = Seq( + "-Xsemanticdb" + ) + + // Override scalacOptions to include both base options and SemanticDB options + override def scalacOptions = T { + super.scalacOptions() ++ baseScalacOptions() ++ semanticDbOptions + } + + // Base test configuration + trait IWTests extends ScalaTests with ScalafmtModule +} diff --git a/mill-iw-support/core/src/IWScalaVersions.scala b/mill-iw-support/core/src/IWScalaVersions.scala new file mode 100644 index 0000000..aa70cb6 --- /dev/null +++ b/mill-iw-support/core/src/IWScalaVersions.scala @@ -0,0 +1,9 @@ +package works.iterative.mill + +trait IWScalaVersions { + val scala2Version = "2.13.16" + val scala3Version = "3.6.4" + val scala3LTSVersion = "3.3.5" +} + +object IWScalaVersions extends IWScalaVersions diff --git a/mill-iw-support/core/src/package.scala b/mill-iw-support/core/src/package.scala new file mode 100644 index 0000000..1ba4556 --- /dev/null +++ b/mill-iw-support/core/src/package.scala @@ -0,0 +1,6 @@ +package works.iterative + +package object mill { + val IWVersions = IWMillVersions + val IWDeps = IWMillDeps +} diff --git a/mill-iw-support/example/.mill-version b/mill-iw-support/example/.mill-version new file mode 100644 index 0000000..54dbed4 --- /dev/null +++ b/mill-iw-support/example/.mill-version @@ -0,0 +1 @@ +0.12.10 diff --git a/mill-iw-support/example/build.sc b/mill-iw-support/example/build.sc index 5da77ab..cae5a6a 100644 --- a/mill-iw-support/example/build.sc +++ b/mill-iw-support/example/build.sc @@ -1,11 +1,22 @@ import mill._ import mill.scalalib._ +import mill.scalalib.publish._ +import mill.scalalib.scalafmt._ + +// Import our IW Mill Support library import $ivy.`works.iterative::mill-iw-support:0.1.0-SNAPSHOT` import works.iterative.mill._ +// Define a BOM (Bill of Materials) module +object bom extends IWBomModule + +// Example module using the IW Mill support with BOM object example extends IWScalaModule with IWPublishModule { def publishVersion = "0.1.0-SNAPSHOT" - + + // Use the BOM module for dependency management + override def bomModuleDeps = Seq(bom) + def pomSettings = PomSettings( description = "Example Mill Project using IW Mill Support", organization = "com.example", @@ -16,15 +27,25 @@ Developer("developer", "Example Developer", "https://github.com/example") ) ) - - // Use centralized dependency versions and definitions - def ivyDeps = super.ivyDeps() ++ IWMillDeps.useZIO() ++ Agg( - IWMillDeps.zioJson, - IWMillDeps.tapirCore, - IWMillDeps.tapirZIO, - IWMillDeps.tapirZIOJson + + // With BOM, we don't need to specify versions + // They're managed by the BOM module + def ivyDeps = super.ivyDeps() ++ Agg( + ivy"dev.zio::zio", + ivy"dev.zio::zio-json", + ivy"com.softwaremill.sttp.tapir::tapir-core", + ivy"com.softwaremill.sttp.tapir::tapir-zio", + ivy"com.softwaremill.sttp.tapir::tapir-json-zio" ) - - // Define test module extending IWTests - object test extends Tests with IWTests -} \ No newline at end of file + + // Define test module using standard Mill approach + object test extends ScalaTests with TestModule.ZioTest { + // Use the BOM module for dependency management + override def bomModuleDeps = Seq(bom) + + def ivyDeps = super.ivyDeps() ++ Agg( + ivy"dev.zio::zio-test", // Version comes from BOM + ivy"dev.zio::zio-test-sbt" // Version comes from BOM + ) + } +} diff --git a/mill-iw-support/example/example/src/Example.scala b/mill-iw-support/example/example/src/Example.scala new file mode 100644 index 0000000..aa28980 --- /dev/null +++ b/mill-iw-support/example/example/src/Example.scala @@ -0,0 +1,27 @@ +package example + +import zio._ +import zio.json._ + +// Simple example class +case class User(id: String, name: String, email: String) + +object User { + // JSON codecs using ZIO JSON + implicit val encoder: JsonEncoder[User] = DeriveJsonEncoder.gen[User] + implicit val decoder: JsonDecoder[User] = DeriveJsonDecoder.gen[User] +} + +object Example extends ZIOAppDefault { + + val program = for { + _ <- Console.printLine("Starting example application") + user = User("1", "John Doe", "john@example.com") + json <- ZIO.succeed(user.toJson) + _ <- Console.printLine(s"User as JSON: $json") + parsed <- ZIO.fromEither(json.fromJson[User]) + _ <- Console.printLine(s"Parsed user: $parsed") + } yield () + + def run = program +} \ No newline at end of file diff --git a/mill-iw-support/example/example/test/src/ExampleTest.scala b/mill-iw-support/example/example/test/src/ExampleTest.scala new file mode 100644 index 0000000..951c2d8 --- /dev/null +++ b/mill-iw-support/example/example/test/src/ExampleTest.scala @@ -0,0 +1,26 @@ +package example + +import zio._ +import zio.json._ +import zio.test._ +import zio.test.Assertion._ + +object ExampleTest extends ZIOSpecDefault { + override def spec = suite("Example suite")( + test("User JSON serialization") { + val user = User("1", "John Doe", "john@example.com") + val json = user.toJson + val expected = + """{"id":"1","name":"John Doe","email":"john@example.com"}""" + + assertTrue(json == expected) + }, + test("User JSON deserialization") { + val json = """{"id":"1","name":"John Doe","email":"john@example.com"}""" + val parsed = json.fromJson[User] + val expected = User("1", "John Doe", "john@example.com") + + assertTrue(parsed == Right(expected)) + } + ) +} diff --git a/mill-iw-support/example/mill b/mill-iw-support/example/mill new file mode 100755 index 0000000..f349762 --- /dev/null +++ b/mill-iw-support/example/mill @@ -0,0 +1,265 @@ +#!/usr/bin/env sh + +# This is a wrapper script, that automatically download mill from GitHub release pages +# You can give the required mill version with --mill-version parameter +# If no version is given, it falls back to the value of DEFAULT_MILL_VERSION +# +# Original Project page: https://github.com/lefou/millw +# Script Version: 0.4.12 +# +# If you want to improve this script, please also contribute your changes back! +# +# Licensed under the Apache License, Version 2.0 + +set -e + +if [ -z "${DEFAULT_MILL_VERSION}" ] ; then + DEFAULT_MILL_VERSION=0.12.10 +fi + + +if [ -z "${GITHUB_RELEASE_CDN}" ] ; then + GITHUB_RELEASE_CDN="" +fi + + +MILL_REPO_URL="https://github.com/com-lihaoyi/mill" + +if [ -z "${CURL_CMD}" ] ; then + CURL_CMD=curl +fi + +# Explicit commandline argument takes precedence over all other methods +if [ "$1" = "--mill-version" ] ; then + shift + if [ "x$1" != "x" ] ; then + MILL_VERSION="$1" + shift + else + echo "You specified --mill-version without a version." 1>&2 + echo "Please provide a version that matches one provided on" 1>&2 + echo "${MILL_REPO_URL}/releases" 1>&2 + false + fi +fi + +# Please note, that if a MILL_VERSION is already set in the environment, +# We reuse it's value and skip searching for a value. + +# If not already set, read .mill-version file +if [ -z "${MILL_VERSION}" ] ; then + if [ -f ".mill-version" ] ; then + MILL_VERSION="$(tr '\r' '\n' < .mill-version | head -n 1 2> /dev/null)" + elif [ -f ".config/mill-version" ] ; then + MILL_VERSION="$(tr '\r' '\n' < .config/mill-version | head -n 1 2> /dev/null)" + fi +fi + +MILL_USER_CACHE_DIR="${XDG_CACHE_HOME:-${HOME}/.cache}/mill" + +if [ -z "${MILL_DOWNLOAD_PATH}" ] ; then + MILL_DOWNLOAD_PATH="${MILL_USER_CACHE_DIR}/download" +fi + +# If not already set, try to fetch newest from Github +if [ -z "${MILL_VERSION}" ] ; then + # TODO: try to load latest version from release page + echo "No mill version specified." 1>&2 + echo "You should provide a version via '.mill-version' file or --mill-version option." 1>&2 + + mkdir -p "${MILL_DOWNLOAD_PATH}" + LANG=C touch -d '1 hour ago' "${MILL_DOWNLOAD_PATH}/.expire_latest" 2>/dev/null || ( + # we might be on OSX or BSD which don't have -d option for touch + # but probably a -A [-][[hh]mm]SS + touch "${MILL_DOWNLOAD_PATH}/.expire_latest"; touch -A -010000 "${MILL_DOWNLOAD_PATH}/.expire_latest" + ) || ( + # in case we still failed, we retry the first touch command with the intention + # to show the (previously suppressed) error message + LANG=C touch -d '1 hour ago' "${MILL_DOWNLOAD_PATH}/.expire_latest" + ) + + # POSIX shell variant of bash's -nt operator, see https://unix.stackexchange.com/a/449744/6993 + # if [ "${MILL_DOWNLOAD_PATH}/.latest" -nt "${MILL_DOWNLOAD_PATH}/.expire_latest" ] ; then + if [ -n "$(find -L "${MILL_DOWNLOAD_PATH}/.latest" -prune -newer "${MILL_DOWNLOAD_PATH}/.expire_latest")" ]; then + # we know a current latest version + MILL_VERSION=$(head -n 1 "${MILL_DOWNLOAD_PATH}"/.latest 2> /dev/null) + fi + + if [ -z "${MILL_VERSION}" ] ; then + # we don't know a current latest version + echo "Retrieving latest mill version ..." 1>&2 + LANG=C ${CURL_CMD} -s -i -f -I ${MILL_REPO_URL}/releases/latest 2> /dev/null | grep --ignore-case Location: | sed s'/^.*tag\///' | tr -d '\r\n' > "${MILL_DOWNLOAD_PATH}/.latest" + MILL_VERSION=$(head -n 1 "${MILL_DOWNLOAD_PATH}"/.latest 2> /dev/null) + fi + + if [ -z "${MILL_VERSION}" ] ; then + # Last resort + MILL_VERSION="${DEFAULT_MILL_VERSION}" + echo "Falling back to hardcoded mill version ${MILL_VERSION}" 1>&2 + else + echo "Using mill version ${MILL_VERSION}" 1>&2 + fi +fi + +MILL_NATIVE_SUFFIX="-native" +FULL_MILL_VERSION=$MILL_VERSION +ARTIFACT_SUFFIX="" +case "$MILL_VERSION" in + *"$MILL_NATIVE_SUFFIX") + MILL_VERSION=${MILL_VERSION%"$MILL_NATIVE_SUFFIX"} + if [ "$(expr substr $(uname -s) 1 5 2>/dev/null)" = "Linux" ]; then + if [ "$(uname -m)" = "aarch64" ]; then + ARTIFACT_SUFFIX="-native-linux-aarch64" + else + ARTIFACT_SUFFIX="-native-linux-amd64" + fi + elif [ "$(uname)" = "Darwin" ]; then + if [ "$(uname -m)" = "arm64" ]; then + ARTIFACT_SUFFIX="-native-mac-aarch64" + else + ARTIFACT_SUFFIX="-native-mac-amd64" + fi + else + echo "This native mill launcher supports only Linux and macOS." 1>&2 + exit 1 + fi +esac + +MILL="${MILL_DOWNLOAD_PATH}/${FULL_MILL_VERSION}" + +try_to_use_system_mill() { + if [ "$(uname)" != "Linux" ]; then + return 0 + fi + + MILL_IN_PATH="$(command -v mill || true)" + + if [ -z "${MILL_IN_PATH}" ]; then + return 0 + fi + + SYSTEM_MILL_FIRST_TWO_BYTES=$(head --bytes=2 "${MILL_IN_PATH}") + if [ "${SYSTEM_MILL_FIRST_TWO_BYTES}" = "#!" ]; then + # MILL_IN_PATH is (very likely) a shell script and not the mill + # executable, ignore it. + return 0 + fi + + SYSTEM_MILL_PATH=$(readlink -e "${MILL_IN_PATH}") + SYSTEM_MILL_SIZE=$(stat --format=%s "${SYSTEM_MILL_PATH}") + SYSTEM_MILL_MTIME=$(stat --format=%y "${SYSTEM_MILL_PATH}") + + if [ ! -d "${MILL_USER_CACHE_DIR}" ]; then + mkdir -p "${MILL_USER_CACHE_DIR}" + fi + + SYSTEM_MILL_INFO_FILE="${MILL_USER_CACHE_DIR}/system-mill-info" + if [ -f "${SYSTEM_MILL_INFO_FILE}" ]; then + parseSystemMillInfo() { + LINE_NUMBER="${1}" + # Select the line number of the SYSTEM_MILL_INFO_FILE, cut the + # variable definition in that line in two halves and return + # the value, and finally remove the quotes. + sed -n "${LINE_NUMBER}p" "${SYSTEM_MILL_INFO_FILE}" |\ + cut -d= -f2 |\ + sed 's/"\(.*\)"/\1/' + } + + CACHED_SYSTEM_MILL_PATH=$(parseSystemMillInfo 1) + CACHED_SYSTEM_MILL_VERSION=$(parseSystemMillInfo 2) + CACHED_SYSTEM_MILL_SIZE=$(parseSystemMillInfo 3) + CACHED_SYSTEM_MILL_MTIME=$(parseSystemMillInfo 4) + + if [ "${SYSTEM_MILL_PATH}" = "${CACHED_SYSTEM_MILL_PATH}" ] \ + && [ "${SYSTEM_MILL_SIZE}" = "${CACHED_SYSTEM_MILL_SIZE}" ] \ + && [ "${SYSTEM_MILL_MTIME}" = "${CACHED_SYSTEM_MILL_MTIME}" ]; then + if [ "${CACHED_SYSTEM_MILL_VERSION}" = "${MILL_VERSION}" ]; then + MILL="${SYSTEM_MILL_PATH}" + return 0 + else + return 0 + fi + fi + fi + + SYSTEM_MILL_VERSION=$(${SYSTEM_MILL_PATH} --version | head -n1 | sed -n 's/^Mill.*version \(.*\)/\1/p') + + cat < "${SYSTEM_MILL_INFO_FILE}" +CACHED_SYSTEM_MILL_PATH="${SYSTEM_MILL_PATH}" +CACHED_SYSTEM_MILL_VERSION="${SYSTEM_MILL_VERSION}" +CACHED_SYSTEM_MILL_SIZE="${SYSTEM_MILL_SIZE}" +CACHED_SYSTEM_MILL_MTIME="${SYSTEM_MILL_MTIME}" +EOF + + if [ "${SYSTEM_MILL_VERSION}" = "${MILL_VERSION}" ]; then + MILL="${SYSTEM_MILL_PATH}" + fi +} +try_to_use_system_mill + +# If not already downloaded, download it +if [ ! -s "${MILL}" ] ; then + + # support old non-XDG download dir + MILL_OLD_DOWNLOAD_PATH="${HOME}/.mill/download" + OLD_MILL="${MILL_OLD_DOWNLOAD_PATH}/${MILL_VERSION}" + if [ -x "${OLD_MILL}" ] ; then + MILL="${OLD_MILL}" + else + case $MILL_VERSION in + 0.0.* | 0.1.* | 0.2.* | 0.3.* | 0.4.* ) + DOWNLOAD_SUFFIX="" + DOWNLOAD_FROM_MAVEN=0 + ;; + 0.5.* | 0.6.* | 0.7.* | 0.8.* | 0.9.* | 0.10.* | 0.11.0-M* ) + DOWNLOAD_SUFFIX="-assembly" + DOWNLOAD_FROM_MAVEN=0 + ;; + *) + DOWNLOAD_SUFFIX="-assembly" + DOWNLOAD_FROM_MAVEN=1 + ;; + esac + + DOWNLOAD_FILE=$(mktemp mill.XXXXXX) + + if [ "$DOWNLOAD_FROM_MAVEN" = "1" ] ; then + DOWNLOAD_URL="https://repo1.maven.org/maven2/com/lihaoyi/mill-dist${ARTIFACT_SUFFIX}/${MILL_VERSION}/mill-dist${ARTIFACT_SUFFIX}-${MILL_VERSION}.jar" + else + MILL_VERSION_TAG=$(echo "$MILL_VERSION" | sed -E 's/([^-]+)(-M[0-9]+)?(-.*)?/\1\2/') + DOWNLOAD_URL="${GITHUB_RELEASE_CDN}${MILL_REPO_URL}/releases/download/${MILL_VERSION_TAG}/${MILL_VERSION}${DOWNLOAD_SUFFIX}" + unset MILL_VERSION_TAG + fi + + # TODO: handle command not found + echo "Downloading mill ${MILL_VERSION} from ${DOWNLOAD_URL} ..." 1>&2 + ${CURL_CMD} -f -L -o "${DOWNLOAD_FILE}" "${DOWNLOAD_URL}" + chmod +x "${DOWNLOAD_FILE}" + mkdir -p "${MILL_DOWNLOAD_PATH}" + mv "${DOWNLOAD_FILE}" "${MILL}" + + unset DOWNLOAD_FILE + unset DOWNLOAD_SUFFIX + fi +fi + +if [ -z "$MILL_MAIN_CLI" ] ; then + MILL_MAIN_CLI="${0}" +fi + +MILL_FIRST_ARG="" +if [ "$1" = "--bsp" ] || [ "$1" = "-i" ] || [ "$1" = "--interactive" ] || [ "$1" = "--no-server" ] || [ "$1" = "--repl" ] || [ "$1" = "--help" ] ; then + # Need to preserve the first position of those listed options + MILL_FIRST_ARG=$1 + shift +fi + +unset MILL_DOWNLOAD_PATH +unset MILL_OLD_DOWNLOAD_PATH +unset OLD_MILL +unset MILL_VERSION +unset MILL_REPO_URL + +# We don't quote MILL_FIRST_ARG on purpose, so we can expand the empty value without quotes +# shellcheck disable=SC2086 +exec "${MILL}" $MILL_FIRST_ARG -D "mill.main.cli=${MILL_MAIN_CLI}" "$@" \ No newline at end of file diff --git a/mill-iw-support/.mill-version b/mill-iw-support/.mill-version new file mode 100644 index 0000000..54dbed4 --- /dev/null +++ b/mill-iw-support/.mill-version @@ -0,0 +1 @@ +0.12.10 diff --git a/mill-iw-support/.scalafmt.conf b/mill-iw-support/.scalafmt.conf new file mode 100644 index 0000000..ee7753a --- /dev/null +++ b/mill-iw-support/.scalafmt.conf @@ -0,0 +1,2 @@ +version = "3.7.15" +runner.dialect = scala213 \ No newline at end of file diff --git a/mill-iw-support/README.md b/mill-iw-support/README.md index 4986e5c..9bb1ba8 100644 --- a/mill-iw-support/README.md +++ b/mill-iw-support/README.md @@ -28,8 +28,45 @@ IWMillDeps.tapirCore // ivy"com.softwaremill.sttp.tapir::tapir-core:1.11.16" // Use dependency groups -IWMillDeps.useZIO() // Core ZIO dependencies with test framework -IWMillDeps.useZIOAll() // Comprehensive ZIO stack +IWMillDeps.zioCore // List of core ZIO dependencies +IWMillDeps.zioAll // List of all ZIO dependencies +``` + +### IWBomModule + +Bill of Materials (BOM) module for centralized dependency management: + +```scala +import mill._ +import works.iterative.mill._ + +// Define a BOM module +object bom extends IWBomModule { + def publishVersion = "0.1.0-SNAPSHOT" + + def pomSettings = PomSettings( + // Standard POM settings + ) + + // Override with custom dependencies if needed + override def depManagement = T { + super.depManagement() ++ Agg( + ivy"com.example::custom-lib:1.0.0" + ) + } +} + +// Use the BOM in modules +object myModule extends IWScalaModule { + // Reference the BOM module + override def bomModuleDeps = Seq(bom) + + // No need to specify versions - they come from the BOM + def ivyDeps = super.ivyDeps() ++ Agg( + ivy"dev.zio::zio", + ivy"dev.zio::zio-json" + ) +} ``` ### IWScalaModule @@ -44,10 +81,18 @@ // Use default Scala version (3.6.3) // and standard compiler options with SemanticDB enabled - def ivyDeps = super.ivyDeps() ++ IWMillDeps.useZIO() + def ivyDeps = super.ivyDeps() ++ Agg( + ivy"${IWMillDeps.zio}", + ivy"${IWMillDeps.zioJson}" + ) // Standard test module configuration - object test extends Tests with IWTests + object test extends ScalaTests with TestModule.ZioTest { + def ivyDeps = super.ivyDeps() ++ Agg( + ivy"${IWMillDeps.zioTest}", + ivy"${IWMillDeps.zioTestSbt}" + ) + } } ``` @@ -96,6 +141,51 @@ } ``` +## Dependency Management Approaches + +This library provides two approaches for dependency management: + +### 1. IWMillDeps (String Constants) + +Use the IWMillDeps object to access dependency strings with correct versions: + +```scala +def ivyDeps = super.ivyDeps() ++ Agg( + ivy"${IWMillDeps.zio}", + ivy"${IWMillDeps.zioJson}" +) +``` + +### 2. BOM (Bill of Materials) + +Use the IWBomModule to centralize dependency versions: + +```scala +// Define a BOM module +object bom extends IWBomModule { + def publishVersion = "0.1.0-SNAPSHOT" + def pomSettings = PomSettings(...) +} + +// Use the BOM in modules +object myModule extends IWScalaModule { + override def bomModuleDeps = Seq(bom) + + // No need to specify versions + def ivyDeps = super.ivyDeps() ++ Agg( + ivy"dev.zio::zio", + ivy"dev.zio::zio-json" + ) +} +``` + +The BOM approach offers these advantages: +- Enforces consistent versions across modules +- Simplifies version updates +- Controls transitive dependency versions +- Makes build files cleaner (no version strings) +- Can be published as a separate artifact + ## Examples See the `example` directory for a complete working example. diff --git a/mill-iw-support/build.sc b/mill-iw-support/build.sc index 5df80ae..2ed832c 100644 --- a/mill-iw-support/build.sc +++ b/mill-iw-support/build.sc @@ -2,20 +2,33 @@ import mill.scalalib._ import mill.scalalib.publish._ import mill.scalalib.scalafmt._ +import mill.scalalib.TestModule.Utest -object `mill-iw-support` extends Cross[IWMillSupportModule]("2.13.16", "3.6.3") +/** Main module for the mill-iw-support library. Built for Scala 2.13 which is + * compatible with Mill. + */ +object core extends ScalaModule with ScalafmtModule with PublishModule { + val millVersion = "0.12.10" -class IWMillSupportModule(val crossScalaVersion: String) extends CrossScalaModule with ScalafmtModule with PublishModule { + def scalaVersion = "2.13.16" + + def artifactName = "mill-iw-support" + def publishVersion = "0.1.0-SNAPSHOT" def pomSettings = PomSettings( description = "Iterative Works Mill Support Library", organization = "works.iterative", - url = "https://github.com/iterative-works/mill-iw-support", + url = "https://github.com/iterative-works/iw-project-support", licenses = Seq(License.MIT), - versionControl = VersionControl.github("iterative-works", "mill-iw-support"), + versionControl = + VersionControl.github("iterative-works", "iw-project-support"), developers = Seq( - Developer("mph", "Michal Přihoda", "https://github.com/iterative-works") + Developer( + "mprihoda", + "Michal Příhoda", + "https://github.com/iterative-works" + ) ) ) @@ -23,10 +36,15 @@ ivy"com.lihaoyi::os-lib:0.9.3" ) - object test extends Tests with ScalafmtModule { + // Mill's own dependencies needed for compilation + def compileIvyDeps = Agg( + ivy"com.lihaoyi::mill-main:${millVersion}", + ivy"com.lihaoyi::mill-scalalib:${millVersion}" + ) + + object test extends ScalaTests with Utest with ScalafmtModule { def ivyDeps = Agg( ivy"com.lihaoyi::utest:0.8.2" ) - def testFramework = "utest.runner.Framework" } -} \ No newline at end of file +} diff --git a/mill-iw-support/core/src/IWBomModule.scala b/mill-iw-support/core/src/IWBomModule.scala new file mode 100644 index 0000000..d8e70bb --- /dev/null +++ b/mill-iw-support/core/src/IWBomModule.scala @@ -0,0 +1,97 @@ +package works.iterative.mill + +import mill._ +import mill.scalalib._ +import mill.scalalib.publish._ + +/** Standard Bill of Materials (BOM) module for Iterative Works projects using + * Mill. This module centralizes dependency versioning across projects. + * + * A BOM module allows: + * 1. Centralized version management for dependencies 2. Enforcing specific + * versions of transitive dependencies 3. Applying uniform dependency + * management across multiple modules 4. Publishing a BOM artifact that + * other projects can consume + * + * Usage: + * 1. Create a BOM module in your build: + * ```scala + * object bom extends IWBomModule { + * def publishVersion = "0.1.0-SNAPSHOT" + * + * // Add your organization-specific customizations here + * def pomSettings = PomSettings(...) + * } + * ``` + * + * 2. Use the BOM in your modules: + * ```scala + * object myModule extends IWScalaModule { + * def bomModuleDeps = Seq(bom) + * + * def ivyDeps = Agg( + * // No need to specify versions as they come from the BOM + * ivy"dev.zio::zio", + * ivy"dev.zio::zio-json" + * ) + * } + * ``` + */ +trait IWBomModule extends BomModule with ScalaModule with IWScalaVersions { + + override def scalaVersion: T[String] = scala3Version + + /** The default implementation includes all standard IW dependencies from + * IWMillVersions and IWMillDeps. + */ + override def depManagement = T { + val V = IWMillVersions + + Agg( + // ZIO Core + ivy"dev.zio::zio:${V.zio}", + ivy"dev.zio::zio-test:${V.zio}", + ivy"dev.zio::zio-test-sbt:${V.zio}", + ivy"dev.zio::zio-streams:${V.zio}", + + // ZIO Extensions + ivy"dev.zio::zio-config:${V.zioConfig}", + ivy"dev.zio::zio-config-typesafe:${V.zioConfig}", + ivy"dev.zio::zio-config-magnolia:${V.zioConfig}", + ivy"dev.zio::zio-json:${V.zioJson}", + ivy"dev.zio::zio-logging:${V.zioLogging}", + ivy"dev.zio::zio-logging-slf4j:${V.zioLogging}", + ivy"dev.zio::zio-prelude:${V.zioPrelude}", + ivy"dev.zio::zio-interop-cats:${V.zioInteropCats}", + ivy"dev.zio::zio-interop-reactivestreams:${V.zioInteropReactiveStreams}", + ivy"dev.zio::zio-nio:${V.zioNIO}", + ivy"dev.zio::zio-cli:${V.zioCli}", + ivy"dev.zio::zio-schema:${V.zioSchema}", + ivy"dev.zio::zio-schema-derivation:${V.zioSchema}", + + // Database + ivy"io.getquill::quill-jdbc-zio:${V.quill}", + ivy"com.augustnagro::magnum:${V.magnum}", + ivy"com.augustnagro::magnumzio:${V.magnum}", + ivy"com.augustnagro::magnumpg:${V.magnum}", + + // HTTP + ivy"com.softwaremill.sttp.tapir::tapir-core:${V.tapir}", + ivy"com.softwaremill.sttp.tapir::tapir-zio:${V.tapir}", + ivy"com.softwaremill.sttp.tapir::tapir-json-zio:${V.tapir}", + + // UI/Frontend + ivy"com.lihaoyi::scalatags:${V.scalatags}", + ivy"com.raquo::laminar:${V.laminar}", + ivy"com.raquo::waypoint:${V.waypoint}", + ivy"be.doeraene::url-dsl:${V.urlDsl}", + + // Testing + ivy"org.scalatest::scalatest:${V.scalaTest}", + ivy"com.lihaoyi::utest:${V.utest}", + + // Logging + ivy"ch.qos.logback:logback-classic:${V.logbackClassic}" + ) + } +} diff --git a/mill-iw-support/core/src/IWMillDeps.scala b/mill-iw-support/core/src/IWMillDeps.scala new file mode 100644 index 0000000..d4cac1c --- /dev/null +++ b/mill-iw-support/core/src/IWMillDeps.scala @@ -0,0 +1,80 @@ +package works.iterative.mill + +import mill.api.Loose + +/** Standardized dependency management for Iterative Works projects using Mill. + * This object provides methods to get dependency strings with proper + * versioning. + * + * All versions are centralized in [[IWMillVersions]]. + * + * This object is designed to be used in build.sc files where the ivy string + * interpolator is available, like: + * + * {{{ + * def ivyDeps = Agg( + * ivy"$${IWMillDeps.zio}", + * ivy"$${IWMillDeps.zioJson}" + * ) + * }}} + */ +object IWMillDeps { + val V = IWMillVersions + + // ZIO Core + val zio = s"dev.zio::zio:${V.zio}" + val zioTest = s"dev.zio::zio-test:${V.zio}" + val zioTestSbt = s"dev.zio::zio-test-sbt:${V.zio}" + val zioStreams = s"dev.zio::zio-streams:${V.zio}" + + // ZIO Extensions + val zioConfig = s"dev.zio::zio-config:${V.zioConfig}" + val zioConfigTypesafe = s"dev.zio::zio-config-typesafe:${V.zioConfig}" + val zioConfigMagnolia = s"dev.zio::zio-config-magnolia:${V.zioConfig}" + val zioJson = s"dev.zio::zio-json:${V.zioJson}" + val zioLogging = s"dev.zio::zio-logging:${V.zioLogging}" + val zioLoggingSlf4j = s"dev.zio::zio-logging-slf4j:${V.zioLogging}" + val zioPrelude = s"dev.zio::zio-prelude:${V.zioPrelude}" + val zioInteropCats = s"dev.zio::zio-interop-cats:${V.zioInteropCats}" + val zioInteropReactiveStreams = + s"dev.zio::zio-interop-reactivestreams:${V.zioInteropReactiveStreams}" + val zioNIO = s"dev.zio::zio-nio:${V.zioNIO}" + val zioCli = s"dev.zio::zio-cli:${V.zioCli}" + val zioSchema = s"dev.zio::zio-schema:${V.zioSchema}" + val zioSchemaDerivation = s"dev.zio::zio-schema-derivation:${V.zioSchema}" + + // Collection of ZIO dependencies + val zioCore = List(zio, zioTest, zioTestSbt) + val zioJson_ = List(zioJson) + val zioAll = zioCore ++ List( + zioStreams, + zioConfig, + zioConfigTypesafe, + zioConfigMagnolia, + zioLogging, + zioPrelude + ) ++ zioJson_ + + // Database + val quill = s"io.getquill::quill-jdbc-zio:${V.quill}" + val magnum = s"com.augustnagro::magnum_3:${V.magnum}" + val magnumZIO = s"com.augustnagro::magnumzio_3:${V.magnum}" + val magnumPG = s"com.augustnagro::magnumpg_3:${V.magnum}" + + // HTTP + val tapirCore = s"com.softwaremill.sttp.tapir::tapir-core:${V.tapir}" + val tapirZIO = s"com.softwaremill.sttp.tapir::tapir-zio:${V.tapir}" + val tapirZIOJson = s"com.softwaremill.sttp.tapir::tapir-json-zio:${V.tapir}" + + // UI/Frontend + val scalatags = s"com.lihaoyi::scalatags:${V.scalatags}" + val laminar = s"com.raquo::laminar:${V.laminar}" + val waypoint = s"com.raquo::waypoint:${V.waypoint}" + val urlDsl = s"be.doeraene::url-dsl:${V.urlDsl}" + + // Testing + val scalaTest = s"org.scalatest::scalatest:${V.scalaTest}" + + // Logging + val logbackClassic = s"ch.qos.logback:logback-classic:${V.logbackClassic}" +} diff --git a/mill-iw-support/core/src/IWMillVersions.scala b/mill-iw-support/core/src/IWMillVersions.scala new file mode 100644 index 0000000..0ad9bd2 --- /dev/null +++ b/mill-iw-support/core/src/IWMillVersions.scala @@ -0,0 +1,56 @@ +package works.iterative.mill + +/** + * Centralized version management for Iterative Works projects using Mill. + * This object contains all library versions used across projects. + * + * Update this object to bump versions for all projects. + */ +object IWMillVersions { + // ZIO Ecosystem + val zio = "2.1.16" + val zioConfig = "4.0.3" + val zioCli = "0.7.0" + val zioInteropCats = "23.1.0.3" + val zioInteropReactiveStreams = "2.0.2" + val zioJson = "0.7.36" + val zioLogging = "2.5.0" + val zioNIO = "2.0.2" + val zioPrelude = "1.0.0-RC39" + val zioQuery = "0.7.6" + val zioSchema = "1.6.3" + + // Akka Ecosystem + val akka = "2.6.18" + val akkaHttp = "10.4.0" + + // Other Libraries + val cats = "2.13.0" + val utest = "0.8.2" + val caliban = "2.9.2" + val chimney = "1.7.3" + val ducktape = "0.2.7" + val elastic4s = "7.12.4" + val http4s = "0.23.30" + val http4sBlaze = "0.23.17" + val http4sPac4J = "5.0.0" + val laminar = "17.2.0" + val laminext = "0.17.0" + val logbackClassic = "1.5.17" + val magnum = "2.0.0-M1" + val pac4j = "6.0.3" + val play = "2.8.18" + val playJson = "2.9.3" + val quill = "4.8.6" + val scalaJsMacroTaskExecutor = "1.1.1" + val scalaJsJavaSecureRandom = "1.0.0" + val scalatags = "0.13.1" + val scalaTest = "3.2.19" + val slick = "3.3.3" + val sttpClient3 = "3.10.3" + val sttpClient4 = "4.0.0-RC1" + val support = "1.7.0" + val tapir = "1.11.16" + val urlDsl = "0.7.0" + val waypoint = "9.0.0" +} \ No newline at end of file diff --git a/mill-iw-support/core/src/IWPublishModule.scala b/mill-iw-support/core/src/IWPublishModule.scala new file mode 100644 index 0000000..5ae0ade --- /dev/null +++ b/mill-iw-support/core/src/IWPublishModule.scala @@ -0,0 +1,22 @@ +package works.iterative.mill + +import mill._ +import mill.scalalib._ +import mill.scalalib.publish._ + +/** Standard publish module trait for Iterative Works projects using Mill. This + * trait sets up standard publishing configuration for IW Maven repositories. + * + * To use this trait, simply extend it in your module and define: + * - publishVersion: The version to publish + * - pomSettings: The POM settings for the publication + * + * Use IW_USERNAME and IW_PASSWORD environment variables for authentication. + */ +trait IWPublishModule extends PublishModule { + + override def sonatypeUri = "https://dig.iterative.works/maven/releases" + override def sonatypeSnapshotUri = + "https://dig.iterative.works/maven/snapshots" + +} diff --git a/mill-iw-support/core/src/IWScalaModule.scala b/mill-iw-support/core/src/IWScalaModule.scala new file mode 100644 index 0000000..021bccc --- /dev/null +++ b/mill-iw-support/core/src/IWScalaModule.scala @@ -0,0 +1,97 @@ +package works.iterative.mill + +import mill._ +import mill.scalalib._ +import mill.scalalib.scalafmt._ + +/** Standard Scala module trait for Iterative Works projects using Mill. This + * trait sets up standard compiler options and configuration. + */ +trait IWScalaModule + extends ScalaModule + with ScalafmtModule + with IWScalaVersions { + + override def scalaVersion = scala3Version + + // Define base compiler options based on Scala version + def baseScalacOptions = T { + if (scalaVersion().startsWith("2.")) { + Seq( + "-encoding", + "utf8", + "-feature", + "-unchecked", + "-language:existentials", + "-language:experimental.macros", + "-language:higherKinds", + "-language:implicitConversions", + "-Xlint:adapted-args", + "-Xlint:constant", + "-Xlint:delayedinit-select", + "-Xlint:deprecation", + "-Xlint:doc-detached", + "-Xlint:implicit-recursion", + "-Xlint:implicit-not-found", + "-Xlint:inaccessible", + "-Xlint:infer-any", + "-Xlint:missing-interpolator", + "-Xlint:nullary-unit", + "-Xlint:option-implicit", + "-Xlint:package-object-classes", + "-Xlint:poly-implicit-overload", + "-Xlint:private-shadow", + "-Xlint:stars-align", + "-Xlint:strict-unsealed-patmat", + "-Xlint:type-parameter-shadow", + "-Xlint:-byname-implicit", + "-Wdead-code", + "-Wextra-implicit", + "-Wnumeric-widen", + "-Wvalue-discard", + "-Wnonunit-statement", + "-Wunused:nowarn", + "-Wunused:implicits", + "-Wunused:explicits", + "-Wunused:imports", + "-Wunused:locals", + "-Wunused:params", + "-Wunused:patvars", + "-Wunused:privates" + ) + } else { + Seq( + "-encoding", + "utf8", + "-deprecation", + "-feature", + "-unchecked", + "-language:experimental.macros", + "-language:higherKinds", + "-language:implicitConversions", + "-Xkind-projector", + "-Wvalue-discard", + "-Wnonunit-statement", + "-Wunused:implicits", + "-Wunused:explicits", + "-Wunused:imports", + "-Wunused:locals", + "-Wunused:params", + "-Wunused:privates" + ) + } + } + + // Enable SemanticDB for tooling + def semanticDbOptions = Seq( + "-Xsemanticdb" + ) + + // Override scalacOptions to include both base options and SemanticDB options + override def scalacOptions = T { + super.scalacOptions() ++ baseScalacOptions() ++ semanticDbOptions + } + + // Base test configuration + trait IWTests extends ScalaTests with ScalafmtModule +} diff --git a/mill-iw-support/core/src/IWScalaVersions.scala b/mill-iw-support/core/src/IWScalaVersions.scala new file mode 100644 index 0000000..aa70cb6 --- /dev/null +++ b/mill-iw-support/core/src/IWScalaVersions.scala @@ -0,0 +1,9 @@ +package works.iterative.mill + +trait IWScalaVersions { + val scala2Version = "2.13.16" + val scala3Version = "3.6.4" + val scala3LTSVersion = "3.3.5" +} + +object IWScalaVersions extends IWScalaVersions diff --git a/mill-iw-support/core/src/package.scala b/mill-iw-support/core/src/package.scala new file mode 100644 index 0000000..1ba4556 --- /dev/null +++ b/mill-iw-support/core/src/package.scala @@ -0,0 +1,6 @@ +package works.iterative + +package object mill { + val IWVersions = IWMillVersions + val IWDeps = IWMillDeps +} diff --git a/mill-iw-support/example/.mill-version b/mill-iw-support/example/.mill-version new file mode 100644 index 0000000..54dbed4 --- /dev/null +++ b/mill-iw-support/example/.mill-version @@ -0,0 +1 @@ +0.12.10 diff --git a/mill-iw-support/example/build.sc b/mill-iw-support/example/build.sc index 5da77ab..cae5a6a 100644 --- a/mill-iw-support/example/build.sc +++ b/mill-iw-support/example/build.sc @@ -1,11 +1,22 @@ import mill._ import mill.scalalib._ +import mill.scalalib.publish._ +import mill.scalalib.scalafmt._ + +// Import our IW Mill Support library import $ivy.`works.iterative::mill-iw-support:0.1.0-SNAPSHOT` import works.iterative.mill._ +// Define a BOM (Bill of Materials) module +object bom extends IWBomModule + +// Example module using the IW Mill support with BOM object example extends IWScalaModule with IWPublishModule { def publishVersion = "0.1.0-SNAPSHOT" - + + // Use the BOM module for dependency management + override def bomModuleDeps = Seq(bom) + def pomSettings = PomSettings( description = "Example Mill Project using IW Mill Support", organization = "com.example", @@ -16,15 +27,25 @@ Developer("developer", "Example Developer", "https://github.com/example") ) ) - - // Use centralized dependency versions and definitions - def ivyDeps = super.ivyDeps() ++ IWMillDeps.useZIO() ++ Agg( - IWMillDeps.zioJson, - IWMillDeps.tapirCore, - IWMillDeps.tapirZIO, - IWMillDeps.tapirZIOJson + + // With BOM, we don't need to specify versions + // They're managed by the BOM module + def ivyDeps = super.ivyDeps() ++ Agg( + ivy"dev.zio::zio", + ivy"dev.zio::zio-json", + ivy"com.softwaremill.sttp.tapir::tapir-core", + ivy"com.softwaremill.sttp.tapir::tapir-zio", + ivy"com.softwaremill.sttp.tapir::tapir-json-zio" ) - - // Define test module extending IWTests - object test extends Tests with IWTests -} \ No newline at end of file + + // Define test module using standard Mill approach + object test extends ScalaTests with TestModule.ZioTest { + // Use the BOM module for dependency management + override def bomModuleDeps = Seq(bom) + + def ivyDeps = super.ivyDeps() ++ Agg( + ivy"dev.zio::zio-test", // Version comes from BOM + ivy"dev.zio::zio-test-sbt" // Version comes from BOM + ) + } +} diff --git a/mill-iw-support/example/example/src/Example.scala b/mill-iw-support/example/example/src/Example.scala new file mode 100644 index 0000000..aa28980 --- /dev/null +++ b/mill-iw-support/example/example/src/Example.scala @@ -0,0 +1,27 @@ +package example + +import zio._ +import zio.json._ + +// Simple example class +case class User(id: String, name: String, email: String) + +object User { + // JSON codecs using ZIO JSON + implicit val encoder: JsonEncoder[User] = DeriveJsonEncoder.gen[User] + implicit val decoder: JsonDecoder[User] = DeriveJsonDecoder.gen[User] +} + +object Example extends ZIOAppDefault { + + val program = for { + _ <- Console.printLine("Starting example application") + user = User("1", "John Doe", "john@example.com") + json <- ZIO.succeed(user.toJson) + _ <- Console.printLine(s"User as JSON: $json") + parsed <- ZIO.fromEither(json.fromJson[User]) + _ <- Console.printLine(s"Parsed user: $parsed") + } yield () + + def run = program +} \ No newline at end of file diff --git a/mill-iw-support/example/example/test/src/ExampleTest.scala b/mill-iw-support/example/example/test/src/ExampleTest.scala new file mode 100644 index 0000000..951c2d8 --- /dev/null +++ b/mill-iw-support/example/example/test/src/ExampleTest.scala @@ -0,0 +1,26 @@ +package example + +import zio._ +import zio.json._ +import zio.test._ +import zio.test.Assertion._ + +object ExampleTest extends ZIOSpecDefault { + override def spec = suite("Example suite")( + test("User JSON serialization") { + val user = User("1", "John Doe", "john@example.com") + val json = user.toJson + val expected = + """{"id":"1","name":"John Doe","email":"john@example.com"}""" + + assertTrue(json == expected) + }, + test("User JSON deserialization") { + val json = """{"id":"1","name":"John Doe","email":"john@example.com"}""" + val parsed = json.fromJson[User] + val expected = User("1", "John Doe", "john@example.com") + + assertTrue(parsed == Right(expected)) + } + ) +} diff --git a/mill-iw-support/example/mill b/mill-iw-support/example/mill new file mode 100755 index 0000000..f349762 --- /dev/null +++ b/mill-iw-support/example/mill @@ -0,0 +1,265 @@ +#!/usr/bin/env sh + +# This is a wrapper script, that automatically download mill from GitHub release pages +# You can give the required mill version with --mill-version parameter +# If no version is given, it falls back to the value of DEFAULT_MILL_VERSION +# +# Original Project page: https://github.com/lefou/millw +# Script Version: 0.4.12 +# +# If you want to improve this script, please also contribute your changes back! +# +# Licensed under the Apache License, Version 2.0 + +set -e + +if [ -z "${DEFAULT_MILL_VERSION}" ] ; then + DEFAULT_MILL_VERSION=0.12.10 +fi + + +if [ -z "${GITHUB_RELEASE_CDN}" ] ; then + GITHUB_RELEASE_CDN="" +fi + + +MILL_REPO_URL="https://github.com/com-lihaoyi/mill" + +if [ -z "${CURL_CMD}" ] ; then + CURL_CMD=curl +fi + +# Explicit commandline argument takes precedence over all other methods +if [ "$1" = "--mill-version" ] ; then + shift + if [ "x$1" != "x" ] ; then + MILL_VERSION="$1" + shift + else + echo "You specified --mill-version without a version." 1>&2 + echo "Please provide a version that matches one provided on" 1>&2 + echo "${MILL_REPO_URL}/releases" 1>&2 + false + fi +fi + +# Please note, that if a MILL_VERSION is already set in the environment, +# We reuse it's value and skip searching for a value. + +# If not already set, read .mill-version file +if [ -z "${MILL_VERSION}" ] ; then + if [ -f ".mill-version" ] ; then + MILL_VERSION="$(tr '\r' '\n' < .mill-version | head -n 1 2> /dev/null)" + elif [ -f ".config/mill-version" ] ; then + MILL_VERSION="$(tr '\r' '\n' < .config/mill-version | head -n 1 2> /dev/null)" + fi +fi + +MILL_USER_CACHE_DIR="${XDG_CACHE_HOME:-${HOME}/.cache}/mill" + +if [ -z "${MILL_DOWNLOAD_PATH}" ] ; then + MILL_DOWNLOAD_PATH="${MILL_USER_CACHE_DIR}/download" +fi + +# If not already set, try to fetch newest from Github +if [ -z "${MILL_VERSION}" ] ; then + # TODO: try to load latest version from release page + echo "No mill version specified." 1>&2 + echo "You should provide a version via '.mill-version' file or --mill-version option." 1>&2 + + mkdir -p "${MILL_DOWNLOAD_PATH}" + LANG=C touch -d '1 hour ago' "${MILL_DOWNLOAD_PATH}/.expire_latest" 2>/dev/null || ( + # we might be on OSX or BSD which don't have -d option for touch + # but probably a -A [-][[hh]mm]SS + touch "${MILL_DOWNLOAD_PATH}/.expire_latest"; touch -A -010000 "${MILL_DOWNLOAD_PATH}/.expire_latest" + ) || ( + # in case we still failed, we retry the first touch command with the intention + # to show the (previously suppressed) error message + LANG=C touch -d '1 hour ago' "${MILL_DOWNLOAD_PATH}/.expire_latest" + ) + + # POSIX shell variant of bash's -nt operator, see https://unix.stackexchange.com/a/449744/6993 + # if [ "${MILL_DOWNLOAD_PATH}/.latest" -nt "${MILL_DOWNLOAD_PATH}/.expire_latest" ] ; then + if [ -n "$(find -L "${MILL_DOWNLOAD_PATH}/.latest" -prune -newer "${MILL_DOWNLOAD_PATH}/.expire_latest")" ]; then + # we know a current latest version + MILL_VERSION=$(head -n 1 "${MILL_DOWNLOAD_PATH}"/.latest 2> /dev/null) + fi + + if [ -z "${MILL_VERSION}" ] ; then + # we don't know a current latest version + echo "Retrieving latest mill version ..." 1>&2 + LANG=C ${CURL_CMD} -s -i -f -I ${MILL_REPO_URL}/releases/latest 2> /dev/null | grep --ignore-case Location: | sed s'/^.*tag\///' | tr -d '\r\n' > "${MILL_DOWNLOAD_PATH}/.latest" + MILL_VERSION=$(head -n 1 "${MILL_DOWNLOAD_PATH}"/.latest 2> /dev/null) + fi + + if [ -z "${MILL_VERSION}" ] ; then + # Last resort + MILL_VERSION="${DEFAULT_MILL_VERSION}" + echo "Falling back to hardcoded mill version ${MILL_VERSION}" 1>&2 + else + echo "Using mill version ${MILL_VERSION}" 1>&2 + fi +fi + +MILL_NATIVE_SUFFIX="-native" +FULL_MILL_VERSION=$MILL_VERSION +ARTIFACT_SUFFIX="" +case "$MILL_VERSION" in + *"$MILL_NATIVE_SUFFIX") + MILL_VERSION=${MILL_VERSION%"$MILL_NATIVE_SUFFIX"} + if [ "$(expr substr $(uname -s) 1 5 2>/dev/null)" = "Linux" ]; then + if [ "$(uname -m)" = "aarch64" ]; then + ARTIFACT_SUFFIX="-native-linux-aarch64" + else + ARTIFACT_SUFFIX="-native-linux-amd64" + fi + elif [ "$(uname)" = "Darwin" ]; then + if [ "$(uname -m)" = "arm64" ]; then + ARTIFACT_SUFFIX="-native-mac-aarch64" + else + ARTIFACT_SUFFIX="-native-mac-amd64" + fi + else + echo "This native mill launcher supports only Linux and macOS." 1>&2 + exit 1 + fi +esac + +MILL="${MILL_DOWNLOAD_PATH}/${FULL_MILL_VERSION}" + +try_to_use_system_mill() { + if [ "$(uname)" != "Linux" ]; then + return 0 + fi + + MILL_IN_PATH="$(command -v mill || true)" + + if [ -z "${MILL_IN_PATH}" ]; then + return 0 + fi + + SYSTEM_MILL_FIRST_TWO_BYTES=$(head --bytes=2 "${MILL_IN_PATH}") + if [ "${SYSTEM_MILL_FIRST_TWO_BYTES}" = "#!" ]; then + # MILL_IN_PATH is (very likely) a shell script and not the mill + # executable, ignore it. + return 0 + fi + + SYSTEM_MILL_PATH=$(readlink -e "${MILL_IN_PATH}") + SYSTEM_MILL_SIZE=$(stat --format=%s "${SYSTEM_MILL_PATH}") + SYSTEM_MILL_MTIME=$(stat --format=%y "${SYSTEM_MILL_PATH}") + + if [ ! -d "${MILL_USER_CACHE_DIR}" ]; then + mkdir -p "${MILL_USER_CACHE_DIR}" + fi + + SYSTEM_MILL_INFO_FILE="${MILL_USER_CACHE_DIR}/system-mill-info" + if [ -f "${SYSTEM_MILL_INFO_FILE}" ]; then + parseSystemMillInfo() { + LINE_NUMBER="${1}" + # Select the line number of the SYSTEM_MILL_INFO_FILE, cut the + # variable definition in that line in two halves and return + # the value, and finally remove the quotes. + sed -n "${LINE_NUMBER}p" "${SYSTEM_MILL_INFO_FILE}" |\ + cut -d= -f2 |\ + sed 's/"\(.*\)"/\1/' + } + + CACHED_SYSTEM_MILL_PATH=$(parseSystemMillInfo 1) + CACHED_SYSTEM_MILL_VERSION=$(parseSystemMillInfo 2) + CACHED_SYSTEM_MILL_SIZE=$(parseSystemMillInfo 3) + CACHED_SYSTEM_MILL_MTIME=$(parseSystemMillInfo 4) + + if [ "${SYSTEM_MILL_PATH}" = "${CACHED_SYSTEM_MILL_PATH}" ] \ + && [ "${SYSTEM_MILL_SIZE}" = "${CACHED_SYSTEM_MILL_SIZE}" ] \ + && [ "${SYSTEM_MILL_MTIME}" = "${CACHED_SYSTEM_MILL_MTIME}" ]; then + if [ "${CACHED_SYSTEM_MILL_VERSION}" = "${MILL_VERSION}" ]; then + MILL="${SYSTEM_MILL_PATH}" + return 0 + else + return 0 + fi + fi + fi + + SYSTEM_MILL_VERSION=$(${SYSTEM_MILL_PATH} --version | head -n1 | sed -n 's/^Mill.*version \(.*\)/\1/p') + + cat < "${SYSTEM_MILL_INFO_FILE}" +CACHED_SYSTEM_MILL_PATH="${SYSTEM_MILL_PATH}" +CACHED_SYSTEM_MILL_VERSION="${SYSTEM_MILL_VERSION}" +CACHED_SYSTEM_MILL_SIZE="${SYSTEM_MILL_SIZE}" +CACHED_SYSTEM_MILL_MTIME="${SYSTEM_MILL_MTIME}" +EOF + + if [ "${SYSTEM_MILL_VERSION}" = "${MILL_VERSION}" ]; then + MILL="${SYSTEM_MILL_PATH}" + fi +} +try_to_use_system_mill + +# If not already downloaded, download it +if [ ! -s "${MILL}" ] ; then + + # support old non-XDG download dir + MILL_OLD_DOWNLOAD_PATH="${HOME}/.mill/download" + OLD_MILL="${MILL_OLD_DOWNLOAD_PATH}/${MILL_VERSION}" + if [ -x "${OLD_MILL}" ] ; then + MILL="${OLD_MILL}" + else + case $MILL_VERSION in + 0.0.* | 0.1.* | 0.2.* | 0.3.* | 0.4.* ) + DOWNLOAD_SUFFIX="" + DOWNLOAD_FROM_MAVEN=0 + ;; + 0.5.* | 0.6.* | 0.7.* | 0.8.* | 0.9.* | 0.10.* | 0.11.0-M* ) + DOWNLOAD_SUFFIX="-assembly" + DOWNLOAD_FROM_MAVEN=0 + ;; + *) + DOWNLOAD_SUFFIX="-assembly" + DOWNLOAD_FROM_MAVEN=1 + ;; + esac + + DOWNLOAD_FILE=$(mktemp mill.XXXXXX) + + if [ "$DOWNLOAD_FROM_MAVEN" = "1" ] ; then + DOWNLOAD_URL="https://repo1.maven.org/maven2/com/lihaoyi/mill-dist${ARTIFACT_SUFFIX}/${MILL_VERSION}/mill-dist${ARTIFACT_SUFFIX}-${MILL_VERSION}.jar" + else + MILL_VERSION_TAG=$(echo "$MILL_VERSION" | sed -E 's/([^-]+)(-M[0-9]+)?(-.*)?/\1\2/') + DOWNLOAD_URL="${GITHUB_RELEASE_CDN}${MILL_REPO_URL}/releases/download/${MILL_VERSION_TAG}/${MILL_VERSION}${DOWNLOAD_SUFFIX}" + unset MILL_VERSION_TAG + fi + + # TODO: handle command not found + echo "Downloading mill ${MILL_VERSION} from ${DOWNLOAD_URL} ..." 1>&2 + ${CURL_CMD} -f -L -o "${DOWNLOAD_FILE}" "${DOWNLOAD_URL}" + chmod +x "${DOWNLOAD_FILE}" + mkdir -p "${MILL_DOWNLOAD_PATH}" + mv "${DOWNLOAD_FILE}" "${MILL}" + + unset DOWNLOAD_FILE + unset DOWNLOAD_SUFFIX + fi +fi + +if [ -z "$MILL_MAIN_CLI" ] ; then + MILL_MAIN_CLI="${0}" +fi + +MILL_FIRST_ARG="" +if [ "$1" = "--bsp" ] || [ "$1" = "-i" ] || [ "$1" = "--interactive" ] || [ "$1" = "--no-server" ] || [ "$1" = "--repl" ] || [ "$1" = "--help" ] ; then + # Need to preserve the first position of those listed options + MILL_FIRST_ARG=$1 + shift +fi + +unset MILL_DOWNLOAD_PATH +unset MILL_OLD_DOWNLOAD_PATH +unset OLD_MILL +unset MILL_VERSION +unset MILL_REPO_URL + +# We don't quote MILL_FIRST_ARG on purpose, so we can expand the empty value without quotes +# shellcheck disable=SC2086 +exec "${MILL}" $MILL_FIRST_ARG -D "mill.main.cli=${MILL_MAIN_CLI}" "$@" \ No newline at end of file diff --git a/mill-iw-support/example/src/Example.scala b/mill-iw-support/example/src/Example.scala deleted file mode 100644 index aa28980..0000000 --- a/mill-iw-support/example/src/Example.scala +++ /dev/null @@ -1,27 +0,0 @@ -package example - -import zio._ -import zio.json._ - -// Simple example class -case class User(id: String, name: String, email: String) - -object User { - // JSON codecs using ZIO JSON - implicit val encoder: JsonEncoder[User] = DeriveJsonEncoder.gen[User] - implicit val decoder: JsonDecoder[User] = DeriveJsonDecoder.gen[User] -} - -object Example extends ZIOAppDefault { - - val program = for { - _ <- Console.printLine("Starting example application") - user = User("1", "John Doe", "john@example.com") - json <- ZIO.succeed(user.toJson) - _ <- Console.printLine(s"User as JSON: $json") - parsed <- ZIO.fromEither(json.fromJson[User]) - _ <- Console.printLine(s"Parsed user: $parsed") - } yield () - - def run = program -} \ No newline at end of file diff --git a/mill-iw-support/.mill-version b/mill-iw-support/.mill-version new file mode 100644 index 0000000..54dbed4 --- /dev/null +++ b/mill-iw-support/.mill-version @@ -0,0 +1 @@ +0.12.10 diff --git a/mill-iw-support/.scalafmt.conf b/mill-iw-support/.scalafmt.conf new file mode 100644 index 0000000..ee7753a --- /dev/null +++ b/mill-iw-support/.scalafmt.conf @@ -0,0 +1,2 @@ +version = "3.7.15" +runner.dialect = scala213 \ No newline at end of file diff --git a/mill-iw-support/README.md b/mill-iw-support/README.md index 4986e5c..9bb1ba8 100644 --- a/mill-iw-support/README.md +++ b/mill-iw-support/README.md @@ -28,8 +28,45 @@ IWMillDeps.tapirCore // ivy"com.softwaremill.sttp.tapir::tapir-core:1.11.16" // Use dependency groups -IWMillDeps.useZIO() // Core ZIO dependencies with test framework -IWMillDeps.useZIOAll() // Comprehensive ZIO stack +IWMillDeps.zioCore // List of core ZIO dependencies +IWMillDeps.zioAll // List of all ZIO dependencies +``` + +### IWBomModule + +Bill of Materials (BOM) module for centralized dependency management: + +```scala +import mill._ +import works.iterative.mill._ + +// Define a BOM module +object bom extends IWBomModule { + def publishVersion = "0.1.0-SNAPSHOT" + + def pomSettings = PomSettings( + // Standard POM settings + ) + + // Override with custom dependencies if needed + override def depManagement = T { + super.depManagement() ++ Agg( + ivy"com.example::custom-lib:1.0.0" + ) + } +} + +// Use the BOM in modules +object myModule extends IWScalaModule { + // Reference the BOM module + override def bomModuleDeps = Seq(bom) + + // No need to specify versions - they come from the BOM + def ivyDeps = super.ivyDeps() ++ Agg( + ivy"dev.zio::zio", + ivy"dev.zio::zio-json" + ) +} ``` ### IWScalaModule @@ -44,10 +81,18 @@ // Use default Scala version (3.6.3) // and standard compiler options with SemanticDB enabled - def ivyDeps = super.ivyDeps() ++ IWMillDeps.useZIO() + def ivyDeps = super.ivyDeps() ++ Agg( + ivy"${IWMillDeps.zio}", + ivy"${IWMillDeps.zioJson}" + ) // Standard test module configuration - object test extends Tests with IWTests + object test extends ScalaTests with TestModule.ZioTest { + def ivyDeps = super.ivyDeps() ++ Agg( + ivy"${IWMillDeps.zioTest}", + ivy"${IWMillDeps.zioTestSbt}" + ) + } } ``` @@ -96,6 +141,51 @@ } ``` +## Dependency Management Approaches + +This library provides two approaches for dependency management: + +### 1. IWMillDeps (String Constants) + +Use the IWMillDeps object to access dependency strings with correct versions: + +```scala +def ivyDeps = super.ivyDeps() ++ Agg( + ivy"${IWMillDeps.zio}", + ivy"${IWMillDeps.zioJson}" +) +``` + +### 2. BOM (Bill of Materials) + +Use the IWBomModule to centralize dependency versions: + +```scala +// Define a BOM module +object bom extends IWBomModule { + def publishVersion = "0.1.0-SNAPSHOT" + def pomSettings = PomSettings(...) +} + +// Use the BOM in modules +object myModule extends IWScalaModule { + override def bomModuleDeps = Seq(bom) + + // No need to specify versions + def ivyDeps = super.ivyDeps() ++ Agg( + ivy"dev.zio::zio", + ivy"dev.zio::zio-json" + ) +} +``` + +The BOM approach offers these advantages: +- Enforces consistent versions across modules +- Simplifies version updates +- Controls transitive dependency versions +- Makes build files cleaner (no version strings) +- Can be published as a separate artifact + ## Examples See the `example` directory for a complete working example. diff --git a/mill-iw-support/build.sc b/mill-iw-support/build.sc index 5df80ae..2ed832c 100644 --- a/mill-iw-support/build.sc +++ b/mill-iw-support/build.sc @@ -2,20 +2,33 @@ import mill.scalalib._ import mill.scalalib.publish._ import mill.scalalib.scalafmt._ +import mill.scalalib.TestModule.Utest -object `mill-iw-support` extends Cross[IWMillSupportModule]("2.13.16", "3.6.3") +/** Main module for the mill-iw-support library. Built for Scala 2.13 which is + * compatible with Mill. + */ +object core extends ScalaModule with ScalafmtModule with PublishModule { + val millVersion = "0.12.10" -class IWMillSupportModule(val crossScalaVersion: String) extends CrossScalaModule with ScalafmtModule with PublishModule { + def scalaVersion = "2.13.16" + + def artifactName = "mill-iw-support" + def publishVersion = "0.1.0-SNAPSHOT" def pomSettings = PomSettings( description = "Iterative Works Mill Support Library", organization = "works.iterative", - url = "https://github.com/iterative-works/mill-iw-support", + url = "https://github.com/iterative-works/iw-project-support", licenses = Seq(License.MIT), - versionControl = VersionControl.github("iterative-works", "mill-iw-support"), + versionControl = + VersionControl.github("iterative-works", "iw-project-support"), developers = Seq( - Developer("mph", "Michal Přihoda", "https://github.com/iterative-works") + Developer( + "mprihoda", + "Michal Příhoda", + "https://github.com/iterative-works" + ) ) ) @@ -23,10 +36,15 @@ ivy"com.lihaoyi::os-lib:0.9.3" ) - object test extends Tests with ScalafmtModule { + // Mill's own dependencies needed for compilation + def compileIvyDeps = Agg( + ivy"com.lihaoyi::mill-main:${millVersion}", + ivy"com.lihaoyi::mill-scalalib:${millVersion}" + ) + + object test extends ScalaTests with Utest with ScalafmtModule { def ivyDeps = Agg( ivy"com.lihaoyi::utest:0.8.2" ) - def testFramework = "utest.runner.Framework" } -} \ No newline at end of file +} diff --git a/mill-iw-support/core/src/IWBomModule.scala b/mill-iw-support/core/src/IWBomModule.scala new file mode 100644 index 0000000..d8e70bb --- /dev/null +++ b/mill-iw-support/core/src/IWBomModule.scala @@ -0,0 +1,97 @@ +package works.iterative.mill + +import mill._ +import mill.scalalib._ +import mill.scalalib.publish._ + +/** Standard Bill of Materials (BOM) module for Iterative Works projects using + * Mill. This module centralizes dependency versioning across projects. + * + * A BOM module allows: + * 1. Centralized version management for dependencies 2. Enforcing specific + * versions of transitive dependencies 3. Applying uniform dependency + * management across multiple modules 4. Publishing a BOM artifact that + * other projects can consume + * + * Usage: + * 1. Create a BOM module in your build: + * ```scala + * object bom extends IWBomModule { + * def publishVersion = "0.1.0-SNAPSHOT" + * + * // Add your organization-specific customizations here + * def pomSettings = PomSettings(...) + * } + * ``` + * + * 2. Use the BOM in your modules: + * ```scala + * object myModule extends IWScalaModule { + * def bomModuleDeps = Seq(bom) + * + * def ivyDeps = Agg( + * // No need to specify versions as they come from the BOM + * ivy"dev.zio::zio", + * ivy"dev.zio::zio-json" + * ) + * } + * ``` + */ +trait IWBomModule extends BomModule with ScalaModule with IWScalaVersions { + + override def scalaVersion: T[String] = scala3Version + + /** The default implementation includes all standard IW dependencies from + * IWMillVersions and IWMillDeps. + */ + override def depManagement = T { + val V = IWMillVersions + + Agg( + // ZIO Core + ivy"dev.zio::zio:${V.zio}", + ivy"dev.zio::zio-test:${V.zio}", + ivy"dev.zio::zio-test-sbt:${V.zio}", + ivy"dev.zio::zio-streams:${V.zio}", + + // ZIO Extensions + ivy"dev.zio::zio-config:${V.zioConfig}", + ivy"dev.zio::zio-config-typesafe:${V.zioConfig}", + ivy"dev.zio::zio-config-magnolia:${V.zioConfig}", + ivy"dev.zio::zio-json:${V.zioJson}", + ivy"dev.zio::zio-logging:${V.zioLogging}", + ivy"dev.zio::zio-logging-slf4j:${V.zioLogging}", + ivy"dev.zio::zio-prelude:${V.zioPrelude}", + ivy"dev.zio::zio-interop-cats:${V.zioInteropCats}", + ivy"dev.zio::zio-interop-reactivestreams:${V.zioInteropReactiveStreams}", + ivy"dev.zio::zio-nio:${V.zioNIO}", + ivy"dev.zio::zio-cli:${V.zioCli}", + ivy"dev.zio::zio-schema:${V.zioSchema}", + ivy"dev.zio::zio-schema-derivation:${V.zioSchema}", + + // Database + ivy"io.getquill::quill-jdbc-zio:${V.quill}", + ivy"com.augustnagro::magnum:${V.magnum}", + ivy"com.augustnagro::magnumzio:${V.magnum}", + ivy"com.augustnagro::magnumpg:${V.magnum}", + + // HTTP + ivy"com.softwaremill.sttp.tapir::tapir-core:${V.tapir}", + ivy"com.softwaremill.sttp.tapir::tapir-zio:${V.tapir}", + ivy"com.softwaremill.sttp.tapir::tapir-json-zio:${V.tapir}", + + // UI/Frontend + ivy"com.lihaoyi::scalatags:${V.scalatags}", + ivy"com.raquo::laminar:${V.laminar}", + ivy"com.raquo::waypoint:${V.waypoint}", + ivy"be.doeraene::url-dsl:${V.urlDsl}", + + // Testing + ivy"org.scalatest::scalatest:${V.scalaTest}", + ivy"com.lihaoyi::utest:${V.utest}", + + // Logging + ivy"ch.qos.logback:logback-classic:${V.logbackClassic}" + ) + } +} diff --git a/mill-iw-support/core/src/IWMillDeps.scala b/mill-iw-support/core/src/IWMillDeps.scala new file mode 100644 index 0000000..d4cac1c --- /dev/null +++ b/mill-iw-support/core/src/IWMillDeps.scala @@ -0,0 +1,80 @@ +package works.iterative.mill + +import mill.api.Loose + +/** Standardized dependency management for Iterative Works projects using Mill. + * This object provides methods to get dependency strings with proper + * versioning. + * + * All versions are centralized in [[IWMillVersions]]. + * + * This object is designed to be used in build.sc files where the ivy string + * interpolator is available, like: + * + * {{{ + * def ivyDeps = Agg( + * ivy"$${IWMillDeps.zio}", + * ivy"$${IWMillDeps.zioJson}" + * ) + * }}} + */ +object IWMillDeps { + val V = IWMillVersions + + // ZIO Core + val zio = s"dev.zio::zio:${V.zio}" + val zioTest = s"dev.zio::zio-test:${V.zio}" + val zioTestSbt = s"dev.zio::zio-test-sbt:${V.zio}" + val zioStreams = s"dev.zio::zio-streams:${V.zio}" + + // ZIO Extensions + val zioConfig = s"dev.zio::zio-config:${V.zioConfig}" + val zioConfigTypesafe = s"dev.zio::zio-config-typesafe:${V.zioConfig}" + val zioConfigMagnolia = s"dev.zio::zio-config-magnolia:${V.zioConfig}" + val zioJson = s"dev.zio::zio-json:${V.zioJson}" + val zioLogging = s"dev.zio::zio-logging:${V.zioLogging}" + val zioLoggingSlf4j = s"dev.zio::zio-logging-slf4j:${V.zioLogging}" + val zioPrelude = s"dev.zio::zio-prelude:${V.zioPrelude}" + val zioInteropCats = s"dev.zio::zio-interop-cats:${V.zioInteropCats}" + val zioInteropReactiveStreams = + s"dev.zio::zio-interop-reactivestreams:${V.zioInteropReactiveStreams}" + val zioNIO = s"dev.zio::zio-nio:${V.zioNIO}" + val zioCli = s"dev.zio::zio-cli:${V.zioCli}" + val zioSchema = s"dev.zio::zio-schema:${V.zioSchema}" + val zioSchemaDerivation = s"dev.zio::zio-schema-derivation:${V.zioSchema}" + + // Collection of ZIO dependencies + val zioCore = List(zio, zioTest, zioTestSbt) + val zioJson_ = List(zioJson) + val zioAll = zioCore ++ List( + zioStreams, + zioConfig, + zioConfigTypesafe, + zioConfigMagnolia, + zioLogging, + zioPrelude + ) ++ zioJson_ + + // Database + val quill = s"io.getquill::quill-jdbc-zio:${V.quill}" + val magnum = s"com.augustnagro::magnum_3:${V.magnum}" + val magnumZIO = s"com.augustnagro::magnumzio_3:${V.magnum}" + val magnumPG = s"com.augustnagro::magnumpg_3:${V.magnum}" + + // HTTP + val tapirCore = s"com.softwaremill.sttp.tapir::tapir-core:${V.tapir}" + val tapirZIO = s"com.softwaremill.sttp.tapir::tapir-zio:${V.tapir}" + val tapirZIOJson = s"com.softwaremill.sttp.tapir::tapir-json-zio:${V.tapir}" + + // UI/Frontend + val scalatags = s"com.lihaoyi::scalatags:${V.scalatags}" + val laminar = s"com.raquo::laminar:${V.laminar}" + val waypoint = s"com.raquo::waypoint:${V.waypoint}" + val urlDsl = s"be.doeraene::url-dsl:${V.urlDsl}" + + // Testing + val scalaTest = s"org.scalatest::scalatest:${V.scalaTest}" + + // Logging + val logbackClassic = s"ch.qos.logback:logback-classic:${V.logbackClassic}" +} diff --git a/mill-iw-support/core/src/IWMillVersions.scala b/mill-iw-support/core/src/IWMillVersions.scala new file mode 100644 index 0000000..0ad9bd2 --- /dev/null +++ b/mill-iw-support/core/src/IWMillVersions.scala @@ -0,0 +1,56 @@ +package works.iterative.mill + +/** + * Centralized version management for Iterative Works projects using Mill. + * This object contains all library versions used across projects. + * + * Update this object to bump versions for all projects. + */ +object IWMillVersions { + // ZIO Ecosystem + val zio = "2.1.16" + val zioConfig = "4.0.3" + val zioCli = "0.7.0" + val zioInteropCats = "23.1.0.3" + val zioInteropReactiveStreams = "2.0.2" + val zioJson = "0.7.36" + val zioLogging = "2.5.0" + val zioNIO = "2.0.2" + val zioPrelude = "1.0.0-RC39" + val zioQuery = "0.7.6" + val zioSchema = "1.6.3" + + // Akka Ecosystem + val akka = "2.6.18" + val akkaHttp = "10.4.0" + + // Other Libraries + val cats = "2.13.0" + val utest = "0.8.2" + val caliban = "2.9.2" + val chimney = "1.7.3" + val ducktape = "0.2.7" + val elastic4s = "7.12.4" + val http4s = "0.23.30" + val http4sBlaze = "0.23.17" + val http4sPac4J = "5.0.0" + val laminar = "17.2.0" + val laminext = "0.17.0" + val logbackClassic = "1.5.17" + val magnum = "2.0.0-M1" + val pac4j = "6.0.3" + val play = "2.8.18" + val playJson = "2.9.3" + val quill = "4.8.6" + val scalaJsMacroTaskExecutor = "1.1.1" + val scalaJsJavaSecureRandom = "1.0.0" + val scalatags = "0.13.1" + val scalaTest = "3.2.19" + val slick = "3.3.3" + val sttpClient3 = "3.10.3" + val sttpClient4 = "4.0.0-RC1" + val support = "1.7.0" + val tapir = "1.11.16" + val urlDsl = "0.7.0" + val waypoint = "9.0.0" +} \ No newline at end of file diff --git a/mill-iw-support/core/src/IWPublishModule.scala b/mill-iw-support/core/src/IWPublishModule.scala new file mode 100644 index 0000000..5ae0ade --- /dev/null +++ b/mill-iw-support/core/src/IWPublishModule.scala @@ -0,0 +1,22 @@ +package works.iterative.mill + +import mill._ +import mill.scalalib._ +import mill.scalalib.publish._ + +/** Standard publish module trait for Iterative Works projects using Mill. This + * trait sets up standard publishing configuration for IW Maven repositories. + * + * To use this trait, simply extend it in your module and define: + * - publishVersion: The version to publish + * - pomSettings: The POM settings for the publication + * + * Use IW_USERNAME and IW_PASSWORD environment variables for authentication. + */ +trait IWPublishModule extends PublishModule { + + override def sonatypeUri = "https://dig.iterative.works/maven/releases" + override def sonatypeSnapshotUri = + "https://dig.iterative.works/maven/snapshots" + +} diff --git a/mill-iw-support/core/src/IWScalaModule.scala b/mill-iw-support/core/src/IWScalaModule.scala new file mode 100644 index 0000000..021bccc --- /dev/null +++ b/mill-iw-support/core/src/IWScalaModule.scala @@ -0,0 +1,97 @@ +package works.iterative.mill + +import mill._ +import mill.scalalib._ +import mill.scalalib.scalafmt._ + +/** Standard Scala module trait for Iterative Works projects using Mill. This + * trait sets up standard compiler options and configuration. + */ +trait IWScalaModule + extends ScalaModule + with ScalafmtModule + with IWScalaVersions { + + override def scalaVersion = scala3Version + + // Define base compiler options based on Scala version + def baseScalacOptions = T { + if (scalaVersion().startsWith("2.")) { + Seq( + "-encoding", + "utf8", + "-feature", + "-unchecked", + "-language:existentials", + "-language:experimental.macros", + "-language:higherKinds", + "-language:implicitConversions", + "-Xlint:adapted-args", + "-Xlint:constant", + "-Xlint:delayedinit-select", + "-Xlint:deprecation", + "-Xlint:doc-detached", + "-Xlint:implicit-recursion", + "-Xlint:implicit-not-found", + "-Xlint:inaccessible", + "-Xlint:infer-any", + "-Xlint:missing-interpolator", + "-Xlint:nullary-unit", + "-Xlint:option-implicit", + "-Xlint:package-object-classes", + "-Xlint:poly-implicit-overload", + "-Xlint:private-shadow", + "-Xlint:stars-align", + "-Xlint:strict-unsealed-patmat", + "-Xlint:type-parameter-shadow", + "-Xlint:-byname-implicit", + "-Wdead-code", + "-Wextra-implicit", + "-Wnumeric-widen", + "-Wvalue-discard", + "-Wnonunit-statement", + "-Wunused:nowarn", + "-Wunused:implicits", + "-Wunused:explicits", + "-Wunused:imports", + "-Wunused:locals", + "-Wunused:params", + "-Wunused:patvars", + "-Wunused:privates" + ) + } else { + Seq( + "-encoding", + "utf8", + "-deprecation", + "-feature", + "-unchecked", + "-language:experimental.macros", + "-language:higherKinds", + "-language:implicitConversions", + "-Xkind-projector", + "-Wvalue-discard", + "-Wnonunit-statement", + "-Wunused:implicits", + "-Wunused:explicits", + "-Wunused:imports", + "-Wunused:locals", + "-Wunused:params", + "-Wunused:privates" + ) + } + } + + // Enable SemanticDB for tooling + def semanticDbOptions = Seq( + "-Xsemanticdb" + ) + + // Override scalacOptions to include both base options and SemanticDB options + override def scalacOptions = T { + super.scalacOptions() ++ baseScalacOptions() ++ semanticDbOptions + } + + // Base test configuration + trait IWTests extends ScalaTests with ScalafmtModule +} diff --git a/mill-iw-support/core/src/IWScalaVersions.scala b/mill-iw-support/core/src/IWScalaVersions.scala new file mode 100644 index 0000000..aa70cb6 --- /dev/null +++ b/mill-iw-support/core/src/IWScalaVersions.scala @@ -0,0 +1,9 @@ +package works.iterative.mill + +trait IWScalaVersions { + val scala2Version = "2.13.16" + val scala3Version = "3.6.4" + val scala3LTSVersion = "3.3.5" +} + +object IWScalaVersions extends IWScalaVersions diff --git a/mill-iw-support/core/src/package.scala b/mill-iw-support/core/src/package.scala new file mode 100644 index 0000000..1ba4556 --- /dev/null +++ b/mill-iw-support/core/src/package.scala @@ -0,0 +1,6 @@ +package works.iterative + +package object mill { + val IWVersions = IWMillVersions + val IWDeps = IWMillDeps +} diff --git a/mill-iw-support/example/.mill-version b/mill-iw-support/example/.mill-version new file mode 100644 index 0000000..54dbed4 --- /dev/null +++ b/mill-iw-support/example/.mill-version @@ -0,0 +1 @@ +0.12.10 diff --git a/mill-iw-support/example/build.sc b/mill-iw-support/example/build.sc index 5da77ab..cae5a6a 100644 --- a/mill-iw-support/example/build.sc +++ b/mill-iw-support/example/build.sc @@ -1,11 +1,22 @@ import mill._ import mill.scalalib._ +import mill.scalalib.publish._ +import mill.scalalib.scalafmt._ + +// Import our IW Mill Support library import $ivy.`works.iterative::mill-iw-support:0.1.0-SNAPSHOT` import works.iterative.mill._ +// Define a BOM (Bill of Materials) module +object bom extends IWBomModule + +// Example module using the IW Mill support with BOM object example extends IWScalaModule with IWPublishModule { def publishVersion = "0.1.0-SNAPSHOT" - + + // Use the BOM module for dependency management + override def bomModuleDeps = Seq(bom) + def pomSettings = PomSettings( description = "Example Mill Project using IW Mill Support", organization = "com.example", @@ -16,15 +27,25 @@ Developer("developer", "Example Developer", "https://github.com/example") ) ) - - // Use centralized dependency versions and definitions - def ivyDeps = super.ivyDeps() ++ IWMillDeps.useZIO() ++ Agg( - IWMillDeps.zioJson, - IWMillDeps.tapirCore, - IWMillDeps.tapirZIO, - IWMillDeps.tapirZIOJson + + // With BOM, we don't need to specify versions + // They're managed by the BOM module + def ivyDeps = super.ivyDeps() ++ Agg( + ivy"dev.zio::zio", + ivy"dev.zio::zio-json", + ivy"com.softwaremill.sttp.tapir::tapir-core", + ivy"com.softwaremill.sttp.tapir::tapir-zio", + ivy"com.softwaremill.sttp.tapir::tapir-json-zio" ) - - // Define test module extending IWTests - object test extends Tests with IWTests -} \ No newline at end of file + + // Define test module using standard Mill approach + object test extends ScalaTests with TestModule.ZioTest { + // Use the BOM module for dependency management + override def bomModuleDeps = Seq(bom) + + def ivyDeps = super.ivyDeps() ++ Agg( + ivy"dev.zio::zio-test", // Version comes from BOM + ivy"dev.zio::zio-test-sbt" // Version comes from BOM + ) + } +} diff --git a/mill-iw-support/example/example/src/Example.scala b/mill-iw-support/example/example/src/Example.scala new file mode 100644 index 0000000..aa28980 --- /dev/null +++ b/mill-iw-support/example/example/src/Example.scala @@ -0,0 +1,27 @@ +package example + +import zio._ +import zio.json._ + +// Simple example class +case class User(id: String, name: String, email: String) + +object User { + // JSON codecs using ZIO JSON + implicit val encoder: JsonEncoder[User] = DeriveJsonEncoder.gen[User] + implicit val decoder: JsonDecoder[User] = DeriveJsonDecoder.gen[User] +} + +object Example extends ZIOAppDefault { + + val program = for { + _ <- Console.printLine("Starting example application") + user = User("1", "John Doe", "john@example.com") + json <- ZIO.succeed(user.toJson) + _ <- Console.printLine(s"User as JSON: $json") + parsed <- ZIO.fromEither(json.fromJson[User]) + _ <- Console.printLine(s"Parsed user: $parsed") + } yield () + + def run = program +} \ No newline at end of file diff --git a/mill-iw-support/example/example/test/src/ExampleTest.scala b/mill-iw-support/example/example/test/src/ExampleTest.scala new file mode 100644 index 0000000..951c2d8 --- /dev/null +++ b/mill-iw-support/example/example/test/src/ExampleTest.scala @@ -0,0 +1,26 @@ +package example + +import zio._ +import zio.json._ +import zio.test._ +import zio.test.Assertion._ + +object ExampleTest extends ZIOSpecDefault { + override def spec = suite("Example suite")( + test("User JSON serialization") { + val user = User("1", "John Doe", "john@example.com") + val json = user.toJson + val expected = + """{"id":"1","name":"John Doe","email":"john@example.com"}""" + + assertTrue(json == expected) + }, + test("User JSON deserialization") { + val json = """{"id":"1","name":"John Doe","email":"john@example.com"}""" + val parsed = json.fromJson[User] + val expected = User("1", "John Doe", "john@example.com") + + assertTrue(parsed == Right(expected)) + } + ) +} diff --git a/mill-iw-support/example/mill b/mill-iw-support/example/mill new file mode 100755 index 0000000..f349762 --- /dev/null +++ b/mill-iw-support/example/mill @@ -0,0 +1,265 @@ +#!/usr/bin/env sh + +# This is a wrapper script, that automatically download mill from GitHub release pages +# You can give the required mill version with --mill-version parameter +# If no version is given, it falls back to the value of DEFAULT_MILL_VERSION +# +# Original Project page: https://github.com/lefou/millw +# Script Version: 0.4.12 +# +# If you want to improve this script, please also contribute your changes back! +# +# Licensed under the Apache License, Version 2.0 + +set -e + +if [ -z "${DEFAULT_MILL_VERSION}" ] ; then + DEFAULT_MILL_VERSION=0.12.10 +fi + + +if [ -z "${GITHUB_RELEASE_CDN}" ] ; then + GITHUB_RELEASE_CDN="" +fi + + +MILL_REPO_URL="https://github.com/com-lihaoyi/mill" + +if [ -z "${CURL_CMD}" ] ; then + CURL_CMD=curl +fi + +# Explicit commandline argument takes precedence over all other methods +if [ "$1" = "--mill-version" ] ; then + shift + if [ "x$1" != "x" ] ; then + MILL_VERSION="$1" + shift + else + echo "You specified --mill-version without a version." 1>&2 + echo "Please provide a version that matches one provided on" 1>&2 + echo "${MILL_REPO_URL}/releases" 1>&2 + false + fi +fi + +# Please note, that if a MILL_VERSION is already set in the environment, +# We reuse it's value and skip searching for a value. + +# If not already set, read .mill-version file +if [ -z "${MILL_VERSION}" ] ; then + if [ -f ".mill-version" ] ; then + MILL_VERSION="$(tr '\r' '\n' < .mill-version | head -n 1 2> /dev/null)" + elif [ -f ".config/mill-version" ] ; then + MILL_VERSION="$(tr '\r' '\n' < .config/mill-version | head -n 1 2> /dev/null)" + fi +fi + +MILL_USER_CACHE_DIR="${XDG_CACHE_HOME:-${HOME}/.cache}/mill" + +if [ -z "${MILL_DOWNLOAD_PATH}" ] ; then + MILL_DOWNLOAD_PATH="${MILL_USER_CACHE_DIR}/download" +fi + +# If not already set, try to fetch newest from Github +if [ -z "${MILL_VERSION}" ] ; then + # TODO: try to load latest version from release page + echo "No mill version specified." 1>&2 + echo "You should provide a version via '.mill-version' file or --mill-version option." 1>&2 + + mkdir -p "${MILL_DOWNLOAD_PATH}" + LANG=C touch -d '1 hour ago' "${MILL_DOWNLOAD_PATH}/.expire_latest" 2>/dev/null || ( + # we might be on OSX or BSD which don't have -d option for touch + # but probably a -A [-][[hh]mm]SS + touch "${MILL_DOWNLOAD_PATH}/.expire_latest"; touch -A -010000 "${MILL_DOWNLOAD_PATH}/.expire_latest" + ) || ( + # in case we still failed, we retry the first touch command with the intention + # to show the (previously suppressed) error message + LANG=C touch -d '1 hour ago' "${MILL_DOWNLOAD_PATH}/.expire_latest" + ) + + # POSIX shell variant of bash's -nt operator, see https://unix.stackexchange.com/a/449744/6993 + # if [ "${MILL_DOWNLOAD_PATH}/.latest" -nt "${MILL_DOWNLOAD_PATH}/.expire_latest" ] ; then + if [ -n "$(find -L "${MILL_DOWNLOAD_PATH}/.latest" -prune -newer "${MILL_DOWNLOAD_PATH}/.expire_latest")" ]; then + # we know a current latest version + MILL_VERSION=$(head -n 1 "${MILL_DOWNLOAD_PATH}"/.latest 2> /dev/null) + fi + + if [ -z "${MILL_VERSION}" ] ; then + # we don't know a current latest version + echo "Retrieving latest mill version ..." 1>&2 + LANG=C ${CURL_CMD} -s -i -f -I ${MILL_REPO_URL}/releases/latest 2> /dev/null | grep --ignore-case Location: | sed s'/^.*tag\///' | tr -d '\r\n' > "${MILL_DOWNLOAD_PATH}/.latest" + MILL_VERSION=$(head -n 1 "${MILL_DOWNLOAD_PATH}"/.latest 2> /dev/null) + fi + + if [ -z "${MILL_VERSION}" ] ; then + # Last resort + MILL_VERSION="${DEFAULT_MILL_VERSION}" + echo "Falling back to hardcoded mill version ${MILL_VERSION}" 1>&2 + else + echo "Using mill version ${MILL_VERSION}" 1>&2 + fi +fi + +MILL_NATIVE_SUFFIX="-native" +FULL_MILL_VERSION=$MILL_VERSION +ARTIFACT_SUFFIX="" +case "$MILL_VERSION" in + *"$MILL_NATIVE_SUFFIX") + MILL_VERSION=${MILL_VERSION%"$MILL_NATIVE_SUFFIX"} + if [ "$(expr substr $(uname -s) 1 5 2>/dev/null)" = "Linux" ]; then + if [ "$(uname -m)" = "aarch64" ]; then + ARTIFACT_SUFFIX="-native-linux-aarch64" + else + ARTIFACT_SUFFIX="-native-linux-amd64" + fi + elif [ "$(uname)" = "Darwin" ]; then + if [ "$(uname -m)" = "arm64" ]; then + ARTIFACT_SUFFIX="-native-mac-aarch64" + else + ARTIFACT_SUFFIX="-native-mac-amd64" + fi + else + echo "This native mill launcher supports only Linux and macOS." 1>&2 + exit 1 + fi +esac + +MILL="${MILL_DOWNLOAD_PATH}/${FULL_MILL_VERSION}" + +try_to_use_system_mill() { + if [ "$(uname)" != "Linux" ]; then + return 0 + fi + + MILL_IN_PATH="$(command -v mill || true)" + + if [ -z "${MILL_IN_PATH}" ]; then + return 0 + fi + + SYSTEM_MILL_FIRST_TWO_BYTES=$(head --bytes=2 "${MILL_IN_PATH}") + if [ "${SYSTEM_MILL_FIRST_TWO_BYTES}" = "#!" ]; then + # MILL_IN_PATH is (very likely) a shell script and not the mill + # executable, ignore it. + return 0 + fi + + SYSTEM_MILL_PATH=$(readlink -e "${MILL_IN_PATH}") + SYSTEM_MILL_SIZE=$(stat --format=%s "${SYSTEM_MILL_PATH}") + SYSTEM_MILL_MTIME=$(stat --format=%y "${SYSTEM_MILL_PATH}") + + if [ ! -d "${MILL_USER_CACHE_DIR}" ]; then + mkdir -p "${MILL_USER_CACHE_DIR}" + fi + + SYSTEM_MILL_INFO_FILE="${MILL_USER_CACHE_DIR}/system-mill-info" + if [ -f "${SYSTEM_MILL_INFO_FILE}" ]; then + parseSystemMillInfo() { + LINE_NUMBER="${1}" + # Select the line number of the SYSTEM_MILL_INFO_FILE, cut the + # variable definition in that line in two halves and return + # the value, and finally remove the quotes. + sed -n "${LINE_NUMBER}p" "${SYSTEM_MILL_INFO_FILE}" |\ + cut -d= -f2 |\ + sed 's/"\(.*\)"/\1/' + } + + CACHED_SYSTEM_MILL_PATH=$(parseSystemMillInfo 1) + CACHED_SYSTEM_MILL_VERSION=$(parseSystemMillInfo 2) + CACHED_SYSTEM_MILL_SIZE=$(parseSystemMillInfo 3) + CACHED_SYSTEM_MILL_MTIME=$(parseSystemMillInfo 4) + + if [ "${SYSTEM_MILL_PATH}" = "${CACHED_SYSTEM_MILL_PATH}" ] \ + && [ "${SYSTEM_MILL_SIZE}" = "${CACHED_SYSTEM_MILL_SIZE}" ] \ + && [ "${SYSTEM_MILL_MTIME}" = "${CACHED_SYSTEM_MILL_MTIME}" ]; then + if [ "${CACHED_SYSTEM_MILL_VERSION}" = "${MILL_VERSION}" ]; then + MILL="${SYSTEM_MILL_PATH}" + return 0 + else + return 0 + fi + fi + fi + + SYSTEM_MILL_VERSION=$(${SYSTEM_MILL_PATH} --version | head -n1 | sed -n 's/^Mill.*version \(.*\)/\1/p') + + cat < "${SYSTEM_MILL_INFO_FILE}" +CACHED_SYSTEM_MILL_PATH="${SYSTEM_MILL_PATH}" +CACHED_SYSTEM_MILL_VERSION="${SYSTEM_MILL_VERSION}" +CACHED_SYSTEM_MILL_SIZE="${SYSTEM_MILL_SIZE}" +CACHED_SYSTEM_MILL_MTIME="${SYSTEM_MILL_MTIME}" +EOF + + if [ "${SYSTEM_MILL_VERSION}" = "${MILL_VERSION}" ]; then + MILL="${SYSTEM_MILL_PATH}" + fi +} +try_to_use_system_mill + +# If not already downloaded, download it +if [ ! -s "${MILL}" ] ; then + + # support old non-XDG download dir + MILL_OLD_DOWNLOAD_PATH="${HOME}/.mill/download" + OLD_MILL="${MILL_OLD_DOWNLOAD_PATH}/${MILL_VERSION}" + if [ -x "${OLD_MILL}" ] ; then + MILL="${OLD_MILL}" + else + case $MILL_VERSION in + 0.0.* | 0.1.* | 0.2.* | 0.3.* | 0.4.* ) + DOWNLOAD_SUFFIX="" + DOWNLOAD_FROM_MAVEN=0 + ;; + 0.5.* | 0.6.* | 0.7.* | 0.8.* | 0.9.* | 0.10.* | 0.11.0-M* ) + DOWNLOAD_SUFFIX="-assembly" + DOWNLOAD_FROM_MAVEN=0 + ;; + *) + DOWNLOAD_SUFFIX="-assembly" + DOWNLOAD_FROM_MAVEN=1 + ;; + esac + + DOWNLOAD_FILE=$(mktemp mill.XXXXXX) + + if [ "$DOWNLOAD_FROM_MAVEN" = "1" ] ; then + DOWNLOAD_URL="https://repo1.maven.org/maven2/com/lihaoyi/mill-dist${ARTIFACT_SUFFIX}/${MILL_VERSION}/mill-dist${ARTIFACT_SUFFIX}-${MILL_VERSION}.jar" + else + MILL_VERSION_TAG=$(echo "$MILL_VERSION" | sed -E 's/([^-]+)(-M[0-9]+)?(-.*)?/\1\2/') + DOWNLOAD_URL="${GITHUB_RELEASE_CDN}${MILL_REPO_URL}/releases/download/${MILL_VERSION_TAG}/${MILL_VERSION}${DOWNLOAD_SUFFIX}" + unset MILL_VERSION_TAG + fi + + # TODO: handle command not found + echo "Downloading mill ${MILL_VERSION} from ${DOWNLOAD_URL} ..." 1>&2 + ${CURL_CMD} -f -L -o "${DOWNLOAD_FILE}" "${DOWNLOAD_URL}" + chmod +x "${DOWNLOAD_FILE}" + mkdir -p "${MILL_DOWNLOAD_PATH}" + mv "${DOWNLOAD_FILE}" "${MILL}" + + unset DOWNLOAD_FILE + unset DOWNLOAD_SUFFIX + fi +fi + +if [ -z "$MILL_MAIN_CLI" ] ; then + MILL_MAIN_CLI="${0}" +fi + +MILL_FIRST_ARG="" +if [ "$1" = "--bsp" ] || [ "$1" = "-i" ] || [ "$1" = "--interactive" ] || [ "$1" = "--no-server" ] || [ "$1" = "--repl" ] || [ "$1" = "--help" ] ; then + # Need to preserve the first position of those listed options + MILL_FIRST_ARG=$1 + shift +fi + +unset MILL_DOWNLOAD_PATH +unset MILL_OLD_DOWNLOAD_PATH +unset OLD_MILL +unset MILL_VERSION +unset MILL_REPO_URL + +# We don't quote MILL_FIRST_ARG on purpose, so we can expand the empty value without quotes +# shellcheck disable=SC2086 +exec "${MILL}" $MILL_FIRST_ARG -D "mill.main.cli=${MILL_MAIN_CLI}" "$@" \ No newline at end of file diff --git a/mill-iw-support/example/src/Example.scala b/mill-iw-support/example/src/Example.scala deleted file mode 100644 index aa28980..0000000 --- a/mill-iw-support/example/src/Example.scala +++ /dev/null @@ -1,27 +0,0 @@ -package example - -import zio._ -import zio.json._ - -// Simple example class -case class User(id: String, name: String, email: String) - -object User { - // JSON codecs using ZIO JSON - implicit val encoder: JsonEncoder[User] = DeriveJsonEncoder.gen[User] - implicit val decoder: JsonDecoder[User] = DeriveJsonDecoder.gen[User] -} - -object Example extends ZIOAppDefault { - - val program = for { - _ <- Console.printLine("Starting example application") - user = User("1", "John Doe", "john@example.com") - json <- ZIO.succeed(user.toJson) - _ <- Console.printLine(s"User as JSON: $json") - parsed <- ZIO.fromEither(json.fromJson[User]) - _ <- Console.printLine(s"Parsed user: $parsed") - } yield () - - def run = program -} \ No newline at end of file diff --git a/mill-iw-support/example/test/src/ExampleTest.scala b/mill-iw-support/example/test/src/ExampleTest.scala deleted file mode 100644 index 20f0c06..0000000 --- a/mill-iw-support/example/test/src/ExampleTest.scala +++ /dev/null @@ -1,27 +0,0 @@ -package example - -import utest._ -import zio._ -import zio.json._ -import zio.test._ -import zio.test.Assertion._ - -object ExampleTest extends TestSuite { - val tests = Tests { - test("User JSON serialization") { - val user = User("1", "John Doe", "john@example.com") - val json = user.toJson - val expected = """{"id":"1","name":"John Doe","email":"john@example.com"}""" - - assert(json == expected) - } - - test("User JSON deserialization") { - val json = """{"id":"1","name":"John Doe","email":"john@example.com"}""" - val parsed = json.fromJson[User] - val expected = User("1", "John Doe", "john@example.com") - - assert(parsed == Right(expected)) - } - } -} \ No newline at end of file diff --git a/mill-iw-support/.mill-version b/mill-iw-support/.mill-version new file mode 100644 index 0000000..54dbed4 --- /dev/null +++ b/mill-iw-support/.mill-version @@ -0,0 +1 @@ +0.12.10 diff --git a/mill-iw-support/.scalafmt.conf b/mill-iw-support/.scalafmt.conf new file mode 100644 index 0000000..ee7753a --- /dev/null +++ b/mill-iw-support/.scalafmt.conf @@ -0,0 +1,2 @@ +version = "3.7.15" +runner.dialect = scala213 \ No newline at end of file diff --git a/mill-iw-support/README.md b/mill-iw-support/README.md index 4986e5c..9bb1ba8 100644 --- a/mill-iw-support/README.md +++ b/mill-iw-support/README.md @@ -28,8 +28,45 @@ IWMillDeps.tapirCore // ivy"com.softwaremill.sttp.tapir::tapir-core:1.11.16" // Use dependency groups -IWMillDeps.useZIO() // Core ZIO dependencies with test framework -IWMillDeps.useZIOAll() // Comprehensive ZIO stack +IWMillDeps.zioCore // List of core ZIO dependencies +IWMillDeps.zioAll // List of all ZIO dependencies +``` + +### IWBomModule + +Bill of Materials (BOM) module for centralized dependency management: + +```scala +import mill._ +import works.iterative.mill._ + +// Define a BOM module +object bom extends IWBomModule { + def publishVersion = "0.1.0-SNAPSHOT" + + def pomSettings = PomSettings( + // Standard POM settings + ) + + // Override with custom dependencies if needed + override def depManagement = T { + super.depManagement() ++ Agg( + ivy"com.example::custom-lib:1.0.0" + ) + } +} + +// Use the BOM in modules +object myModule extends IWScalaModule { + // Reference the BOM module + override def bomModuleDeps = Seq(bom) + + // No need to specify versions - they come from the BOM + def ivyDeps = super.ivyDeps() ++ Agg( + ivy"dev.zio::zio", + ivy"dev.zio::zio-json" + ) +} ``` ### IWScalaModule @@ -44,10 +81,18 @@ // Use default Scala version (3.6.3) // and standard compiler options with SemanticDB enabled - def ivyDeps = super.ivyDeps() ++ IWMillDeps.useZIO() + def ivyDeps = super.ivyDeps() ++ Agg( + ivy"${IWMillDeps.zio}", + ivy"${IWMillDeps.zioJson}" + ) // Standard test module configuration - object test extends Tests with IWTests + object test extends ScalaTests with TestModule.ZioTest { + def ivyDeps = super.ivyDeps() ++ Agg( + ivy"${IWMillDeps.zioTest}", + ivy"${IWMillDeps.zioTestSbt}" + ) + } } ``` @@ -96,6 +141,51 @@ } ``` +## Dependency Management Approaches + +This library provides two approaches for dependency management: + +### 1. IWMillDeps (String Constants) + +Use the IWMillDeps object to access dependency strings with correct versions: + +```scala +def ivyDeps = super.ivyDeps() ++ Agg( + ivy"${IWMillDeps.zio}", + ivy"${IWMillDeps.zioJson}" +) +``` + +### 2. BOM (Bill of Materials) + +Use the IWBomModule to centralize dependency versions: + +```scala +// Define a BOM module +object bom extends IWBomModule { + def publishVersion = "0.1.0-SNAPSHOT" + def pomSettings = PomSettings(...) +} + +// Use the BOM in modules +object myModule extends IWScalaModule { + override def bomModuleDeps = Seq(bom) + + // No need to specify versions + def ivyDeps = super.ivyDeps() ++ Agg( + ivy"dev.zio::zio", + ivy"dev.zio::zio-json" + ) +} +``` + +The BOM approach offers these advantages: +- Enforces consistent versions across modules +- Simplifies version updates +- Controls transitive dependency versions +- Makes build files cleaner (no version strings) +- Can be published as a separate artifact + ## Examples See the `example` directory for a complete working example. diff --git a/mill-iw-support/build.sc b/mill-iw-support/build.sc index 5df80ae..2ed832c 100644 --- a/mill-iw-support/build.sc +++ b/mill-iw-support/build.sc @@ -2,20 +2,33 @@ import mill.scalalib._ import mill.scalalib.publish._ import mill.scalalib.scalafmt._ +import mill.scalalib.TestModule.Utest -object `mill-iw-support` extends Cross[IWMillSupportModule]("2.13.16", "3.6.3") +/** Main module for the mill-iw-support library. Built for Scala 2.13 which is + * compatible with Mill. + */ +object core extends ScalaModule with ScalafmtModule with PublishModule { + val millVersion = "0.12.10" -class IWMillSupportModule(val crossScalaVersion: String) extends CrossScalaModule with ScalafmtModule with PublishModule { + def scalaVersion = "2.13.16" + + def artifactName = "mill-iw-support" + def publishVersion = "0.1.0-SNAPSHOT" def pomSettings = PomSettings( description = "Iterative Works Mill Support Library", organization = "works.iterative", - url = "https://github.com/iterative-works/mill-iw-support", + url = "https://github.com/iterative-works/iw-project-support", licenses = Seq(License.MIT), - versionControl = VersionControl.github("iterative-works", "mill-iw-support"), + versionControl = + VersionControl.github("iterative-works", "iw-project-support"), developers = Seq( - Developer("mph", "Michal Přihoda", "https://github.com/iterative-works") + Developer( + "mprihoda", + "Michal Příhoda", + "https://github.com/iterative-works" + ) ) ) @@ -23,10 +36,15 @@ ivy"com.lihaoyi::os-lib:0.9.3" ) - object test extends Tests with ScalafmtModule { + // Mill's own dependencies needed for compilation + def compileIvyDeps = Agg( + ivy"com.lihaoyi::mill-main:${millVersion}", + ivy"com.lihaoyi::mill-scalalib:${millVersion}" + ) + + object test extends ScalaTests with Utest with ScalafmtModule { def ivyDeps = Agg( ivy"com.lihaoyi::utest:0.8.2" ) - def testFramework = "utest.runner.Framework" } -} \ No newline at end of file +} diff --git a/mill-iw-support/core/src/IWBomModule.scala b/mill-iw-support/core/src/IWBomModule.scala new file mode 100644 index 0000000..d8e70bb --- /dev/null +++ b/mill-iw-support/core/src/IWBomModule.scala @@ -0,0 +1,97 @@ +package works.iterative.mill + +import mill._ +import mill.scalalib._ +import mill.scalalib.publish._ + +/** Standard Bill of Materials (BOM) module for Iterative Works projects using + * Mill. This module centralizes dependency versioning across projects. + * + * A BOM module allows: + * 1. Centralized version management for dependencies 2. Enforcing specific + * versions of transitive dependencies 3. Applying uniform dependency + * management across multiple modules 4. Publishing a BOM artifact that + * other projects can consume + * + * Usage: + * 1. Create a BOM module in your build: + * ```scala + * object bom extends IWBomModule { + * def publishVersion = "0.1.0-SNAPSHOT" + * + * // Add your organization-specific customizations here + * def pomSettings = PomSettings(...) + * } + * ``` + * + * 2. Use the BOM in your modules: + * ```scala + * object myModule extends IWScalaModule { + * def bomModuleDeps = Seq(bom) + * + * def ivyDeps = Agg( + * // No need to specify versions as they come from the BOM + * ivy"dev.zio::zio", + * ivy"dev.zio::zio-json" + * ) + * } + * ``` + */ +trait IWBomModule extends BomModule with ScalaModule with IWScalaVersions { + + override def scalaVersion: T[String] = scala3Version + + /** The default implementation includes all standard IW dependencies from + * IWMillVersions and IWMillDeps. + */ + override def depManagement = T { + val V = IWMillVersions + + Agg( + // ZIO Core + ivy"dev.zio::zio:${V.zio}", + ivy"dev.zio::zio-test:${V.zio}", + ivy"dev.zio::zio-test-sbt:${V.zio}", + ivy"dev.zio::zio-streams:${V.zio}", + + // ZIO Extensions + ivy"dev.zio::zio-config:${V.zioConfig}", + ivy"dev.zio::zio-config-typesafe:${V.zioConfig}", + ivy"dev.zio::zio-config-magnolia:${V.zioConfig}", + ivy"dev.zio::zio-json:${V.zioJson}", + ivy"dev.zio::zio-logging:${V.zioLogging}", + ivy"dev.zio::zio-logging-slf4j:${V.zioLogging}", + ivy"dev.zio::zio-prelude:${V.zioPrelude}", + ivy"dev.zio::zio-interop-cats:${V.zioInteropCats}", + ivy"dev.zio::zio-interop-reactivestreams:${V.zioInteropReactiveStreams}", + ivy"dev.zio::zio-nio:${V.zioNIO}", + ivy"dev.zio::zio-cli:${V.zioCli}", + ivy"dev.zio::zio-schema:${V.zioSchema}", + ivy"dev.zio::zio-schema-derivation:${V.zioSchema}", + + // Database + ivy"io.getquill::quill-jdbc-zio:${V.quill}", + ivy"com.augustnagro::magnum:${V.magnum}", + ivy"com.augustnagro::magnumzio:${V.magnum}", + ivy"com.augustnagro::magnumpg:${V.magnum}", + + // HTTP + ivy"com.softwaremill.sttp.tapir::tapir-core:${V.tapir}", + ivy"com.softwaremill.sttp.tapir::tapir-zio:${V.tapir}", + ivy"com.softwaremill.sttp.tapir::tapir-json-zio:${V.tapir}", + + // UI/Frontend + ivy"com.lihaoyi::scalatags:${V.scalatags}", + ivy"com.raquo::laminar:${V.laminar}", + ivy"com.raquo::waypoint:${V.waypoint}", + ivy"be.doeraene::url-dsl:${V.urlDsl}", + + // Testing + ivy"org.scalatest::scalatest:${V.scalaTest}", + ivy"com.lihaoyi::utest:${V.utest}", + + // Logging + ivy"ch.qos.logback:logback-classic:${V.logbackClassic}" + ) + } +} diff --git a/mill-iw-support/core/src/IWMillDeps.scala b/mill-iw-support/core/src/IWMillDeps.scala new file mode 100644 index 0000000..d4cac1c --- /dev/null +++ b/mill-iw-support/core/src/IWMillDeps.scala @@ -0,0 +1,80 @@ +package works.iterative.mill + +import mill.api.Loose + +/** Standardized dependency management for Iterative Works projects using Mill. + * This object provides methods to get dependency strings with proper + * versioning. + * + * All versions are centralized in [[IWMillVersions]]. + * + * This object is designed to be used in build.sc files where the ivy string + * interpolator is available, like: + * + * {{{ + * def ivyDeps = Agg( + * ivy"$${IWMillDeps.zio}", + * ivy"$${IWMillDeps.zioJson}" + * ) + * }}} + */ +object IWMillDeps { + val V = IWMillVersions + + // ZIO Core + val zio = s"dev.zio::zio:${V.zio}" + val zioTest = s"dev.zio::zio-test:${V.zio}" + val zioTestSbt = s"dev.zio::zio-test-sbt:${V.zio}" + val zioStreams = s"dev.zio::zio-streams:${V.zio}" + + // ZIO Extensions + val zioConfig = s"dev.zio::zio-config:${V.zioConfig}" + val zioConfigTypesafe = s"dev.zio::zio-config-typesafe:${V.zioConfig}" + val zioConfigMagnolia = s"dev.zio::zio-config-magnolia:${V.zioConfig}" + val zioJson = s"dev.zio::zio-json:${V.zioJson}" + val zioLogging = s"dev.zio::zio-logging:${V.zioLogging}" + val zioLoggingSlf4j = s"dev.zio::zio-logging-slf4j:${V.zioLogging}" + val zioPrelude = s"dev.zio::zio-prelude:${V.zioPrelude}" + val zioInteropCats = s"dev.zio::zio-interop-cats:${V.zioInteropCats}" + val zioInteropReactiveStreams = + s"dev.zio::zio-interop-reactivestreams:${V.zioInteropReactiveStreams}" + val zioNIO = s"dev.zio::zio-nio:${V.zioNIO}" + val zioCli = s"dev.zio::zio-cli:${V.zioCli}" + val zioSchema = s"dev.zio::zio-schema:${V.zioSchema}" + val zioSchemaDerivation = s"dev.zio::zio-schema-derivation:${V.zioSchema}" + + // Collection of ZIO dependencies + val zioCore = List(zio, zioTest, zioTestSbt) + val zioJson_ = List(zioJson) + val zioAll = zioCore ++ List( + zioStreams, + zioConfig, + zioConfigTypesafe, + zioConfigMagnolia, + zioLogging, + zioPrelude + ) ++ zioJson_ + + // Database + val quill = s"io.getquill::quill-jdbc-zio:${V.quill}" + val magnum = s"com.augustnagro::magnum_3:${V.magnum}" + val magnumZIO = s"com.augustnagro::magnumzio_3:${V.magnum}" + val magnumPG = s"com.augustnagro::magnumpg_3:${V.magnum}" + + // HTTP + val tapirCore = s"com.softwaremill.sttp.tapir::tapir-core:${V.tapir}" + val tapirZIO = s"com.softwaremill.sttp.tapir::tapir-zio:${V.tapir}" + val tapirZIOJson = s"com.softwaremill.sttp.tapir::tapir-json-zio:${V.tapir}" + + // UI/Frontend + val scalatags = s"com.lihaoyi::scalatags:${V.scalatags}" + val laminar = s"com.raquo::laminar:${V.laminar}" + val waypoint = s"com.raquo::waypoint:${V.waypoint}" + val urlDsl = s"be.doeraene::url-dsl:${V.urlDsl}" + + // Testing + val scalaTest = s"org.scalatest::scalatest:${V.scalaTest}" + + // Logging + val logbackClassic = s"ch.qos.logback:logback-classic:${V.logbackClassic}" +} diff --git a/mill-iw-support/core/src/IWMillVersions.scala b/mill-iw-support/core/src/IWMillVersions.scala new file mode 100644 index 0000000..0ad9bd2 --- /dev/null +++ b/mill-iw-support/core/src/IWMillVersions.scala @@ -0,0 +1,56 @@ +package works.iterative.mill + +/** + * Centralized version management for Iterative Works projects using Mill. + * This object contains all library versions used across projects. + * + * Update this object to bump versions for all projects. + */ +object IWMillVersions { + // ZIO Ecosystem + val zio = "2.1.16" + val zioConfig = "4.0.3" + val zioCli = "0.7.0" + val zioInteropCats = "23.1.0.3" + val zioInteropReactiveStreams = "2.0.2" + val zioJson = "0.7.36" + val zioLogging = "2.5.0" + val zioNIO = "2.0.2" + val zioPrelude = "1.0.0-RC39" + val zioQuery = "0.7.6" + val zioSchema = "1.6.3" + + // Akka Ecosystem + val akka = "2.6.18" + val akkaHttp = "10.4.0" + + // Other Libraries + val cats = "2.13.0" + val utest = "0.8.2" + val caliban = "2.9.2" + val chimney = "1.7.3" + val ducktape = "0.2.7" + val elastic4s = "7.12.4" + val http4s = "0.23.30" + val http4sBlaze = "0.23.17" + val http4sPac4J = "5.0.0" + val laminar = "17.2.0" + val laminext = "0.17.0" + val logbackClassic = "1.5.17" + val magnum = "2.0.0-M1" + val pac4j = "6.0.3" + val play = "2.8.18" + val playJson = "2.9.3" + val quill = "4.8.6" + val scalaJsMacroTaskExecutor = "1.1.1" + val scalaJsJavaSecureRandom = "1.0.0" + val scalatags = "0.13.1" + val scalaTest = "3.2.19" + val slick = "3.3.3" + val sttpClient3 = "3.10.3" + val sttpClient4 = "4.0.0-RC1" + val support = "1.7.0" + val tapir = "1.11.16" + val urlDsl = "0.7.0" + val waypoint = "9.0.0" +} \ No newline at end of file diff --git a/mill-iw-support/core/src/IWPublishModule.scala b/mill-iw-support/core/src/IWPublishModule.scala new file mode 100644 index 0000000..5ae0ade --- /dev/null +++ b/mill-iw-support/core/src/IWPublishModule.scala @@ -0,0 +1,22 @@ +package works.iterative.mill + +import mill._ +import mill.scalalib._ +import mill.scalalib.publish._ + +/** Standard publish module trait for Iterative Works projects using Mill. This + * trait sets up standard publishing configuration for IW Maven repositories. + * + * To use this trait, simply extend it in your module and define: + * - publishVersion: The version to publish + * - pomSettings: The POM settings for the publication + * + * Use IW_USERNAME and IW_PASSWORD environment variables for authentication. + */ +trait IWPublishModule extends PublishModule { + + override def sonatypeUri = "https://dig.iterative.works/maven/releases" + override def sonatypeSnapshotUri = + "https://dig.iterative.works/maven/snapshots" + +} diff --git a/mill-iw-support/core/src/IWScalaModule.scala b/mill-iw-support/core/src/IWScalaModule.scala new file mode 100644 index 0000000..021bccc --- /dev/null +++ b/mill-iw-support/core/src/IWScalaModule.scala @@ -0,0 +1,97 @@ +package works.iterative.mill + +import mill._ +import mill.scalalib._ +import mill.scalalib.scalafmt._ + +/** Standard Scala module trait for Iterative Works projects using Mill. This + * trait sets up standard compiler options and configuration. + */ +trait IWScalaModule + extends ScalaModule + with ScalafmtModule + with IWScalaVersions { + + override def scalaVersion = scala3Version + + // Define base compiler options based on Scala version + def baseScalacOptions = T { + if (scalaVersion().startsWith("2.")) { + Seq( + "-encoding", + "utf8", + "-feature", + "-unchecked", + "-language:existentials", + "-language:experimental.macros", + "-language:higherKinds", + "-language:implicitConversions", + "-Xlint:adapted-args", + "-Xlint:constant", + "-Xlint:delayedinit-select", + "-Xlint:deprecation", + "-Xlint:doc-detached", + "-Xlint:implicit-recursion", + "-Xlint:implicit-not-found", + "-Xlint:inaccessible", + "-Xlint:infer-any", + "-Xlint:missing-interpolator", + "-Xlint:nullary-unit", + "-Xlint:option-implicit", + "-Xlint:package-object-classes", + "-Xlint:poly-implicit-overload", + "-Xlint:private-shadow", + "-Xlint:stars-align", + "-Xlint:strict-unsealed-patmat", + "-Xlint:type-parameter-shadow", + "-Xlint:-byname-implicit", + "-Wdead-code", + "-Wextra-implicit", + "-Wnumeric-widen", + "-Wvalue-discard", + "-Wnonunit-statement", + "-Wunused:nowarn", + "-Wunused:implicits", + "-Wunused:explicits", + "-Wunused:imports", + "-Wunused:locals", + "-Wunused:params", + "-Wunused:patvars", + "-Wunused:privates" + ) + } else { + Seq( + "-encoding", + "utf8", + "-deprecation", + "-feature", + "-unchecked", + "-language:experimental.macros", + "-language:higherKinds", + "-language:implicitConversions", + "-Xkind-projector", + "-Wvalue-discard", + "-Wnonunit-statement", + "-Wunused:implicits", + "-Wunused:explicits", + "-Wunused:imports", + "-Wunused:locals", + "-Wunused:params", + "-Wunused:privates" + ) + } + } + + // Enable SemanticDB for tooling + def semanticDbOptions = Seq( + "-Xsemanticdb" + ) + + // Override scalacOptions to include both base options and SemanticDB options + override def scalacOptions = T { + super.scalacOptions() ++ baseScalacOptions() ++ semanticDbOptions + } + + // Base test configuration + trait IWTests extends ScalaTests with ScalafmtModule +} diff --git a/mill-iw-support/core/src/IWScalaVersions.scala b/mill-iw-support/core/src/IWScalaVersions.scala new file mode 100644 index 0000000..aa70cb6 --- /dev/null +++ b/mill-iw-support/core/src/IWScalaVersions.scala @@ -0,0 +1,9 @@ +package works.iterative.mill + +trait IWScalaVersions { + val scala2Version = "2.13.16" + val scala3Version = "3.6.4" + val scala3LTSVersion = "3.3.5" +} + +object IWScalaVersions extends IWScalaVersions diff --git a/mill-iw-support/core/src/package.scala b/mill-iw-support/core/src/package.scala new file mode 100644 index 0000000..1ba4556 --- /dev/null +++ b/mill-iw-support/core/src/package.scala @@ -0,0 +1,6 @@ +package works.iterative + +package object mill { + val IWVersions = IWMillVersions + val IWDeps = IWMillDeps +} diff --git a/mill-iw-support/example/.mill-version b/mill-iw-support/example/.mill-version new file mode 100644 index 0000000..54dbed4 --- /dev/null +++ b/mill-iw-support/example/.mill-version @@ -0,0 +1 @@ +0.12.10 diff --git a/mill-iw-support/example/build.sc b/mill-iw-support/example/build.sc index 5da77ab..cae5a6a 100644 --- a/mill-iw-support/example/build.sc +++ b/mill-iw-support/example/build.sc @@ -1,11 +1,22 @@ import mill._ import mill.scalalib._ +import mill.scalalib.publish._ +import mill.scalalib.scalafmt._ + +// Import our IW Mill Support library import $ivy.`works.iterative::mill-iw-support:0.1.0-SNAPSHOT` import works.iterative.mill._ +// Define a BOM (Bill of Materials) module +object bom extends IWBomModule + +// Example module using the IW Mill support with BOM object example extends IWScalaModule with IWPublishModule { def publishVersion = "0.1.0-SNAPSHOT" - + + // Use the BOM module for dependency management + override def bomModuleDeps = Seq(bom) + def pomSettings = PomSettings( description = "Example Mill Project using IW Mill Support", organization = "com.example", @@ -16,15 +27,25 @@ Developer("developer", "Example Developer", "https://github.com/example") ) ) - - // Use centralized dependency versions and definitions - def ivyDeps = super.ivyDeps() ++ IWMillDeps.useZIO() ++ Agg( - IWMillDeps.zioJson, - IWMillDeps.tapirCore, - IWMillDeps.tapirZIO, - IWMillDeps.tapirZIOJson + + // With BOM, we don't need to specify versions + // They're managed by the BOM module + def ivyDeps = super.ivyDeps() ++ Agg( + ivy"dev.zio::zio", + ivy"dev.zio::zio-json", + ivy"com.softwaremill.sttp.tapir::tapir-core", + ivy"com.softwaremill.sttp.tapir::tapir-zio", + ivy"com.softwaremill.sttp.tapir::tapir-json-zio" ) - - // Define test module extending IWTests - object test extends Tests with IWTests -} \ No newline at end of file + + // Define test module using standard Mill approach + object test extends ScalaTests with TestModule.ZioTest { + // Use the BOM module for dependency management + override def bomModuleDeps = Seq(bom) + + def ivyDeps = super.ivyDeps() ++ Agg( + ivy"dev.zio::zio-test", // Version comes from BOM + ivy"dev.zio::zio-test-sbt" // Version comes from BOM + ) + } +} diff --git a/mill-iw-support/example/example/src/Example.scala b/mill-iw-support/example/example/src/Example.scala new file mode 100644 index 0000000..aa28980 --- /dev/null +++ b/mill-iw-support/example/example/src/Example.scala @@ -0,0 +1,27 @@ +package example + +import zio._ +import zio.json._ + +// Simple example class +case class User(id: String, name: String, email: String) + +object User { + // JSON codecs using ZIO JSON + implicit val encoder: JsonEncoder[User] = DeriveJsonEncoder.gen[User] + implicit val decoder: JsonDecoder[User] = DeriveJsonDecoder.gen[User] +} + +object Example extends ZIOAppDefault { + + val program = for { + _ <- Console.printLine("Starting example application") + user = User("1", "John Doe", "john@example.com") + json <- ZIO.succeed(user.toJson) + _ <- Console.printLine(s"User as JSON: $json") + parsed <- ZIO.fromEither(json.fromJson[User]) + _ <- Console.printLine(s"Parsed user: $parsed") + } yield () + + def run = program +} \ No newline at end of file diff --git a/mill-iw-support/example/example/test/src/ExampleTest.scala b/mill-iw-support/example/example/test/src/ExampleTest.scala new file mode 100644 index 0000000..951c2d8 --- /dev/null +++ b/mill-iw-support/example/example/test/src/ExampleTest.scala @@ -0,0 +1,26 @@ +package example + +import zio._ +import zio.json._ +import zio.test._ +import zio.test.Assertion._ + +object ExampleTest extends ZIOSpecDefault { + override def spec = suite("Example suite")( + test("User JSON serialization") { + val user = User("1", "John Doe", "john@example.com") + val json = user.toJson + val expected = + """{"id":"1","name":"John Doe","email":"john@example.com"}""" + + assertTrue(json == expected) + }, + test("User JSON deserialization") { + val json = """{"id":"1","name":"John Doe","email":"john@example.com"}""" + val parsed = json.fromJson[User] + val expected = User("1", "John Doe", "john@example.com") + + assertTrue(parsed == Right(expected)) + } + ) +} diff --git a/mill-iw-support/example/mill b/mill-iw-support/example/mill new file mode 100755 index 0000000..f349762 --- /dev/null +++ b/mill-iw-support/example/mill @@ -0,0 +1,265 @@ +#!/usr/bin/env sh + +# This is a wrapper script, that automatically download mill from GitHub release pages +# You can give the required mill version with --mill-version parameter +# If no version is given, it falls back to the value of DEFAULT_MILL_VERSION +# +# Original Project page: https://github.com/lefou/millw +# Script Version: 0.4.12 +# +# If you want to improve this script, please also contribute your changes back! +# +# Licensed under the Apache License, Version 2.0 + +set -e + +if [ -z "${DEFAULT_MILL_VERSION}" ] ; then + DEFAULT_MILL_VERSION=0.12.10 +fi + + +if [ -z "${GITHUB_RELEASE_CDN}" ] ; then + GITHUB_RELEASE_CDN="" +fi + + +MILL_REPO_URL="https://github.com/com-lihaoyi/mill" + +if [ -z "${CURL_CMD}" ] ; then + CURL_CMD=curl +fi + +# Explicit commandline argument takes precedence over all other methods +if [ "$1" = "--mill-version" ] ; then + shift + if [ "x$1" != "x" ] ; then + MILL_VERSION="$1" + shift + else + echo "You specified --mill-version without a version." 1>&2 + echo "Please provide a version that matches one provided on" 1>&2 + echo "${MILL_REPO_URL}/releases" 1>&2 + false + fi +fi + +# Please note, that if a MILL_VERSION is already set in the environment, +# We reuse it's value and skip searching for a value. + +# If not already set, read .mill-version file +if [ -z "${MILL_VERSION}" ] ; then + if [ -f ".mill-version" ] ; then + MILL_VERSION="$(tr '\r' '\n' < .mill-version | head -n 1 2> /dev/null)" + elif [ -f ".config/mill-version" ] ; then + MILL_VERSION="$(tr '\r' '\n' < .config/mill-version | head -n 1 2> /dev/null)" + fi +fi + +MILL_USER_CACHE_DIR="${XDG_CACHE_HOME:-${HOME}/.cache}/mill" + +if [ -z "${MILL_DOWNLOAD_PATH}" ] ; then + MILL_DOWNLOAD_PATH="${MILL_USER_CACHE_DIR}/download" +fi + +# If not already set, try to fetch newest from Github +if [ -z "${MILL_VERSION}" ] ; then + # TODO: try to load latest version from release page + echo "No mill version specified." 1>&2 + echo "You should provide a version via '.mill-version' file or --mill-version option." 1>&2 + + mkdir -p "${MILL_DOWNLOAD_PATH}" + LANG=C touch -d '1 hour ago' "${MILL_DOWNLOAD_PATH}/.expire_latest" 2>/dev/null || ( + # we might be on OSX or BSD which don't have -d option for touch + # but probably a -A [-][[hh]mm]SS + touch "${MILL_DOWNLOAD_PATH}/.expire_latest"; touch -A -010000 "${MILL_DOWNLOAD_PATH}/.expire_latest" + ) || ( + # in case we still failed, we retry the first touch command with the intention + # to show the (previously suppressed) error message + LANG=C touch -d '1 hour ago' "${MILL_DOWNLOAD_PATH}/.expire_latest" + ) + + # POSIX shell variant of bash's -nt operator, see https://unix.stackexchange.com/a/449744/6993 + # if [ "${MILL_DOWNLOAD_PATH}/.latest" -nt "${MILL_DOWNLOAD_PATH}/.expire_latest" ] ; then + if [ -n "$(find -L "${MILL_DOWNLOAD_PATH}/.latest" -prune -newer "${MILL_DOWNLOAD_PATH}/.expire_latest")" ]; then + # we know a current latest version + MILL_VERSION=$(head -n 1 "${MILL_DOWNLOAD_PATH}"/.latest 2> /dev/null) + fi + + if [ -z "${MILL_VERSION}" ] ; then + # we don't know a current latest version + echo "Retrieving latest mill version ..." 1>&2 + LANG=C ${CURL_CMD} -s -i -f -I ${MILL_REPO_URL}/releases/latest 2> /dev/null | grep --ignore-case Location: | sed s'/^.*tag\///' | tr -d '\r\n' > "${MILL_DOWNLOAD_PATH}/.latest" + MILL_VERSION=$(head -n 1 "${MILL_DOWNLOAD_PATH}"/.latest 2> /dev/null) + fi + + if [ -z "${MILL_VERSION}" ] ; then + # Last resort + MILL_VERSION="${DEFAULT_MILL_VERSION}" + echo "Falling back to hardcoded mill version ${MILL_VERSION}" 1>&2 + else + echo "Using mill version ${MILL_VERSION}" 1>&2 + fi +fi + +MILL_NATIVE_SUFFIX="-native" +FULL_MILL_VERSION=$MILL_VERSION +ARTIFACT_SUFFIX="" +case "$MILL_VERSION" in + *"$MILL_NATIVE_SUFFIX") + MILL_VERSION=${MILL_VERSION%"$MILL_NATIVE_SUFFIX"} + if [ "$(expr substr $(uname -s) 1 5 2>/dev/null)" = "Linux" ]; then + if [ "$(uname -m)" = "aarch64" ]; then + ARTIFACT_SUFFIX="-native-linux-aarch64" + else + ARTIFACT_SUFFIX="-native-linux-amd64" + fi + elif [ "$(uname)" = "Darwin" ]; then + if [ "$(uname -m)" = "arm64" ]; then + ARTIFACT_SUFFIX="-native-mac-aarch64" + else + ARTIFACT_SUFFIX="-native-mac-amd64" + fi + else + echo "This native mill launcher supports only Linux and macOS." 1>&2 + exit 1 + fi +esac + +MILL="${MILL_DOWNLOAD_PATH}/${FULL_MILL_VERSION}" + +try_to_use_system_mill() { + if [ "$(uname)" != "Linux" ]; then + return 0 + fi + + MILL_IN_PATH="$(command -v mill || true)" + + if [ -z "${MILL_IN_PATH}" ]; then + return 0 + fi + + SYSTEM_MILL_FIRST_TWO_BYTES=$(head --bytes=2 "${MILL_IN_PATH}") + if [ "${SYSTEM_MILL_FIRST_TWO_BYTES}" = "#!" ]; then + # MILL_IN_PATH is (very likely) a shell script and not the mill + # executable, ignore it. + return 0 + fi + + SYSTEM_MILL_PATH=$(readlink -e "${MILL_IN_PATH}") + SYSTEM_MILL_SIZE=$(stat --format=%s "${SYSTEM_MILL_PATH}") + SYSTEM_MILL_MTIME=$(stat --format=%y "${SYSTEM_MILL_PATH}") + + if [ ! -d "${MILL_USER_CACHE_DIR}" ]; then + mkdir -p "${MILL_USER_CACHE_DIR}" + fi + + SYSTEM_MILL_INFO_FILE="${MILL_USER_CACHE_DIR}/system-mill-info" + if [ -f "${SYSTEM_MILL_INFO_FILE}" ]; then + parseSystemMillInfo() { + LINE_NUMBER="${1}" + # Select the line number of the SYSTEM_MILL_INFO_FILE, cut the + # variable definition in that line in two halves and return + # the value, and finally remove the quotes. + sed -n "${LINE_NUMBER}p" "${SYSTEM_MILL_INFO_FILE}" |\ + cut -d= -f2 |\ + sed 's/"\(.*\)"/\1/' + } + + CACHED_SYSTEM_MILL_PATH=$(parseSystemMillInfo 1) + CACHED_SYSTEM_MILL_VERSION=$(parseSystemMillInfo 2) + CACHED_SYSTEM_MILL_SIZE=$(parseSystemMillInfo 3) + CACHED_SYSTEM_MILL_MTIME=$(parseSystemMillInfo 4) + + if [ "${SYSTEM_MILL_PATH}" = "${CACHED_SYSTEM_MILL_PATH}" ] \ + && [ "${SYSTEM_MILL_SIZE}" = "${CACHED_SYSTEM_MILL_SIZE}" ] \ + && [ "${SYSTEM_MILL_MTIME}" = "${CACHED_SYSTEM_MILL_MTIME}" ]; then + if [ "${CACHED_SYSTEM_MILL_VERSION}" = "${MILL_VERSION}" ]; then + MILL="${SYSTEM_MILL_PATH}" + return 0 + else + return 0 + fi + fi + fi + + SYSTEM_MILL_VERSION=$(${SYSTEM_MILL_PATH} --version | head -n1 | sed -n 's/^Mill.*version \(.*\)/\1/p') + + cat < "${SYSTEM_MILL_INFO_FILE}" +CACHED_SYSTEM_MILL_PATH="${SYSTEM_MILL_PATH}" +CACHED_SYSTEM_MILL_VERSION="${SYSTEM_MILL_VERSION}" +CACHED_SYSTEM_MILL_SIZE="${SYSTEM_MILL_SIZE}" +CACHED_SYSTEM_MILL_MTIME="${SYSTEM_MILL_MTIME}" +EOF + + if [ "${SYSTEM_MILL_VERSION}" = "${MILL_VERSION}" ]; then + MILL="${SYSTEM_MILL_PATH}" + fi +} +try_to_use_system_mill + +# If not already downloaded, download it +if [ ! -s "${MILL}" ] ; then + + # support old non-XDG download dir + MILL_OLD_DOWNLOAD_PATH="${HOME}/.mill/download" + OLD_MILL="${MILL_OLD_DOWNLOAD_PATH}/${MILL_VERSION}" + if [ -x "${OLD_MILL}" ] ; then + MILL="${OLD_MILL}" + else + case $MILL_VERSION in + 0.0.* | 0.1.* | 0.2.* | 0.3.* | 0.4.* ) + DOWNLOAD_SUFFIX="" + DOWNLOAD_FROM_MAVEN=0 + ;; + 0.5.* | 0.6.* | 0.7.* | 0.8.* | 0.9.* | 0.10.* | 0.11.0-M* ) + DOWNLOAD_SUFFIX="-assembly" + DOWNLOAD_FROM_MAVEN=0 + ;; + *) + DOWNLOAD_SUFFIX="-assembly" + DOWNLOAD_FROM_MAVEN=1 + ;; + esac + + DOWNLOAD_FILE=$(mktemp mill.XXXXXX) + + if [ "$DOWNLOAD_FROM_MAVEN" = "1" ] ; then + DOWNLOAD_URL="https://repo1.maven.org/maven2/com/lihaoyi/mill-dist${ARTIFACT_SUFFIX}/${MILL_VERSION}/mill-dist${ARTIFACT_SUFFIX}-${MILL_VERSION}.jar" + else + MILL_VERSION_TAG=$(echo "$MILL_VERSION" | sed -E 's/([^-]+)(-M[0-9]+)?(-.*)?/\1\2/') + DOWNLOAD_URL="${GITHUB_RELEASE_CDN}${MILL_REPO_URL}/releases/download/${MILL_VERSION_TAG}/${MILL_VERSION}${DOWNLOAD_SUFFIX}" + unset MILL_VERSION_TAG + fi + + # TODO: handle command not found + echo "Downloading mill ${MILL_VERSION} from ${DOWNLOAD_URL} ..." 1>&2 + ${CURL_CMD} -f -L -o "${DOWNLOAD_FILE}" "${DOWNLOAD_URL}" + chmod +x "${DOWNLOAD_FILE}" + mkdir -p "${MILL_DOWNLOAD_PATH}" + mv "${DOWNLOAD_FILE}" "${MILL}" + + unset DOWNLOAD_FILE + unset DOWNLOAD_SUFFIX + fi +fi + +if [ -z "$MILL_MAIN_CLI" ] ; then + MILL_MAIN_CLI="${0}" +fi + +MILL_FIRST_ARG="" +if [ "$1" = "--bsp" ] || [ "$1" = "-i" ] || [ "$1" = "--interactive" ] || [ "$1" = "--no-server" ] || [ "$1" = "--repl" ] || [ "$1" = "--help" ] ; then + # Need to preserve the first position of those listed options + MILL_FIRST_ARG=$1 + shift +fi + +unset MILL_DOWNLOAD_PATH +unset MILL_OLD_DOWNLOAD_PATH +unset OLD_MILL +unset MILL_VERSION +unset MILL_REPO_URL + +# We don't quote MILL_FIRST_ARG on purpose, so we can expand the empty value without quotes +# shellcheck disable=SC2086 +exec "${MILL}" $MILL_FIRST_ARG -D "mill.main.cli=${MILL_MAIN_CLI}" "$@" \ No newline at end of file diff --git a/mill-iw-support/example/src/Example.scala b/mill-iw-support/example/src/Example.scala deleted file mode 100644 index aa28980..0000000 --- a/mill-iw-support/example/src/Example.scala +++ /dev/null @@ -1,27 +0,0 @@ -package example - -import zio._ -import zio.json._ - -// Simple example class -case class User(id: String, name: String, email: String) - -object User { - // JSON codecs using ZIO JSON - implicit val encoder: JsonEncoder[User] = DeriveJsonEncoder.gen[User] - implicit val decoder: JsonDecoder[User] = DeriveJsonDecoder.gen[User] -} - -object Example extends ZIOAppDefault { - - val program = for { - _ <- Console.printLine("Starting example application") - user = User("1", "John Doe", "john@example.com") - json <- ZIO.succeed(user.toJson) - _ <- Console.printLine(s"User as JSON: $json") - parsed <- ZIO.fromEither(json.fromJson[User]) - _ <- Console.printLine(s"Parsed user: $parsed") - } yield () - - def run = program -} \ No newline at end of file diff --git a/mill-iw-support/example/test/src/ExampleTest.scala b/mill-iw-support/example/test/src/ExampleTest.scala deleted file mode 100644 index 20f0c06..0000000 --- a/mill-iw-support/example/test/src/ExampleTest.scala +++ /dev/null @@ -1,27 +0,0 @@ -package example - -import utest._ -import zio._ -import zio.json._ -import zio.test._ -import zio.test.Assertion._ - -object ExampleTest extends TestSuite { - val tests = Tests { - test("User JSON serialization") { - val user = User("1", "John Doe", "john@example.com") - val json = user.toJson - val expected = """{"id":"1","name":"John Doe","email":"john@example.com"}""" - - assert(json == expected) - } - - test("User JSON deserialization") { - val json = """{"id":"1","name":"John Doe","email":"john@example.com"}""" - val parsed = json.fromJson[User] - val expected = User("1", "John Doe", "john@example.com") - - assert(parsed == Right(expected)) - } - } -} \ No newline at end of file diff --git a/mill-iw-support/mill b/mill-iw-support/mill new file mode 100755 index 0000000..f349762 --- /dev/null +++ b/mill-iw-support/mill @@ -0,0 +1,265 @@ +#!/usr/bin/env sh + +# This is a wrapper script, that automatically download mill from GitHub release pages +# You can give the required mill version with --mill-version parameter +# If no version is given, it falls back to the value of DEFAULT_MILL_VERSION +# +# Original Project page: https://github.com/lefou/millw +# Script Version: 0.4.12 +# +# If you want to improve this script, please also contribute your changes back! +# +# Licensed under the Apache License, Version 2.0 + +set -e + +if [ -z "${DEFAULT_MILL_VERSION}" ] ; then + DEFAULT_MILL_VERSION=0.12.10 +fi + + +if [ -z "${GITHUB_RELEASE_CDN}" ] ; then + GITHUB_RELEASE_CDN="" +fi + + +MILL_REPO_URL="https://github.com/com-lihaoyi/mill" + +if [ -z "${CURL_CMD}" ] ; then + CURL_CMD=curl +fi + +# Explicit commandline argument takes precedence over all other methods +if [ "$1" = "--mill-version" ] ; then + shift + if [ "x$1" != "x" ] ; then + MILL_VERSION="$1" + shift + else + echo "You specified --mill-version without a version." 1>&2 + echo "Please provide a version that matches one provided on" 1>&2 + echo "${MILL_REPO_URL}/releases" 1>&2 + false + fi +fi + +# Please note, that if a MILL_VERSION is already set in the environment, +# We reuse it's value and skip searching for a value. + +# If not already set, read .mill-version file +if [ -z "${MILL_VERSION}" ] ; then + if [ -f ".mill-version" ] ; then + MILL_VERSION="$(tr '\r' '\n' < .mill-version | head -n 1 2> /dev/null)" + elif [ -f ".config/mill-version" ] ; then + MILL_VERSION="$(tr '\r' '\n' < .config/mill-version | head -n 1 2> /dev/null)" + fi +fi + +MILL_USER_CACHE_DIR="${XDG_CACHE_HOME:-${HOME}/.cache}/mill" + +if [ -z "${MILL_DOWNLOAD_PATH}" ] ; then + MILL_DOWNLOAD_PATH="${MILL_USER_CACHE_DIR}/download" +fi + +# If not already set, try to fetch newest from Github +if [ -z "${MILL_VERSION}" ] ; then + # TODO: try to load latest version from release page + echo "No mill version specified." 1>&2 + echo "You should provide a version via '.mill-version' file or --mill-version option." 1>&2 + + mkdir -p "${MILL_DOWNLOAD_PATH}" + LANG=C touch -d '1 hour ago' "${MILL_DOWNLOAD_PATH}/.expire_latest" 2>/dev/null || ( + # we might be on OSX or BSD which don't have -d option for touch + # but probably a -A [-][[hh]mm]SS + touch "${MILL_DOWNLOAD_PATH}/.expire_latest"; touch -A -010000 "${MILL_DOWNLOAD_PATH}/.expire_latest" + ) || ( + # in case we still failed, we retry the first touch command with the intention + # to show the (previously suppressed) error message + LANG=C touch -d '1 hour ago' "${MILL_DOWNLOAD_PATH}/.expire_latest" + ) + + # POSIX shell variant of bash's -nt operator, see https://unix.stackexchange.com/a/449744/6993 + # if [ "${MILL_DOWNLOAD_PATH}/.latest" -nt "${MILL_DOWNLOAD_PATH}/.expire_latest" ] ; then + if [ -n "$(find -L "${MILL_DOWNLOAD_PATH}/.latest" -prune -newer "${MILL_DOWNLOAD_PATH}/.expire_latest")" ]; then + # we know a current latest version + MILL_VERSION=$(head -n 1 "${MILL_DOWNLOAD_PATH}"/.latest 2> /dev/null) + fi + + if [ -z "${MILL_VERSION}" ] ; then + # we don't know a current latest version + echo "Retrieving latest mill version ..." 1>&2 + LANG=C ${CURL_CMD} -s -i -f -I ${MILL_REPO_URL}/releases/latest 2> /dev/null | grep --ignore-case Location: | sed s'/^.*tag\///' | tr -d '\r\n' > "${MILL_DOWNLOAD_PATH}/.latest" + MILL_VERSION=$(head -n 1 "${MILL_DOWNLOAD_PATH}"/.latest 2> /dev/null) + fi + + if [ -z "${MILL_VERSION}" ] ; then + # Last resort + MILL_VERSION="${DEFAULT_MILL_VERSION}" + echo "Falling back to hardcoded mill version ${MILL_VERSION}" 1>&2 + else + echo "Using mill version ${MILL_VERSION}" 1>&2 + fi +fi + +MILL_NATIVE_SUFFIX="-native" +FULL_MILL_VERSION=$MILL_VERSION +ARTIFACT_SUFFIX="" +case "$MILL_VERSION" in + *"$MILL_NATIVE_SUFFIX") + MILL_VERSION=${MILL_VERSION%"$MILL_NATIVE_SUFFIX"} + if [ "$(expr substr $(uname -s) 1 5 2>/dev/null)" = "Linux" ]; then + if [ "$(uname -m)" = "aarch64" ]; then + ARTIFACT_SUFFIX="-native-linux-aarch64" + else + ARTIFACT_SUFFIX="-native-linux-amd64" + fi + elif [ "$(uname)" = "Darwin" ]; then + if [ "$(uname -m)" = "arm64" ]; then + ARTIFACT_SUFFIX="-native-mac-aarch64" + else + ARTIFACT_SUFFIX="-native-mac-amd64" + fi + else + echo "This native mill launcher supports only Linux and macOS." 1>&2 + exit 1 + fi +esac + +MILL="${MILL_DOWNLOAD_PATH}/${FULL_MILL_VERSION}" + +try_to_use_system_mill() { + if [ "$(uname)" != "Linux" ]; then + return 0 + fi + + MILL_IN_PATH="$(command -v mill || true)" + + if [ -z "${MILL_IN_PATH}" ]; then + return 0 + fi + + SYSTEM_MILL_FIRST_TWO_BYTES=$(head --bytes=2 "${MILL_IN_PATH}") + if [ "${SYSTEM_MILL_FIRST_TWO_BYTES}" = "#!" ]; then + # MILL_IN_PATH is (very likely) a shell script and not the mill + # executable, ignore it. + return 0 + fi + + SYSTEM_MILL_PATH=$(readlink -e "${MILL_IN_PATH}") + SYSTEM_MILL_SIZE=$(stat --format=%s "${SYSTEM_MILL_PATH}") + SYSTEM_MILL_MTIME=$(stat --format=%y "${SYSTEM_MILL_PATH}") + + if [ ! -d "${MILL_USER_CACHE_DIR}" ]; then + mkdir -p "${MILL_USER_CACHE_DIR}" + fi + + SYSTEM_MILL_INFO_FILE="${MILL_USER_CACHE_DIR}/system-mill-info" + if [ -f "${SYSTEM_MILL_INFO_FILE}" ]; then + parseSystemMillInfo() { + LINE_NUMBER="${1}" + # Select the line number of the SYSTEM_MILL_INFO_FILE, cut the + # variable definition in that line in two halves and return + # the value, and finally remove the quotes. + sed -n "${LINE_NUMBER}p" "${SYSTEM_MILL_INFO_FILE}" |\ + cut -d= -f2 |\ + sed 's/"\(.*\)"/\1/' + } + + CACHED_SYSTEM_MILL_PATH=$(parseSystemMillInfo 1) + CACHED_SYSTEM_MILL_VERSION=$(parseSystemMillInfo 2) + CACHED_SYSTEM_MILL_SIZE=$(parseSystemMillInfo 3) + CACHED_SYSTEM_MILL_MTIME=$(parseSystemMillInfo 4) + + if [ "${SYSTEM_MILL_PATH}" = "${CACHED_SYSTEM_MILL_PATH}" ] \ + && [ "${SYSTEM_MILL_SIZE}" = "${CACHED_SYSTEM_MILL_SIZE}" ] \ + && [ "${SYSTEM_MILL_MTIME}" = "${CACHED_SYSTEM_MILL_MTIME}" ]; then + if [ "${CACHED_SYSTEM_MILL_VERSION}" = "${MILL_VERSION}" ]; then + MILL="${SYSTEM_MILL_PATH}" + return 0 + else + return 0 + fi + fi + fi + + SYSTEM_MILL_VERSION=$(${SYSTEM_MILL_PATH} --version | head -n1 | sed -n 's/^Mill.*version \(.*\)/\1/p') + + cat < "${SYSTEM_MILL_INFO_FILE}" +CACHED_SYSTEM_MILL_PATH="${SYSTEM_MILL_PATH}" +CACHED_SYSTEM_MILL_VERSION="${SYSTEM_MILL_VERSION}" +CACHED_SYSTEM_MILL_SIZE="${SYSTEM_MILL_SIZE}" +CACHED_SYSTEM_MILL_MTIME="${SYSTEM_MILL_MTIME}" +EOF + + if [ "${SYSTEM_MILL_VERSION}" = "${MILL_VERSION}" ]; then + MILL="${SYSTEM_MILL_PATH}" + fi +} +try_to_use_system_mill + +# If not already downloaded, download it +if [ ! -s "${MILL}" ] ; then + + # support old non-XDG download dir + MILL_OLD_DOWNLOAD_PATH="${HOME}/.mill/download" + OLD_MILL="${MILL_OLD_DOWNLOAD_PATH}/${MILL_VERSION}" + if [ -x "${OLD_MILL}" ] ; then + MILL="${OLD_MILL}" + else + case $MILL_VERSION in + 0.0.* | 0.1.* | 0.2.* | 0.3.* | 0.4.* ) + DOWNLOAD_SUFFIX="" + DOWNLOAD_FROM_MAVEN=0 + ;; + 0.5.* | 0.6.* | 0.7.* | 0.8.* | 0.9.* | 0.10.* | 0.11.0-M* ) + DOWNLOAD_SUFFIX="-assembly" + DOWNLOAD_FROM_MAVEN=0 + ;; + *) + DOWNLOAD_SUFFIX="-assembly" + DOWNLOAD_FROM_MAVEN=1 + ;; + esac + + DOWNLOAD_FILE=$(mktemp mill.XXXXXX) + + if [ "$DOWNLOAD_FROM_MAVEN" = "1" ] ; then + DOWNLOAD_URL="https://repo1.maven.org/maven2/com/lihaoyi/mill-dist${ARTIFACT_SUFFIX}/${MILL_VERSION}/mill-dist${ARTIFACT_SUFFIX}-${MILL_VERSION}.jar" + else + MILL_VERSION_TAG=$(echo "$MILL_VERSION" | sed -E 's/([^-]+)(-M[0-9]+)?(-.*)?/\1\2/') + DOWNLOAD_URL="${GITHUB_RELEASE_CDN}${MILL_REPO_URL}/releases/download/${MILL_VERSION_TAG}/${MILL_VERSION}${DOWNLOAD_SUFFIX}" + unset MILL_VERSION_TAG + fi + + # TODO: handle command not found + echo "Downloading mill ${MILL_VERSION} from ${DOWNLOAD_URL} ..." 1>&2 + ${CURL_CMD} -f -L -o "${DOWNLOAD_FILE}" "${DOWNLOAD_URL}" + chmod +x "${DOWNLOAD_FILE}" + mkdir -p "${MILL_DOWNLOAD_PATH}" + mv "${DOWNLOAD_FILE}" "${MILL}" + + unset DOWNLOAD_FILE + unset DOWNLOAD_SUFFIX + fi +fi + +if [ -z "$MILL_MAIN_CLI" ] ; then + MILL_MAIN_CLI="${0}" +fi + +MILL_FIRST_ARG="" +if [ "$1" = "--bsp" ] || [ "$1" = "-i" ] || [ "$1" = "--interactive" ] || [ "$1" = "--no-server" ] || [ "$1" = "--repl" ] || [ "$1" = "--help" ] ; then + # Need to preserve the first position of those listed options + MILL_FIRST_ARG=$1 + shift +fi + +unset MILL_DOWNLOAD_PATH +unset MILL_OLD_DOWNLOAD_PATH +unset OLD_MILL +unset MILL_VERSION +unset MILL_REPO_URL + +# We don't quote MILL_FIRST_ARG on purpose, so we can expand the empty value without quotes +# shellcheck disable=SC2086 +exec "${MILL}" $MILL_FIRST_ARG -D "mill.main.cli=${MILL_MAIN_CLI}" "$@" \ No newline at end of file diff --git a/mill-iw-support/.mill-version b/mill-iw-support/.mill-version new file mode 100644 index 0000000..54dbed4 --- /dev/null +++ b/mill-iw-support/.mill-version @@ -0,0 +1 @@ +0.12.10 diff --git a/mill-iw-support/.scalafmt.conf b/mill-iw-support/.scalafmt.conf new file mode 100644 index 0000000..ee7753a --- /dev/null +++ b/mill-iw-support/.scalafmt.conf @@ -0,0 +1,2 @@ +version = "3.7.15" +runner.dialect = scala213 \ No newline at end of file diff --git a/mill-iw-support/README.md b/mill-iw-support/README.md index 4986e5c..9bb1ba8 100644 --- a/mill-iw-support/README.md +++ b/mill-iw-support/README.md @@ -28,8 +28,45 @@ IWMillDeps.tapirCore // ivy"com.softwaremill.sttp.tapir::tapir-core:1.11.16" // Use dependency groups -IWMillDeps.useZIO() // Core ZIO dependencies with test framework -IWMillDeps.useZIOAll() // Comprehensive ZIO stack +IWMillDeps.zioCore // List of core ZIO dependencies +IWMillDeps.zioAll // List of all ZIO dependencies +``` + +### IWBomModule + +Bill of Materials (BOM) module for centralized dependency management: + +```scala +import mill._ +import works.iterative.mill._ + +// Define a BOM module +object bom extends IWBomModule { + def publishVersion = "0.1.0-SNAPSHOT" + + def pomSettings = PomSettings( + // Standard POM settings + ) + + // Override with custom dependencies if needed + override def depManagement = T { + super.depManagement() ++ Agg( + ivy"com.example::custom-lib:1.0.0" + ) + } +} + +// Use the BOM in modules +object myModule extends IWScalaModule { + // Reference the BOM module + override def bomModuleDeps = Seq(bom) + + // No need to specify versions - they come from the BOM + def ivyDeps = super.ivyDeps() ++ Agg( + ivy"dev.zio::zio", + ivy"dev.zio::zio-json" + ) +} ``` ### IWScalaModule @@ -44,10 +81,18 @@ // Use default Scala version (3.6.3) // and standard compiler options with SemanticDB enabled - def ivyDeps = super.ivyDeps() ++ IWMillDeps.useZIO() + def ivyDeps = super.ivyDeps() ++ Agg( + ivy"${IWMillDeps.zio}", + ivy"${IWMillDeps.zioJson}" + ) // Standard test module configuration - object test extends Tests with IWTests + object test extends ScalaTests with TestModule.ZioTest { + def ivyDeps = super.ivyDeps() ++ Agg( + ivy"${IWMillDeps.zioTest}", + ivy"${IWMillDeps.zioTestSbt}" + ) + } } ``` @@ -96,6 +141,51 @@ } ``` +## Dependency Management Approaches + +This library provides two approaches for dependency management: + +### 1. IWMillDeps (String Constants) + +Use the IWMillDeps object to access dependency strings with correct versions: + +```scala +def ivyDeps = super.ivyDeps() ++ Agg( + ivy"${IWMillDeps.zio}", + ivy"${IWMillDeps.zioJson}" +) +``` + +### 2. BOM (Bill of Materials) + +Use the IWBomModule to centralize dependency versions: + +```scala +// Define a BOM module +object bom extends IWBomModule { + def publishVersion = "0.1.0-SNAPSHOT" + def pomSettings = PomSettings(...) +} + +// Use the BOM in modules +object myModule extends IWScalaModule { + override def bomModuleDeps = Seq(bom) + + // No need to specify versions + def ivyDeps = super.ivyDeps() ++ Agg( + ivy"dev.zio::zio", + ivy"dev.zio::zio-json" + ) +} +``` + +The BOM approach offers these advantages: +- Enforces consistent versions across modules +- Simplifies version updates +- Controls transitive dependency versions +- Makes build files cleaner (no version strings) +- Can be published as a separate artifact + ## Examples See the `example` directory for a complete working example. diff --git a/mill-iw-support/build.sc b/mill-iw-support/build.sc index 5df80ae..2ed832c 100644 --- a/mill-iw-support/build.sc +++ b/mill-iw-support/build.sc @@ -2,20 +2,33 @@ import mill.scalalib._ import mill.scalalib.publish._ import mill.scalalib.scalafmt._ +import mill.scalalib.TestModule.Utest -object `mill-iw-support` extends Cross[IWMillSupportModule]("2.13.16", "3.6.3") +/** Main module for the mill-iw-support library. Built for Scala 2.13 which is + * compatible with Mill. + */ +object core extends ScalaModule with ScalafmtModule with PublishModule { + val millVersion = "0.12.10" -class IWMillSupportModule(val crossScalaVersion: String) extends CrossScalaModule with ScalafmtModule with PublishModule { + def scalaVersion = "2.13.16" + + def artifactName = "mill-iw-support" + def publishVersion = "0.1.0-SNAPSHOT" def pomSettings = PomSettings( description = "Iterative Works Mill Support Library", organization = "works.iterative", - url = "https://github.com/iterative-works/mill-iw-support", + url = "https://github.com/iterative-works/iw-project-support", licenses = Seq(License.MIT), - versionControl = VersionControl.github("iterative-works", "mill-iw-support"), + versionControl = + VersionControl.github("iterative-works", "iw-project-support"), developers = Seq( - Developer("mph", "Michal Přihoda", "https://github.com/iterative-works") + Developer( + "mprihoda", + "Michal Příhoda", + "https://github.com/iterative-works" + ) ) ) @@ -23,10 +36,15 @@ ivy"com.lihaoyi::os-lib:0.9.3" ) - object test extends Tests with ScalafmtModule { + // Mill's own dependencies needed for compilation + def compileIvyDeps = Agg( + ivy"com.lihaoyi::mill-main:${millVersion}", + ivy"com.lihaoyi::mill-scalalib:${millVersion}" + ) + + object test extends ScalaTests with Utest with ScalafmtModule { def ivyDeps = Agg( ivy"com.lihaoyi::utest:0.8.2" ) - def testFramework = "utest.runner.Framework" } -} \ No newline at end of file +} diff --git a/mill-iw-support/core/src/IWBomModule.scala b/mill-iw-support/core/src/IWBomModule.scala new file mode 100644 index 0000000..d8e70bb --- /dev/null +++ b/mill-iw-support/core/src/IWBomModule.scala @@ -0,0 +1,97 @@ +package works.iterative.mill + +import mill._ +import mill.scalalib._ +import mill.scalalib.publish._ + +/** Standard Bill of Materials (BOM) module for Iterative Works projects using + * Mill. This module centralizes dependency versioning across projects. + * + * A BOM module allows: + * 1. Centralized version management for dependencies 2. Enforcing specific + * versions of transitive dependencies 3. Applying uniform dependency + * management across multiple modules 4. Publishing a BOM artifact that + * other projects can consume + * + * Usage: + * 1. Create a BOM module in your build: + * ```scala + * object bom extends IWBomModule { + * def publishVersion = "0.1.0-SNAPSHOT" + * + * // Add your organization-specific customizations here + * def pomSettings = PomSettings(...) + * } + * ``` + * + * 2. Use the BOM in your modules: + * ```scala + * object myModule extends IWScalaModule { + * def bomModuleDeps = Seq(bom) + * + * def ivyDeps = Agg( + * // No need to specify versions as they come from the BOM + * ivy"dev.zio::zio", + * ivy"dev.zio::zio-json" + * ) + * } + * ``` + */ +trait IWBomModule extends BomModule with ScalaModule with IWScalaVersions { + + override def scalaVersion: T[String] = scala3Version + + /** The default implementation includes all standard IW dependencies from + * IWMillVersions and IWMillDeps. + */ + override def depManagement = T { + val V = IWMillVersions + + Agg( + // ZIO Core + ivy"dev.zio::zio:${V.zio}", + ivy"dev.zio::zio-test:${V.zio}", + ivy"dev.zio::zio-test-sbt:${V.zio}", + ivy"dev.zio::zio-streams:${V.zio}", + + // ZIO Extensions + ivy"dev.zio::zio-config:${V.zioConfig}", + ivy"dev.zio::zio-config-typesafe:${V.zioConfig}", + ivy"dev.zio::zio-config-magnolia:${V.zioConfig}", + ivy"dev.zio::zio-json:${V.zioJson}", + ivy"dev.zio::zio-logging:${V.zioLogging}", + ivy"dev.zio::zio-logging-slf4j:${V.zioLogging}", + ivy"dev.zio::zio-prelude:${V.zioPrelude}", + ivy"dev.zio::zio-interop-cats:${V.zioInteropCats}", + ivy"dev.zio::zio-interop-reactivestreams:${V.zioInteropReactiveStreams}", + ivy"dev.zio::zio-nio:${V.zioNIO}", + ivy"dev.zio::zio-cli:${V.zioCli}", + ivy"dev.zio::zio-schema:${V.zioSchema}", + ivy"dev.zio::zio-schema-derivation:${V.zioSchema}", + + // Database + ivy"io.getquill::quill-jdbc-zio:${V.quill}", + ivy"com.augustnagro::magnum:${V.magnum}", + ivy"com.augustnagro::magnumzio:${V.magnum}", + ivy"com.augustnagro::magnumpg:${V.magnum}", + + // HTTP + ivy"com.softwaremill.sttp.tapir::tapir-core:${V.tapir}", + ivy"com.softwaremill.sttp.tapir::tapir-zio:${V.tapir}", + ivy"com.softwaremill.sttp.tapir::tapir-json-zio:${V.tapir}", + + // UI/Frontend + ivy"com.lihaoyi::scalatags:${V.scalatags}", + ivy"com.raquo::laminar:${V.laminar}", + ivy"com.raquo::waypoint:${V.waypoint}", + ivy"be.doeraene::url-dsl:${V.urlDsl}", + + // Testing + ivy"org.scalatest::scalatest:${V.scalaTest}", + ivy"com.lihaoyi::utest:${V.utest}", + + // Logging + ivy"ch.qos.logback:logback-classic:${V.logbackClassic}" + ) + } +} diff --git a/mill-iw-support/core/src/IWMillDeps.scala b/mill-iw-support/core/src/IWMillDeps.scala new file mode 100644 index 0000000..d4cac1c --- /dev/null +++ b/mill-iw-support/core/src/IWMillDeps.scala @@ -0,0 +1,80 @@ +package works.iterative.mill + +import mill.api.Loose + +/** Standardized dependency management for Iterative Works projects using Mill. + * This object provides methods to get dependency strings with proper + * versioning. + * + * All versions are centralized in [[IWMillVersions]]. + * + * This object is designed to be used in build.sc files where the ivy string + * interpolator is available, like: + * + * {{{ + * def ivyDeps = Agg( + * ivy"$${IWMillDeps.zio}", + * ivy"$${IWMillDeps.zioJson}" + * ) + * }}} + */ +object IWMillDeps { + val V = IWMillVersions + + // ZIO Core + val zio = s"dev.zio::zio:${V.zio}" + val zioTest = s"dev.zio::zio-test:${V.zio}" + val zioTestSbt = s"dev.zio::zio-test-sbt:${V.zio}" + val zioStreams = s"dev.zio::zio-streams:${V.zio}" + + // ZIO Extensions + val zioConfig = s"dev.zio::zio-config:${V.zioConfig}" + val zioConfigTypesafe = s"dev.zio::zio-config-typesafe:${V.zioConfig}" + val zioConfigMagnolia = s"dev.zio::zio-config-magnolia:${V.zioConfig}" + val zioJson = s"dev.zio::zio-json:${V.zioJson}" + val zioLogging = s"dev.zio::zio-logging:${V.zioLogging}" + val zioLoggingSlf4j = s"dev.zio::zio-logging-slf4j:${V.zioLogging}" + val zioPrelude = s"dev.zio::zio-prelude:${V.zioPrelude}" + val zioInteropCats = s"dev.zio::zio-interop-cats:${V.zioInteropCats}" + val zioInteropReactiveStreams = + s"dev.zio::zio-interop-reactivestreams:${V.zioInteropReactiveStreams}" + val zioNIO = s"dev.zio::zio-nio:${V.zioNIO}" + val zioCli = s"dev.zio::zio-cli:${V.zioCli}" + val zioSchema = s"dev.zio::zio-schema:${V.zioSchema}" + val zioSchemaDerivation = s"dev.zio::zio-schema-derivation:${V.zioSchema}" + + // Collection of ZIO dependencies + val zioCore = List(zio, zioTest, zioTestSbt) + val zioJson_ = List(zioJson) + val zioAll = zioCore ++ List( + zioStreams, + zioConfig, + zioConfigTypesafe, + zioConfigMagnolia, + zioLogging, + zioPrelude + ) ++ zioJson_ + + // Database + val quill = s"io.getquill::quill-jdbc-zio:${V.quill}" + val magnum = s"com.augustnagro::magnum_3:${V.magnum}" + val magnumZIO = s"com.augustnagro::magnumzio_3:${V.magnum}" + val magnumPG = s"com.augustnagro::magnumpg_3:${V.magnum}" + + // HTTP + val tapirCore = s"com.softwaremill.sttp.tapir::tapir-core:${V.tapir}" + val tapirZIO = s"com.softwaremill.sttp.tapir::tapir-zio:${V.tapir}" + val tapirZIOJson = s"com.softwaremill.sttp.tapir::tapir-json-zio:${V.tapir}" + + // UI/Frontend + val scalatags = s"com.lihaoyi::scalatags:${V.scalatags}" + val laminar = s"com.raquo::laminar:${V.laminar}" + val waypoint = s"com.raquo::waypoint:${V.waypoint}" + val urlDsl = s"be.doeraene::url-dsl:${V.urlDsl}" + + // Testing + val scalaTest = s"org.scalatest::scalatest:${V.scalaTest}" + + // Logging + val logbackClassic = s"ch.qos.logback:logback-classic:${V.logbackClassic}" +} diff --git a/mill-iw-support/core/src/IWMillVersions.scala b/mill-iw-support/core/src/IWMillVersions.scala new file mode 100644 index 0000000..0ad9bd2 --- /dev/null +++ b/mill-iw-support/core/src/IWMillVersions.scala @@ -0,0 +1,56 @@ +package works.iterative.mill + +/** + * Centralized version management for Iterative Works projects using Mill. + * This object contains all library versions used across projects. + * + * Update this object to bump versions for all projects. + */ +object IWMillVersions { + // ZIO Ecosystem + val zio = "2.1.16" + val zioConfig = "4.0.3" + val zioCli = "0.7.0" + val zioInteropCats = "23.1.0.3" + val zioInteropReactiveStreams = "2.0.2" + val zioJson = "0.7.36" + val zioLogging = "2.5.0" + val zioNIO = "2.0.2" + val zioPrelude = "1.0.0-RC39" + val zioQuery = "0.7.6" + val zioSchema = "1.6.3" + + // Akka Ecosystem + val akka = "2.6.18" + val akkaHttp = "10.4.0" + + // Other Libraries + val cats = "2.13.0" + val utest = "0.8.2" + val caliban = "2.9.2" + val chimney = "1.7.3" + val ducktape = "0.2.7" + val elastic4s = "7.12.4" + val http4s = "0.23.30" + val http4sBlaze = "0.23.17" + val http4sPac4J = "5.0.0" + val laminar = "17.2.0" + val laminext = "0.17.0" + val logbackClassic = "1.5.17" + val magnum = "2.0.0-M1" + val pac4j = "6.0.3" + val play = "2.8.18" + val playJson = "2.9.3" + val quill = "4.8.6" + val scalaJsMacroTaskExecutor = "1.1.1" + val scalaJsJavaSecureRandom = "1.0.0" + val scalatags = "0.13.1" + val scalaTest = "3.2.19" + val slick = "3.3.3" + val sttpClient3 = "3.10.3" + val sttpClient4 = "4.0.0-RC1" + val support = "1.7.0" + val tapir = "1.11.16" + val urlDsl = "0.7.0" + val waypoint = "9.0.0" +} \ No newline at end of file diff --git a/mill-iw-support/core/src/IWPublishModule.scala b/mill-iw-support/core/src/IWPublishModule.scala new file mode 100644 index 0000000..5ae0ade --- /dev/null +++ b/mill-iw-support/core/src/IWPublishModule.scala @@ -0,0 +1,22 @@ +package works.iterative.mill + +import mill._ +import mill.scalalib._ +import mill.scalalib.publish._ + +/** Standard publish module trait for Iterative Works projects using Mill. This + * trait sets up standard publishing configuration for IW Maven repositories. + * + * To use this trait, simply extend it in your module and define: + * - publishVersion: The version to publish + * - pomSettings: The POM settings for the publication + * + * Use IW_USERNAME and IW_PASSWORD environment variables for authentication. + */ +trait IWPublishModule extends PublishModule { + + override def sonatypeUri = "https://dig.iterative.works/maven/releases" + override def sonatypeSnapshotUri = + "https://dig.iterative.works/maven/snapshots" + +} diff --git a/mill-iw-support/core/src/IWScalaModule.scala b/mill-iw-support/core/src/IWScalaModule.scala new file mode 100644 index 0000000..021bccc --- /dev/null +++ b/mill-iw-support/core/src/IWScalaModule.scala @@ -0,0 +1,97 @@ +package works.iterative.mill + +import mill._ +import mill.scalalib._ +import mill.scalalib.scalafmt._ + +/** Standard Scala module trait for Iterative Works projects using Mill. This + * trait sets up standard compiler options and configuration. + */ +trait IWScalaModule + extends ScalaModule + with ScalafmtModule + with IWScalaVersions { + + override def scalaVersion = scala3Version + + // Define base compiler options based on Scala version + def baseScalacOptions = T { + if (scalaVersion().startsWith("2.")) { + Seq( + "-encoding", + "utf8", + "-feature", + "-unchecked", + "-language:existentials", + "-language:experimental.macros", + "-language:higherKinds", + "-language:implicitConversions", + "-Xlint:adapted-args", + "-Xlint:constant", + "-Xlint:delayedinit-select", + "-Xlint:deprecation", + "-Xlint:doc-detached", + "-Xlint:implicit-recursion", + "-Xlint:implicit-not-found", + "-Xlint:inaccessible", + "-Xlint:infer-any", + "-Xlint:missing-interpolator", + "-Xlint:nullary-unit", + "-Xlint:option-implicit", + "-Xlint:package-object-classes", + "-Xlint:poly-implicit-overload", + "-Xlint:private-shadow", + "-Xlint:stars-align", + "-Xlint:strict-unsealed-patmat", + "-Xlint:type-parameter-shadow", + "-Xlint:-byname-implicit", + "-Wdead-code", + "-Wextra-implicit", + "-Wnumeric-widen", + "-Wvalue-discard", + "-Wnonunit-statement", + "-Wunused:nowarn", + "-Wunused:implicits", + "-Wunused:explicits", + "-Wunused:imports", + "-Wunused:locals", + "-Wunused:params", + "-Wunused:patvars", + "-Wunused:privates" + ) + } else { + Seq( + "-encoding", + "utf8", + "-deprecation", + "-feature", + "-unchecked", + "-language:experimental.macros", + "-language:higherKinds", + "-language:implicitConversions", + "-Xkind-projector", + "-Wvalue-discard", + "-Wnonunit-statement", + "-Wunused:implicits", + "-Wunused:explicits", + "-Wunused:imports", + "-Wunused:locals", + "-Wunused:params", + "-Wunused:privates" + ) + } + } + + // Enable SemanticDB for tooling + def semanticDbOptions = Seq( + "-Xsemanticdb" + ) + + // Override scalacOptions to include both base options and SemanticDB options + override def scalacOptions = T { + super.scalacOptions() ++ baseScalacOptions() ++ semanticDbOptions + } + + // Base test configuration + trait IWTests extends ScalaTests with ScalafmtModule +} diff --git a/mill-iw-support/core/src/IWScalaVersions.scala b/mill-iw-support/core/src/IWScalaVersions.scala new file mode 100644 index 0000000..aa70cb6 --- /dev/null +++ b/mill-iw-support/core/src/IWScalaVersions.scala @@ -0,0 +1,9 @@ +package works.iterative.mill + +trait IWScalaVersions { + val scala2Version = "2.13.16" + val scala3Version = "3.6.4" + val scala3LTSVersion = "3.3.5" +} + +object IWScalaVersions extends IWScalaVersions diff --git a/mill-iw-support/core/src/package.scala b/mill-iw-support/core/src/package.scala new file mode 100644 index 0000000..1ba4556 --- /dev/null +++ b/mill-iw-support/core/src/package.scala @@ -0,0 +1,6 @@ +package works.iterative + +package object mill { + val IWVersions = IWMillVersions + val IWDeps = IWMillDeps +} diff --git a/mill-iw-support/example/.mill-version b/mill-iw-support/example/.mill-version new file mode 100644 index 0000000..54dbed4 --- /dev/null +++ b/mill-iw-support/example/.mill-version @@ -0,0 +1 @@ +0.12.10 diff --git a/mill-iw-support/example/build.sc b/mill-iw-support/example/build.sc index 5da77ab..cae5a6a 100644 --- a/mill-iw-support/example/build.sc +++ b/mill-iw-support/example/build.sc @@ -1,11 +1,22 @@ import mill._ import mill.scalalib._ +import mill.scalalib.publish._ +import mill.scalalib.scalafmt._ + +// Import our IW Mill Support library import $ivy.`works.iterative::mill-iw-support:0.1.0-SNAPSHOT` import works.iterative.mill._ +// Define a BOM (Bill of Materials) module +object bom extends IWBomModule + +// Example module using the IW Mill support with BOM object example extends IWScalaModule with IWPublishModule { def publishVersion = "0.1.0-SNAPSHOT" - + + // Use the BOM module for dependency management + override def bomModuleDeps = Seq(bom) + def pomSettings = PomSettings( description = "Example Mill Project using IW Mill Support", organization = "com.example", @@ -16,15 +27,25 @@ Developer("developer", "Example Developer", "https://github.com/example") ) ) - - // Use centralized dependency versions and definitions - def ivyDeps = super.ivyDeps() ++ IWMillDeps.useZIO() ++ Agg( - IWMillDeps.zioJson, - IWMillDeps.tapirCore, - IWMillDeps.tapirZIO, - IWMillDeps.tapirZIOJson + + // With BOM, we don't need to specify versions + // They're managed by the BOM module + def ivyDeps = super.ivyDeps() ++ Agg( + ivy"dev.zio::zio", + ivy"dev.zio::zio-json", + ivy"com.softwaremill.sttp.tapir::tapir-core", + ivy"com.softwaremill.sttp.tapir::tapir-zio", + ivy"com.softwaremill.sttp.tapir::tapir-json-zio" ) - - // Define test module extending IWTests - object test extends Tests with IWTests -} \ No newline at end of file + + // Define test module using standard Mill approach + object test extends ScalaTests with TestModule.ZioTest { + // Use the BOM module for dependency management + override def bomModuleDeps = Seq(bom) + + def ivyDeps = super.ivyDeps() ++ Agg( + ivy"dev.zio::zio-test", // Version comes from BOM + ivy"dev.zio::zio-test-sbt" // Version comes from BOM + ) + } +} diff --git a/mill-iw-support/example/example/src/Example.scala b/mill-iw-support/example/example/src/Example.scala new file mode 100644 index 0000000..aa28980 --- /dev/null +++ b/mill-iw-support/example/example/src/Example.scala @@ -0,0 +1,27 @@ +package example + +import zio._ +import zio.json._ + +// Simple example class +case class User(id: String, name: String, email: String) + +object User { + // JSON codecs using ZIO JSON + implicit val encoder: JsonEncoder[User] = DeriveJsonEncoder.gen[User] + implicit val decoder: JsonDecoder[User] = DeriveJsonDecoder.gen[User] +} + +object Example extends ZIOAppDefault { + + val program = for { + _ <- Console.printLine("Starting example application") + user = User("1", "John Doe", "john@example.com") + json <- ZIO.succeed(user.toJson) + _ <- Console.printLine(s"User as JSON: $json") + parsed <- ZIO.fromEither(json.fromJson[User]) + _ <- Console.printLine(s"Parsed user: $parsed") + } yield () + + def run = program +} \ No newline at end of file diff --git a/mill-iw-support/example/example/test/src/ExampleTest.scala b/mill-iw-support/example/example/test/src/ExampleTest.scala new file mode 100644 index 0000000..951c2d8 --- /dev/null +++ b/mill-iw-support/example/example/test/src/ExampleTest.scala @@ -0,0 +1,26 @@ +package example + +import zio._ +import zio.json._ +import zio.test._ +import zio.test.Assertion._ + +object ExampleTest extends ZIOSpecDefault { + override def spec = suite("Example suite")( + test("User JSON serialization") { + val user = User("1", "John Doe", "john@example.com") + val json = user.toJson + val expected = + """{"id":"1","name":"John Doe","email":"john@example.com"}""" + + assertTrue(json == expected) + }, + test("User JSON deserialization") { + val json = """{"id":"1","name":"John Doe","email":"john@example.com"}""" + val parsed = json.fromJson[User] + val expected = User("1", "John Doe", "john@example.com") + + assertTrue(parsed == Right(expected)) + } + ) +} diff --git a/mill-iw-support/example/mill b/mill-iw-support/example/mill new file mode 100755 index 0000000..f349762 --- /dev/null +++ b/mill-iw-support/example/mill @@ -0,0 +1,265 @@ +#!/usr/bin/env sh + +# This is a wrapper script, that automatically download mill from GitHub release pages +# You can give the required mill version with --mill-version parameter +# If no version is given, it falls back to the value of DEFAULT_MILL_VERSION +# +# Original Project page: https://github.com/lefou/millw +# Script Version: 0.4.12 +# +# If you want to improve this script, please also contribute your changes back! +# +# Licensed under the Apache License, Version 2.0 + +set -e + +if [ -z "${DEFAULT_MILL_VERSION}" ] ; then + DEFAULT_MILL_VERSION=0.12.10 +fi + + +if [ -z "${GITHUB_RELEASE_CDN}" ] ; then + GITHUB_RELEASE_CDN="" +fi + + +MILL_REPO_URL="https://github.com/com-lihaoyi/mill" + +if [ -z "${CURL_CMD}" ] ; then + CURL_CMD=curl +fi + +# Explicit commandline argument takes precedence over all other methods +if [ "$1" = "--mill-version" ] ; then + shift + if [ "x$1" != "x" ] ; then + MILL_VERSION="$1" + shift + else + echo "You specified --mill-version without a version." 1>&2 + echo "Please provide a version that matches one provided on" 1>&2 + echo "${MILL_REPO_URL}/releases" 1>&2 + false + fi +fi + +# Please note, that if a MILL_VERSION is already set in the environment, +# We reuse it's value and skip searching for a value. + +# If not already set, read .mill-version file +if [ -z "${MILL_VERSION}" ] ; then + if [ -f ".mill-version" ] ; then + MILL_VERSION="$(tr '\r' '\n' < .mill-version | head -n 1 2> /dev/null)" + elif [ -f ".config/mill-version" ] ; then + MILL_VERSION="$(tr '\r' '\n' < .config/mill-version | head -n 1 2> /dev/null)" + fi +fi + +MILL_USER_CACHE_DIR="${XDG_CACHE_HOME:-${HOME}/.cache}/mill" + +if [ -z "${MILL_DOWNLOAD_PATH}" ] ; then + MILL_DOWNLOAD_PATH="${MILL_USER_CACHE_DIR}/download" +fi + +# If not already set, try to fetch newest from Github +if [ -z "${MILL_VERSION}" ] ; then + # TODO: try to load latest version from release page + echo "No mill version specified." 1>&2 + echo "You should provide a version via '.mill-version' file or --mill-version option." 1>&2 + + mkdir -p "${MILL_DOWNLOAD_PATH}" + LANG=C touch -d '1 hour ago' "${MILL_DOWNLOAD_PATH}/.expire_latest" 2>/dev/null || ( + # we might be on OSX or BSD which don't have -d option for touch + # but probably a -A [-][[hh]mm]SS + touch "${MILL_DOWNLOAD_PATH}/.expire_latest"; touch -A -010000 "${MILL_DOWNLOAD_PATH}/.expire_latest" + ) || ( + # in case we still failed, we retry the first touch command with the intention + # to show the (previously suppressed) error message + LANG=C touch -d '1 hour ago' "${MILL_DOWNLOAD_PATH}/.expire_latest" + ) + + # POSIX shell variant of bash's -nt operator, see https://unix.stackexchange.com/a/449744/6993 + # if [ "${MILL_DOWNLOAD_PATH}/.latest" -nt "${MILL_DOWNLOAD_PATH}/.expire_latest" ] ; then + if [ -n "$(find -L "${MILL_DOWNLOAD_PATH}/.latest" -prune -newer "${MILL_DOWNLOAD_PATH}/.expire_latest")" ]; then + # we know a current latest version + MILL_VERSION=$(head -n 1 "${MILL_DOWNLOAD_PATH}"/.latest 2> /dev/null) + fi + + if [ -z "${MILL_VERSION}" ] ; then + # we don't know a current latest version + echo "Retrieving latest mill version ..." 1>&2 + LANG=C ${CURL_CMD} -s -i -f -I ${MILL_REPO_URL}/releases/latest 2> /dev/null | grep --ignore-case Location: | sed s'/^.*tag\///' | tr -d '\r\n' > "${MILL_DOWNLOAD_PATH}/.latest" + MILL_VERSION=$(head -n 1 "${MILL_DOWNLOAD_PATH}"/.latest 2> /dev/null) + fi + + if [ -z "${MILL_VERSION}" ] ; then + # Last resort + MILL_VERSION="${DEFAULT_MILL_VERSION}" + echo "Falling back to hardcoded mill version ${MILL_VERSION}" 1>&2 + else + echo "Using mill version ${MILL_VERSION}" 1>&2 + fi +fi + +MILL_NATIVE_SUFFIX="-native" +FULL_MILL_VERSION=$MILL_VERSION +ARTIFACT_SUFFIX="" +case "$MILL_VERSION" in + *"$MILL_NATIVE_SUFFIX") + MILL_VERSION=${MILL_VERSION%"$MILL_NATIVE_SUFFIX"} + if [ "$(expr substr $(uname -s) 1 5 2>/dev/null)" = "Linux" ]; then + if [ "$(uname -m)" = "aarch64" ]; then + ARTIFACT_SUFFIX="-native-linux-aarch64" + else + ARTIFACT_SUFFIX="-native-linux-amd64" + fi + elif [ "$(uname)" = "Darwin" ]; then + if [ "$(uname -m)" = "arm64" ]; then + ARTIFACT_SUFFIX="-native-mac-aarch64" + else + ARTIFACT_SUFFIX="-native-mac-amd64" + fi + else + echo "This native mill launcher supports only Linux and macOS." 1>&2 + exit 1 + fi +esac + +MILL="${MILL_DOWNLOAD_PATH}/${FULL_MILL_VERSION}" + +try_to_use_system_mill() { + if [ "$(uname)" != "Linux" ]; then + return 0 + fi + + MILL_IN_PATH="$(command -v mill || true)" + + if [ -z "${MILL_IN_PATH}" ]; then + return 0 + fi + + SYSTEM_MILL_FIRST_TWO_BYTES=$(head --bytes=2 "${MILL_IN_PATH}") + if [ "${SYSTEM_MILL_FIRST_TWO_BYTES}" = "#!" ]; then + # MILL_IN_PATH is (very likely) a shell script and not the mill + # executable, ignore it. + return 0 + fi + + SYSTEM_MILL_PATH=$(readlink -e "${MILL_IN_PATH}") + SYSTEM_MILL_SIZE=$(stat --format=%s "${SYSTEM_MILL_PATH}") + SYSTEM_MILL_MTIME=$(stat --format=%y "${SYSTEM_MILL_PATH}") + + if [ ! -d "${MILL_USER_CACHE_DIR}" ]; then + mkdir -p "${MILL_USER_CACHE_DIR}" + fi + + SYSTEM_MILL_INFO_FILE="${MILL_USER_CACHE_DIR}/system-mill-info" + if [ -f "${SYSTEM_MILL_INFO_FILE}" ]; then + parseSystemMillInfo() { + LINE_NUMBER="${1}" + # Select the line number of the SYSTEM_MILL_INFO_FILE, cut the + # variable definition in that line in two halves and return + # the value, and finally remove the quotes. + sed -n "${LINE_NUMBER}p" "${SYSTEM_MILL_INFO_FILE}" |\ + cut -d= -f2 |\ + sed 's/"\(.*\)"/\1/' + } + + CACHED_SYSTEM_MILL_PATH=$(parseSystemMillInfo 1) + CACHED_SYSTEM_MILL_VERSION=$(parseSystemMillInfo 2) + CACHED_SYSTEM_MILL_SIZE=$(parseSystemMillInfo 3) + CACHED_SYSTEM_MILL_MTIME=$(parseSystemMillInfo 4) + + if [ "${SYSTEM_MILL_PATH}" = "${CACHED_SYSTEM_MILL_PATH}" ] \ + && [ "${SYSTEM_MILL_SIZE}" = "${CACHED_SYSTEM_MILL_SIZE}" ] \ + && [ "${SYSTEM_MILL_MTIME}" = "${CACHED_SYSTEM_MILL_MTIME}" ]; then + if [ "${CACHED_SYSTEM_MILL_VERSION}" = "${MILL_VERSION}" ]; then + MILL="${SYSTEM_MILL_PATH}" + return 0 + else + return 0 + fi + fi + fi + + SYSTEM_MILL_VERSION=$(${SYSTEM_MILL_PATH} --version | head -n1 | sed -n 's/^Mill.*version \(.*\)/\1/p') + + cat < "${SYSTEM_MILL_INFO_FILE}" +CACHED_SYSTEM_MILL_PATH="${SYSTEM_MILL_PATH}" +CACHED_SYSTEM_MILL_VERSION="${SYSTEM_MILL_VERSION}" +CACHED_SYSTEM_MILL_SIZE="${SYSTEM_MILL_SIZE}" +CACHED_SYSTEM_MILL_MTIME="${SYSTEM_MILL_MTIME}" +EOF + + if [ "${SYSTEM_MILL_VERSION}" = "${MILL_VERSION}" ]; then + MILL="${SYSTEM_MILL_PATH}" + fi +} +try_to_use_system_mill + +# If not already downloaded, download it +if [ ! -s "${MILL}" ] ; then + + # support old non-XDG download dir + MILL_OLD_DOWNLOAD_PATH="${HOME}/.mill/download" + OLD_MILL="${MILL_OLD_DOWNLOAD_PATH}/${MILL_VERSION}" + if [ -x "${OLD_MILL}" ] ; then + MILL="${OLD_MILL}" + else + case $MILL_VERSION in + 0.0.* | 0.1.* | 0.2.* | 0.3.* | 0.4.* ) + DOWNLOAD_SUFFIX="" + DOWNLOAD_FROM_MAVEN=0 + ;; + 0.5.* | 0.6.* | 0.7.* | 0.8.* | 0.9.* | 0.10.* | 0.11.0-M* ) + DOWNLOAD_SUFFIX="-assembly" + DOWNLOAD_FROM_MAVEN=0 + ;; + *) + DOWNLOAD_SUFFIX="-assembly" + DOWNLOAD_FROM_MAVEN=1 + ;; + esac + + DOWNLOAD_FILE=$(mktemp mill.XXXXXX) + + if [ "$DOWNLOAD_FROM_MAVEN" = "1" ] ; then + DOWNLOAD_URL="https://repo1.maven.org/maven2/com/lihaoyi/mill-dist${ARTIFACT_SUFFIX}/${MILL_VERSION}/mill-dist${ARTIFACT_SUFFIX}-${MILL_VERSION}.jar" + else + MILL_VERSION_TAG=$(echo "$MILL_VERSION" | sed -E 's/([^-]+)(-M[0-9]+)?(-.*)?/\1\2/') + DOWNLOAD_URL="${GITHUB_RELEASE_CDN}${MILL_REPO_URL}/releases/download/${MILL_VERSION_TAG}/${MILL_VERSION}${DOWNLOAD_SUFFIX}" + unset MILL_VERSION_TAG + fi + + # TODO: handle command not found + echo "Downloading mill ${MILL_VERSION} from ${DOWNLOAD_URL} ..." 1>&2 + ${CURL_CMD} -f -L -o "${DOWNLOAD_FILE}" "${DOWNLOAD_URL}" + chmod +x "${DOWNLOAD_FILE}" + mkdir -p "${MILL_DOWNLOAD_PATH}" + mv "${DOWNLOAD_FILE}" "${MILL}" + + unset DOWNLOAD_FILE + unset DOWNLOAD_SUFFIX + fi +fi + +if [ -z "$MILL_MAIN_CLI" ] ; then + MILL_MAIN_CLI="${0}" +fi + +MILL_FIRST_ARG="" +if [ "$1" = "--bsp" ] || [ "$1" = "-i" ] || [ "$1" = "--interactive" ] || [ "$1" = "--no-server" ] || [ "$1" = "--repl" ] || [ "$1" = "--help" ] ; then + # Need to preserve the first position of those listed options + MILL_FIRST_ARG=$1 + shift +fi + +unset MILL_DOWNLOAD_PATH +unset MILL_OLD_DOWNLOAD_PATH +unset OLD_MILL +unset MILL_VERSION +unset MILL_REPO_URL + +# We don't quote MILL_FIRST_ARG on purpose, so we can expand the empty value without quotes +# shellcheck disable=SC2086 +exec "${MILL}" $MILL_FIRST_ARG -D "mill.main.cli=${MILL_MAIN_CLI}" "$@" \ No newline at end of file diff --git a/mill-iw-support/example/src/Example.scala b/mill-iw-support/example/src/Example.scala deleted file mode 100644 index aa28980..0000000 --- a/mill-iw-support/example/src/Example.scala +++ /dev/null @@ -1,27 +0,0 @@ -package example - -import zio._ -import zio.json._ - -// Simple example class -case class User(id: String, name: String, email: String) - -object User { - // JSON codecs using ZIO JSON - implicit val encoder: JsonEncoder[User] = DeriveJsonEncoder.gen[User] - implicit val decoder: JsonDecoder[User] = DeriveJsonDecoder.gen[User] -} - -object Example extends ZIOAppDefault { - - val program = for { - _ <- Console.printLine("Starting example application") - user = User("1", "John Doe", "john@example.com") - json <- ZIO.succeed(user.toJson) - _ <- Console.printLine(s"User as JSON: $json") - parsed <- ZIO.fromEither(json.fromJson[User]) - _ <- Console.printLine(s"Parsed user: $parsed") - } yield () - - def run = program -} \ No newline at end of file diff --git a/mill-iw-support/example/test/src/ExampleTest.scala b/mill-iw-support/example/test/src/ExampleTest.scala deleted file mode 100644 index 20f0c06..0000000 --- a/mill-iw-support/example/test/src/ExampleTest.scala +++ /dev/null @@ -1,27 +0,0 @@ -package example - -import utest._ -import zio._ -import zio.json._ -import zio.test._ -import zio.test.Assertion._ - -object ExampleTest extends TestSuite { - val tests = Tests { - test("User JSON serialization") { - val user = User("1", "John Doe", "john@example.com") - val json = user.toJson - val expected = """{"id":"1","name":"John Doe","email":"john@example.com"}""" - - assert(json == expected) - } - - test("User JSON deserialization") { - val json = """{"id":"1","name":"John Doe","email":"john@example.com"}""" - val parsed = json.fromJson[User] - val expected = User("1", "John Doe", "john@example.com") - - assert(parsed == Right(expected)) - } - } -} \ No newline at end of file diff --git a/mill-iw-support/mill b/mill-iw-support/mill new file mode 100755 index 0000000..f349762 --- /dev/null +++ b/mill-iw-support/mill @@ -0,0 +1,265 @@ +#!/usr/bin/env sh + +# This is a wrapper script, that automatically download mill from GitHub release pages +# You can give the required mill version with --mill-version parameter +# If no version is given, it falls back to the value of DEFAULT_MILL_VERSION +# +# Original Project page: https://github.com/lefou/millw +# Script Version: 0.4.12 +# +# If you want to improve this script, please also contribute your changes back! +# +# Licensed under the Apache License, Version 2.0 + +set -e + +if [ -z "${DEFAULT_MILL_VERSION}" ] ; then + DEFAULT_MILL_VERSION=0.12.10 +fi + + +if [ -z "${GITHUB_RELEASE_CDN}" ] ; then + GITHUB_RELEASE_CDN="" +fi + + +MILL_REPO_URL="https://github.com/com-lihaoyi/mill" + +if [ -z "${CURL_CMD}" ] ; then + CURL_CMD=curl +fi + +# Explicit commandline argument takes precedence over all other methods +if [ "$1" = "--mill-version" ] ; then + shift + if [ "x$1" != "x" ] ; then + MILL_VERSION="$1" + shift + else + echo "You specified --mill-version without a version." 1>&2 + echo "Please provide a version that matches one provided on" 1>&2 + echo "${MILL_REPO_URL}/releases" 1>&2 + false + fi +fi + +# Please note, that if a MILL_VERSION is already set in the environment, +# We reuse it's value and skip searching for a value. + +# If not already set, read .mill-version file +if [ -z "${MILL_VERSION}" ] ; then + if [ -f ".mill-version" ] ; then + MILL_VERSION="$(tr '\r' '\n' < .mill-version | head -n 1 2> /dev/null)" + elif [ -f ".config/mill-version" ] ; then + MILL_VERSION="$(tr '\r' '\n' < .config/mill-version | head -n 1 2> /dev/null)" + fi +fi + +MILL_USER_CACHE_DIR="${XDG_CACHE_HOME:-${HOME}/.cache}/mill" + +if [ -z "${MILL_DOWNLOAD_PATH}" ] ; then + MILL_DOWNLOAD_PATH="${MILL_USER_CACHE_DIR}/download" +fi + +# If not already set, try to fetch newest from Github +if [ -z "${MILL_VERSION}" ] ; then + # TODO: try to load latest version from release page + echo "No mill version specified." 1>&2 + echo "You should provide a version via '.mill-version' file or --mill-version option." 1>&2 + + mkdir -p "${MILL_DOWNLOAD_PATH}" + LANG=C touch -d '1 hour ago' "${MILL_DOWNLOAD_PATH}/.expire_latest" 2>/dev/null || ( + # we might be on OSX or BSD which don't have -d option for touch + # but probably a -A [-][[hh]mm]SS + touch "${MILL_DOWNLOAD_PATH}/.expire_latest"; touch -A -010000 "${MILL_DOWNLOAD_PATH}/.expire_latest" + ) || ( + # in case we still failed, we retry the first touch command with the intention + # to show the (previously suppressed) error message + LANG=C touch -d '1 hour ago' "${MILL_DOWNLOAD_PATH}/.expire_latest" + ) + + # POSIX shell variant of bash's -nt operator, see https://unix.stackexchange.com/a/449744/6993 + # if [ "${MILL_DOWNLOAD_PATH}/.latest" -nt "${MILL_DOWNLOAD_PATH}/.expire_latest" ] ; then + if [ -n "$(find -L "${MILL_DOWNLOAD_PATH}/.latest" -prune -newer "${MILL_DOWNLOAD_PATH}/.expire_latest")" ]; then + # we know a current latest version + MILL_VERSION=$(head -n 1 "${MILL_DOWNLOAD_PATH}"/.latest 2> /dev/null) + fi + + if [ -z "${MILL_VERSION}" ] ; then + # we don't know a current latest version + echo "Retrieving latest mill version ..." 1>&2 + LANG=C ${CURL_CMD} -s -i -f -I ${MILL_REPO_URL}/releases/latest 2> /dev/null | grep --ignore-case Location: | sed s'/^.*tag\///' | tr -d '\r\n' > "${MILL_DOWNLOAD_PATH}/.latest" + MILL_VERSION=$(head -n 1 "${MILL_DOWNLOAD_PATH}"/.latest 2> /dev/null) + fi + + if [ -z "${MILL_VERSION}" ] ; then + # Last resort + MILL_VERSION="${DEFAULT_MILL_VERSION}" + echo "Falling back to hardcoded mill version ${MILL_VERSION}" 1>&2 + else + echo "Using mill version ${MILL_VERSION}" 1>&2 + fi +fi + +MILL_NATIVE_SUFFIX="-native" +FULL_MILL_VERSION=$MILL_VERSION +ARTIFACT_SUFFIX="" +case "$MILL_VERSION" in + *"$MILL_NATIVE_SUFFIX") + MILL_VERSION=${MILL_VERSION%"$MILL_NATIVE_SUFFIX"} + if [ "$(expr substr $(uname -s) 1 5 2>/dev/null)" = "Linux" ]; then + if [ "$(uname -m)" = "aarch64" ]; then + ARTIFACT_SUFFIX="-native-linux-aarch64" + else + ARTIFACT_SUFFIX="-native-linux-amd64" + fi + elif [ "$(uname)" = "Darwin" ]; then + if [ "$(uname -m)" = "arm64" ]; then + ARTIFACT_SUFFIX="-native-mac-aarch64" + else + ARTIFACT_SUFFIX="-native-mac-amd64" + fi + else + echo "This native mill launcher supports only Linux and macOS." 1>&2 + exit 1 + fi +esac + +MILL="${MILL_DOWNLOAD_PATH}/${FULL_MILL_VERSION}" + +try_to_use_system_mill() { + if [ "$(uname)" != "Linux" ]; then + return 0 + fi + + MILL_IN_PATH="$(command -v mill || true)" + + if [ -z "${MILL_IN_PATH}" ]; then + return 0 + fi + + SYSTEM_MILL_FIRST_TWO_BYTES=$(head --bytes=2 "${MILL_IN_PATH}") + if [ "${SYSTEM_MILL_FIRST_TWO_BYTES}" = "#!" ]; then + # MILL_IN_PATH is (very likely) a shell script and not the mill + # executable, ignore it. + return 0 + fi + + SYSTEM_MILL_PATH=$(readlink -e "${MILL_IN_PATH}") + SYSTEM_MILL_SIZE=$(stat --format=%s "${SYSTEM_MILL_PATH}") + SYSTEM_MILL_MTIME=$(stat --format=%y "${SYSTEM_MILL_PATH}") + + if [ ! -d "${MILL_USER_CACHE_DIR}" ]; then + mkdir -p "${MILL_USER_CACHE_DIR}" + fi + + SYSTEM_MILL_INFO_FILE="${MILL_USER_CACHE_DIR}/system-mill-info" + if [ -f "${SYSTEM_MILL_INFO_FILE}" ]; then + parseSystemMillInfo() { + LINE_NUMBER="${1}" + # Select the line number of the SYSTEM_MILL_INFO_FILE, cut the + # variable definition in that line in two halves and return + # the value, and finally remove the quotes. + sed -n "${LINE_NUMBER}p" "${SYSTEM_MILL_INFO_FILE}" |\ + cut -d= -f2 |\ + sed 's/"\(.*\)"/\1/' + } + + CACHED_SYSTEM_MILL_PATH=$(parseSystemMillInfo 1) + CACHED_SYSTEM_MILL_VERSION=$(parseSystemMillInfo 2) + CACHED_SYSTEM_MILL_SIZE=$(parseSystemMillInfo 3) + CACHED_SYSTEM_MILL_MTIME=$(parseSystemMillInfo 4) + + if [ "${SYSTEM_MILL_PATH}" = "${CACHED_SYSTEM_MILL_PATH}" ] \ + && [ "${SYSTEM_MILL_SIZE}" = "${CACHED_SYSTEM_MILL_SIZE}" ] \ + && [ "${SYSTEM_MILL_MTIME}" = "${CACHED_SYSTEM_MILL_MTIME}" ]; then + if [ "${CACHED_SYSTEM_MILL_VERSION}" = "${MILL_VERSION}" ]; then + MILL="${SYSTEM_MILL_PATH}" + return 0 + else + return 0 + fi + fi + fi + + SYSTEM_MILL_VERSION=$(${SYSTEM_MILL_PATH} --version | head -n1 | sed -n 's/^Mill.*version \(.*\)/\1/p') + + cat < "${SYSTEM_MILL_INFO_FILE}" +CACHED_SYSTEM_MILL_PATH="${SYSTEM_MILL_PATH}" +CACHED_SYSTEM_MILL_VERSION="${SYSTEM_MILL_VERSION}" +CACHED_SYSTEM_MILL_SIZE="${SYSTEM_MILL_SIZE}" +CACHED_SYSTEM_MILL_MTIME="${SYSTEM_MILL_MTIME}" +EOF + + if [ "${SYSTEM_MILL_VERSION}" = "${MILL_VERSION}" ]; then + MILL="${SYSTEM_MILL_PATH}" + fi +} +try_to_use_system_mill + +# If not already downloaded, download it +if [ ! -s "${MILL}" ] ; then + + # support old non-XDG download dir + MILL_OLD_DOWNLOAD_PATH="${HOME}/.mill/download" + OLD_MILL="${MILL_OLD_DOWNLOAD_PATH}/${MILL_VERSION}" + if [ -x "${OLD_MILL}" ] ; then + MILL="${OLD_MILL}" + else + case $MILL_VERSION in + 0.0.* | 0.1.* | 0.2.* | 0.3.* | 0.4.* ) + DOWNLOAD_SUFFIX="" + DOWNLOAD_FROM_MAVEN=0 + ;; + 0.5.* | 0.6.* | 0.7.* | 0.8.* | 0.9.* | 0.10.* | 0.11.0-M* ) + DOWNLOAD_SUFFIX="-assembly" + DOWNLOAD_FROM_MAVEN=0 + ;; + *) + DOWNLOAD_SUFFIX="-assembly" + DOWNLOAD_FROM_MAVEN=1 + ;; + esac + + DOWNLOAD_FILE=$(mktemp mill.XXXXXX) + + if [ "$DOWNLOAD_FROM_MAVEN" = "1" ] ; then + DOWNLOAD_URL="https://repo1.maven.org/maven2/com/lihaoyi/mill-dist${ARTIFACT_SUFFIX}/${MILL_VERSION}/mill-dist${ARTIFACT_SUFFIX}-${MILL_VERSION}.jar" + else + MILL_VERSION_TAG=$(echo "$MILL_VERSION" | sed -E 's/([^-]+)(-M[0-9]+)?(-.*)?/\1\2/') + DOWNLOAD_URL="${GITHUB_RELEASE_CDN}${MILL_REPO_URL}/releases/download/${MILL_VERSION_TAG}/${MILL_VERSION}${DOWNLOAD_SUFFIX}" + unset MILL_VERSION_TAG + fi + + # TODO: handle command not found + echo "Downloading mill ${MILL_VERSION} from ${DOWNLOAD_URL} ..." 1>&2 + ${CURL_CMD} -f -L -o "${DOWNLOAD_FILE}" "${DOWNLOAD_URL}" + chmod +x "${DOWNLOAD_FILE}" + mkdir -p "${MILL_DOWNLOAD_PATH}" + mv "${DOWNLOAD_FILE}" "${MILL}" + + unset DOWNLOAD_FILE + unset DOWNLOAD_SUFFIX + fi +fi + +if [ -z "$MILL_MAIN_CLI" ] ; then + MILL_MAIN_CLI="${0}" +fi + +MILL_FIRST_ARG="" +if [ "$1" = "--bsp" ] || [ "$1" = "-i" ] || [ "$1" = "--interactive" ] || [ "$1" = "--no-server" ] || [ "$1" = "--repl" ] || [ "$1" = "--help" ] ; then + # Need to preserve the first position of those listed options + MILL_FIRST_ARG=$1 + shift +fi + +unset MILL_DOWNLOAD_PATH +unset MILL_OLD_DOWNLOAD_PATH +unset OLD_MILL +unset MILL_VERSION +unset MILL_REPO_URL + +# We don't quote MILL_FIRST_ARG on purpose, so we can expand the empty value without quotes +# shellcheck disable=SC2086 +exec "${MILL}" $MILL_FIRST_ARG -D "mill.main.cli=${MILL_MAIN_CLI}" "$@" \ No newline at end of file diff --git a/mill-iw-support/src/main/scala/works/iterative/mill/IWMillDeps.scala b/mill-iw-support/src/main/scala/works/iterative/mill/IWMillDeps.scala deleted file mode 100644 index e21af78..0000000 --- a/mill-iw-support/src/main/scala/works/iterative/mill/IWMillDeps.scala +++ /dev/null @@ -1,78 +0,0 @@ -package works.iterative.mill - -import mill.api.Loose -import mill.scalalib.Dep - -/** - * Standardized dependency management for Iterative Works projects using Mill. - * This object provides methods to get dependencies with proper versioning. - * - * All versions are centralized in [[IWMillVersions]]. - */ -object IWMillDeps { - import IWMillVersions as V - - // ZIO Core - def zio = ivy"dev.zio::zio:${V.zio}" - def zioTest = ivy"dev.zio::zio-test:${V.zio}" - def zioTestSbt = ivy"dev.zio::zio-test-sbt:${V.zio}" - def zioStreams = ivy"dev.zio::zio-streams:${V.zio}" - - // ZIO Extensions - def zioConfig = ivy"dev.zio::zio-config:${V.zioConfig}" - def zioConfigTypesafe = ivy"dev.zio::zio-config-typesafe:${V.zioConfig}" - def zioConfigMagnolia = ivy"dev.zio::zio-config-magnolia:${V.zioConfig}" - def zioJson = ivy"dev.zio::zio-json:${V.zioJson}" - def zioLogging = ivy"dev.zio::zio-logging:${V.zioLogging}" - def zioLoggingSlf4j = ivy"dev.zio::zio-logging-slf4j:${V.zioLogging}" - def zioPrelude = ivy"dev.zio::zio-prelude:${V.zioPrelude}" - def zioInteropCats = ivy"dev.zio::zio-interop-cats:${V.zioInteropCats}" - def zioInteropReactiveStreams = ivy"dev.zio::zio-interop-reactivestreams:${V.zioInteropReactiveStreams}" - def zioNIO = ivy"dev.zio::zio-nio:${V.zioNIO}" - def zioCli = ivy"dev.zio::zio-cli:${V.zioCli}" - def zioSchema = ivy"dev.zio::zio-schema:${V.zioSchema}" - def zioSchemaDerivation = ivy"dev.zio::zio-schema-derivation:${V.zioSchema}" - - // Helper methods for ZIO - def useZIO(): Loose.Agg[Dep] = Loose.Agg( - zio, - zioTest, - zioTestSbt - ) - - def useZIOJson: Loose.Agg[Dep] = Loose.Agg( - zioJson - ) - - def useZIOAll(): Loose.Agg[Dep] = useZIO() ++ Loose.Agg( - zioStreams, - zioConfig, - zioConfigTypesafe, - zioConfigMagnolia, - zioLogging, - zioPrelude - ) ++ useZIOJson - - // Database - def quill = ivy"io.getquill::quill-jdbc-zio:${V.quill}" - def magnum = ivy"com.augustnagro::magnum_3:${V.magnum}" - def magnumZIO = ivy"com.augustnagro::magnumzio_3:${V.magnum}" - def magnumPG = ivy"com.augustnagro::magnumpg_3:${V.magnum}" - - // HTTP - def tapirCore = ivy"com.softwaremill.sttp.tapir::tapir-core:${V.tapir}" - def tapirZIO = ivy"com.softwaremill.sttp.tapir::tapir-zio:${V.tapir}" - def tapirZIOJson = ivy"com.softwaremill.sttp.tapir::tapir-json-zio:${V.tapir}" - - // UI/Frontend - def scalatags = ivy"com.lihaoyi::scalatags:${V.scalatags}" - def laminar = ivy"com.raquo::laminar:${V.laminar}" - def waypoint = ivy"com.raquo::waypoint:${V.waypoint}" - def urlDsl = ivy"be.doeraene::url-dsl:${V.urlDsl}" - - // Testing - def scalaTest = ivy"org.scalatest::scalatest:${V.scalaTest}" - - // Logging - def logbackClassic = ivy"ch.qos.logback:logback-classic:${V.logbackClassic}" -} \ No newline at end of file diff --git a/mill-iw-support/.mill-version b/mill-iw-support/.mill-version new file mode 100644 index 0000000..54dbed4 --- /dev/null +++ b/mill-iw-support/.mill-version @@ -0,0 +1 @@ +0.12.10 diff --git a/mill-iw-support/.scalafmt.conf b/mill-iw-support/.scalafmt.conf new file mode 100644 index 0000000..ee7753a --- /dev/null +++ b/mill-iw-support/.scalafmt.conf @@ -0,0 +1,2 @@ +version = "3.7.15" +runner.dialect = scala213 \ No newline at end of file diff --git a/mill-iw-support/README.md b/mill-iw-support/README.md index 4986e5c..9bb1ba8 100644 --- a/mill-iw-support/README.md +++ b/mill-iw-support/README.md @@ -28,8 +28,45 @@ IWMillDeps.tapirCore // ivy"com.softwaremill.sttp.tapir::tapir-core:1.11.16" // Use dependency groups -IWMillDeps.useZIO() // Core ZIO dependencies with test framework -IWMillDeps.useZIOAll() // Comprehensive ZIO stack +IWMillDeps.zioCore // List of core ZIO dependencies +IWMillDeps.zioAll // List of all ZIO dependencies +``` + +### IWBomModule + +Bill of Materials (BOM) module for centralized dependency management: + +```scala +import mill._ +import works.iterative.mill._ + +// Define a BOM module +object bom extends IWBomModule { + def publishVersion = "0.1.0-SNAPSHOT" + + def pomSettings = PomSettings( + // Standard POM settings + ) + + // Override with custom dependencies if needed + override def depManagement = T { + super.depManagement() ++ Agg( + ivy"com.example::custom-lib:1.0.0" + ) + } +} + +// Use the BOM in modules +object myModule extends IWScalaModule { + // Reference the BOM module + override def bomModuleDeps = Seq(bom) + + // No need to specify versions - they come from the BOM + def ivyDeps = super.ivyDeps() ++ Agg( + ivy"dev.zio::zio", + ivy"dev.zio::zio-json" + ) +} ``` ### IWScalaModule @@ -44,10 +81,18 @@ // Use default Scala version (3.6.3) // and standard compiler options with SemanticDB enabled - def ivyDeps = super.ivyDeps() ++ IWMillDeps.useZIO() + def ivyDeps = super.ivyDeps() ++ Agg( + ivy"${IWMillDeps.zio}", + ivy"${IWMillDeps.zioJson}" + ) // Standard test module configuration - object test extends Tests with IWTests + object test extends ScalaTests with TestModule.ZioTest { + def ivyDeps = super.ivyDeps() ++ Agg( + ivy"${IWMillDeps.zioTest}", + ivy"${IWMillDeps.zioTestSbt}" + ) + } } ``` @@ -96,6 +141,51 @@ } ``` +## Dependency Management Approaches + +This library provides two approaches for dependency management: + +### 1. IWMillDeps (String Constants) + +Use the IWMillDeps object to access dependency strings with correct versions: + +```scala +def ivyDeps = super.ivyDeps() ++ Agg( + ivy"${IWMillDeps.zio}", + ivy"${IWMillDeps.zioJson}" +) +``` + +### 2. BOM (Bill of Materials) + +Use the IWBomModule to centralize dependency versions: + +```scala +// Define a BOM module +object bom extends IWBomModule { + def publishVersion = "0.1.0-SNAPSHOT" + def pomSettings = PomSettings(...) +} + +// Use the BOM in modules +object myModule extends IWScalaModule { + override def bomModuleDeps = Seq(bom) + + // No need to specify versions + def ivyDeps = super.ivyDeps() ++ Agg( + ivy"dev.zio::zio", + ivy"dev.zio::zio-json" + ) +} +``` + +The BOM approach offers these advantages: +- Enforces consistent versions across modules +- Simplifies version updates +- Controls transitive dependency versions +- Makes build files cleaner (no version strings) +- Can be published as a separate artifact + ## Examples See the `example` directory for a complete working example. diff --git a/mill-iw-support/build.sc b/mill-iw-support/build.sc index 5df80ae..2ed832c 100644 --- a/mill-iw-support/build.sc +++ b/mill-iw-support/build.sc @@ -2,20 +2,33 @@ import mill.scalalib._ import mill.scalalib.publish._ import mill.scalalib.scalafmt._ +import mill.scalalib.TestModule.Utest -object `mill-iw-support` extends Cross[IWMillSupportModule]("2.13.16", "3.6.3") +/** Main module for the mill-iw-support library. Built for Scala 2.13 which is + * compatible with Mill. + */ +object core extends ScalaModule with ScalafmtModule with PublishModule { + val millVersion = "0.12.10" -class IWMillSupportModule(val crossScalaVersion: String) extends CrossScalaModule with ScalafmtModule with PublishModule { + def scalaVersion = "2.13.16" + + def artifactName = "mill-iw-support" + def publishVersion = "0.1.0-SNAPSHOT" def pomSettings = PomSettings( description = "Iterative Works Mill Support Library", organization = "works.iterative", - url = "https://github.com/iterative-works/mill-iw-support", + url = "https://github.com/iterative-works/iw-project-support", licenses = Seq(License.MIT), - versionControl = VersionControl.github("iterative-works", "mill-iw-support"), + versionControl = + VersionControl.github("iterative-works", "iw-project-support"), developers = Seq( - Developer("mph", "Michal Přihoda", "https://github.com/iterative-works") + Developer( + "mprihoda", + "Michal Příhoda", + "https://github.com/iterative-works" + ) ) ) @@ -23,10 +36,15 @@ ivy"com.lihaoyi::os-lib:0.9.3" ) - object test extends Tests with ScalafmtModule { + // Mill's own dependencies needed for compilation + def compileIvyDeps = Agg( + ivy"com.lihaoyi::mill-main:${millVersion}", + ivy"com.lihaoyi::mill-scalalib:${millVersion}" + ) + + object test extends ScalaTests with Utest with ScalafmtModule { def ivyDeps = Agg( ivy"com.lihaoyi::utest:0.8.2" ) - def testFramework = "utest.runner.Framework" } -} \ No newline at end of file +} diff --git a/mill-iw-support/core/src/IWBomModule.scala b/mill-iw-support/core/src/IWBomModule.scala new file mode 100644 index 0000000..d8e70bb --- /dev/null +++ b/mill-iw-support/core/src/IWBomModule.scala @@ -0,0 +1,97 @@ +package works.iterative.mill + +import mill._ +import mill.scalalib._ +import mill.scalalib.publish._ + +/** Standard Bill of Materials (BOM) module for Iterative Works projects using + * Mill. This module centralizes dependency versioning across projects. + * + * A BOM module allows: + * 1. Centralized version management for dependencies 2. Enforcing specific + * versions of transitive dependencies 3. Applying uniform dependency + * management across multiple modules 4. Publishing a BOM artifact that + * other projects can consume + * + * Usage: + * 1. Create a BOM module in your build: + * ```scala + * object bom extends IWBomModule { + * def publishVersion = "0.1.0-SNAPSHOT" + * + * // Add your organization-specific customizations here + * def pomSettings = PomSettings(...) + * } + * ``` + * + * 2. Use the BOM in your modules: + * ```scala + * object myModule extends IWScalaModule { + * def bomModuleDeps = Seq(bom) + * + * def ivyDeps = Agg( + * // No need to specify versions as they come from the BOM + * ivy"dev.zio::zio", + * ivy"dev.zio::zio-json" + * ) + * } + * ``` + */ +trait IWBomModule extends BomModule with ScalaModule with IWScalaVersions { + + override def scalaVersion: T[String] = scala3Version + + /** The default implementation includes all standard IW dependencies from + * IWMillVersions and IWMillDeps. + */ + override def depManagement = T { + val V = IWMillVersions + + Agg( + // ZIO Core + ivy"dev.zio::zio:${V.zio}", + ivy"dev.zio::zio-test:${V.zio}", + ivy"dev.zio::zio-test-sbt:${V.zio}", + ivy"dev.zio::zio-streams:${V.zio}", + + // ZIO Extensions + ivy"dev.zio::zio-config:${V.zioConfig}", + ivy"dev.zio::zio-config-typesafe:${V.zioConfig}", + ivy"dev.zio::zio-config-magnolia:${V.zioConfig}", + ivy"dev.zio::zio-json:${V.zioJson}", + ivy"dev.zio::zio-logging:${V.zioLogging}", + ivy"dev.zio::zio-logging-slf4j:${V.zioLogging}", + ivy"dev.zio::zio-prelude:${V.zioPrelude}", + ivy"dev.zio::zio-interop-cats:${V.zioInteropCats}", + ivy"dev.zio::zio-interop-reactivestreams:${V.zioInteropReactiveStreams}", + ivy"dev.zio::zio-nio:${V.zioNIO}", + ivy"dev.zio::zio-cli:${V.zioCli}", + ivy"dev.zio::zio-schema:${V.zioSchema}", + ivy"dev.zio::zio-schema-derivation:${V.zioSchema}", + + // Database + ivy"io.getquill::quill-jdbc-zio:${V.quill}", + ivy"com.augustnagro::magnum:${V.magnum}", + ivy"com.augustnagro::magnumzio:${V.magnum}", + ivy"com.augustnagro::magnumpg:${V.magnum}", + + // HTTP + ivy"com.softwaremill.sttp.tapir::tapir-core:${V.tapir}", + ivy"com.softwaremill.sttp.tapir::tapir-zio:${V.tapir}", + ivy"com.softwaremill.sttp.tapir::tapir-json-zio:${V.tapir}", + + // UI/Frontend + ivy"com.lihaoyi::scalatags:${V.scalatags}", + ivy"com.raquo::laminar:${V.laminar}", + ivy"com.raquo::waypoint:${V.waypoint}", + ivy"be.doeraene::url-dsl:${V.urlDsl}", + + // Testing + ivy"org.scalatest::scalatest:${V.scalaTest}", + ivy"com.lihaoyi::utest:${V.utest}", + + // Logging + ivy"ch.qos.logback:logback-classic:${V.logbackClassic}" + ) + } +} diff --git a/mill-iw-support/core/src/IWMillDeps.scala b/mill-iw-support/core/src/IWMillDeps.scala new file mode 100644 index 0000000..d4cac1c --- /dev/null +++ b/mill-iw-support/core/src/IWMillDeps.scala @@ -0,0 +1,80 @@ +package works.iterative.mill + +import mill.api.Loose + +/** Standardized dependency management for Iterative Works projects using Mill. + * This object provides methods to get dependency strings with proper + * versioning. + * + * All versions are centralized in [[IWMillVersions]]. + * + * This object is designed to be used in build.sc files where the ivy string + * interpolator is available, like: + * + * {{{ + * def ivyDeps = Agg( + * ivy"$${IWMillDeps.zio}", + * ivy"$${IWMillDeps.zioJson}" + * ) + * }}} + */ +object IWMillDeps { + val V = IWMillVersions + + // ZIO Core + val zio = s"dev.zio::zio:${V.zio}" + val zioTest = s"dev.zio::zio-test:${V.zio}" + val zioTestSbt = s"dev.zio::zio-test-sbt:${V.zio}" + val zioStreams = s"dev.zio::zio-streams:${V.zio}" + + // ZIO Extensions + val zioConfig = s"dev.zio::zio-config:${V.zioConfig}" + val zioConfigTypesafe = s"dev.zio::zio-config-typesafe:${V.zioConfig}" + val zioConfigMagnolia = s"dev.zio::zio-config-magnolia:${V.zioConfig}" + val zioJson = s"dev.zio::zio-json:${V.zioJson}" + val zioLogging = s"dev.zio::zio-logging:${V.zioLogging}" + val zioLoggingSlf4j = s"dev.zio::zio-logging-slf4j:${V.zioLogging}" + val zioPrelude = s"dev.zio::zio-prelude:${V.zioPrelude}" + val zioInteropCats = s"dev.zio::zio-interop-cats:${V.zioInteropCats}" + val zioInteropReactiveStreams = + s"dev.zio::zio-interop-reactivestreams:${V.zioInteropReactiveStreams}" + val zioNIO = s"dev.zio::zio-nio:${V.zioNIO}" + val zioCli = s"dev.zio::zio-cli:${V.zioCli}" + val zioSchema = s"dev.zio::zio-schema:${V.zioSchema}" + val zioSchemaDerivation = s"dev.zio::zio-schema-derivation:${V.zioSchema}" + + // Collection of ZIO dependencies + val zioCore = List(zio, zioTest, zioTestSbt) + val zioJson_ = List(zioJson) + val zioAll = zioCore ++ List( + zioStreams, + zioConfig, + zioConfigTypesafe, + zioConfigMagnolia, + zioLogging, + zioPrelude + ) ++ zioJson_ + + // Database + val quill = s"io.getquill::quill-jdbc-zio:${V.quill}" + val magnum = s"com.augustnagro::magnum_3:${V.magnum}" + val magnumZIO = s"com.augustnagro::magnumzio_3:${V.magnum}" + val magnumPG = s"com.augustnagro::magnumpg_3:${V.magnum}" + + // HTTP + val tapirCore = s"com.softwaremill.sttp.tapir::tapir-core:${V.tapir}" + val tapirZIO = s"com.softwaremill.sttp.tapir::tapir-zio:${V.tapir}" + val tapirZIOJson = s"com.softwaremill.sttp.tapir::tapir-json-zio:${V.tapir}" + + // UI/Frontend + val scalatags = s"com.lihaoyi::scalatags:${V.scalatags}" + val laminar = s"com.raquo::laminar:${V.laminar}" + val waypoint = s"com.raquo::waypoint:${V.waypoint}" + val urlDsl = s"be.doeraene::url-dsl:${V.urlDsl}" + + // Testing + val scalaTest = s"org.scalatest::scalatest:${V.scalaTest}" + + // Logging + val logbackClassic = s"ch.qos.logback:logback-classic:${V.logbackClassic}" +} diff --git a/mill-iw-support/core/src/IWMillVersions.scala b/mill-iw-support/core/src/IWMillVersions.scala new file mode 100644 index 0000000..0ad9bd2 --- /dev/null +++ b/mill-iw-support/core/src/IWMillVersions.scala @@ -0,0 +1,56 @@ +package works.iterative.mill + +/** + * Centralized version management for Iterative Works projects using Mill. + * This object contains all library versions used across projects. + * + * Update this object to bump versions for all projects. + */ +object IWMillVersions { + // ZIO Ecosystem + val zio = "2.1.16" + val zioConfig = "4.0.3" + val zioCli = "0.7.0" + val zioInteropCats = "23.1.0.3" + val zioInteropReactiveStreams = "2.0.2" + val zioJson = "0.7.36" + val zioLogging = "2.5.0" + val zioNIO = "2.0.2" + val zioPrelude = "1.0.0-RC39" + val zioQuery = "0.7.6" + val zioSchema = "1.6.3" + + // Akka Ecosystem + val akka = "2.6.18" + val akkaHttp = "10.4.0" + + // Other Libraries + val cats = "2.13.0" + val utest = "0.8.2" + val caliban = "2.9.2" + val chimney = "1.7.3" + val ducktape = "0.2.7" + val elastic4s = "7.12.4" + val http4s = "0.23.30" + val http4sBlaze = "0.23.17" + val http4sPac4J = "5.0.0" + val laminar = "17.2.0" + val laminext = "0.17.0" + val logbackClassic = "1.5.17" + val magnum = "2.0.0-M1" + val pac4j = "6.0.3" + val play = "2.8.18" + val playJson = "2.9.3" + val quill = "4.8.6" + val scalaJsMacroTaskExecutor = "1.1.1" + val scalaJsJavaSecureRandom = "1.0.0" + val scalatags = "0.13.1" + val scalaTest = "3.2.19" + val slick = "3.3.3" + val sttpClient3 = "3.10.3" + val sttpClient4 = "4.0.0-RC1" + val support = "1.7.0" + val tapir = "1.11.16" + val urlDsl = "0.7.0" + val waypoint = "9.0.0" +} \ No newline at end of file diff --git a/mill-iw-support/core/src/IWPublishModule.scala b/mill-iw-support/core/src/IWPublishModule.scala new file mode 100644 index 0000000..5ae0ade --- /dev/null +++ b/mill-iw-support/core/src/IWPublishModule.scala @@ -0,0 +1,22 @@ +package works.iterative.mill + +import mill._ +import mill.scalalib._ +import mill.scalalib.publish._ + +/** Standard publish module trait for Iterative Works projects using Mill. This + * trait sets up standard publishing configuration for IW Maven repositories. + * + * To use this trait, simply extend it in your module and define: + * - publishVersion: The version to publish + * - pomSettings: The POM settings for the publication + * + * Use IW_USERNAME and IW_PASSWORD environment variables for authentication. + */ +trait IWPublishModule extends PublishModule { + + override def sonatypeUri = "https://dig.iterative.works/maven/releases" + override def sonatypeSnapshotUri = + "https://dig.iterative.works/maven/snapshots" + +} diff --git a/mill-iw-support/core/src/IWScalaModule.scala b/mill-iw-support/core/src/IWScalaModule.scala new file mode 100644 index 0000000..021bccc --- /dev/null +++ b/mill-iw-support/core/src/IWScalaModule.scala @@ -0,0 +1,97 @@ +package works.iterative.mill + +import mill._ +import mill.scalalib._ +import mill.scalalib.scalafmt._ + +/** Standard Scala module trait for Iterative Works projects using Mill. This + * trait sets up standard compiler options and configuration. + */ +trait IWScalaModule + extends ScalaModule + with ScalafmtModule + with IWScalaVersions { + + override def scalaVersion = scala3Version + + // Define base compiler options based on Scala version + def baseScalacOptions = T { + if (scalaVersion().startsWith("2.")) { + Seq( + "-encoding", + "utf8", + "-feature", + "-unchecked", + "-language:existentials", + "-language:experimental.macros", + "-language:higherKinds", + "-language:implicitConversions", + "-Xlint:adapted-args", + "-Xlint:constant", + "-Xlint:delayedinit-select", + "-Xlint:deprecation", + "-Xlint:doc-detached", + "-Xlint:implicit-recursion", + "-Xlint:implicit-not-found", + "-Xlint:inaccessible", + "-Xlint:infer-any", + "-Xlint:missing-interpolator", + "-Xlint:nullary-unit", + "-Xlint:option-implicit", + "-Xlint:package-object-classes", + "-Xlint:poly-implicit-overload", + "-Xlint:private-shadow", + "-Xlint:stars-align", + "-Xlint:strict-unsealed-patmat", + "-Xlint:type-parameter-shadow", + "-Xlint:-byname-implicit", + "-Wdead-code", + "-Wextra-implicit", + "-Wnumeric-widen", + "-Wvalue-discard", + "-Wnonunit-statement", + "-Wunused:nowarn", + "-Wunused:implicits", + "-Wunused:explicits", + "-Wunused:imports", + "-Wunused:locals", + "-Wunused:params", + "-Wunused:patvars", + "-Wunused:privates" + ) + } else { + Seq( + "-encoding", + "utf8", + "-deprecation", + "-feature", + "-unchecked", + "-language:experimental.macros", + "-language:higherKinds", + "-language:implicitConversions", + "-Xkind-projector", + "-Wvalue-discard", + "-Wnonunit-statement", + "-Wunused:implicits", + "-Wunused:explicits", + "-Wunused:imports", + "-Wunused:locals", + "-Wunused:params", + "-Wunused:privates" + ) + } + } + + // Enable SemanticDB for tooling + def semanticDbOptions = Seq( + "-Xsemanticdb" + ) + + // Override scalacOptions to include both base options and SemanticDB options + override def scalacOptions = T { + super.scalacOptions() ++ baseScalacOptions() ++ semanticDbOptions + } + + // Base test configuration + trait IWTests extends ScalaTests with ScalafmtModule +} diff --git a/mill-iw-support/core/src/IWScalaVersions.scala b/mill-iw-support/core/src/IWScalaVersions.scala new file mode 100644 index 0000000..aa70cb6 --- /dev/null +++ b/mill-iw-support/core/src/IWScalaVersions.scala @@ -0,0 +1,9 @@ +package works.iterative.mill + +trait IWScalaVersions { + val scala2Version = "2.13.16" + val scala3Version = "3.6.4" + val scala3LTSVersion = "3.3.5" +} + +object IWScalaVersions extends IWScalaVersions diff --git a/mill-iw-support/core/src/package.scala b/mill-iw-support/core/src/package.scala new file mode 100644 index 0000000..1ba4556 --- /dev/null +++ b/mill-iw-support/core/src/package.scala @@ -0,0 +1,6 @@ +package works.iterative + +package object mill { + val IWVersions = IWMillVersions + val IWDeps = IWMillDeps +} diff --git a/mill-iw-support/example/.mill-version b/mill-iw-support/example/.mill-version new file mode 100644 index 0000000..54dbed4 --- /dev/null +++ b/mill-iw-support/example/.mill-version @@ -0,0 +1 @@ +0.12.10 diff --git a/mill-iw-support/example/build.sc b/mill-iw-support/example/build.sc index 5da77ab..cae5a6a 100644 --- a/mill-iw-support/example/build.sc +++ b/mill-iw-support/example/build.sc @@ -1,11 +1,22 @@ import mill._ import mill.scalalib._ +import mill.scalalib.publish._ +import mill.scalalib.scalafmt._ + +// Import our IW Mill Support library import $ivy.`works.iterative::mill-iw-support:0.1.0-SNAPSHOT` import works.iterative.mill._ +// Define a BOM (Bill of Materials) module +object bom extends IWBomModule + +// Example module using the IW Mill support with BOM object example extends IWScalaModule with IWPublishModule { def publishVersion = "0.1.0-SNAPSHOT" - + + // Use the BOM module for dependency management + override def bomModuleDeps = Seq(bom) + def pomSettings = PomSettings( description = "Example Mill Project using IW Mill Support", organization = "com.example", @@ -16,15 +27,25 @@ Developer("developer", "Example Developer", "https://github.com/example") ) ) - - // Use centralized dependency versions and definitions - def ivyDeps = super.ivyDeps() ++ IWMillDeps.useZIO() ++ Agg( - IWMillDeps.zioJson, - IWMillDeps.tapirCore, - IWMillDeps.tapirZIO, - IWMillDeps.tapirZIOJson + + // With BOM, we don't need to specify versions + // They're managed by the BOM module + def ivyDeps = super.ivyDeps() ++ Agg( + ivy"dev.zio::zio", + ivy"dev.zio::zio-json", + ivy"com.softwaremill.sttp.tapir::tapir-core", + ivy"com.softwaremill.sttp.tapir::tapir-zio", + ivy"com.softwaremill.sttp.tapir::tapir-json-zio" ) - - // Define test module extending IWTests - object test extends Tests with IWTests -} \ No newline at end of file + + // Define test module using standard Mill approach + object test extends ScalaTests with TestModule.ZioTest { + // Use the BOM module for dependency management + override def bomModuleDeps = Seq(bom) + + def ivyDeps = super.ivyDeps() ++ Agg( + ivy"dev.zio::zio-test", // Version comes from BOM + ivy"dev.zio::zio-test-sbt" // Version comes from BOM + ) + } +} diff --git a/mill-iw-support/example/example/src/Example.scala b/mill-iw-support/example/example/src/Example.scala new file mode 100644 index 0000000..aa28980 --- /dev/null +++ b/mill-iw-support/example/example/src/Example.scala @@ -0,0 +1,27 @@ +package example + +import zio._ +import zio.json._ + +// Simple example class +case class User(id: String, name: String, email: String) + +object User { + // JSON codecs using ZIO JSON + implicit val encoder: JsonEncoder[User] = DeriveJsonEncoder.gen[User] + implicit val decoder: JsonDecoder[User] = DeriveJsonDecoder.gen[User] +} + +object Example extends ZIOAppDefault { + + val program = for { + _ <- Console.printLine("Starting example application") + user = User("1", "John Doe", "john@example.com") + json <- ZIO.succeed(user.toJson) + _ <- Console.printLine(s"User as JSON: $json") + parsed <- ZIO.fromEither(json.fromJson[User]) + _ <- Console.printLine(s"Parsed user: $parsed") + } yield () + + def run = program +} \ No newline at end of file diff --git a/mill-iw-support/example/example/test/src/ExampleTest.scala b/mill-iw-support/example/example/test/src/ExampleTest.scala new file mode 100644 index 0000000..951c2d8 --- /dev/null +++ b/mill-iw-support/example/example/test/src/ExampleTest.scala @@ -0,0 +1,26 @@ +package example + +import zio._ +import zio.json._ +import zio.test._ +import zio.test.Assertion._ + +object ExampleTest extends ZIOSpecDefault { + override def spec = suite("Example suite")( + test("User JSON serialization") { + val user = User("1", "John Doe", "john@example.com") + val json = user.toJson + val expected = + """{"id":"1","name":"John Doe","email":"john@example.com"}""" + + assertTrue(json == expected) + }, + test("User JSON deserialization") { + val json = """{"id":"1","name":"John Doe","email":"john@example.com"}""" + val parsed = json.fromJson[User] + val expected = User("1", "John Doe", "john@example.com") + + assertTrue(parsed == Right(expected)) + } + ) +} diff --git a/mill-iw-support/example/mill b/mill-iw-support/example/mill new file mode 100755 index 0000000..f349762 --- /dev/null +++ b/mill-iw-support/example/mill @@ -0,0 +1,265 @@ +#!/usr/bin/env sh + +# This is a wrapper script, that automatically download mill from GitHub release pages +# You can give the required mill version with --mill-version parameter +# If no version is given, it falls back to the value of DEFAULT_MILL_VERSION +# +# Original Project page: https://github.com/lefou/millw +# Script Version: 0.4.12 +# +# If you want to improve this script, please also contribute your changes back! +# +# Licensed under the Apache License, Version 2.0 + +set -e + +if [ -z "${DEFAULT_MILL_VERSION}" ] ; then + DEFAULT_MILL_VERSION=0.12.10 +fi + + +if [ -z "${GITHUB_RELEASE_CDN}" ] ; then + GITHUB_RELEASE_CDN="" +fi + + +MILL_REPO_URL="https://github.com/com-lihaoyi/mill" + +if [ -z "${CURL_CMD}" ] ; then + CURL_CMD=curl +fi + +# Explicit commandline argument takes precedence over all other methods +if [ "$1" = "--mill-version" ] ; then + shift + if [ "x$1" != "x" ] ; then + MILL_VERSION="$1" + shift + else + echo "You specified --mill-version without a version." 1>&2 + echo "Please provide a version that matches one provided on" 1>&2 + echo "${MILL_REPO_URL}/releases" 1>&2 + false + fi +fi + +# Please note, that if a MILL_VERSION is already set in the environment, +# We reuse it's value and skip searching for a value. + +# If not already set, read .mill-version file +if [ -z "${MILL_VERSION}" ] ; then + if [ -f ".mill-version" ] ; then + MILL_VERSION="$(tr '\r' '\n' < .mill-version | head -n 1 2> /dev/null)" + elif [ -f ".config/mill-version" ] ; then + MILL_VERSION="$(tr '\r' '\n' < .config/mill-version | head -n 1 2> /dev/null)" + fi +fi + +MILL_USER_CACHE_DIR="${XDG_CACHE_HOME:-${HOME}/.cache}/mill" + +if [ -z "${MILL_DOWNLOAD_PATH}" ] ; then + MILL_DOWNLOAD_PATH="${MILL_USER_CACHE_DIR}/download" +fi + +# If not already set, try to fetch newest from Github +if [ -z "${MILL_VERSION}" ] ; then + # TODO: try to load latest version from release page + echo "No mill version specified." 1>&2 + echo "You should provide a version via '.mill-version' file or --mill-version option." 1>&2 + + mkdir -p "${MILL_DOWNLOAD_PATH}" + LANG=C touch -d '1 hour ago' "${MILL_DOWNLOAD_PATH}/.expire_latest" 2>/dev/null || ( + # we might be on OSX or BSD which don't have -d option for touch + # but probably a -A [-][[hh]mm]SS + touch "${MILL_DOWNLOAD_PATH}/.expire_latest"; touch -A -010000 "${MILL_DOWNLOAD_PATH}/.expire_latest" + ) || ( + # in case we still failed, we retry the first touch command with the intention + # to show the (previously suppressed) error message + LANG=C touch -d '1 hour ago' "${MILL_DOWNLOAD_PATH}/.expire_latest" + ) + + # POSIX shell variant of bash's -nt operator, see https://unix.stackexchange.com/a/449744/6993 + # if [ "${MILL_DOWNLOAD_PATH}/.latest" -nt "${MILL_DOWNLOAD_PATH}/.expire_latest" ] ; then + if [ -n "$(find -L "${MILL_DOWNLOAD_PATH}/.latest" -prune -newer "${MILL_DOWNLOAD_PATH}/.expire_latest")" ]; then + # we know a current latest version + MILL_VERSION=$(head -n 1 "${MILL_DOWNLOAD_PATH}"/.latest 2> /dev/null) + fi + + if [ -z "${MILL_VERSION}" ] ; then + # we don't know a current latest version + echo "Retrieving latest mill version ..." 1>&2 + LANG=C ${CURL_CMD} -s -i -f -I ${MILL_REPO_URL}/releases/latest 2> /dev/null | grep --ignore-case Location: | sed s'/^.*tag\///' | tr -d '\r\n' > "${MILL_DOWNLOAD_PATH}/.latest" + MILL_VERSION=$(head -n 1 "${MILL_DOWNLOAD_PATH}"/.latest 2> /dev/null) + fi + + if [ -z "${MILL_VERSION}" ] ; then + # Last resort + MILL_VERSION="${DEFAULT_MILL_VERSION}" + echo "Falling back to hardcoded mill version ${MILL_VERSION}" 1>&2 + else + echo "Using mill version ${MILL_VERSION}" 1>&2 + fi +fi + +MILL_NATIVE_SUFFIX="-native" +FULL_MILL_VERSION=$MILL_VERSION +ARTIFACT_SUFFIX="" +case "$MILL_VERSION" in + *"$MILL_NATIVE_SUFFIX") + MILL_VERSION=${MILL_VERSION%"$MILL_NATIVE_SUFFIX"} + if [ "$(expr substr $(uname -s) 1 5 2>/dev/null)" = "Linux" ]; then + if [ "$(uname -m)" = "aarch64" ]; then + ARTIFACT_SUFFIX="-native-linux-aarch64" + else + ARTIFACT_SUFFIX="-native-linux-amd64" + fi + elif [ "$(uname)" = "Darwin" ]; then + if [ "$(uname -m)" = "arm64" ]; then + ARTIFACT_SUFFIX="-native-mac-aarch64" + else + ARTIFACT_SUFFIX="-native-mac-amd64" + fi + else + echo "This native mill launcher supports only Linux and macOS." 1>&2 + exit 1 + fi +esac + +MILL="${MILL_DOWNLOAD_PATH}/${FULL_MILL_VERSION}" + +try_to_use_system_mill() { + if [ "$(uname)" != "Linux" ]; then + return 0 + fi + + MILL_IN_PATH="$(command -v mill || true)" + + if [ -z "${MILL_IN_PATH}" ]; then + return 0 + fi + + SYSTEM_MILL_FIRST_TWO_BYTES=$(head --bytes=2 "${MILL_IN_PATH}") + if [ "${SYSTEM_MILL_FIRST_TWO_BYTES}" = "#!" ]; then + # MILL_IN_PATH is (very likely) a shell script and not the mill + # executable, ignore it. + return 0 + fi + + SYSTEM_MILL_PATH=$(readlink -e "${MILL_IN_PATH}") + SYSTEM_MILL_SIZE=$(stat --format=%s "${SYSTEM_MILL_PATH}") + SYSTEM_MILL_MTIME=$(stat --format=%y "${SYSTEM_MILL_PATH}") + + if [ ! -d "${MILL_USER_CACHE_DIR}" ]; then + mkdir -p "${MILL_USER_CACHE_DIR}" + fi + + SYSTEM_MILL_INFO_FILE="${MILL_USER_CACHE_DIR}/system-mill-info" + if [ -f "${SYSTEM_MILL_INFO_FILE}" ]; then + parseSystemMillInfo() { + LINE_NUMBER="${1}" + # Select the line number of the SYSTEM_MILL_INFO_FILE, cut the + # variable definition in that line in two halves and return + # the value, and finally remove the quotes. + sed -n "${LINE_NUMBER}p" "${SYSTEM_MILL_INFO_FILE}" |\ + cut -d= -f2 |\ + sed 's/"\(.*\)"/\1/' + } + + CACHED_SYSTEM_MILL_PATH=$(parseSystemMillInfo 1) + CACHED_SYSTEM_MILL_VERSION=$(parseSystemMillInfo 2) + CACHED_SYSTEM_MILL_SIZE=$(parseSystemMillInfo 3) + CACHED_SYSTEM_MILL_MTIME=$(parseSystemMillInfo 4) + + if [ "${SYSTEM_MILL_PATH}" = "${CACHED_SYSTEM_MILL_PATH}" ] \ + && [ "${SYSTEM_MILL_SIZE}" = "${CACHED_SYSTEM_MILL_SIZE}" ] \ + && [ "${SYSTEM_MILL_MTIME}" = "${CACHED_SYSTEM_MILL_MTIME}" ]; then + if [ "${CACHED_SYSTEM_MILL_VERSION}" = "${MILL_VERSION}" ]; then + MILL="${SYSTEM_MILL_PATH}" + return 0 + else + return 0 + fi + fi + fi + + SYSTEM_MILL_VERSION=$(${SYSTEM_MILL_PATH} --version | head -n1 | sed -n 's/^Mill.*version \(.*\)/\1/p') + + cat < "${SYSTEM_MILL_INFO_FILE}" +CACHED_SYSTEM_MILL_PATH="${SYSTEM_MILL_PATH}" +CACHED_SYSTEM_MILL_VERSION="${SYSTEM_MILL_VERSION}" +CACHED_SYSTEM_MILL_SIZE="${SYSTEM_MILL_SIZE}" +CACHED_SYSTEM_MILL_MTIME="${SYSTEM_MILL_MTIME}" +EOF + + if [ "${SYSTEM_MILL_VERSION}" = "${MILL_VERSION}" ]; then + MILL="${SYSTEM_MILL_PATH}" + fi +} +try_to_use_system_mill + +# If not already downloaded, download it +if [ ! -s "${MILL}" ] ; then + + # support old non-XDG download dir + MILL_OLD_DOWNLOAD_PATH="${HOME}/.mill/download" + OLD_MILL="${MILL_OLD_DOWNLOAD_PATH}/${MILL_VERSION}" + if [ -x "${OLD_MILL}" ] ; then + MILL="${OLD_MILL}" + else + case $MILL_VERSION in + 0.0.* | 0.1.* | 0.2.* | 0.3.* | 0.4.* ) + DOWNLOAD_SUFFIX="" + DOWNLOAD_FROM_MAVEN=0 + ;; + 0.5.* | 0.6.* | 0.7.* | 0.8.* | 0.9.* | 0.10.* | 0.11.0-M* ) + DOWNLOAD_SUFFIX="-assembly" + DOWNLOAD_FROM_MAVEN=0 + ;; + *) + DOWNLOAD_SUFFIX="-assembly" + DOWNLOAD_FROM_MAVEN=1 + ;; + esac + + DOWNLOAD_FILE=$(mktemp mill.XXXXXX) + + if [ "$DOWNLOAD_FROM_MAVEN" = "1" ] ; then + DOWNLOAD_URL="https://repo1.maven.org/maven2/com/lihaoyi/mill-dist${ARTIFACT_SUFFIX}/${MILL_VERSION}/mill-dist${ARTIFACT_SUFFIX}-${MILL_VERSION}.jar" + else + MILL_VERSION_TAG=$(echo "$MILL_VERSION" | sed -E 's/([^-]+)(-M[0-9]+)?(-.*)?/\1\2/') + DOWNLOAD_URL="${GITHUB_RELEASE_CDN}${MILL_REPO_URL}/releases/download/${MILL_VERSION_TAG}/${MILL_VERSION}${DOWNLOAD_SUFFIX}" + unset MILL_VERSION_TAG + fi + + # TODO: handle command not found + echo "Downloading mill ${MILL_VERSION} from ${DOWNLOAD_URL} ..." 1>&2 + ${CURL_CMD} -f -L -o "${DOWNLOAD_FILE}" "${DOWNLOAD_URL}" + chmod +x "${DOWNLOAD_FILE}" + mkdir -p "${MILL_DOWNLOAD_PATH}" + mv "${DOWNLOAD_FILE}" "${MILL}" + + unset DOWNLOAD_FILE + unset DOWNLOAD_SUFFIX + fi +fi + +if [ -z "$MILL_MAIN_CLI" ] ; then + MILL_MAIN_CLI="${0}" +fi + +MILL_FIRST_ARG="" +if [ "$1" = "--bsp" ] || [ "$1" = "-i" ] || [ "$1" = "--interactive" ] || [ "$1" = "--no-server" ] || [ "$1" = "--repl" ] || [ "$1" = "--help" ] ; then + # Need to preserve the first position of those listed options + MILL_FIRST_ARG=$1 + shift +fi + +unset MILL_DOWNLOAD_PATH +unset MILL_OLD_DOWNLOAD_PATH +unset OLD_MILL +unset MILL_VERSION +unset MILL_REPO_URL + +# We don't quote MILL_FIRST_ARG on purpose, so we can expand the empty value without quotes +# shellcheck disable=SC2086 +exec "${MILL}" $MILL_FIRST_ARG -D "mill.main.cli=${MILL_MAIN_CLI}" "$@" \ No newline at end of file diff --git a/mill-iw-support/example/src/Example.scala b/mill-iw-support/example/src/Example.scala deleted file mode 100644 index aa28980..0000000 --- a/mill-iw-support/example/src/Example.scala +++ /dev/null @@ -1,27 +0,0 @@ -package example - -import zio._ -import zio.json._ - -// Simple example class -case class User(id: String, name: String, email: String) - -object User { - // JSON codecs using ZIO JSON - implicit val encoder: JsonEncoder[User] = DeriveJsonEncoder.gen[User] - implicit val decoder: JsonDecoder[User] = DeriveJsonDecoder.gen[User] -} - -object Example extends ZIOAppDefault { - - val program = for { - _ <- Console.printLine("Starting example application") - user = User("1", "John Doe", "john@example.com") - json <- ZIO.succeed(user.toJson) - _ <- Console.printLine(s"User as JSON: $json") - parsed <- ZIO.fromEither(json.fromJson[User]) - _ <- Console.printLine(s"Parsed user: $parsed") - } yield () - - def run = program -} \ No newline at end of file diff --git a/mill-iw-support/example/test/src/ExampleTest.scala b/mill-iw-support/example/test/src/ExampleTest.scala deleted file mode 100644 index 20f0c06..0000000 --- a/mill-iw-support/example/test/src/ExampleTest.scala +++ /dev/null @@ -1,27 +0,0 @@ -package example - -import utest._ -import zio._ -import zio.json._ -import zio.test._ -import zio.test.Assertion._ - -object ExampleTest extends TestSuite { - val tests = Tests { - test("User JSON serialization") { - val user = User("1", "John Doe", "john@example.com") - val json = user.toJson - val expected = """{"id":"1","name":"John Doe","email":"john@example.com"}""" - - assert(json == expected) - } - - test("User JSON deserialization") { - val json = """{"id":"1","name":"John Doe","email":"john@example.com"}""" - val parsed = json.fromJson[User] - val expected = User("1", "John Doe", "john@example.com") - - assert(parsed == Right(expected)) - } - } -} \ No newline at end of file diff --git a/mill-iw-support/mill b/mill-iw-support/mill new file mode 100755 index 0000000..f349762 --- /dev/null +++ b/mill-iw-support/mill @@ -0,0 +1,265 @@ +#!/usr/bin/env sh + +# This is a wrapper script, that automatically download mill from GitHub release pages +# You can give the required mill version with --mill-version parameter +# If no version is given, it falls back to the value of DEFAULT_MILL_VERSION +# +# Original Project page: https://github.com/lefou/millw +# Script Version: 0.4.12 +# +# If you want to improve this script, please also contribute your changes back! +# +# Licensed under the Apache License, Version 2.0 + +set -e + +if [ -z "${DEFAULT_MILL_VERSION}" ] ; then + DEFAULT_MILL_VERSION=0.12.10 +fi + + +if [ -z "${GITHUB_RELEASE_CDN}" ] ; then + GITHUB_RELEASE_CDN="" +fi + + +MILL_REPO_URL="https://github.com/com-lihaoyi/mill" + +if [ -z "${CURL_CMD}" ] ; then + CURL_CMD=curl +fi + +# Explicit commandline argument takes precedence over all other methods +if [ "$1" = "--mill-version" ] ; then + shift + if [ "x$1" != "x" ] ; then + MILL_VERSION="$1" + shift + else + echo "You specified --mill-version without a version." 1>&2 + echo "Please provide a version that matches one provided on" 1>&2 + echo "${MILL_REPO_URL}/releases" 1>&2 + false + fi +fi + +# Please note, that if a MILL_VERSION is already set in the environment, +# We reuse it's value and skip searching for a value. + +# If not already set, read .mill-version file +if [ -z "${MILL_VERSION}" ] ; then + if [ -f ".mill-version" ] ; then + MILL_VERSION="$(tr '\r' '\n' < .mill-version | head -n 1 2> /dev/null)" + elif [ -f ".config/mill-version" ] ; then + MILL_VERSION="$(tr '\r' '\n' < .config/mill-version | head -n 1 2> /dev/null)" + fi +fi + +MILL_USER_CACHE_DIR="${XDG_CACHE_HOME:-${HOME}/.cache}/mill" + +if [ -z "${MILL_DOWNLOAD_PATH}" ] ; then + MILL_DOWNLOAD_PATH="${MILL_USER_CACHE_DIR}/download" +fi + +# If not already set, try to fetch newest from Github +if [ -z "${MILL_VERSION}" ] ; then + # TODO: try to load latest version from release page + echo "No mill version specified." 1>&2 + echo "You should provide a version via '.mill-version' file or --mill-version option." 1>&2 + + mkdir -p "${MILL_DOWNLOAD_PATH}" + LANG=C touch -d '1 hour ago' "${MILL_DOWNLOAD_PATH}/.expire_latest" 2>/dev/null || ( + # we might be on OSX or BSD which don't have -d option for touch + # but probably a -A [-][[hh]mm]SS + touch "${MILL_DOWNLOAD_PATH}/.expire_latest"; touch -A -010000 "${MILL_DOWNLOAD_PATH}/.expire_latest" + ) || ( + # in case we still failed, we retry the first touch command with the intention + # to show the (previously suppressed) error message + LANG=C touch -d '1 hour ago' "${MILL_DOWNLOAD_PATH}/.expire_latest" + ) + + # POSIX shell variant of bash's -nt operator, see https://unix.stackexchange.com/a/449744/6993 + # if [ "${MILL_DOWNLOAD_PATH}/.latest" -nt "${MILL_DOWNLOAD_PATH}/.expire_latest" ] ; then + if [ -n "$(find -L "${MILL_DOWNLOAD_PATH}/.latest" -prune -newer "${MILL_DOWNLOAD_PATH}/.expire_latest")" ]; then + # we know a current latest version + MILL_VERSION=$(head -n 1 "${MILL_DOWNLOAD_PATH}"/.latest 2> /dev/null) + fi + + if [ -z "${MILL_VERSION}" ] ; then + # we don't know a current latest version + echo "Retrieving latest mill version ..." 1>&2 + LANG=C ${CURL_CMD} -s -i -f -I ${MILL_REPO_URL}/releases/latest 2> /dev/null | grep --ignore-case Location: | sed s'/^.*tag\///' | tr -d '\r\n' > "${MILL_DOWNLOAD_PATH}/.latest" + MILL_VERSION=$(head -n 1 "${MILL_DOWNLOAD_PATH}"/.latest 2> /dev/null) + fi + + if [ -z "${MILL_VERSION}" ] ; then + # Last resort + MILL_VERSION="${DEFAULT_MILL_VERSION}" + echo "Falling back to hardcoded mill version ${MILL_VERSION}" 1>&2 + else + echo "Using mill version ${MILL_VERSION}" 1>&2 + fi +fi + +MILL_NATIVE_SUFFIX="-native" +FULL_MILL_VERSION=$MILL_VERSION +ARTIFACT_SUFFIX="" +case "$MILL_VERSION" in + *"$MILL_NATIVE_SUFFIX") + MILL_VERSION=${MILL_VERSION%"$MILL_NATIVE_SUFFIX"} + if [ "$(expr substr $(uname -s) 1 5 2>/dev/null)" = "Linux" ]; then + if [ "$(uname -m)" = "aarch64" ]; then + ARTIFACT_SUFFIX="-native-linux-aarch64" + else + ARTIFACT_SUFFIX="-native-linux-amd64" + fi + elif [ "$(uname)" = "Darwin" ]; then + if [ "$(uname -m)" = "arm64" ]; then + ARTIFACT_SUFFIX="-native-mac-aarch64" + else + ARTIFACT_SUFFIX="-native-mac-amd64" + fi + else + echo "This native mill launcher supports only Linux and macOS." 1>&2 + exit 1 + fi +esac + +MILL="${MILL_DOWNLOAD_PATH}/${FULL_MILL_VERSION}" + +try_to_use_system_mill() { + if [ "$(uname)" != "Linux" ]; then + return 0 + fi + + MILL_IN_PATH="$(command -v mill || true)" + + if [ -z "${MILL_IN_PATH}" ]; then + return 0 + fi + + SYSTEM_MILL_FIRST_TWO_BYTES=$(head --bytes=2 "${MILL_IN_PATH}") + if [ "${SYSTEM_MILL_FIRST_TWO_BYTES}" = "#!" ]; then + # MILL_IN_PATH is (very likely) a shell script and not the mill + # executable, ignore it. + return 0 + fi + + SYSTEM_MILL_PATH=$(readlink -e "${MILL_IN_PATH}") + SYSTEM_MILL_SIZE=$(stat --format=%s "${SYSTEM_MILL_PATH}") + SYSTEM_MILL_MTIME=$(stat --format=%y "${SYSTEM_MILL_PATH}") + + if [ ! -d "${MILL_USER_CACHE_DIR}" ]; then + mkdir -p "${MILL_USER_CACHE_DIR}" + fi + + SYSTEM_MILL_INFO_FILE="${MILL_USER_CACHE_DIR}/system-mill-info" + if [ -f "${SYSTEM_MILL_INFO_FILE}" ]; then + parseSystemMillInfo() { + LINE_NUMBER="${1}" + # Select the line number of the SYSTEM_MILL_INFO_FILE, cut the + # variable definition in that line in two halves and return + # the value, and finally remove the quotes. + sed -n "${LINE_NUMBER}p" "${SYSTEM_MILL_INFO_FILE}" |\ + cut -d= -f2 |\ + sed 's/"\(.*\)"/\1/' + } + + CACHED_SYSTEM_MILL_PATH=$(parseSystemMillInfo 1) + CACHED_SYSTEM_MILL_VERSION=$(parseSystemMillInfo 2) + CACHED_SYSTEM_MILL_SIZE=$(parseSystemMillInfo 3) + CACHED_SYSTEM_MILL_MTIME=$(parseSystemMillInfo 4) + + if [ "${SYSTEM_MILL_PATH}" = "${CACHED_SYSTEM_MILL_PATH}" ] \ + && [ "${SYSTEM_MILL_SIZE}" = "${CACHED_SYSTEM_MILL_SIZE}" ] \ + && [ "${SYSTEM_MILL_MTIME}" = "${CACHED_SYSTEM_MILL_MTIME}" ]; then + if [ "${CACHED_SYSTEM_MILL_VERSION}" = "${MILL_VERSION}" ]; then + MILL="${SYSTEM_MILL_PATH}" + return 0 + else + return 0 + fi + fi + fi + + SYSTEM_MILL_VERSION=$(${SYSTEM_MILL_PATH} --version | head -n1 | sed -n 's/^Mill.*version \(.*\)/\1/p') + + cat < "${SYSTEM_MILL_INFO_FILE}" +CACHED_SYSTEM_MILL_PATH="${SYSTEM_MILL_PATH}" +CACHED_SYSTEM_MILL_VERSION="${SYSTEM_MILL_VERSION}" +CACHED_SYSTEM_MILL_SIZE="${SYSTEM_MILL_SIZE}" +CACHED_SYSTEM_MILL_MTIME="${SYSTEM_MILL_MTIME}" +EOF + + if [ "${SYSTEM_MILL_VERSION}" = "${MILL_VERSION}" ]; then + MILL="${SYSTEM_MILL_PATH}" + fi +} +try_to_use_system_mill + +# If not already downloaded, download it +if [ ! -s "${MILL}" ] ; then + + # support old non-XDG download dir + MILL_OLD_DOWNLOAD_PATH="${HOME}/.mill/download" + OLD_MILL="${MILL_OLD_DOWNLOAD_PATH}/${MILL_VERSION}" + if [ -x "${OLD_MILL}" ] ; then + MILL="${OLD_MILL}" + else + case $MILL_VERSION in + 0.0.* | 0.1.* | 0.2.* | 0.3.* | 0.4.* ) + DOWNLOAD_SUFFIX="" + DOWNLOAD_FROM_MAVEN=0 + ;; + 0.5.* | 0.6.* | 0.7.* | 0.8.* | 0.9.* | 0.10.* | 0.11.0-M* ) + DOWNLOAD_SUFFIX="-assembly" + DOWNLOAD_FROM_MAVEN=0 + ;; + *) + DOWNLOAD_SUFFIX="-assembly" + DOWNLOAD_FROM_MAVEN=1 + ;; + esac + + DOWNLOAD_FILE=$(mktemp mill.XXXXXX) + + if [ "$DOWNLOAD_FROM_MAVEN" = "1" ] ; then + DOWNLOAD_URL="https://repo1.maven.org/maven2/com/lihaoyi/mill-dist${ARTIFACT_SUFFIX}/${MILL_VERSION}/mill-dist${ARTIFACT_SUFFIX}-${MILL_VERSION}.jar" + else + MILL_VERSION_TAG=$(echo "$MILL_VERSION" | sed -E 's/([^-]+)(-M[0-9]+)?(-.*)?/\1\2/') + DOWNLOAD_URL="${GITHUB_RELEASE_CDN}${MILL_REPO_URL}/releases/download/${MILL_VERSION_TAG}/${MILL_VERSION}${DOWNLOAD_SUFFIX}" + unset MILL_VERSION_TAG + fi + + # TODO: handle command not found + echo "Downloading mill ${MILL_VERSION} from ${DOWNLOAD_URL} ..." 1>&2 + ${CURL_CMD} -f -L -o "${DOWNLOAD_FILE}" "${DOWNLOAD_URL}" + chmod +x "${DOWNLOAD_FILE}" + mkdir -p "${MILL_DOWNLOAD_PATH}" + mv "${DOWNLOAD_FILE}" "${MILL}" + + unset DOWNLOAD_FILE + unset DOWNLOAD_SUFFIX + fi +fi + +if [ -z "$MILL_MAIN_CLI" ] ; then + MILL_MAIN_CLI="${0}" +fi + +MILL_FIRST_ARG="" +if [ "$1" = "--bsp" ] || [ "$1" = "-i" ] || [ "$1" = "--interactive" ] || [ "$1" = "--no-server" ] || [ "$1" = "--repl" ] || [ "$1" = "--help" ] ; then + # Need to preserve the first position of those listed options + MILL_FIRST_ARG=$1 + shift +fi + +unset MILL_DOWNLOAD_PATH +unset MILL_OLD_DOWNLOAD_PATH +unset OLD_MILL +unset MILL_VERSION +unset MILL_REPO_URL + +# We don't quote MILL_FIRST_ARG on purpose, so we can expand the empty value without quotes +# shellcheck disable=SC2086 +exec "${MILL}" $MILL_FIRST_ARG -D "mill.main.cli=${MILL_MAIN_CLI}" "$@" \ No newline at end of file diff --git a/mill-iw-support/src/main/scala/works/iterative/mill/IWMillDeps.scala b/mill-iw-support/src/main/scala/works/iterative/mill/IWMillDeps.scala deleted file mode 100644 index e21af78..0000000 --- a/mill-iw-support/src/main/scala/works/iterative/mill/IWMillDeps.scala +++ /dev/null @@ -1,78 +0,0 @@ -package works.iterative.mill - -import mill.api.Loose -import mill.scalalib.Dep - -/** - * Standardized dependency management for Iterative Works projects using Mill. - * This object provides methods to get dependencies with proper versioning. - * - * All versions are centralized in [[IWMillVersions]]. - */ -object IWMillDeps { - import IWMillVersions as V - - // ZIO Core - def zio = ivy"dev.zio::zio:${V.zio}" - def zioTest = ivy"dev.zio::zio-test:${V.zio}" - def zioTestSbt = ivy"dev.zio::zio-test-sbt:${V.zio}" - def zioStreams = ivy"dev.zio::zio-streams:${V.zio}" - - // ZIO Extensions - def zioConfig = ivy"dev.zio::zio-config:${V.zioConfig}" - def zioConfigTypesafe = ivy"dev.zio::zio-config-typesafe:${V.zioConfig}" - def zioConfigMagnolia = ivy"dev.zio::zio-config-magnolia:${V.zioConfig}" - def zioJson = ivy"dev.zio::zio-json:${V.zioJson}" - def zioLogging = ivy"dev.zio::zio-logging:${V.zioLogging}" - def zioLoggingSlf4j = ivy"dev.zio::zio-logging-slf4j:${V.zioLogging}" - def zioPrelude = ivy"dev.zio::zio-prelude:${V.zioPrelude}" - def zioInteropCats = ivy"dev.zio::zio-interop-cats:${V.zioInteropCats}" - def zioInteropReactiveStreams = ivy"dev.zio::zio-interop-reactivestreams:${V.zioInteropReactiveStreams}" - def zioNIO = ivy"dev.zio::zio-nio:${V.zioNIO}" - def zioCli = ivy"dev.zio::zio-cli:${V.zioCli}" - def zioSchema = ivy"dev.zio::zio-schema:${V.zioSchema}" - def zioSchemaDerivation = ivy"dev.zio::zio-schema-derivation:${V.zioSchema}" - - // Helper methods for ZIO - def useZIO(): Loose.Agg[Dep] = Loose.Agg( - zio, - zioTest, - zioTestSbt - ) - - def useZIOJson: Loose.Agg[Dep] = Loose.Agg( - zioJson - ) - - def useZIOAll(): Loose.Agg[Dep] = useZIO() ++ Loose.Agg( - zioStreams, - zioConfig, - zioConfigTypesafe, - zioConfigMagnolia, - zioLogging, - zioPrelude - ) ++ useZIOJson - - // Database - def quill = ivy"io.getquill::quill-jdbc-zio:${V.quill}" - def magnum = ivy"com.augustnagro::magnum_3:${V.magnum}" - def magnumZIO = ivy"com.augustnagro::magnumzio_3:${V.magnum}" - def magnumPG = ivy"com.augustnagro::magnumpg_3:${V.magnum}" - - // HTTP - def tapirCore = ivy"com.softwaremill.sttp.tapir::tapir-core:${V.tapir}" - def tapirZIO = ivy"com.softwaremill.sttp.tapir::tapir-zio:${V.tapir}" - def tapirZIOJson = ivy"com.softwaremill.sttp.tapir::tapir-json-zio:${V.tapir}" - - // UI/Frontend - def scalatags = ivy"com.lihaoyi::scalatags:${V.scalatags}" - def laminar = ivy"com.raquo::laminar:${V.laminar}" - def waypoint = ivy"com.raquo::waypoint:${V.waypoint}" - def urlDsl = ivy"be.doeraene::url-dsl:${V.urlDsl}" - - // Testing - def scalaTest = ivy"org.scalatest::scalatest:${V.scalaTest}" - - // Logging - def logbackClassic = ivy"ch.qos.logback:logback-classic:${V.logbackClassic}" -} \ No newline at end of file diff --git a/mill-iw-support/src/main/scala/works/iterative/mill/IWMillVersions.scala b/mill-iw-support/src/main/scala/works/iterative/mill/IWMillVersions.scala deleted file mode 100644 index 4e3150a..0000000 --- a/mill-iw-support/src/main/scala/works/iterative/mill/IWMillVersions.scala +++ /dev/null @@ -1,55 +0,0 @@ -package works.iterative.mill - -/** - * Centralized version management for Iterative Works projects using Mill. - * This object contains all library versions used across projects. - * - * Update this object to bump versions for all projects. - */ -object IWMillVersions { - // ZIO Ecosystem - val zio = "2.1.16" - val zioConfig = "4.0.3" - val zioCli = "0.7.0" - val zioInteropCats = "23.1.0.3" - val zioInteropReactiveStreams = "2.0.2" - val zioJson = "0.7.36" - val zioLogging = "2.5.0" - val zioNIO = "2.0.2" - val zioPrelude = "1.0.0-RC39" - val zioQuery = "0.7.6" - val zioSchema = "1.6.3" - - // Akka Ecosystem - val akka = "2.6.18" - val akkaHttp = "10.4.0" - - // Other Libraries - val cats = "2.13.0" - val caliban = "2.9.2" - val chimney = "1.7.3" - val ducktape = "0.2.7" - val elastic4s = "7.12.4" - val http4s = "0.23.30" - val http4sBlaze = "0.23.17" - val http4sPac4J = "5.0.0" - val laminar = "17.2.0" - val laminext = "0.17.0" - val logbackClassic = "1.5.17" - val magnum = "2.0.0-M1" - val pac4j = "6.0.3" - val play = "2.8.18" - val playJson = "2.9.3" - val quill = "4.8.6" - val scalaJsMacroTaskExecutor = "1.1.1" - val scalaJsJavaSecureRandom = "1.0.0" - val scalatags = "0.13.1" - val scalaTest = "3.2.19" - val slick = "3.3.3" - val sttpClient3 = "3.10.3" - val sttpClient4 = "4.0.0-RC1" - val support = "1.7.0" - val tapir = "1.11.16" - val urlDsl = "0.7.0" - val waypoint = "9.0.0" -} \ No newline at end of file diff --git a/mill-iw-support/.mill-version b/mill-iw-support/.mill-version new file mode 100644 index 0000000..54dbed4 --- /dev/null +++ b/mill-iw-support/.mill-version @@ -0,0 +1 @@ +0.12.10 diff --git a/mill-iw-support/.scalafmt.conf b/mill-iw-support/.scalafmt.conf new file mode 100644 index 0000000..ee7753a --- /dev/null +++ b/mill-iw-support/.scalafmt.conf @@ -0,0 +1,2 @@ +version = "3.7.15" +runner.dialect = scala213 \ No newline at end of file diff --git a/mill-iw-support/README.md b/mill-iw-support/README.md index 4986e5c..9bb1ba8 100644 --- a/mill-iw-support/README.md +++ b/mill-iw-support/README.md @@ -28,8 +28,45 @@ IWMillDeps.tapirCore // ivy"com.softwaremill.sttp.tapir::tapir-core:1.11.16" // Use dependency groups -IWMillDeps.useZIO() // Core ZIO dependencies with test framework -IWMillDeps.useZIOAll() // Comprehensive ZIO stack +IWMillDeps.zioCore // List of core ZIO dependencies +IWMillDeps.zioAll // List of all ZIO dependencies +``` + +### IWBomModule + +Bill of Materials (BOM) module for centralized dependency management: + +```scala +import mill._ +import works.iterative.mill._ + +// Define a BOM module +object bom extends IWBomModule { + def publishVersion = "0.1.0-SNAPSHOT" + + def pomSettings = PomSettings( + // Standard POM settings + ) + + // Override with custom dependencies if needed + override def depManagement = T { + super.depManagement() ++ Agg( + ivy"com.example::custom-lib:1.0.0" + ) + } +} + +// Use the BOM in modules +object myModule extends IWScalaModule { + // Reference the BOM module + override def bomModuleDeps = Seq(bom) + + // No need to specify versions - they come from the BOM + def ivyDeps = super.ivyDeps() ++ Agg( + ivy"dev.zio::zio", + ivy"dev.zio::zio-json" + ) +} ``` ### IWScalaModule @@ -44,10 +81,18 @@ // Use default Scala version (3.6.3) // and standard compiler options with SemanticDB enabled - def ivyDeps = super.ivyDeps() ++ IWMillDeps.useZIO() + def ivyDeps = super.ivyDeps() ++ Agg( + ivy"${IWMillDeps.zio}", + ivy"${IWMillDeps.zioJson}" + ) // Standard test module configuration - object test extends Tests with IWTests + object test extends ScalaTests with TestModule.ZioTest { + def ivyDeps = super.ivyDeps() ++ Agg( + ivy"${IWMillDeps.zioTest}", + ivy"${IWMillDeps.zioTestSbt}" + ) + } } ``` @@ -96,6 +141,51 @@ } ``` +## Dependency Management Approaches + +This library provides two approaches for dependency management: + +### 1. IWMillDeps (String Constants) + +Use the IWMillDeps object to access dependency strings with correct versions: + +```scala +def ivyDeps = super.ivyDeps() ++ Agg( + ivy"${IWMillDeps.zio}", + ivy"${IWMillDeps.zioJson}" +) +``` + +### 2. BOM (Bill of Materials) + +Use the IWBomModule to centralize dependency versions: + +```scala +// Define a BOM module +object bom extends IWBomModule { + def publishVersion = "0.1.0-SNAPSHOT" + def pomSettings = PomSettings(...) +} + +// Use the BOM in modules +object myModule extends IWScalaModule { + override def bomModuleDeps = Seq(bom) + + // No need to specify versions + def ivyDeps = super.ivyDeps() ++ Agg( + ivy"dev.zio::zio", + ivy"dev.zio::zio-json" + ) +} +``` + +The BOM approach offers these advantages: +- Enforces consistent versions across modules +- Simplifies version updates +- Controls transitive dependency versions +- Makes build files cleaner (no version strings) +- Can be published as a separate artifact + ## Examples See the `example` directory for a complete working example. diff --git a/mill-iw-support/build.sc b/mill-iw-support/build.sc index 5df80ae..2ed832c 100644 --- a/mill-iw-support/build.sc +++ b/mill-iw-support/build.sc @@ -2,20 +2,33 @@ import mill.scalalib._ import mill.scalalib.publish._ import mill.scalalib.scalafmt._ +import mill.scalalib.TestModule.Utest -object `mill-iw-support` extends Cross[IWMillSupportModule]("2.13.16", "3.6.3") +/** Main module for the mill-iw-support library. Built for Scala 2.13 which is + * compatible with Mill. + */ +object core extends ScalaModule with ScalafmtModule with PublishModule { + val millVersion = "0.12.10" -class IWMillSupportModule(val crossScalaVersion: String) extends CrossScalaModule with ScalafmtModule with PublishModule { + def scalaVersion = "2.13.16" + + def artifactName = "mill-iw-support" + def publishVersion = "0.1.0-SNAPSHOT" def pomSettings = PomSettings( description = "Iterative Works Mill Support Library", organization = "works.iterative", - url = "https://github.com/iterative-works/mill-iw-support", + url = "https://github.com/iterative-works/iw-project-support", licenses = Seq(License.MIT), - versionControl = VersionControl.github("iterative-works", "mill-iw-support"), + versionControl = + VersionControl.github("iterative-works", "iw-project-support"), developers = Seq( - Developer("mph", "Michal Přihoda", "https://github.com/iterative-works") + Developer( + "mprihoda", + "Michal Příhoda", + "https://github.com/iterative-works" + ) ) ) @@ -23,10 +36,15 @@ ivy"com.lihaoyi::os-lib:0.9.3" ) - object test extends Tests with ScalafmtModule { + // Mill's own dependencies needed for compilation + def compileIvyDeps = Agg( + ivy"com.lihaoyi::mill-main:${millVersion}", + ivy"com.lihaoyi::mill-scalalib:${millVersion}" + ) + + object test extends ScalaTests with Utest with ScalafmtModule { def ivyDeps = Agg( ivy"com.lihaoyi::utest:0.8.2" ) - def testFramework = "utest.runner.Framework" } -} \ No newline at end of file +} diff --git a/mill-iw-support/core/src/IWBomModule.scala b/mill-iw-support/core/src/IWBomModule.scala new file mode 100644 index 0000000..d8e70bb --- /dev/null +++ b/mill-iw-support/core/src/IWBomModule.scala @@ -0,0 +1,97 @@ +package works.iterative.mill + +import mill._ +import mill.scalalib._ +import mill.scalalib.publish._ + +/** Standard Bill of Materials (BOM) module for Iterative Works projects using + * Mill. This module centralizes dependency versioning across projects. + * + * A BOM module allows: + * 1. Centralized version management for dependencies 2. Enforcing specific + * versions of transitive dependencies 3. Applying uniform dependency + * management across multiple modules 4. Publishing a BOM artifact that + * other projects can consume + * + * Usage: + * 1. Create a BOM module in your build: + * ```scala + * object bom extends IWBomModule { + * def publishVersion = "0.1.0-SNAPSHOT" + * + * // Add your organization-specific customizations here + * def pomSettings = PomSettings(...) + * } + * ``` + * + * 2. Use the BOM in your modules: + * ```scala + * object myModule extends IWScalaModule { + * def bomModuleDeps = Seq(bom) + * + * def ivyDeps = Agg( + * // No need to specify versions as they come from the BOM + * ivy"dev.zio::zio", + * ivy"dev.zio::zio-json" + * ) + * } + * ``` + */ +trait IWBomModule extends BomModule with ScalaModule with IWScalaVersions { + + override def scalaVersion: T[String] = scala3Version + + /** The default implementation includes all standard IW dependencies from + * IWMillVersions and IWMillDeps. + */ + override def depManagement = T { + val V = IWMillVersions + + Agg( + // ZIO Core + ivy"dev.zio::zio:${V.zio}", + ivy"dev.zio::zio-test:${V.zio}", + ivy"dev.zio::zio-test-sbt:${V.zio}", + ivy"dev.zio::zio-streams:${V.zio}", + + // ZIO Extensions + ivy"dev.zio::zio-config:${V.zioConfig}", + ivy"dev.zio::zio-config-typesafe:${V.zioConfig}", + ivy"dev.zio::zio-config-magnolia:${V.zioConfig}", + ivy"dev.zio::zio-json:${V.zioJson}", + ivy"dev.zio::zio-logging:${V.zioLogging}", + ivy"dev.zio::zio-logging-slf4j:${V.zioLogging}", + ivy"dev.zio::zio-prelude:${V.zioPrelude}", + ivy"dev.zio::zio-interop-cats:${V.zioInteropCats}", + ivy"dev.zio::zio-interop-reactivestreams:${V.zioInteropReactiveStreams}", + ivy"dev.zio::zio-nio:${V.zioNIO}", + ivy"dev.zio::zio-cli:${V.zioCli}", + ivy"dev.zio::zio-schema:${V.zioSchema}", + ivy"dev.zio::zio-schema-derivation:${V.zioSchema}", + + // Database + ivy"io.getquill::quill-jdbc-zio:${V.quill}", + ivy"com.augustnagro::magnum:${V.magnum}", + ivy"com.augustnagro::magnumzio:${V.magnum}", + ivy"com.augustnagro::magnumpg:${V.magnum}", + + // HTTP + ivy"com.softwaremill.sttp.tapir::tapir-core:${V.tapir}", + ivy"com.softwaremill.sttp.tapir::tapir-zio:${V.tapir}", + ivy"com.softwaremill.sttp.tapir::tapir-json-zio:${V.tapir}", + + // UI/Frontend + ivy"com.lihaoyi::scalatags:${V.scalatags}", + ivy"com.raquo::laminar:${V.laminar}", + ivy"com.raquo::waypoint:${V.waypoint}", + ivy"be.doeraene::url-dsl:${V.urlDsl}", + + // Testing + ivy"org.scalatest::scalatest:${V.scalaTest}", + ivy"com.lihaoyi::utest:${V.utest}", + + // Logging + ivy"ch.qos.logback:logback-classic:${V.logbackClassic}" + ) + } +} diff --git a/mill-iw-support/core/src/IWMillDeps.scala b/mill-iw-support/core/src/IWMillDeps.scala new file mode 100644 index 0000000..d4cac1c --- /dev/null +++ b/mill-iw-support/core/src/IWMillDeps.scala @@ -0,0 +1,80 @@ +package works.iterative.mill + +import mill.api.Loose + +/** Standardized dependency management for Iterative Works projects using Mill. + * This object provides methods to get dependency strings with proper + * versioning. + * + * All versions are centralized in [[IWMillVersions]]. + * + * This object is designed to be used in build.sc files where the ivy string + * interpolator is available, like: + * + * {{{ + * def ivyDeps = Agg( + * ivy"$${IWMillDeps.zio}", + * ivy"$${IWMillDeps.zioJson}" + * ) + * }}} + */ +object IWMillDeps { + val V = IWMillVersions + + // ZIO Core + val zio = s"dev.zio::zio:${V.zio}" + val zioTest = s"dev.zio::zio-test:${V.zio}" + val zioTestSbt = s"dev.zio::zio-test-sbt:${V.zio}" + val zioStreams = s"dev.zio::zio-streams:${V.zio}" + + // ZIO Extensions + val zioConfig = s"dev.zio::zio-config:${V.zioConfig}" + val zioConfigTypesafe = s"dev.zio::zio-config-typesafe:${V.zioConfig}" + val zioConfigMagnolia = s"dev.zio::zio-config-magnolia:${V.zioConfig}" + val zioJson = s"dev.zio::zio-json:${V.zioJson}" + val zioLogging = s"dev.zio::zio-logging:${V.zioLogging}" + val zioLoggingSlf4j = s"dev.zio::zio-logging-slf4j:${V.zioLogging}" + val zioPrelude = s"dev.zio::zio-prelude:${V.zioPrelude}" + val zioInteropCats = s"dev.zio::zio-interop-cats:${V.zioInteropCats}" + val zioInteropReactiveStreams = + s"dev.zio::zio-interop-reactivestreams:${V.zioInteropReactiveStreams}" + val zioNIO = s"dev.zio::zio-nio:${V.zioNIO}" + val zioCli = s"dev.zio::zio-cli:${V.zioCli}" + val zioSchema = s"dev.zio::zio-schema:${V.zioSchema}" + val zioSchemaDerivation = s"dev.zio::zio-schema-derivation:${V.zioSchema}" + + // Collection of ZIO dependencies + val zioCore = List(zio, zioTest, zioTestSbt) + val zioJson_ = List(zioJson) + val zioAll = zioCore ++ List( + zioStreams, + zioConfig, + zioConfigTypesafe, + zioConfigMagnolia, + zioLogging, + zioPrelude + ) ++ zioJson_ + + // Database + val quill = s"io.getquill::quill-jdbc-zio:${V.quill}" + val magnum = s"com.augustnagro::magnum_3:${V.magnum}" + val magnumZIO = s"com.augustnagro::magnumzio_3:${V.magnum}" + val magnumPG = s"com.augustnagro::magnumpg_3:${V.magnum}" + + // HTTP + val tapirCore = s"com.softwaremill.sttp.tapir::tapir-core:${V.tapir}" + val tapirZIO = s"com.softwaremill.sttp.tapir::tapir-zio:${V.tapir}" + val tapirZIOJson = s"com.softwaremill.sttp.tapir::tapir-json-zio:${V.tapir}" + + // UI/Frontend + val scalatags = s"com.lihaoyi::scalatags:${V.scalatags}" + val laminar = s"com.raquo::laminar:${V.laminar}" + val waypoint = s"com.raquo::waypoint:${V.waypoint}" + val urlDsl = s"be.doeraene::url-dsl:${V.urlDsl}" + + // Testing + val scalaTest = s"org.scalatest::scalatest:${V.scalaTest}" + + // Logging + val logbackClassic = s"ch.qos.logback:logback-classic:${V.logbackClassic}" +} diff --git a/mill-iw-support/core/src/IWMillVersions.scala b/mill-iw-support/core/src/IWMillVersions.scala new file mode 100644 index 0000000..0ad9bd2 --- /dev/null +++ b/mill-iw-support/core/src/IWMillVersions.scala @@ -0,0 +1,56 @@ +package works.iterative.mill + +/** + * Centralized version management for Iterative Works projects using Mill. + * This object contains all library versions used across projects. + * + * Update this object to bump versions for all projects. + */ +object IWMillVersions { + // ZIO Ecosystem + val zio = "2.1.16" + val zioConfig = "4.0.3" + val zioCli = "0.7.0" + val zioInteropCats = "23.1.0.3" + val zioInteropReactiveStreams = "2.0.2" + val zioJson = "0.7.36" + val zioLogging = "2.5.0" + val zioNIO = "2.0.2" + val zioPrelude = "1.0.0-RC39" + val zioQuery = "0.7.6" + val zioSchema = "1.6.3" + + // Akka Ecosystem + val akka = "2.6.18" + val akkaHttp = "10.4.0" + + // Other Libraries + val cats = "2.13.0" + val utest = "0.8.2" + val caliban = "2.9.2" + val chimney = "1.7.3" + val ducktape = "0.2.7" + val elastic4s = "7.12.4" + val http4s = "0.23.30" + val http4sBlaze = "0.23.17" + val http4sPac4J = "5.0.0" + val laminar = "17.2.0" + val laminext = "0.17.0" + val logbackClassic = "1.5.17" + val magnum = "2.0.0-M1" + val pac4j = "6.0.3" + val play = "2.8.18" + val playJson = "2.9.3" + val quill = "4.8.6" + val scalaJsMacroTaskExecutor = "1.1.1" + val scalaJsJavaSecureRandom = "1.0.0" + val scalatags = "0.13.1" + val scalaTest = "3.2.19" + val slick = "3.3.3" + val sttpClient3 = "3.10.3" + val sttpClient4 = "4.0.0-RC1" + val support = "1.7.0" + val tapir = "1.11.16" + val urlDsl = "0.7.0" + val waypoint = "9.0.0" +} \ No newline at end of file diff --git a/mill-iw-support/core/src/IWPublishModule.scala b/mill-iw-support/core/src/IWPublishModule.scala new file mode 100644 index 0000000..5ae0ade --- /dev/null +++ b/mill-iw-support/core/src/IWPublishModule.scala @@ -0,0 +1,22 @@ +package works.iterative.mill + +import mill._ +import mill.scalalib._ +import mill.scalalib.publish._ + +/** Standard publish module trait for Iterative Works projects using Mill. This + * trait sets up standard publishing configuration for IW Maven repositories. + * + * To use this trait, simply extend it in your module and define: + * - publishVersion: The version to publish + * - pomSettings: The POM settings for the publication + * + * Use IW_USERNAME and IW_PASSWORD environment variables for authentication. + */ +trait IWPublishModule extends PublishModule { + + override def sonatypeUri = "https://dig.iterative.works/maven/releases" + override def sonatypeSnapshotUri = + "https://dig.iterative.works/maven/snapshots" + +} diff --git a/mill-iw-support/core/src/IWScalaModule.scala b/mill-iw-support/core/src/IWScalaModule.scala new file mode 100644 index 0000000..021bccc --- /dev/null +++ b/mill-iw-support/core/src/IWScalaModule.scala @@ -0,0 +1,97 @@ +package works.iterative.mill + +import mill._ +import mill.scalalib._ +import mill.scalalib.scalafmt._ + +/** Standard Scala module trait for Iterative Works projects using Mill. This + * trait sets up standard compiler options and configuration. + */ +trait IWScalaModule + extends ScalaModule + with ScalafmtModule + with IWScalaVersions { + + override def scalaVersion = scala3Version + + // Define base compiler options based on Scala version + def baseScalacOptions = T { + if (scalaVersion().startsWith("2.")) { + Seq( + "-encoding", + "utf8", + "-feature", + "-unchecked", + "-language:existentials", + "-language:experimental.macros", + "-language:higherKinds", + "-language:implicitConversions", + "-Xlint:adapted-args", + "-Xlint:constant", + "-Xlint:delayedinit-select", + "-Xlint:deprecation", + "-Xlint:doc-detached", + "-Xlint:implicit-recursion", + "-Xlint:implicit-not-found", + "-Xlint:inaccessible", + "-Xlint:infer-any", + "-Xlint:missing-interpolator", + "-Xlint:nullary-unit", + "-Xlint:option-implicit", + "-Xlint:package-object-classes", + "-Xlint:poly-implicit-overload", + "-Xlint:private-shadow", + "-Xlint:stars-align", + "-Xlint:strict-unsealed-patmat", + "-Xlint:type-parameter-shadow", + "-Xlint:-byname-implicit", + "-Wdead-code", + "-Wextra-implicit", + "-Wnumeric-widen", + "-Wvalue-discard", + "-Wnonunit-statement", + "-Wunused:nowarn", + "-Wunused:implicits", + "-Wunused:explicits", + "-Wunused:imports", + "-Wunused:locals", + "-Wunused:params", + "-Wunused:patvars", + "-Wunused:privates" + ) + } else { + Seq( + "-encoding", + "utf8", + "-deprecation", + "-feature", + "-unchecked", + "-language:experimental.macros", + "-language:higherKinds", + "-language:implicitConversions", + "-Xkind-projector", + "-Wvalue-discard", + "-Wnonunit-statement", + "-Wunused:implicits", + "-Wunused:explicits", + "-Wunused:imports", + "-Wunused:locals", + "-Wunused:params", + "-Wunused:privates" + ) + } + } + + // Enable SemanticDB for tooling + def semanticDbOptions = Seq( + "-Xsemanticdb" + ) + + // Override scalacOptions to include both base options and SemanticDB options + override def scalacOptions = T { + super.scalacOptions() ++ baseScalacOptions() ++ semanticDbOptions + } + + // Base test configuration + trait IWTests extends ScalaTests with ScalafmtModule +} diff --git a/mill-iw-support/core/src/IWScalaVersions.scala b/mill-iw-support/core/src/IWScalaVersions.scala new file mode 100644 index 0000000..aa70cb6 --- /dev/null +++ b/mill-iw-support/core/src/IWScalaVersions.scala @@ -0,0 +1,9 @@ +package works.iterative.mill + +trait IWScalaVersions { + val scala2Version = "2.13.16" + val scala3Version = "3.6.4" + val scala3LTSVersion = "3.3.5" +} + +object IWScalaVersions extends IWScalaVersions diff --git a/mill-iw-support/core/src/package.scala b/mill-iw-support/core/src/package.scala new file mode 100644 index 0000000..1ba4556 --- /dev/null +++ b/mill-iw-support/core/src/package.scala @@ -0,0 +1,6 @@ +package works.iterative + +package object mill { + val IWVersions = IWMillVersions + val IWDeps = IWMillDeps +} diff --git a/mill-iw-support/example/.mill-version b/mill-iw-support/example/.mill-version new file mode 100644 index 0000000..54dbed4 --- /dev/null +++ b/mill-iw-support/example/.mill-version @@ -0,0 +1 @@ +0.12.10 diff --git a/mill-iw-support/example/build.sc b/mill-iw-support/example/build.sc index 5da77ab..cae5a6a 100644 --- a/mill-iw-support/example/build.sc +++ b/mill-iw-support/example/build.sc @@ -1,11 +1,22 @@ import mill._ import mill.scalalib._ +import mill.scalalib.publish._ +import mill.scalalib.scalafmt._ + +// Import our IW Mill Support library import $ivy.`works.iterative::mill-iw-support:0.1.0-SNAPSHOT` import works.iterative.mill._ +// Define a BOM (Bill of Materials) module +object bom extends IWBomModule + +// Example module using the IW Mill support with BOM object example extends IWScalaModule with IWPublishModule { def publishVersion = "0.1.0-SNAPSHOT" - + + // Use the BOM module for dependency management + override def bomModuleDeps = Seq(bom) + def pomSettings = PomSettings( description = "Example Mill Project using IW Mill Support", organization = "com.example", @@ -16,15 +27,25 @@ Developer("developer", "Example Developer", "https://github.com/example") ) ) - - // Use centralized dependency versions and definitions - def ivyDeps = super.ivyDeps() ++ IWMillDeps.useZIO() ++ Agg( - IWMillDeps.zioJson, - IWMillDeps.tapirCore, - IWMillDeps.tapirZIO, - IWMillDeps.tapirZIOJson + + // With BOM, we don't need to specify versions + // They're managed by the BOM module + def ivyDeps = super.ivyDeps() ++ Agg( + ivy"dev.zio::zio", + ivy"dev.zio::zio-json", + ivy"com.softwaremill.sttp.tapir::tapir-core", + ivy"com.softwaremill.sttp.tapir::tapir-zio", + ivy"com.softwaremill.sttp.tapir::tapir-json-zio" ) - - // Define test module extending IWTests - object test extends Tests with IWTests -} \ No newline at end of file + + // Define test module using standard Mill approach + object test extends ScalaTests with TestModule.ZioTest { + // Use the BOM module for dependency management + override def bomModuleDeps = Seq(bom) + + def ivyDeps = super.ivyDeps() ++ Agg( + ivy"dev.zio::zio-test", // Version comes from BOM + ivy"dev.zio::zio-test-sbt" // Version comes from BOM + ) + } +} diff --git a/mill-iw-support/example/example/src/Example.scala b/mill-iw-support/example/example/src/Example.scala new file mode 100644 index 0000000..aa28980 --- /dev/null +++ b/mill-iw-support/example/example/src/Example.scala @@ -0,0 +1,27 @@ +package example + +import zio._ +import zio.json._ + +// Simple example class +case class User(id: String, name: String, email: String) + +object User { + // JSON codecs using ZIO JSON + implicit val encoder: JsonEncoder[User] = DeriveJsonEncoder.gen[User] + implicit val decoder: JsonDecoder[User] = DeriveJsonDecoder.gen[User] +} + +object Example extends ZIOAppDefault { + + val program = for { + _ <- Console.printLine("Starting example application") + user = User("1", "John Doe", "john@example.com") + json <- ZIO.succeed(user.toJson) + _ <- Console.printLine(s"User as JSON: $json") + parsed <- ZIO.fromEither(json.fromJson[User]) + _ <- Console.printLine(s"Parsed user: $parsed") + } yield () + + def run = program +} \ No newline at end of file diff --git a/mill-iw-support/example/example/test/src/ExampleTest.scala b/mill-iw-support/example/example/test/src/ExampleTest.scala new file mode 100644 index 0000000..951c2d8 --- /dev/null +++ b/mill-iw-support/example/example/test/src/ExampleTest.scala @@ -0,0 +1,26 @@ +package example + +import zio._ +import zio.json._ +import zio.test._ +import zio.test.Assertion._ + +object ExampleTest extends ZIOSpecDefault { + override def spec = suite("Example suite")( + test("User JSON serialization") { + val user = User("1", "John Doe", "john@example.com") + val json = user.toJson + val expected = + """{"id":"1","name":"John Doe","email":"john@example.com"}""" + + assertTrue(json == expected) + }, + test("User JSON deserialization") { + val json = """{"id":"1","name":"John Doe","email":"john@example.com"}""" + val parsed = json.fromJson[User] + val expected = User("1", "John Doe", "john@example.com") + + assertTrue(parsed == Right(expected)) + } + ) +} diff --git a/mill-iw-support/example/mill b/mill-iw-support/example/mill new file mode 100755 index 0000000..f349762 --- /dev/null +++ b/mill-iw-support/example/mill @@ -0,0 +1,265 @@ +#!/usr/bin/env sh + +# This is a wrapper script, that automatically download mill from GitHub release pages +# You can give the required mill version with --mill-version parameter +# If no version is given, it falls back to the value of DEFAULT_MILL_VERSION +# +# Original Project page: https://github.com/lefou/millw +# Script Version: 0.4.12 +# +# If you want to improve this script, please also contribute your changes back! +# +# Licensed under the Apache License, Version 2.0 + +set -e + +if [ -z "${DEFAULT_MILL_VERSION}" ] ; then + DEFAULT_MILL_VERSION=0.12.10 +fi + + +if [ -z "${GITHUB_RELEASE_CDN}" ] ; then + GITHUB_RELEASE_CDN="" +fi + + +MILL_REPO_URL="https://github.com/com-lihaoyi/mill" + +if [ -z "${CURL_CMD}" ] ; then + CURL_CMD=curl +fi + +# Explicit commandline argument takes precedence over all other methods +if [ "$1" = "--mill-version" ] ; then + shift + if [ "x$1" != "x" ] ; then + MILL_VERSION="$1" + shift + else + echo "You specified --mill-version without a version." 1>&2 + echo "Please provide a version that matches one provided on" 1>&2 + echo "${MILL_REPO_URL}/releases" 1>&2 + false + fi +fi + +# Please note, that if a MILL_VERSION is already set in the environment, +# We reuse it's value and skip searching for a value. + +# If not already set, read .mill-version file +if [ -z "${MILL_VERSION}" ] ; then + if [ -f ".mill-version" ] ; then + MILL_VERSION="$(tr '\r' '\n' < .mill-version | head -n 1 2> /dev/null)" + elif [ -f ".config/mill-version" ] ; then + MILL_VERSION="$(tr '\r' '\n' < .config/mill-version | head -n 1 2> /dev/null)" + fi +fi + +MILL_USER_CACHE_DIR="${XDG_CACHE_HOME:-${HOME}/.cache}/mill" + +if [ -z "${MILL_DOWNLOAD_PATH}" ] ; then + MILL_DOWNLOAD_PATH="${MILL_USER_CACHE_DIR}/download" +fi + +# If not already set, try to fetch newest from Github +if [ -z "${MILL_VERSION}" ] ; then + # TODO: try to load latest version from release page + echo "No mill version specified." 1>&2 + echo "You should provide a version via '.mill-version' file or --mill-version option." 1>&2 + + mkdir -p "${MILL_DOWNLOAD_PATH}" + LANG=C touch -d '1 hour ago' "${MILL_DOWNLOAD_PATH}/.expire_latest" 2>/dev/null || ( + # we might be on OSX or BSD which don't have -d option for touch + # but probably a -A [-][[hh]mm]SS + touch "${MILL_DOWNLOAD_PATH}/.expire_latest"; touch -A -010000 "${MILL_DOWNLOAD_PATH}/.expire_latest" + ) || ( + # in case we still failed, we retry the first touch command with the intention + # to show the (previously suppressed) error message + LANG=C touch -d '1 hour ago' "${MILL_DOWNLOAD_PATH}/.expire_latest" + ) + + # POSIX shell variant of bash's -nt operator, see https://unix.stackexchange.com/a/449744/6993 + # if [ "${MILL_DOWNLOAD_PATH}/.latest" -nt "${MILL_DOWNLOAD_PATH}/.expire_latest" ] ; then + if [ -n "$(find -L "${MILL_DOWNLOAD_PATH}/.latest" -prune -newer "${MILL_DOWNLOAD_PATH}/.expire_latest")" ]; then + # we know a current latest version + MILL_VERSION=$(head -n 1 "${MILL_DOWNLOAD_PATH}"/.latest 2> /dev/null) + fi + + if [ -z "${MILL_VERSION}" ] ; then + # we don't know a current latest version + echo "Retrieving latest mill version ..." 1>&2 + LANG=C ${CURL_CMD} -s -i -f -I ${MILL_REPO_URL}/releases/latest 2> /dev/null | grep --ignore-case Location: | sed s'/^.*tag\///' | tr -d '\r\n' > "${MILL_DOWNLOAD_PATH}/.latest" + MILL_VERSION=$(head -n 1 "${MILL_DOWNLOAD_PATH}"/.latest 2> /dev/null) + fi + + if [ -z "${MILL_VERSION}" ] ; then + # Last resort + MILL_VERSION="${DEFAULT_MILL_VERSION}" + echo "Falling back to hardcoded mill version ${MILL_VERSION}" 1>&2 + else + echo "Using mill version ${MILL_VERSION}" 1>&2 + fi +fi + +MILL_NATIVE_SUFFIX="-native" +FULL_MILL_VERSION=$MILL_VERSION +ARTIFACT_SUFFIX="" +case "$MILL_VERSION" in + *"$MILL_NATIVE_SUFFIX") + MILL_VERSION=${MILL_VERSION%"$MILL_NATIVE_SUFFIX"} + if [ "$(expr substr $(uname -s) 1 5 2>/dev/null)" = "Linux" ]; then + if [ "$(uname -m)" = "aarch64" ]; then + ARTIFACT_SUFFIX="-native-linux-aarch64" + else + ARTIFACT_SUFFIX="-native-linux-amd64" + fi + elif [ "$(uname)" = "Darwin" ]; then + if [ "$(uname -m)" = "arm64" ]; then + ARTIFACT_SUFFIX="-native-mac-aarch64" + else + ARTIFACT_SUFFIX="-native-mac-amd64" + fi + else + echo "This native mill launcher supports only Linux and macOS." 1>&2 + exit 1 + fi +esac + +MILL="${MILL_DOWNLOAD_PATH}/${FULL_MILL_VERSION}" + +try_to_use_system_mill() { + if [ "$(uname)" != "Linux" ]; then + return 0 + fi + + MILL_IN_PATH="$(command -v mill || true)" + + if [ -z "${MILL_IN_PATH}" ]; then + return 0 + fi + + SYSTEM_MILL_FIRST_TWO_BYTES=$(head --bytes=2 "${MILL_IN_PATH}") + if [ "${SYSTEM_MILL_FIRST_TWO_BYTES}" = "#!" ]; then + # MILL_IN_PATH is (very likely) a shell script and not the mill + # executable, ignore it. + return 0 + fi + + SYSTEM_MILL_PATH=$(readlink -e "${MILL_IN_PATH}") + SYSTEM_MILL_SIZE=$(stat --format=%s "${SYSTEM_MILL_PATH}") + SYSTEM_MILL_MTIME=$(stat --format=%y "${SYSTEM_MILL_PATH}") + + if [ ! -d "${MILL_USER_CACHE_DIR}" ]; then + mkdir -p "${MILL_USER_CACHE_DIR}" + fi + + SYSTEM_MILL_INFO_FILE="${MILL_USER_CACHE_DIR}/system-mill-info" + if [ -f "${SYSTEM_MILL_INFO_FILE}" ]; then + parseSystemMillInfo() { + LINE_NUMBER="${1}" + # Select the line number of the SYSTEM_MILL_INFO_FILE, cut the + # variable definition in that line in two halves and return + # the value, and finally remove the quotes. + sed -n "${LINE_NUMBER}p" "${SYSTEM_MILL_INFO_FILE}" |\ + cut -d= -f2 |\ + sed 's/"\(.*\)"/\1/' + } + + CACHED_SYSTEM_MILL_PATH=$(parseSystemMillInfo 1) + CACHED_SYSTEM_MILL_VERSION=$(parseSystemMillInfo 2) + CACHED_SYSTEM_MILL_SIZE=$(parseSystemMillInfo 3) + CACHED_SYSTEM_MILL_MTIME=$(parseSystemMillInfo 4) + + if [ "${SYSTEM_MILL_PATH}" = "${CACHED_SYSTEM_MILL_PATH}" ] \ + && [ "${SYSTEM_MILL_SIZE}" = "${CACHED_SYSTEM_MILL_SIZE}" ] \ + && [ "${SYSTEM_MILL_MTIME}" = "${CACHED_SYSTEM_MILL_MTIME}" ]; then + if [ "${CACHED_SYSTEM_MILL_VERSION}" = "${MILL_VERSION}" ]; then + MILL="${SYSTEM_MILL_PATH}" + return 0 + else + return 0 + fi + fi + fi + + SYSTEM_MILL_VERSION=$(${SYSTEM_MILL_PATH} --version | head -n1 | sed -n 's/^Mill.*version \(.*\)/\1/p') + + cat < "${SYSTEM_MILL_INFO_FILE}" +CACHED_SYSTEM_MILL_PATH="${SYSTEM_MILL_PATH}" +CACHED_SYSTEM_MILL_VERSION="${SYSTEM_MILL_VERSION}" +CACHED_SYSTEM_MILL_SIZE="${SYSTEM_MILL_SIZE}" +CACHED_SYSTEM_MILL_MTIME="${SYSTEM_MILL_MTIME}" +EOF + + if [ "${SYSTEM_MILL_VERSION}" = "${MILL_VERSION}" ]; then + MILL="${SYSTEM_MILL_PATH}" + fi +} +try_to_use_system_mill + +# If not already downloaded, download it +if [ ! -s "${MILL}" ] ; then + + # support old non-XDG download dir + MILL_OLD_DOWNLOAD_PATH="${HOME}/.mill/download" + OLD_MILL="${MILL_OLD_DOWNLOAD_PATH}/${MILL_VERSION}" + if [ -x "${OLD_MILL}" ] ; then + MILL="${OLD_MILL}" + else + case $MILL_VERSION in + 0.0.* | 0.1.* | 0.2.* | 0.3.* | 0.4.* ) + DOWNLOAD_SUFFIX="" + DOWNLOAD_FROM_MAVEN=0 + ;; + 0.5.* | 0.6.* | 0.7.* | 0.8.* | 0.9.* | 0.10.* | 0.11.0-M* ) + DOWNLOAD_SUFFIX="-assembly" + DOWNLOAD_FROM_MAVEN=0 + ;; + *) + DOWNLOAD_SUFFIX="-assembly" + DOWNLOAD_FROM_MAVEN=1 + ;; + esac + + DOWNLOAD_FILE=$(mktemp mill.XXXXXX) + + if [ "$DOWNLOAD_FROM_MAVEN" = "1" ] ; then + DOWNLOAD_URL="https://repo1.maven.org/maven2/com/lihaoyi/mill-dist${ARTIFACT_SUFFIX}/${MILL_VERSION}/mill-dist${ARTIFACT_SUFFIX}-${MILL_VERSION}.jar" + else + MILL_VERSION_TAG=$(echo "$MILL_VERSION" | sed -E 's/([^-]+)(-M[0-9]+)?(-.*)?/\1\2/') + DOWNLOAD_URL="${GITHUB_RELEASE_CDN}${MILL_REPO_URL}/releases/download/${MILL_VERSION_TAG}/${MILL_VERSION}${DOWNLOAD_SUFFIX}" + unset MILL_VERSION_TAG + fi + + # TODO: handle command not found + echo "Downloading mill ${MILL_VERSION} from ${DOWNLOAD_URL} ..." 1>&2 + ${CURL_CMD} -f -L -o "${DOWNLOAD_FILE}" "${DOWNLOAD_URL}" + chmod +x "${DOWNLOAD_FILE}" + mkdir -p "${MILL_DOWNLOAD_PATH}" + mv "${DOWNLOAD_FILE}" "${MILL}" + + unset DOWNLOAD_FILE + unset DOWNLOAD_SUFFIX + fi +fi + +if [ -z "$MILL_MAIN_CLI" ] ; then + MILL_MAIN_CLI="${0}" +fi + +MILL_FIRST_ARG="" +if [ "$1" = "--bsp" ] || [ "$1" = "-i" ] || [ "$1" = "--interactive" ] || [ "$1" = "--no-server" ] || [ "$1" = "--repl" ] || [ "$1" = "--help" ] ; then + # Need to preserve the first position of those listed options + MILL_FIRST_ARG=$1 + shift +fi + +unset MILL_DOWNLOAD_PATH +unset MILL_OLD_DOWNLOAD_PATH +unset OLD_MILL +unset MILL_VERSION +unset MILL_REPO_URL + +# We don't quote MILL_FIRST_ARG on purpose, so we can expand the empty value without quotes +# shellcheck disable=SC2086 +exec "${MILL}" $MILL_FIRST_ARG -D "mill.main.cli=${MILL_MAIN_CLI}" "$@" \ No newline at end of file diff --git a/mill-iw-support/example/src/Example.scala b/mill-iw-support/example/src/Example.scala deleted file mode 100644 index aa28980..0000000 --- a/mill-iw-support/example/src/Example.scala +++ /dev/null @@ -1,27 +0,0 @@ -package example - -import zio._ -import zio.json._ - -// Simple example class -case class User(id: String, name: String, email: String) - -object User { - // JSON codecs using ZIO JSON - implicit val encoder: JsonEncoder[User] = DeriveJsonEncoder.gen[User] - implicit val decoder: JsonDecoder[User] = DeriveJsonDecoder.gen[User] -} - -object Example extends ZIOAppDefault { - - val program = for { - _ <- Console.printLine("Starting example application") - user = User("1", "John Doe", "john@example.com") - json <- ZIO.succeed(user.toJson) - _ <- Console.printLine(s"User as JSON: $json") - parsed <- ZIO.fromEither(json.fromJson[User]) - _ <- Console.printLine(s"Parsed user: $parsed") - } yield () - - def run = program -} \ No newline at end of file diff --git a/mill-iw-support/example/test/src/ExampleTest.scala b/mill-iw-support/example/test/src/ExampleTest.scala deleted file mode 100644 index 20f0c06..0000000 --- a/mill-iw-support/example/test/src/ExampleTest.scala +++ /dev/null @@ -1,27 +0,0 @@ -package example - -import utest._ -import zio._ -import zio.json._ -import zio.test._ -import zio.test.Assertion._ - -object ExampleTest extends TestSuite { - val tests = Tests { - test("User JSON serialization") { - val user = User("1", "John Doe", "john@example.com") - val json = user.toJson - val expected = """{"id":"1","name":"John Doe","email":"john@example.com"}""" - - assert(json == expected) - } - - test("User JSON deserialization") { - val json = """{"id":"1","name":"John Doe","email":"john@example.com"}""" - val parsed = json.fromJson[User] - val expected = User("1", "John Doe", "john@example.com") - - assert(parsed == Right(expected)) - } - } -} \ No newline at end of file diff --git a/mill-iw-support/mill b/mill-iw-support/mill new file mode 100755 index 0000000..f349762 --- /dev/null +++ b/mill-iw-support/mill @@ -0,0 +1,265 @@ +#!/usr/bin/env sh + +# This is a wrapper script, that automatically download mill from GitHub release pages +# You can give the required mill version with --mill-version parameter +# If no version is given, it falls back to the value of DEFAULT_MILL_VERSION +# +# Original Project page: https://github.com/lefou/millw +# Script Version: 0.4.12 +# +# If you want to improve this script, please also contribute your changes back! +# +# Licensed under the Apache License, Version 2.0 + +set -e + +if [ -z "${DEFAULT_MILL_VERSION}" ] ; then + DEFAULT_MILL_VERSION=0.12.10 +fi + + +if [ -z "${GITHUB_RELEASE_CDN}" ] ; then + GITHUB_RELEASE_CDN="" +fi + + +MILL_REPO_URL="https://github.com/com-lihaoyi/mill" + +if [ -z "${CURL_CMD}" ] ; then + CURL_CMD=curl +fi + +# Explicit commandline argument takes precedence over all other methods +if [ "$1" = "--mill-version" ] ; then + shift + if [ "x$1" != "x" ] ; then + MILL_VERSION="$1" + shift + else + echo "You specified --mill-version without a version." 1>&2 + echo "Please provide a version that matches one provided on" 1>&2 + echo "${MILL_REPO_URL}/releases" 1>&2 + false + fi +fi + +# Please note, that if a MILL_VERSION is already set in the environment, +# We reuse it's value and skip searching for a value. + +# If not already set, read .mill-version file +if [ -z "${MILL_VERSION}" ] ; then + if [ -f ".mill-version" ] ; then + MILL_VERSION="$(tr '\r' '\n' < .mill-version | head -n 1 2> /dev/null)" + elif [ -f ".config/mill-version" ] ; then + MILL_VERSION="$(tr '\r' '\n' < .config/mill-version | head -n 1 2> /dev/null)" + fi +fi + +MILL_USER_CACHE_DIR="${XDG_CACHE_HOME:-${HOME}/.cache}/mill" + +if [ -z "${MILL_DOWNLOAD_PATH}" ] ; then + MILL_DOWNLOAD_PATH="${MILL_USER_CACHE_DIR}/download" +fi + +# If not already set, try to fetch newest from Github +if [ -z "${MILL_VERSION}" ] ; then + # TODO: try to load latest version from release page + echo "No mill version specified." 1>&2 + echo "You should provide a version via '.mill-version' file or --mill-version option." 1>&2 + + mkdir -p "${MILL_DOWNLOAD_PATH}" + LANG=C touch -d '1 hour ago' "${MILL_DOWNLOAD_PATH}/.expire_latest" 2>/dev/null || ( + # we might be on OSX or BSD which don't have -d option for touch + # but probably a -A [-][[hh]mm]SS + touch "${MILL_DOWNLOAD_PATH}/.expire_latest"; touch -A -010000 "${MILL_DOWNLOAD_PATH}/.expire_latest" + ) || ( + # in case we still failed, we retry the first touch command with the intention + # to show the (previously suppressed) error message + LANG=C touch -d '1 hour ago' "${MILL_DOWNLOAD_PATH}/.expire_latest" + ) + + # POSIX shell variant of bash's -nt operator, see https://unix.stackexchange.com/a/449744/6993 + # if [ "${MILL_DOWNLOAD_PATH}/.latest" -nt "${MILL_DOWNLOAD_PATH}/.expire_latest" ] ; then + if [ -n "$(find -L "${MILL_DOWNLOAD_PATH}/.latest" -prune -newer "${MILL_DOWNLOAD_PATH}/.expire_latest")" ]; then + # we know a current latest version + MILL_VERSION=$(head -n 1 "${MILL_DOWNLOAD_PATH}"/.latest 2> /dev/null) + fi + + if [ -z "${MILL_VERSION}" ] ; then + # we don't know a current latest version + echo "Retrieving latest mill version ..." 1>&2 + LANG=C ${CURL_CMD} -s -i -f -I ${MILL_REPO_URL}/releases/latest 2> /dev/null | grep --ignore-case Location: | sed s'/^.*tag\///' | tr -d '\r\n' > "${MILL_DOWNLOAD_PATH}/.latest" + MILL_VERSION=$(head -n 1 "${MILL_DOWNLOAD_PATH}"/.latest 2> /dev/null) + fi + + if [ -z "${MILL_VERSION}" ] ; then + # Last resort + MILL_VERSION="${DEFAULT_MILL_VERSION}" + echo "Falling back to hardcoded mill version ${MILL_VERSION}" 1>&2 + else + echo "Using mill version ${MILL_VERSION}" 1>&2 + fi +fi + +MILL_NATIVE_SUFFIX="-native" +FULL_MILL_VERSION=$MILL_VERSION +ARTIFACT_SUFFIX="" +case "$MILL_VERSION" in + *"$MILL_NATIVE_SUFFIX") + MILL_VERSION=${MILL_VERSION%"$MILL_NATIVE_SUFFIX"} + if [ "$(expr substr $(uname -s) 1 5 2>/dev/null)" = "Linux" ]; then + if [ "$(uname -m)" = "aarch64" ]; then + ARTIFACT_SUFFIX="-native-linux-aarch64" + else + ARTIFACT_SUFFIX="-native-linux-amd64" + fi + elif [ "$(uname)" = "Darwin" ]; then + if [ "$(uname -m)" = "arm64" ]; then + ARTIFACT_SUFFIX="-native-mac-aarch64" + else + ARTIFACT_SUFFIX="-native-mac-amd64" + fi + else + echo "This native mill launcher supports only Linux and macOS." 1>&2 + exit 1 + fi +esac + +MILL="${MILL_DOWNLOAD_PATH}/${FULL_MILL_VERSION}" + +try_to_use_system_mill() { + if [ "$(uname)" != "Linux" ]; then + return 0 + fi + + MILL_IN_PATH="$(command -v mill || true)" + + if [ -z "${MILL_IN_PATH}" ]; then + return 0 + fi + + SYSTEM_MILL_FIRST_TWO_BYTES=$(head --bytes=2 "${MILL_IN_PATH}") + if [ "${SYSTEM_MILL_FIRST_TWO_BYTES}" = "#!" ]; then + # MILL_IN_PATH is (very likely) a shell script and not the mill + # executable, ignore it. + return 0 + fi + + SYSTEM_MILL_PATH=$(readlink -e "${MILL_IN_PATH}") + SYSTEM_MILL_SIZE=$(stat --format=%s "${SYSTEM_MILL_PATH}") + SYSTEM_MILL_MTIME=$(stat --format=%y "${SYSTEM_MILL_PATH}") + + if [ ! -d "${MILL_USER_CACHE_DIR}" ]; then + mkdir -p "${MILL_USER_CACHE_DIR}" + fi + + SYSTEM_MILL_INFO_FILE="${MILL_USER_CACHE_DIR}/system-mill-info" + if [ -f "${SYSTEM_MILL_INFO_FILE}" ]; then + parseSystemMillInfo() { + LINE_NUMBER="${1}" + # Select the line number of the SYSTEM_MILL_INFO_FILE, cut the + # variable definition in that line in two halves and return + # the value, and finally remove the quotes. + sed -n "${LINE_NUMBER}p" "${SYSTEM_MILL_INFO_FILE}" |\ + cut -d= -f2 |\ + sed 's/"\(.*\)"/\1/' + } + + CACHED_SYSTEM_MILL_PATH=$(parseSystemMillInfo 1) + CACHED_SYSTEM_MILL_VERSION=$(parseSystemMillInfo 2) + CACHED_SYSTEM_MILL_SIZE=$(parseSystemMillInfo 3) + CACHED_SYSTEM_MILL_MTIME=$(parseSystemMillInfo 4) + + if [ "${SYSTEM_MILL_PATH}" = "${CACHED_SYSTEM_MILL_PATH}" ] \ + && [ "${SYSTEM_MILL_SIZE}" = "${CACHED_SYSTEM_MILL_SIZE}" ] \ + && [ "${SYSTEM_MILL_MTIME}" = "${CACHED_SYSTEM_MILL_MTIME}" ]; then + if [ "${CACHED_SYSTEM_MILL_VERSION}" = "${MILL_VERSION}" ]; then + MILL="${SYSTEM_MILL_PATH}" + return 0 + else + return 0 + fi + fi + fi + + SYSTEM_MILL_VERSION=$(${SYSTEM_MILL_PATH} --version | head -n1 | sed -n 's/^Mill.*version \(.*\)/\1/p') + + cat < "${SYSTEM_MILL_INFO_FILE}" +CACHED_SYSTEM_MILL_PATH="${SYSTEM_MILL_PATH}" +CACHED_SYSTEM_MILL_VERSION="${SYSTEM_MILL_VERSION}" +CACHED_SYSTEM_MILL_SIZE="${SYSTEM_MILL_SIZE}" +CACHED_SYSTEM_MILL_MTIME="${SYSTEM_MILL_MTIME}" +EOF + + if [ "${SYSTEM_MILL_VERSION}" = "${MILL_VERSION}" ]; then + MILL="${SYSTEM_MILL_PATH}" + fi +} +try_to_use_system_mill + +# If not already downloaded, download it +if [ ! -s "${MILL}" ] ; then + + # support old non-XDG download dir + MILL_OLD_DOWNLOAD_PATH="${HOME}/.mill/download" + OLD_MILL="${MILL_OLD_DOWNLOAD_PATH}/${MILL_VERSION}" + if [ -x "${OLD_MILL}" ] ; then + MILL="${OLD_MILL}" + else + case $MILL_VERSION in + 0.0.* | 0.1.* | 0.2.* | 0.3.* | 0.4.* ) + DOWNLOAD_SUFFIX="" + DOWNLOAD_FROM_MAVEN=0 + ;; + 0.5.* | 0.6.* | 0.7.* | 0.8.* | 0.9.* | 0.10.* | 0.11.0-M* ) + DOWNLOAD_SUFFIX="-assembly" + DOWNLOAD_FROM_MAVEN=0 + ;; + *) + DOWNLOAD_SUFFIX="-assembly" + DOWNLOAD_FROM_MAVEN=1 + ;; + esac + + DOWNLOAD_FILE=$(mktemp mill.XXXXXX) + + if [ "$DOWNLOAD_FROM_MAVEN" = "1" ] ; then + DOWNLOAD_URL="https://repo1.maven.org/maven2/com/lihaoyi/mill-dist${ARTIFACT_SUFFIX}/${MILL_VERSION}/mill-dist${ARTIFACT_SUFFIX}-${MILL_VERSION}.jar" + else + MILL_VERSION_TAG=$(echo "$MILL_VERSION" | sed -E 's/([^-]+)(-M[0-9]+)?(-.*)?/\1\2/') + DOWNLOAD_URL="${GITHUB_RELEASE_CDN}${MILL_REPO_URL}/releases/download/${MILL_VERSION_TAG}/${MILL_VERSION}${DOWNLOAD_SUFFIX}" + unset MILL_VERSION_TAG + fi + + # TODO: handle command not found + echo "Downloading mill ${MILL_VERSION} from ${DOWNLOAD_URL} ..." 1>&2 + ${CURL_CMD} -f -L -o "${DOWNLOAD_FILE}" "${DOWNLOAD_URL}" + chmod +x "${DOWNLOAD_FILE}" + mkdir -p "${MILL_DOWNLOAD_PATH}" + mv "${DOWNLOAD_FILE}" "${MILL}" + + unset DOWNLOAD_FILE + unset DOWNLOAD_SUFFIX + fi +fi + +if [ -z "$MILL_MAIN_CLI" ] ; then + MILL_MAIN_CLI="${0}" +fi + +MILL_FIRST_ARG="" +if [ "$1" = "--bsp" ] || [ "$1" = "-i" ] || [ "$1" = "--interactive" ] || [ "$1" = "--no-server" ] || [ "$1" = "--repl" ] || [ "$1" = "--help" ] ; then + # Need to preserve the first position of those listed options + MILL_FIRST_ARG=$1 + shift +fi + +unset MILL_DOWNLOAD_PATH +unset MILL_OLD_DOWNLOAD_PATH +unset OLD_MILL +unset MILL_VERSION +unset MILL_REPO_URL + +# We don't quote MILL_FIRST_ARG on purpose, so we can expand the empty value without quotes +# shellcheck disable=SC2086 +exec "${MILL}" $MILL_FIRST_ARG -D "mill.main.cli=${MILL_MAIN_CLI}" "$@" \ No newline at end of file diff --git a/mill-iw-support/src/main/scala/works/iterative/mill/IWMillDeps.scala b/mill-iw-support/src/main/scala/works/iterative/mill/IWMillDeps.scala deleted file mode 100644 index e21af78..0000000 --- a/mill-iw-support/src/main/scala/works/iterative/mill/IWMillDeps.scala +++ /dev/null @@ -1,78 +0,0 @@ -package works.iterative.mill - -import mill.api.Loose -import mill.scalalib.Dep - -/** - * Standardized dependency management for Iterative Works projects using Mill. - * This object provides methods to get dependencies with proper versioning. - * - * All versions are centralized in [[IWMillVersions]]. - */ -object IWMillDeps { - import IWMillVersions as V - - // ZIO Core - def zio = ivy"dev.zio::zio:${V.zio}" - def zioTest = ivy"dev.zio::zio-test:${V.zio}" - def zioTestSbt = ivy"dev.zio::zio-test-sbt:${V.zio}" - def zioStreams = ivy"dev.zio::zio-streams:${V.zio}" - - // ZIO Extensions - def zioConfig = ivy"dev.zio::zio-config:${V.zioConfig}" - def zioConfigTypesafe = ivy"dev.zio::zio-config-typesafe:${V.zioConfig}" - def zioConfigMagnolia = ivy"dev.zio::zio-config-magnolia:${V.zioConfig}" - def zioJson = ivy"dev.zio::zio-json:${V.zioJson}" - def zioLogging = ivy"dev.zio::zio-logging:${V.zioLogging}" - def zioLoggingSlf4j = ivy"dev.zio::zio-logging-slf4j:${V.zioLogging}" - def zioPrelude = ivy"dev.zio::zio-prelude:${V.zioPrelude}" - def zioInteropCats = ivy"dev.zio::zio-interop-cats:${V.zioInteropCats}" - def zioInteropReactiveStreams = ivy"dev.zio::zio-interop-reactivestreams:${V.zioInteropReactiveStreams}" - def zioNIO = ivy"dev.zio::zio-nio:${V.zioNIO}" - def zioCli = ivy"dev.zio::zio-cli:${V.zioCli}" - def zioSchema = ivy"dev.zio::zio-schema:${V.zioSchema}" - def zioSchemaDerivation = ivy"dev.zio::zio-schema-derivation:${V.zioSchema}" - - // Helper methods for ZIO - def useZIO(): Loose.Agg[Dep] = Loose.Agg( - zio, - zioTest, - zioTestSbt - ) - - def useZIOJson: Loose.Agg[Dep] = Loose.Agg( - zioJson - ) - - def useZIOAll(): Loose.Agg[Dep] = useZIO() ++ Loose.Agg( - zioStreams, - zioConfig, - zioConfigTypesafe, - zioConfigMagnolia, - zioLogging, - zioPrelude - ) ++ useZIOJson - - // Database - def quill = ivy"io.getquill::quill-jdbc-zio:${V.quill}" - def magnum = ivy"com.augustnagro::magnum_3:${V.magnum}" - def magnumZIO = ivy"com.augustnagro::magnumzio_3:${V.magnum}" - def magnumPG = ivy"com.augustnagro::magnumpg_3:${V.magnum}" - - // HTTP - def tapirCore = ivy"com.softwaremill.sttp.tapir::tapir-core:${V.tapir}" - def tapirZIO = ivy"com.softwaremill.sttp.tapir::tapir-zio:${V.tapir}" - def tapirZIOJson = ivy"com.softwaremill.sttp.tapir::tapir-json-zio:${V.tapir}" - - // UI/Frontend - def scalatags = ivy"com.lihaoyi::scalatags:${V.scalatags}" - def laminar = ivy"com.raquo::laminar:${V.laminar}" - def waypoint = ivy"com.raquo::waypoint:${V.waypoint}" - def urlDsl = ivy"be.doeraene::url-dsl:${V.urlDsl}" - - // Testing - def scalaTest = ivy"org.scalatest::scalatest:${V.scalaTest}" - - // Logging - def logbackClassic = ivy"ch.qos.logback:logback-classic:${V.logbackClassic}" -} \ No newline at end of file diff --git a/mill-iw-support/src/main/scala/works/iterative/mill/IWMillVersions.scala b/mill-iw-support/src/main/scala/works/iterative/mill/IWMillVersions.scala deleted file mode 100644 index 4e3150a..0000000 --- a/mill-iw-support/src/main/scala/works/iterative/mill/IWMillVersions.scala +++ /dev/null @@ -1,55 +0,0 @@ -package works.iterative.mill - -/** - * Centralized version management for Iterative Works projects using Mill. - * This object contains all library versions used across projects. - * - * Update this object to bump versions for all projects. - */ -object IWMillVersions { - // ZIO Ecosystem - val zio = "2.1.16" - val zioConfig = "4.0.3" - val zioCli = "0.7.0" - val zioInteropCats = "23.1.0.3" - val zioInteropReactiveStreams = "2.0.2" - val zioJson = "0.7.36" - val zioLogging = "2.5.0" - val zioNIO = "2.0.2" - val zioPrelude = "1.0.0-RC39" - val zioQuery = "0.7.6" - val zioSchema = "1.6.3" - - // Akka Ecosystem - val akka = "2.6.18" - val akkaHttp = "10.4.0" - - // Other Libraries - val cats = "2.13.0" - val caliban = "2.9.2" - val chimney = "1.7.3" - val ducktape = "0.2.7" - val elastic4s = "7.12.4" - val http4s = "0.23.30" - val http4sBlaze = "0.23.17" - val http4sPac4J = "5.0.0" - val laminar = "17.2.0" - val laminext = "0.17.0" - val logbackClassic = "1.5.17" - val magnum = "2.0.0-M1" - val pac4j = "6.0.3" - val play = "2.8.18" - val playJson = "2.9.3" - val quill = "4.8.6" - val scalaJsMacroTaskExecutor = "1.1.1" - val scalaJsJavaSecureRandom = "1.0.0" - val scalatags = "0.13.1" - val scalaTest = "3.2.19" - val slick = "3.3.3" - val sttpClient3 = "3.10.3" - val sttpClient4 = "4.0.0-RC1" - val support = "1.7.0" - val tapir = "1.11.16" - val urlDsl = "0.7.0" - val waypoint = "9.0.0" -} \ No newline at end of file diff --git a/mill-iw-support/src/main/scala/works/iterative/mill/IWPublishModule.scala b/mill-iw-support/src/main/scala/works/iterative/mill/IWPublishModule.scala deleted file mode 100644 index 29a2df4..0000000 --- a/mill-iw-support/src/main/scala/works/iterative/mill/IWPublishModule.scala +++ /dev/null @@ -1,43 +0,0 @@ -package works.iterative.mill - -import mill._ -import mill.scalalib._ -import mill.scalalib.publish._ - -/** - * Standard publish module trait for Iterative Works projects using Mill. - * This trait sets up standard publishing configuration for IW Maven repositories. - */ -trait IWPublishModule extends PublishModule { - def publishVersion: T[String] - - def pomSettings: T[PomSettings] - - /** - * Determines the repository URL based on version. - * - Snapshot versions go to the snapshots repository - * - Release versions go to the releases repository - */ - def repositoryUrl: T[String] = T { - if (publishVersion().endsWith("-SNAPSHOT")) - "https://dig.iterative.works/maven/snapshots" - else - "https://dig.iterative.works/maven/releases" - } - - /** - * Configure publish credentials from environment variables. - */ - def publishCredentials: T[PublishCredentials] = T { - val username = sys.env.getOrElse("IW_USERNAME", "") - val password = sys.env.getOrElse("IW_PASSWORD", "") - PublishCredentials("GitBucket Maven Repository", "dig.iterative.works", username, password) - } - - /** - * Configure publication to IW Maven repository. - */ - def publishTo: T[Option[PublishRepository]] = T { - Some(PublishRepository(repositoryUrl(), publishCredentials())) - } -} \ No newline at end of file diff --git a/mill-iw-support/.mill-version b/mill-iw-support/.mill-version new file mode 100644 index 0000000..54dbed4 --- /dev/null +++ b/mill-iw-support/.mill-version @@ -0,0 +1 @@ +0.12.10 diff --git a/mill-iw-support/.scalafmt.conf b/mill-iw-support/.scalafmt.conf new file mode 100644 index 0000000..ee7753a --- /dev/null +++ b/mill-iw-support/.scalafmt.conf @@ -0,0 +1,2 @@ +version = "3.7.15" +runner.dialect = scala213 \ No newline at end of file diff --git a/mill-iw-support/README.md b/mill-iw-support/README.md index 4986e5c..9bb1ba8 100644 --- a/mill-iw-support/README.md +++ b/mill-iw-support/README.md @@ -28,8 +28,45 @@ IWMillDeps.tapirCore // ivy"com.softwaremill.sttp.tapir::tapir-core:1.11.16" // Use dependency groups -IWMillDeps.useZIO() // Core ZIO dependencies with test framework -IWMillDeps.useZIOAll() // Comprehensive ZIO stack +IWMillDeps.zioCore // List of core ZIO dependencies +IWMillDeps.zioAll // List of all ZIO dependencies +``` + +### IWBomModule + +Bill of Materials (BOM) module for centralized dependency management: + +```scala +import mill._ +import works.iterative.mill._ + +// Define a BOM module +object bom extends IWBomModule { + def publishVersion = "0.1.0-SNAPSHOT" + + def pomSettings = PomSettings( + // Standard POM settings + ) + + // Override with custom dependencies if needed + override def depManagement = T { + super.depManagement() ++ Agg( + ivy"com.example::custom-lib:1.0.0" + ) + } +} + +// Use the BOM in modules +object myModule extends IWScalaModule { + // Reference the BOM module + override def bomModuleDeps = Seq(bom) + + // No need to specify versions - they come from the BOM + def ivyDeps = super.ivyDeps() ++ Agg( + ivy"dev.zio::zio", + ivy"dev.zio::zio-json" + ) +} ``` ### IWScalaModule @@ -44,10 +81,18 @@ // Use default Scala version (3.6.3) // and standard compiler options with SemanticDB enabled - def ivyDeps = super.ivyDeps() ++ IWMillDeps.useZIO() + def ivyDeps = super.ivyDeps() ++ Agg( + ivy"${IWMillDeps.zio}", + ivy"${IWMillDeps.zioJson}" + ) // Standard test module configuration - object test extends Tests with IWTests + object test extends ScalaTests with TestModule.ZioTest { + def ivyDeps = super.ivyDeps() ++ Agg( + ivy"${IWMillDeps.zioTest}", + ivy"${IWMillDeps.zioTestSbt}" + ) + } } ``` @@ -96,6 +141,51 @@ } ``` +## Dependency Management Approaches + +This library provides two approaches for dependency management: + +### 1. IWMillDeps (String Constants) + +Use the IWMillDeps object to access dependency strings with correct versions: + +```scala +def ivyDeps = super.ivyDeps() ++ Agg( + ivy"${IWMillDeps.zio}", + ivy"${IWMillDeps.zioJson}" +) +``` + +### 2. BOM (Bill of Materials) + +Use the IWBomModule to centralize dependency versions: + +```scala +// Define a BOM module +object bom extends IWBomModule { + def publishVersion = "0.1.0-SNAPSHOT" + def pomSettings = PomSettings(...) +} + +// Use the BOM in modules +object myModule extends IWScalaModule { + override def bomModuleDeps = Seq(bom) + + // No need to specify versions + def ivyDeps = super.ivyDeps() ++ Agg( + ivy"dev.zio::zio", + ivy"dev.zio::zio-json" + ) +} +``` + +The BOM approach offers these advantages: +- Enforces consistent versions across modules +- Simplifies version updates +- Controls transitive dependency versions +- Makes build files cleaner (no version strings) +- Can be published as a separate artifact + ## Examples See the `example` directory for a complete working example. diff --git a/mill-iw-support/build.sc b/mill-iw-support/build.sc index 5df80ae..2ed832c 100644 --- a/mill-iw-support/build.sc +++ b/mill-iw-support/build.sc @@ -2,20 +2,33 @@ import mill.scalalib._ import mill.scalalib.publish._ import mill.scalalib.scalafmt._ +import mill.scalalib.TestModule.Utest -object `mill-iw-support` extends Cross[IWMillSupportModule]("2.13.16", "3.6.3") +/** Main module for the mill-iw-support library. Built for Scala 2.13 which is + * compatible with Mill. + */ +object core extends ScalaModule with ScalafmtModule with PublishModule { + val millVersion = "0.12.10" -class IWMillSupportModule(val crossScalaVersion: String) extends CrossScalaModule with ScalafmtModule with PublishModule { + def scalaVersion = "2.13.16" + + def artifactName = "mill-iw-support" + def publishVersion = "0.1.0-SNAPSHOT" def pomSettings = PomSettings( description = "Iterative Works Mill Support Library", organization = "works.iterative", - url = "https://github.com/iterative-works/mill-iw-support", + url = "https://github.com/iterative-works/iw-project-support", licenses = Seq(License.MIT), - versionControl = VersionControl.github("iterative-works", "mill-iw-support"), + versionControl = + VersionControl.github("iterative-works", "iw-project-support"), developers = Seq( - Developer("mph", "Michal Přihoda", "https://github.com/iterative-works") + Developer( + "mprihoda", + "Michal Příhoda", + "https://github.com/iterative-works" + ) ) ) @@ -23,10 +36,15 @@ ivy"com.lihaoyi::os-lib:0.9.3" ) - object test extends Tests with ScalafmtModule { + // Mill's own dependencies needed for compilation + def compileIvyDeps = Agg( + ivy"com.lihaoyi::mill-main:${millVersion}", + ivy"com.lihaoyi::mill-scalalib:${millVersion}" + ) + + object test extends ScalaTests with Utest with ScalafmtModule { def ivyDeps = Agg( ivy"com.lihaoyi::utest:0.8.2" ) - def testFramework = "utest.runner.Framework" } -} \ No newline at end of file +} diff --git a/mill-iw-support/core/src/IWBomModule.scala b/mill-iw-support/core/src/IWBomModule.scala new file mode 100644 index 0000000..d8e70bb --- /dev/null +++ b/mill-iw-support/core/src/IWBomModule.scala @@ -0,0 +1,97 @@ +package works.iterative.mill + +import mill._ +import mill.scalalib._ +import mill.scalalib.publish._ + +/** Standard Bill of Materials (BOM) module for Iterative Works projects using + * Mill. This module centralizes dependency versioning across projects. + * + * A BOM module allows: + * 1. Centralized version management for dependencies 2. Enforcing specific + * versions of transitive dependencies 3. Applying uniform dependency + * management across multiple modules 4. Publishing a BOM artifact that + * other projects can consume + * + * Usage: + * 1. Create a BOM module in your build: + * ```scala + * object bom extends IWBomModule { + * def publishVersion = "0.1.0-SNAPSHOT" + * + * // Add your organization-specific customizations here + * def pomSettings = PomSettings(...) + * } + * ``` + * + * 2. Use the BOM in your modules: + * ```scala + * object myModule extends IWScalaModule { + * def bomModuleDeps = Seq(bom) + * + * def ivyDeps = Agg( + * // No need to specify versions as they come from the BOM + * ivy"dev.zio::zio", + * ivy"dev.zio::zio-json" + * ) + * } + * ``` + */ +trait IWBomModule extends BomModule with ScalaModule with IWScalaVersions { + + override def scalaVersion: T[String] = scala3Version + + /** The default implementation includes all standard IW dependencies from + * IWMillVersions and IWMillDeps. + */ + override def depManagement = T { + val V = IWMillVersions + + Agg( + // ZIO Core + ivy"dev.zio::zio:${V.zio}", + ivy"dev.zio::zio-test:${V.zio}", + ivy"dev.zio::zio-test-sbt:${V.zio}", + ivy"dev.zio::zio-streams:${V.zio}", + + // ZIO Extensions + ivy"dev.zio::zio-config:${V.zioConfig}", + ivy"dev.zio::zio-config-typesafe:${V.zioConfig}", + ivy"dev.zio::zio-config-magnolia:${V.zioConfig}", + ivy"dev.zio::zio-json:${V.zioJson}", + ivy"dev.zio::zio-logging:${V.zioLogging}", + ivy"dev.zio::zio-logging-slf4j:${V.zioLogging}", + ivy"dev.zio::zio-prelude:${V.zioPrelude}", + ivy"dev.zio::zio-interop-cats:${V.zioInteropCats}", + ivy"dev.zio::zio-interop-reactivestreams:${V.zioInteropReactiveStreams}", + ivy"dev.zio::zio-nio:${V.zioNIO}", + ivy"dev.zio::zio-cli:${V.zioCli}", + ivy"dev.zio::zio-schema:${V.zioSchema}", + ivy"dev.zio::zio-schema-derivation:${V.zioSchema}", + + // Database + ivy"io.getquill::quill-jdbc-zio:${V.quill}", + ivy"com.augustnagro::magnum:${V.magnum}", + ivy"com.augustnagro::magnumzio:${V.magnum}", + ivy"com.augustnagro::magnumpg:${V.magnum}", + + // HTTP + ivy"com.softwaremill.sttp.tapir::tapir-core:${V.tapir}", + ivy"com.softwaremill.sttp.tapir::tapir-zio:${V.tapir}", + ivy"com.softwaremill.sttp.tapir::tapir-json-zio:${V.tapir}", + + // UI/Frontend + ivy"com.lihaoyi::scalatags:${V.scalatags}", + ivy"com.raquo::laminar:${V.laminar}", + ivy"com.raquo::waypoint:${V.waypoint}", + ivy"be.doeraene::url-dsl:${V.urlDsl}", + + // Testing + ivy"org.scalatest::scalatest:${V.scalaTest}", + ivy"com.lihaoyi::utest:${V.utest}", + + // Logging + ivy"ch.qos.logback:logback-classic:${V.logbackClassic}" + ) + } +} diff --git a/mill-iw-support/core/src/IWMillDeps.scala b/mill-iw-support/core/src/IWMillDeps.scala new file mode 100644 index 0000000..d4cac1c --- /dev/null +++ b/mill-iw-support/core/src/IWMillDeps.scala @@ -0,0 +1,80 @@ +package works.iterative.mill + +import mill.api.Loose + +/** Standardized dependency management for Iterative Works projects using Mill. + * This object provides methods to get dependency strings with proper + * versioning. + * + * All versions are centralized in [[IWMillVersions]]. + * + * This object is designed to be used in build.sc files where the ivy string + * interpolator is available, like: + * + * {{{ + * def ivyDeps = Agg( + * ivy"$${IWMillDeps.zio}", + * ivy"$${IWMillDeps.zioJson}" + * ) + * }}} + */ +object IWMillDeps { + val V = IWMillVersions + + // ZIO Core + val zio = s"dev.zio::zio:${V.zio}" + val zioTest = s"dev.zio::zio-test:${V.zio}" + val zioTestSbt = s"dev.zio::zio-test-sbt:${V.zio}" + val zioStreams = s"dev.zio::zio-streams:${V.zio}" + + // ZIO Extensions + val zioConfig = s"dev.zio::zio-config:${V.zioConfig}" + val zioConfigTypesafe = s"dev.zio::zio-config-typesafe:${V.zioConfig}" + val zioConfigMagnolia = s"dev.zio::zio-config-magnolia:${V.zioConfig}" + val zioJson = s"dev.zio::zio-json:${V.zioJson}" + val zioLogging = s"dev.zio::zio-logging:${V.zioLogging}" + val zioLoggingSlf4j = s"dev.zio::zio-logging-slf4j:${V.zioLogging}" + val zioPrelude = s"dev.zio::zio-prelude:${V.zioPrelude}" + val zioInteropCats = s"dev.zio::zio-interop-cats:${V.zioInteropCats}" + val zioInteropReactiveStreams = + s"dev.zio::zio-interop-reactivestreams:${V.zioInteropReactiveStreams}" + val zioNIO = s"dev.zio::zio-nio:${V.zioNIO}" + val zioCli = s"dev.zio::zio-cli:${V.zioCli}" + val zioSchema = s"dev.zio::zio-schema:${V.zioSchema}" + val zioSchemaDerivation = s"dev.zio::zio-schema-derivation:${V.zioSchema}" + + // Collection of ZIO dependencies + val zioCore = List(zio, zioTest, zioTestSbt) + val zioJson_ = List(zioJson) + val zioAll = zioCore ++ List( + zioStreams, + zioConfig, + zioConfigTypesafe, + zioConfigMagnolia, + zioLogging, + zioPrelude + ) ++ zioJson_ + + // Database + val quill = s"io.getquill::quill-jdbc-zio:${V.quill}" + val magnum = s"com.augustnagro::magnum_3:${V.magnum}" + val magnumZIO = s"com.augustnagro::magnumzio_3:${V.magnum}" + val magnumPG = s"com.augustnagro::magnumpg_3:${V.magnum}" + + // HTTP + val tapirCore = s"com.softwaremill.sttp.tapir::tapir-core:${V.tapir}" + val tapirZIO = s"com.softwaremill.sttp.tapir::tapir-zio:${V.tapir}" + val tapirZIOJson = s"com.softwaremill.sttp.tapir::tapir-json-zio:${V.tapir}" + + // UI/Frontend + val scalatags = s"com.lihaoyi::scalatags:${V.scalatags}" + val laminar = s"com.raquo::laminar:${V.laminar}" + val waypoint = s"com.raquo::waypoint:${V.waypoint}" + val urlDsl = s"be.doeraene::url-dsl:${V.urlDsl}" + + // Testing + val scalaTest = s"org.scalatest::scalatest:${V.scalaTest}" + + // Logging + val logbackClassic = s"ch.qos.logback:logback-classic:${V.logbackClassic}" +} diff --git a/mill-iw-support/core/src/IWMillVersions.scala b/mill-iw-support/core/src/IWMillVersions.scala new file mode 100644 index 0000000..0ad9bd2 --- /dev/null +++ b/mill-iw-support/core/src/IWMillVersions.scala @@ -0,0 +1,56 @@ +package works.iterative.mill + +/** + * Centralized version management for Iterative Works projects using Mill. + * This object contains all library versions used across projects. + * + * Update this object to bump versions for all projects. + */ +object IWMillVersions { + // ZIO Ecosystem + val zio = "2.1.16" + val zioConfig = "4.0.3" + val zioCli = "0.7.0" + val zioInteropCats = "23.1.0.3" + val zioInteropReactiveStreams = "2.0.2" + val zioJson = "0.7.36" + val zioLogging = "2.5.0" + val zioNIO = "2.0.2" + val zioPrelude = "1.0.0-RC39" + val zioQuery = "0.7.6" + val zioSchema = "1.6.3" + + // Akka Ecosystem + val akka = "2.6.18" + val akkaHttp = "10.4.0" + + // Other Libraries + val cats = "2.13.0" + val utest = "0.8.2" + val caliban = "2.9.2" + val chimney = "1.7.3" + val ducktape = "0.2.7" + val elastic4s = "7.12.4" + val http4s = "0.23.30" + val http4sBlaze = "0.23.17" + val http4sPac4J = "5.0.0" + val laminar = "17.2.0" + val laminext = "0.17.0" + val logbackClassic = "1.5.17" + val magnum = "2.0.0-M1" + val pac4j = "6.0.3" + val play = "2.8.18" + val playJson = "2.9.3" + val quill = "4.8.6" + val scalaJsMacroTaskExecutor = "1.1.1" + val scalaJsJavaSecureRandom = "1.0.0" + val scalatags = "0.13.1" + val scalaTest = "3.2.19" + val slick = "3.3.3" + val sttpClient3 = "3.10.3" + val sttpClient4 = "4.0.0-RC1" + val support = "1.7.0" + val tapir = "1.11.16" + val urlDsl = "0.7.0" + val waypoint = "9.0.0" +} \ No newline at end of file diff --git a/mill-iw-support/core/src/IWPublishModule.scala b/mill-iw-support/core/src/IWPublishModule.scala new file mode 100644 index 0000000..5ae0ade --- /dev/null +++ b/mill-iw-support/core/src/IWPublishModule.scala @@ -0,0 +1,22 @@ +package works.iterative.mill + +import mill._ +import mill.scalalib._ +import mill.scalalib.publish._ + +/** Standard publish module trait for Iterative Works projects using Mill. This + * trait sets up standard publishing configuration for IW Maven repositories. + * + * To use this trait, simply extend it in your module and define: + * - publishVersion: The version to publish + * - pomSettings: The POM settings for the publication + * + * Use IW_USERNAME and IW_PASSWORD environment variables for authentication. + */ +trait IWPublishModule extends PublishModule { + + override def sonatypeUri = "https://dig.iterative.works/maven/releases" + override def sonatypeSnapshotUri = + "https://dig.iterative.works/maven/snapshots" + +} diff --git a/mill-iw-support/core/src/IWScalaModule.scala b/mill-iw-support/core/src/IWScalaModule.scala new file mode 100644 index 0000000..021bccc --- /dev/null +++ b/mill-iw-support/core/src/IWScalaModule.scala @@ -0,0 +1,97 @@ +package works.iterative.mill + +import mill._ +import mill.scalalib._ +import mill.scalalib.scalafmt._ + +/** Standard Scala module trait for Iterative Works projects using Mill. This + * trait sets up standard compiler options and configuration. + */ +trait IWScalaModule + extends ScalaModule + with ScalafmtModule + with IWScalaVersions { + + override def scalaVersion = scala3Version + + // Define base compiler options based on Scala version + def baseScalacOptions = T { + if (scalaVersion().startsWith("2.")) { + Seq( + "-encoding", + "utf8", + "-feature", + "-unchecked", + "-language:existentials", + "-language:experimental.macros", + "-language:higherKinds", + "-language:implicitConversions", + "-Xlint:adapted-args", + "-Xlint:constant", + "-Xlint:delayedinit-select", + "-Xlint:deprecation", + "-Xlint:doc-detached", + "-Xlint:implicit-recursion", + "-Xlint:implicit-not-found", + "-Xlint:inaccessible", + "-Xlint:infer-any", + "-Xlint:missing-interpolator", + "-Xlint:nullary-unit", + "-Xlint:option-implicit", + "-Xlint:package-object-classes", + "-Xlint:poly-implicit-overload", + "-Xlint:private-shadow", + "-Xlint:stars-align", + "-Xlint:strict-unsealed-patmat", + "-Xlint:type-parameter-shadow", + "-Xlint:-byname-implicit", + "-Wdead-code", + "-Wextra-implicit", + "-Wnumeric-widen", + "-Wvalue-discard", + "-Wnonunit-statement", + "-Wunused:nowarn", + "-Wunused:implicits", + "-Wunused:explicits", + "-Wunused:imports", + "-Wunused:locals", + "-Wunused:params", + "-Wunused:patvars", + "-Wunused:privates" + ) + } else { + Seq( + "-encoding", + "utf8", + "-deprecation", + "-feature", + "-unchecked", + "-language:experimental.macros", + "-language:higherKinds", + "-language:implicitConversions", + "-Xkind-projector", + "-Wvalue-discard", + "-Wnonunit-statement", + "-Wunused:implicits", + "-Wunused:explicits", + "-Wunused:imports", + "-Wunused:locals", + "-Wunused:params", + "-Wunused:privates" + ) + } + } + + // Enable SemanticDB for tooling + def semanticDbOptions = Seq( + "-Xsemanticdb" + ) + + // Override scalacOptions to include both base options and SemanticDB options + override def scalacOptions = T { + super.scalacOptions() ++ baseScalacOptions() ++ semanticDbOptions + } + + // Base test configuration + trait IWTests extends ScalaTests with ScalafmtModule +} diff --git a/mill-iw-support/core/src/IWScalaVersions.scala b/mill-iw-support/core/src/IWScalaVersions.scala new file mode 100644 index 0000000..aa70cb6 --- /dev/null +++ b/mill-iw-support/core/src/IWScalaVersions.scala @@ -0,0 +1,9 @@ +package works.iterative.mill + +trait IWScalaVersions { + val scala2Version = "2.13.16" + val scala3Version = "3.6.4" + val scala3LTSVersion = "3.3.5" +} + +object IWScalaVersions extends IWScalaVersions diff --git a/mill-iw-support/core/src/package.scala b/mill-iw-support/core/src/package.scala new file mode 100644 index 0000000..1ba4556 --- /dev/null +++ b/mill-iw-support/core/src/package.scala @@ -0,0 +1,6 @@ +package works.iterative + +package object mill { + val IWVersions = IWMillVersions + val IWDeps = IWMillDeps +} diff --git a/mill-iw-support/example/.mill-version b/mill-iw-support/example/.mill-version new file mode 100644 index 0000000..54dbed4 --- /dev/null +++ b/mill-iw-support/example/.mill-version @@ -0,0 +1 @@ +0.12.10 diff --git a/mill-iw-support/example/build.sc b/mill-iw-support/example/build.sc index 5da77ab..cae5a6a 100644 --- a/mill-iw-support/example/build.sc +++ b/mill-iw-support/example/build.sc @@ -1,11 +1,22 @@ import mill._ import mill.scalalib._ +import mill.scalalib.publish._ +import mill.scalalib.scalafmt._ + +// Import our IW Mill Support library import $ivy.`works.iterative::mill-iw-support:0.1.0-SNAPSHOT` import works.iterative.mill._ +// Define a BOM (Bill of Materials) module +object bom extends IWBomModule + +// Example module using the IW Mill support with BOM object example extends IWScalaModule with IWPublishModule { def publishVersion = "0.1.0-SNAPSHOT" - + + // Use the BOM module for dependency management + override def bomModuleDeps = Seq(bom) + def pomSettings = PomSettings( description = "Example Mill Project using IW Mill Support", organization = "com.example", @@ -16,15 +27,25 @@ Developer("developer", "Example Developer", "https://github.com/example") ) ) - - // Use centralized dependency versions and definitions - def ivyDeps = super.ivyDeps() ++ IWMillDeps.useZIO() ++ Agg( - IWMillDeps.zioJson, - IWMillDeps.tapirCore, - IWMillDeps.tapirZIO, - IWMillDeps.tapirZIOJson + + // With BOM, we don't need to specify versions + // They're managed by the BOM module + def ivyDeps = super.ivyDeps() ++ Agg( + ivy"dev.zio::zio", + ivy"dev.zio::zio-json", + ivy"com.softwaremill.sttp.tapir::tapir-core", + ivy"com.softwaremill.sttp.tapir::tapir-zio", + ivy"com.softwaremill.sttp.tapir::tapir-json-zio" ) - - // Define test module extending IWTests - object test extends Tests with IWTests -} \ No newline at end of file + + // Define test module using standard Mill approach + object test extends ScalaTests with TestModule.ZioTest { + // Use the BOM module for dependency management + override def bomModuleDeps = Seq(bom) + + def ivyDeps = super.ivyDeps() ++ Agg( + ivy"dev.zio::zio-test", // Version comes from BOM + ivy"dev.zio::zio-test-sbt" // Version comes from BOM + ) + } +} diff --git a/mill-iw-support/example/example/src/Example.scala b/mill-iw-support/example/example/src/Example.scala new file mode 100644 index 0000000..aa28980 --- /dev/null +++ b/mill-iw-support/example/example/src/Example.scala @@ -0,0 +1,27 @@ +package example + +import zio._ +import zio.json._ + +// Simple example class +case class User(id: String, name: String, email: String) + +object User { + // JSON codecs using ZIO JSON + implicit val encoder: JsonEncoder[User] = DeriveJsonEncoder.gen[User] + implicit val decoder: JsonDecoder[User] = DeriveJsonDecoder.gen[User] +} + +object Example extends ZIOAppDefault { + + val program = for { + _ <- Console.printLine("Starting example application") + user = User("1", "John Doe", "john@example.com") + json <- ZIO.succeed(user.toJson) + _ <- Console.printLine(s"User as JSON: $json") + parsed <- ZIO.fromEither(json.fromJson[User]) + _ <- Console.printLine(s"Parsed user: $parsed") + } yield () + + def run = program +} \ No newline at end of file diff --git a/mill-iw-support/example/example/test/src/ExampleTest.scala b/mill-iw-support/example/example/test/src/ExampleTest.scala new file mode 100644 index 0000000..951c2d8 --- /dev/null +++ b/mill-iw-support/example/example/test/src/ExampleTest.scala @@ -0,0 +1,26 @@ +package example + +import zio._ +import zio.json._ +import zio.test._ +import zio.test.Assertion._ + +object ExampleTest extends ZIOSpecDefault { + override def spec = suite("Example suite")( + test("User JSON serialization") { + val user = User("1", "John Doe", "john@example.com") + val json = user.toJson + val expected = + """{"id":"1","name":"John Doe","email":"john@example.com"}""" + + assertTrue(json == expected) + }, + test("User JSON deserialization") { + val json = """{"id":"1","name":"John Doe","email":"john@example.com"}""" + val parsed = json.fromJson[User] + val expected = User("1", "John Doe", "john@example.com") + + assertTrue(parsed == Right(expected)) + } + ) +} diff --git a/mill-iw-support/example/mill b/mill-iw-support/example/mill new file mode 100755 index 0000000..f349762 --- /dev/null +++ b/mill-iw-support/example/mill @@ -0,0 +1,265 @@ +#!/usr/bin/env sh + +# This is a wrapper script, that automatically download mill from GitHub release pages +# You can give the required mill version with --mill-version parameter +# If no version is given, it falls back to the value of DEFAULT_MILL_VERSION +# +# Original Project page: https://github.com/lefou/millw +# Script Version: 0.4.12 +# +# If you want to improve this script, please also contribute your changes back! +# +# Licensed under the Apache License, Version 2.0 + +set -e + +if [ -z "${DEFAULT_MILL_VERSION}" ] ; then + DEFAULT_MILL_VERSION=0.12.10 +fi + + +if [ -z "${GITHUB_RELEASE_CDN}" ] ; then + GITHUB_RELEASE_CDN="" +fi + + +MILL_REPO_URL="https://github.com/com-lihaoyi/mill" + +if [ -z "${CURL_CMD}" ] ; then + CURL_CMD=curl +fi + +# Explicit commandline argument takes precedence over all other methods +if [ "$1" = "--mill-version" ] ; then + shift + if [ "x$1" != "x" ] ; then + MILL_VERSION="$1" + shift + else + echo "You specified --mill-version without a version." 1>&2 + echo "Please provide a version that matches one provided on" 1>&2 + echo "${MILL_REPO_URL}/releases" 1>&2 + false + fi +fi + +# Please note, that if a MILL_VERSION is already set in the environment, +# We reuse it's value and skip searching for a value. + +# If not already set, read .mill-version file +if [ -z "${MILL_VERSION}" ] ; then + if [ -f ".mill-version" ] ; then + MILL_VERSION="$(tr '\r' '\n' < .mill-version | head -n 1 2> /dev/null)" + elif [ -f ".config/mill-version" ] ; then + MILL_VERSION="$(tr '\r' '\n' < .config/mill-version | head -n 1 2> /dev/null)" + fi +fi + +MILL_USER_CACHE_DIR="${XDG_CACHE_HOME:-${HOME}/.cache}/mill" + +if [ -z "${MILL_DOWNLOAD_PATH}" ] ; then + MILL_DOWNLOAD_PATH="${MILL_USER_CACHE_DIR}/download" +fi + +# If not already set, try to fetch newest from Github +if [ -z "${MILL_VERSION}" ] ; then + # TODO: try to load latest version from release page + echo "No mill version specified." 1>&2 + echo "You should provide a version via '.mill-version' file or --mill-version option." 1>&2 + + mkdir -p "${MILL_DOWNLOAD_PATH}" + LANG=C touch -d '1 hour ago' "${MILL_DOWNLOAD_PATH}/.expire_latest" 2>/dev/null || ( + # we might be on OSX or BSD which don't have -d option for touch + # but probably a -A [-][[hh]mm]SS + touch "${MILL_DOWNLOAD_PATH}/.expire_latest"; touch -A -010000 "${MILL_DOWNLOAD_PATH}/.expire_latest" + ) || ( + # in case we still failed, we retry the first touch command with the intention + # to show the (previously suppressed) error message + LANG=C touch -d '1 hour ago' "${MILL_DOWNLOAD_PATH}/.expire_latest" + ) + + # POSIX shell variant of bash's -nt operator, see https://unix.stackexchange.com/a/449744/6993 + # if [ "${MILL_DOWNLOAD_PATH}/.latest" -nt "${MILL_DOWNLOAD_PATH}/.expire_latest" ] ; then + if [ -n "$(find -L "${MILL_DOWNLOAD_PATH}/.latest" -prune -newer "${MILL_DOWNLOAD_PATH}/.expire_latest")" ]; then + # we know a current latest version + MILL_VERSION=$(head -n 1 "${MILL_DOWNLOAD_PATH}"/.latest 2> /dev/null) + fi + + if [ -z "${MILL_VERSION}" ] ; then + # we don't know a current latest version + echo "Retrieving latest mill version ..." 1>&2 + LANG=C ${CURL_CMD} -s -i -f -I ${MILL_REPO_URL}/releases/latest 2> /dev/null | grep --ignore-case Location: | sed s'/^.*tag\///' | tr -d '\r\n' > "${MILL_DOWNLOAD_PATH}/.latest" + MILL_VERSION=$(head -n 1 "${MILL_DOWNLOAD_PATH}"/.latest 2> /dev/null) + fi + + if [ -z "${MILL_VERSION}" ] ; then + # Last resort + MILL_VERSION="${DEFAULT_MILL_VERSION}" + echo "Falling back to hardcoded mill version ${MILL_VERSION}" 1>&2 + else + echo "Using mill version ${MILL_VERSION}" 1>&2 + fi +fi + +MILL_NATIVE_SUFFIX="-native" +FULL_MILL_VERSION=$MILL_VERSION +ARTIFACT_SUFFIX="" +case "$MILL_VERSION" in + *"$MILL_NATIVE_SUFFIX") + MILL_VERSION=${MILL_VERSION%"$MILL_NATIVE_SUFFIX"} + if [ "$(expr substr $(uname -s) 1 5 2>/dev/null)" = "Linux" ]; then + if [ "$(uname -m)" = "aarch64" ]; then + ARTIFACT_SUFFIX="-native-linux-aarch64" + else + ARTIFACT_SUFFIX="-native-linux-amd64" + fi + elif [ "$(uname)" = "Darwin" ]; then + if [ "$(uname -m)" = "arm64" ]; then + ARTIFACT_SUFFIX="-native-mac-aarch64" + else + ARTIFACT_SUFFIX="-native-mac-amd64" + fi + else + echo "This native mill launcher supports only Linux and macOS." 1>&2 + exit 1 + fi +esac + +MILL="${MILL_DOWNLOAD_PATH}/${FULL_MILL_VERSION}" + +try_to_use_system_mill() { + if [ "$(uname)" != "Linux" ]; then + return 0 + fi + + MILL_IN_PATH="$(command -v mill || true)" + + if [ -z "${MILL_IN_PATH}" ]; then + return 0 + fi + + SYSTEM_MILL_FIRST_TWO_BYTES=$(head --bytes=2 "${MILL_IN_PATH}") + if [ "${SYSTEM_MILL_FIRST_TWO_BYTES}" = "#!" ]; then + # MILL_IN_PATH is (very likely) a shell script and not the mill + # executable, ignore it. + return 0 + fi + + SYSTEM_MILL_PATH=$(readlink -e "${MILL_IN_PATH}") + SYSTEM_MILL_SIZE=$(stat --format=%s "${SYSTEM_MILL_PATH}") + SYSTEM_MILL_MTIME=$(stat --format=%y "${SYSTEM_MILL_PATH}") + + if [ ! -d "${MILL_USER_CACHE_DIR}" ]; then + mkdir -p "${MILL_USER_CACHE_DIR}" + fi + + SYSTEM_MILL_INFO_FILE="${MILL_USER_CACHE_DIR}/system-mill-info" + if [ -f "${SYSTEM_MILL_INFO_FILE}" ]; then + parseSystemMillInfo() { + LINE_NUMBER="${1}" + # Select the line number of the SYSTEM_MILL_INFO_FILE, cut the + # variable definition in that line in two halves and return + # the value, and finally remove the quotes. + sed -n "${LINE_NUMBER}p" "${SYSTEM_MILL_INFO_FILE}" |\ + cut -d= -f2 |\ + sed 's/"\(.*\)"/\1/' + } + + CACHED_SYSTEM_MILL_PATH=$(parseSystemMillInfo 1) + CACHED_SYSTEM_MILL_VERSION=$(parseSystemMillInfo 2) + CACHED_SYSTEM_MILL_SIZE=$(parseSystemMillInfo 3) + CACHED_SYSTEM_MILL_MTIME=$(parseSystemMillInfo 4) + + if [ "${SYSTEM_MILL_PATH}" = "${CACHED_SYSTEM_MILL_PATH}" ] \ + && [ "${SYSTEM_MILL_SIZE}" = "${CACHED_SYSTEM_MILL_SIZE}" ] \ + && [ "${SYSTEM_MILL_MTIME}" = "${CACHED_SYSTEM_MILL_MTIME}" ]; then + if [ "${CACHED_SYSTEM_MILL_VERSION}" = "${MILL_VERSION}" ]; then + MILL="${SYSTEM_MILL_PATH}" + return 0 + else + return 0 + fi + fi + fi + + SYSTEM_MILL_VERSION=$(${SYSTEM_MILL_PATH} --version | head -n1 | sed -n 's/^Mill.*version \(.*\)/\1/p') + + cat < "${SYSTEM_MILL_INFO_FILE}" +CACHED_SYSTEM_MILL_PATH="${SYSTEM_MILL_PATH}" +CACHED_SYSTEM_MILL_VERSION="${SYSTEM_MILL_VERSION}" +CACHED_SYSTEM_MILL_SIZE="${SYSTEM_MILL_SIZE}" +CACHED_SYSTEM_MILL_MTIME="${SYSTEM_MILL_MTIME}" +EOF + + if [ "${SYSTEM_MILL_VERSION}" = "${MILL_VERSION}" ]; then + MILL="${SYSTEM_MILL_PATH}" + fi +} +try_to_use_system_mill + +# If not already downloaded, download it +if [ ! -s "${MILL}" ] ; then + + # support old non-XDG download dir + MILL_OLD_DOWNLOAD_PATH="${HOME}/.mill/download" + OLD_MILL="${MILL_OLD_DOWNLOAD_PATH}/${MILL_VERSION}" + if [ -x "${OLD_MILL}" ] ; then + MILL="${OLD_MILL}" + else + case $MILL_VERSION in + 0.0.* | 0.1.* | 0.2.* | 0.3.* | 0.4.* ) + DOWNLOAD_SUFFIX="" + DOWNLOAD_FROM_MAVEN=0 + ;; + 0.5.* | 0.6.* | 0.7.* | 0.8.* | 0.9.* | 0.10.* | 0.11.0-M* ) + DOWNLOAD_SUFFIX="-assembly" + DOWNLOAD_FROM_MAVEN=0 + ;; + *) + DOWNLOAD_SUFFIX="-assembly" + DOWNLOAD_FROM_MAVEN=1 + ;; + esac + + DOWNLOAD_FILE=$(mktemp mill.XXXXXX) + + if [ "$DOWNLOAD_FROM_MAVEN" = "1" ] ; then + DOWNLOAD_URL="https://repo1.maven.org/maven2/com/lihaoyi/mill-dist${ARTIFACT_SUFFIX}/${MILL_VERSION}/mill-dist${ARTIFACT_SUFFIX}-${MILL_VERSION}.jar" + else + MILL_VERSION_TAG=$(echo "$MILL_VERSION" | sed -E 's/([^-]+)(-M[0-9]+)?(-.*)?/\1\2/') + DOWNLOAD_URL="${GITHUB_RELEASE_CDN}${MILL_REPO_URL}/releases/download/${MILL_VERSION_TAG}/${MILL_VERSION}${DOWNLOAD_SUFFIX}" + unset MILL_VERSION_TAG + fi + + # TODO: handle command not found + echo "Downloading mill ${MILL_VERSION} from ${DOWNLOAD_URL} ..." 1>&2 + ${CURL_CMD} -f -L -o "${DOWNLOAD_FILE}" "${DOWNLOAD_URL}" + chmod +x "${DOWNLOAD_FILE}" + mkdir -p "${MILL_DOWNLOAD_PATH}" + mv "${DOWNLOAD_FILE}" "${MILL}" + + unset DOWNLOAD_FILE + unset DOWNLOAD_SUFFIX + fi +fi + +if [ -z "$MILL_MAIN_CLI" ] ; then + MILL_MAIN_CLI="${0}" +fi + +MILL_FIRST_ARG="" +if [ "$1" = "--bsp" ] || [ "$1" = "-i" ] || [ "$1" = "--interactive" ] || [ "$1" = "--no-server" ] || [ "$1" = "--repl" ] || [ "$1" = "--help" ] ; then + # Need to preserve the first position of those listed options + MILL_FIRST_ARG=$1 + shift +fi + +unset MILL_DOWNLOAD_PATH +unset MILL_OLD_DOWNLOAD_PATH +unset OLD_MILL +unset MILL_VERSION +unset MILL_REPO_URL + +# We don't quote MILL_FIRST_ARG on purpose, so we can expand the empty value without quotes +# shellcheck disable=SC2086 +exec "${MILL}" $MILL_FIRST_ARG -D "mill.main.cli=${MILL_MAIN_CLI}" "$@" \ No newline at end of file diff --git a/mill-iw-support/example/src/Example.scala b/mill-iw-support/example/src/Example.scala deleted file mode 100644 index aa28980..0000000 --- a/mill-iw-support/example/src/Example.scala +++ /dev/null @@ -1,27 +0,0 @@ -package example - -import zio._ -import zio.json._ - -// Simple example class -case class User(id: String, name: String, email: String) - -object User { - // JSON codecs using ZIO JSON - implicit val encoder: JsonEncoder[User] = DeriveJsonEncoder.gen[User] - implicit val decoder: JsonDecoder[User] = DeriveJsonDecoder.gen[User] -} - -object Example extends ZIOAppDefault { - - val program = for { - _ <- Console.printLine("Starting example application") - user = User("1", "John Doe", "john@example.com") - json <- ZIO.succeed(user.toJson) - _ <- Console.printLine(s"User as JSON: $json") - parsed <- ZIO.fromEither(json.fromJson[User]) - _ <- Console.printLine(s"Parsed user: $parsed") - } yield () - - def run = program -} \ No newline at end of file diff --git a/mill-iw-support/example/test/src/ExampleTest.scala b/mill-iw-support/example/test/src/ExampleTest.scala deleted file mode 100644 index 20f0c06..0000000 --- a/mill-iw-support/example/test/src/ExampleTest.scala +++ /dev/null @@ -1,27 +0,0 @@ -package example - -import utest._ -import zio._ -import zio.json._ -import zio.test._ -import zio.test.Assertion._ - -object ExampleTest extends TestSuite { - val tests = Tests { - test("User JSON serialization") { - val user = User("1", "John Doe", "john@example.com") - val json = user.toJson - val expected = """{"id":"1","name":"John Doe","email":"john@example.com"}""" - - assert(json == expected) - } - - test("User JSON deserialization") { - val json = """{"id":"1","name":"John Doe","email":"john@example.com"}""" - val parsed = json.fromJson[User] - val expected = User("1", "John Doe", "john@example.com") - - assert(parsed == Right(expected)) - } - } -} \ No newline at end of file diff --git a/mill-iw-support/mill b/mill-iw-support/mill new file mode 100755 index 0000000..f349762 --- /dev/null +++ b/mill-iw-support/mill @@ -0,0 +1,265 @@ +#!/usr/bin/env sh + +# This is a wrapper script, that automatically download mill from GitHub release pages +# You can give the required mill version with --mill-version parameter +# If no version is given, it falls back to the value of DEFAULT_MILL_VERSION +# +# Original Project page: https://github.com/lefou/millw +# Script Version: 0.4.12 +# +# If you want to improve this script, please also contribute your changes back! +# +# Licensed under the Apache License, Version 2.0 + +set -e + +if [ -z "${DEFAULT_MILL_VERSION}" ] ; then + DEFAULT_MILL_VERSION=0.12.10 +fi + + +if [ -z "${GITHUB_RELEASE_CDN}" ] ; then + GITHUB_RELEASE_CDN="" +fi + + +MILL_REPO_URL="https://github.com/com-lihaoyi/mill" + +if [ -z "${CURL_CMD}" ] ; then + CURL_CMD=curl +fi + +# Explicit commandline argument takes precedence over all other methods +if [ "$1" = "--mill-version" ] ; then + shift + if [ "x$1" != "x" ] ; then + MILL_VERSION="$1" + shift + else + echo "You specified --mill-version without a version." 1>&2 + echo "Please provide a version that matches one provided on" 1>&2 + echo "${MILL_REPO_URL}/releases" 1>&2 + false + fi +fi + +# Please note, that if a MILL_VERSION is already set in the environment, +# We reuse it's value and skip searching for a value. + +# If not already set, read .mill-version file +if [ -z "${MILL_VERSION}" ] ; then + if [ -f ".mill-version" ] ; then + MILL_VERSION="$(tr '\r' '\n' < .mill-version | head -n 1 2> /dev/null)" + elif [ -f ".config/mill-version" ] ; then + MILL_VERSION="$(tr '\r' '\n' < .config/mill-version | head -n 1 2> /dev/null)" + fi +fi + +MILL_USER_CACHE_DIR="${XDG_CACHE_HOME:-${HOME}/.cache}/mill" + +if [ -z "${MILL_DOWNLOAD_PATH}" ] ; then + MILL_DOWNLOAD_PATH="${MILL_USER_CACHE_DIR}/download" +fi + +# If not already set, try to fetch newest from Github +if [ -z "${MILL_VERSION}" ] ; then + # TODO: try to load latest version from release page + echo "No mill version specified." 1>&2 + echo "You should provide a version via '.mill-version' file or --mill-version option." 1>&2 + + mkdir -p "${MILL_DOWNLOAD_PATH}" + LANG=C touch -d '1 hour ago' "${MILL_DOWNLOAD_PATH}/.expire_latest" 2>/dev/null || ( + # we might be on OSX or BSD which don't have -d option for touch + # but probably a -A [-][[hh]mm]SS + touch "${MILL_DOWNLOAD_PATH}/.expire_latest"; touch -A -010000 "${MILL_DOWNLOAD_PATH}/.expire_latest" + ) || ( + # in case we still failed, we retry the first touch command with the intention + # to show the (previously suppressed) error message + LANG=C touch -d '1 hour ago' "${MILL_DOWNLOAD_PATH}/.expire_latest" + ) + + # POSIX shell variant of bash's -nt operator, see https://unix.stackexchange.com/a/449744/6993 + # if [ "${MILL_DOWNLOAD_PATH}/.latest" -nt "${MILL_DOWNLOAD_PATH}/.expire_latest" ] ; then + if [ -n "$(find -L "${MILL_DOWNLOAD_PATH}/.latest" -prune -newer "${MILL_DOWNLOAD_PATH}/.expire_latest")" ]; then + # we know a current latest version + MILL_VERSION=$(head -n 1 "${MILL_DOWNLOAD_PATH}"/.latest 2> /dev/null) + fi + + if [ -z "${MILL_VERSION}" ] ; then + # we don't know a current latest version + echo "Retrieving latest mill version ..." 1>&2 + LANG=C ${CURL_CMD} -s -i -f -I ${MILL_REPO_URL}/releases/latest 2> /dev/null | grep --ignore-case Location: | sed s'/^.*tag\///' | tr -d '\r\n' > "${MILL_DOWNLOAD_PATH}/.latest" + MILL_VERSION=$(head -n 1 "${MILL_DOWNLOAD_PATH}"/.latest 2> /dev/null) + fi + + if [ -z "${MILL_VERSION}" ] ; then + # Last resort + MILL_VERSION="${DEFAULT_MILL_VERSION}" + echo "Falling back to hardcoded mill version ${MILL_VERSION}" 1>&2 + else + echo "Using mill version ${MILL_VERSION}" 1>&2 + fi +fi + +MILL_NATIVE_SUFFIX="-native" +FULL_MILL_VERSION=$MILL_VERSION +ARTIFACT_SUFFIX="" +case "$MILL_VERSION" in + *"$MILL_NATIVE_SUFFIX") + MILL_VERSION=${MILL_VERSION%"$MILL_NATIVE_SUFFIX"} + if [ "$(expr substr $(uname -s) 1 5 2>/dev/null)" = "Linux" ]; then + if [ "$(uname -m)" = "aarch64" ]; then + ARTIFACT_SUFFIX="-native-linux-aarch64" + else + ARTIFACT_SUFFIX="-native-linux-amd64" + fi + elif [ "$(uname)" = "Darwin" ]; then + if [ "$(uname -m)" = "arm64" ]; then + ARTIFACT_SUFFIX="-native-mac-aarch64" + else + ARTIFACT_SUFFIX="-native-mac-amd64" + fi + else + echo "This native mill launcher supports only Linux and macOS." 1>&2 + exit 1 + fi +esac + +MILL="${MILL_DOWNLOAD_PATH}/${FULL_MILL_VERSION}" + +try_to_use_system_mill() { + if [ "$(uname)" != "Linux" ]; then + return 0 + fi + + MILL_IN_PATH="$(command -v mill || true)" + + if [ -z "${MILL_IN_PATH}" ]; then + return 0 + fi + + SYSTEM_MILL_FIRST_TWO_BYTES=$(head --bytes=2 "${MILL_IN_PATH}") + if [ "${SYSTEM_MILL_FIRST_TWO_BYTES}" = "#!" ]; then + # MILL_IN_PATH is (very likely) a shell script and not the mill + # executable, ignore it. + return 0 + fi + + SYSTEM_MILL_PATH=$(readlink -e "${MILL_IN_PATH}") + SYSTEM_MILL_SIZE=$(stat --format=%s "${SYSTEM_MILL_PATH}") + SYSTEM_MILL_MTIME=$(stat --format=%y "${SYSTEM_MILL_PATH}") + + if [ ! -d "${MILL_USER_CACHE_DIR}" ]; then + mkdir -p "${MILL_USER_CACHE_DIR}" + fi + + SYSTEM_MILL_INFO_FILE="${MILL_USER_CACHE_DIR}/system-mill-info" + if [ -f "${SYSTEM_MILL_INFO_FILE}" ]; then + parseSystemMillInfo() { + LINE_NUMBER="${1}" + # Select the line number of the SYSTEM_MILL_INFO_FILE, cut the + # variable definition in that line in two halves and return + # the value, and finally remove the quotes. + sed -n "${LINE_NUMBER}p" "${SYSTEM_MILL_INFO_FILE}" |\ + cut -d= -f2 |\ + sed 's/"\(.*\)"/\1/' + } + + CACHED_SYSTEM_MILL_PATH=$(parseSystemMillInfo 1) + CACHED_SYSTEM_MILL_VERSION=$(parseSystemMillInfo 2) + CACHED_SYSTEM_MILL_SIZE=$(parseSystemMillInfo 3) + CACHED_SYSTEM_MILL_MTIME=$(parseSystemMillInfo 4) + + if [ "${SYSTEM_MILL_PATH}" = "${CACHED_SYSTEM_MILL_PATH}" ] \ + && [ "${SYSTEM_MILL_SIZE}" = "${CACHED_SYSTEM_MILL_SIZE}" ] \ + && [ "${SYSTEM_MILL_MTIME}" = "${CACHED_SYSTEM_MILL_MTIME}" ]; then + if [ "${CACHED_SYSTEM_MILL_VERSION}" = "${MILL_VERSION}" ]; then + MILL="${SYSTEM_MILL_PATH}" + return 0 + else + return 0 + fi + fi + fi + + SYSTEM_MILL_VERSION=$(${SYSTEM_MILL_PATH} --version | head -n1 | sed -n 's/^Mill.*version \(.*\)/\1/p') + + cat < "${SYSTEM_MILL_INFO_FILE}" +CACHED_SYSTEM_MILL_PATH="${SYSTEM_MILL_PATH}" +CACHED_SYSTEM_MILL_VERSION="${SYSTEM_MILL_VERSION}" +CACHED_SYSTEM_MILL_SIZE="${SYSTEM_MILL_SIZE}" +CACHED_SYSTEM_MILL_MTIME="${SYSTEM_MILL_MTIME}" +EOF + + if [ "${SYSTEM_MILL_VERSION}" = "${MILL_VERSION}" ]; then + MILL="${SYSTEM_MILL_PATH}" + fi +} +try_to_use_system_mill + +# If not already downloaded, download it +if [ ! -s "${MILL}" ] ; then + + # support old non-XDG download dir + MILL_OLD_DOWNLOAD_PATH="${HOME}/.mill/download" + OLD_MILL="${MILL_OLD_DOWNLOAD_PATH}/${MILL_VERSION}" + if [ -x "${OLD_MILL}" ] ; then + MILL="${OLD_MILL}" + else + case $MILL_VERSION in + 0.0.* | 0.1.* | 0.2.* | 0.3.* | 0.4.* ) + DOWNLOAD_SUFFIX="" + DOWNLOAD_FROM_MAVEN=0 + ;; + 0.5.* | 0.6.* | 0.7.* | 0.8.* | 0.9.* | 0.10.* | 0.11.0-M* ) + DOWNLOAD_SUFFIX="-assembly" + DOWNLOAD_FROM_MAVEN=0 + ;; + *) + DOWNLOAD_SUFFIX="-assembly" + DOWNLOAD_FROM_MAVEN=1 + ;; + esac + + DOWNLOAD_FILE=$(mktemp mill.XXXXXX) + + if [ "$DOWNLOAD_FROM_MAVEN" = "1" ] ; then + DOWNLOAD_URL="https://repo1.maven.org/maven2/com/lihaoyi/mill-dist${ARTIFACT_SUFFIX}/${MILL_VERSION}/mill-dist${ARTIFACT_SUFFIX}-${MILL_VERSION}.jar" + else + MILL_VERSION_TAG=$(echo "$MILL_VERSION" | sed -E 's/([^-]+)(-M[0-9]+)?(-.*)?/\1\2/') + DOWNLOAD_URL="${GITHUB_RELEASE_CDN}${MILL_REPO_URL}/releases/download/${MILL_VERSION_TAG}/${MILL_VERSION}${DOWNLOAD_SUFFIX}" + unset MILL_VERSION_TAG + fi + + # TODO: handle command not found + echo "Downloading mill ${MILL_VERSION} from ${DOWNLOAD_URL} ..." 1>&2 + ${CURL_CMD} -f -L -o "${DOWNLOAD_FILE}" "${DOWNLOAD_URL}" + chmod +x "${DOWNLOAD_FILE}" + mkdir -p "${MILL_DOWNLOAD_PATH}" + mv "${DOWNLOAD_FILE}" "${MILL}" + + unset DOWNLOAD_FILE + unset DOWNLOAD_SUFFIX + fi +fi + +if [ -z "$MILL_MAIN_CLI" ] ; then + MILL_MAIN_CLI="${0}" +fi + +MILL_FIRST_ARG="" +if [ "$1" = "--bsp" ] || [ "$1" = "-i" ] || [ "$1" = "--interactive" ] || [ "$1" = "--no-server" ] || [ "$1" = "--repl" ] || [ "$1" = "--help" ] ; then + # Need to preserve the first position of those listed options + MILL_FIRST_ARG=$1 + shift +fi + +unset MILL_DOWNLOAD_PATH +unset MILL_OLD_DOWNLOAD_PATH +unset OLD_MILL +unset MILL_VERSION +unset MILL_REPO_URL + +# We don't quote MILL_FIRST_ARG on purpose, so we can expand the empty value without quotes +# shellcheck disable=SC2086 +exec "${MILL}" $MILL_FIRST_ARG -D "mill.main.cli=${MILL_MAIN_CLI}" "$@" \ No newline at end of file diff --git a/mill-iw-support/src/main/scala/works/iterative/mill/IWMillDeps.scala b/mill-iw-support/src/main/scala/works/iterative/mill/IWMillDeps.scala deleted file mode 100644 index e21af78..0000000 --- a/mill-iw-support/src/main/scala/works/iterative/mill/IWMillDeps.scala +++ /dev/null @@ -1,78 +0,0 @@ -package works.iterative.mill - -import mill.api.Loose -import mill.scalalib.Dep - -/** - * Standardized dependency management for Iterative Works projects using Mill. - * This object provides methods to get dependencies with proper versioning. - * - * All versions are centralized in [[IWMillVersions]]. - */ -object IWMillDeps { - import IWMillVersions as V - - // ZIO Core - def zio = ivy"dev.zio::zio:${V.zio}" - def zioTest = ivy"dev.zio::zio-test:${V.zio}" - def zioTestSbt = ivy"dev.zio::zio-test-sbt:${V.zio}" - def zioStreams = ivy"dev.zio::zio-streams:${V.zio}" - - // ZIO Extensions - def zioConfig = ivy"dev.zio::zio-config:${V.zioConfig}" - def zioConfigTypesafe = ivy"dev.zio::zio-config-typesafe:${V.zioConfig}" - def zioConfigMagnolia = ivy"dev.zio::zio-config-magnolia:${V.zioConfig}" - def zioJson = ivy"dev.zio::zio-json:${V.zioJson}" - def zioLogging = ivy"dev.zio::zio-logging:${V.zioLogging}" - def zioLoggingSlf4j = ivy"dev.zio::zio-logging-slf4j:${V.zioLogging}" - def zioPrelude = ivy"dev.zio::zio-prelude:${V.zioPrelude}" - def zioInteropCats = ivy"dev.zio::zio-interop-cats:${V.zioInteropCats}" - def zioInteropReactiveStreams = ivy"dev.zio::zio-interop-reactivestreams:${V.zioInteropReactiveStreams}" - def zioNIO = ivy"dev.zio::zio-nio:${V.zioNIO}" - def zioCli = ivy"dev.zio::zio-cli:${V.zioCli}" - def zioSchema = ivy"dev.zio::zio-schema:${V.zioSchema}" - def zioSchemaDerivation = ivy"dev.zio::zio-schema-derivation:${V.zioSchema}" - - // Helper methods for ZIO - def useZIO(): Loose.Agg[Dep] = Loose.Agg( - zio, - zioTest, - zioTestSbt - ) - - def useZIOJson: Loose.Agg[Dep] = Loose.Agg( - zioJson - ) - - def useZIOAll(): Loose.Agg[Dep] = useZIO() ++ Loose.Agg( - zioStreams, - zioConfig, - zioConfigTypesafe, - zioConfigMagnolia, - zioLogging, - zioPrelude - ) ++ useZIOJson - - // Database - def quill = ivy"io.getquill::quill-jdbc-zio:${V.quill}" - def magnum = ivy"com.augustnagro::magnum_3:${V.magnum}" - def magnumZIO = ivy"com.augustnagro::magnumzio_3:${V.magnum}" - def magnumPG = ivy"com.augustnagro::magnumpg_3:${V.magnum}" - - // HTTP - def tapirCore = ivy"com.softwaremill.sttp.tapir::tapir-core:${V.tapir}" - def tapirZIO = ivy"com.softwaremill.sttp.tapir::tapir-zio:${V.tapir}" - def tapirZIOJson = ivy"com.softwaremill.sttp.tapir::tapir-json-zio:${V.tapir}" - - // UI/Frontend - def scalatags = ivy"com.lihaoyi::scalatags:${V.scalatags}" - def laminar = ivy"com.raquo::laminar:${V.laminar}" - def waypoint = ivy"com.raquo::waypoint:${V.waypoint}" - def urlDsl = ivy"be.doeraene::url-dsl:${V.urlDsl}" - - // Testing - def scalaTest = ivy"org.scalatest::scalatest:${V.scalaTest}" - - // Logging - def logbackClassic = ivy"ch.qos.logback:logback-classic:${V.logbackClassic}" -} \ No newline at end of file diff --git a/mill-iw-support/src/main/scala/works/iterative/mill/IWMillVersions.scala b/mill-iw-support/src/main/scala/works/iterative/mill/IWMillVersions.scala deleted file mode 100644 index 4e3150a..0000000 --- a/mill-iw-support/src/main/scala/works/iterative/mill/IWMillVersions.scala +++ /dev/null @@ -1,55 +0,0 @@ -package works.iterative.mill - -/** - * Centralized version management for Iterative Works projects using Mill. - * This object contains all library versions used across projects. - * - * Update this object to bump versions for all projects. - */ -object IWMillVersions { - // ZIO Ecosystem - val zio = "2.1.16" - val zioConfig = "4.0.3" - val zioCli = "0.7.0" - val zioInteropCats = "23.1.0.3" - val zioInteropReactiveStreams = "2.0.2" - val zioJson = "0.7.36" - val zioLogging = "2.5.0" - val zioNIO = "2.0.2" - val zioPrelude = "1.0.0-RC39" - val zioQuery = "0.7.6" - val zioSchema = "1.6.3" - - // Akka Ecosystem - val akka = "2.6.18" - val akkaHttp = "10.4.0" - - // Other Libraries - val cats = "2.13.0" - val caliban = "2.9.2" - val chimney = "1.7.3" - val ducktape = "0.2.7" - val elastic4s = "7.12.4" - val http4s = "0.23.30" - val http4sBlaze = "0.23.17" - val http4sPac4J = "5.0.0" - val laminar = "17.2.0" - val laminext = "0.17.0" - val logbackClassic = "1.5.17" - val magnum = "2.0.0-M1" - val pac4j = "6.0.3" - val play = "2.8.18" - val playJson = "2.9.3" - val quill = "4.8.6" - val scalaJsMacroTaskExecutor = "1.1.1" - val scalaJsJavaSecureRandom = "1.0.0" - val scalatags = "0.13.1" - val scalaTest = "3.2.19" - val slick = "3.3.3" - val sttpClient3 = "3.10.3" - val sttpClient4 = "4.0.0-RC1" - val support = "1.7.0" - val tapir = "1.11.16" - val urlDsl = "0.7.0" - val waypoint = "9.0.0" -} \ No newline at end of file diff --git a/mill-iw-support/src/main/scala/works/iterative/mill/IWPublishModule.scala b/mill-iw-support/src/main/scala/works/iterative/mill/IWPublishModule.scala deleted file mode 100644 index 29a2df4..0000000 --- a/mill-iw-support/src/main/scala/works/iterative/mill/IWPublishModule.scala +++ /dev/null @@ -1,43 +0,0 @@ -package works.iterative.mill - -import mill._ -import mill.scalalib._ -import mill.scalalib.publish._ - -/** - * Standard publish module trait for Iterative Works projects using Mill. - * This trait sets up standard publishing configuration for IW Maven repositories. - */ -trait IWPublishModule extends PublishModule { - def publishVersion: T[String] - - def pomSettings: T[PomSettings] - - /** - * Determines the repository URL based on version. - * - Snapshot versions go to the snapshots repository - * - Release versions go to the releases repository - */ - def repositoryUrl: T[String] = T { - if (publishVersion().endsWith("-SNAPSHOT")) - "https://dig.iterative.works/maven/snapshots" - else - "https://dig.iterative.works/maven/releases" - } - - /** - * Configure publish credentials from environment variables. - */ - def publishCredentials: T[PublishCredentials] = T { - val username = sys.env.getOrElse("IW_USERNAME", "") - val password = sys.env.getOrElse("IW_PASSWORD", "") - PublishCredentials("GitBucket Maven Repository", "dig.iterative.works", username, password) - } - - /** - * Configure publication to IW Maven repository. - */ - def publishTo: T[Option[PublishRepository]] = T { - Some(PublishRepository(repositoryUrl(), publishCredentials())) - } -} \ No newline at end of file diff --git a/mill-iw-support/src/main/scala/works/iterative/mill/IWScalaModule.scala b/mill-iw-support/src/main/scala/works/iterative/mill/IWScalaModule.scala deleted file mode 100644 index 4f7c38c..0000000 --- a/mill-iw-support/src/main/scala/works/iterative/mill/IWScalaModule.scala +++ /dev/null @@ -1,54 +0,0 @@ -package works.iterative.mill - -import mill._ -import mill.scalalib._ -import mill.scalalib.scalafmt._ - -/** - * Standard Scala module trait for Iterative Works projects using Mill. - * This trait sets up standard compiler options and configuration. - */ -trait IWScalaModule extends ScalaModule with ScalafmtModule { - // Default to Scala 3.6.3 as in SBT - def scalaVersion = "3.6.3" - - // Default compiler options - def scalacOptions = super.scalacOptions() ++ ( - if (scalaVersion().startsWith("2.")) { - Seq( - "-Xfatal-warnings", - "-deprecation", - "-feature", - "-unchecked", - "-language:higherKinds", - "-language:implicitConversions", - "-Wunused:imports" - ) - } else { - Seq( - "-Xfatal-warnings", - "-deprecation", - "-feature", - "-unchecked", - "-language:higherKinds", - "-language:implicitConversions", - "-Wunused:imports" - ) - } - ) - - // Enable SemanticDB for tooling - def semanticDbOptions = Seq( - "-Xsemanticdb" - ) - - // Override scalacOptions to include SemanticDB options - override def scalacOptions = super.scalacOptions() ++ semanticDbOptions - - // Test configuration - trait IWTests extends ScalaTests with TestModule.Utest with ScalafmtModule { - def ivyDeps = super.ivyDeps() ++ Agg( - ivy"com.lihaoyi::utest:0.8.2" - ) - } -} \ No newline at end of file