{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE RecordWildCards, DeriveDataTypeable, OverloadedStrings #-}

-- | Nix configuration
module Stack.Config.Nix
       (nixOptsFromMonoid
       ,nixCompiler
       ,StackNixException(..)
       ) where

import Stack.Prelude
import Control.Monad.Extra (ifM)
import qualified Data.Text as T
import qualified Data.Text.IO as TIO
import Distribution.System (OS (..))
import Stack.Constants
import Stack.Types.Config
import Stack.Types.Nix
import System.Directory (doesFileExist)

-- | Interprets NixOptsMonoid options.
nixOptsFromMonoid
    :: HasRunner env
    => NixOptsMonoid
    -> OS
    -> RIO env NixOpts
nixOptsFromMonoid :: NixOptsMonoid -> OS -> RIO env NixOpts
nixOptsFromMonoid NixOptsMonoid{First Bool
First FilePath
First [Text]
FirstFalse
nixMonoidAddGCRoots :: NixOptsMonoid -> FirstFalse
nixMonoidPath :: NixOptsMonoid -> First [Text]
nixMonoidShellOptions :: NixOptsMonoid -> First [Text]
nixMonoidInitFile :: NixOptsMonoid -> First FilePath
nixMonoidPackages :: NixOptsMonoid -> First [Text]
nixMonoidPureShell :: NixOptsMonoid -> First Bool
nixMonoidEnable :: NixOptsMonoid -> First Bool
nixMonoidAddGCRoots :: FirstFalse
nixMonoidPath :: First [Text]
nixMonoidShellOptions :: First [Text]
nixMonoidInitFile :: First FilePath
nixMonoidPackages :: First [Text]
nixMonoidPureShell :: First Bool
nixMonoidEnable :: First Bool
..} OS
os = do
    let defaultPure :: Bool
defaultPure = case OS
os of
          OS
OSX -> Bool
False
          OS
_ -> Bool
True
        nixPureShell :: Bool
nixPureShell = Bool -> First Bool -> Bool
forall a. a -> First a -> a
fromFirst Bool
defaultPure First Bool
nixMonoidPureShell
        nixPackages :: [Text]
nixPackages = [Text] -> First [Text] -> [Text]
forall a. a -> First a -> a
fromFirst [] First [Text]
nixMonoidPackages
        nixInitFile :: Maybe FilePath
nixInitFile = First FilePath -> Maybe FilePath
forall a. First a -> Maybe a
getFirst First FilePath
nixMonoidInitFile
        nixShellOptions :: [Text]
nixShellOptions = [Text] -> First [Text] -> [Text]
forall a. a -> First a -> a
fromFirst [] First [Text]
nixMonoidShellOptions
                          [Text] -> [Text] -> [Text]
forall a. [a] -> [a] -> [a]
++ Text -> [Text] -> [Text]
forall t. t -> [t] -> [t]
prefixAll (FilePath -> Text
T.pack FilePath
"-I") ([Text] -> First [Text] -> [Text]
forall a. a -> First a -> a
fromFirst [] First [Text]
nixMonoidPath)
        nixAddGCRoots :: Bool
nixAddGCRoots   = FirstFalse -> Bool
fromFirstFalse FirstFalse
nixMonoidAddGCRoots

    -- Enable Nix-mode by default on NixOS, unless Docker-mode was specified
    Bool
osIsNixOS <- RIO env Bool
forall (m :: * -> *). MonadIO m => m Bool
isNixOS
    let nixEnable0 :: Bool
nixEnable0 = Bool -> First Bool -> Bool
forall a. a -> First a -> a
fromFirst Bool
osIsNixOS First Bool
nixMonoidEnable

    Bool
nixEnable <- case () of ()
_
                                | Bool
nixEnable0 Bool -> Bool -> Bool
&& Bool
osIsWindows -> do
                                      Utf8Builder -> RIO env ()
forall (m :: * -> *) env.
(MonadIO m, MonadReader env m, HasLogFunc env, HasCallStack) =>
Utf8Builder -> m ()
logInfo Utf8Builder
"Note: Disabling nix integration, since this is being run in Windows"
                                      Bool -> RIO env Bool
forall (m :: * -> *) a. Monad m => a -> m a
return Bool
False
                                | Bool
otherwise                 -> Bool -> RIO env Bool
forall (m :: * -> *) a. Monad m => a -> m a
return Bool
nixEnable0

    Bool -> RIO env () -> RIO env ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Bool -> Bool
not ([Text] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null [Text]
nixPackages) Bool -> Bool -> Bool
&& Maybe FilePath -> Bool
forall a. Maybe a -> Bool
isJust Maybe FilePath
nixInitFile) (RIO env () -> RIO env ()) -> RIO env () -> RIO env ()
forall a b. (a -> b) -> a -> b
$
       StackNixException -> RIO env ()
forall (m :: * -> *) e a. (MonadIO m, Exception e) => e -> m a
throwIO StackNixException
NixCannotUseShellFileAndPackagesException
    NixOpts -> RIO env NixOpts
forall (m :: * -> *) a. Monad m => a -> m a
return NixOpts :: Bool
-> Bool -> [Text] -> Maybe FilePath -> [Text] -> Bool -> NixOpts
NixOpts{Bool
[Text]
Maybe FilePath
nixAddGCRoots :: Bool
nixShellOptions :: [Text]
nixInitFile :: Maybe FilePath
nixPackages :: [Text]
nixPureShell :: Bool
nixEnable :: Bool
nixEnable :: Bool
nixAddGCRoots :: Bool
nixShellOptions :: [Text]
nixInitFile :: Maybe FilePath
nixPackages :: [Text]
nixPureShell :: Bool
..}
  where prefixAll :: t -> [t] -> [t]
prefixAll t
p (t
x:[t]
xs) = t
p t -> [t] -> [t]
forall t. t -> [t] -> [t]
: t
x t -> [t] -> [t]
forall t. t -> [t] -> [t]
: t -> [t] -> [t]
prefixAll t
p [t]
xs
        prefixAll t
_ [t]
_      = []

nixCompiler :: WantedCompiler -> Either StringException T.Text
nixCompiler :: WantedCompiler -> Either StringException Text
nixCompiler WantedCompiler
compilerVersion =
  case WantedCompiler
compilerVersion of
    WCGhc Version
version ->
      case (Char -> Bool) -> Text -> [Text]
T.split (Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
== Char
'.') (FilePath -> Text
forall a. IsString a => FilePath -> a
fromString (FilePath -> Text) -> FilePath -> Text
forall a b. (a -> b) -> a -> b
$ Version -> FilePath
versionString Version
version) of
        Text
x : Text
y : [Text]
minor ->
          Text -> Either StringException Text
forall a b. b -> Either a b
Right (Text -> Either StringException Text)
-> Text -> Either StringException Text
forall a b. (a -> b) -> a -> b
$
          case [Text]
minor of
            [] ->
              -- The minor version is not specified. Select the latest minor
              -- version in Nixpkgs corresponding to the requested major
              -- version.
              let major :: Text
major = [Text] -> Text
T.concat [Text
x, Text
y] in
              Text
"(let compilers = builtins.filter \
              \(name: builtins.match \
              \\"ghc" Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
major Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
"[[:digit:]]*\" name != null) \
              \(lib.attrNames haskell.compiler); in \
              \if compilers == [] \
              \then abort \"No compiler found for GHC "
              Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> FilePath -> Text
T.pack (Version -> FilePath
versionString Version
version) Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
"\"\
              \else haskell.compiler.${builtins.head compilers})"
            [Text]
_ -> Text
"haskell.compiler.ghc" Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> [Text] -> Text
T.concat (Text
x Text -> [Text] -> [Text]
forall t. t -> [t] -> [t]
: Text
y Text -> [Text] -> [Text]
forall t. t -> [t] -> [t]
: [Text]
minor)
        [Text]
_ -> StringException -> Either StringException Text
forall a b. a -> Either a b
Left (StringException -> Either StringException Text)
-> StringException -> Either StringException Text
forall a b. (a -> b) -> a -> b
$ HasCallStack => FilePath -> StringException
FilePath -> StringException
stringException FilePath
"GHC major version not specified"
    WCGhcjs{} -> StringException -> Either StringException Text
forall a b. a -> Either a b
Left (StringException -> Either StringException Text)
-> StringException -> Either StringException Text
forall a b. (a -> b) -> a -> b
$ HasCallStack => FilePath -> StringException
FilePath -> StringException
stringException FilePath
"Only GHC is supported by stack --nix"
    WCGhcGit{} -> StringException -> Either StringException Text
forall a b. a -> Either a b
Left (StringException -> Either StringException Text)
-> StringException -> Either StringException Text
forall a b. (a -> b) -> a -> b
$ HasCallStack => FilePath -> StringException
FilePath -> StringException
stringException FilePath
"Only GHC is supported by stack --nix"

-- Exceptions thown specifically by Stack.Nix
data StackNixException
  = NixCannotUseShellFileAndPackagesException
    -- ^ Nix can't be given packages and a shell file at the same time
    deriving (Typeable)

instance Exception StackNixException

instance Show StackNixException where
  show :: StackNixException -> FilePath
show StackNixException
NixCannotUseShellFileAndPackagesException =
    FilePath
"You cannot have packages and a shell-file filled at the same time in your nix-shell configuration."

isNixOS :: MonadIO m => m Bool
isNixOS :: m Bool
isNixOS = IO Bool -> m Bool
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO Bool -> m Bool) -> IO Bool -> m Bool
forall a b. (a -> b) -> a -> b
$ do
    let fp :: FilePath
fp = FilePath
"/etc/os-release"
    IO Bool -> IO Bool -> IO Bool -> IO Bool
forall (m :: * -> *) a. Monad m => m Bool -> m a -> m a -> m a
ifM (FilePath -> IO Bool
doesFileExist FilePath
fp)
        (Text -> Text -> Bool
T.isInfixOf Text
"ID=nixos" (Text -> Bool) -> IO Text -> IO Bool
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> FilePath -> IO Text
TIO.readFile FilePath
fp)
        (Bool -> IO Bool
forall (m :: * -> *) a. Monad m => a -> m a
return Bool
False)