How to load a class from the source code using reflection inside SBT task?

送分小仙女□ 提交于 2020-07-09 09:34:30

问题


I'm using SBT to build my project. I want to analyze some classes from my source code using either Scala or Java reflection during the build process.

How do I define an SBT task that loads a single known class or all classes from my source code?

import sbt._

val loadedClasses = taskKey[Seq[Class[_]]]("All classes from the source")

val classToLoad = settingKey[String]("Scala class name to load")
val loadedClass = taskKey[Seq[Class[_]]]("Loaded classToLoad")

回答1:


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)



回答2:


Based on Reference scala file from build.sbt add the following to project/build.sbt

Compile / unmanagedSourceDirectories += baseDirectory.value / ".." / "src" / "main" / "scala"

and then scala-reflect on project's sources from within build.sbt like so

val reflectScalaClasses = taskKey[Unit]("Reflect on project sources from within sbt")
reflectScalaClasses := {
  import scala.reflect.runtime.universe._
  println(typeOf[example.Hello])
}

where

src
├── main
│   └── scala
│       └── example
│           ├── Hello.scala


来源:https://stackoverflow.com/questions/62029478/how-to-load-a-class-from-the-source-code-using-reflection-inside-sbt-task

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!