SBT任务运行时添加JAR包:类路径扫描失败问题
I’ve run into this exact classpath isolation problem with SBT before—let’s break down why it’s happening and how to fix it.
Why This Happens
SBT runs your build tasks in a separate classloader environment (the "build definition classpath") that’s distinct from your project’s compile/runtime classpath. Even if you’ve added Scapegoat as a project dependency, that JAR lives in your project’s classpath, not the build’s. Since your task is part of the build definition, it can’t see those JARs unless you explicitly add them to the build’s classpath.
Step 1: Add Scapegoat to the Build Classpath
First, make sure Scapegoat is available to your build tasks by adding it to the project/plugins.sbt file (this configures dependencies for the build itself, not your project):
// project/plugins.sbt libraryDependencies += "com.sksamuel.scapegoat" %% "scalac-scapegoat-plugin" % "1.4.18" // Use your target version
If you’re using a newer Scapegoat version tied to specific Scala versions, add explicit cross-versioning:
libraryDependencies += "com.sksamuel.scapegoat" %% "scalac-scapegoat-plugin" % "1.4.18" cross CrossVersion.full
Step 2: Explicitly Pass the Build Classpath to Your Scanner
Fast-Classpath-Scanner (or its successor, ClassGraph) might not automatically pick up all JARs in SBT’s build classloader. Manually pass the build’s full classpath to the scanner to fix this:
Using ClassGraph (Recommended)
// In your build.sbt import sbt._ import com.sksamuel.scapegoat.inspections.Inspection import io.github.classgraph.ClassGraph lazy val generateInspectionSources = taskKey[Unit]("Generates managed sources for Scapegoat inspections") generateInspectionSources := { // Fetch the full classpath of the build definition val buildClasspath = (fullClasspath in Compile in ThisBuild).value.map(_.data.toURI.toURL) val scanResult = new ClassGraph() .addClasspathUrls(buildClasspath: _*) .whitelistPackages("com.sksamuel.scapegoat.inspections") .enableClassInfo() .scan() try { // Retrieve all subclasses of Inspection val inspectionClasses = scanResult.getSubclassesOf(classOf[Inspection]).getNames // Insert your source generation logic here println(s"Successfully found ${inspectionClasses.size} Scapegoat inspection classes") } finally { scanResult.close() } }
Using Original Fast-Classpath-Scanner
// In your build.sbt import sbt._ import com.sksamuel.scapegoat.inspections.Inspection import io.github.lukehutch.fastclasspathscanner.FastClasspathScanner lazy val generateInspectionSources = taskKey[Unit]("Generates managed sources for Scapegoat inspections") generateInspectionSources := { // Use the build definition's classloader val buildClassLoader = getClass.getClassLoader val scanner = new FastClasspathScanner("com.sksamuel.scapegoat.inspections") .addClassLoader(buildClassLoader) .matchSubclassesOf(classOf[Inspection]) .scan() val inspectionClasses = scanner.getNamesOfSubclassesOf(classOf[Inspection]) // Insert your source generation logic here println(s"Successfully found ${inspectionClasses.size} Scapegoat inspection classes") }
Step 3: Verify Classloader Isolation
If issues persist, double-check that your task uses the build’s classloader (not the project’s). In the task code, getClass.getClassLoader will always point to the correct build definition classloader.
内容的提问来源于stack exchange,提问作者Luis Miguel Mejía Suárez

