【问题标题】:Changing the compiler's classpath from a Scala macro?从 Scala 宏更改编译器的类路径?
【发布时间】:2013-05-27 12:03:34
【问题描述】:

假设我们想在 Scala 中构建一个类似于 require 的功能,该功能在许多脚本语言中都很常见。

如果我们需要这样的库...

require("some-library.jar")

...我们需要一种方法在执行 require 宏时将此 jar 添加到编译器的类路径中。如何做到这一点?

【问题讨论】:

  • 你可以尝试调整这个解决方案stackoverflow.com/questions/1010919/…
  • 那是关于更改运行时类路径,所以不是我需要的。
  • 但如果是在宏的定义中(不是生成的代码),应该由编译器运行,改its运行时类路径。
  • 是的,但是编译器的运行时类路径仍然不是编译类路径。是吗?
  • 我以为是在写评论时,但进一步认为应该是错误的。

标签: scala macros classpath scala-2.10


【解决方案1】:

另一个有趣的@kim-stebel 问题。

我的第一个想法是,您的编译器可以使用 findMacroClassLoader 自定义宏类路径。感谢@extempore,REPL 使用了它。

这对于像 requires("myfoo.jar") { mymacro } 这样的习语很有用。

您的问题是您是否可以更新编译器的类路径。这可以通过从您的上下文宇宙向下转换到编译器来实现,为此 isCompilerUniverse == true。然后你可以platform.updateClassPath。

更新代码:

这类似于在所需宏的类路径中使用特殊占位符的想法。

下面提到的奇特方法是使用自定义类路径,该路径可以报告该位置的所有必需类。下面提到的旧代码是用于类路径中的虚拟目录。

这种快速而俗气的方式使用文件系统来解压你的 jar,并要求编译器重新扫描它。

由于 invalidateClassPathEntries 的限制,占位符必须是实际目录,它想要检查规范文件路径是否在类路径上。

package alacs

import scala.language.experimental.macros
import scala.reflect.macros.Context

import scala.sys.process._
import java.io.File

/** A require macro to dynamically fudge the compilation classpath.  */
object PathMaker {
  // special place to unpack required libs, must be on the initial classpath
  val entry = "required"

  // whether to report updated syms without overly verbose -verbose
  val talky = true

  def require(c: Context)(name: c.Expr[String]): c.Expr[Unit] = {
    import c.universe._
    val st = c.universe.asInstanceOf[scala.reflect.internal.SymbolTable]
    if (st.isCompilerUniverse) {
      val Literal(Constant(what: String)) = name.tree
      if (update(what)) {
        val global = st.asInstanceOf[scala.tools.nsc.Global]
        val (updated, _) = global invalidateClassPathEntries entry
        c.info(c.enclosingPosition, s"Updated symbols $updated", force = talky)
      } else {
        c.abort(c.enclosingPosition, s"Couldn't unpack '$what' into '$entry'")
      }
    }
    reify { () }
  }

  // figure out where name is, and update the special class path entry
  def update(name: String): Boolean = {
    // Process doesn't parse the command, it just splits on space,
    // something about working on Windows
    //val status = s"sh -c \"mkdir $entry ; ( cd $entry ; jar xf ../$name )\"".!

    // but Process can set cwd for you
    val command = s"jar xf ../$name"
    val status = Process(command, new File(entry)).!
    (status == 0)
  }
}

所需的 API:

package alacs

import scala.language.experimental.macros

object Require {
  def require(name: String): Unit = macro PathMaker.require
}

用法:

package sample

import alacs.Require._

/** Sample app requiring something not on the class path. */
object Test extends App {
  require("special.jar")
  import special._
  Console println Special(7, "seven")
}

特殊.jar 中打包的东西

package special

case class Special(i: Int, s: String)

以这种方式测试:

rm -rf required
mkdir required
skalac pathmaker.scala
skalac -cp .:required require.scala sample.scala
skala -cp .:special.jar sample.Test

apm@mara:~/tmp/pathmaker$ . ./b
Unpack special.jar
sample.scala:8: Updated symbols List(package special)
  require("special.jar")
         ^
Special(7,seven)

宏不会将其潜入运行时路径,这是脚本要做的事情。

但我认为 require 宏可以做一些方便的事情,比如有条件地抓取不同版本的 jar,具有不同的编译时特性(常量等)。

更新,只是验证它很疯狂:

  require("fast.jar")
  import constants._
  Console println speed

  require("slow.jar")
  Console println speed

在哪里

package object constants {
  //final val speed = 55
  final val speed = 85
}

$ skalac -d fast.jar constants.scala

并使用内联常量运行它

85
55

新的警告:这是我的第一个宏,我正在为另一个应用程序寻找 invalidateClassPathEntries,所以我还没有探索限制。

更新:一个限制是控制宏何时展开。我想展示一些东西是如何针对旧 api 和新 api 进行编译的,并且必须将代码包装在块中以确保符号在需要之前可用:

require("oldfoo.jar")
locally {
  import foo._
  // something
  require("newfoo.jar")
  // try again
}

旧警告: 很抱歉答案含糊,我知道你不遵守;我稍后会尝试,但与此同时,也许有人会清楚地向前迈进。

以前,我已经连接到全局编译器的“平台”实现,但是对于这个用例来说,这可能是多余的。那时,您可以对类路径做任何您想做的事情,但我认为您需要更多开箱即用的东西。

【讨论】:

  • @som-snytt 能否请您扩展代码示例,说明如何使用全局编译器来完全修改类路径,scala.tools.nsc.Global 类中的所有值似乎都是不可变的。
  • @RicardoGladwell 可变的东西是类路径本身。在此解决方案中,文件系统用于更新某个位置的内容,并要求编译器刷新该位置。也可以使用自定义 ClassPath 实现来实例化全局并虚拟地执行等效操作,但这并不优雅。正在努力改进 ClassPath 抽象。
  • @som-snytt 如果我从 macroClassLoader 中获取 URLClassLoader 并通过添加额外的 JAR URL 来修改它的状态,那会起作用吗?
  • @RicardoGladwell 当你尝试过时告诉我。你可以告诉它为宏使用什么类加载器,看看 REPL 做了什么。
【解决方案2】:

正如 Eugene Burmanko 在this question 中所说,目前(Scala 2.10)不能在宏之外添加/删除定义。如果你愿意使用macro paradise,或许有办法。

【讨论】:

  • 如果我正确理解尤金的回答,他只是指宏定义创建。此外,“可能有办法”并不是我在答案中寻找的。 ;)
猜你喜欢
  • 2023-01-05
  • 2023-04-07
  • 1970-01-01
  • 1970-01-01
  • 2019-03-27
  • 2021-11-17
  • 2020-11-10
  • 2020-04-29
  • 1970-01-01
相关资源
最近更新 更多