Newer
Older
iw-project-support / dev-logs / 20250402-research.md

Research on Mill vs SBT - 2025-04-02

Key Components of Current SBT Infrastructure

1. IWMaterialsVersions

  • Simple object with val declarations for all library versions
  • Centralized version management through a single file
  • Versioning for all major libraries (ZIO, Akka, HTTP clients, etc.)
  • Used as a reference in IWMaterialsDeps

2. IWMaterialsDeps

  • Contains dependency definitions with proper cross-platform support
  • Groups related dependencies with helper methods like useZIO(), useZIOAll()
  • Handles Scala.js dependencies with %%% cross-version support
  • Implements specialized library sets (ZIO, Akka, HTTP, etc.)
  • Provides standard compiler options for certain libraries (like ZIO JSON)

3. IWScalaProjectPlugin

  • Implements an SBT AutoPlugin for standardizing projects
  • Sets default Scala version (currently prioritizing Scala 3.6.3)
  • Enables SemanticDB for tooling
  • Sets up scalafmt and scalafix integration
  • Configures publishing to IW Maven repositories
  • Sets test logging preferences

4. IWPluginPresets

  • Centralizes SBT plugin management
  • Provides helper methods for adding common plugin sets
  • Handles complex plugin dependencies (like Scala.js ecosystem)

5. VitePlugin

  • Example of custom SBT plugin for JavaScript tooling
  • Shows integration with external tools like Vite
  • Manages dev server lifecycle
  • Configures environment variables and file monitoring

Mill Build Tool Overview

Based on our research from the Mill documentation, here are the key aspects of Mill:

1. Core Features

  • Supports multiple languages (Java, Scala, Kotlin)
  • Faster builds through aggressive caching and parallelism
  • Better IDE support with superior autocomplete and navigation
  • Requires fewer plugins for common workflows
  • Build files are regular Scala code (build.sc)

2. Module System

  • Uses a module tree and task graph with immutable configuration
  • Modules are defined as objects extending traits (like ScalaModule)
  • Example:
    object foo extends ScalaModule {
    def scalaVersion = "3.6.3"
    def ivyDeps = Agg(
      ivy"com.lihaoyi::scalatags:0.13.1"
    )
    }
  • Tasks are defined as methods that can reference other tasks
  • Automatic caching and incremental computation

3. Dependency Management

  • Uses Ivy syntax: ivy"org::name:version" (with :: for Scala deps)
  • Dependencies defined in module definition:
    • def ivyDeps = Agg(...) for external dependencies
    • def moduleDeps = Seq(...) for internal module dependencies
  • Can define custom resolvers and credentials

4. Publishing

  • Uses PublishModule trait
  • Requires def publishVersion and def pomSettings
  • Example:
    object foo extends ScalaModule with PublishModule {
    def publishVersion = "0.0.1"
    def pomSettings = PomSettings(
      description = "Hello",
      organization = "com.lihaoyi",
      url = "https://github.com/lihaoyi/example",
      licenses = Seq(License.MIT)
    )
    }

5. Extension Development

  • Mill uses traits for sharing functionality between modules
  • Can create custom modules that extend existing Mill modules
  • Extensions are regular JVM libraries that can be published to Maven repos
  • Isolated classloaders for worker modules to avoid conflicts

Differences Between SBT and Mill

1. Configuration Syntax

  • SBT: DSL in build.sbt files with key-value pairs
  • Mill: Regular Scala code in build.sc files, with modules as objects

2. Plugin System

  • SBT: Formal plugin system with AutoPlugin
  • Mill: Traits and regular libraries, no formal plugin system

3. Dependency Notation

  • SBT: "org" %% "name" % "version"
  • Mill: ivy"org::name:version"

4. Cross Building

  • SBT: Uses + prefix for commands, crossScalaVersions setting
  • Mill: Uses Cross[Module] for multi-version builds, with version-specific source dirs

5. Task Definition

  • SBT: Tasks defined as settings with complex DSL
  • Mill: Tasks defined as methods with explicit dependencies

Mill Equivalent Components Design

1. IWMillVersions

  • Object with the same version constants as IWMaterialsVersions
  • Example:
    object IWMillVersions {
    val zio = "2.1.16"
    val zioConfig = "4.0.3"
    // ... other versions
    }

2. IWMillDeps

  • Object with methods to create dependencies with proper Mill notation
  • Example:
    object IWMillDeps {
    def zio = ivy"dev.zio::zio:${IWMillVersions.zio}"
    def zioConfig = ivy"dev.zio::zio-config:${IWMillVersions.zioConfig}"
    
    def useZIO() = Agg(
      zio,
      zioTest,
      zioTestSbt
    )
    }

3. IWScalaModule trait

  • Trait that can be mixed into ScalaModule
  • Sets default Scala version and compiler options
  • Example:
    trait IWScalaModule extends ScalaModule {
    def scalaVersion = "3.6.3"
    
    // Standard compiler options
    def scalacOptions = super.scalacOptions() ++ Seq(
      "-Xfatal-warnings",
      "-deprecation"
    )
    }

4. IWPublishModule trait

  • Extension of PublishModule for IW Maven repository setup
  • Example:
    trait IWPublishModule extends PublishModule {
    def publishVersion: T[String]
    
    def pomSettings: T[PomSettings]
    
    def repositoryURL = if (publishVersion().endsWith("-SNAPSHOT"))
      "https://dig.iterative.works/maven/snapshots"
    else
      "https://dig.iterative.works/maven/releases"
    }

Implementation Planning

For each component we'll need to:

  1. Create the basic structure of the Mill support library
  2. Implement the version and dependency objects
  3. Create traits for standard module configuration
  4. Implement publishing support
  5. Create example modules and migration guides

Mill's trait-based approach should make our implementation more natural and flexible than SBT plugins, as we can create composable traits that work directly with Mill's module system.