forked from commercialhaskell/stack
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathRunners.hs
More file actions
218 lines (203 loc) · 8.43 KB
/
Copy pathRunners.hs
File metadata and controls
218 lines (203 loc) · 8.43 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
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE TemplateHaskell #-}
-- | Utilities for running stack commands.
module Stack.Runners
( withGlobalConfigAndLock
, withConfigAndLock
, withMiniConfigAndLock
, withBuildConfigAndLock
, withBuildConfig
, withBuildConfigExt
, loadConfigWithOpts
, loadCompilerVersion
, withUserFileLock
, munlockFile
) where
import Control.Monad hiding (forM)
import Control.Monad.Logger
import Control.Exception.Lifted as EL
import Control.Monad.IO.Class
import Control.Monad.Trans.Control
import Data.IORef
import Data.Traversable
import Path
import Path.IO
import Stack.Config
import qualified Stack.Docker as Docker
import qualified Stack.Nix as Nix
import Stack.Setup
import Stack.Types.Compiler (CompilerVersion)
import Stack.Types.Config
import Stack.Types.StackT
import System.Environment (getEnvironment)
import System.IO
import System.FileLock
loadCompilerVersion :: GlobalOpts
-> LoadConfig (StackT () IO)
-> IO CompilerVersion
loadCompilerVersion go lc = do
bconfig <- runStackTGlobal () go $
lcLoadBuildConfig lc (globalCompiler go)
return $ view wantedCompilerVersionL bconfig
-- | Enforce mutual exclusion of every action running via this
-- function, on this path, on this users account.
--
-- A lock file is created inside the given directory. Currently,
-- stack uses locks per-snapshot. In the future, stack may refine
-- this to an even more fine-grain locking approach.
--
withUserFileLock :: (MonadBaseControl IO m, MonadIO m)
=> GlobalOpts
-> Path Abs Dir
-> (Maybe FileLock -> m a)
-> m a
withUserFileLock go@GlobalOpts{} dir act = do
env <- liftIO getEnvironment
let toLock = lookup "STACK_LOCK" env == Just "true"
if toLock
then do
let lockfile = $(mkRelFile "lockfile")
let pth = dir </> lockfile
ensureDir dir
-- Just in case of asynchronous exceptions, we need to be careful
-- when using tryLockFile here:
EL.bracket (liftIO $ tryLockFile (toFilePath pth) Exclusive)
(maybe (return ()) (liftIO . unlockFile))
(\fstTry ->
case fstTry of
Just lk -> EL.finally (act $ Just lk) (liftIO $ unlockFile lk)
Nothing ->
do let chatter = globalLogLevel go /= LevelOther "silent"
when chatter $
liftIO $ hPutStrLn stderr $ "Failed to grab lock ("++show pth++
"); other stack instance running. Waiting..."
EL.bracket (liftIO $ lockFile (toFilePath pth) Exclusive)
(liftIO . unlockFile)
(\lk -> do
when chatter $
liftIO $ hPutStrLn stderr "Lock acquired, proceeding."
act $ Just lk))
else act Nothing
withConfigAndLock
:: GlobalOpts
-> StackT Config IO ()
-> IO ()
withConfigAndLock go@GlobalOpts{..} inner = do
lc <- loadConfigWithOpts go
withUserFileLock go (configStackRoot $ lcConfig lc) $ \lk ->
runStackTGlobal (lcConfig lc) go $
Docker.reexecWithOptionalContainer
(lcProjectRoot lc)
Nothing
(runStackTGlobal (lcConfig lc) go inner)
Nothing
(Just $ munlockFile lk)
-- | Loads global config, ignoring any configuration which would be
-- loaded due to $PWD.
withGlobalConfigAndLock
:: GlobalOpts
-> StackT Config IO ()
-> IO ()
withGlobalConfigAndLock go@GlobalOpts{..} inner = do
lc <- runStackTGlobal () go $
loadConfigMaybeProject globalConfigMonoid Nothing Nothing
withUserFileLock go (configStackRoot $ lcConfig lc) $ \_lk ->
runStackTGlobal (lcConfig lc) go inner
-- For now the non-locking version just unlocks immediately.
-- That is, there's still a serialization point.
withBuildConfig
:: GlobalOpts
-> StackT EnvConfig IO ()
-> IO ()
withBuildConfig go inner =
withBuildConfigAndLock go (\lk -> do munlockFile lk
inner)
withBuildConfigAndLock
:: GlobalOpts
-> (Maybe FileLock -> StackT EnvConfig IO ())
-> IO ()
withBuildConfigAndLock go inner =
withBuildConfigExt go Nothing inner Nothing
withBuildConfigExt
:: GlobalOpts
-> Maybe (StackT Config IO ())
-- ^ Action to perform before the build. This will be run on the host
-- OS even if Docker is enabled for builds. The build config is not
-- available in this action, since that would require build tools to be
-- installed on the host OS.
-> (Maybe FileLock -> StackT EnvConfig IO ())
-- ^ Action that uses the build config. If Docker is enabled for builds,
-- this will be run in a Docker container.
-> Maybe (StackT Config IO ())
-- ^ Action to perform after the build. This will be run on the host
-- OS even if Docker is enabled for builds. The build config is not
-- available in this action, since that would require build tools to be
-- installed on the host OS.
-> IO ()
withBuildConfigExt go@GlobalOpts{..} mbefore inner mafter = do
lc <- loadConfigWithOpts go
withUserFileLock go (configStackRoot $ lcConfig lc) $ \lk0 -> do
-- A local bit of state for communication between callbacks:
curLk <- newIORef lk0
let inner' lk =
-- Locking policy: This is only used for build commands, which
-- only need to lock the snapshot, not the global lock. We
-- trade in the lock here.
do dir <- installationRootDeps
-- Hand-over-hand locking:
withUserFileLock go dir $ \lk2 -> do
liftIO $ writeIORef curLk lk2
liftIO $ munlockFile lk
$logDebug "Starting to execute command inside EnvConfig"
inner lk2
let inner'' lk = do
bconfig <- runStackTGlobal () go $
lcLoadBuildConfig lc globalCompiler
envConfig <-
runStackTGlobal
bconfig go
(setupEnv Nothing)
runStackTGlobal
envConfig
go
(inner' lk)
let getCompilerVersion = loadCompilerVersion go lc
runStackTGlobal (lcConfig lc) go $
Docker.reexecWithOptionalContainer
(lcProjectRoot lc)
mbefore
(runStackTGlobal (lcConfig lc) go $
Nix.reexecWithOptionalShell (lcProjectRoot lc) getCompilerVersion (inner'' lk0))
mafter
(Just $ liftIO $
do lk' <- readIORef curLk
munlockFile lk')
-- | Load the configuration. Convenience function used
-- throughout this module.
loadConfigWithOpts :: GlobalOpts -> IO (LoadConfig (StackT () IO))
loadConfigWithOpts go@GlobalOpts{..} = do
mstackYaml <- forM globalStackYaml resolveFile'
runStackTGlobal () go $ do
lc <- loadConfig globalConfigMonoid globalResolver mstackYaml
-- If we have been relaunched in a Docker container, perform in-container initialization
-- (switch UID, etc.). We do this after first loading the configuration since it must
-- happen ASAP but needs a configuration.
case globalDockerEntrypoint of
Just de -> Docker.entrypoint (lcConfig lc) de
Nothing -> return ()
return lc
withMiniConfigAndLock
:: GlobalOpts
-> StackT MiniConfig IO ()
-> IO ()
withMiniConfigAndLock go@GlobalOpts{..} inner = do
miniConfig <- runStackTGlobal () go $ do
lc <- loadConfigMaybeProject globalConfigMonoid globalResolver Nothing
loadMiniConfig (lcConfig lc)
runStackTGlobal miniConfig go inner
-- | Unlock a lock file, if the value is Just
munlockFile :: MonadIO m => Maybe FileLock -> m ()
munlockFile Nothing = return ()
munlockFile (Just lk) = liftIO $ unlockFile lk