如何从我的 Haskell 源文件中访问 cabal/Setup.hs 信息?

How to access cabal/Setup.hs information from within my Haskell source files?

我需要使用 Haskell .hs 源文件中的一些 Cabal 级信息。

比如获取dist/构建目录的路径(configDistPref;我不想硬编码dist/),这样我就可以在里面查找一些内容它在 TemplateHaskell.

中使用 runIO

从普通的 Haskell 文件,我似乎无法访问 Cabal 级别的信息。

我可以通过哪些方式将自定义 Setup.hs 文件中的信息导入我的 Haskell 源文件?

Cabal 和 ghc 似乎没有开箱即用的功能来了解您的 dist/ 目录或类似信息。以下是一些手动执行此操作的方法:

解决方案 1:将整个 Cabal 信息序列化到已知位置

cabal-toolkit 库显示了一个解决方案。

它给你一个挂钩修饰符 userHooksWithBuildInfo :: UserHooks -> UserHooks,你可以在你的 Setup.hs 中调用它,它序列化 LocalBuildInfo(其中包括所有重要的 Cabal 级信息,包括 ConfigFlags{ configDistPref }) 到源目录中的点文件(称为 .lbi.buildinfo)。

然后它提供了一个模板Haskell函数localBuildInfoTypedQ :: Q (TExp LocalBuildInfo)读取该文件,以便您可以在 TH 中加载信息。

(我上面链接的 Hackage 版本可能缺少 addDependentFile 调用 here to notice when the serialised info changes, but the Github version already has a fix)。


但是,如果您希望这样的临时文件最终位于 dist/ 目录中,这也无济于事(因为如前所述,点文件位于项目的顶层,而您应该将其添加到 .gitignore).

解决方案 2:使用 -D 标志将特定信息传递给 GHC

通过 confHook,使用 "-ghc-options=-D__CPP_CABAL_DIST_DIR__=" ++ configDistPref 调用 cabal 配置。您的 Setup.hs 文件示例:

main = do
  defaultMainWithHooks $
    simpleUserHooks
      { confHook = \inputs configFlags@ConfigFlags{ configDistPref = configDistPref } -> do
          putStrLn "In Cabal configure hook"

          let distDir = fromFlagOrDefault "dist" configDistPref

          (confHook simpleUserHooks) inputs (configFlags{ configProgramArgs = ("ghc", ["-D__CPP_CABAL_DIST_DIR__=" ++ distDir]) : configProgramArgs configFlags })
      }

那么唯一的困难是将 __CPP_CABAL_DIST_DIR__ 宏实际拼接到代码中,因为您不能在 Haskell 字符串文字中编写 "__CPP_CABAL_DIST_DIR__",那将不会被替换通过 CPP,所以你必须使用像 this one, e.g. [r|__CPP_CABAL_DIST_DIR__] just to get that macro in; the usual way of doing CPP stringification with #__CPP_CABAL_DIST_DIR__ doesn't work in ghc's CPP, as explained here:

这样的字符串准引号

There are three main reasons why code with CPP might not work as expected:

You used #, ## or __VA_ARGS__. GHC runs CPP in traditional mode, which disables all advanced features.

解决方案 3:通过使用预处理器生成 Haskell 模块来传递特定信息

您可以在 Setup.hs 中添加一个 hookedPreProcessors 到您的挂钩中。在此挂钩中,您将要访问的来自 Cabal 的任何信息呈现为 Haskell 文字或字符串。

可以找到有关如何编写预处理器的示例 here in the Cabal documentation, or even clearer, in this answer

不要忘记将你的预处理器要生成的模块添加到你的.cabal文件中的exposed-modulesother-modules,并放入一个与预处理器匹配的文件扩展到您的项目中,否则预处理器将不会创建 .hs 文件。


对于我自己的用例,我使用解决方案 3 因为它很干净,不需要任何 CPP 或模板Haskell 魔法,给出了很好的错误消息并且不需要 runhaskell Setup.hs configure 到 运行 的等效项,build 就足够了,因为预处理器在构建时是 运行。