302 lines
10 KiB
Haskell
302 lines
10 KiB
Haskell
{-# LANGUAGE ApplicativeDo #-}
|
|
{-# LANGUAGE CPP #-}
|
|
{-# LANGUAGE FlexibleContexts #-}
|
|
{-# LANGUAGE LambdaCase #-}
|
|
{-# LANGUAGE OverloadedStrings #-}
|
|
{-# LANGUAGE ScopedTypeVariables #-}
|
|
{-# LANGUAGE TupleSections #-}
|
|
{-# LANGUAGE TypeApplications #-}
|
|
{-# LANGUAGE ViewPatterns #-}
|
|
|
|
{-# OPTIONS_GHC -Wno-missing-signatures #-}
|
|
{-# OPTIONS_GHC -Wno-orphans #-}
|
|
{-# OPTIONS_GHC -fno-warn-name-shadowing #-}
|
|
|
|
module Nix.Effects.Basic where
|
|
|
|
import Control.Monad
|
|
import Control.Monad.State.Strict
|
|
import Data.HashMap.Lazy ( HashMap )
|
|
import qualified Data.HashMap.Lazy as M
|
|
import Data.List
|
|
import Data.List.Split
|
|
import Data.Maybe ( maybeToList )
|
|
import Data.Text ( Text )
|
|
import qualified Data.Text as Text
|
|
import Data.Text.Prettyprint.Doc
|
|
import Nix.Atoms
|
|
import Nix.Convert
|
|
import Nix.Effects
|
|
import Nix.Exec ( MonadNix
|
|
, callFunc
|
|
, evalExprLoc
|
|
, nixInstantiateExpr
|
|
)
|
|
import Nix.Expr
|
|
import Nix.Frames
|
|
import Nix.Normal
|
|
import Nix.Parser
|
|
import Nix.Pretty
|
|
import Nix.Render
|
|
import Nix.Scope
|
|
import Nix.String
|
|
import Nix.String.Coerce
|
|
import Nix.Utils
|
|
import Nix.Value
|
|
import Nix.Value.Monad
|
|
import System.FilePath
|
|
|
|
#ifdef MIN_VERSION_ghc_datasize
|
|
#if MIN_VERSION_ghc_datasize(0,2,0)
|
|
import GHC.DataSize
|
|
#endif
|
|
#endif
|
|
|
|
defaultMakeAbsolutePath :: MonadNix e t f m => FilePath -> m FilePath
|
|
defaultMakeAbsolutePath 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
|
|
|
|
expandHomePath :: MonadFile m => FilePath -> m FilePath
|
|
expandHomePath ('~' : xs) = flip (++) xs <$> getHomeDirectory
|
|
expandHomePath p = return p
|
|
|
|
-- | 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
|
|
|
|
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 ]
|
|
|
|
defaultFindEnvPath :: MonadNix e t f m => String -> m FilePath
|
|
defaultFindEnvPath = findEnvPathM
|
|
|
|
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
|
|
|
|
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's 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 ut
|
|
Nothing ->
|
|
throwError
|
|
$ ErrorCall
|
|
$ "__nixPath must be a list of attr sets"
|
|
++ " with 'path' elements, but received: "
|
|
++ show s
|
|
|
|
fetchTarball
|
|
:: forall e t f m . MonadNix e t f m => NValue t f m -> m (NValue t f m)
|
|
fetchTarball = flip demand $ \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
|
|
++ "\"; }"
|
|
|
|
defaultFindPath :: MonadNix e t f m => [NValue t f m] -> FilePath -> m FilePath
|
|
defaultFindPath = findPathM
|
|
|
|
findPathM
|
|
:: forall e t f m
|
|
. MonadNix e t f m
|
|
=> [NValue t f m]
|
|
-> FilePath
|
|
-> m FilePath
|
|
findPathM = findPathBy path
|
|
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
|
|
|
|
defaultImportPath
|
|
:: (MonadNix e t f m, MonadState (HashMap FilePath NExprLoc) m)
|
|
=> FilePath
|
|
-> m (NValue t f m)
|
|
defaultImportPath path = do
|
|
traceM $ "Importing file " ++ path
|
|
withFrame Info (ErrorCall $ "While importing file " ++ show path) $ do
|
|
imports <- 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
|
|
modify (M.insert path expr)
|
|
pure expr
|
|
|
|
defaultPathToDefaultNix :: MonadNix e t f m => FilePath -> m FilePath
|
|
defaultPathToDefaultNix = pathToDefaultNixFile
|
|
|
|
-- 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
|
|
|
|
defaultDerivationStrict
|
|
:: forall e t f m . MonadNix e t f m => NValue t f m -> m (NValue t f m)
|
|
defaultDerivationStrict = fromValue @(AttrSet (NValue t f m)) >=> \s -> do
|
|
nn <- maybe (pure False) (demand ?? fromValue) (M.lookup "__ignoreNulls" s)
|
|
s' <- M.fromList <$> mapMaybeM (handleEntry nn) (M.toList s)
|
|
v' <- normalForm =<< toValue @(AttrSet (NValue t f m)) @_ @(NValue t f m) s'
|
|
nixInstantiateExpr $ "derivationStrict " ++ show (prettyNValue v')
|
|
where
|
|
mapMaybeM :: (a -> m (Maybe b)) -> [a] -> m [b]
|
|
mapMaybeM op = foldr f (return [])
|
|
where f x xs = op x >>= (<$> xs) . (++) . maybeToList
|
|
|
|
handleEntry :: Bool -> (Text, NValue t f m) -> m (Maybe (Text, NValue 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" -> demand v $ fmap Just . coerceNixList
|
|
"__ignoreNulls" -> pure Nothing
|
|
_ -> demand v $ \case
|
|
NVConstant NNull | ignoreNulls -> pure Nothing
|
|
v' -> Just <$> coerceNix v'
|
|
where
|
|
coerceNix :: NValue t f m -> m (NValue t f m)
|
|
coerceNix = toValue <=< coerceToString callFunc CopyToStore CoerceAny
|
|
|
|
coerceNixList :: NValue t f m -> m (NValue t f m)
|
|
coerceNixList v = do
|
|
xs <- fromValue @[NValue t f m] v
|
|
ys <- traverse (`demand` coerceNix) xs
|
|
toValue @[NValue t f m] ys
|
|
|
|
defaultTraceEffect :: MonadPutStr m => String -> m ()
|
|
defaultTraceEffect = Nix.Effects.putStrLn
|