TemplateHaskell:addDependentFile文件不存在时失效,需支持重编译与Fallback
解决方案
方案1:利用GHC对不存在文件的依赖支持(推荐)
从GHC 8.6版本开始,addDependentFile允许注册不存在的文件作为编译依赖。即使目标文件当前不存在,GHC也不会报错,只会在该文件后续被创建或修改时自动触发重编译。
修改后的代码示例:
import Control.Monad.IO.Class (MonadIO (liftIO)) import Language.Haskell.TH.Syntax (Lift (liftTyped), addDependentFile) import Language.Haskell.TH.Syntax.Compat (SpliceQ, liftSplice) import System.Directory (doesFileExist) myFn :: SpliceQ String myFn = do let filePath = "your-target-file.txt" fallbackPath = "your-fallback-file.txt" -- 直接注册目标文件为依赖,无论其是否存在 liftSplice (addDependentFile filePath >>= liftTyped) exists <- liftIO (doesFileExist filePath) content <- liftIO $ if exists then readFile filePath else readFile fallbackPath liftTyped content
方案2:兼容旧版GHC的替代方案
如果项目需要兼容GHC 8.6之前的版本,可以通过依赖目标文件所在的目录来间接监听文件创建:
import Control.Monad.IO.Class (MonadIO (liftIO)) import Language.Haskell.TH.Syntax (Lift (liftTyped), addDependentFile) import Language.Haskell.TH.Syntax.Compat (SpliceQ, liftSplice) import System.Directory (doesFileExist) import System.FilePath (takeDirectory) myFn :: SpliceQ String myFn = do let filePath = "your-target-file.txt" fallbackPath = "your-fallback-file.txt" dirPath = takeDirectory filePath -- 依赖目标文件所在的目录,目录内任何变化(包括新文件创建)都会触发重编译 liftSplice (addDependentFile dirPath >>= liftTyped) exists <- liftIO (doesFileExist filePath) content <- liftIO $ if exists then readFile filePath else readFile fallbackPath liftTyped content
这种方式的 trade-off 是:目录内任何文件的修改(包括无关文件)都会触发重编译,但能保证目标文件被创建时会重新执行TH代码。
备选:fallback时强制每次重编译
如果上述方案都不适用,想要在使用fallback文件时强制每次编译都重新检查目标文件是否存在,可以通过动态更新一个标记文件实现:
import Control.Monad.IO.Class (MonadIO (liftIO)) import Language.Haskell.TH.Syntax (Lift (liftTyped), addDependentFile) import Language.Haskell.TH.Syntax.Compat (SpliceQ, liftSplice) import System.Directory (doesFileExist) import System.IO (writeFile) import Data.Time.Clock (getCurrentTime, formatTime) import Data.Time.Format (defaultTimeLocale) myFn :: SpliceQ String myFn = do let filePath = "your-target-file.txt" fallbackPath = "your-fallback-file.txt" markerFile = ".th-recompile-marker" exists <- liftIO (doesFileExist filePath) if exists then do liftSplice (addDependentFile filePath >>= liftTyped) content <- liftIO $ readFile filePath liftTyped content else do -- 每次fallback时更新标记文件内容,强制下次编译重跑TH liftIO $ do currentTime <- formatTime defaultTimeLocale "%s" <$> getCurrentTime writeFile markerFile currentTime liftSplice (addDependentFile markerFile >>= liftTyped) content <- liftIO $ readFile fallbackPath liftTyped content
原理是:当使用fallback时,每次都会更新标记文件的内容,GHC依赖该文件后,下次编译会检测到文件变化,从而重新执行TH代码,再次检查目标文件是否存在。
内容的提问来源于stack exchange,提问作者xbalaj
相关产品推荐
相关产品推荐

