Scala 构建工具实战:sbt、Mill 与 Scala CLI 工程化

系统覆盖 Scala 三大构建工具:sbt 核心概念(build.sbt、模块、依赖、task)、Mill 简洁替代、Scala CLI 脚本化与云端,多模块工程、依赖管理、增量编译与 CI 全流程。

引言

Scala 的构建工具经历三代演化:sbt(行业标准,功能最全但学习曲线陡)、Mill(sbt 的去魔法替代,配置即 Scala 代码)、Scala CLI(面向脚本与快速起步)。理解它们共有的核心模型——声明式配置 + 增量编译 + 依赖图解析——就能在三者间自由切换。本文以 sbt 为主线讲透概念,再给 Mill 与 Scala CLI 的对比落地。

前置:任何 Scala 项目实操。与测试、打包的衔接见 /scala-testing-practice/、/scala-web-http-apps/。


目录


1. 三大构建工具定位

工具定位配置语言学习曲线生态
sbt行业标准构建器.sbt DSL + Scala陡最全(插件无数)
Millsbt 的现代替代纯 Scala平中等(快速成长)
Scala CLI脚本/实验/云端注释指令 + Scala平面向快速开发

一句话选型:

  • 生产多模块项目、要成熟生态 → sbt
  • 想要「配置就是普通 Scala、可调试」 → Mill
  • 写脚本、教学、跑单文件 → Scala CLI

sbt 生态占绝对主流,生态缺口最小;Mill/Scala CLI 的体验更「现代化」。


2. sbt 核心模型:build.sbt 与模块

最小 build.sbt:

// build.sbt
ThisBuild / scalaVersion := "3.3.4"
ThisBuild / organization := "com.example"

lazy val root = (project in file("."))
  .settings(
    name := "my-app",
    libraryDependencies ++= Seq(
      "org.typelevel" %% "cats-effect" % "3.5.4",
      "org.scalatest" %% "scalatest" % "3.2.19" % Test
    )
  )

核心概念:setting 与 task:

概念类型求值时机示例
SettingSettingKey[A]加载一次scalaVersion
TaskTaskKey[A]每次调用compile / test / package
输入任务InputKey[A]带参数runMain / testOnly

常用命令:

sbt compile          # 编译
sbt test             # 测试
sbt run              # 运行 main
sbt console          # REPL(含项目 classpath)
sbt clean            # 清产物
sbt scalafmt         # 格式化
sbt evicted          # 显示依赖冲突

3. 依赖管理:libraryDependencies 与解析

依赖坐标语法:

libraryDependencies += "org.typelevel" %% "cats-effect" % "3.5.4"
//                     groupId       %% artifact  %% 版本
//  %% 自动补 Scala 二进制版本(cats-effect_3)
//  %  精确 artifact(Java 库用单 %)

libraryDependencies += "org.postgresql" % "postgresql" % "42.7.4"

依赖分类:

配置用途
默认(Compile)主代码可用
% Test仅测试
% Provided编译期有,运行期容器提供
% Runtime运行期需要,编译不需要
% "optional"可选传递

版本冲突(两个库要不同版本的同一个依赖):用 dependencyOverrides 或 coursier 的强制版本:

ThisBuild / dependencyOverrides ++= Seq(
  "org.typelevel" %% "cats-core" % "2.12.0"
)

解析策略:默认最新版本优先(UpdateOptions 可调为「冲突时报错」)。


4. 多模块工程:subproject 与聚合

多模块让「核心库」「Web」「测试工具」分而治之,各管各的依赖:

lazy val core = (project in file("modules/core"))
  .settings(
    name := "core",
    libraryDependencies += "org.typelevel" %% "cats-core" % "2.12.0"
  )

lazy val web = (project in file("modules/web"))
  .dependsOn(core)          // web 依赖 core(编译顺序自动)
  .settings(
    name := "web",
    libraryDependencies += "com.typesafe.akka" %% "akka-http" % "10.5.3"
  )

lazy val root = (project in file("."))
  .aggregate(core, web)     // 聚合:sbt test 会跑所有模块
  .dependsOn(core, web)

模块常用命令:

sbt core/compile          # 只编译 core
sbt "web/testOnly *Api*"  # 只跑 web 模块某测试
sbt +compile              # 多 Scala 版本交叉编译

聚合与依赖的区别:dependsOn 建立代码依赖;aggregate 只是「批量执行命令」。独立发布用 publish / skip := true。


5. 自定义 task 与 setting

业务定制(生成版本文件、自定义检查):

// build.sbt
val genVersion = taskKey[File]("生成 version.txt")

lazy val root = (project in file(".")).settings(
  genVersion := {
    val f = (Compile / resourceManaged).value / "version.txt"
    val v = version.value
    IO.write(f, v)
    f
  },
  // 编译前自动生成
  Compile / resourceGenerators += genVersion.taskValue
)

依赖其他 task:用 .value 宏引用——sbt 会解析执行顺序(这是 sbt 的 DAG 调度):

val checkVersion = taskKey[Unit]("校验版本")
checkVersion := {
  val v = version.value                 // 依赖 setting
  val s = (Compile / compile).value     // 依赖编译
  assert(v.nonEmpty, "version 不能为空")
}

6. 增量编译与缓存

Zinc 增量编译:sbt 内置增量编译器——只重编译变更文件及其依赖:

  • 改一个文件 → 只重编该文件及直接依赖
  • 输出缓存到 target/,重启 sbt 复用
  • ~compile 监听变更自动重编

常见提速手段:

手段效果
拆模块(依赖更薄)缩小重编范围
-Xmax-inlines 合理值避免内联膨胀
避免大宏/派生大量代码减编译压力
sbt bloopInstall用 Bloop 单独增量引擎
远程缓存(CI)复用上次产物

sbt 慢的根因往往是依赖图太大 + 模块耦合,优化编译前先看 target/scala-*/analysis。


7. Mill:配置即代码

Mill 的 build.sc 是普通 Scala 程序——可调试、可复用函数、无魔法:

// build.sc
import mill._, scalalib._

object core extends ScalaModule {
  def scalaVersion = "3.3.4"
  def ivyDeps = Agg(
    ivy"org.typelevel::cats-core:2.12.0"
  )
}

object web extends ScalaModule {
  def moduleDeps = Seq(core)          // 依赖 core
  def ivyDeps = Agg(
    ivy"com.typesafe.akka::akka-http:10.5.3"
  )
}

命令:./mill core.compile / ./mill web.test——一切皆 task,语法与 sbt 异曲同工但更透明。

Mill vs sbt:

维度sbtMill
配置语言.sbt DSL纯 Scala
可调试性中等高
插件生态极丰富发展中
学习曲线陡平

8. Scala CLI:脚本化与云端

Scala CLI 面向「单文件即程序」,注释指令声明依赖:

// script.sc
//> using scala 3.3.4
//> using dep org.typelevel::cats-effect::3.5.4
//> using mainClass Main

import cats.effect.IOApp

object Main extends IOApp.Simple {
  def run = IO.println("hello from scala-cli")
}
scala-cli run script.sc      # 直接跑
scala-cli package script.sc  # 打包可执行
scala-cli test --watch       # 测试监听

云端能力:scala-cli 集成 Scala Toolkit,可远程跑/共享(如 scastie 后端)——非常适合快速原型与教学。

适用边界:>3 文件、要长期维护 → 交给 sbt/Mill;临时脚本、探索 → Scala CLI。


9. CI 集成与发布

GitHub Actions 示例:

- uses: actions/setup-java@v4
  with: { distribution: temurin, java-version: 21 }

- name: 编译与测试
  run: sbt -Dsbt.color=false clean coverage test coverageReport

- name: 发布(打 tag 时)
  if: startsWith(github.ref, 'refs/tags/')
  run: sbt publish
  env:
    SONATYPE_USERNAME: ${{ secrets.SONATYPE_USER }}
    SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASS }}

发布到 Maven Central(Sonatype):

// plugins.sbt
addSbtPlugin("org.xerial.sbt" % "sbt-sonatype" % "3.11.3")
addSbtPlugin("com.github.sbt" % "sbt-pgp" % "2.2.1")

// 关键 setting
ThisBuild / publishTo := sonatypePublishToBundle.value
ThisBuild / licenses := Seq("MIT" -> url("https://..."))
ThisBuild / developers := List(Developer("id", "Name", "mail", url("...")))
sbt publishSigned +publishSigned    # 签名发布
sbt sonatypeBundleRelease          # 释放到 Central

CI 关注清单:缓存 ~/.ivy2 与 ~/.cache/coursier、失败保留测试报告、多 JDK 矩阵。


10. 构建工具选型速查表

需求推荐
生产多模块 + 成熟生态sbt
配置即 Scala、可调试Mill
脚本/单文件/教学Scala CLI
依赖管理三者均基于 Coursier 解析
增量编译sbt Zinc / Bloop
多 Scala 版本+compile(sbt)/ Mill 矩阵
发布 Centralsbt-sonatype + pgp
CI 加速缓存 ivy2/coursier + Bloop

一句话记忆:sbt 全而稳、Mill 简而透明、Scala CLI 快而轻;工程选 sbt 保生态,脚本选 Scala CLI 保速度,纠结时试 Mill。


延伸阅读

  • /scala-testing-practice/ — 测试框架在 sbt 里的集成
  • /scala-web-http-apps/ — 打包发布(sbt-native-packager / Docker)
  • /scala-metaprogramming/ — 宏项目需要单独的 macro 模块与构建配置
  • /scala3-modern-features/ — Scala 3 语法在构建脚本中的应用

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「scala」更多文章

  1. 纯函数式效果系统实战:Cats Effect IO 与 ZIO
  2. Scala.js 与 Scala Native:跨平台编译、互操作与工程实践
  3. Scala 领域建模实战:ADT、类型驱动设计与模块化架构