Program.hs 22.1 KB
Newer Older
1
{-# OPTIONS -cpp #-}
ijones's avatar
ijones committed
2
3
-----------------------------------------------------------------------------
-- |
4
-- Module      :  Distribution.Simple.Program
ijones's avatar
ijones committed
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
-- Copyright   :  Isaac Jones 2006
-- 
-- Maintainer  :  Isaac Jones <ijones@syntaxpolice.org>
-- Stability   :  alpha
-- Portability :  GHC, Hugs
--
-- Explanation: A program is basically a name, a location, and some
-- arguments.
--
-- 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 good default behavior for trying to find \"foo\" in
-- PATH, being able to override its location, etc.
--
-- 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.

26
module Distribution.Simple.Program (
27
28
29
    -- * Program and functions for constructing them
      Program(..)
    , simpleProgram
30
    , findProgramOnPath
31
32
33
34
35
36
37
38
    , findProgramVersion

    -- * Configured program and related functions
    , ConfiguredProgram(..)
    , programPath
    , ProgArg
    , ProgramLocation(..)
    , rawSystemProgram
39
    , rawSystemProgramStdout
40
41
42
43
44
45
46
47
48

    -- * The collection of unconfigured and configured progams
    , builtinPrograms

    -- * The collection of configured programs we can run
    , ProgramConfiguration
    , emptyProgramConfiguration
    , defaultProgramConfiguration
    , addKnownProgram
49
    , lookupKnownProgram
50
51
52
53
54
55
56
57
58
    , knownPrograms
    , userSpecifyPath
    , userMaybeSpecifyPath
    , userSpecifyArgs
    , lookupProgram
    , updateProgram
    , configureAllKnownPrograms
    , requireProgram
    , rawSystemProgramConf
59
    , rawSystemProgramStdoutConf
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83

    -- * Programs that Cabal knows about
    , ghcProgram
    , ghcPkgProgram
    , nhcProgram
    , hmakeProgram
    , jhcProgram
    , hugsProgram
    , ffihugsProgram
    , ranlibProgram
    , arProgram
    , happyProgram
    , alexProgram
    , hsc2hsProgram
    , c2hsProgram
    , cpphsProgram
    , hscolourProgram
    , haddockProgram
    , greencardProgram
    , ldProgram
    , tarProgram
    , cppProgram
    , pfesetupProgram
    ) where
ijones's avatar
ijones committed
84

85
import qualified Distribution.Compat.Map as Map
86
87
import Distribution.Compat.Directory (findExecutable)
import Distribution.Simple.Utils (die, rawSystemExit, rawSystemStdout)
88
89
import Distribution.Version (Version, readVersion, showVersion,
                             VersionRange(..), withinRange, showVersionRange)
90
import Distribution.Verbosity
91
import System.Directory (doesFileExist)
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
import Control.Monad (when, join, foldM)
import Control.Exception as Exception (catch)

-- | Represents a program which can be configured.
data Program = Program {
        -- | The simple name of the program, eg. ghc
        programName :: String,
        
        -- | A function to search for the program if it's location was not
        -- specified by the user. Usually this will just be a 
        programFindLocation :: Verbosity -> IO (Maybe FilePath),
        
        -- | Try to find the version of the program. For many programs this is
        -- not possible or is not necessary so it's ok to return Nothing.
        programFindVersion :: Verbosity -> FilePath -> IO (Maybe Version)
    }

type ProgArg = String

data ConfiguredProgram = ConfiguredProgram {
        -- | Just the name again
        programId :: String,
        
        -- | The version of this program, if it is known.
        programVersion :: Maybe Version,

        -- | Default command-line args for this program.
        -- These flags will appear first on the command line, so they can be
        -- overridden by subsequent flags.
        programArgs :: [ProgArg],

        -- | Location of the program. eg. @\/usr\/bin\/ghc-6.4@
        programLocation :: ProgramLocation    
    } deriving (Read, Show)

-- | Where a program was found. Also tells us whether it's specifed by user or
128
-- not.  This includes not just the path, but the program as well.
129
130
data ProgramLocation
    = UserSpecified { locationPath :: FilePath }
nominolo@gmail.com's avatar
nominolo@gmail.com committed
131
132
      -- ^The user gave the path to this program,
      -- eg. --ghc-path=\/usr\/bin\/ghc-6.6
133
    | FoundOnSystem { locationPath :: FilePath }
nominolo@gmail.com's avatar
nominolo@gmail.com committed
134
      -- ^The location of the program, as located by searching PATH.
ijones's avatar
ijones committed
135
      deriving (Read, Show)
136

137
138
139
-- ------------------------------------------------------------
-- * Programs functions
-- ------------------------------------------------------------
140

141
142
143
-- | The full path of a configured program.
programPath :: ConfiguredProgram -> FilePath
programPath = locationPath . programLocation
ijones's avatar
ijones committed
144

145
146
147
148
149
150
151
152
153
-- | Make a simple named program.
--
-- By default we'll just search for it in the path and not try to find the
-- version name. You can override these behaviours if necessary, eg:
--
-- > simpleProgram "foo" { programFindLocation = ... , programFindVersion ... }
--
simpleProgram :: String -> Program
simpleProgram name = 
154
  Program name (findProgramOnPath name) (\_ _ -> return Nothing)
155
156

-- | Look for a program on the path.
157
158
findProgramOnPath :: FilePath -> Verbosity -> IO (Maybe FilePath)
findProgramOnPath prog verbosity = do
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
  when (verbosity >= deafening) $
      putStrLn $ "searching for " ++ prog ++ " in path."
  res <- findExecutable prog
  when (verbosity >= deafening) $ case res of
      Nothing   -> putStrLn ("Cannot find " ++ prog ++ " on the path")
      Just path -> putStrLn ("found " ++ prog ++ " at "++ path)
  return res

-- | Look for a program and try to find it's version number. It can accept
-- either an absolute path or the name of a program binary, in which case we
-- will look for the program on the path.
--
findProgramVersion :: ProgArg            -- ^ version args
                   -> (String -> String) -- ^ function to select version
                                         --   number from program output
                   -> Verbosity
                   -> FilePath           -- ^ location
                   -> IO (Maybe Version)
findProgramVersion versionArg selectVersion verbosity path = do
  str <- rawSystemStdout verbosity path [versionArg]
         `Exception.catch` \_ -> return ""
  let version = readVersion (selectVersion str)
  case version of
      Nothing -> when (verbosity >= normal) $
                   putStrLn $ "cannot determine version of " ++ path ++ " :\n"
                           ++ show str
      Just v  -> when (verbosity >= deafening) $
                   putStrLn $ path ++ " is version " ++ showVersion v
  return version

-- ------------------------------------------------------------
-- * 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 ProgramConfiguration = ProgramConfiguration {
        unconfiguredProgs :: UnconfiguredProgs,
        configuredProgs   :: ConfiguredProgs
    }
type UnconfiguredProgram = (Program, Maybe FilePath, [ProgArg])
type UnconfiguredProgs = Map.Map String UnconfiguredProgram
type ConfiguredProgs =   Map.Map String ConfiguredProgram

emptyProgramConfiguration :: ProgramConfiguration
emptyProgramConfiguration = ProgramConfiguration Map.empty Map.empty

defaultProgramConfiguration :: ProgramConfiguration
defaultProgramConfiguration =
  foldl (flip addKnownProgram) emptyProgramConfiguration builtinPrograms

-- internal helpers:
updateUnconfiguredProgs :: (UnconfiguredProgs -> UnconfiguredProgs)
                        -> ProgramConfiguration -> ProgramConfiguration
updateUnconfiguredProgs update conf =
  conf { unconfiguredProgs = update (unconfiguredProgs conf) }
updateConfiguredProgs :: (ConfiguredProgs -> ConfiguredProgs)
                      -> ProgramConfiguration -> ProgramConfiguration
updateConfiguredProgs update conf =
  conf { configuredProgs = update (configuredProgs conf) }

-- Read & Show instances are based on listToFM
-- Note that we only serialise the configured part of the database, this is
-- because we don't need the unconfigured part after the configure stage, and
-- additionally because we cannot read/show 'Program' as it contains functions.
230
instance Show ProgramConfiguration where
231
  show = show . Map.toAscList . configuredProgs
232
233

instance Read ProgramConfiguration where
234
235
236
  readsPrec p s =
    [ (emptyProgramConfiguration { configuredProgs = Map.fromList s' }, r)
    | (s', r) <- readsPrec p s ]
ijones's avatar
ijones committed
237

238
239
-- -------------------------------
-- Managing unconfigured programs
ijones's avatar
ijones committed
240

241
242
243
244
245
-- | Add a known program that we may configure later
addKnownProgram :: Program -> ProgramConfiguration -> ProgramConfiguration
addKnownProgram prog = updateUnconfiguredProgs $
  Map.insert (programName prog) (prog, Nothing, [])

246
247
248
249
lookupKnownProgram :: String -> ProgramConfiguration -> Maybe Program
lookupKnownProgram name =
  fmap (\(p,_,_)->p) . Map.lookup name . unconfiguredProgs

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
knownPrograms :: ProgramConfiguration -> [(Program, Maybe ConfiguredProgram)]
knownPrograms conf = 
  [ (p,p') | (p,_,_) <- Map.elems (unconfiguredProgs conf)
           , let p' = Map.lookup (programName p) (configuredProgs conf) ]

-- |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
                -> ProgramConfiguration -> ProgramConfiguration
userSpecifyPath name path = updateUnconfiguredProgs $
  flip Map.update name $ \(prog, _, args) -> Just (prog, Just path, args)

userMaybeSpecifyPath :: String -> Maybe FilePath
                     -> ProgramConfiguration -> ProgramConfiguration
userMaybeSpecifyPath _    Nothing conf     = conf
userMaybeSpecifyPath name (Just path) conf = userSpecifyPath name path conf

-- |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
                -> ProgramConfiguration
                -> ProgramConfiguration
userSpecifyArgs name args' = updateUnconfiguredProgs $
  flip Map.update name $ \(prog, path, args) -> Just (prog, path, args ++ args')

userSpecifiedPath :: Program -> ProgramConfiguration -> Maybe FilePath
userSpecifiedPath prog =
  join . fmap (\(_,p,_)->p) . Map.lookup (programName prog) . unconfiguredProgs

userSpecifiedArgs :: Program -> ProgramConfiguration -> [ProgArg]
userSpecifiedArgs prog =
  maybe [] (\(_,_,as)->as) . Map.lookup (programName prog) . unconfiguredProgs

-- -----------------------------
-- Managing configured programs

-- | Try to find a configured program
lookupProgram :: Program -> ProgramConfiguration -> Maybe ConfiguredProgram
lookupProgram prog = Map.lookup (programName prog) . configuredProgs

-- | Update a configured program in the database.
updateProgram :: ConfiguredProgram -> ProgramConfiguration
                                   -> ProgramConfiguration
updateProgram prog = updateConfiguredProgs $
  Map.insert (programId prog) prog

-- ---------------------------
-- Configuring known programs

-- | Try to configure a specific program. If the program is already included in
-- the colleciton of unconfigured programs then we use any user-supplied
-- location and arguments. If the program gets configured sucessfully it gets 
-- added to the configured collection.
307
--
308
309
310
-- 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.
311
--
312
313
314
315
316
317
318
319
320
321
322
323
324
325
-- 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 sucessfully configured use
-- 'requireProgram'. 
--
configureProgram :: Verbosity
                 -> Program
                 -> ProgramConfiguration
                 -> IO ProgramConfiguration
configureProgram verbosity prog conf = do
  let name = programName prog
  maybeLocation <- case userSpecifiedPath prog conf of
    Nothing   -> programFindLocation prog verbosity
             >>= return . fmap FoundOnSystem
326
327
328
    Just path -> do
      absolute <- doesFileExist path
      if absolute
329
        then return (Just (UserSpecified path))
330
        else findProgramOnPath path verbosity
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
         >>= maybe (die notFound) (return . Just . UserSpecified)
      where notFound = "Cannot find " ++ name ++ " at "
                     ++ path ++ " or on the path"
  case maybeLocation of
    Nothing -> return conf
    Just location -> do
      version <- programFindVersion prog verbosity (locationPath location)
      let configuredProg = ConfiguredProgram {
            programId       = name,
            programVersion  = version,
            programArgs     = userSpecifiedArgs prog conf,
            programLocation = location
          }
      return (updateConfiguredProgs (Map.insert name configuredProg) conf)

-- | Try to configure all the known programs that have not yet been configured.
configureAllKnownPrograms :: Verbosity
                  -> ProgramConfiguration
                  -> IO ProgramConfiguration
configureAllKnownPrograms verbosity conf =
  foldM (flip (configureProgram verbosity)) conf
    [ prog | (prog,_,_) <- Map.elems (unconfiguredProgs conf
                     `Map.difference` configuredProgs conf) ]

-- | Check that a program is configured and available to be run.
--
-- Additionally check that the version of the program number is suitable.
-- For example 'AnyVersion' or @'orLaterVersion' ('Version' [1,0] [])@
--
-- It raises an exception if the program could not be configured or the version
-- is unsuitable, otherwise it returns the configured program.
requireProgram :: Verbosity -> Program -> VersionRange -> ProgramConfiguration
               -> IO (ConfiguredProgram, ProgramConfiguration)
requireProgram verbosity prog range conf = do
  
  -- If it's not already been configured, try to configure it now
  conf' <- case lookupProgram prog conf of
    Nothing -> configureProgram verbosity prog conf
    Just _  -> return conf
  
  case lookupProgram prog conf' of
    Nothing                           -> die notFound
    Just configuredProg
      | range == AnyVersion           -> return (configuredProg, conf')
    Just configuredProg@ConfiguredProgram { programLocation = location } ->
      case programVersion configuredProg of
        Just version
          | withinRange version range -> return (configuredProg, conf')
          | otherwise                 -> die (badVersion version location)
        Nothing                       -> die (noVersion location)

  where notFound       = programName prog ++ versionRequirement
                      ++ " is required but it could not be found."
        badVersion v l = programName prog ++ versionRequirement
                      ++ " is required but the version found at "
                      ++ locationPath l ++ " is version " ++ showVersion v
        noVersion l    = programName prog ++ versionRequirement
                      ++ " is required but the version of "
                      ++ locationPath l ++ " could not be determined."
        versionRequirement
          | range == AnyVersion = ""
          | otherwise           = " version " ++ showVersionRange range
393

394
395
396
-- ------------------------------------------------------------
-- * Running programs
-- ------------------------------------------------------------
397

398
399
400
401
402
403
404
405
-- | Runs the given configured program.
rawSystemProgram :: Verbosity          -- ^Verbosity
                 -> ConfiguredProgram  -- ^The program to run
                 -> [ProgArg]          -- ^Any /extra/ arguments to add
                 -> IO ()
rawSystemProgram verbosity prog extraArgs
  = rawSystemExit verbosity (programPath prog) (programArgs prog ++ extraArgs)

406
407
408
409
410
411
412
413
-- | Runs the given configured program and gets the output.
rawSystemProgramStdout :: Verbosity          -- ^Verbosity
                       -> ConfiguredProgram  -- ^The program to run
                       -> [ProgArg]          -- ^Any /extra/ arguments to add
                       -> IO String
rawSystemProgramStdout verbosity prog extraArgs
  = rawSystemStdout verbosity (programPath prog) (programArgs prog ++ extraArgs)

414
415
416
417
418
419
420
421
422
423
-- | Looks up the given program in the program configuration and runs it.
rawSystemProgramConf :: Verbosity            -- ^verbosity
                     -> Program              -- ^The program to run
                     -> ProgramConfiguration -- ^look up the program here
                     -> [ProgArg]            -- ^Any /extra/ arguments to add
                     -> IO ()
rawSystemProgramConf verbosity prog programConf extraArgs =
  case lookupProgram prog programConf of
    Nothing -> die (programName prog ++ " command not found")
    Just configuredProg -> rawSystemProgram verbosity configuredProg extraArgs
424

425
426
427
428
429
430
431
432
433
434
435
-- | Looks up the given program in the program configuration and runs it.
rawSystemProgramStdoutConf :: Verbosity            -- ^verbosity
                           -> Program              -- ^The program to run
                           -> ProgramConfiguration -- ^look up the program here
                           -> [ProgArg]            -- ^Any /extra/ arguments to add
                           -> IO String
rawSystemProgramStdoutConf verbosity prog programConf extraArgs =
  case lookupProgram prog programConf of
    Nothing -> die (programName prog ++ " command not found")
    Just configuredProg -> rawSystemProgramStdout verbosity configuredProg extraArgs

ijones's avatar
ijones committed
436
-- ------------------------------------------------------------
437
-- * Known programs
ijones's avatar
ijones committed
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
-- | The default list of programs.
-- These programs are typically used internally to Cabal.
builtinPrograms :: [Program]
builtinPrograms =
    [
    -- compilers and related progs
      ghcProgram
    , ghcPkgProgram
    , hugsProgram
    , ffihugsProgram
    , nhcProgram
    , hmakeProgram
    , jhcProgram
    -- preprocessors
    , hscolourProgram
    , haddockProgram
    , happyProgram
    , alexProgram
    , hsc2hsProgram
    , c2hsProgram
    , cpphsProgram
    , greencardProgram
    , pfesetupProgram
    -- platform toolchain
    , ranlibProgram
    , arProgram
    , ldProgram
    , tarProgram
    ]

ijones's avatar
ijones committed
470
ghcProgram :: Program
471
472
473
ghcProgram = (simpleProgram "ghc") {
    programFindVersion = findProgramVersion "--numeric-version" id
  }
ijones's avatar
ijones committed
474
475

ghcPkgProgram :: Program
476
477
478
479
480
481
482
483
ghcPkgProgram = (simpleProgram "ghc-pkg") {
    programFindVersion = findProgramVersion "--version" $ \str ->
      -- Invoking "ghc-pkg --version" gives a string like
      -- "GHC package manager version 6.4.1"
      case words str of
        (_:_:_:_:ver:_) -> ver
        _               -> ""
  }
ijones's avatar
ijones committed
484
485
486
487

nhcProgram :: Program
nhcProgram = simpleProgram "nhc"

488
489
490
491
492
493
494
hmakeProgram :: Program
hmakeProgram = (simpleProgram "hmake") {
    programFindVersion = findProgramVersion "--version" $ \str ->
      case words str of
        (_:ver:_) -> ver
        _         -> ""
  }
ekarttun's avatar
ekarttun committed
495

496
497
498
499
500
501
502
503
504
jhcProgram :: Program
jhcProgram = (simpleProgram "jhc") {
    programFindVersion = findProgramVersion "--version" $ \str ->
      case words str of
        (_:ver:_) -> ver
        _         -> ""
  }

-- AArgh! Finding the version of hugs or ffihugs is almost impossible.
ijones's avatar
ijones committed
505
506
507
hugsProgram :: Program
hugsProgram = simpleProgram "hugs"

508
509
510
ffihugsProgram :: Program
ffihugsProgram = simpleProgram "ffihugs"

511
happyProgram :: Program
512
513
514
515
516
517
518
519
happyProgram = (simpleProgram "happy") {
    programFindVersion = findProgramVersion "--version" $ \str ->
      -- Invoking "happy --version" gives a string like
      -- "Happy Version 1.16 Copyright (c) ...."
      case words str of
        (_:_:ver:_) -> ver
        _           -> ""
  }
520

ijones's avatar
ijones committed
521
alexProgram :: Program
522
523
524
525
526
527
528
529
530
alexProgram = (simpleProgram "alex") {
    programFindVersion = findProgramVersion "--version" $ \str ->
      -- Invoking "alex --version" gives a string like
      -- "Alex version 2.1.0, (c) 2003 Chris Dornan and Simon Marlow"
      case words str of
        (_:_:ver:_) -> takeWhile (`elem` ('.':['0'..'9'])) ver
        _           -> ""
  }

ijones's avatar
ijones committed
531

532
533
534
535
536
537
ranlibProgram :: Program
ranlibProgram = simpleProgram "ranlib"

arProgram :: Program
arProgram = simpleProgram "ar"

ijones's avatar
ijones committed
538
hsc2hsProgram :: Program
539
540
541
542
543
544
545
hsc2hsProgram = (simpleProgram "hsc2hs") {
    programFindVersion = findProgramVersion "--version" $ \str ->
      -- Invoking "hsc2hs --version" gives a string like "hsc2hs version 0.66"
      case words str of
        (_:_:ver:_) -> ver
        _           -> ""
  }
ijones's avatar
ijones committed
546
547

c2hsProgram :: Program
548
549
550
c2hsProgram = (simpleProgram "c2hs") {
    programFindVersion = findProgramVersion "--numeric-version" id
  }
ijones's avatar
ijones committed
551
552

cpphsProgram :: Program
553
554
555
556
557
558
559
cpphsProgram = (simpleProgram "cpphs") {
    programFindVersion = findProgramVersion "--version" $ \str ->
      -- Invoking "cpphs --version" gives a string like "cpphs 1.3"
      case words str of
        (_:ver:_) -> ver
        _         -> ""
  }
ijones's avatar
ijones committed
560

Roberto Zunino's avatar
Roberto Zunino committed
561
hscolourProgram :: Program
562
hscolourProgram = (simpleProgram "hscolour") {
563
    programFindLocation = findProgramOnPath "HsColour",
564
565
566
567
568
569
    programFindVersion  = findProgramVersion "-version" $ \str ->
      -- Invoking "HsColour -version" gives a string like "HsColour 1.7"
      case words str of
        (_:ver:_) -> ver
        _         -> ""
  }
Roberto Zunino's avatar
Roberto Zunino committed
570

ijones's avatar
ijones committed
571
haddockProgram :: Program
572
573
574
575
576
577
578
579
haddockProgram = (simpleProgram "haddock") {
    programFindVersion = findProgramVersion "--version" $ \str ->
      -- Invoking "haddock --version" gives a string like
      -- "Haddock version 0.8, (c) Simon Marlow 2006"
      case words str of
        (_:_:ver:_) -> takeWhile (`elem` ('.':['0'..'9'])) ver
        _           -> ""
  }
ijones's avatar
ijones committed
580
581
582
583
584

greencardProgram :: Program
greencardProgram = simpleProgram "greencard"

ldProgram :: Program
585
ldProgram = simpleProgram "ld"
586
587
588

tarProgram :: Program
tarProgram = simpleProgram "tar"
ijones's avatar
ijones committed
589
590
591
592
593
594

cppProgram :: Program
cppProgram = simpleProgram "cpp"

pfesetupProgram :: Program
pfesetupProgram = simpleProgram "pfesetup"