@@ -9,24 +9,26 @@ module Obelisk.Command.Thunk
99 , getLatestRev
1010 , updateThunkToLatest
1111 , createThunkWithLatest
12+ , nixBuildThunkAttrWithCache
1213 ) where
1314
1415import Control.Exception
1516import Control.Monad
16−import qualified Data.ByteString.Lazy as LBS
1717import Data.Aeson as Aeson
1818import Data.Aeson.Encode.Pretty
19+import qualified Data.ByteString.Lazy as LBS
1920import Data.Foldable
2021import Data.Git.Ref (Ref)
2122import qualified Data.Git.Ref as Ref
22−import Data.Maybe
23−import Data.Monoid
2423import Data.List.NonEmpty (NonEmpty (..))
2524import qualified Data.List.NonEmpty as NonEmpty
25+import Data.Maybe
26+import Data.Monoid
2627import Data.Text (Text)
2728import qualified Data.Text as T
2829import qualified Data.Text.IO as T
2930import Data.Text.Encoding
31+import Data.Time.Clock.POSIX (posixSecondsToUTCTime)
3032import Data.Yaml (parseMaybe, (.:))
3133import qualified Data.Yaml as Yaml
3234import GitHub
@@ -36,6 +38,9 @@ import Network.URI
3638import System.Directory
3739import System.Exit
3840import System.FilePath
41+import System.IO
42+import System.IO.Error
43+import System.Posix (modificationTime, getSymbolicLinkStatus)
3944import System.Process
4045
4146-- | A reference to the exact data that a thunk should translate into
@@ -241,3 +246,45 @@ getHubAuth domain = do
241246 return $ OAuth $ encodeUtf8 token
242247
243248--TODO: when checking something out, make a shallow clone
249+
250+-- | Checks a cache directory to see if there is a fresh symlink
251+-- to the result of building an attribute of a thunk.
252+-- If no cache hit is found, nix-build is called to build the attribute
253+-- and the result is symlinked into the cache.
254+nixBuildThunkAttrWithCache :: FilePath
255+ -- ^ Path to directory containing Thunk
256+ -> String
257+ -- ^ Attribute to build
258+ -> IO FilePath
259+ -- ^ Symlink to cached or built nix output
260+-- WARNING: If the thunk uses an impure reference such as '<nixpkgs>'
261+-- the caching mechanism will fail as it merely measures the modification
262+-- time of the cache link and the expression to build.
263+nixBuildThunkAttrWithCache thunkDir attr = do
264+ let cacheErrHandler e
265+ | isDoesNotExistError e = return Nothing -- expected from a cache miss
266+ | otherwise = hPutStrLn stderr (displayException e) >> return Nothing
267+ cacheDir = thunkDir </> ".attr-cache"
268+ cachePath = cacheDir </> attr <.> "out"
269+ createDirectoryIfMissing False cacheDir
270+ latestChange <- maximum <$> mapM (getModificationTime . (thunkDir </>)) ["default.nix", "github.json"]
271+ cacheHit <- handle cacheErrHandler $ do
272+ cacheTime <- posixSecondsToUTCTime . realToFrac . modificationTime <$> getSymbolicLinkStatus cachePath
273+ return $ if latestChange <= cacheTime
274+ then Just cachePath
275+ else Nothing
276+ case cacheHit of
277+ Just c -> return c
278+ Nothing -> do
279+ hPutStrLn stderr (mconcat [ thunkDir, ":", attr, " not cached, building ..."])
280+ (_, _, err, p) <- runInteractiveProcess "nix-build"
281+ [ thunkDir
282+ , "-A", attr
283+ , "--indirect", "--add-root", cachePath
284+ ] Nothing Nothing
285+ waitForProcess p >>= \case
286+ ExitSuccess -> return ()
287+ _ -> do
288+ LBS.putStr =<< LBS.hGetContents err
289+ fail "nix-build failed"
290+ return cachePath