hnix/Nix/Monad/Instance.hs
2018-04-07 11:38:12 -07:00

176 lines
6.5 KiB
Haskell

{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE PartialTypeSignatures #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
{-# OPTIONS_GHC -Wno-missing-signatures #-}
{-# OPTIONS_GHC -fno-warn-name-shadowing #-}
module Nix.Monad.Instance where
import Control.Monad
import Control.Monad.Fix
import Control.Monad.IO.Class
import Control.Monad.Reader (MonadReader)
import Control.Monad.Trans.Reader
import qualified Data.ByteString as BS
import Data.Fix
import qualified Data.HashMap.Lazy as M
import Data.IORef
import Data.List
import Data.List.Split
import Data.Text (Text)
import qualified Data.Text as Text
import Nix.Atoms
import Nix.Eval
import Nix.Monad
import Nix.Parser
import Nix.Scope
import Nix.Stack
import Nix.Thunk
import Nix.Utils
import Nix.Value
import System.Directory
import System.Environment
import System.Exit (ExitCode (ExitSuccess))
import System.FilePath
import qualified System.Info
import System.Posix.Files
import System.Process (readProcessWithExitCode)
data Context m v = Context
{ scopes :: Scopes m v
, frames :: Frames
}
newtype Lazy m a = Lazy
{ runLazy :: ReaderT (Context (Lazy m) (NThunk (Lazy m))) m a }
deriving (Functor, Applicative, Monad, MonadFix, MonadIO,
MonadReader (Context (Lazy m) (NThunk (Lazy m))))
instance Has (Context m v) (Scopes m v) where
hasLens f (Context x y) = flip Context y <$> f x
instance Has (Context m v) Frames where
hasLens f (Context x y) = Context x <$> f y
-- | Incorrectly normalize paths by rewriting patterns like @a/b/..@ to @a@.
-- This is incorrect on POSIX systems, because if @b@ is a symlink, its parent
-- may be a different directory from @a@. See the discussion at
-- https://hackage.haskell.org/package/directory-1.3.1.5/docs/System-Directory.html#v:canonicalizePath
removeDotDotIndirections :: FilePath -> FilePath
removeDotDotIndirections = intercalate "/" . go [] . splitOn "/"
where go s [] = reverse s
go (_:s) ("..":rest) = go s rest
go s (this:rest) = go (this:s) rest
instance (MonadFix m, MonadNix (Lazy m), MonadIO m)
=> MonadExpr (NThunk (Lazy m)) (NValue (Lazy m)) (Lazy m) where
embedSet = return . flip NVSet M.empty
projectSet = \case
NVSet s _ -> return $ Just s
_ -> return Nothing
projectSetWithPos = \case
NVSet s p -> return $ Just (s, p)
_ -> return Nothing
type MText (Lazy m) = (Text, DList Text)
wrapText = return . (, mempty)
unwrapText = return . fst
embedText = return . uncurry NVStr
projectText = \case
NVConstant NNull -> return $ Just Nothing
v -> fmap (Just . Just) . valueText True =<< normalForm v
instance MonadIO m => MonadVar (Lazy m) where
type Var (Lazy m) = IORef
newVar = liftIO . newIORef
readVar = liftIO . readIORef
writeVar = (liftIO .) . writeIORef
atomicModifyVar = (liftIO .) . atomicModifyIORef
instance MonadIO m => MonadFile (Lazy m) where
readFile = liftIO . BS.readFile
instance (MonadFix m, MonadIO m) => MonadNix (Lazy m) where
addPath path = liftIO $ do
(exitCode, out, _) <-
readProcessWithExitCode "nix-store" ["--add", path] ""
case exitCode of
ExitSuccess -> do
let dropTrailingLinefeed p = take (length p - 1) p
return $ StorePath $ dropTrailingLinefeed out
_ -> error $ "No such file or directory: " ++ show path
makeAbsolutePath origPath = do
absPath <- if isAbsolute origPath then pure origPath else do
cwd <- do
mres <- lookupVar @_ @(NThunk (Lazy m)) "__cur_file"
case mres of
Nothing -> liftIO getCurrentDirectory
Just v -> force v $ \case
NVLiteralPath s -> return $ takeDirectory s
v -> throwError $ "when resolving relative path,"
++ " __cur_file is in scope,"
++ " but is not a path; it is: "
++ show (void v)
pure $ cwd </> origPath
liftIO $ removeDotDotIndirections <$> canonicalizePath absPath
pathExists = liftIO . fileExist
-- jww (2018-03-29): Cache which files have been read in.
importFile scope path = do
mres <- lookupVar @(Context (Lazy m) (NThunk (Lazy m)))
"__cur_file"
path' <- case mres of
Nothing -> do
traceM "No known current directory"
return path
Just p -> force p $ normalForm >=> \case
Fix (NVLiteralPath p') -> do
traceM $ "Current file being evaluated is: "
++ show p'
return $ takeDirectory p' </> path
x -> error $ "How can the current directory be: " ++ show x
traceM $ "Importing file " ++ path'
withStringContext ("While importing file " ++ show path') $ do
eres <- Lazy $ parseNixFileLoc path'
case eres of
Failure err -> error $ "Parse failed: " ++ show err
Success expr -> do
let ref = valueThunk @(Lazy m) (NVLiteralPath path')
-- Use this cookie so that when we evaluate the next
-- import, we'll remember which directory its containing
-- file was in.
pushScope (M.singleton "__cur_file" ref)
(pushScope scope (framedEvalExpr eval expr))
getEnvVar = liftIO . lookupEnv
getCurrentSystemOS = return $ Text.pack System.Info.os
-- Invert the conversion done by GHC_CONVERT_CPU in GHC's aclocal.m4
getCurrentSystemArch = return $ Text.pack $ case System.Info.arch of
"i386" -> "i686"
arch -> arch
listDirectory = liftIO . System.Directory.listDirectory
getSymbolicLinkStatus = liftIO . System.Posix.Files.getSymbolicLinkStatus
runLazyM :: MonadIO m => Lazy m a -> m a
runLazyM = flip runReaderT (Context emptyScopes []) . runLazy