Package.hs 24.5 KB
Newer Older
md9ms's avatar
md9ms committed
1
{-# OPTIONS -cpp #-}
2
3
4
5
6
7
8
9
10
-----------------------------------------------------------------------------
-- |
-- Module      :  Distribution.Package
-- Copyright   :  Isaac Jones 2003-2004
-- 
-- Maintainer  :  Isaac Jones <ijones@syntaxpolice.org>
-- Stability   :  alpha
-- Portability :  
--
ijones's avatar
ijones committed
11
-- Explanation: Package description and parsing
12

ijones's avatar
ijones committed
13
{- All rights reserved.
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

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:

    * Redistributions of source code must retain the above copyright
      notice, this list of conditions and the following disclaimer.

    * Redistributions in binary form must reproduce the above
      copyright notice, this list of conditions and the following
      disclaimer in the documentation and/or other materials provided
      with the distribution.

    * Neither the name of Isaac Jones nor the names of other
      contributors may be used to endorse or promote products derived
      from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -}

43
module Distribution.Package (
simonmar's avatar
simonmar committed
44
45
	PackageIdentifier(..), 
	showPackageId,
46
	PackageDescription(..),
47
48
49
50
	emptyPackageDescription,
        readPackageDescription,
        writePackageDescription,
        hasLibs,
md9ms's avatar
md9ms committed
51
        BuildInfo(..),
ka2_mail's avatar
ka2_mail committed
52
        emptyBuildInfo,
53
        Executable(..),
54
        emptyExecutable,
ijones's avatar
ijones committed
55
#ifdef DEBUG
56
        hunitTests,
ijones's avatar
ijones committed
57
        test
ijones's avatar
ijones committed
58
#endif
59
  ) where
60

61
import Control.Monad(foldM, liftM)
62
import Control.Exception(bracket)
63
import Data.Char
md9ms's avatar
md9ms committed
64
import Data.List(isPrefixOf)
65
import Data.Maybe(fromMaybe)
ijones's avatar
ijones committed
66

67
import Distribution.Version(Version(..), VersionRange(..),
68
69
                            showVersion, parseVersion, 
                            showVersionRange, parseVersionRange)
md9ms's avatar
md9ms committed
70
import Distribution.Misc(License(..), Dependency(..), Extension(..))
md9ms's avatar
md9ms committed
71
import Distribution.Setup(CompilerFlavor(..))
72

73
import System.IO(openFile, IOMode(..), hGetContents, hClose, hPutStrLn)
74

75
import Compat.H98
76
import Compat.ReadP
ijones's avatar
ijones committed
77

ijones's avatar
ijones committed
78
#ifdef DEBUG
ijones's avatar
ijones committed
79
import HUnit (Test(..), (~:), (~=?), assertEqual, assertBool, Assertion, runTestTT)
ijones's avatar
ijones committed
80
81
#endif

82
83
data PackageIdentifier
    = PackageIdentifier {pkgName::String, pkgVersion::Version}
simonmar's avatar
simonmar committed
84
      deriving (Read, Show, Eq)
85

simonmar's avatar
simonmar committed
86
showPackageId :: PackageIdentifier -> String
ijones's avatar
ijones committed
87
showPackageId (PackageIdentifier n (Version [] _)) = n -- if no version, don't show version.
simonmar's avatar
simonmar committed
88
89
90
showPackageId pkgid = 
  pkgName pkgid ++ '-': showVersion (pkgVersion pkgid)

91

92
93
94
95
96
97
98
99
100
101
-- | This data type is the internal representation of the file @pkg.descr@.
-- It contains two kinds of information about the package: information
-- which is needed for all packages, such as the package name and version, and 
-- information which is needed for the simple build system only, such as 
-- the compiler options and library name.
-- 
data PackageDescription
    =  PackageDescription {
	-- the following are required by all packages:
	package        :: PackageIdentifier,
102
        license        :: License,
103
104
105
        copyright      :: String,
        maintainer     :: String,
        stability      :: String,
106
        library        :: Maybe BuildInfo,
107
108
109
110
        executables    :: [Executable]
    }
    deriving (Show, Read, Eq)

111
112
113
114
115
116
117
118
119
120
emptyPackageDescription :: PackageDescription
emptyPackageDescription
    =  PackageDescription {package      = PackageIdentifier "" (Version [] []),
                      license      = AllRightsReserved,
                      copyright    = "",
                      maintainer   = "",
                      stability    = "",
                      library      = Nothing,
                      executables  = []
                     }
ijones's avatar
ijones committed
121

122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
-- |Set the name for this package. Convenience function.
setPkgName :: String -> PackageDescription -> PackageDescription
setPkgName n desc@PackageDescription{package=pkgIdent}
    = desc{package=pkgIdent{pkgName=n}}

-- |Set the version for this package. Convenience function.
setPkgVersion :: Version -> PackageDescription -> PackageDescription
setPkgVersion v desc@PackageDescription{package=pkgIdent}
    = desc{package=pkgIdent{pkgVersion=v}}

-- |does this package have any libraries?
hasLibs :: PackageDescription -> Bool
hasLibs p = case library p of
            Just l  -> if null (cSources l) && null (modules l)
                       then False else True
            Nothing -> False

            
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
data BuildInfo = BuildInfo {
        buildDepends    :: [Dependency],
        modules         :: [String],
	exposedModules  :: [String],
        cSources        :: [FilePath],
        hsSourceDir     :: FilePath,
        extensions      :: [Extension],
        extraLibs       :: [String],
        includeDirs     :: [FilePath],
        includes        :: [FilePath],
        options         :: [(CompilerFlavor,[String])]
    }
    deriving (Show,Read,Eq)

emptyBuildInfo :: BuildInfo
emptyBuildInfo = BuildInfo {
                      buildDepends   = [],
                      modules        = [],
		      exposedModules = [], -- Only used for libs
		      cSources       = [],
		      hsSourceDir    = ".", -- FIX: FileUtils.currentDir
                      extensions     = [],
                      extraLibs      = [],
                      includeDirs    = [],
                      includes       = [],
                      options        = []
                     }
167
                     
168
169
170
171
-- |Add options for a specific compiler. Convenience function.
setOptions :: CompilerFlavor -> [String] -> BuildInfo -> BuildInfo
setOptions c xs desc@BuildInfo{options=opts}
    = desc{options=(c,xs):opts}
ijones's avatar
ijones committed
172

ijones's avatar
ijones committed
173

174
175
176
177
178
179
180
181
182
183
184
185
186
data Executable = Executable {
        exeName    :: String,
        modulePath :: FilePath,
        buildInfo  :: BuildInfo
    }
    deriving (Show, Read, Eq)

emptyExecutable :: Executable
emptyExecutable = Executable {
                      exeName = "",
                      modulePath = "",
                      buildInfo = emptyBuildInfo
                     }
ijones's avatar
ijones committed
187

ijones's avatar
ijones committed
188
-- ------------------------------------------------------------
189
-- * Parsing & Pretty printing
ijones's avatar
ijones committed
190
191
-- ------------------------------------------------------------

192
193
194
195
196
type LineNo = Int

data PError = AmbigousParse String LineNo
            | NoParse String LineNo
            | FromString String (Maybe LineNo)
md9ms's avatar
md9ms committed
197
198
199
        deriving Show

instance Error PError where
200
        strMsg s = FromString s Nothing
md9ms's avatar
md9ms committed
201

md9ms's avatar
md9ms committed
202
showError :: PError -> String
203
204
205
206
207
208
209
showError (AmbigousParse f n)     = "Line "++show n++": Ambigous parse in field '"++f++"'"
showError (NoParse f n)           = "Line "++show n++": Parse of field '"++f++"' failed"
showError (FromString s (Just n)) = "Line "++show n++": " ++ s
showError (FromString s Nothing)  = s

myError :: LineNo -> String -> Either PError a
myError n s = Left $ FromString s (Just n)
md9ms's avatar
md9ms committed
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
data Field a 
  = Field 
      { fieldName     :: String
      , fieldGet      :: a -> String
      , fieldSet      :: LineNo -> String -> a -> Either PError a
      }

basicStanzaFields :: [Field PackageDescription]
basicStanzaFields =
 [ simpleField "name"
                           id                     parsePackageName
                           (pkgName . package)    (\name pkg -> pkg{package=(package pkg){pkgName=name}})
 , simpleField "version"
                           showVersion            parseVersion 
                           (pkgVersion . package) (\ver pkg -> pkg{package=(package pkg){pkgVersion=ver}})
 , licenseField "license" False
                           license                (\l pkg -> pkg{license=l})
 , licenseField "license-file" True
                           license                (\l pkg -> pkg{license=l})
 , simpleField "copyright"
                           id                     (munch (const True))
                           copyright              (\val pkg -> pkg{copyright=val})
 , simpleField "maintainer"
                           id                     (munch (const True))
                           maintainer             (\val pkg -> pkg{maintainer=val})
 , simpleField "stability"
                           id                     (munch (const True))
                           stability              (\val pkg -> pkg{stability=val})
 ]

executableStanzaFields :: [Field Executable]
executableStanzaFields =
 [ simpleField "executable"
                           id                 (munch (const True))
                           exeName            (\xs    exe -> exe{exeName=xs})
 , simpleField "main-is"
                           id                 parseFilePath
                           modulePath         (\xs    exe -> exe{modulePath=xs})
 ]

binfoFields :: [Field BuildInfo]
binfoFields =
 [ listField   "build-depends"   
                           showDependency     parseDependency
                           buildDepends       (\xs    binfo -> binfo{buildDepends=xs})
 , listField   "modules"         
                           id                 parseModuleName
                           modules            (\xs    binfo -> binfo{modules=xs})
 , listField   "exposed-modules"
                           id                 parseModuleName
                           exposedModules     (\xs    binfo -> binfo{exposedModules=xs})
 , listField   "c-sources"
                           id                 parseFilePath
                           cSources           (\paths binfo -> binfo{cSources=paths})
 , listField   "extensions"
                           show               parseExtension
                           extensions         (\exts  binfo -> binfo{extensions=exts})
 , listField   "extra-libs"
                           id                 parseLibName
                           extraLibs          (\xs    binfo -> binfo{extraLibs=xs})
 , listField   "includes"
                           id                 parseFilePath
                           includes           (\paths binfo -> binfo{includes=paths})
 , simpleField "hs-source-dir"
                           id                 parseFilePath
                           hsSourceDir        (\path  binfo -> binfo{hsSourceDir=path})
 , optsField   "options-ghc"  GHC
                           options            (\path  binfo -> binfo{options=path})
 , optsField   "options-hugs" Hugs
                           options            (\path  binfo -> binfo{options=path})
 , optsField   "options-nhc"  NHC
                           options            (\path  binfo -> binfo{options=path})
 ]

simpleField :: String -> (a -> String) -> (ReadP a a) -> (b -> a) -> (a -> b -> b) -> Field b
simpleField name showF readF get set = Field name
   (\st -> name ++ ": " ++ showF (get st))
   (\lineNo val st -> do
       x <- runP lineNo name readF val
       return (set x st))

listField :: String -> (a -> String) -> (ReadP [a] a) -> (b -> [a]) -> ([a] -> b -> b) -> Field b
listField name showF readF get set = Field name
   (\st -> case get st of
        [] -> ""
        (value:values) ->
           init (unlines ((name ++ ": " ++ showF value) : 
                          map (\val -> (replicate (length name) ' '++", "++showF val)) values)))
   (\lineNo val st -> do
       xs <- runP lineNo name (parseCommaList readF) val
       return (set xs st))

licenseField :: String -> Bool -> (b -> License) -> (License -> b -> b) -> Field b
licenseField name flag get set = Field name
   (\st -> case get st of
             OtherLicense path | flag      -> name ++ ": " ++ path
                               | otherwise -> ""
             license           | not flag  -> name ++ ": " ++ show license
                               | otherwise -> "")
   (\lineNo val st ->
       if flag 
         then do 
            path <- runP lineNo name parseFilePath val
            return (set (OtherLicense path) st)
         else do
            x <- runP lineNo name parseLicense val
            return (set x st))

optsField :: String -> CompilerFlavor -> (b -> [(CompilerFlavor,[String])]) -> ([(CompilerFlavor,[String])] -> b -> b) -> Field b
optsField name flavor get set = Field name
   (\st -> case lookup flavor (get st) of
        Just args -> name++": "++unwords args
        Nothing   -> "")
   (\lineNo val st -> 
       let
         old_val  = get st
         old_args = case lookup flavor old_val of
                       Just args -> args
                       Nothing   -> []
         val'     = filter (\(f,_) -> f/=flavor) old_val
       in return (set ((flavor,words val++old_args) : val') st))

-- --------------------------------------------
-- ** Parsing

-- |Parse the given package file.
readPackageDescription :: FilePath -> IO PackageDescription
readPackageDescription p = do 
  h <- openFile p ReadMode
  str <- hGetContents h
  case parseDescription str of
    Left  e -> error (showError e) -- FIXME
    Right PackageDescription{library=Nothing, executables=[]} -> error "no library listed, and no executable stanza."
    Right x -> return x

md9ms's avatar
md9ms committed
346
parseDescription :: String -> Either PError PackageDescription
347
parseDescription inp = do let (st:sts) = splitStanzas inp
348
                          pkg <- foldM (parseBasicStanza basicStanzaFields) emptyPackageDescription st
349
350
351
                          exes <- mapM parseExecutableStanza sts
                          return pkg{executables=exes}
  where -- The basic stanza, with library building info
352
353
354
355
356
357
358
359
360
        parseBasicStanza ((Field name get set):fields) pkg (lineNo, f, val)
          | name == f = set lineNo val pkg
          | otherwise = parseBasicStanza fields pkg (lineNo, f, val)
        parseBasicStanza [] pkg (lineNo, f, val) = do
          let lib = fromMaybe emptyBuildInfo (library pkg)
	  lib' <- parseBInfoField binfoFields lib (lineNo, f, val)
          return pkg{library=Just lib'}

        parseExecutableStanza st@((lineNo, f@"executable",eName):st1) =
361
          case lookupField "main-is" st of
362
363
364
365
	    Just (lineNo,val) -> foldM (parseExecutableField executableStanzaFields) emptyExecutable st
	    Nothing           -> fail $ "No 'Main-Is' field found for " ++ eName ++ " stanza"
        parseExecutableStanza ((lineNo, f,_):_) = 
          myError lineNo $ "'Executable' stanza starting with field '" ++ f ++ "'"
md9ms's avatar
md9ms committed
366
        parseExecutableStanza _ = error "This shouldn't happen!"
367
368
369
370
371
372
373
374
375
376
377
378
379

        parseExecutableField ((Field name get set):fields) exe (lineNo, f, val)
	  | name == f = set lineNo val exe
	  | otherwise = parseExecutableField fields exe (lineNo, f, val)
	parseExecutableField [] exe (lineNo, f, val) = do
	  binfo <- parseBInfoField binfoFields (buildInfo exe) (lineNo, f, val)
          return exe{buildInfo=binfo}

        parseBInfoField ((Field name get set):fields) binfo (lineNo, f, val)
	  | name == f = set lineNo val binfo
	  | otherwise = parseBInfoField fields binfo (lineNo, f, val)
	parseBInfoField [] binfo (lineNo, f, val) =
	  myError lineNo $ "Unknown field '" ++ f ++ "'"
md9ms's avatar
md9ms committed
380
        -- ...
381
382
383
384
385
386
        lookupField :: String -> Stanza -> Maybe (LineNo,String)
        lookupField x [] = Nothing
        lookupField x ((n,f,v):st)
          | x == f      = Just (n,v)
          | otherwise   = lookupField x st

387

388
389
390
391
392
runP :: LineNo -> String -> ReadP a a -> String -> Either PError a
runP lineNo field p s =
  case [ x | (x,"") <- results ] of
    [a] -> Right a
    []  -> case [ x | (x,ys) <- results, all isSpace ys ] of
393
             [a] -> Right a
394
395
396
397
             []  -> Left (NoParse field lineNo)
             _   -> Left (AmbigousParse field lineNo)
    _   -> Left (AmbigousParse field lineNo)
  where results = readP_to_S p s
398

399
400
type Stanza = [(LineNo,String,String)]

401
402
403
-- |Split a string into blank line-separated stanzas of
-- "Field: value" groups
splitStanzas :: String -> [Stanza]
404
405
406
407
408
409
splitStanzas = map merge . groupStanzas . filter validLine . zip [1..] . lines
  where validLine (_,s) = case dropWhile isSpace s of
                            '-':'-':_ -> False      -- Comment
                            _         -> True
        allSpaces (_,xs) = all isSpace xs
        groupStanzas :: [(Int,String)] -> [[(Int,String)]]
410
        groupStanzas [] = []
411
412
413
        groupStanzas xs = let (ys,zs) = break allSpaces xs
                           in ys : groupStanzas (dropWhile allSpaces zs)
        merge ((n,x):(_,' ':s):ys) = case dropWhile isSpace s of
414
415
                                       ('.':s') -> merge ((n,x++"\n"++s'):ys)
                                       s'       -> merge ((n,x++"\n"++s'):ys)
416
417
418
419
420
        merge ((n,x):ys) = brk n x : merge ys
        merge []         = []
        brk n xs = case break (==':') xs of
                     (fld, ':':val) -> (n, map toLower fld, dropWhile isSpace val)
                     (fld, _)       -> error $ "Line "++show n++": Invalid syntax (no colon after field name)"
ijones's avatar
ijones committed
421

422
-- |parse a module name
423
parseModuleName :: ReadP r String
md9ms's avatar
md9ms committed
424
425
426
parseModuleName = do c <- satisfy isUpper
                     cs <- munch (\x -> isAlphaNum x || x `elem` "_'.")
                     return (c:cs)
427
428
429
430
431
432
433

parseFilePath :: ReadP r FilePath
parseFilePath = parseReadS <++ (munch1 (\x -> isAlphaNum x || x `elem` "-+/_."))

parseReadS :: Read a => ReadP r a
parseReadS = readS_to_P reads

434
435
436
437
438
parsePackageName :: ReadP r String
parsePackageName = do n <- satisfy isAlpha
                      name <- munch1 (\x -> isAlphaNum x || x `elem` "-")
                      return (n:name)

439
parseDependency :: ReadP r Dependency
440
parseDependency = do name <- parsePackageName
441
442
443
                     skipSpaces
                     ver <- parseVersionRange <++ return AnyVersion
                     skipSpaces
444
445
                     return $ Dependency name ver

446
parseLicense :: ReadP r License
447
parseLicense = parseReadS
ijones's avatar
ijones committed
448

449
parseExtension :: ReadP r Extension
md9ms's avatar
md9ms committed
450
parseExtension = parseReadS
md9ms's avatar
md9ms committed
451

md9ms's avatar
md9ms committed
452
453
parseLibName :: ReadP r String
parseLibName = munch1 (\x -> not (isSpace x) && x /= ',')
ijones's avatar
ijones committed
454

455
456
457
458
parseCommaList :: ReadP r a -- ^The parser for the stuff between commas
               -> ReadP r [a]
parseCommaList p = sepBy1 p separator
    where separator = skipSpaces >> char ',' >> skipSpaces
ijones's avatar
ijones committed
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

-- --------------------------------------------
-- ** Pretty printing

writePackageDescription :: FilePath -> PackageDescription -> IO ()
writePackageDescription fpath pkg = bracket (openFile fpath WriteMode) hClose $ \hFile -> do
  hPutFields hFile pkg basicStanzaFields
  case library pkg of
    Nothing  -> return ()
    Just lib -> hPutFields hFile lib binfoFields
  mapM_ (hPutExecutable hFile) (executables pkg)
  where
    hPutExecutable hFile exe = do
      hPutStrLn hFile ""
      hPutFields hFile exe executableStanzaFields
      hPutFields hFile (buildInfo exe) binfoFields

    hPutFields hFile pkg [] = return ()
    hPutFields hFile pkg ((Field name get set):flds)
      | value /= "" = do
           hPutStrLn hFile value
           hPutFields hFile pkg flds
      | otherwise = do
           hPutFields hFile pkg flds
      where
        value = get pkg
        
showDependency :: Dependency -> String
showDependency (Dependency name ver) = name ++ " " ++ showVersionRange ver

ijones's avatar
ijones committed
491
492
493
494
-- ------------------------------------------------------------
-- * Testing
-- ------------------------------------------------------------
#ifdef DEBUG
495
496
497
498
499
500
501
502
503
testPkgDesc = unlines [
        "-- Required",
        "Name: Cabal",
        "Version: 0.1.1.1.1-rain",
        "License: LGPL",
        "Copyright: Free Text String",
        "-- Optional - may be in source?",
        "Stability: Free Text String",
        "Build-Depends: haskell-src, HUnit>=1.0.0-rain",
504
505
        "Modules: Distribution.Package, Distribution.Version,",
        "         Distribution.Simple.GHCPackageConfig",
506
507
508
        "C-Sources: not/even/rain.c, such/small/hands",
        "HS-Source-Dir: src",
        "Exposed-Modules: Distribution.Void, Foo.Bar",
md9ms's avatar
md9ms committed
509
        "Extensions: OverlappingInstances, TypeSynonymInstances",
510
511
        "Extra-Libs: libfoo, bar, bang",
        "Include-Dirs: your/slightest, look/will",
md9ms's avatar
md9ms committed
512
        "Includes: /easily/unclose, /me, \"funky, path\\\\name\"",
513
        "Options-ghc: -fTH -fglasgow-exts",
514
515
516
517
        "Options-hugs: +TH",
        "",
        "-- Next is an executable",
        "Executable: somescript",
518
        "Main-is: SomeFile.hs",
519
520
521
        "Modules: Foo1, Util, Main",
        "HS-Source-Dir: scripts",
        "Extensions: OverlappingInstances"
522
        ]
ijones's avatar
ijones committed
523
524
525

testPkgDescAnswer = 
 PackageDescription {package = PackageIdentifier {pkgName = "Cabal",
ijones's avatar
ijones committed
526
                                                 pkgVersion = Version {versionBranch = [0,1,1,1,1],
md9ms's avatar
md9ms committed
527
                                                 versionTags = ["rain"]}},
ijones's avatar
ijones committed
528
                    license = LGPL,
ijones's avatar
ijones committed
529
                    copyright = "Free Text String",
ijones's avatar
ijones committed
530
                    maintainer = "",
ijones's avatar
ijones committed
531
                    stability = "Free Text String",
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547

                    library = Just $ BuildInfo {
                        buildDepends = [Dependency "haskell-src" AnyVersion,
                                        Dependency "HUnit"
                                         (UnionVersionRanges (ThisVersion (Version [1,0,0] ["rain"]))
                                          (LaterVersion (Version [1,0,0] ["rain"])))],

                        modules = ["Distribution.Package","Distribution.Version",
                                      "Distribution.Simple.GHCPackageConfig"],

                        cSources = ["not/even/rain.c", "such/small/hands"],
                        hsSourceDir = "src",
                        exposedModules = ["Distribution.Void", "Foo.Bar"],
                        extensions = [OverlappingInstances, TypeSynonymInstances],
                        extraLibs = ["libfoo", "bar", "bang"],
                        includeDirs = ["your/slightest", "look/will"],
md9ms's avatar
md9ms committed
548
                        includes = ["/easily/unclose", "/me", "funky, path\\name"],
549
550
                        -- Note reversed order:
                        options = [(Hugs,["+TH"]), (GHC,["-fTH","-fglasgow-exts"])]
551
                    },
552
553
                    executables = [Executable "somescript" "SomeFile.hs" (
                      emptyBuildInfo{
554
555
556
557
                        modules = ["Foo1","Util","Main"],
                        hsSourceDir = "scripts",
                        extensions = [OverlappingInstances]
                      })]
ijones's avatar
ijones committed
558
559
}

ijones's avatar
ijones committed
560
hunitTests :: [Test]
561
hunitTests = [
md9ms's avatar
md9ms committed
562
563
              TestLabel "license parsers" $ TestCase $
                 sequence_ [assertRight ("license " ++ show lVal) lVal
564
                                        (runP 1 "license" parseLicense (show lVal))
md9ms's avatar
md9ms committed
565
                           | lVal <- [GPL,LGPL,BSD3,BSD4]],
ijones's avatar
ijones committed
566
567
568
569

              TestLabel "Required fields" $ TestCase $
                 do assertRight "some fields"
                       emptyPackageDescription{package=(PackageIdentifier "foo"
md9ms's avatar
md9ms committed
570
                                                        (Version [0,0] ["asdf"]))}
ijones's avatar
ijones committed
571
572
573
574
                       (parseDescription "Name: foo\nVersion: 0.0-asdf")

                    assertRight "more fields foo"
                       emptyPackageDescription{package=(PackageIdentifier "foo"
md9ms's avatar
md9ms committed
575
                                                        (Version [0,0]["asdf"])),
ijones's avatar
ijones committed
576
577
578
579
580
                                               license=GPL}
                       (parseDescription "Name: foo\nVersion:0.0-asdf\nLicense: GPL")

                    assertRight "required fields for foo"
                       emptyPackageDescription{package=(PackageIdentifier "foo"
md9ms's avatar
md9ms committed
581
                                                        (Version [0,0]["asdf"])),
ijones's avatar
ijones committed
582
583
584
                                        license=GPL, copyright="2004 isaac jones"}
                       (parseDescription "Name: foo\nVersion:0.0-asdf\nCopyright: 2004 isaac jones\nLicense: GPL"),
                                          
585
             TestCase $ assertRight "no library" Nothing
586
                        (library `liftM` parseDescription "Name: foo\nVersion: 1\nLicense: GPL\nMaintainer: someone\n\nExecutable: script\nMain-is: SomeFile.hs\n"),
ijones's avatar
ijones committed
587
588
589
590

             TestLabel "Package description" $ TestCase $ 
                assertRight "entire package description" testPkgDescAnswer
                                                         (parseDescription testPkgDesc)
md9ms's avatar
md9ms committed
591

ijones's avatar
ijones committed
592
593
             ]

ijones's avatar
ijones committed
594

ijones's avatar
ijones committed
595
596
597
598
599
600
601
602
603
604
assertRight :: (Eq val) => String -> val -> (Either a val) -> Assertion
assertRight mes expected actual
    =  assertBool mes
           (case actual of
             (Right v) -> v == expected
             _         -> False)

isError (Left _) = True
isError _        = False

ijones's avatar
ijones committed
605
test = runTestTT (TestList hunitTests)
ijones's avatar
ijones committed
606
#endif