forked from commercialhaskell/stack
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathExec.hs
More file actions
169 lines (149 loc) · 5.85 KB
/
Exec.hs
File metadata and controls
169 lines (149 loc) · 5.85 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
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
-- | Types and function related to Stack's @exec@, @ghc@, @run@, @runghc@ and
-- @runhaskell@ commands.
module Stack.Exec
( ExecOpts (..)
, SpecialExecCmd (..)
, ExecOptsExtra (..)
, execCmd
) where
import qualified Data.List as L
import qualified Data.Map.Strict as Map
import qualified Data.Set as Set
import qualified Data.Text as T
import RIO.Process ( exec )
import Stack.Build ( build )
import Stack.Build.Target ( NeedTargets (..) )
import Stack.GhcPkg ( findGhcPkgField )
import Stack.Setup ( withNewLocalBuildTargets )
import Stack.Types.NamedComponent ( NamedComponent (..), isCExe )
import Stack.Prelude
import Stack.Runners ( ShouldReexec (..), withConfig, withEnvConfig )
import Stack.Types.BuildConfig
( BuildConfig (..), HasBuildConfig (..) )
import Stack.Types.BuildOpts
( BuildOptsCLI (..), defaultBuildOptsCLI )
import Stack.Types.CompilerPaths
( CompilerPaths (..), HasCompiler (..), getGhcPkgExe )
import Stack.Types.Config ( Config (..), HasConfig (..) )
import Stack.Types.EnvConfig ( EnvConfig )
import Stack.Types.EnvSettings ( EnvSettings (..) )
import Stack.Types.Runner ( Runner )
import Stack.Types.SourceMap ( SMWanted (..), ppComponents )
import System.Directory ( withCurrentDirectory )
import System.FilePath ( isValid )
-- | Type representing exceptions thrown by functions in the "Stack.Exec"
-- module.
newtype ExecException
= InvalidPathForExec FilePath
deriving (Show, Typeable)
instance Exception ExecException where
displayException (InvalidPathForExec path) = concat
[ "Error: [S-1541]\n"
, "Got an invalid '--cwd' argument for 'stack exec' ("
, path
, ")."
]
-- | Type representing \'pretty\' exceptions thrown by functions in the
-- "Stack.Exec" module.
data ExecPrettyException
= PackageIdNotFoundBug !String
| ExecutableToRunNotFound
deriving (Show, Typeable)
instance Pretty ExecPrettyException where
pretty (PackageIdNotFoundBug name) = bugPrettyReport "[S-8251]" $
"Could not find the package id of the package" <+>
style Target (fromString name)
<> "."
pretty ExecutableToRunNotFound =
"[S-2483]"
<> line
<> flow "No executables found."
instance Exception ExecPrettyException
-- Type representing Stack's execution commands.
data SpecialExecCmd
= ExecCmd String
| ExecRun
| ExecGhc
| ExecRunGhc
deriving (Eq, Show)
data ExecOptsExtra = ExecOptsExtra
{ eoEnvSettings :: !EnvSettings
, eoPackages :: ![String]
, eoRtsOptions :: ![String]
, eoCwd :: !(Maybe FilePath)
}
deriving Show
-- Type representing options for Stack's execution commands.
data ExecOpts = ExecOpts
{ eoCmd :: !SpecialExecCmd
, eoArgs :: ![String]
, eoExtra :: !ExecOptsExtra
}
deriving Show
-- | The function underlying Stack's @exec@, @ghc@, @run@, @runghc@ and
-- @runhaskell@ commands. Execute a command.
execCmd :: ExecOpts -> RIO Runner ()
execCmd ExecOpts {..} =
withConfig YesReexec $ withEnvConfig AllowNoTargets boptsCLI $ do
unless (null targets) $ build Nothing
config <- view configL
menv <- liftIO $ configProcessContextSettings config eoEnvSettings
withProcessContext menv $ do
-- Add RTS options to arguments
let argsWithRts args = if null eoRtsOptions
then args :: [String]
else args ++ ["+RTS"] ++ eoRtsOptions ++ ["-RTS"]
(cmd, args) <- case (eoCmd, argsWithRts eoArgs) of
(ExecCmd cmd, args) -> pure (cmd, args)
(ExecRun, args) -> getRunCmd args
(ExecGhc, args) -> getGhcCmd eoPackages args
(ExecRunGhc, args) -> getRunGhcCmd eoPackages args
runWithPath eoCwd $ exec cmd args
where
ExecOptsExtra {..} = eoExtra
targets = concatMap words eoPackages
boptsCLI = defaultBuildOptsCLI
{ boptsCLITargets = map T.pack targets
}
-- return the package-id of the first package in GHC_PACKAGE_PATH
getPkgId name = do
pkg <- getGhcPkgExe
mId <- findGhcPkgField pkg [] name "id"
case mId of
Just i -> pure (L.head $ words (T.unpack i))
-- should never happen as we have already installed the packages
_ -> prettyThrowIO (PackageIdNotFoundBug name)
getPkgOpts pkgs =
map ("-package-id=" ++) <$> mapM getPkgId pkgs
getRunCmd args = do
packages <- view $ buildConfigL.to (smwProject . bcSMWanted)
pkgComponents <- for (Map.elems packages) ppComponents
let executables = filter isCExe $ concatMap Set.toList pkgComponents
let (exe, args') = case args of
[] -> (firstExe, args)
x:xs -> case L.find (\y -> y == CExe (T.pack x)) executables of
Nothing -> (firstExe, args)
argExe -> (argExe, xs)
where
firstExe = listToMaybe executables
case exe of
Just (CExe exe') -> do
withNewLocalBuildTargets [T.cons ':' exe'] $ build Nothing
pure (T.unpack exe', args')
_ -> prettyThrowIO ExecutableToRunNotFound
getGhcCmd pkgs args = do
pkgopts <- getPkgOpts pkgs
compiler <- view $ compilerPathsL.to cpCompiler
pure (toFilePath compiler, pkgopts ++ args)
getRunGhcCmd pkgs args = do
pkgopts <- getPkgOpts pkgs
interpret <- view $ compilerPathsL.to cpInterpreter
pure (toFilePath interpret, pkgopts ++ args)
runWithPath :: Maybe FilePath -> RIO EnvConfig () -> RIO EnvConfig ()
runWithPath path callback = case path of
Nothing -> callback
Just p | not (isValid p) -> throwIO $ InvalidPathForExec p
Just p -> withUnliftIO $ \ul -> withCurrentDirectory p $ unliftIO ul callback