使用 Shake Haskell 构建库,如何使用需要达到固定点的程序编写规则?想象一下,我有一个程序foo
,它获取一个文件input
并生成一个输出文件,它应该foo
重复应用,直到输出文件不改变。我怎样才能在 Shake 中写出来?
这种模式的典型例子是 LaTeX。
使用 Shake Haskell 构建库,如何使用需要达到固定点的程序编写规则?想象一下,我有一个程序foo
,它获取一个文件input
并生成一个输出文件,它应该foo
重复应用,直到输出文件不改变。我怎样才能在 Shake 中写出来?
这种模式的典型例子是 LaTeX。
首先,请注意重复调用 Latex 并不总是产生一个固定点,因此请确保您对迭代有限制。此外,一些发行版 (MikTex) 提供了可以根据需要自动运行多次的 Latex 版本,因此如果您使用这些版本,问题就会消失。
编写自己的foo_transitive
命令
假设每次运行都foo
具有相同的依赖关系,解决问题的最简单方法是在构建系统之外解决问题。只需编写一个foo_transitive
命令,无论是作为 shell 脚本还是作为 Haskell 函数,当提供输入文件时,通过重复运行并检查它是否已达到固定点来生成输出文件。构建系统现在可以使用foo_transitive
并且没有关于依赖项的问题。
在构建系统中对其进行编码
你需要写两条规则,一条是一步,另一条是确定哪一步是正确的:
let step i = "tempfile" <.> show i
"tempfile.*" *> \out -> do
let i = read $ takeExtension out :: Int
if i == 0 then
copyFile "input" out
else
let prev = step (i-1)
need [prev]
-- perhaps require addition dependencies, depending on prev
system' "foo" [prev,out]
"output" *> \out -> do
let f i = do
old <- readFile' $ step (i-1)
new <- readFile' $ step i
if old == new || i > 100 then copyFile (step i) out else f (i+1)
f 1
第一条规则生成tempfile.2
自tempfile.1
等等,所以我们可以need ["tempfile.100"]
得到第 100 次迭代。如果每个步骤中的依赖关系发生变化,我们可以查看之前的结果来计算新的依赖关系。
第二条规则循环检查序列中的每一对值,并在它们相等时停止。如果您在生产构建系统中实现此功能,您可能希望避免调用readFile'
每个元素两次(一次 asi-1
和一次 as i
)。
扩展@Neil Mitchell的答案,下面是foo_transitive
. 话虽如此,对于这种特殊情况,我只使用latexmk
which Does The Right Thing™。
import Control.Monad.Fix (fix, mfix)
import Control.Monad.IO.Class (MonadIO(liftIO))
import Text.Printf (printf)
type SHA = Int
data TeXCompilationStage
= Init
| BibTeX
| Recompile SHA
deriving (Show, Eq)
data TeXResult
= Stable SHA
| Unstable
deriving (Show, Eq)
f retry x budgetLaTeXCalls
| budgetLaTeXCalls <= 0
= do
liftIO $ putStrLn "Budget for LaTeX depleted; result didn't converge"
return Unstable
| otherwise
= case x of
Init -> do
liftIO $ do
putStrLn "Init"
putStrLn " # latex"
retry BibTeX (budgetLaTeXCalls-1)
BibTeX -> do
liftIO $ do
putStrLn "BibTeX"
putStrLn " # bibtex"
retry (Recompile 0) budgetLaTeXCalls
Recompile previousSHA -> do
let budgetLaTeXCalls' = budgetLaTeXCalls - 1
calculcatedSHA = 3
liftIO $ do
printf "Recompile (budget: %d)\n" budgetLaTeXCalls
printf " Prevous SHA:%d\n Current SHA:%d\n" previousSHA calculcatedSHA
if calculcatedSHA == previousSHA
then do
liftIO $ putStrLn " Stabilized"
return $ Stable calculcatedSHA
else do
liftIO $ putStrLn " Unstable"
retry (Recompile (previousSHA+1)) (budgetLaTeXCalls-1)
latex :: Int -> IO TeXResult
latex = fix f Init