module Distribution.PackageDescription (
PackageDescription(..),
GenericPackageDescription(..),
finalizePackageDescription,
flattenPackageDescription,
emptyPackageDescription,
readPackageDescription,
writePackageDescription,
parsePackageDescription,
showPackageDescription,
BuildType(..),
Library(..),
withLib,
hasLibs,
libModules,
Executable(..),
withExe,
hasExes,
exeModules,
FieldDescr(..),
LineNo,
sanityCheckPackage,
BuildInfo(..),
emptyBuildInfo,
allBuildInfo,
HookedBuildInfo,
emptyHookedBuildInfo,
readHookedBuildInfo,
parseHookedBuildInfo,
writeHookedBuildInfo,
showHookedBuildInfo,
updatePackageDescription,
satisfyDependency,
ParseResult(..),
hcOptions,
autogenModuleName,
haddockName,
setupMessage,
cabalVersion,
#ifdef DEBUG
hunitTests,
test
#endif
) where
import Control.Monad(liftM, foldM, when, unless)
import Data.Char
import Data.Maybe(isNothing, isJust, catMaybes, listToMaybe, maybeToList)
import Data.List (nub, maximumBy, unfoldr, partition)
import Text.PrettyPrint.HughesPJ as Pretty
import System.Directory(doesFileExist)
import Distribution.ParseUtils
import Distribution.Package(PackageIdentifier(..),showPackageId,
parsePackageName)
import Distribution.Version(Version(..), VersionRange(..), withinRange,
showVersion, parseVersion, showVersionRange,
parseVersionRange, isAnyVersion)
import Distribution.License(License(..))
import Distribution.Version(Dependency(..))
import Distribution.Verbosity
import Distribution.Compiler(CompilerFlavor(..))
import Distribution.Configuration
import Distribution.Simple.Utils(currentDir, die, dieWithLocation, warn, notice)
import Language.Haskell.Extension(Extension(..))
import Distribution.Compat.ReadP as ReadP hiding (get)
import System.FilePath((<.>))
import Data.Monoid
#ifdef DEBUG
import Data.List ( sortBy )
import Test.HUnit (Test(..), assertBool, Assertion, runTestTT, Counts, assertEqual)
#endif
cabalVersion :: Version
#ifdef CABAL_VERSION
cabalVersion = Version [CABAL_VERSION] []
#else
cabalVersion = error "Cabal was not bootstrapped correctly"
#endif
data PackageDescription
= PackageDescription {
package :: PackageIdentifier,
license :: License,
licenseFile :: FilePath,
copyright :: String,
maintainer :: String,
author :: String,
stability :: String,
testedWith :: [(CompilerFlavor,VersionRange)],
homepage :: String,
pkgUrl :: String,
synopsis :: String,
description :: String,
category :: String,
buildDepends :: [Dependency],
descCabalVersion :: VersionRange,
buildType :: BuildType,
library :: Maybe Library,
executables :: [Executable],
dataFiles :: [FilePath],
extraSrcFiles :: [FilePath],
extraTmpFiles :: [FilePath]
}
deriving (Show, Read, Eq)
emptyPackageDescription :: PackageDescription
emptyPackageDescription
= PackageDescription {package = PackageIdentifier "" (Version [] []),
license = AllRightsReserved,
licenseFile = "",
descCabalVersion = AnyVersion,
buildType = Custom,
copyright = "",
maintainer = "",
author = "",
stability = "",
testedWith = [],
buildDepends = [],
homepage = "",
pkgUrl = "",
synopsis = "",
description = "",
category = "",
library = Nothing,
executables = [],
dataFiles = [],
extraSrcFiles = [],
extraTmpFiles = []
}
data GenericPackageDescription =
GenericPackageDescription {
packageDescription :: PackageDescription,
genPackageFlags :: [Flag],
condLibrary :: Maybe (CondTree ConfVar [Dependency] Library),
condExecutables :: [(String, CondTree ConfVar [Dependency] Executable)]
}
instance Show GenericPackageDescription where
show (GenericPackageDescription pkg flgs mlib exes) =
showPackageDescription pkg ++ "\n" ++
(render $ vcat $ map ppFlag flgs) ++ "\n" ++
render (maybe empty (\l -> showStanza "Library" (ppCondTree l showDeps)) mlib)
++ "\n" ++
(render $ vcat $
map (\(n,ct) -> showStanza ("Executable " ++ n) (ppCondTree ct showDeps)) exes)
where
ppFlag (MkFlag name desc dflt) =
showStanza ("Flag " ++ name)
((if (null desc) then empty else
text ("Description: " ++ desc)) $+$
text ("Default: " ++ show dflt))
showDeps = fsep . punctuate comma . map showDependency
showStanza h b = text h <+> lbrace $+$ nest 2 b $+$ rbrace
data PDTagged = Lib Library | Exe String Executable | PDNull
instance Monoid PDTagged where
mempty = PDNull
PDNull `mappend` x = x
x `mappend` PDNull = x
Lib l `mappend` Lib l' = Lib (l `mappend` l')
Exe n e `mappend` Exe n' e' | n == n' = Exe n (e `mappend` e')
_ `mappend` _ = bug "Cannot combine incompatible tags"
finalizePackageDescription
:: [(String,Bool)]
-> Maybe [PackageIdentifier]
-> String
-> String
-> (String, Version)
-> GenericPackageDescription
-> Either [Dependency]
(PackageDescription, [(String,Bool)])
finalizePackageDescription userflags mpkgs os arch impl
(GenericPackageDescription pkg flags mlib0 exes0) =
case resolveFlags of
Right ((mlib, exes'), deps, flagVals) ->
Right ( pkg { library = mlib
, executables = exes'
, buildDepends = nub deps
}
, flagVals )
Left missing -> Left $ nub missing
where
condTrees = maybeToList (fmap (mapTreeData Lib) mlib0 )
++ map (\(name,tree) -> mapTreeData (Exe name) tree) exes0
untagRslts = foldr untag (Nothing, [])
where
untag (Lib _) (Just _, _) = bug "Only one library expected"
untag (Lib l) (Nothing, exes) = (Just l, exes)
untag (Exe n e) (mlib, exes)
| any ((== n) . fst) exes = bug "Exe with same name found"
| otherwise = (mlib, exes ++ [(n, e)])
untag PDNull x = x
resolveFlags =
case resolveWithFlags flagChoices os arch impl condTrees check of
Right (as, ds, fs) ->
let (mlib, exes) = untagRslts as in
Right ( (fmap libFillInDefaults mlib,
map (\(n,e) -> (exeFillInDefaults e) { exeName = n }) exes),
ds, fs)
Left missing -> Left missing
flagChoices = map (\(MkFlag n _ d) -> (n, d2c n d)) flags
d2c n b = maybe [b, not b] (\x -> [x]) $ lookup n userflags
check ds = if all satisfyDep ds
then DepOk
else MissingDeps $ filter (not . satisfyDep) ds
satisfyDep = maybe (const True)
(\pkgs -> isJust . satisfyDependency pkgs)
mpkgs
flattenPackageDescription :: GenericPackageDescription -> PackageDescription
flattenPackageDescription (GenericPackageDescription pkg _ mlib0 exes0) =
pkg { library = mlib
, executables = reverse exes
, buildDepends = nub $ ldeps ++ reverse edeps
}
where
(mlib, ldeps) = case mlib0 of
Just lib -> let (l,ds) = ignoreConditions lib in
(Just (libFillInDefaults l), ds)
Nothing -> (Nothing, [])
(exes, edeps) = foldr flattenExe ([],[]) exes0
flattenExe (n, t) (es, ds) =
let (e, ds') = ignoreConditions t in
( (exeFillInDefaults $ e { exeName = n }) : es, ds' ++ ds )
data BuildType
= Simple
| Configure
| Make
| Custom
deriving (Show, Read, Eq)
reqNameName :: String
reqNameName = "name"
reqNameVersion :: String
reqNameVersion = "version"
reqNameCopyright :: String
reqNameCopyright = "copyright"
reqNameMaintainer :: String
reqNameMaintainer = "maintainer"
reqNameSynopsis :: String
reqNameSynopsis = "synopsis"
pkgDescrFieldDescrs :: [FieldDescr PackageDescription]
pkgDescrFieldDescrs =
[ simpleField reqNameName
text parsePackageName
(pkgName . package) (\name pkg -> pkg{package=(package pkg){pkgName=name}})
, simpleField reqNameVersion
(text . showVersion) parseVersion
(pkgVersion . package) (\ver pkg -> pkg{package=(package pkg){pkgVersion=ver}})
, simpleField "cabal-version"
(text . showVersionRange) parseVersionRange
descCabalVersion (\v pkg -> pkg{descCabalVersion=v})
, simpleField "build-type"
(text . show) parseReadSQ
buildType (\t pkg -> pkg{buildType=t})
, simpleField "license"
(text . show) parseLicenseQ
license (\l pkg -> pkg{license=l})
, simpleField "license-file"
showFilePath parseFilePathQ
licenseFile (\l pkg -> pkg{licenseFile=l})
, simpleField reqNameCopyright
showFreeText (munch (const True))
copyright (\val pkg -> pkg{copyright=val})
, simpleField reqNameMaintainer
showFreeText (munch (const True))
maintainer (\val pkg -> pkg{maintainer=val})
, commaListField "build-depends"
showDependency parseDependency
buildDepends (\xs pkg -> pkg{buildDepends=xs})
, simpleField "stability"
showFreeText (munch (const True))
stability (\val pkg -> pkg{stability=val})
, simpleField "homepage"
showFreeText (munch (const True))
homepage (\val pkg -> pkg{homepage=val})
, simpleField "package-url"
showFreeText (munch (const True))
pkgUrl (\val pkg -> pkg{pkgUrl=val})
, simpleField reqNameSynopsis
showFreeText (munch (const True))
synopsis (\val pkg -> pkg{synopsis=val})
, simpleField "description"
showFreeText (munch (const True))
description (\val pkg -> pkg{description=val})
, simpleField "category"
showFreeText (munch (const True))
category (\val pkg -> pkg{category=val})
, simpleField "author"
showFreeText (munch (const True))
author (\val pkg -> pkg{author=val})
, listField "tested-with"
showTestedWith parseTestedWithQ
testedWith (\val pkg -> pkg{testedWith=val})
, listField "data-files"
showFilePath parseFilePathQ
dataFiles (\val pkg -> pkg{dataFiles=val})
, listField "extra-source-files"
showFilePath parseFilePathQ
extraSrcFiles (\val pkg -> pkg{extraSrcFiles=val})
, listField "extra-tmp-files"
showFilePath parseFilePathQ
extraTmpFiles (\val pkg -> pkg{extraTmpFiles=val})
]
data Library = Library {
exposedModules :: [String],
libBuildInfo :: BuildInfo
}
deriving (Show, Eq, Read)
instance Monoid Library where
mempty = nullLibrary
mappend = unionLibrary
emptyLibrary :: Library
emptyLibrary = Library [] emptyBuildInfo
nullLibrary :: Library
nullLibrary = Library [] nullBuildInfo
hasLibs :: PackageDescription -> Bool
hasLibs p = maybe False (buildable . libBuildInfo) (library p)
maybeHasLibs :: PackageDescription -> Maybe Library
maybeHasLibs p =
library p >>= (\lib -> toMaybe (buildable (libBuildInfo lib)) lib)
withLib :: PackageDescription -> a -> (Library -> IO a) -> IO a
withLib pkg_descr a f =
maybe (return a) f (maybeHasLibs pkg_descr)
libModules :: PackageDescription -> [String]
libModules PackageDescription{library=lib}
= maybe [] exposedModules lib
++ maybe [] (otherModules . libBuildInfo) lib
libFieldDescrs :: [FieldDescr Library]
libFieldDescrs = map biToLib binfoFieldDescrs
++ [
listField "exposed-modules" text parseModuleNameQ
exposedModules (\mods lib -> lib{exposedModules=mods})
]
where biToLib = liftField libBuildInfo (\bi lib -> lib{libBuildInfo=bi})
unionLibrary :: Library -> Library -> Library
unionLibrary l1 l2 =
l1 { exposedModules = combine exposedModules
, libBuildInfo = unionBuildInfo (libBuildInfo l1) (libBuildInfo l2)
}
where combine f = f l1 ++ f l2
libFillInDefaults :: Library -> Library
libFillInDefaults lib@(Library { libBuildInfo = bi }) =
lib { libBuildInfo = biFillInDefaults bi }
data Executable = Executable {
exeName :: String,
modulePath :: FilePath,
buildInfo :: BuildInfo
}
deriving (Show, Read, Eq)
instance Monoid Executable where
mempty = nullExecutable
mappend = unionExecutable
emptyExecutable :: Executable
emptyExecutable = Executable {
exeName = "",
modulePath = "",
buildInfo = emptyBuildInfo
}
nullExecutable :: Executable
nullExecutable = emptyExecutable { buildInfo = nullBuildInfo }
exeFillInDefaults :: Executable -> Executable
exeFillInDefaults exe@(Executable { buildInfo = bi }) =
exe { buildInfo = biFillInDefaults bi }
hasExes :: PackageDescription -> Bool
hasExes p = any (buildable . buildInfo) (executables p)
withExe :: PackageDescription -> (Executable -> IO a) -> IO ()
withExe pkg_descr f =
sequence_ [f exe | exe <- executables pkg_descr, buildable (buildInfo exe)]
exeModules :: PackageDescription -> [String]
exeModules PackageDescription{executables=execs}
= concatMap (otherModules . buildInfo) execs
executableFieldDescrs :: [FieldDescr Executable]
executableFieldDescrs =
[
simpleField "executable"
showToken parseTokenQ
exeName (\xs exe -> exe{exeName=xs})
, simpleField "main-is"
showFilePath parseFilePathQ
modulePath (\xs exe -> exe{modulePath=xs})
]
++ map biToExe binfoFieldDescrs
where biToExe = liftField buildInfo (\bi exe -> exe{buildInfo=bi})
unionExecutable :: Executable -> Executable -> Executable
unionExecutable e1 e2 =
e1 { exeName = combine exeName
, modulePath = combine modulePath
, buildInfo = unionBuildInfo (buildInfo e1) (buildInfo e2)
}
where combine f = case (f e1, f e2) of
("","") -> ""
("", x) -> x
(x, "") -> x
(x, y) -> error $ "Ambiguous values for executable field: '"
++ x ++ "' and '" ++ y ++ "'"
data BuildInfo = BuildInfo {
buildable :: Bool,
buildTools :: [Dependency],
cppOptions :: [String],
ccOptions :: [String],
ldOptions :: [String],
pkgconfigDepends :: [Dependency],
frameworks :: [String],
cSources :: [FilePath],
hsSourceDirs :: [FilePath],
otherModules :: [String],
extensions :: [Extension],
extraLibs :: [String],
extraLibDirs :: [String],
includeDirs :: [FilePath],
includes :: [FilePath],
installIncludes :: [FilePath],
options :: [(CompilerFlavor,[String])],
ghcProfOptions :: [String],
ghcSharedOptions :: [String]
}
deriving (Show,Read,Eq)
nullBuildInfo :: BuildInfo
nullBuildInfo = BuildInfo {
buildable = True,
buildTools = [],
cppOptions = [],
ccOptions = [],
ldOptions = [],
pkgconfigDepends = [],
frameworks = [],
cSources = [],
hsSourceDirs = [],
otherModules = [],
extensions = [],
extraLibs = [],
extraLibDirs = [],
includeDirs = [],
includes = [],
installIncludes = [],
options = [],
ghcProfOptions = [],
ghcSharedOptions = []
}
emptyBuildInfo :: BuildInfo
emptyBuildInfo = nullBuildInfo { hsSourceDirs = [currentDir] }
allBuildInfo :: PackageDescription -> [BuildInfo]
allBuildInfo pkg_descr = [ bi | Just lib <- [library pkg_descr]
, let bi = libBuildInfo lib
, buildable bi ]
++ [ bi | exe <- executables pkg_descr
, let bi = buildInfo exe
, buildable bi ]
biFillInDefaults :: BuildInfo -> BuildInfo
biFillInDefaults bi =
if null (hsSourceDirs bi)
then bi { hsSourceDirs = [currentDir] }
else bi
type HookedBuildInfo = (Maybe BuildInfo, [(String, BuildInfo)])
emptyHookedBuildInfo :: HookedBuildInfo
emptyHookedBuildInfo = (Nothing, [])
binfoFieldDescrs :: [FieldDescr BuildInfo]
binfoFieldDescrs =
[ simpleField "buildable"
(text . show) parseReadS
buildable (\val binfo -> binfo{buildable=val})
, commaListField "build-tools"
showDependency parseDependency
buildTools (\xs binfo -> binfo{buildTools=xs})
, listField "cpp-options"
showToken parseTokenQ
cppOptions (\val binfo -> binfo{cppOptions=val})
, listField "cc-options"
showToken parseTokenQ
ccOptions (\val binfo -> binfo{ccOptions=val})
, listField "ld-options"
showToken parseTokenQ
ldOptions (\val binfo -> binfo{ldOptions=val})
, commaListField "pkgconfig-depends"
showDependency parsePkgconfigDependency
pkgconfigDepends (\xs binfo -> binfo{pkgconfigDepends=xs})
, listField "frameworks"
showToken parseTokenQ
frameworks (\val binfo -> binfo{frameworks=val})
, listField "c-sources"
showFilePath parseFilePathQ
cSources (\paths binfo -> binfo{cSources=paths})
, listField "extensions"
(text . show) parseExtensionQ
extensions (\exts binfo -> binfo{extensions=exts})
, listField "extra-libraries"
showToken parseTokenQ
extraLibs (\xs binfo -> binfo{extraLibs=xs})
, listField "extra-lib-dirs"
showFilePath parseFilePathQ
extraLibDirs (\xs binfo -> binfo{extraLibDirs=xs})
, listField "includes"
showFilePath parseFilePathQ
includes (\paths binfo -> binfo{includes=paths})
, listField "install-includes"
showFilePath parseFilePathQ
installIncludes (\paths binfo -> binfo{installIncludes=paths})
, listField "include-dirs"
showFilePath parseFilePathQ
includeDirs (\paths binfo -> binfo{includeDirs=paths})
, listField "hs-source-dirs"
showFilePath parseFilePathQ
hsSourceDirs (\paths binfo -> binfo{hsSourceDirs=paths})
, listField "other-modules"
text parseModuleNameQ
otherModules (\val binfo -> binfo{otherModules=val})
, listField "ghc-prof-options"
text parseTokenQ
ghcProfOptions (\val binfo -> binfo{ghcProfOptions=val})
, listField "ghc-shared-options"
text parseTokenQ
ghcProfOptions (\val binfo -> binfo{ghcSharedOptions=val})
, optsField "ghc-options" GHC
options (\path binfo -> binfo{options=path})
, optsField "hugs-options" Hugs
options (\path binfo -> binfo{options=path})
, optsField "nhc98-options" NHC
options (\path binfo -> binfo{options=path})
, optsField "jhc-options" JHC
options (\path binfo -> binfo{options=path})
]
flagFieldDescrs :: [FieldDescr Flag]
flagFieldDescrs =
[ simpleField "description"
showFreeText (munch (const True))
flagDescription (\val fl -> fl{ flagDescription = val })
, simpleField "default"
(text . show) parseReadS
flagDefault (\val fl -> fl{ flagDefault = val })
]
satisfyDependency :: [PackageIdentifier] -> Dependency
-> Maybe PackageIdentifier
satisfyDependency pkgs (Dependency pkgname vrange) =
case filter ok pkgs of
[] -> Nothing
qs -> Just (maximumBy versions qs)
where
ok p = pkgName p == pkgname && pkgVersion p `withinRange` vrange
versions a b = pkgVersion a `compare` pkgVersion b
updatePackageDescription :: HookedBuildInfo -> PackageDescription -> PackageDescription
updatePackageDescription (mb_lib_bi, exe_bi) p
= p{ executables = updateExecutables exe_bi (executables p)
, library = updateLibrary mb_lib_bi (library p)
}
where
updateLibrary :: Maybe BuildInfo -> Maybe Library -> Maybe Library
updateLibrary (Just bi) (Just lib) = Just (lib{libBuildInfo = unionBuildInfo bi (libBuildInfo lib)})
updateLibrary Nothing mb_lib = mb_lib
updateLibrary (Just bi) Nothing = Just emptyLibrary{libBuildInfo=bi}
updateExecutables :: [(String, BuildInfo)]
-> [Executable]
-> [Executable]
updateExecutables exe_bi' executables' = foldr updateExecutable executables' exe_bi'
updateExecutable :: (String, BuildInfo)
-> [Executable]
-> [Executable]
updateExecutable _ [] = []
updateExecutable exe_bi'@(name,bi) (exe:exes)
| exeName exe == name = exe{buildInfo = unionBuildInfo bi (buildInfo exe)} : exes
| otherwise = exe : updateExecutable exe_bi' exes
unionBuildInfo :: BuildInfo -> BuildInfo -> BuildInfo
unionBuildInfo b1 b2
= b1{buildable = buildable b1 && buildable b2,
buildTools = combine buildTools,
cppOptions = combine cppOptions,
ccOptions = combine ccOptions,
ldOptions = combine ldOptions,
pkgconfigDepends = combine pkgconfigDepends,
frameworks = combine frameworks,
cSources = combine cSources,
hsSourceDirs = combine hsSourceDirs,
otherModules = combine otherModules,
extensions = combine extensions,
extraLibs = combine extraLibs,
extraLibDirs = combine extraLibDirs,
includeDirs = combine includeDirs,
includes = combine includes,
installIncludes = combine installIncludes,
options = combine options
}
where
combine :: (Eq a) => (BuildInfo -> [a]) -> [a]
combine f = nub $ f b1 ++ f b2
hcOptions :: CompilerFlavor -> [(CompilerFlavor, [String])] -> [String]
hcOptions hc hc_opts = [opt | (hc',opts) <- hc_opts, hc' == hc, opt <- opts]
autogenModuleName :: PackageDescription -> String
autogenModuleName pkg_descr =
"Paths_" ++ map fixchar (pkgName (package pkg_descr))
where fixchar '-' = '_'
fixchar c = c
haddockName :: PackageDescription -> FilePath
haddockName pkg_descr = pkgName (package pkg_descr) <.> "haddock"
setupMessage :: Verbosity -> String -> PackageDescription -> IO ()
setupMessage verbosity msg pkg_descr =
notice verbosity (msg ++ ' ':showPackageId (package pkg_descr) ++ "...")
readAndParseFile :: Verbosity -> (String -> ParseResult a) -> FilePath -> IO a
readAndParseFile verbosity parser fpath = do
exists <- doesFileExist fpath
when (not exists) (die $ "Error Parsing: file \"" ++ fpath ++ "\" doesn't exist. Cannot continue.")
str <- readFile fpath
case parser str of
ParseFailed e -> do
let (line, message) = locatedErrorMsg e
dieWithLocation fpath line message
ParseOk ws x -> do
mapM_ (warn verbosity) (reverse ws)
return x
readHookedBuildInfo :: Verbosity -> FilePath -> IO HookedBuildInfo
readHookedBuildInfo verbosity = readAndParseFile verbosity parseHookedBuildInfo
readPackageDescription :: Verbosity -> FilePath -> IO GenericPackageDescription
readPackageDescription verbosity =
readAndParseFile verbosity parsePackageDescription
stanzas :: [Field] -> [[Field]]
stanzas [] = []
stanzas (f:fields) = (f:this) : stanzas rest
where
(this, rest) = break isStanzaHeader fields
isStanzaHeader :: Field -> Bool
isStanzaHeader (F _ f _) = f == "executable"
isStanzaHeader _ = False
mapSimpleFields :: (Field -> ParseResult Field) -> [Field]
-> ParseResult [Field]
mapSimpleFields f fs = mapM walk fs
where
walk fld@(F _ _ _) = f fld
walk (IfBlock l c fs1 fs2) = do
fs1' <- mapM walk fs1
fs2' <- mapM walk fs2
return (IfBlock l c fs1' fs2')
walk (Section ln n l fs1) = do
fs1' <- mapM walk fs1
return (Section ln n l fs1')
constraintFieldNames :: [String]
constraintFieldNames = ["build-depends"]
parseConstraint :: Field -> ParseResult [Dependency]
parseConstraint (F l n v)
| n == "build-depends" = runP l n (parseCommaList parseDependency) v
parseConstraint f = bug $ "Constraint was expected (got: " ++ show f ++ ")"
libFieldNames :: [String]
libFieldNames = map fieldName libFieldDescrs
++ buildInfoNames ++ constraintFieldNames
buildInfoNames :: [String]
buildInfoNames = map fieldName binfoFieldDescrs
++ map fst deprecatedFieldsBuildInfo
newtype StT s m a = StT { runStT :: s -> m (a,s) }
instance Monad m => Monad (StT s m) where
return a = StT (\s -> return (a,s))
StT f >>= g = StT $ \s -> do
(a,s') <- f s
runStT (g a) s'
get :: Monad m => StT s m s
get = StT $ \s -> return (s, s)
modify :: Monad m => (s -> s) -> StT s m ()
modify f = StT $ \s -> return ((),f s)
lift :: Monad m => m a -> StT s m a
lift m = StT $ \s -> m >>= \a -> return (a,s)
evalStT :: Monad m => StT s m a -> s -> m a
evalStT st s = runStT st s >>= return . fst
type PM a = StT [Field] ParseResult a
peekField :: PM (Maybe Field)
peekField = get >>= return . listToMaybe
skipField :: PM ()
skipField = modify tail
parsePackageDescription :: String -> ParseResult GenericPackageDescription
parsePackageDescription file = do
let tabs = findIndentTabs file
fields0 <- readFields file `catchParseError` \err ->
case err of
TabsError tabLineNo -> reportTabsError
[ t | t@(lineNo',_) <- tabs
, lineNo' >= tabLineNo ]
_ -> parseFail err
let sf = sectionizeFields fields0
fields <- mapSimpleFields deprecField sf
flip evalStT fields $ do
hfs <- getHeader []
pkg <- lift $ parseFields pkgDescrFieldDescrs emptyPackageDescription hfs
(flags, mlib, exes) <- getBody
warnIfRest
when (not (oldSyntax fields0)) $
maybeWarnCabalVersion pkg
return (GenericPackageDescription pkg flags mlib exes)
where
oldSyntax flds = all isSimpleField flds
reportTabsError tabs =
syntaxError (fst (head tabs)) $
"Do not use tabs for indentation (use spaces instead)\n"
++ " Tabs were used at (line,column): " ++ show tabs
maybeWarnCabalVersion pkg =
when (pkgName (package pkg) /= "Cabal"
&& isAnyVersion (descCabalVersion pkg)) $
lift $ warning $
"A package using section syntax should require\n"
++ "\"Cabal-Version: >= 1.2\" or equivalent."
sectionizeFields fs
| oldSyntax fs =
let
(hdr0, exes0) = break ((=="executable") . fName) fs
(hdr, libfs0) = partition (not . (`elem` libFieldNames) . fName) hdr0
(deps, libfs) = partition ((== "build-depends") . fName)
libfs0
exes = unfoldr toExe exes0
toExe [] = Nothing
toExe (F l e n : r)
| e == "executable" =
let (efs, r') = break ((=="executable") . fName) r
in Just (Section l "executable" n (deps ++ efs), r')
toExe _ = bug "unexpeced input to 'toExe'"
in
hdr ++
(if null libfs then []
else [Section (lineNo (head libfs)) "library" "" (deps ++ libfs)])
++ exes
| otherwise = fs
isSimpleField (F _ _ _) = True
isSimpleField _ = False
warnIfRest :: PM ()
warnIfRest = do
s <- get
case s of
[] -> return ()
_ -> lift $ warning "Ignoring trailing declarations."
getHeader :: [Field] -> PM [Field]
getHeader acc = peekField >>= \mf -> case mf of
Just f@(F _ _ _) -> skipField >> getHeader (f:acc)
_ -> return (reverse acc)
getBody :: PM ([Flag]
,Maybe (CondTree ConfVar [Dependency] Library)
,[(String, CondTree ConfVar [Dependency] Executable)])
getBody = do
mf <- peekField
case mf of
Just (Section _ sn _label _fields)
| sn == "flag" -> do
flags <- getFlags []
(lib, exes) <- getLibOrExe
return (flags, lib, exes)
| otherwise -> do
(lib,exes) <- getLibOrExe
return ([], lib, exes)
Nothing -> do lift $ warning "No library or executable specified"
return ([], Nothing, [])
Just f -> lift $ syntaxError (lineNo f) $
"Construct not supported at this position: " ++ show f
getFlags :: [Flag] -> StT [Field] ParseResult [Flag]
getFlags acc = peekField >>= \mf -> case mf of
Just (Section _ sn sl fs)
| sn == "flag" -> do
fl <- lift $ parseFields
flagFieldDescrs
(MkFlag (map toLower sl) "" True)
fs
skipField >> getFlags (fl : acc)
_ -> return (reverse acc)
getLibOrExe :: PM (Maybe (CondTree ConfVar [Dependency] Library)
,[(String, CondTree ConfVar [Dependency] Executable)])
getLibOrExe = peekField >>= \mf -> case mf of
Just (Section n sn sl fs)
| sn == "executable" -> do
when (null sl) $ lift $
syntaxError n "'executable' needs one argument (the executable's name)"
exename <- lift $ runP n "executable" parseTokenQ sl
flds <- collectFields parseExeFields fs
skipField
(lib, exes) <- getLibOrExe
return (lib, exes ++ [(exename, flds)])
| sn == "library" -> do
when (not (null sl)) $ lift $
syntaxError n "'library' expects no argument"
flds <- collectFields parseLibFields fs
skipField
(lib, exes) <- getLibOrExe
return (maybe (Just flds)
(const (error "Multiple libraries specified"))
lib
, exes)
| otherwise -> do
lift $ warning $ "Unknown section type: " ++ sn ++ " ignoring..."
return (Nothing, [])
Just x -> lift $ syntaxError (lineNo x) $ "Section expected."
Nothing -> return (Nothing, [])
collectFields :: ([Field] -> PM a) -> [Field]
-> PM (CondTree ConfVar [Dependency] a)
collectFields parser allflds = do
unless (null subSects) $
lift $ warning $ "Unknown section types: " ++ show (map fName subSects)
++ "\n Probable cause: missing colon after field name, or newer Cabal version required"
a <- parser dataFlds
deps <- liftM concat . mapM (lift . parseConstraint) $ depFlds
ifs <- mapM processIfs condFlds
return (CondNode a deps ifs)
where
(depFlds, dataFlds) = partition isConstraint simplFlds
(simplFlds, cplxFlds) = partition isSimple allflds
(condFlds, subSects) = partition isCond cplxFlds
isSimple (F _ _ _) = True
isSimple _ = False
isCond (IfBlock _ _ _ _) = True
isCond _ = False
isConstraint (F _ n _) = n `elem` constraintFieldNames
isConstraint _ = False
processIfs (IfBlock l c t e) = do
cnd <- lift $ runP l "if" parseCondition c
t' <- collectFields parser t
e' <- case e of
[] -> return Nothing
es -> do fs <- collectFields parser es
return (Just fs)
return (cnd, t', e')
processIfs _ = bug "processIfs called with wrong field type"
parseLibFields :: [Field] -> StT s ParseResult Library
parseLibFields = lift . parseFields libFieldDescrs nullLibrary
parseExeFields :: [Field] -> StT s ParseResult Executable
parseExeFields = lift . parseFields executableFieldDescrs nullExecutable
parseFields :: [FieldDescr a] -> a -> [Field] -> ParseResult a
parseFields descrs ini fields =
do (a, unknowns) <- foldM (parseField descrs) (ini, []) fields
when (not (null unknowns)) $ do
warning $ render $
text "Unknown fields:" <+>
commaSep (map (\(l,u) -> u ++ " (line " ++ show l ++ ")")
(reverse unknowns))
$+$
text "Fields allowed in this section:" $$
nest 4 (commaSep $ map fieldName descrs)
return a
where
commaSep = fsep . punctuate comma . map text
parseField :: [FieldDescr a] -> (a,[(Int,String)]) -> Field -> ParseResult (a, [(Int,String)])
parseField ((FieldDescr name _ parse):fields) (a, us) (F line f val)
| name == f = parse line val a >>= \a' -> return (a',us)
| otherwise = parseField fields (a,us) (F line f val)
parseField [] (a,us) (F _ ('x':'-':_) _) = return (a, us)
parseField [] (a,us) (F l f _) = do
return (a, ((l,f):us))
parseField _ _ _ = error "'parseField' called on a non-field. This is a bug."
deprecatedFields :: [(String,String)]
deprecatedFields =
deprecatedFieldsPkgDescr ++ deprecatedFieldsBuildInfo
deprecatedFieldsPkgDescr :: [(String,String)]
deprecatedFieldsPkgDescr = [ ("other-files", "extra-source-files") ]
deprecatedFieldsBuildInfo :: [(String,String)]
deprecatedFieldsBuildInfo = [ ("hs-source-dir","hs-source-dirs") ]
deprecField :: Field -> ParseResult Field
deprecField (F line fld val) = do
fld' <- case lookup fld deprecatedFields of
Nothing -> return fld
Just newName -> do
warning $ "The field \"" ++ fld
++ "\" is deprecated, please use \"" ++ newName ++ "\""
return newName
return (F line fld' val)
deprecField _ = error "'deprecField' called on a non-field. This is a bug."
parseHookedBuildInfo :: String -> ParseResult HookedBuildInfo
parseHookedBuildInfo inp = do
fields <- readFields inp
let ss@(mLibFields:exes) = stanzas fields
mLib <- parseLib mLibFields
biExes <- mapM parseExe (maybe ss (const exes) mLib)
return (mLib, biExes)
where
parseLib :: [Field] -> ParseResult (Maybe BuildInfo)
parseLib (bi@((F _ inFieldName _):_))
| map toLower inFieldName /= "executable" = liftM Just (parseBI bi)
parseLib _ = return Nothing
parseExe :: [Field] -> ParseResult (String, BuildInfo)
parseExe ((F line inFieldName mName):bi)
| map toLower inFieldName == "executable"
= do bis <- parseBI bi
return (mName, bis)
| otherwise = syntaxError line "expecting 'executable' at top of stanza"
parseExe (_:_) = error "`parseExe' called on a non-field. This is a bug."
parseExe [] = syntaxError 0 "error in parsing buildinfo file. Expected executable stanza"
parseBI st = parseFields binfoFieldDescrs emptyBuildInfo st
writePackageDescription :: FilePath -> PackageDescription -> IO ()
writePackageDescription fpath pkg = writeFile fpath (showPackageDescription pkg)
showPackageDescription :: PackageDescription -> String
showPackageDescription pkg = render $
ppFields pkg pkgDescrFieldDescrs $$
(case library pkg of
Nothing -> empty
Just lib -> ppFields lib libFieldDescrs) $$
vcat (map ppExecutable (executables pkg))
where
ppExecutable exe = space $$ ppFields exe executableFieldDescrs
writeHookedBuildInfo :: FilePath -> HookedBuildInfo -> IO ()
writeHookedBuildInfo fpath pbi = writeFile fpath (showHookedBuildInfo pbi)
showHookedBuildInfo :: HookedBuildInfo -> String
showHookedBuildInfo (mb_lib_bi, ex_bi) = render $
(case mb_lib_bi of
Nothing -> empty
Just bi -> ppFields bi binfoFieldDescrs) $$
vcat (map ppExeBuildInfo ex_bi)
where
ppExeBuildInfo (name, bi) =
space $$
text "executable:" <+> text name $$
ppFields bi binfoFieldDescrs
ppFields :: a -> [FieldDescr a] -> Doc
ppFields _ [] = empty
ppFields pkg' ((FieldDescr name getter _):flds) =
ppField name (getter pkg') $$ ppFields pkg' flds
ppField :: String -> Doc -> Doc
ppField name fielddoc = text name <> colon <+> fielddoc
findIndentTabs :: String -> [(Int,Int)]
findIndentTabs = concatMap checkLine
. zip [1..]
. lines
where
checkLine (lineno, l) =
let (indent, _content) = span isSpace l
tabCols = map fst . filter ((== '\t') . snd) . zip [0..]
addLineNo = map (\col -> (lineno,col))
in addLineNo (tabCols indent)
#ifdef DEBUG
test_findIndentTabs = findIndentTabs $ unlines $
[ "foo", " bar", " \t baz", "\t biz\t", "\t\t \t mib" ]
#endif
sanityCheckPackage :: PackageDescription -> IO ([String]
,[String])
sanityCheckPackage pkg_descr =
let libSane = sanityCheckLib (library pkg_descr)
nothingToDo = checkSanity
(null (executables pkg_descr)
&& isNothing (library pkg_descr))
"No executables and no library found. Nothing to do."
noModules = checkSanity (hasMods pkg_descr)
"No exposed modules or executables in this package."
noLicenseFile = checkSanity (null $ licenseFile pkg_descr)
"No license-file field."
goodCabal = let v = (descCabalVersion pkg_descr)
in checkSanity (not $ cabalVersion `withinRange` v)
("This package requires Cabal version: "
++ (showVersionRange v) ++ ".")
in return $ ( catMaybes [nothingToDo, noModules, noLicenseFile],
catMaybes (libSane:goodCabal: checkMissingFields pkg_descr
++ map sanityCheckExe (executables pkg_descr)) )
toMaybe :: Bool -> a -> Maybe a
toMaybe b x = if b then Just x else Nothing
checkMissingFields :: PackageDescription -> [Maybe String]
checkMissingFields pkg_descr =
[missingField (pkgName . package) reqNameName
,missingField (versionBranch .pkgVersion .package) reqNameVersion
]
where missingField :: (PackageDescription -> [a])
-> String
-> Maybe String
missingField f n
= toMaybe (null (f pkg_descr)) ("Missing field: " ++ n)
sanityCheckLib :: Maybe Library -> Maybe String
sanityCheckLib ml = do
l <- ml
toMaybe (buildable (libBuildInfo l) && null (exposedModules l)) $
"A library was specified, but no exposed modules list has been given.\n"
++ "Fields of the library section:\n"
++ (render $ nest 4 $ ppFields l libFieldDescrs )
sanityCheckExe :: Executable -> Maybe String
sanityCheckExe exe
= if null (modulePath exe)
then Just ("No 'Main-Is' field found for executable " ++ exeName exe
++ "Fields of the executable section:\n"
++ (render $ nest 4 $ ppFields exe executableFieldDescrs))
else Nothing
checkSanity :: Bool -> String -> Maybe String
checkSanity = toMaybe
hasMods :: PackageDescription -> Bool
hasMods pkg_descr =
null (executables pkg_descr) &&
maybe True (null . exposedModules) (library pkg_descr)
bug :: String -> a
bug msg = error $ msg ++ ". Consider this a bug."
#ifdef DEBUG
compatTestPkgDesc :: String
compatTestPkgDesc = unlines [
"-- Required",
"Name: Cabal",
"Version: 0.1.1.1.1-rain",
"License: LGPL",
"License-File: foo",
"Copyright: Free Text String",
"Cabal-version: >1.1.1",
"-- Optional - may be in source?",
"Author: Happy Haskell Hacker",
"Homepage: http://www.haskell.org/foo",
"Package-url: http://www.haskell.org/foo",
"Synopsis: a nice package!",
"Description: a really nice package!",
"Category: tools",
"buildable: True",
"CC-OPTIONS: -g -o",
"LD-OPTIONS: -BStatic -dn",
"Frameworks: foo",
"Tested-with: GHC",
"Stability: Free Text String",
"Build-Depends: haskell-src, HUnit>=1.0.0-rain",
"Other-Modules: Distribution.Package, Distribution.Version,",
" Distribution.Simple.GHCPackageConfig",
"Other-files: file1, file2",
"Extra-Tmp-Files: file1, file2",
"C-Sources: not/even/rain.c, such/small/hands",
"HS-Source-Dirs: src, src2",
"Exposed-Modules: Distribution.Void, Foo.Bar",
"Extensions: OverlappingInstances, TypeSynonymInstances",
"Extra-Libraries: libfoo, bar, bang",
"Extra-Lib-Dirs: \"/usr/local/libs\"",
"Include-Dirs: your/slightest, look/will",
"Includes: /easily/unclose, /me, \"funky, path\\\\name\"",
"Install-Includes: /easily/unclose, /me, \"funky, path\\\\name\"",
"GHC-Options: -fTH -fglasgow-exts",
"Hugs-Options: +TH",
"Nhc-Options: ",
"Jhc-Options: ",
"",
"-- Next is an executable",
"Executable: somescript",
"Main-is: SomeFile.hs",
"Other-Modules: Foo1, Util, Main",
"HS-Source-Dir: scripts",
"Extensions: OverlappingInstances",
"GHC-Options: ",
"Hugs-Options: ",
"Nhc-Options: ",
"Jhc-Options: "
]
compatTestPkgDescAnswer :: PackageDescription
compatTestPkgDescAnswer =
PackageDescription
{ package = PackageIdentifier
{ pkgName = "Cabal",
pkgVersion = Version {versionBranch = [0,1,1,1,1],
versionTags = ["rain"]}},
license = LGPL,
licenseFile = "foo",
copyright = "Free Text String",
author = "Happy Haskell Hacker",
homepage = "http://www.haskell.org/foo",
pkgUrl = "http://www.haskell.org/foo",
synopsis = "a nice package!",
description = "a really nice package!",
category = "tools",
descCabalVersion = LaterVersion (Version [1,1,1] []),
buildType = Custom,
buildDepends = [Dependency "haskell-src" AnyVersion,
Dependency "HUnit"
(UnionVersionRanges
(ThisVersion (Version [1,0,0] ["rain"]))
(LaterVersion (Version [1,0,0] ["rain"])))],
testedWith = [(GHC, AnyVersion)],
maintainer = "",
stability = "Free Text String",
extraTmpFiles = ["file1", "file2"],
extraSrcFiles = ["file1", "file2"],
dataFiles = [],
library = Just $ Library {
exposedModules = ["Distribution.Void", "Foo.Bar"],
libBuildInfo = BuildInfo {
buildable = True,
ccOptions = ["-g", "-o"],
ldOptions = ["-BStatic", "-dn"],
frameworks = ["foo"],
cSources = ["not/even/rain.c", "such/small/hands"],
hsSourceDirs = ["src", "src2"],
otherModules = ["Distribution.Package",
"Distribution.Version",
"Distribution.Simple.GHCPackageConfig"],
extensions = [OverlappingInstances, TypeSynonymInstances],
extraLibs = ["libfoo", "bar", "bang"],
extraLibDirs = ["/usr/local/libs"],
includeDirs = ["your/slightest", "look/will"],
includes = ["/easily/unclose", "/me", "funky, path\\name"],
installIncludes = ["/easily/unclose", "/me", "funky, path\\name"],
ghcProfOptions = [],
options = [(GHC,["-fTH","-fglasgow-exts"])
,(Hugs,["+TH"]),(NHC,[]),(JHC,[])]
}},
executables = [Executable "somescript"
"SomeFile.hs" (emptyBuildInfo {
otherModules=["Foo1","Util","Main"],
hsSourceDirs = ["scripts"],
extensions = [OverlappingInstances],
options = [(GHC,[]),(Hugs,[]),(NHC,[]),(JHC,[])]
})]
}
compatParseDescription :: String -> ParseResult PackageDescription
compatParseDescription descr = do
gpd <- parsePackageDescription descr
case finalizePackageDescription [] Nothing "" "" ("",Version [] []) gpd of
Left _ -> syntaxError (1) "finalize failed"
Right (pd,_) -> return pd
hunitTests :: [Test]
hunitTests =
[ TestLabel "license parsers" $ TestCase $
sequence_ [ assertParseOk ("license " ++ show lVal) lVal
(runP 1 "license" parseLicenseQ (show lVal))
| lVal <- [GPL,LGPL,BSD3,BSD4] ]
, TestLabel "Required fields" $ TestCase $
do assertParseOk "some fields"
emptyPackageDescription {
package = (PackageIdentifier "foo"
(Version [0,0] ["asdf"])) }
(compatParseDescription "Name: foo\nVersion: 0.0-asdf")
assertParseOk "more fields foo"
emptyPackageDescription {
package = (PackageIdentifier "foo"
(Version [0,0] ["asdf"])),
license = GPL }
(compatParseDescription "Name: foo\nVersion:0.0-asdf\nLicense: GPL")
assertParseOk "required fields for foo"
emptyPackageDescription {
package = (PackageIdentifier "foo"
(Version [0,0] ["asdf"])),
license = GPL, copyright="2004 isaac jones" }
(compatParseDescription $ "Name: foo\nVersion:0.0-asdf\n"
++ "Copyright: 2004 isaac jones\nLicense: GPL")
, TestCase $ assertParseOk "no library" Nothing
(library `liftM` (compatParseDescription $
"Name: foo\nVersion: 1\nLicense: GPL\n" ++
"Maintainer: someone\n\nExecutable: script\n" ++
"Main-is: SomeFile.hs\n"))
, TestCase $ assertParseOk "translate deprecated fields"
emptyPackageDescription {
extraSrcFiles = ["foo.c", "bar.ml"],
library = Just $ emptyLibrary {
libBuildInfo = emptyBuildInfo { hsSourceDirs = ["foo","bar"] }}}
(compatParseDescription $
"hs-source-dir: foo bar\nother-files: foo.c bar.ml")
, TestLabel "Package description" $ TestCase $
assertParseOk "entire package description"
compatTestPkgDescAnswer
(compatParseDescription compatTestPkgDesc)
, TestLabel "Package description pretty" $ TestCase $
case compatParseDescription compatTestPkgDesc of
ParseFailed _ -> assertBool "can't parse description" False
ParseOk _ d ->
case compatParseDescription $ showPackageDescription d of
ParseFailed _ ->
assertBool "can't parse description after pretty print!" False
ParseOk _ d' ->
assertBool ("parse . show . parse not identity."
++" Incorrect fields:\n"
++ (unlines $ comparePackageDescriptions d d'))
(d == d')
, TestLabel "Sanity checker" $ TestCase $ do
(warns, ers) <- sanityCheckPackage emptyPackageDescription
assertEqual "Wrong number of errors" 2 (length ers)
assertEqual "Wrong number of warnings" 3 (length warns)
]
comparePackageDescriptions :: PackageDescription
-> PackageDescription
-> [String]
comparePackageDescriptions p1 p2
= catMaybes $ myCmp package "package"
: myCmp license "license"
: myCmp licenseFile "licenseFile"
: myCmp copyright "copyright"
: myCmp maintainer "maintainer"
: myCmp author "author"
: myCmp stability "stability"
: myCmp testedWith "testedWith"
: myCmp homepage "homepage"
: myCmp pkgUrl "pkgUrl"
: myCmp synopsis "synopsis"
: myCmp description "description"
: myCmp category "category"
: myCmp buildDepends "buildDepends"
: myCmp library "library"
: myCmp executables "executables"
: myCmp descCabalVersion "cabal-version"
: myCmp buildType "build-type" : []
where canon_p1 = canonOptions p1
canon_p2 = canonOptions p2
myCmp :: (Eq a, Show a) => (PackageDescription -> a)
-> String
-> Maybe String
myCmp f er = let e1 = f canon_p1
e2 = f canon_p2
in toMaybe (e1 /= e2)
(er ++ " Expected: " ++ show e1
++ " Got: " ++ show e2)
canonOptions :: PackageDescription -> PackageDescription
canonOptions pd =
pd{ library = fmap canonLib (library pd),
executables = map canonExe (executables pd) }
where
canonLib l = l { libBuildInfo = canonBI (libBuildInfo l) }
canonExe e = e { buildInfo = canonBI (buildInfo e) }
canonBI bi = bi { options = canonOptions (options bi) }
canonOptions opts = sortBy (comparing fst) opts
comparing f a b = f a `compare` f b
assertParseOk :: (Eq val) => String -> val -> ParseResult val -> Assertion
assertParseOk mes expected actual
= assertBool mes
(case actual of
ParseOk _ v -> v == expected
_ -> False)
test :: IO Counts
test = runTestTT (TestList hunitTests)
test_stanzas' = parsePackageDescription testFile
testFile = unlines $
[ "Name: dwim"
, "Cabal-version: >= 1.7"
, ""
, "Description: This is a test file "
, " with a description longer than two lines. "
, ""
, "flag Debug {"
, " Description: Enable debug information"
, " Default: False"
, "}"
, "flag build_wibble {"
, "}"
, ""
, "library {"
, " build-depends: blub"
, " exposed-modules: DWIM.Main, DWIM"
, " if os(win32) && flag(debug) {"
, " build-depends: hunit"
, " ghc-options: -DDEBUG"
, " exposed-modules: DWIM.Internal"
, " if !flag(debug) {"
, " build-depends: impossible"
, " }"
, " }"
, "}"
, ""
, "executable foo-bar {"
, " Main-is: Foo.hs"
, " Build-depends: blab"
, "}"
, "executable wobble {"
, " Main-is: Wobble.hs"
, " if flag(debug) {"
, " Build-depends: hunit"
, " }"
, "}"
, "executable wibble {"
, " Main-is: Wibble.hs"
, " hs-source-dirs: wib-stuff"
, " if flag(build_wibble) {"
, " Build-depends: wiblib >= 0.42"
, " } else {"
, " buildable: False"
, " }"
, "}"
]
test_finalizePD =
case parsePackageDescription testFile of
ParseFailed err -> print err
ParseOk _ ppd -> do
case finalizePackageDescription [("debug",True)] (Just pkgs) os arch impl ppd of
Right (pd,fs) -> do putStrLn $ showPackageDescription pd
print fs
Left missing -> putStrLn $ "missing: " ++ show missing
putStrLn $ showPackageDescription $
flattenPackageDescription ppd
where
pkgs = [ PackageIdentifier "blub" (Version [1,0] [])
, PackageIdentifier "blab" (Version [0,1] [])
]
os = "win32"
arch = "amd64"
impl = ("ghc", Version [6,6] [])
#endif