Как загрузить класс из исходного кода с помощью отражения внутри задачи SBT? - PullRequest
2 голосов
/ 26 мая 2020

Я использую SBT для сборки своего проекта. Я хочу проанализировать некоторые классы из моего исходного кода, используя отражение Scala или Java в процессе сборки.

Как мне определить задачу SBT, которая загружает один известный класс или все классы из моего источника код?

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")

Ответы [ 2 ]

3 голосов
/ 26 мая 2020

Вы можете использовать вывод задачи fullClasspathAsJars SBT, чтобы получить доступ к JAR, созданным из вашего исходного кода. Эта задача не включает JAR-файлы зависимостей. Затем вы можете создать ClassLoader для загрузки классов из этих JAR:

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)
}

Затем, если вы знаете имя своего класса в JAR, вы можете использовать этот ClassLoader для его загрузки.

Обратите внимание на разницу между Scala именами классов и именами классов в JAR. Имена классов Scala могут быть изменены, и один класс Scala может создавать несколько классов в JAR. Например, класс my.company.Box.MyClass из следующего фрагмента создает два класса JAR: my.company.Box$MyClass и my.company.Box$MyClass$, причем последний является классом сопутствующего объекта.

package my.company
object Box {
  case class MyClass()
}

Итак, если вы хотите укажите класс по его имени Scala или чтобы перечислить все классы, определенные в источнике, вы должны использовать вывод задачи compile SBT. Эта задача создает объект CompileAnalysis, который является частью внутреннего SBT API и может измениться в будущем. Следующий код работает с SBT 1.3.10.

Чтобы загрузить класс по его Scala имени:

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)

Чтобы вывести список всех классов из исходного кода:

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)
1 голос
/ 26 мая 2020

На основе Ссылка scala файл из build.sbt добавьте следующее в project/build.sbt

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

, а затем scala-reflect в исходники проекта из build.sbt например поэтому

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

где

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