I have a shake build (version 0.16.4) that builds a lot of docker images out of Dockerfiles and other support files. I would like to factor out all those build in a single "rule" whose output will be a docker image. I have read How to define a timer rule in Shake about custom rules in shake but this does not explain how to define one's own output. 
I would like to express something like
 Image "foo" "latest" %> \ img -> do
     need [ ... ]
     buildDockerImage "docker/foo" img
 Image "bar" "latest" %> \ img -> 
     needImage "foo" "latest"
     ...
and then have shake track the image as a dependency. I have already implemented that kind of stuff in an older version of shake but I am clueless about how to do it in > 0.16.
Update:
I have tried this, following the guidelines from https://hackage.haskell.org/package/shake-0.17.3/docs/Development-Shake-Rule.html
newtype Repo = Repo String
    deriving (Show, Eq, Hashable, Binary, NFData)
newtype Tag = Tag String
    deriving (Show, Eq, Hashable, Binary, NFData)
newtype SHA256 = SHA256 String
    deriving (Show, Eq, Hashable, Binary, NFData)
newtype Image = Image (Repo,Tag)
    deriving (Show, Eq, Hashable, Binary, NFData)
type instance RuleResult Image = SHA256
data ImageRule = ImageRule Image (Action ())
imageRule :: (Repo, Tag) -> Action () -> Rules ()
imageRule k a = addUserRule $ ImageRule (Image k) a
needImage :: (Repo,Tag) -> Action SHA256
needImage = apply1 . Image
toBytes :: String -> BS.ByteString
toBytes = encodeUtf8 . Text.pack
fromBytes :: BS.ByteString -> String
fromBytes = Text.unpack . decodeUtf8
addBuiltinDockerRule :: Rules ()
addBuiltinDockerRule = addBuiltinRule noLint imageIdentity run
    where
      imageIdentity _ (SHA256 v) = toBytes v
      imageSha (Image (Repo r,Tag t)) = do
        Stdout out <- cmd "docker" [ "images", "--no-trunc", "-q", r <> ":" <> t ]
        pure $ BS.unpack out
      run :: BuiltinRun Image SHA256
      run key old mode = do
          current <- imageSha key
          liftIO $ putStrLn ("current:"  <> show current)
          if mode == RunDependenciesSame && fmap BS.unpack old == Just current then
              return $ RunResult ChangedNothing (BS.pack current) (SHA256 $ fromBytes $ BS.pack current)
          else do
              (_, act) <- getUserRuleOne key (const Nothing) $ \(ImageRule k act) -> if k == key then Just act else Nothing
              act
              current <- imageSha key
              return $ RunResult ChangedRecomputeDiff (BS.pack current) (SHA256 $ fromBytes $ BS.pack current)
And then using it in a Build.hs file:
main :: IO ()
main = shakeArgs options $ do
  addBuiltinDockerRule
  want [ ".haskell.img" ]
  imageRule (Repo "haskell", Tag "latest") $ do
    need [ "docker/haskell/Dockerfile" ]
    cmd "docker" [ "build", "-t", "haskell:latest", "-f", "docker/haskell/Dockerfile" ]
  ".haskell.img" %> \ fp -> do
    needImage (Repo "haskell", Tag "latest")
    Stdout out <- cmd "docker" [ "images", "--no-trunc", "-q", "haskell:latest" ]
    writeFile' fp out
This seems to work but one shortcoming is that it's not possible to want an image: I have to add a rule with file to make it work. 
So the proposed solution works. One can add a "phony" target to ensure the images are built on demand:
"haskell.img" ~> void (needImage "haskell")
                        If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With