-
Notifications
You must be signed in to change notification settings - Fork 704
/
Copy pathDb.hs
566 lines (506 loc) · 19.1 KB
/
Db.hs
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
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE RankNTypes #-}
-----------------------------------------------------------------------------
-- |
-- Module : Distribution.Simple.Program.Db
-- Copyright : Isaac Jones 2006, Duncan Coutts 2007-2009
--
-- Maintainer : [email protected]
-- Portability : portable
--
-- This provides a 'ProgramDb' type which holds configured and not-yet
-- configured programs. It is the parameter to lots of actions elsewhere in
-- Cabal that need to look up and run programs. If we had a Cabal monad,
-- the 'ProgramDb' would probably be a reader or state component of it.
--
-- One nice thing about using it is that any program that is
-- registered with Cabal will get some \"configure\" and \".cabal\"
-- helpers like --with-foo-args --foo-path= and extra-foo-args.
--
-- There's also a hook for adding programs in a Setup.lhs script. See
-- hookedPrograms in 'Distribution.Simple.UserHooks'. This gives a
-- hook user the ability to get the above flags and such so that they
-- don't have to write all the PATH logic inside Setup.lhs.
module Distribution.Simple.Program.Db
( -- * The collection of configured programs we can run
ProgramDb (..)
, emptyProgramDb
, defaultProgramDb
, restoreProgramDb
-- ** Query and manipulate the program db
, addKnownProgram
, addKnownPrograms
, prependProgramSearchPath
, prependProgramSearchPathNoLogging
, lookupKnownProgram
, knownPrograms
, getProgramSearchPath
, setProgramSearchPath
, modifyProgramSearchPath
, userSpecifyPath
, userSpecifyPaths
, userMaybeSpecifyPath
, userSpecifyArgs
, userSpecifyArgss
, userSpecifiedArgs
, lookupProgram
, lookupProgramByName
, updateProgram
, configuredPrograms
-- ** Query and manipulate the program db
, configureProgram
, configureUnconfiguredProgram
, configureAllKnownPrograms
, unconfigureProgram
, lookupProgramVersion
, reconfigurePrograms
, requireProgram
, requireProgramVersion
, needProgram
-- * Internal functions
, UnconfiguredProgs
, ConfiguredProgs
, updateUnconfiguredProgs
, updateConfiguredProgs
) where
import Distribution.Compat.Prelude
import Prelude ()
import Distribution.Simple.Program.Builtin
import Distribution.Simple.Program.Find
import Distribution.Simple.Program.Types
import Distribution.Simple.Utils
import Distribution.Utils.Structured (Structure (..), Structured (..))
import Distribution.Verbosity
import Distribution.Version
import Data.Tuple (swap)
import qualified Data.Map as Map
import Distribution.Simple.Errors
-- ------------------------------------------------------------
-- * Programs database
-- ------------------------------------------------------------
-- | The configuration is a collection of information about programs. It
-- contains information both about configured programs and also about programs
-- that we are yet to configure.
--
-- The idea is that we start from a collection of unconfigured programs and one
-- by one we try to configure them at which point we move them into the
-- configured collection. For unconfigured programs we record not just the
-- 'Program' but also any user-provided arguments and location for the program.
data ProgramDb = ProgramDb
{ unconfiguredProgs :: UnconfiguredProgs
, progSearchPath :: ProgramSearchPath
, progOverrideEnv :: [(String, Maybe String)]
, configuredProgs :: ConfiguredProgs
}
type UnconfiguredProgram = (Program, Maybe FilePath, [ProgArg])
type UnconfiguredProgs = Map.Map String UnconfiguredProgram
type ConfiguredProgs = Map.Map String ConfiguredProgram
emptyProgramDb :: ProgramDb
emptyProgramDb = ProgramDb Map.empty defaultProgramSearchPath [] Map.empty
defaultProgramDb :: ProgramDb
defaultProgramDb = restoreProgramDb builtinPrograms emptyProgramDb
-- internal helpers:
updateUnconfiguredProgs
:: (UnconfiguredProgs -> UnconfiguredProgs)
-> ProgramDb
-> ProgramDb
updateUnconfiguredProgs update progdb =
progdb{unconfiguredProgs = update (unconfiguredProgs progdb)}
updateConfiguredProgs
:: (ConfiguredProgs -> ConfiguredProgs)
-> ProgramDb
-> ProgramDb
updateConfiguredProgs update progdb =
progdb{configuredProgs = update (configuredProgs progdb)}
-- Read & Show instances are based on listToFM
-- | Note that this instance does not preserve the known 'Program's.
-- See 'restoreProgramDb' for details.
instance Show ProgramDb where
show = show . Map.toAscList . configuredProgs
-- | Note that this instance does not preserve the known 'Program's.
-- See 'restoreProgramDb' for details.
instance Read ProgramDb where
readsPrec p s =
[ (emptyProgramDb{configuredProgs = Map.fromList s'}, r)
| (s', r) <- readsPrec p s
]
-- | Note that this instance does not preserve the known 'Program's.
-- See 'restoreProgramDb' for details.
instance Binary ProgramDb where
put db = do
put (progSearchPath db)
put (progOverrideEnv db)
put (configuredProgs db)
get = do
searchpath <- get
overrides <- get
progs <- get
return $!
emptyProgramDb
{ progSearchPath = searchpath
, progOverrideEnv = overrides
, configuredProgs = progs
}
instance Structured ProgramDb where
structure p =
Nominal
(typeRep p)
0
"ProgramDb"
[ structure (Proxy :: Proxy ProgramSearchPath)
, structure (Proxy :: Proxy [(String, Maybe String)])
, structure (Proxy :: Proxy ConfiguredProgs)
]
-- | The 'Read'\/'Show' and 'Binary' instances do not preserve all the
-- unconfigured 'Programs' because 'Program' is not in 'Read'\/'Show' because
-- it contains functions. So to fully restore a deserialised 'ProgramDb' use
-- this function to add back all the known 'Program's.
--
-- * It does not add the default programs, but you probably want them, use
-- 'builtinPrograms' in addition to any extra you might need.
restoreProgramDb :: [Program] -> ProgramDb -> ProgramDb
restoreProgramDb = addKnownPrograms
-- -------------------------------
-- Managing unconfigured programs
-- | Add a known program that we may configure later
addKnownProgram :: Program -> ProgramDb -> ProgramDb
addKnownProgram prog =
updateUnconfiguredProgs $
Map.insertWith combine (programName prog) (prog, Nothing, [])
where
combine _ (_, path, args) = (prog, path, args)
addKnownPrograms :: [Program] -> ProgramDb -> ProgramDb
addKnownPrograms progs progdb = foldl' (flip addKnownProgram) progdb progs
lookupKnownProgram :: String -> ProgramDb -> Maybe Program
lookupKnownProgram name =
fmap (\(p, _, _) -> p) . Map.lookup name . unconfiguredProgs
knownPrograms :: ProgramDb -> [(Program, Maybe ConfiguredProgram)]
knownPrograms progdb =
[ (p, p') | (p, _, _) <- Map.elems (unconfiguredProgs progdb), let p' = Map.lookup (programName p) (configuredProgs progdb)
]
-- | Get the current 'ProgramSearchPath' used by the 'ProgramDb'.
-- This is the default list of locations where programs are looked for when
-- configuring them. This can be overridden for specific programs (with
-- 'userSpecifyPath'), and specific known programs can modify or ignore this
-- search path in their own configuration code.
getProgramSearchPath :: ProgramDb -> ProgramSearchPath
getProgramSearchPath = progSearchPath
-- | Change the current 'ProgramSearchPath' used by the 'ProgramDb'.
-- This will affect programs that are configured from here on, so you
-- should usually set it before configuring any programs.
setProgramSearchPath :: ProgramSearchPath -> ProgramDb -> ProgramDb
setProgramSearchPath searchpath db = db{progSearchPath = searchpath}
-- | Modify the current 'ProgramSearchPath' used by the 'ProgramDb'.
-- This will affect programs that are configured from here on, so you
-- should usually modify it before configuring any programs.
modifyProgramSearchPath
:: (ProgramSearchPath -> ProgramSearchPath)
-> ProgramDb
-> ProgramDb
modifyProgramSearchPath f db =
setProgramSearchPath (f $ getProgramSearchPath db) db
-- | Modify the current 'ProgramSearchPath' used by the 'ProgramDb'
-- by prepending the provided extra paths.
--
-- - Logs the added paths in info verbosity.
-- - Prepends environment variable overrides.
prependProgramSearchPath
:: Verbosity
-> [FilePath]
-> [(String, Maybe FilePath)]
-> ProgramDb
-> IO ProgramDb
prependProgramSearchPath verbosity extraPaths extraEnv db = do
unless (null extraPaths) $
logExtraProgramSearchPath verbosity extraPaths
unless (null extraEnv) $
logExtraProgramOverrideEnv verbosity extraEnv
return $ prependProgramSearchPathNoLogging extraPaths extraEnv db
prependProgramSearchPathNoLogging
:: [FilePath]
-> [(String, Maybe String)]
-> ProgramDb
-> ProgramDb
prependProgramSearchPathNoLogging extraPaths extraEnv db =
let db' = modifyProgramSearchPath (nub . (map ProgramSearchPathDir extraPaths ++)) db
db'' = db'{progOverrideEnv = extraEnv ++ progOverrideEnv db'}
in db''
-- | User-specify this path. Basically override any path information
-- for this program in the configuration. If it's not a known
-- program ignore it.
userSpecifyPath
:: String
-- ^ Program name
-> FilePath
-- ^ user-specified path to the program
-> ProgramDb
-> ProgramDb
userSpecifyPath name path = updateUnconfiguredProgs $
flip Map.update name $
\(prog, _, args) -> Just (prog, Just path, args)
userMaybeSpecifyPath
:: String
-> Maybe FilePath
-> ProgramDb
-> ProgramDb
userMaybeSpecifyPath _ Nothing progdb = progdb
userMaybeSpecifyPath name (Just path) progdb = userSpecifyPath name path progdb
-- | User-specify the arguments for this program. Basically override
-- any args information for this program in the configuration. If it's
-- not a known program, ignore it..
userSpecifyArgs
:: String
-- ^ Program name
-> [ProgArg]
-- ^ user-specified args
-> ProgramDb
-> ProgramDb
userSpecifyArgs name args' =
updateUnconfiguredProgs
( flip Map.update name $
\(prog, path, args) -> Just (prog, path, args ++ args')
)
. updateConfiguredProgs
( flip Map.update name $
\prog ->
Just
prog
{ programOverrideArgs =
programOverrideArgs prog
++ args'
}
)
-- | Like 'userSpecifyPath' but for a list of progs and their paths.
userSpecifyPaths
:: [(String, FilePath)]
-> ProgramDb
-> ProgramDb
userSpecifyPaths paths progdb =
foldl' (\progdb' (prog, path) -> userSpecifyPath prog path progdb') progdb paths
-- | Like 'userSpecifyPath' but for a list of progs and their args.
userSpecifyArgss
:: [(String, [ProgArg])]
-> ProgramDb
-> ProgramDb
userSpecifyArgss argss progdb =
foldl' (\progdb' (prog, args) -> userSpecifyArgs prog args progdb') progdb argss
-- | Get the path that has been previously specified for a program, if any.
userSpecifiedPath :: Program -> ProgramDb -> Maybe FilePath
userSpecifiedPath prog =
join . fmap (\(_, p, _) -> p) . Map.lookup (programName prog) . unconfiguredProgs
-- | Get any extra args that have been previously specified for a program.
userSpecifiedArgs :: Program -> ProgramDb -> [ProgArg]
userSpecifiedArgs prog =
maybe [] (\(_, _, as) -> as) . Map.lookup (programName prog) . unconfiguredProgs
-- -----------------------------
-- Managing configured programs
-- | Try to find a configured program
lookupProgram :: Program -> ProgramDb -> Maybe ConfiguredProgram
lookupProgram = lookupProgramByName . programName
-- | Try to find a configured program
lookupProgramByName :: String -> ProgramDb -> Maybe ConfiguredProgram
lookupProgramByName name = Map.lookup name . configuredProgs
-- | Update a configured program in the database.
updateProgram
:: ConfiguredProgram
-> ProgramDb
-> ProgramDb
updateProgram prog =
updateConfiguredProgs $
Map.insert (programId prog) prog
-- | List all configured programs.
configuredPrograms :: ProgramDb -> [ConfiguredProgram]
configuredPrograms = Map.elems . configuredProgs
-- ---------------------------
-- Configuring known programs
-- | Try to configure a specific program and add it to the program database.
--
-- If the program is already included in the collection of unconfigured programs,
-- then we use any user-supplied location and arguments.
-- If the program gets configured successfully, it gets added to the configured
-- collection.
--
-- Note that it is not a failure if the program cannot be configured. It's only
-- a failure if the user supplied a location and the program could not be found
-- at that location.
--
-- The reason for it not being a failure at this stage is that we don't know up
-- front all the programs we will need, so we try to configure them all.
-- To verify that a program was actually successfully configured use
-- 'requireProgram'.
configureProgram
:: Verbosity
-> Program
-> ProgramDb
-> IO ProgramDb
configureProgram verbosity prog progdb = do
mbConfiguredProg <- configureUnconfiguredProgram verbosity prog progdb
case mbConfiguredProg of
Nothing -> return progdb
Just configuredProg -> do
let progdb' =
updateConfiguredProgs
(Map.insert (programName prog) configuredProg)
progdb
return progdb'
-- | Try to configure a specific program. If the program is already included in
-- the collection of unconfigured programs then we use any user-supplied
-- location and arguments.
configureUnconfiguredProgram
:: Verbosity
-> Program
-> ProgramDb
-> IO (Maybe ConfiguredProgram)
configureUnconfiguredProgram verbosity prog progdb = do
let name = programName prog
maybeLocation <- case userSpecifiedPath prog progdb of
Nothing ->
programFindLocation prog verbosity (progSearchPath progdb)
>>= return . fmap (swap . fmap FoundOnSystem . swap)
Just path -> do
absolute <- doesExecutableExist path
if absolute
then return (Just (UserSpecified path, []))
else
findProgramOnSearchPath verbosity (progSearchPath progdb) path
>>= maybe
(dieWithException verbosity $ ConfigureProgram name path)
(return . Just . swap . fmap UserSpecified . swap)
case maybeLocation of
Nothing -> return Nothing
Just (location, triedLocations) -> do
version <- programFindVersion prog verbosity (locationPath location)
newPath <- programSearchPathAsPATHVar (progSearchPath progdb)
let configuredProg =
ConfiguredProgram
{ programId = name
, programVersion = version
, programDefaultArgs = []
, programOverrideArgs = userSpecifiedArgs prog progdb
, programOverrideEnv = [("PATH", Just newPath)] ++ progOverrideEnv progdb
, programProperties = Map.empty
, programLocation = location
, programMonitorFiles = triedLocations
}
configuredProg' <- programPostConf prog verbosity configuredProg
return $ Just configuredProg'
-- | Configure a bunch of programs using 'configureProgram'. Just a 'foldM'.
configurePrograms
:: Verbosity
-> [Program]
-> ProgramDb
-> IO ProgramDb
configurePrograms verbosity progs progdb =
foldM (flip (configureProgram verbosity)) progdb progs
-- | Unconfigure a program. This is basically a hack and you shouldn't
-- use it, but it can be handy for making sure a 'requireProgram'
-- actually reconfigures.
unconfigureProgram :: String -> ProgramDb -> ProgramDb
unconfigureProgram progname =
updateConfiguredProgs $ Map.delete progname
-- | Try to configure all the known programs that have not yet been configured.
configureAllKnownPrograms
:: Verbosity
-> ProgramDb
-> IO ProgramDb
configureAllKnownPrograms verbosity progdb =
configurePrograms
verbosity
[prog | (prog, _, _) <- Map.elems notYetConfigured]
progdb
where
notYetConfigured =
unconfiguredProgs progdb
`Map.difference` configuredProgs progdb
-- | reconfigure a bunch of programs given new user-specified args. It takes
-- the same inputs as 'userSpecifyPath' and 'userSpecifyArgs' and for all progs
-- with a new path it calls 'configureProgram'.
reconfigurePrograms
:: Verbosity
-> [(String, FilePath)]
-> [(String, [ProgArg])]
-> ProgramDb
-> IO ProgramDb
reconfigurePrograms verbosity paths argss progdb = do
configurePrograms verbosity progs
. userSpecifyPaths paths
. userSpecifyArgss argss
$ progdb
where
progs = catMaybes [lookupKnownProgram name progdb | (name, _) <- paths]
-- | Check that a program is configured and available to be run.
--
-- It raises an exception if the program could not be configured, otherwise
-- it returns the configured program.
requireProgram
:: Verbosity
-> Program
-> ProgramDb
-> IO (ConfiguredProgram, ProgramDb)
requireProgram verbosity prog progdb = do
mres <- needProgram verbosity prog progdb
case mres of
Nothing -> dieWithException verbosity $ RequireProgram (programName prog)
Just res -> return res
-- | Check that a program is configured and available to be run.
--
-- It returns 'Nothing' if the program couldn't be configured,
-- or is not found.
--
-- @since 3.0.1.0
needProgram
:: Verbosity
-> Program
-> ProgramDb
-> IO (Maybe (ConfiguredProgram, ProgramDb))
needProgram verbosity prog progdb = do
-- If it's not already been configured, try to configure it now
progdb' <- case lookupProgram prog progdb of
Nothing -> configureProgram verbosity prog progdb
Just _ -> return progdb
case lookupProgram prog progdb' of
Nothing -> return Nothing
Just configuredProg -> return (Just (configuredProg, progdb'))
-- | Check that a program is configured and available to be run.
--
-- Additionally check that the program version number is suitable and return
-- it. For example you could require 'AnyVersion' or @'orLaterVersion'
-- ('Version' [1,0] [])@
--
-- It returns the configured program, its version number and a possibly updated
-- 'ProgramDb'. If the program could not be configured or the version is
-- unsuitable, it returns an error value.
lookupProgramVersion
:: Verbosity
-> Program
-> VersionRange
-> ProgramDb
-> IO (Either CabalException (ConfiguredProgram, Version, ProgramDb))
lookupProgramVersion verbosity prog range programDb = do
-- If it's not already been configured, try to configure it now
programDb' <- case lookupProgram prog programDb of
Nothing -> configureProgram verbosity prog programDb
Just _ -> return programDb
case lookupProgram prog programDb' of
Nothing -> return $! Left $ NoProgramFound (programName prog) range
Just configuredProg@ConfiguredProgram{programLocation = location} ->
case programVersion configuredProg of
Just version
| withinRange version range ->
return $! Right (configuredProg, version, programDb')
| otherwise ->
return $! Left $ BadVersionDb (programName prog) version range (locationPath location)
Nothing ->
return $! Left $ UnknownVersionDb (programName prog) range (locationPath location)
-- | Like 'lookupProgramVersion', but raises an exception in case of error
-- instead of returning 'Left errMsg'.
requireProgramVersion
:: Verbosity
-> Program
-> VersionRange
-> ProgramDb
-> IO (ConfiguredProgram, Version, ProgramDb)
requireProgramVersion verbosity prog range programDb =
join $
either (dieWithException verbosity) return
`fmap` lookupProgramVersion verbosity prog range programDb