如何从我的 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-modules
或other-modules
,并放入一个与预处理器匹配的文件扩展到您的项目中,否则预处理器将不会创建 .hs
文件。
对于我自己的用例,我使用解决方案 3 因为它很干净,不需要任何 CPP 或模板Haskell 魔法,给出了很好的错误消息并且不需要 runhaskell Setup.hs configure
到 运行 的等效项,build
就足够了,因为预处理器在构建时是 运行。
我需要使用 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-modules
或other-modules
,并放入一个与预处理器匹配的文件扩展到您的项目中,否则预处理器将不会创建 .hs
文件。
对于我自己的用例,我使用解决方案 3 因为它很干净,不需要任何 CPP 或模板Haskell 魔法,给出了很好的错误消息并且不需要 runhaskell Setup.hs configure
到 运行 的等效项,build
就足够了,因为预处理器在构建时是 运行。