You can use the output of the fullClasspathAsJars
SBT task to get access to the JARs produced from you source code. This task doesn't include JARs of the dependencies. Then you can create a ClassLoader
to load classes from those JARs:
import java.net.URLClassLoader
val classLoader = taskKey[ClassLoader]("Class loader for source classes")
classLoader := {
val jarUrls = (Compile / fullClasspathAsJars).value.map(_.data.toURI.toURL).toArray
new URLClassLoader(jarUrls, ClassLoader.getSystemClassLoader)
}
Then if you know the name of your class in the JAR, you can use this ClassLoader
to load it.
Note the difference between Scala class names and class names in the JAR. Scala class names may be mangled, and one Scala class can produce several classes in the JAR. For example my.company.Box.MyClass
class from the following snippet produces two JAR classes: my.company.Box$MyClass
and my.company.Box$MyClass$
, the latter being the class of the companion object.
package my.company
object Box {
case class MyClass()
}
So if you want to specify a class by its Scala name or to list all classes defined in the source, you have to use the output of the compile
SBT task. This task produces a CompileAnalysis
object which is part of internal SBT API and is prone to change in the future. The following code works as of SBT 1.3.10.
To load a class by its Scala name:
import sbt.internal.inc.Analysis
import xsbti.compile.CompileAnalysis
def loadClass(
scalaClassName: String,
classLoader: ClassLoader,
compilation: CompileAnalysis
): List[Class[_]] = {
compilation match {
case analysis: Analysis =>
analysis.relations.productClassName
.forward(scalaClassName)
.map(classLoader.loadClass)
.toList
}
}
classToLoad := "my.company.Box.MyClass"
loadedClass := loadClass(
classToLoad.value,
classLoader.value,
(Compile / compile).value)
To list all classes from the source code:
def loadAllClasses(
classLoader: ClassLoader,
compilation: CompileAnalysis,
): List[Class[_]] = {
val fullClassNames = compilation match {
case analysis: Analysis =>
analysis.relations.allSources.flatMap { source =>
// Scala class names
val classNames = analysis.relations.classNames(source)
val getProductName = analysis.relations.productClassName
classNames.flatMap { className =>
// Class names in the JAR
val productNames = getProductName.forward(className)
if (productNames.isEmpty) Set(className) else productNames
}
}.toList
}
fullClassNames.map(className => classLoader.loadClass(className))
}
loadedClasses := loadAllClasses(
classLoader.value,
(Compile / compile).value)