如何在Shake中编写定点构建规则,例如胶乳

时间:2013-01-31 09:18:48

标签: haskell shake-build-system

使用Shake Haskell构建库,如何使用需要达到固定点的程序编写规则?想象一下,我有一个程序foo,它接受​​一个文件input并生成一个输出文件,该文件应该重复应用foo,直到输出文件没有改变。我怎么能在Shake中写出来?

这种模式的典型例子是LaTeX。

2 个答案:

答案 0 :(得分:13)

首先,请注意,重复调用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'两次(一次为i-1,一次为i)。

答案 1 :(得分:1)

扩展@Neil Mitchell的答案,下面是foo_transitive的示例代码。话虽如此,对于这个特殊情况,我只会使用latexmk做出正确的事情。

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