Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to write fixed point build rules in Shake, e.g. Latex

Using the Shake Haskell build library, how can I write a rule using a program that needs to reach a fixed point? Imagine I have a program foo that takes a file input and produces an output file, which should have foo applied repeatedly until the output file does not change. How can I write that in Shake?

The typical example of this pattern is LaTeX.

like image 424
Neil Mitchell Avatar asked Jan 31 '13 09:01

Neil Mitchell


1 Answers

Firstly, note that calling Latex repeatedly does not always produce a fixed point, so make sure you have a bound on the iterations. Also, some distributions (MikTex) provide Latex versions that automatically run as many times as they need to, so if you use those instead the problem goes away.

Write your own foo_transitive command

The easiest way to solve the problem, assuming each run of foo has the same dependencies, is to solve the problem outside the build system. Just write a foo_transitive command, either as a shell script or as a Haskell function, that when supplied an input file produces an output file by running repeatedly and checking if it has reached a fixed point. The build system can now use foo_transitive and there are no issues about dependencies.

Encode it in the build system

You need to write two rules, one which makes one step, and one which figures out which step is the right one to use:

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

The first rule generates tempfile.2 from tempfile.1 and so on, so we can need ["tempfile.100"] to get the 100th iteration. If the dependencies change in each step we can look at the previous result to calculate the new dependencies.

The second rule loops round checking each pair of values in the sequence, and stopping when they are equal. If you are implementing this in a production build system you may wish to avoid calling readFile' on each element twice (once as i-1 and once as i).

like image 94
Neil Mitchell Avatar answered Oct 17 '22 01:10

Neil Mitchell