@@ -9,14 +9,16 @@ module Obelisk.Command.Thunk
9 , getLatestRev
9 , getLatestRev
10 , updateThunkToLatest
10 , updateThunkToLatest
11 , createThunkWithLatest
11 , createThunkWithLatest
12− , nixBuildThunkAttrWithCache
12+ , nixBuildAttrWithCache
15import Control.Exception
15import Control.Exception
16import Control.Monad
16import Control.Monad
17+import Control.Monad.Except
17import Data.Aeson as Aeson
18import Data.Aeson as Aeson
18import Data.Aeson.Encode.Pretty
19import Data.Aeson.Encode.Pretty
19import qualified Data.ByteString.Lazy as LBS
20import qualified Data.ByteString.Lazy as LBS
20import Data.Foldable
22import Data.Foldable
21import Data.Git.Ref (Ref)
23import Data.Git.Ref (Ref)
22import qualified Data.Git.Ref as Ref
24import qualified Data.Git.Ref as Ref
@@ -24,6 +26,7 @@ import Data.List.NonEmpty (NonEmpty (..))
24import qualified Data.List.NonEmpty as NonEmpty
26import qualified Data.List.NonEmpty as NonEmpty
25import Data.Maybe
27import Data.Maybe
26import Data.Monoid
28import Data.Monoid
27import qualified Data.Set as Set
30import qualified Data.Set as Set
28import Data.Text (Text)
31import Data.Text (Text)
29import qualified Data.Text as T
32import qualified Data.Text as T
@@ -36,6 +39,7 @@ import GitHub
36import GitHub.Endpoints.Repos.Contents (archiveForR)
39import GitHub.Endpoints.Repos.Contents (archiveForR)
37import GitHub.Data.Name
40import GitHub.Data.Name
38import Network.URI
41import Network.URI
42+import Obelisk.Command.Nix
39import System.Directory
43import System.Directory
40import System.Exit
44import System.Exit
41import System.FilePath
45import System.FilePath
@@ -109,10 +113,15 @@ getLatestRev = \case
109 , _thunkRev_nixSha256 = nixSha256
113 , _thunkRev_nixSha256 = nixSha256
117+ = ReadThunkError_WrongContents (Set String) (Set String)
118+ | ReadThunkError_UnrecognizedLoader Text
119+ | ReadThunkError_UnparseablePtr LBS.ByteString
112-- | Read a thunk and validate that it is only a thunk. If additional data is
121-- | Read a thunk and validate that it is only a thunk. If additional data is
113-- present, fail.
122-- present, fail.
114−readThunk :: FilePath -> IO (Maybe ThunkPtr)
123+readThunk :: FilePath -> IO (Either ReadThunkError ThunkPtr)
115−readThunk thunkDir = do
124+readThunk thunkDir = runExceptT $ do
116 let thunkLoader = thunkDir </> "default.nix"
125 let thunkLoader = thunkDir </> "default.nix"
117 githubJson = thunkDir </> "github.json"
126 githubJson = thunkDir </> "github.json"
118 attrCache = thunkDir </> ".attr-cache"
127 attrCache = thunkDir </> ".attr-cache"
@@ -124,16 +133,18 @@ readThunk thunkDir = do
125 -- Ensure that there aren't any other files in the thunk
134 -- Ensure that there aren't any other files in the thunk
126 -- NB: System.Directory.listDirectory returns the contents without the directory path
135 -- NB: System.Directory.listDirectory returns the contents without the directory path
127− files <- Set.fromList . fmap (thunkDir </>) <$> listDirectory thunkDir
136+ files <- liftIO $ Set.fromList . fmap (thunkDir </>) <$> listDirectory thunkDir
128 let unexpectedContents = files `Set.difference` expectedContents
137 let unexpectedContents = files `Set.difference` expectedContents
129 missingContents = expectedContents `Set.difference` files
138 missingContents = expectedContents `Set.difference` files
130− guard $ Set.null unexpectedContents && Set.null missingContents
139+ unless (Set.null unexpectedContents && Set.null missingContents) $ do
140+ throwError $ ReadThunkError_WrongContents unexpectedContents missingContents
132 -- Ensure that we recognize the thunk loader
142 -- Ensure that we recognize the thunk loader
133− loader <- T.readFile thunkLoader
143+ loader <- liftIO $ T.readFile thunkLoader
134− guard $ loader `elem` gitHubStandaloneLoaders
144+ unless (loader `elem` gitHubStandaloneLoaders) $ do
145+ throwError $ ReadThunkError_UnrecognizedLoader loader
136− txt <- LBS.readFile githubJson
147+ txt <- liftIO $ LBS.readFile githubJson
137 let p v = do
148 let p v = do
138 owner <- v Aeson..: "owner"
149 owner <- v Aeson..: "owner"
139 repo <- v Aeson..: "repo"
150 repo <- v Aeson..: "repo"
@@ -153,12 +164,14 @@ readThunk thunkDir = do
153 , _gitHubSource_private = fromMaybe False mPrivate
164 , _gitHubSource_private = fromMaybe False mPrivate
156− return $ parseMaybe p =<< decode txt
167+ case parseMaybe p =<< decode txt of
168+ Nothing -> throwError $ ReadThunkError_UnparseablePtr txt
169+ Just ptr -> return ptr
158overwriteThunk :: FilePath -> ThunkPtr -> IO ()
171overwriteThunk :: FilePath -> ThunkPtr -> IO ()
159overwriteThunk target thunk = do
172overwriteThunk target thunk = do
160 -- Ensure that this directory is a valid thunk (i.e. so we aren't losing any data)
173 -- Ensure that this directory is a valid thunk (i.e. so we aren't losing any data)
161− Just _ <- readThunk target
174+ Right _ <- readThunk target
163 --TODO: Is there a safer way to do this overwriting?
176 --TODO: Is there a safer way to do this overwriting?
164 removeDirectoryRecursive target
177 removeDirectoryRecursive target
@@ -208,7 +221,7 @@ createThunkWithLatest target s = do
209updateThunkToLatest :: FilePath -> IO ()
222updateThunkToLatest :: FilePath -> IO ()
210updateThunkToLatest target = do
223updateThunkToLatest target = do
211− Just ptr <- readThunk target
224+ Right ptr <- readThunk target
212 let src = _thunkPtr_source ptr
225 let src = _thunkPtr_source ptr
213 rev <- getLatestRev src
226 rev <- getLatestRev src
214 overwriteThunk target $ ThunkPtr
227 overwriteThunk target $ ThunkPtr
@@ -285,15 +298,30 @@ nixBuildThunkAttrWithCache thunkDir attr = do
285 case cacheHit of
298 case cacheHit of
286 Just c -> return c
299 Just c -> return c
287 Nothing -> do
300 Nothing -> do
288− hPutStrLn stderr (mconcat [ thunkDir, ":", attr, " not cached, building ..."])
301+ hPutStrLn stderr (mconcat [thunkDir, ": ", attr, " not cached, building ..."])
289− (_, _, err, p) <- runInteractiveProcess "nix-build"
302+ _ <- nixBuild $ def
303+ { _nixBuildConfig_target = Target
290− [ thunkDir
304+ { _target_path = thunkDir
291− , "-A", attr
305+ , _target_attr = Just attr
292− , "--indirect", "--add-root", cachePath
306+ }
293− ] Nothing Nothing
307+ , _nixBuildConfig_outLink = OutLink_IndirectRoot cachePath
294− waitForProcess p >>= \case
308+ }
295− ExitSuccess -> return ()
297− LBS.putStr =<< LBS.hGetContents err
298− fail "nix-build failed"
299 return cachePath
309 return cachePath
311+-- | Build a nix attribute, and cache the result if possible
312+nixBuildAttrWithCache :: FilePath
313+ -- ^ Path to directory containing Thunk
315+ -- ^ Attribute to build
317+ -- ^ Symlink to cached or built nix output
318+nixBuildAttrWithCache exprPath attr = do
319+ readThunk exprPath >>= \case
320+ Right _ -> nixBuildThunkAttrWithCache exprPath attr
321+ Left _ -> nixBuild $ def
322+ { _nixBuildConfig_target = Target
323+ { _target_path = exprPath
324+ , _target_attr = Just attr
326+ , _nixBuildConfig_outLink = OutLink_None