Skip to content

Commit ff4f7db

Browse files
authored
Merge pull request #6945 from commercialhaskell/fix6861
Fix #6861 Use nix-instantiate command, with failure exception
2 parents 660875f + b2e8ab0 commit ff4f7db

7 files changed

Lines changed: 147 additions & 18 deletions

File tree

ChangeLog.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ Behavior changes:
2424
* When Stack's Nix integration is enabled and a `shell-file` is not specified,
2525
Stack sets `LANG=C.UTF-8` (rather than `en_US.UTF-8`, which may not be
2626
available if Stack has been built from source).
27+
* With Nix integration, Stack uses `nix-instantiate` before `nix-shell`,
28+
enabling Stack to catch and report when the Nix expression cannot be
29+
evaluated by Nix. The `nix-instantiate-options` option is added, accordingly.
2730

2831
Other enhancements:
2932

doc/maintainers/stack_errors.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
In connection with considering Stack's support of the
66
[Haskell Error Index](https://errors.haskell.org/) initiative, this page seeks
77
to take stock of the errors that Stack itself can raise, by reference to the
8-
`master` branch of the Stack repository. Last updated: 2026-04-24.
8+
`master` branch of the Stack repository. Last updated: 2026-07-07.
99

1010
* `Stack.main`: catches exceptions from action `commandLineHandler`.
1111

@@ -214,6 +214,11 @@ to take stock of the errors that Stack itself can raise, by reference to the
214214

215215
~~~haskell
216216
[S-7384] = CannotDetermineProjectRoot
217+
[S-1264] | NixInstantiateCommandFailure [String] ByteString
218+
[S-3220] | NixInstantiateCommandNoDerivationPath
219+
[S-5924] | NixInstantiateCommandNoSingleDerivationPath [String] [S8.ByteString]
220+
[S-1537] | NixInstantiateCommandInvalidDerivationPath [String] String
221+
217222
~~~
218223

219224
- `Stack.PackageDump.PackageDumpException`

doc/topics/nix_integration.md

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,15 @@ C libraries that you would normally install manually.
3232

3333
Nix's `nix-shell` starts an interactive shell based on a Nix expression. Stack
3434
can automatically create a Nix build environment in the background using
35-
`nix-shell`. There are two alternative options to create such a build
36-
environment:
35+
`nix-instantiate` to produce a store derivation and `nix-shell` with that store
36+
derivation. This two-step process (since Stack UNRELEASED) allows Stack to
37+
report separately any errors in producing the store derivation. There are two
38+
alternative options to create such a build environment:
3739

3840
1. provide a list of [Nix packages][nix-search-packages]. To these, Stack will
3941
add Nix packages for the GHC compiler, `git` (the distributed version control
4042
system), `gcc` (the GNU compiler collection), `gmp` (the GNU multiple
41-
precision arithmetic library) and `cacert` (a bundle of X.509 certificates of
43+
precision arithmetic library) and `cacert` (a bundle of X.509 certificates of
4244
public Certificate Authorities); and
4345
2. provide a `shell.nix` file that gives you more control over the libraries and
4446
tools available inside the shell.
@@ -430,6 +432,12 @@ nix:
430432
# is already present and not empty.
431433
shell-file: shell.nix
432434

435+
# A list of strings, empty by default. Additional options that will be passed
436+
# verbatim to the `nix-instantiate` command.
437+
#
438+
# Since Stack UNRELEASED
439+
nix-instantiate-options: []
440+
433441
# A list of strings, empty by default. Additional options that will be passed
434442
# verbatim to the `nix-shell` command.
435443
nix-shell-options: []

src/Stack/Config/Nix.hs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -83,10 +83,11 @@ nixOptsFromMonoid nixMonoid os = do
8383
pureShell = fromFirst defaultPure nixMonoid.pureShell
8484
packages = fromFirst [] nixMonoid.packages
8585
initFile = getFirst nixMonoid.initFile
86-
shellOptions =
87-
fromFirst [] nixMonoid.shellOptions
88-
++ prefixAll (T.pack "-I") (fromFirst [] nixMonoid.path)
89-
addGCRoots = fromFirstFalse nixMonoid.addGCRoots
86+
includePaths = prefixAll (T.pack "-I") (fromFirst [] nixMonoid.path)
87+
instantiateOptions =
88+
fromFirst [] nixMonoid.instantiateOptions ++ includePaths
89+
shellOptions = fromFirst [] nixMonoid.shellOptions ++ includePaths
90+
addGCRoots = fromFirstFalse nixMonoid.addGCRoots
9091

9192
-- Enable Nix-mode by default on NixOS, unless Docker-mode was specified
9293
osIsNixOS <- isNixOS
@@ -108,6 +109,7 @@ nixOptsFromMonoid nixMonoid os = do
108109
, pureShell
109110
, packages
110111
, initFile
112+
, instantiateOptions
111113
, shellOptions
112114
, addGCRoots
113115
}

src/Stack/Nix.hs

Lines changed: 104 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,13 @@ module Stack.Nix
1717
) where
1818

1919
import qualified Data.Text as T
20+
import qualified Data.ByteString.Lazy.Char8 as S8
21+
import Path ( parseAbsFile )
2022
import Path.IO ( resolveFile )
21-
import RIO.Process ( exec, processContextL )
23+
import RIO.Process
24+
( HasProcessContext (..), exec, proc, processContextL
25+
, readProcess
26+
)
2227
import Stack.Config ( getInContainer, withBuildConfig )
2328
import Stack.Config.Nix ( nixCompiler, nixCompilerVersion )
2429
import Stack.Constants
@@ -41,13 +46,58 @@ import qualified System.FilePath as F
4146
data NixPrettyException
4247
= CannotDetermineProjectRoot
4348
-- ^ Can't determine the project root (location of the shell file if any).
49+
| NixInstantiateCommandFailure ![String] !S8.ByteString
50+
| NixInstantiateCommandNoDerivationPath ![String]
51+
| NixInstantiateCommandNoSingleDerivationPath ![String] ![S8.ByteString]
52+
| NixInstantiateCommandInvalidDerivationPath ![String] !String
4453
deriving Show
4554

4655
instance Pretty NixPrettyException where
4756
pretty CannotDetermineProjectRoot =
4857
"[S-7384]"
4958
<> line
5059
<> flow "Cannot determine project root directory."
60+
pretty (NixInstantiateCommandFailure args e) =
61+
"[S-1264]"
62+
<> line
63+
<> whileUsingNixInstantiate args
64+
<> flow "Stack encountered the following error:"
65+
<> blankLine
66+
<> string (T.unpack . textDisplay . displayBytesUtf8 $ S8.toStrict e)
67+
pretty (NixInstantiateCommandNoDerivationPath args) =
68+
"[S-3220]"
69+
<> line
70+
<> whileUsingNixInstantiate args
71+
<> flow "the command succeeded but did not output a path to a store \
72+
\derivation."
73+
pretty (NixInstantiateCommandNoSingleDerivationPath args drvPaths) =
74+
"[S-5924]"
75+
<> line
76+
<> whileUsingNixInstantiate args
77+
<> flow "the command succeeded but expected a single path to a store \
78+
\derivation and output was:"
79+
<> line
80+
<> bulletedList (map (style File . string . S8.unpack) drvPaths)
81+
<> line
82+
pretty (NixInstantiateCommandInvalidDerivationPath args drvPath) =
83+
"[S-1537]"
84+
<> line
85+
<> whileUsingNixInstantiate args
86+
<> flow "the command succeeded but expected a valid path to a store \
87+
\derivation and output was:"
88+
<> line
89+
<> style Error (string drvPath)
90+
91+
whileUsingNixInstantiate :: [String] -> StyleDoc
92+
whileUsingNixInstantiate args =
93+
fillSep
94+
[ flow "While using"
95+
, style Shell "nix-instantiate" <> ","
96+
, flow "with arguments:"
97+
]
98+
<> line
99+
<> bulletedList (map (style Shell . string) args)
100+
<> blankLine
51101

52102
instance Exception NixPrettyException
53103

@@ -129,24 +179,31 @@ runShellAndExit = do
129179
, "} \"\""
130180
]
131181
]
132-
133-
fullArgs = concat
134-
[ [ "--pure" | pureShell ]
135-
, if addGCRoots
182+
instantiateArgs = concat
183+
[ if addGCRoots
136184
then [ "--indirect"
185+
-- From Nix 2.4, the --indirect flag is a no op and
186+
-- --add-root always generates indirect GC roots.
137187
, "--add-root"
138188
, toFilePath
139189
config.workDir
140190
F.</> "nix-gc-symlinks"
141191
F.</> "gc-root"
142192
]
143193
else []
144-
, map T.unpack config.nix.shellOptions
145194
, nixopts
146-
, ["--run", unwords (cmnd:"$STACK_IN_NIX_EXTRA_ARGS":args')]
147-
-- Using --run instead of --command so we cannot end up in the
148-
-- nix-shell if stack build is Ctrl-C'd
195+
, map T.unpack config.nix.instantiateOptions
196+
, [ "--show-trace" ]
149197
]
198+
shellArgs = concat
199+
[ [ "--pure" | pureShell ]
200+
, map T.unpack config.nix.shellOptions
201+
, [ "--run"
202+
-- Using --run instead of --command so we cannot end up in the
203+
-- nix-shell if stack build is Ctrl-C'd
204+
, unwords (cmnd : "$STACK_IN_NIX_EXTRA_ARGS" : args')
205+
]
206+
]
150207
pathVar <- liftIO $ lookupEnv "PATH"
151208
logDebug $ "PATH is: " <> displayShow pathVar
152209
logDebug $
@@ -159,7 +216,44 @@ runShellAndExit = do
159216
"with nix packages: "
160217
<> display (T.intercalate ", " pkgs)
161218
)
162-
exec "nix-shell" fullArgs
219+
runNixShellExec instantiateArgs shellArgs
220+
221+
runNixShellExec
222+
:: (HasProcessContext env, HasLogFunc env)
223+
=> [String]
224+
-- ^ nix-instantiate args
225+
-> [String]
226+
-- ^ nix-shell args
227+
-> RIO env a
228+
runNixShellExec instantiateArgs shellArgs = do
229+
-- The nix-instantiate command instantiates (produces) a store derivation
230+
-- from a Nix expression and prints the path of the store derivation on
231+
-- standard output:
232+
(ec, out, err) <- proc "nix-instantiate" instantiateArgs readProcess
233+
case ec of
234+
ExitFailure _ ->
235+
-- As of Nix 2.34, there appears to be no way to capture coloured output
236+
prettyThrowIO (NixInstantiateCommandFailure instantiateArgs err)
237+
ExitSuccess -> do
238+
drvPath <- case S8.lines out of
239+
[] -> prettyThrowIO $
240+
NixInstantiateCommandNoDerivationPath instantiateArgs
241+
-- nix-instantiate prints the .drv path
242+
[drvPath'] ->
243+
let drvPath'' = S8.unpack drvPath'
244+
in if isJust $ parseAbsFile drvPath''
245+
then
246+
pure drvPath''
247+
else
248+
prettyThrowIO $
249+
NixInstantiateCommandInvalidDerivationPath
250+
instantiateArgs
251+
drvPath''
252+
drvPaths -> prettyThrowIO $
253+
NixInstantiateCommandNoSingleDerivationPath
254+
instantiateArgs
255+
drvPaths
256+
exec "nix-shell" (drvPath : shellArgs)
163257

164258
-- | Shell-escape quotes inside the string and enclose it in quotes.
165259
escape :: String -> String

src/Stack/Options/NixParser.hs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,12 @@ nixOptsParser hide0 = overrideActivation <$>
5353
\users)."
5454
<> hide
5555
))
56+
<*> optionalFirst (textArgsOption
57+
( long "nix-instantiate-options"
58+
<> metavar "OPTIONS"
59+
<> help "Additional options passed to nix-instantiate."
60+
<> hide
61+
))
5662
<*> optionalFirst (textArgsOption
5763
( long "nix-shell-options"
5864
<> metavar "OPTIONS"

src/Stack/Types/Nix.hs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ module Stack.Types.Nix
2020
, nixPackagesArgName
2121
, nixPathArgName
2222
, nixPureShellArgName
23+
, nixInstantiateOptsArgName
2324
, nixShellOptsArgName
2425
) where
2526

@@ -39,6 +40,8 @@ data NixOpts = NixOpts
3940
, initFile :: !(Maybe FilePath)
4041
-- ^ The path of a file containing preconfiguration of the environment
4142
-- (e.g shell.nix)
43+
, instantiateOptions :: ![Text]
44+
-- ^ Options to be given to the nix-instantiate command line
4245
, shellOptions :: ![Text]
4346
-- ^ Options to be given to the nix-shell command line
4447
, addGCRoots :: !Bool
@@ -59,6 +62,8 @@ data NixOptsMonoid = NixOptsMonoid
5962
, initFile :: !(First FilePath)
6063
-- ^ The path of a file containing preconfiguration of the environment (e.g
6164
-- shell.nix)
65+
, instantiateOptions :: !(First [Text])
66+
-- ^ Options to be given to the nix-instantiate command line
6267
, shellOptions :: !(First [Text])
6368
-- ^ Options to be given to the nix-shell command line
6469
, path :: !(First [Text])
@@ -76,6 +81,7 @@ instance FromJSON (WithJSONWarnings NixOptsMonoid) where
7681
pureShell <- First <$> o ..:? nixPureShellArgName
7782
packages <- First <$> o ..:? nixPackagesArgName
7883
initFile <- First <$> o ..:? nixInitFileArgName
84+
instantiateOptions <- First <$> o ..:? nixInstantiateOptsArgName
7985
shellOptions <- First <$> o ..:? nixShellOptsArgName
8086
path <- First <$> o ..:? nixPathArgName
8187
addGCRoots <- FirstFalse <$> o ..:? nixAddGCRootsArgName
@@ -84,6 +90,7 @@ instance FromJSON (WithJSONWarnings NixOptsMonoid) where
8490
, pureShell
8591
, packages
8692
, initFile
93+
, instantiateOptions
8794
, shellOptions
8895
, path
8996
, addGCRoots
@@ -114,6 +121,10 @@ nixPackagesArgName = "packages"
114121
nixInitFileArgName :: Text
115122
nixInitFileArgName = "shell-file"
116123

124+
-- | Extra options for the nix-instantiate command argument name.
125+
nixInstantiateOptsArgName :: Text
126+
nixInstantiateOptsArgName = "nix-instantiate-options"
127+
117128
-- | Extra options for the nix-shell command argument name.
118129
nixShellOptsArgName :: Text
119130
nixShellOptsArgName = "nix-shell-options"

0 commit comments

Comments
 (0)