packages feed

nix-freeze-tree-0.1.0.1: src/NixHash.hs

{-# LANGUAGE DataKinds         #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TypeApplications  #-}

module NixHash (mkFileAttrs, replaceLeadingDot) where

import           BuilderDirs (BuilderDirs, currentDir)
import           Control.Monad.IO.Class (MonadIO (..))
import           Control.Monad.Trans.Reader
import           Crypto.Hash (Digest, SHA256, hash)
import qualified Data.ByteString as B
import           Data.Text (Text)
import qualified Data.Text as T
import           Nix
import           Path (Abs, File, Path, Rel, toFilePath, (</>))
import           Path.IO (withSystemTempFile)
import           System.IO
import           System.Nix.Hash (BaseEncoding (..), encodeDigestWith)
import           System.Nix.Nar (buildNarIO, narEffectsIO)

-- | Build a set of @filename -> derivation@ attributes.
mkFileAttrs :: [Path Rel File] -> ReaderT BuilderDirs IO [(Text, NExpr)]
mkFileAttrs = traverse $ \f -> do
  h <- mkFileAttr f
  pure (T.pack (toFilePath f), h)

-- | Hash the file, using Nix's NAR hash (the default to
-- @builtins.path@ is NAR-style recursive hashing), and build a call
-- to Nix's @builtins.path@ function. If @"file1"@ is a regular file,
-- @mkHashEntry "file1"@ will generate:
--
-- @
-- builtins.path {
--   path = ./file1;
--   sha256 = "<<hash of file1>>";
-- }
-- @
--
-- If @".file2"@ starts with a dot, that's an illegal Nix derivation
-- name, so @mkHashEntry ".file2"@ will generate:
--
-- @
-- builtins.path {
--   path = ./.file2;
--   name = "_file2";
--   sha256 = "<<hash of .file2>>"
-- }
-- @
mkFileAttr :: Path Rel File -> ReaderT BuilderDirs IO NExpr
mkFileAttr f = do
  absF <- (</> f) <$> currentDir
  h <- recursiveHash absF

  let bindings =
        [ "path" $= mkPath False (toFilePath absF)
        , "sha256" $= mkStr h
        ] ++ [ "name" $= mkStr (T.pack . replaceLeadingDot $ toFilePath f)
             | head (toFilePath f) == '.'
             ]
  pure $ "builtins" @. "path" @@ mkNonRecSet bindings


-- | Serialise as per
-- https://gist.github.com/jbeda/5c79d2b1434f0018d693 , which is an
-- extract from Figure 5.2 of http://nixos.org/~eelco/pubs/phd-thesis.pdf ,
-- which is the Nix thesis.
recursiveHash :: MonadIO m => Path Abs File -> m Text
recursiveHash absF =
  liftIO . withSystemTempFile "nix-freeze-tree.nar" $ \_ h -> do
    buildNarIO narEffectsIO (toFilePath absF) h
    hSeek h AbsoluteSeek 0
    contents <- B.hGetContents h
    pure $ encodeDigestWith NixBase32 (hash contents :: Digest SHA256)

-- | A leading dot is illegal in Nix derivation names. Replace it with '_'.
replaceLeadingDot :: FilePath -> FilePath
replaceLeadingDot ('.':f) = '_':f
replaceLeadingDot f = f