forked from commercialhaskell/stack
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathGhci.hs
More file actions
284 lines (275 loc) · 10.9 KB
/
Ghci.hs
File metadata and controls
284 lines (275 loc) · 10.9 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
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE RecordWildCards #-}
-- | Run a GHCi configured with the user's project(s).
module Stack.Ghci (GhciOpts(..),GhciPkgInfo(..), ghciSetup, ghci) where
import Control.Monad.Catch
import Control.Monad.IO.Class
import Control.Monad.Logger
import Control.Monad.Reader
import Control.Monad.Trans.Resource
import Data.Function
import Data.List
import Data.Map.Strict (Map)
import qualified Data.Map.Strict as M
import Data.Maybe
import Data.Monoid
import Data.Set (Set)
import qualified Data.Set as S
import Data.Text (Text)
import qualified Data.Text as T
import Distribution.ModuleName (ModuleName)
import Distribution.Text (display)
import Network.HTTP.Client.Conduit
import Path
import Prelude
import Stack.Build
import Stack.Build.Source
import Stack.Build.Target
import Stack.Constants
import Stack.Exec
import Stack.Package
import Stack.Types
import Stack.Types.Internal
-- | Command-line options for GHC.
data GhciOpts = GhciOpts
{ghciTargets :: ![Text]
,ghciArgs :: ![String]
,ghciGhcCommand :: !(Maybe FilePath)
,ghciNoLoadModules :: !Bool
,ghciAdditionalPackages :: ![String]
,ghciMainIs :: !(Maybe Text)
} deriving (Show,Eq)
-- | Necessary information to load a package or its components.
data GhciPkgInfo = GhciPkgInfo
{ ghciPkgName :: PackageName
, ghciPkgOpts :: [String]
, ghciPkgDir :: Path Abs Dir
, ghciPkgModules :: Set ModuleName
, ghciPkgModFiles :: Set (Path Abs File) -- ^ Module file paths.
, ghciPkgCFiles :: Set (Path Abs File) -- ^ C files.
, ghciPkgMainIs :: Map NamedComponent (Set (Path Abs File))
}
-- | Launch a GHCi session for the given local project targets with the
-- given options and configure it with the load paths and extensions
-- of those targets.
ghci
:: (HasConfig r, HasBuildConfig r, HasHttpManager r, MonadMask m, HasLogLevel r, HasTerminal r, HasEnvConfig r, MonadReader r m, MonadIO m, MonadThrow m, MonadLogger m, MonadCatch m, MonadBaseControl IO m)
=> GhciOpts -> m ()
ghci GhciOpts{..} = do
(targets,mainIsTargets,pkgs) <- ghciSetup ghciMainIs ghciTargets
bconfig <- asks getBuildConfig
mainFile <- figureOutMainFile mainIsTargets targets pkgs
wc <- getWhichCompiler
let pkgopts = concatMap ghciPkgOpts pkgs
srcfiles
| ghciNoLoadModules = []
| otherwise =
nub (maybe [] (return . toFilePath) mainFile <>
concatMap (map display . S.toList . ghciPkgModules) pkgs)
odir =
[ "-odir=" <> toFilePath (objectInterfaceDir bconfig)
, "-hidir=" <> toFilePath (objectInterfaceDir bconfig)]
$logInfo
("Configuring GHCi with the following packages: " <>
T.intercalate ", " (map (packageNameText . ghciPkgName) pkgs))
exec
defaultEnvSettings
(fromMaybe (compilerExeName wc) ghciGhcCommand)
("--interactive" : odir <> pkgopts <> srcfiles <> ghciArgs)
-- | Figure out the main-is file to load based on the targets. Sometimes there
-- is none, sometimes it's unambiguous, sometimes it's
-- ambiguous. Warns and returns nothing if it's ambiguous.
figureOutMainFile
:: (Monad m, MonadLogger m)
=> Maybe (Map PackageName SimpleTarget)
-> Map PackageName SimpleTarget
-> [GhciPkgInfo]
-> m (Maybe (Path Abs File))
figureOutMainFile mainIsTargets targets0 packages = do
case candidates of
[] -> return Nothing
[c@(_,_,fp)] -> do $logInfo ("Using main module: " <> renderCandidate c)
return (Just fp)
candidate:_ -> do
let border = $logWarn "* * * * * * * *"
border
$logWarn ("The main module to load is ambiguous. Candidates are: ")
forM_ (map renderCandidate candidates) $logWarn
$logWarn
"None will be loaded. You can specify which one to pick by: "
$logWarn
(" 1) Specifying targets to stack ghci e.g. stack ghci " <>
sampleTargetArg candidate)
$logWarn
(" 2) Specifying what the main is e.g. stack ghci " <>
sampleMainIsArg candidate)
border
return Nothing
where
targets = fromMaybe targets0 mainIsTargets
candidates = do
pkg <- packages
case M.lookup (ghciPkgName pkg) targets of
Nothing -> []
Just target -> do
(component,mains) <-
M.toList
(M.filterWithKey wantedComponent (ghciPkgMainIs pkg))
main <- S.toList mains
return (ghciPkgName pkg, component, main)
where wantedComponent namedC _ =
case target of
STLocalAll -> True
STLocalComps cs -> S.member namedC cs
_ -> False
renderCandidate (pkgName,namedComponent,mainIs) =
"Package `" <> packageNameText pkgName <> "' component " <>
renderComp namedComponent <>
" with main-is file: " <>
T.pack (toFilePath mainIs)
renderComp c =
case c of
CLib -> "lib"
CExe name -> "exe:" <> name
CTest name -> "test:" <> name
CBench name -> "bench:" <> name
sampleTargetArg (pkg,comp,_) =
packageNameText pkg <> ":" <> renderComp comp
sampleMainIsArg (pkg,comp,_) =
"--main-is " <> packageNameText pkg <> ":" <> renderComp comp
-- | Create a list of infos for each target containing necessary
-- information to load that package/components.
ghciSetup
:: (HasConfig r, HasHttpManager r, HasBuildConfig r, MonadMask m, HasTerminal r, HasLogLevel r, HasEnvConfig r, MonadReader r m, MonadIO m, MonadThrow m, MonadLogger m, MonadCatch m, MonadBaseControl IO m)
=> Maybe Text
-> [Text]
-> m (Map PackageName SimpleTarget, Maybe (Map PackageName SimpleTarget), [GhciPkgInfo])
ghciSetup mainIs stringTargets = do
(_,_,targets) <-
parseTargetsFromBuildOpts
AllowNoTargets
defaultBuildOpts
{ boptsTargets = stringTargets
}
mainIsTargets <-
case mainIs of
Nothing -> return Nothing
Just target -> do
(_,_,targets') <-
parseTargetsFromBuildOpts
AllowNoTargets
defaultBuildOpts
{ boptsTargets = [target]
}
return (Just targets')
let bopts = makeBuildOpts targets
econfig <- asks getEnvConfig
(realTargets,_,_,_,sourceMap) <- loadSourceMap AllowNoTargets bopts
locals <-
liftM catMaybes $
forM (M.toList (envConfigPackages econfig)) $
\(dir,validWanted) ->
do cabalfp <- getCabalFileName dir
name <- parsePackageNameFromFilePath cabalfp
if validWanted
then case M.lookup name targets of
Just simpleTargets ->
return (Just (name, (cabalfp, simpleTargets)))
Nothing -> return Nothing
else return Nothing
infos <-
forM locals $
\(name,(cabalfp,components)) ->
makeGhciPkgInfo sourceMap (map fst locals) name cabalfp components
unless (M.null realTargets) (build (const (return ())) Nothing bopts)
return (realTargets, mainIsTargets, infos)
where
makeBuildOpts targets =
base
{ boptsTargets = stringTargets
, boptsTests = any (hasLocalComp isCTest) elems
, boptsBenchmarks = any (hasLocalComp isCBench) elems
, boptsTestOpts = (boptsTestOpts base)
{ toDisableRun = True
, toRerunTests = False
}
, boptsBenchmarkOpts = (boptsBenchmarkOpts base)
{ beoDisableRun = True
}
, boptsBuildSubset = BSOnlyDependencies
}
where
base = defaultBuildOpts
elems = M.elems targets
hasLocalComp p t =
case t of
STLocalComps s -> any p (S.toList s)
STLocalAll -> True
_ -> False
isCTest nc =
case nc of
CTest{} -> True
_ -> False
isCBench nc =
case nc of
CBench{} -> True
_ -> False
-- | Make information necessary to load the given package in GHCi.
makeGhciPkgInfo
:: (MonadReader r m, HasEnvConfig r, MonadLogger m, MonadIO m, MonadCatch m)
=> SourceMap
-> [PackageName]
-> PackageName
-> Path Abs File
-> SimpleTarget
-> m GhciPkgInfo
makeGhciPkgInfo sourceMap locals name cabalfp components = do
econfig <- asks getEnvConfig
bconfig <- asks getBuildConfig
let config =
PackageConfig
{ packageConfigEnableTests = True
, packageConfigEnableBenchmarks = True
, packageConfigFlags = localFlags mempty bconfig name
, packageConfigCompilerVersion = envConfigCompilerVersion econfig
, packageConfigPlatform = configPlatform (getConfig bconfig)
}
(warnings,pkg) <- readPackage config cabalfp
mapM_ (printCabalFileWarning cabalfp) warnings
(componentsModules,componentFiles,componentsOpts,generalOpts) <-
getPackageOpts (packageOpts pkg) sourceMap locals cabalfp
let filterWithinWantedComponents m =
M.elems
(M.filterWithKey
(\k _ ->
case components of
STLocalComps cs -> S.member k cs
_ -> True)
m)
return
GhciPkgInfo
{ ghciPkgName = packageName pkg
, ghciPkgOpts = filter
(not . badForGhci)
(generalOpts <>
concat (filterWithinWantedComponents componentsOpts))
, ghciPkgDir = parent cabalfp
, ghciPkgModules = mconcat
(filterWithinWantedComponents componentsModules)
, ghciPkgModFiles = mconcat
(filterWithinWantedComponents
(M.map (setMapMaybe dotCabalModulePath) componentFiles))
, ghciPkgMainIs = M.map (setMapMaybe dotCabalMainPath) componentFiles
, ghciPkgCFiles = mconcat
(filterWithinWantedComponents
(M.map (setMapMaybe dotCabalCFilePath) componentFiles))
}
where
badForGhci :: String -> Bool
badForGhci x =
isPrefixOf "-O" x || elem x (words "-debug -threaded -ticky")
setMapMaybe f = S.fromList . mapMaybe f . S.toList