How to run a shell command only if a file's contents change?

12 views
Skip to first unread message

mitchel...@gmail.com

unread,
Feb 18, 2018, 6:07:33 PM2/18/18
to Shake build system
Hi again,

What is the recommended way of running some Action if a file (or part of a file) change?

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

Browsing the docs, it seems that the "Oracle" abstraction is the only mechanism by which Shake exposes comparing a value to the last (via Eq). So, I might have 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.

I briefly looked at "addBuiltinRule" and "addUserRule" in "Development.Shake.Rule", and one of the two seems likely to be a solution for me, but I can't quite figure out how either one works :)

Thanks for the help.

Neil Mitchell

unread,
Feb 18, 2018, 6:15:39 PM2/18/18
to Mitchell Rosen, Shake build system
Hi Mitchell,

Your askOracle approach is pretty close, but Shake needs to be able to
identify the "output" of the file, so it can give it a persistent name
between runs, 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_ ...
writeFile' out ""
want ["packages.stamp"]

An alternative to Oracle is to have a file
elm-package-dependencies.json which you generate from
elm-package.json, write using writeFileIfChanged, 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 file to force a rerun.

Thanks, Neil

mitchel...@gmail.com

unread,
Feb 18, 2018, 6:55:08 PM2/18/18
to Shake build system
Great! That worked. Just to clarify, and for anyone else reading, I believe you meant the alternative to using an Oracle involves two additional files:

1) "elm-package-dependencies.json", created from the "dependencies" field of "elm-package.json". Something like:

```
".shake/elm-package-dependencies.json" %> \out -> do
bytes <- readFile' "elm-dependencies.json"
case parse bytes of ->
Left err -> fail err
Right deps -> writeFileChanged out deps
```

2) An empty file like "elm-package-dependencies.stamp", created with:

```
".shake/elm-package-dependencies.stamp" %> \out -> do
need [".shake/elm-package-dependencies.json"]


cmd_ "elm package install --yes"

writeFile' out ""
```

Then, the main build command that we want to install dependencies *prior* to running, would incur a "need" on the stamp file.

Reply all
Reply to author
Forward
0 new messages