Search code examples
haskellshake-build-system

Running an Action if part of a file changes


What is the recommended way of running some Action if part of a file changes?

My use-case is given a file that I know exists (concretely elm-package.json), run a shell command (elm package install --yes) if part of the file changes (the dependencies field).

It seems that the Oracle abstraction exposes comparing a value to the last (via Eq). So I tried a newtype like:

newtype ElmDependencies = ElmDependencies () deriving ...
type instance RuleResult ElmDependencies = String

But now, I get stuck actually using this function of type ElmDependencies -> Action String, since the rule I want to write doesn't actually care what the returned String is, it simply wants to be called if the String changes.

In other words,

action $ do
    _ <- askOracle (ElmDependencies ())
    cmd_ "elm package install --yes"

at the top-level doesn't work; it will run the action every time.


Solution

  • Your askOracle approach is pretty close, but Shake needs to be able to identify the "output" of the action, so it can give it a persistent name between runs, so other steps can depend on it, and use that persistent name to avoid recomputing. One way to do that is to make the action create a stamp file, e.g.:

    "packages.stamp" *> \out -> do
        _ <- askOracle $ ElmDependencies ()
        cmd_ "elm package install --yes"
        writeFile' out ""
    want ["packages.stamp"]
    

    Separately, an alternative to using Oracle is to have a file elm-package-dependencies.json which you generate from elm-package.json, write using writeFileIfChanged (which gives you Eq for files), and depend on that file in packages.stamp. That way you get Eq on files, and can also easily debug it or delete the -dependencies.json file to force a rerun.