引言
Scala 的构建工具经历三代演化:sbt(行业标准,功能最全但学习曲线陡)、Mill(sbt 的去魔法替代,配置即 Scala 代码)、Scala CLI(面向脚本与快速起步)。理解它们共有的核心模型——声明式配置 + 增量编译 + 依赖图解析——就能在三者间自由切换。本文以 sbt 为主线讲透概念,再给 Mill 与 Scala CLI 的对比落地。
前置:任何 Scala 项目实操。与测试、打包的衔接见 /scala-testing-practice/、/scala-web-http-apps/。
目录
- 1. 三大构建工具定位
- 2. sbt 核心模型:build.sbt 与模块
- 3. 依赖管理:libraryDependencies 与解析
- 4. 多模块工程:subproject 与聚合
- 5. 自定义 task 与 setting
- 6. 增量编译与缓存
- 7. Mill:配置即代码
- 8. Scala CLI:脚本化与云端
- 9. CI 集成与发布
- 10. 构建工具选型速查表
- 延伸阅读
1. 三大构建工具定位
| 工具 | 定位 | 配置语言 | 学习曲线 | 生态 |
|---|---|---|---|---|
| sbt | 行业标准构建器 | .sbt DSL + Scala | 陡 | 最全(插件无数) |
| Mill | sbt 的现代替代 | 纯 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:
| 概念 | 类型 | 求值时机 | 示例 |
|---|---|---|---|
| Setting | SettingKey[A] | 加载一次 | scalaVersion |
| Task | TaskKey[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:
| 维度 | sbt | Mill |
|---|---|---|
| 配置语言 | .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 矩阵 |
| 发布 Central | sbt-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 语法在构建脚本中的应用
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。