forked from commercialhaskell/stack
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathNix.hs
More file actions
162 lines (150 loc) · 6.19 KB
/
Nix.hs
File metadata and controls
162 lines (150 loc) · 6.19 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE OverloadedRecordDot #-}
{-# LANGUAGE OverloadedStrings #-}
-- | Run commands in a nix-shell
module Stack.Nix
( nixCmdName
, nixHelpOptName
, runShellAndExit
) where
import qualified Data.Text as T
import Path.IO ( resolveFile )
import RIO.Process ( exec, processContextL )
import Stack.Config ( getInContainer, withBuildConfig )
import Stack.Config.Nix ( nixCompiler, nixCompilerVersion )
import Stack.Constants
( inContainerEnvVar, inNixShellEnvVar
, platformVariantEnvVar
)
import Stack.Prelude
import Stack.Types.BuildConfig ( wantedCompilerVersionL )
import Stack.Types.Config
( Config (..), HasConfig (..), configProjectRoot )
import Stack.Types.Docker ( reExecArgName )
import Stack.Types.Nix ( NixOpts (..) )
import Stack.Types.Version ( showStackVersion )
import System.Environment ( getArgs, getExecutablePath, lookupEnv )
import qualified System.FilePath as F
-- | Type representing exceptions thrown by functions exported by the
-- "Stack.Nix" module.
data NixException
= CannotDetermineProjectRoot
-- ^ Can't determine the project root (location of the shell file if any).
deriving (Show, Typeable)
instance Exception NixException where
displayException CannotDetermineProjectRoot =
"Error: [S-7384]\n"
++ "Cannot determine project root directory."
runShellAndExit :: RIO Config void
runShellAndExit = do
inContainer <- getInContainer -- TODO we can probably assert that this is False based on Stack.Runners now
origArgs <- liftIO getArgs
let args | inContainer = origArgs -- internal-re-exec version already passed
-- first stack when restarting in the container
| otherwise =
("--" ++ reExecArgName ++ "=" ++ showStackVersion) : origArgs
exePath <- liftIO getExecutablePath
config <- view configL
envOverride <- view processContextL
local (set processContextL envOverride) $ do
let cmnd = escape exePath
args' = map escape args
mshellFile <- case configProjectRoot config of
Just projectRoot ->
traverse (resolveFile projectRoot) config.nix.initFile
Nothing -> pure Nothing
-- This will never result in double loading the build config, since:
--
-- 1. This function explicitly takes a Config, not a HasConfig
--
-- 2. This function ends up exiting before running other code
-- (thus the void return type)
compilerVersion <- withBuildConfig $ view wantedCompilerVersionL
ghc <- either throwIO pure $ nixCompiler compilerVersion
ghcVersion <- either throwIO pure $ nixCompilerVersion compilerVersion
let pkgsInConfig = config.nix.packages
pkgs = pkgsInConfig ++ [ghc, "git", "gcc", "gmp"]
pkgsStr = "[" <> T.intercalate " " pkgs <> "]"
pureShell = config.nix.pureShell
addGCRoots = config.nix.addGCRoots
nixopts = case mshellFile of
Just fp ->
[ toFilePath fp
, "--arg"
, "ghc"
, "with (import <nixpkgs> {}); " ++ T.unpack ghc
, "--argstr", "ghcVersion", T.unpack ghcVersion
]
Nothing ->
[ "-E"
, T.unpack $ T.concat
[ "with (import <nixpkgs> {}); "
, "let inputs = ",pkgsStr,"; "
, "libPath = lib.makeLibraryPath inputs; "
, "stackExtraArgs = lib.concatMap (pkg: "
, "[ ''--extra-lib-dirs=${lib.getLib pkg}/lib'' "
, " ''--extra-include-dirs=${lib.getDev pkg}/include'' ]"
, ") inputs; in "
, "runCommand ''myEnv'' { "
, "buildInputs = lib.optional stdenv.isLinux glibcLocales ++ inputs; "
-- glibcLocales is necessary on Linux to avoid warnings about
-- GHC being incapable to set the locale.
, T.pack platformVariantEnvVar <> "=''nix''; "
, T.pack inNixShellEnvVar <> "=1; "
, if inContainer
-- If shell is pure, this env var would not
-- be seen by stack inside nix
then T.pack inContainerEnvVar <> "=1; "
else ""
, "LD_LIBRARY_PATH = libPath;"
-- LD_LIBRARY_PATH is set because for now it's needed by
-- builds using Template Haskell
, "STACK_IN_NIX_EXTRA_ARGS = stackExtraArgs; "
-- overriding default locale so Unicode output using base
-- won't be broken
, "LANG=\"en_US.UTF-8\";"
, "} \"\""
]
]
fullArgs = concat
[ [ "--pure" | pureShell ]
, if addGCRoots
then [ "--indirect"
, "--add-root"
, toFilePath
config.workDir
F.</> "nix-gc-symlinks"
F.</> "gc-root"
]
else []
, map T.unpack config.nix.shellOptions
, nixopts
, ["--run", unwords (cmnd:"$STACK_IN_NIX_EXTRA_ARGS":args')]
-- Using --run instead of --command so we cannot end up in the
-- nix-shell if stack build is Ctrl-C'd
]
pathVar <- liftIO $ lookupEnv "PATH"
logDebug $ "PATH is: " <> displayShow pathVar
logDebug $
"Using a nix-shell environment "
<> ( case mshellFile of
Just path ->
"from file: "
<> fromString (toFilePath path)
Nothing ->
"with nix packages: "
<> display (T.intercalate ", " pkgs)
)
exec "nix-shell" fullArgs
-- | Shell-escape quotes inside the string and enclose it in quotes.
escape :: String -> String
escape str =
"'"
++ foldr (\c -> if c == '\'' then ("'\"'\"'"++) else (c:)) "" str
++ "'"
-- | Command-line argument for "nix"
nixCmdName :: String
nixCmdName = "nix"
nixHelpOptName :: String
nixHelpOptName = nixCmdName ++ "-help"