957 lines
32 KiB
Haskell
957 lines
32 KiB
Haskell
{-# LANGUAGE AllowAmbiguousTypes #-}
|
|
{-# LANGUAGE ApplicativeDo #-}
|
|
{-# LANGUAGE CPP #-}
|
|
{-# LANGUAGE ConstraintKinds #-}
|
|
{-# LANGUAGE DeriveFunctor #-}
|
|
{-# LANGUAGE FlexibleContexts #-}
|
|
{-# LANGUAGE FlexibleInstances #-}
|
|
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
|
|
{-# LANGUAGE LambdaCase #-}
|
|
{-# LANGUAGE MultiParamTypeClasses #-}
|
|
{-# LANGUAGE MultiWayIf #-}
|
|
{-# LANGUAGE OverloadedStrings #-}
|
|
{-# LANGUAGE PartialTypeSignatures #-}
|
|
{-# LANGUAGE RankNTypes #-}
|
|
{-# LANGUAGE ScopedTypeVariables #-}
|
|
{-# LANGUAGE TupleSections #-}
|
|
{-# LANGUAGE TypeApplications #-}
|
|
{-# LANGUAGE TypeFamilies #-}
|
|
{-# LANGUAGE TypeSynonymInstances #-}
|
|
{-# LANGUAGE UndecidableInstances #-}
|
|
{-# LANGUAGE ViewPatterns #-}
|
|
|
|
{-# OPTIONS_GHC -Wno-missing-signatures #-}
|
|
{-# OPTIONS_GHC -Wno-orphans #-}
|
|
{-# OPTIONS_GHC -fno-warn-name-shadowing #-}
|
|
|
|
module Nix.Exec where
|
|
|
|
import Prelude hiding ( putStr
|
|
, putStrLn
|
|
, print
|
|
)
|
|
|
|
import Control.Applicative
|
|
import Control.Monad
|
|
import Control.Monad.Catch hiding ( catchJust )
|
|
import Control.Monad.Fix
|
|
import Control.Monad.Free
|
|
import Control.Monad.Reader
|
|
import Control.Monad.Ref
|
|
import Control.Monad.State.Strict
|
|
import Control.Monad.Trans.Reader ( ReaderT(..) )
|
|
import Control.Monad.Trans.State.Strict
|
|
( StateT(..) )
|
|
import Data.Fix
|
|
import Data.HashMap.Lazy ( HashMap )
|
|
import qualified Data.HashMap.Lazy as M
|
|
import Data.List
|
|
import qualified Data.List.NonEmpty as NE
|
|
import Data.List.Split
|
|
import Data.Maybe ( maybeToList )
|
|
import Data.Text ( Text )
|
|
import qualified Data.Text as Text
|
|
import Data.Text.Prettyprint.Doc
|
|
import Data.Typeable
|
|
import Nix.Atoms
|
|
import Nix.Cited
|
|
import Nix.Context
|
|
import Nix.Convert
|
|
import Nix.Effects
|
|
import Nix.Eval as Eval
|
|
import Nix.Expr
|
|
import Nix.Frames
|
|
import Nix.Normal
|
|
import Nix.Options
|
|
import Nix.Parser
|
|
import Nix.Pretty
|
|
import Nix.Render
|
|
import Nix.Scope
|
|
import Nix.String
|
|
import Nix.Thunk
|
|
import Nix.Utils
|
|
import Nix.Value
|
|
import Nix.Value.Equal
|
|
import Nix.Value.Monad
|
|
#ifdef MIN_VERSION_haskeline
|
|
import System.Console.Haskeline.MonadException hiding(catch)
|
|
#endif
|
|
import System.FilePath
|
|
#ifdef MIN_VERSION_pretty_show
|
|
import qualified Text.Show.Pretty as PS
|
|
#endif
|
|
|
|
#ifdef MIN_VERSION_ghc_datasize
|
|
#if MIN_VERSION_ghc_datasize(0,2,0) && __GLASGOW_HASKELL__ >= 804
|
|
import GHC.DataSize
|
|
#endif
|
|
#endif
|
|
|
|
type MonadCited t f m
|
|
= ( HasCitations m (NValue t f m) t
|
|
, HasCitations1 m (NValue t f m) f
|
|
, MonadDataContext f m
|
|
)
|
|
|
|
nvConstantP
|
|
:: MonadCited t f m => Provenance m (NValue t f m) -> NAtom -> NValue t f m
|
|
nvConstantP p x = addProvenance p (nvConstant x)
|
|
|
|
nvStrP
|
|
:: MonadCited t f m
|
|
=> Provenance m (NValue t f m)
|
|
-> NixString
|
|
-> NValue t f m
|
|
nvStrP p ns = addProvenance p (nvStr ns)
|
|
|
|
nvPathP
|
|
:: MonadCited t f m
|
|
=> Provenance m (NValue t f m)
|
|
-> FilePath
|
|
-> NValue t f m
|
|
nvPathP p x = addProvenance p (nvPath x)
|
|
|
|
nvListP :: MonadCited t f m
|
|
=> Provenance m (NValue t f m) -> [NValue t f m] -> NValue t f m
|
|
nvListP p l = addProvenance p (nvList l)
|
|
|
|
nvSetP
|
|
:: MonadCited t f m
|
|
=> Provenance m (NValue t f m)
|
|
-> AttrSet (NValue t f m)
|
|
-> AttrSet SourcePos
|
|
-> NValue t f m
|
|
nvSetP p s x = addProvenance p (nvSet s x)
|
|
|
|
nvClosureP
|
|
:: MonadCited t f m
|
|
=> Provenance m (NValue t f m)
|
|
-> Params ()
|
|
-> (NValue t f m -> m (NValue t f m))
|
|
-> NValue t f m
|
|
nvClosureP p x f = addProvenance p (nvClosure x f)
|
|
|
|
nvBuiltinP
|
|
:: MonadCited t f m
|
|
=> Provenance m (NValue t f m)
|
|
-> String
|
|
-> (NValue t f m -> m (NValue t f m))
|
|
-> NValue t f m
|
|
nvBuiltinP p name f = addProvenance p (nvBuiltin name f)
|
|
|
|
type MonadCitedThunks t f m
|
|
= ( MonadValue (NValue t f m) m
|
|
, MonadThunk t m (NValue t f m)
|
|
, MonadDataErrorContext t f m
|
|
, HasCitations m (NValue t f m) t
|
|
, HasCitations1 m (NValue t f m) f
|
|
)
|
|
|
|
type MonadNix e t f m
|
|
= ( Has e SrcSpan
|
|
, Has e Options
|
|
, Scoped (NValue t f m) m
|
|
, Framed e m
|
|
, MonadFix m
|
|
, MonadCatch m
|
|
, MonadThrow m
|
|
, Alternative m
|
|
, MonadEffects t f m
|
|
, MonadCitedThunks t f m
|
|
, MonadValue (NValue t f m) m
|
|
)
|
|
|
|
data ExecFrame t f m = Assertion SrcSpan (NValue t f m)
|
|
deriving (Show, Typeable)
|
|
|
|
instance MonadDataErrorContext t f m => Exception (ExecFrame t f m)
|
|
|
|
nverr :: forall e t f s m a . (MonadNix e t f m, Exception s) => s -> m a
|
|
nverr = evalError @(NValue t f m)
|
|
|
|
currentPos :: forall e m . (MonadReader e m, Has e SrcSpan) => m SrcSpan
|
|
currentPos = asks (view hasLens)
|
|
|
|
wrapExprLoc :: SrcSpan -> NExprLocF r -> NExprLoc
|
|
wrapExprLoc span x = Fix (Fix (NSym_ span "<?>") <$ x)
|
|
|
|
instance MonadNix e t f m => MonadEval (NValue t f m) m where
|
|
freeVariable var =
|
|
nverr @e @t @f
|
|
$ ErrorCall
|
|
$ "Undefined variable '"
|
|
++ Text.unpack var
|
|
++ "'"
|
|
|
|
synHole name = do
|
|
span <- currentPos
|
|
scope <- currentScopes
|
|
evalError @(NValue t f m) $ SynHole $ SynHoleInfo
|
|
{ _synHoleInfo_expr = Fix $ NSynHole_ span name
|
|
, _synHoleInfo_scope = scope
|
|
}
|
|
|
|
attrMissing ks Nothing =
|
|
evalError @(NValue t f m)
|
|
$ ErrorCall
|
|
$ "Inheriting unknown attribute: "
|
|
++ intercalate "." (map Text.unpack (NE.toList ks))
|
|
|
|
attrMissing ks (Just s) = do
|
|
s' <- prettyNValue s
|
|
evalError @(NValue t f m)
|
|
$ ErrorCall
|
|
$ "Could not look up attribute "
|
|
++ intercalate "." (map Text.unpack (NE.toList ks))
|
|
++ " in "
|
|
++ show s'
|
|
|
|
evalCurPos = do
|
|
scope <- currentScopes
|
|
span@(SrcSpan delta _) <- currentPos
|
|
addProvenance @_ @_ @(NValue t f m)
|
|
(Provenance scope (NSym_ span "__curPos"))
|
|
<$> toValue delta
|
|
|
|
evaledSym name val = do
|
|
scope <- currentScopes
|
|
span <- currentPos
|
|
pure $ addProvenance @_ @_ @(NValue t f m)
|
|
(Provenance scope (NSym_ span name))
|
|
val
|
|
|
|
evalConstant c = do
|
|
scope <- currentScopes
|
|
span <- currentPos
|
|
pure $ nvConstantP (Provenance scope (NConstant_ span c)) c
|
|
|
|
evalString = assembleString >=> \case
|
|
Just ns -> do
|
|
scope <- currentScopes
|
|
span <- currentPos
|
|
pure $ nvStrP
|
|
(Provenance
|
|
scope
|
|
(NStr_ span (DoubleQuoted [Plain (hackyStringIgnoreContext ns)]))
|
|
)
|
|
ns
|
|
Nothing -> nverr $ ErrorCall "Failed to assemble string"
|
|
|
|
evalLiteralPath p = do
|
|
scope <- currentScopes
|
|
span <- currentPos
|
|
nvPathP (Provenance scope (NLiteralPath_ span p))
|
|
<$> makeAbsolutePath @t @f @m p
|
|
|
|
evalEnvPath p = do
|
|
scope <- currentScopes
|
|
span <- currentPos
|
|
nvPathP (Provenance scope (NEnvPath_ span p)) <$> findEnvPath @t @f @m p
|
|
|
|
evalUnary op arg = do
|
|
scope <- currentScopes
|
|
span <- currentPos
|
|
execUnaryOp scope span op arg
|
|
|
|
evalBinary op larg rarg = do
|
|
scope <- currentScopes
|
|
span <- currentPos
|
|
execBinaryOp scope span op larg rarg
|
|
|
|
evalWith c b = do
|
|
scope <- currentScopes
|
|
span <- currentPos
|
|
(\b -> addProvenance (Provenance scope (NWith_ span Nothing (Just b))) b)
|
|
<$> evalWithAttrSet c b
|
|
|
|
evalIf c t f = do
|
|
scope <- currentScopes
|
|
span <- currentPos
|
|
fromValue c >>= \b -> if b
|
|
then
|
|
(\t -> addProvenance
|
|
(Provenance scope (NIf_ span (Just c) (Just t) Nothing))
|
|
t
|
|
)
|
|
<$> t
|
|
else
|
|
(\f -> addProvenance
|
|
(Provenance scope (NIf_ span (Just c) Nothing (Just f)))
|
|
f
|
|
)
|
|
<$> f
|
|
|
|
evalAssert c body = fromValue c >>= \b -> do
|
|
span <- currentPos
|
|
if b
|
|
then do
|
|
scope <- currentScopes
|
|
(\b ->
|
|
addProvenance (Provenance scope (NAssert_ span (Just c) (Just b))) b
|
|
)
|
|
<$> body
|
|
else nverr $ Assertion span c
|
|
|
|
evalApp f x = do
|
|
scope <- currentScopes
|
|
span <- currentPos
|
|
addProvenance (Provenance scope (NBinary_ span NApp (Just f) Nothing))
|
|
<$> (callFunc f =<< defer x)
|
|
|
|
evalAbs p k = do
|
|
scope <- currentScopes
|
|
span <- currentPos
|
|
pure $ nvClosureP
|
|
(Provenance scope (NAbs_ span (Nothing <$ p) Nothing))
|
|
(void p)
|
|
(\arg -> snd <$> k (pure arg) (\_ b -> ((), ) <$> b))
|
|
|
|
evalError = throwError
|
|
|
|
infixl 1 `callFunc`
|
|
callFunc
|
|
:: forall e t f m
|
|
. MonadNix e t f m
|
|
=> NValue t f m
|
|
-> NValue t f m
|
|
-> m (NValue t f m)
|
|
callFunc fun arg = demand fun $ \fun' -> do
|
|
frames :: Frames <- asks (view hasLens)
|
|
when (length frames > 2000) $ throwError $ ErrorCall
|
|
"Function call stack exhausted"
|
|
case fun' of
|
|
NVClosure params f -> do
|
|
traceM $ "callFunc:NVFunction taking " ++ show params
|
|
f arg
|
|
NVBuiltin name f -> do
|
|
span <- currentPos
|
|
withFrame Info (Calling @m @t name span) (f arg)
|
|
s@(NVSet m _) | Just f <- M.lookup "__functor" m -> do
|
|
traceM "callFunc:__functor"
|
|
demand f $ (`callFunc` s) >=> (`callFunc` arg)
|
|
x -> throwError $ ErrorCall $ "Attempt to call non-function: " ++ show x
|
|
|
|
execUnaryOp
|
|
:: (Framed e m, MonadCited t f m, Show t)
|
|
=> Scopes m (NValue t f m)
|
|
-> SrcSpan
|
|
-> NUnaryOp
|
|
-> NValue t f m
|
|
-> m (NValue t f m)
|
|
execUnaryOp scope span op arg = do
|
|
traceM "NUnary"
|
|
case arg of
|
|
NVConstant c -> case (op, c) of
|
|
(NNeg, NInt i ) -> unaryOp $ NInt (-i)
|
|
(NNeg, NFloat f) -> unaryOp $ NFloat (-f)
|
|
(NNot, NBool b ) -> unaryOp $ NBool (not b)
|
|
_ ->
|
|
throwError
|
|
$ ErrorCall
|
|
$ "unsupported argument type for unary operator "
|
|
++ show op
|
|
x ->
|
|
throwError
|
|
$ ErrorCall
|
|
$ "argument to unary operator"
|
|
++ " must evaluate to an atomic type: "
|
|
++ show x
|
|
where
|
|
unaryOp = pure . nvConstantP (Provenance scope (NUnary_ span op (Just arg)))
|
|
|
|
execBinaryOp
|
|
:: forall e t f m
|
|
. (MonadNix e t f m, MonadEval (NValue t f m) m)
|
|
=> Scopes m (NValue t f m)
|
|
-> SrcSpan
|
|
-> NBinaryOp
|
|
-> NValue t f m
|
|
-> m (NValue t f m)
|
|
-> m (NValue t f m)
|
|
|
|
execBinaryOp scope span NOr larg rarg = fromValue larg >>= \l -> if l
|
|
then orOp Nothing True
|
|
else rarg >>= \rval -> fromValue @Bool rval >>= orOp (Just rval)
|
|
where
|
|
orOp r b = pure $ nvConstantP
|
|
(Provenance scope (NBinary_ span NOr (Just larg) r))
|
|
(NBool b)
|
|
|
|
execBinaryOp scope span NAnd larg rarg = fromValue larg >>= \l -> if l
|
|
then rarg >>= \rval -> fromValue @Bool rval >>= andOp (Just rval)
|
|
else andOp Nothing False
|
|
where
|
|
andOp r b = pure $ nvConstantP
|
|
(Provenance scope (NBinary_ span NAnd (Just larg) r))
|
|
(NBool b)
|
|
|
|
execBinaryOp scope span op lval rarg = do
|
|
rval <- rarg
|
|
let bin :: (Provenance m (NValue t f m) -> a) -> a
|
|
bin f = f (Provenance scope (NBinary_ span op (Just lval) (Just rval)))
|
|
toBool = pure . bin nvConstantP . NBool
|
|
case (lval, rval) of
|
|
(NVConstant lc, NVConstant rc) -> case (op, lc, rc) of
|
|
(NEq , _, _) -> toBool =<< valueEqM lval rval
|
|
(NNEq, _, _) -> toBool . not =<< valueEqM lval rval
|
|
(NLt , l, r) -> toBool $ l < r
|
|
(NLte, l, r) -> toBool $ l <= r
|
|
(NGt , l, r) -> toBool $ l > r
|
|
(NGte, l, r) -> toBool $ l >= r
|
|
(NAnd, _, _) ->
|
|
nverr $ ErrorCall "should be impossible: && is handled above"
|
|
(NOr, _, _) ->
|
|
nverr $ ErrorCall "should be impossible: || is handled above"
|
|
(NPlus , l , r ) -> numBinOp bin (+) l r
|
|
(NMinus, l , r ) -> numBinOp bin (-) l r
|
|
(NMult , l , r ) -> numBinOp bin (*) l r
|
|
(NDiv , l , r ) -> numBinOp' bin div (/) l r
|
|
(NImpl , NBool l, NBool r) -> toBool $ not l || r
|
|
_ -> nverr $ ErrorCall $ unsupportedTypes lval rval
|
|
|
|
(NVStr ls, NVStr rs) -> case op of
|
|
NPlus -> pure $ bin nvStrP (ls `principledStringMappend` rs)
|
|
NEq -> toBool =<< valueEqM lval rval
|
|
NNEq -> toBool . not =<< valueEqM lval rval
|
|
NLt -> toBool $ ls < rs
|
|
NLte -> toBool $ ls <= rs
|
|
NGt -> toBool $ ls > rs
|
|
NGte -> toBool $ ls >= rs
|
|
_ -> nverr $ ErrorCall $ unsupportedTypes lval rval
|
|
|
|
(NVStr _, NVConstant NNull) -> case op of
|
|
NEq -> toBool False
|
|
NNEq -> toBool True
|
|
_ -> nverr $ ErrorCall $ unsupportedTypes lval rval
|
|
|
|
(NVConstant NNull, NVStr _) -> case op of
|
|
NEq -> toBool False
|
|
NNEq -> toBool True
|
|
_ -> nverr $ ErrorCall $ unsupportedTypes lval rval
|
|
|
|
(NVSet ls lp, NVSet rs rp) -> case op of
|
|
NUpdate -> pure $ bin nvSetP (rs `M.union` ls) (rp `M.union` lp)
|
|
NEq -> toBool =<< valueEqM lval rval
|
|
NNEq -> toBool . not =<< valueEqM lval rval
|
|
_ -> nverr $ ErrorCall $ unsupportedTypes lval rval
|
|
|
|
(NVSet ls lp, NVConstant NNull) -> case op of
|
|
NUpdate -> pure $ bin nvSetP ls lp
|
|
NEq -> toBool =<< valueEqM lval (nvSet M.empty M.empty)
|
|
NNEq -> toBool . not =<< valueEqM lval (nvSet M.empty M.empty)
|
|
_ -> nverr $ ErrorCall $ unsupportedTypes lval rval
|
|
|
|
(NVConstant NNull, NVSet rs rp) -> case op of
|
|
NUpdate -> pure $ bin nvSetP rs rp
|
|
NEq -> toBool =<< valueEqM (nvSet M.empty M.empty) rval
|
|
NNEq -> toBool . not =<< valueEqM (nvSet M.empty M.empty) rval
|
|
_ -> nverr $ ErrorCall $ unsupportedTypes lval rval
|
|
|
|
(ls@NVSet{}, NVStr rs) -> case op of
|
|
NPlus ->
|
|
(\ls2 -> bin nvStrP (ls2 `principledStringMappend` rs))
|
|
<$> coerceToString DontCopyToStore CoerceStringy ls
|
|
NEq -> toBool =<< valueEqM lval rval
|
|
NNEq -> toBool . not =<< valueEqM lval rval
|
|
_ -> nverr $ ErrorCall $ unsupportedTypes lval rval
|
|
|
|
(NVStr ls, rs@NVSet{}) -> case op of
|
|
NPlus ->
|
|
(\rs2 -> bin nvStrP (ls `principledStringMappend` rs2))
|
|
<$> coerceToString DontCopyToStore CoerceStringy rs
|
|
NEq -> toBool =<< valueEqM lval rval
|
|
NNEq -> toBool . not =<< valueEqM lval rval
|
|
_ -> nverr $ ErrorCall $ unsupportedTypes lval rval
|
|
|
|
(NVList ls, NVList rs) -> case op of
|
|
NConcat -> pure $ bin nvListP $ ls ++ rs
|
|
NEq -> toBool =<< valueEqM lval rval
|
|
NNEq -> toBool . not =<< valueEqM lval rval
|
|
_ -> nverr $ ErrorCall $ unsupportedTypes lval rval
|
|
|
|
(NVList ls, NVConstant NNull) -> case op of
|
|
NConcat -> pure $ bin nvListP ls
|
|
NEq -> toBool =<< valueEqM lval (nvList [])
|
|
NNEq -> toBool . not =<< valueEqM lval (nvList [])
|
|
_ -> nverr $ ErrorCall $ unsupportedTypes lval rval
|
|
|
|
(NVConstant NNull, NVList rs) -> case op of
|
|
NConcat -> pure $ bin nvListP rs
|
|
NEq -> toBool =<< valueEqM (nvList []) rval
|
|
NNEq -> toBool . not =<< valueEqM (nvList []) rval
|
|
_ -> nverr $ ErrorCall $ unsupportedTypes lval rval
|
|
|
|
(NVPath p, NVStr ns) -> case op of
|
|
NEq -> toBool False -- From eqValues in nix/src/libexpr/eval.cc
|
|
NNEq -> toBool True
|
|
NPlus -> bin nvPathP <$> makeAbsolutePath @t @f
|
|
(p `mappend` Text.unpack (hackyStringIgnoreContext ns))
|
|
_ -> nverr $ ErrorCall $ unsupportedTypes lval rval
|
|
|
|
(NVPath ls, NVPath rs) -> case op of
|
|
NPlus -> bin nvPathP <$> makeAbsolutePath @t @f (ls ++ rs)
|
|
_ -> nverr $ ErrorCall $ unsupportedTypes lval rval
|
|
|
|
_ -> case op of
|
|
NEq -> toBool False
|
|
NNEq -> toBool True
|
|
_ -> nverr $ ErrorCall $ unsupportedTypes lval rval
|
|
where
|
|
unsupportedTypes :: Show a => a -> a -> String
|
|
unsupportedTypes lval rval =
|
|
"Unsupported argument types for binary operator "
|
|
++ show op
|
|
++ ": "
|
|
++ show lval
|
|
++ ", "
|
|
++ show rval
|
|
|
|
numBinOp
|
|
:: (forall r . (Provenance m (NValue t f m) -> r) -> r)
|
|
-> (forall a . Num a => a -> a -> a)
|
|
-> NAtom
|
|
-> NAtom
|
|
-> m (NValue t f m)
|
|
numBinOp bin f = numBinOp' bin f f
|
|
|
|
numBinOp'
|
|
:: (forall r . (Provenance m (NValue t f m) -> r) -> r)
|
|
-> (Integer -> Integer -> Integer)
|
|
-> (Float -> Float -> Float)
|
|
-> NAtom
|
|
-> NAtom
|
|
-> m (NValue t f m)
|
|
numBinOp' bin intF floatF l r = case (l, r) of
|
|
(NInt li, NInt ri ) -> toInt $ li `intF` ri
|
|
(NInt li, NFloat rf) -> toFloat $ fromInteger li `floatF` rf
|
|
(NFloat lf, NInt ri ) -> toFloat $ lf `floatF` fromInteger ri
|
|
(NFloat lf, NFloat rf) -> toFloat $ lf `floatF` rf
|
|
_ -> nverr $ ErrorCall $ unsupportedTypes l r
|
|
where
|
|
toInt = pure . bin nvConstantP . NInt
|
|
toFloat = pure . bin nvConstantP . NFloat
|
|
|
|
-- | Data type to avoid boolean blindness on what used to be called coerceMore
|
|
data CoercionLevel
|
|
= CoerceStringy
|
|
-- ^ Coerce only stringlike types: strings, paths, and appropriate sets
|
|
| CoerceAny
|
|
-- ^ Coerce everything but functions
|
|
deriving (Eq,Ord,Enum,Bounded)
|
|
|
|
-- | Data type to avoid boolean blindness on what used to be called copyToStore
|
|
data CopyToStoreMode
|
|
= CopyToStore
|
|
-- ^ Add paths to the store as they are encountered
|
|
| DontCopyToStore
|
|
-- ^ Add paths to the store as they are encountered
|
|
deriving (Eq,Ord,Enum,Bounded)
|
|
|
|
coerceToString
|
|
:: MonadNix e t f m
|
|
=> CopyToStoreMode
|
|
-> CoercionLevel
|
|
-> NValue t f m
|
|
-> m NixString
|
|
coerceToString ctsm clevel = go
|
|
where
|
|
go = \case
|
|
NVConstant (NBool b)
|
|
|
|
|
-- TODO Return a singleton for "" and "1"
|
|
b && clevel == CoerceAny -> pure
|
|
$ principledMakeNixStringWithoutContext "1"
|
|
| clevel == CoerceAny -> pure $ principledMakeNixStringWithoutContext ""
|
|
NVConstant (NInt n) | clevel == CoerceAny ->
|
|
pure $ principledMakeNixStringWithoutContext $ Text.pack $ show n
|
|
NVConstant (NFloat n) | clevel == CoerceAny ->
|
|
pure $ principledMakeNixStringWithoutContext $ Text.pack $ show n
|
|
NVConstant NNull | clevel == CoerceAny ->
|
|
pure $ principledMakeNixStringWithoutContext ""
|
|
NVStr ns -> pure ns
|
|
NVPath p
|
|
| ctsm == CopyToStore -> storePathToNixString <$> addPath p
|
|
| otherwise -> pure $ principledMakeNixStringWithoutContext $ Text.pack p
|
|
NVList l | clevel == CoerceAny ->
|
|
nixStringUnwords <$> traverse (`demand` go) l
|
|
|
|
v@(NVSet s _) | Just p <- M.lookup "__toString" s ->
|
|
demand p $ (`callFunc` v) >=> go
|
|
|
|
NVSet s _ | Just p <- M.lookup "outPath" s -> demand p go
|
|
|
|
v -> throwError $ ErrorCall $ "Expected a string, but saw: " ++ show v
|
|
|
|
nixStringUnwords =
|
|
principledIntercalateNixString (principledMakeNixStringWithoutContext " ")
|
|
storePathToNixString :: StorePath -> NixString
|
|
storePathToNixString sp = principledMakeNixStringWithSingletonContext
|
|
t
|
|
(StringContext t DirectPath)
|
|
where t = Text.pack $ unStorePath sp
|
|
|
|
fromStringNoContext :: MonadNix e t f m => NixString -> m Text
|
|
fromStringNoContext ns = case principledGetStringNoContext ns of
|
|
Just str -> return str
|
|
Nothing -> throwError $ ErrorCall "expected string with no context"
|
|
|
|
newtype Lazy t (f :: * -> *) m a = Lazy
|
|
{ runLazy :: ReaderT (Context (Lazy t f m) (NValue t f (Lazy t f m)))
|
|
(StateT (HashMap FilePath NExprLoc) m) a }
|
|
deriving
|
|
( Functor
|
|
, Applicative
|
|
, Alternative
|
|
, Monad
|
|
, MonadPlus
|
|
, MonadFix
|
|
, MonadIO
|
|
, MonadCatch
|
|
, MonadThrow
|
|
, MonadReader (Context (Lazy t f m) (NValue t f (Lazy t f m)))
|
|
)
|
|
|
|
instance MonadTrans (Lazy t f) where
|
|
lift = Lazy . lift . lift
|
|
|
|
instance MonadRef m => MonadRef (Lazy t f m) where
|
|
type Ref (Lazy t f m) = Ref m
|
|
newRef = lift . newRef
|
|
readRef = lift . readRef
|
|
writeRef r = lift . writeRef r
|
|
|
|
instance MonadAtomicRef m => MonadAtomicRef (Lazy t f m) where
|
|
atomicModifyRef r = lift . atomicModifyRef r
|
|
|
|
instance (MonadFile m, Monad m) => MonadFile (Lazy t f m)
|
|
|
|
#ifdef MIN_VERSION_haskeline
|
|
instance MonadException m => MonadException (Lazy t f m) where
|
|
controlIO f = Lazy $ controlIO $ \(RunIO run) ->
|
|
let run' = RunIO (fmap Lazy . run . runLazy)
|
|
in runLazy <$> f run'
|
|
#endif
|
|
|
|
instance MonadStore m => MonadStore (Lazy t f m) where
|
|
addPath' = lift . addPath'
|
|
toFile_' n = lift . toFile_' n
|
|
|
|
instance MonadPutStr m => MonadPutStr (Lazy t f m)
|
|
instance MonadHttp m => MonadHttp (Lazy t f m)
|
|
instance MonadEnv m => MonadEnv (Lazy t f m)
|
|
instance MonadInstantiate m => MonadInstantiate (Lazy t f m)
|
|
instance MonadExec m => MonadExec (Lazy t f m)
|
|
instance MonadIntrospect m => MonadIntrospect (Lazy t f m)
|
|
|
|
instance MonadThunkId m => MonadThunkId (Lazy t f m) where
|
|
type ThunkId (Lazy t f m) = ThunkId m
|
|
|
|
instance ( MonadFix m
|
|
, MonadCatch m
|
|
, MonadFile m
|
|
, MonadStore m
|
|
, MonadPutStr m
|
|
, MonadHttp m
|
|
, MonadEnv m
|
|
, MonadInstantiate m
|
|
, MonadExec m
|
|
, MonadIntrospect m
|
|
, Alternative m
|
|
, MonadPlus m
|
|
, MonadCitedThunks t f (Lazy t f m)
|
|
)
|
|
=> MonadEffects t f (Lazy t f m) where
|
|
makeAbsolutePath origPath = do
|
|
origPathExpanded <- expandHomePath origPath
|
|
absPath <- if isAbsolute origPathExpanded
|
|
then pure origPathExpanded
|
|
else do
|
|
cwd <- do
|
|
mres <- lookupVar "__cur_file"
|
|
case mres of
|
|
Nothing -> getCurrentDirectory
|
|
Just v -> demand v $ \case
|
|
NVPath s -> return $ takeDirectory s
|
|
v ->
|
|
throwError
|
|
$ ErrorCall
|
|
$ "when resolving relative path,"
|
|
++ " __cur_file is in scope,"
|
|
++ " but is not a path; it is: "
|
|
++ show v
|
|
pure $ cwd <///> origPathExpanded
|
|
removeDotDotIndirections <$> canonicalizePath absPath
|
|
|
|
-- Given a path, determine the nix file to load
|
|
pathToDefaultNix = pathToDefaultNixFile
|
|
|
|
findEnvPath = findEnvPathM
|
|
findPath = findPathM
|
|
|
|
importPath path = do
|
|
traceM $ "Importing file " ++ path
|
|
withFrame Info (ErrorCall $ "While importing file " ++ show path) $ do
|
|
imports <- Lazy $ ReaderT $ const get
|
|
evalExprLoc =<< case M.lookup path imports of
|
|
Just expr -> pure expr
|
|
Nothing -> do
|
|
eres <- parseNixFileLoc path
|
|
case eres of
|
|
Failure err ->
|
|
throwError
|
|
$ ErrorCall
|
|
. show
|
|
$ fillSep
|
|
$ ["Parse during import failed:", err]
|
|
Success expr -> do
|
|
Lazy $ ReaderT $ const $ modify (M.insert path expr)
|
|
pure expr
|
|
|
|
derivationStrict = fromValue @(AttrSet (NValue t f (Lazy t f m))) >=> \s -> do
|
|
nn <- maybe (pure False) (force ?? fromValue) (M.lookup "__ignoreNulls" s)
|
|
s' <- M.fromList <$> mapMaybeM (handleEntry nn) (M.toList s)
|
|
v' <- normalForm =<< toValue @(AttrSet (NValue t f (Lazy t f m))) @_ @(NValue t f (Lazy t f m)) s'
|
|
nixInstantiateExpr $ "derivationStrict " ++ show (prettyNValueNF v')
|
|
where
|
|
mapMaybeM :: (a -> Lazy t f m (Maybe b)) -> [a] -> Lazy t f m [b]
|
|
mapMaybeM op = foldr f (return [])
|
|
where f x xs = op x >>= (<$> xs) . (++) . maybeToList
|
|
|
|
handleEntry :: Bool -> (Text, NValue t f (Lazy t f m))
|
|
-> Lazy t f m (Maybe (Text, NValue t f (Lazy t f m)))
|
|
handleEntry ignoreNulls (k, v) = fmap (k, ) <$> case k of
|
|
-- The `args' attribute is special: it supplies the command-line
|
|
-- arguments to the builder.
|
|
-- TODO This use of coerceToString is probably not right and may
|
|
-- not have the right arguments.
|
|
"args" -> force v $ fmap Just . coerceNixList
|
|
"__ignoreNulls" -> pure Nothing
|
|
_ -> force v $ \case
|
|
NVConstant NNull | ignoreNulls -> pure Nothing
|
|
v' -> Just <$> coerceNix v'
|
|
where
|
|
coerceNix :: NValue t f (Lazy t f m) -> Lazy t f m (NValue t f (Lazy t f m))
|
|
coerceNix = toValue <=< coerceToString CopyToStore CoerceAny
|
|
|
|
coerceNixList :: NValue t f (Lazy t f m) -> Lazy t f m (NValue t f (Lazy t f m))
|
|
coerceNixList v = do
|
|
xs :: [NValue t f (Lazy t f m)] <- fromValue @[NValue t f (Lazy t f m)] v
|
|
ys :: [NValue t f (Lazy t f m)] <- traverse (\x -> demand x coerceNix) xs
|
|
v' :: NValue t f (Lazy t f m) <- toValue @[NValue t f (Lazy t f m)] ys
|
|
return v'
|
|
|
|
traceEffect = putStrLn
|
|
|
|
getRecursiveSize :: (MonadIntrospect m, Applicative f) => a -> m (NValue t f m)
|
|
getRecursiveSize = fmap (nvConstant . NInt . fromIntegral) . recursiveSize
|
|
|
|
runLazyM :: Options -> MonadIO m => Lazy t f m a -> m a
|
|
runLazyM opts =
|
|
(`evalStateT` M.empty) . (`runReaderT` newContext opts) . runLazy
|
|
|
|
-- | 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
|
|
|
|
expandHomePath :: MonadFile m => FilePath -> m FilePath
|
|
expandHomePath ('~' : xs) = flip (++) xs <$> getHomeDirectory
|
|
expandHomePath p = return p
|
|
|
|
-- Given a path, determine the nix file to load
|
|
pathToDefaultNixFile :: MonadFile m => FilePath -> m FilePath
|
|
pathToDefaultNixFile p = do
|
|
isDir <- doesDirectoryExist p
|
|
pure $ if isDir then p </> "default.nix" else p
|
|
|
|
infixr 9 <///>
|
|
(<///>) :: FilePath -> FilePath -> FilePath
|
|
x <///> y | isAbsolute y || "." `isPrefixOf` y = x </> y
|
|
| otherwise = joinByLargestOverlap x y
|
|
where
|
|
joinByLargestOverlap (splitDirectories -> xs) (splitDirectories -> ys) =
|
|
joinPath $ head
|
|
[ xs ++ drop (length tx) ys | tx <- tails xs, tx `elem` inits ys ]
|
|
|
|
findPathBy
|
|
:: forall e t f m
|
|
. MonadNix e t f m
|
|
=> (FilePath -> m (Maybe FilePath))
|
|
-> [NValue t f m]
|
|
-> FilePath
|
|
-> m FilePath
|
|
findPathBy finder l name = do
|
|
mpath <- foldM go Nothing l
|
|
case mpath of
|
|
Nothing ->
|
|
throwError
|
|
$ ErrorCall
|
|
$ "file '"
|
|
++ name
|
|
++ "' was not found in the Nix search path"
|
|
++ " (add it using $NIX_PATH or -I)"
|
|
Just path -> return path
|
|
where
|
|
go :: Maybe FilePath -> NValue t f m -> m (Maybe FilePath)
|
|
go p@(Just _) _ = pure p
|
|
go Nothing l = demand l $ fromValue >=> \(s :: HashMap Text (NValue t f m)) -> do
|
|
p <- resolvePath s
|
|
demand p $ fromValue >=> \(Path path) -> case M.lookup "prefix" s of
|
|
Nothing -> tryPath path Nothing
|
|
Just pf -> demand pf $ fromValueMay >=> \case
|
|
Just (nsPfx :: NixString) ->
|
|
let pfx = hackyStringIgnoreContext nsPfx
|
|
in if not (Text.null pfx)
|
|
then tryPath path (Just (Text.unpack pfx))
|
|
else tryPath path Nothing
|
|
_ -> tryPath path Nothing
|
|
|
|
tryPath p (Just n) | n' : ns <- splitDirectories name, n == n' =
|
|
finder $ p <///> joinPath ns
|
|
tryPath p _ = finder $ p <///> name
|
|
|
|
resolvePath s = case M.lookup "path" s of
|
|
Just t -> return t
|
|
Nothing -> case M.lookup "uri" s of
|
|
Just ut -> defer $ fetchTarball (demand ut pure)
|
|
Nothing ->
|
|
throwError
|
|
$ ErrorCall
|
|
$ "__nixPath must be a list of attr sets"
|
|
++ " with 'path' elements, but saw: "
|
|
++ show s
|
|
|
|
findPathM :: forall e t f m . MonadNix e t f m
|
|
=> [NValue t f m] -> FilePath -> m FilePath
|
|
findPathM l name = findPathBy path l name
|
|
where
|
|
path :: MonadEffects t f m => FilePath -> m (Maybe FilePath)
|
|
path path = do
|
|
path <- makeAbsolutePath @t @f path
|
|
exists <- doesPathExist path
|
|
return $ if exists then Just path else Nothing
|
|
|
|
findEnvPathM :: forall e t f m . MonadNix e t f m => FilePath -> m FilePath
|
|
findEnvPathM name = do
|
|
mres <- lookupVar "__nixPath"
|
|
case mres of
|
|
Nothing -> error "impossible"
|
|
Just x -> demand x $ fromValue >=> \(l :: [NValue t f m]) ->
|
|
findPathBy nixFilePath l name
|
|
where
|
|
nixFilePath :: MonadEffects t f m => FilePath -> m (Maybe FilePath)
|
|
nixFilePath path = do
|
|
path <- makeAbsolutePath @t @f path
|
|
exists <- doesDirectoryExist path
|
|
path' <- if exists
|
|
then makeAbsolutePath @t @f $ path </> "default.nix"
|
|
else return path
|
|
exists <- doesFileExist path'
|
|
return $ if exists then Just path' else Nothing
|
|
|
|
addTracing
|
|
:: (MonadNix e t f m, Has e Options, MonadReader Int n, Alternative n)
|
|
=> Alg NExprLocF (m a)
|
|
-> Alg NExprLocF (n (m a))
|
|
addTracing k v = do
|
|
depth <- ask
|
|
guard (depth < 2000)
|
|
local succ $ do
|
|
v'@(Compose (Ann span x)) <- sequence v
|
|
return $ do
|
|
opts :: Options <- asks (view hasLens)
|
|
let rendered = if verbose opts >= Chatty
|
|
#ifdef MIN_VERSION_pretty_show
|
|
then pretty $ PS.ppShow (void x)
|
|
#else
|
|
then pretty $ show (void x)
|
|
#endif
|
|
else prettyNix (Fix (Fix (NSym "?") <$ x))
|
|
msg x = pretty ("eval: " ++ replicate depth ' ') <> x
|
|
loc <- renderLocation span (msg rendered <> " ...\n")
|
|
putStr $ show loc
|
|
res <- k v'
|
|
print $ msg rendered <> " ...done"
|
|
return res
|
|
|
|
evalExprLoc :: forall e t f m . MonadNix e t f m => NExprLoc -> m (NValue t f m)
|
|
evalExprLoc expr = do
|
|
opts :: Options <- asks (view hasLens)
|
|
if tracing opts
|
|
then join . (`runReaderT` (0 :: Int)) $ adi
|
|
(addTracing phi)
|
|
(raise (addStackFrames @(NValue t f m) . addSourcePositions))
|
|
expr
|
|
else adi phi (addStackFrames @(NValue t f m) . addSourcePositions) expr
|
|
where
|
|
phi = Eval.eval . annotated . getCompose
|
|
raise k f x = ReaderT $ \e -> k (\t -> runReaderT (f t) e) x
|
|
|
|
fetchTarball
|
|
:: forall e t f m . MonadNix e t f m => m (NValue t f m) -> m (NValue t f m)
|
|
fetchTarball v = v >>= \case
|
|
NVSet s _ -> case M.lookup "url" s of
|
|
Nothing ->
|
|
throwError $ ErrorCall "builtins.fetchTarball: Missing url attribute"
|
|
Just url -> demand url $ go (M.lookup "sha256" s)
|
|
v@NVStr{} -> go Nothing v
|
|
v ->
|
|
throwError
|
|
$ ErrorCall
|
|
$ "builtins.fetchTarball: Expected URI or set, got "
|
|
++ show v
|
|
where
|
|
go :: Maybe (NValue t f m) -> NValue t f m -> m (NValue t f m)
|
|
go msha = \case
|
|
NVStr ns -> fetch (hackyStringIgnoreContext ns) msha
|
|
v ->
|
|
throwError
|
|
$ ErrorCall
|
|
$ "builtins.fetchTarball: Expected URI or string, got "
|
|
++ show v
|
|
|
|
{- jww (2018-04-11): This should be written using pipes in another module
|
|
fetch :: Text -> Maybe (NThunk m) -> m (NValue t f m)
|
|
fetch uri msha = case takeExtension (Text.unpack uri) of
|
|
".tgz" -> undefined
|
|
".gz" -> undefined
|
|
".bz2" -> undefined
|
|
".xz" -> undefined
|
|
".tar" -> undefined
|
|
ext -> throwError $ ErrorCall $ "builtins.fetchTarball: Unsupported extension '"
|
|
++ ext ++ "'"
|
|
-}
|
|
|
|
fetch :: Text -> Maybe (NValue t f m) -> m (NValue t f m)
|
|
fetch uri Nothing =
|
|
nixInstantiateExpr $ "builtins.fetchTarball \"" ++ Text.unpack uri ++ "\""
|
|
fetch url (Just t) = demand t $ fromValue >=> \nsSha ->
|
|
let sha = hackyStringIgnoreContext nsSha
|
|
in nixInstantiateExpr
|
|
$ "builtins.fetchTarball { "
|
|
++ "url = \""
|
|
++ Text.unpack url
|
|
++ "\"; "
|
|
++ "sha256 = \""
|
|
++ Text.unpack sha
|
|
++ "\"; }"
|
|
|
|
exec :: (MonadNix e t f m, MonadInstantiate m) => [String] -> m (NValue t f m)
|
|
exec args = either throwError evalExprLoc =<< exec' args
|
|
|
|
nixInstantiateExpr
|
|
:: (MonadNix e t f m, MonadInstantiate m) => String -> m (NValue t f m)
|
|
nixInstantiateExpr s = either throwError evalExprLoc =<< instantiateExpr s
|
|
|
|
instance Monad m => Scoped (NValue t f (Lazy t f m)) (Lazy t f m) where
|
|
currentScopes = currentScopesReader
|
|
clearScopes = clearScopesReader @(Lazy t f m) @(NValue t f (Lazy t f m))
|
|
pushScopes = pushScopesReader
|
|
lookupVar = lookupVarReader
|