如何创建与操作SymbolicPath类型?
兄弟,我太懂你在自定义Setup脚本里折腾SymbolicPath的痛苦了!这种抽象类型一开始确实摸不着头脑,不过结合你要做的读取源码目录、传给外部程序、注入生成目录这几个需求,咱们一步步来拆解:
一、把SymbolicPath转成实际可用的文件路径(读文件/传给外部程序)
SymbolicPath是Cabal用来做类型安全路径管理的抽象类型,要拿到能直接用的文件路径,得结合它的基准路径(比如PackageDir,也就是你的包根目录)来解析。
你需要用Distribution.Utils.Path里的resolveSymbolicPath函数,它能把SymbolicPath base target转换成实际的FilePath。比如在Setup.hs里,你可以从LocalBuildInfo里拿到包根目录(pkgRoot :: PackageDir),然后遍历hsSourceDirs里的每个路径:
import Distribution.Utils.Path (resolveSymbolicPath) import Distribution.Simple.LocalBuildInfo (LocalBuildInfo(..)) import Distribution.PackageDescription (PackageDescription(..)) -- 从LocalBuildInfo里提取所有源码目录的实际路径 getRealSourcePaths :: LocalBuildInfo -> [FilePath] getRealSourcePaths lbi = let pkgDescr = localPkgDescr lbi pkgRootDir = pkgRoot lbi in map (resolveSymbolicPath pkgRootDir) (hsSourceDirs pkgDescr)
拿到这些FilePath之后,不管是用System.Directory去读文件,还是转成字符串传给外部程序,都跟普通路径操作一样了。
二、创建新的SymbolicPath并注入到hsSourceDirs
要把生成的源码目录加到hsSourceDirs里,得先把普通路径转换成SymbolicPath PackageDir SourceDir类型,这里用makeSymbolicPath函数,配合fromFilePath把字符串路径转成Cabal的Path类型:
import Distribution.Utils.Path (makeSymbolicPath, fromFilePath) import Distribution.PackageDescription (PackageDescription(..)) -- 把生成目录转换成符合要求的SymbolicPath generatedSourceDir :: SymbolicPath PackageDir SourceDir generatedSourceDir = makeSymbolicPath (fromFilePath "./generated-src") -- 更新PackageDescription里的hsSourceDirs updateSourceDirs :: PackageDescription -> PackageDescription updateSourceDirs pkgDescr = pkgDescr { hsSourceDirs = generatedSourceDir : hsSourceDirs pkgDescr }
然后你可以在Setup的钩子(比如buildHook或者configureHook)里,把更新后的PackageDescription传给默认的钩子函数,这样Cabal就会把生成目录当成源码目录处理了。比如在build阶段注入:
import Distribution.Simple (UserHooks(..), defaultMainWithHooks, simpleUserHooks) import Distribution.Simple.Setup (BuildFlags) import Distribution.Simple.LocalBuildInfo (LocalBuildInfo) import Distribution.PackageDescription (PackageDescription) import Distribution.Simple.Utils (Args) myBuildHook :: Args -> BuildFlags -> PackageDescription -> LocalBuildInfo -> IO () myBuildHook args flags oldPkgDescr lbi = do let newPkgDescr = updateSourceDirs oldPkgDescr -- 调用默认的build逻辑,用更新后的包描述 buildHook simpleUserHooks args flags newPkgDescr lbi main :: IO () main = defaultMainWithHooks simpleUserHooks { buildHook = myBuildHook }
几个要注意的小细节
SymbolicPath的两个类型参数(比如PackageDir和SourceDir)是用来做类型约束的,确保你不会把不同用途的路径搞混,所以创建的时候要注意类型匹配,必要时可以显式标注类型。- 如果你的生成目录是绝对路径,
makeSymbolicPath也能处理,resolveSymbolicPath会自动识别,不用额外处理相对/绝对路径的问题。 - 记得在Setup的.cabal文件里添加
Cabal-syntax(或者对应版本的Cabal库)作为依赖,不然会找不到这些路径操作的函数。
备注:内容来源于stack exchange,提问作者Cactus

