简体   繁体   English

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

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

I need to use some Cabal -level information from within my Haskell .hs source files.我需要在我的 Haskell .hs源文件中使用一些Cabal级别的信息。

For example, obtaining the path to the dist/ build directory ( configDistPref ; I don't want to hardcode dist/ ), so that I can look up some contents in it using runIO in TemplateHaskell .例如,获取dist/构建目录的路径( configDistPref ;我不想硬编码dist/ ),以便我可以使用TemplateHaskell runIO查找其中的一些内容。

From normal Haskell files, I don't seem to be able to access Cabal -level information.从普通的 Haskell 文件中,我似乎无法访问Cabal级别的信息。

What ways are there for me to bring that information from a custom Setup.hs file into my Haskell source files?我有什么方法可以将自定义Setup.hs文件中的信息带入我的 Haskell 源文件中?

It seems Cabal and ghc have no out-of-the-box functionality from you to learn your dist/ directory or similar info.似乎 Cabal 和 ghc 没有开箱即用的功能来了解您的dist/目录或类似信息。 Here are some approaches to do it manually:以下是一些手动执行此操作的方法:

Solution 1: Serialising the entire Cabal information into a known place方案一:将整个 Cabal 信息序列化到一个已知的地方

One solution is shown by the cabal-toolkit library. cabal-toolkit库显示了一种解决方案。

It gives you a hook modifier userHooksWithBuildInfo :: UserHooks -> UserHooks that you call in your Setup.hs , which serialises the LocalBuildInfo (which includes all important Cabal-level information, including ConfigFlags{ configDistPref } ) into a dotfile (called .lbi.buildinfo ) in the source directory.它为您提供了一个钩子修饰符userHooksWithBuildInfo :: UserHooks -> UserHooks ,您在userHooksWithBuildInfo :: UserHooks -> UserHooks中调用Setup.hs ,它将LocalBuildInfo (包括所有重要的 Cabal 级别信息,包括ConfigFlags{ configDistPref } )序列化为一个点文件(称为.lbi.buildinfo ) 在源目录中。

It then provides a TemplateHaskell function localBuildInfoTypedQ :: Q (TExp LocalBuildInfo) which reads that file so that you can load the information in TH.然后它提供了一个 TemplateHaskell 函数localBuildInfoTypedQ :: Q (TExp LocalBuildInfo)来读取该文件,以便您可以在 TH 中加载信息。

(I the Hackage version I linked above probably lacks an addDependentFile call here to notice when the serialised info changes, but the Github version already has a fix for it). (我的Hackage版本我上面链接可能缺少addDependentFile呼叫这里来通知时的序列化方式的变化,但GitHub的版本已经有一个修复它)。


However, if you'd like temp files like this to end up in the dist/ directory, this also doesn't help (since as mentioned, the dotfile is in the toplevel of your project and you should add it eg to .gitignore ).但是,如果您希望这样的临时文件最终出现在dist/目录中,这也无济于事(因为如上所述,点文件位于项目的顶层,您应该将其添加到例如.gitignore ) .

Solution 2: Passing specific information to GHC using -D flags解决方案 2:使用-D标志将特定信息传递给 GHC

Via a confHook , that calls cabal configure with "-ghc-options=-D__CPP_CABAL_DIST_DIR__=" ++ configDistPref .通过confHook ,使用"-ghc-options=-D__CPP_CABAL_DIST_DIR__=" ++ configDistPref调用 cabal configure。 Example for your Setup.hs file:您的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 })
      }

Then the only difficulty is to actually splice the __CPP_CABAL_DIST_DIR__ macro into the code because you can't write "__CPP_CABAL_DIST_DIR__" in a Haskell String literal, that won't get substituted by CPP, so you have to use a string quasiquoter like this one , eg [r|__CPP_CABAL_DIST_DIR__] just to get that macro in;然后唯一的困难是将__CPP_CABAL_DIST_DIR__宏实际拼接到代码中,因为您不能在 Haskell 字符串文字中写入"__CPP_CABAL_DIST_DIR__" ,这不会被 CPP 替换,因此您必须使用像这样的字符串准引用器,例如[r|__CPP_CABAL_DIST_DIR__]只是为了得到那个宏; the usual way of doing CPP stringification with #__CPP_CABAL_DIST_DIR__ doesn't work in ghc's CPP, as explained here :做CPP的常用方法字串#__CPP_CABAL_DIST_DIR__不会在GHC的CPP工作,解释在这里

There are three main reasons why code with CPP might not work as expected:使用 CPP 的代码可能无法按预期工作的主要原因有以下三个:

You used # , ## or __VA_ARGS__ .您使用了###__VA_ARGS__ GHC runs CPP in traditional mode, which disables all advanced features. GHC 在传统模式下运行 CPP,这会禁用所有高级功能。

Solution 3: Passing specific information by generating a Haskell module with a preprocessor解决方案3:通过使用预处理器生成Haskell模块来传递特定信息

You can add a hookedPreProcessors to your hooks in Setup.hs .您可以添加hookedPreProcessors在你的钩子Setup.hs In this hook you render as Haskell literals or strings any information from Cabal that you want to access.在这个钩子中,您将想要访问的来自 Cabal 的任何信息呈现为 Haskell 文字或字符串。

An example on how to write a preprocessor can be found here in the Cabal documentation, or even clearer, in this answer .如何编写一个预处理的例子可以发现这里惊天动地文档中,甚至更清楚,在这个答案

Don't forget to add the module to be generated by your pre-processor to the exposed-modules or other-modules in your .cabal file, and put a file matching the preprocessor extension into your project, otherwise the preprocessor will not create the .hs file.不要忘记将你的预处理器生成的模块添加到你的.cabal文件中的exposed-modulesother-modules中,并将与预处理器扩展名匹配的文件放入你的项目中,否则预处理器不会创建.hs文件。


For my own use cases, I'm using Solution 3 because it is clean, doesn't need any CPP or TemplateHaskell magic, gives good error messages and does not require the equivalent of runhaskell Setup.hs configure to run, build is enough because preprocessors are run at build time.对于我自己的用例,我正在使用解决方案 3,因为它很干净,不需要任何 CPP 或 TemplateHaskell 魔法,提供良好的错误消息,并且不需要runhaskell Setup.hs configure的等效runhaskell Setup.hs configure来运行, build就足够了,因为预处理器在构建时运行。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM