Commit 0bf56429 authored by Duncan Coutts's avatar Duncan Coutts
Browse files

Split Distribution.PackageDescription into three other modules

New D.PackageDescription.Types with the types and simple utils
New D.PackageDescription.Parse module for parsing and printing functions.
The sanity check code is moved into the existing QA module.
The public api of Distribution.PackageDescription remains the same.
parent 53062a5d
......@@ -49,6 +49,8 @@ Library
Distribution.Make,
Distribution.Package,
Distribution.PackageDescription,
Distribution.PackageDescription.Types,
Distribution.PackageDescription.Parse,
Distribution.PackageDescription.QA,
Distribution.Configuration,
Distribution.ParseUtils,
......
This diff is collapsed.
This diff is collapsed.
{-# LANGUAGE CPP #-}
{-# OPTIONS_GHC -cpp #-}
{-# OPTIONS_NHC98 -cpp #-}
{-# OPTIONS_JHC -fcpp #-}
-----------------------------------------------------------------------------
-- |
-- Module : Distribution.PackageDescription.QA
......@@ -42,19 +46,122 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -}
module Distribution.PackageDescription.QA (
cabalVersion,
sanityCheckPackage,
-- * Quality Assurance
qaCheckPackage,
QANotice(..)
) where
import Data.Maybe (isNothing)
import Control.Monad(when,unless)
import System.Directory(doesFileExist)
import Data.Maybe (isNothing, catMaybes)
import Control.Monad (when,unless)
import System.Directory (doesFileExist)
import Distribution.PackageDescription.Types
import Distribution.PackageDescription.Parse
import Distribution.Compiler(CompilerFlavor(..))
import Distribution.PackageDescription
import Distribution.License (License(..))
import Text.PrettyPrint.HughesPJ
import Distribution.Version (Version(..), withinRange, showVersionRange)
import Distribution.Package (PackageIdentifier(..))
import System.FilePath (takeExtension)
-- We only get our own version number when we're building with ourselves
cabalVersion :: Version
#ifdef CABAL_VERSION
cabalVersion = Version [CABAL_VERSION] []
#else
cabalVersion = error "Cabal was not bootstrapped correctly"
#endif
-- ------------------------------------------------------------
-- * Sanity Checking
-- ------------------------------------------------------------
-- |Sanity check this description file.
-- FIX: add a sanity check for missing haskell files? That's why its
-- in the IO monad.
sanityCheckPackage :: PackageDescription -> IO ([String] -- Warnings
,[String])-- Errors
sanityCheckPackage pkg_descr = do
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."
goodCabal = let v = (descCabalVersion pkg_descr)
in checkSanity (not $ cabalVersion `withinRange` v)
("This package requires Cabal version: "
++ (showVersionRange v) ++ ".")
noBuildType = checkSanity (isNothing $ buildType pkg_descr)
"No 'build-type' specified. If possible use 'build-type: Simple'"
lf = licenseFile pkg_descr
noLicense <- checkSanityIO (licenseDoesNotExist lf)
("License file " ++ lf ++ " does not exist.")
return $ ( catMaybes [nothingToDo, noModules, noBuildType, noLicense],
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]) -- Field accessor
-> String -- Name of field
-> Maybe String -- error message
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
| null (modulePath exe)
= Just ("No 'Main-Is' field found for executable " ++ exeName exe
++ "Fields of the executable section:\n"
++ (render $ nest 4 $ ppFields exe executableFieldDescrs))
| ext `notElem` [".hs", ".lhs"]
= Just ("The 'Main-Is' field must specify a '.hs' or '.lhs' file\n"
++" (even if it is generated by a preprocessor).")
| otherwise = Nothing
where ext = takeExtension (modulePath exe)
checkSanity :: Bool -> String -> Maybe String
checkSanity = toMaybe
checkSanityIO :: IO Bool -> String -> IO (Maybe String)
checkSanityIO test str = do b <- test
return $ toMaybe b str
hasMods :: PackageDescription -> Bool
hasMods pkg_descr =
null (executables pkg_descr) &&
maybe True (null . exposedModules) (library pkg_descr)
licenseDoesNotExist :: FilePath -> IO Bool
licenseDoesNotExist lf = do
b <- doesFileExist lf
return $ not (null lf || b)
-- ------------------------------------------------------------
-- * Quality Assurance
-- ------------------------------------------------------------
......
-----------------------------------------------------------------------------
-- |
-- Module : Distribution.PackageDescription.Types
-- Copyright : Isaac Jones 2003-2005
--
-- Maintainer : Isaac Jones <ijones@syntaxpolice.org>
-- Stability : alpha
-- Portability : portable
--
-- Package description and parsing.
{- All rights reserved.
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. -}
module Distribution.PackageDescription.Types (
-- * Package descriptions
PackageDescription(..),
GenericPackageDescription(..),
emptyPackageDescription,
BuildType(..),
-- ** Libraries
Library(..),
emptyLibrary,
withLib,
hasLibs,
libModules,
-- ** Executables
Executable(..),
emptyExecutable,
withExe,
hasExes,
exeModules,
-- * Build information
BuildInfo(..),
emptyBuildInfo,
allBuildInfo,
unionBuildInfo,
-- ** Supplementary build information
HookedBuildInfo,
emptyHookedBuildInfo,
updatePackageDescription,
) where
import Data.List (nub)
import Data.Monoid (Monoid(mempty, mappend))
import Distribution.Package (PackageIdentifier(PackageIdentifier))
import Distribution.Version (Version(Version), VersionRange(AnyVersion))
import Distribution.License (License(AllRightsReserved))
import Distribution.Version (Dependency)
import Distribution.Compiler (CompilerFlavor)
import Distribution.Configuration (CondTree, ConfVar, Flag)
import Distribution.Simple.Utils (currentDir)
import Language.Haskell.Extension (Extension)
-- -----------------------------------------------------------------------------
-- The PackageDescription type
-- | This data type is the internal representation of the file @pkg.cabal@.
-- 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,
license :: License,
licenseFile :: FilePath,
copyright :: String,
maintainer :: String,
author :: String,
stability :: String,
testedWith :: [(CompilerFlavor,VersionRange)],
homepage :: String,
pkgUrl :: String,
synopsis :: String, -- ^A one-line summary of this package
description :: String, -- ^A more verbose description of this package
category :: String,
buildDepends :: [Dependency],
descCabalVersion :: VersionRange, -- ^If this package depends on a specific version of Cabal, give that here.
buildType :: Maybe BuildType,
-- components
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 = Nothing,
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)]
}
--deriving (Show)
-- | The type of build system used by this package.
data BuildType
= Simple -- ^ calls @Distribution.Simple.defaultMain@
| Configure -- ^ calls @Distribution.Simple.defaultMainWithHooks defaultUserHooks@,
-- which invokes @configure@ to generate additional build
-- information used by later phases.
| Make -- ^ calls @Distribution.Make.defaultMain@
| Custom -- ^ uses user-supplied @Setup.hs@ or @Setup.lhs@ (default)
deriving (Show, Read, Eq)
-- ---------------------------------------------------------------------------
-- The Library type
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
-- |does this package have any libraries?
hasLibs :: PackageDescription -> Bool
hasLibs p = maybe False (buildable . libBuildInfo) (library p)
-- |'Maybe' version of 'hasLibs'
maybeHasLibs :: PackageDescription -> Maybe Library
maybeHasLibs p =
library p >>= \lib -> if buildable (libBuildInfo lib)
then Just lib
else Nothing
-- |If the package description has a library section, call the given
-- function with the library build info as argument.
withLib :: PackageDescription -> a -> (Library -> IO a) -> IO a
withLib pkg_descr a f =
maybe (return a) f (maybeHasLibs pkg_descr)
-- |Get all the module names from the libraries in this package
libModules :: PackageDescription -> [String]
libModules PackageDescription{library=lib}
= maybe [] exposedModules lib
++ maybe [] (otherModules . libBuildInfo) lib
unionLibrary :: Library -> Library -> Library
unionLibrary l1 l2 =
l1 { exposedModules = combine exposedModules
, libBuildInfo = unionBuildInfo (libBuildInfo l1) (libBuildInfo l2)
}
where combine f = f l1 ++ f l2
-- ---------------------------------------------------------------------------
-- The Executable type
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 }
-- |does this package have any executables?
hasExes :: PackageDescription -> Bool
hasExes p = any (buildable . buildInfo) (executables p)
-- | Perform the action on each buildable 'Executable' in the package
-- description.
withExe :: PackageDescription -> (Executable -> IO a) -> IO ()
withExe pkg_descr f =
sequence_ [f exe | exe <- executables pkg_descr, buildable (buildInfo exe)]
-- |Get all the module names from the exes in this package
exeModules :: PackageDescription -> [String]
exeModules PackageDescription{executables=execs}
= concatMap (otherModules . buildInfo) execs
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 ++ "'"
-- ---------------------------------------------------------------------------
-- The BuildInfo type
-- Consider refactoring into executable and library versions.
data BuildInfo = BuildInfo {
buildable :: Bool, -- ^ component is buildable here
buildTools :: [Dependency], -- ^ tools needed to build this bit
cppOptions :: [String], -- ^ options for pre-processing Haskell code
ccOptions :: [String], -- ^ options for C compiler
ldOptions :: [String], -- ^ options for linker
pkgconfigDepends :: [Dependency], -- ^ pkg-config packages that are used
frameworks :: [String], -- ^support frameworks for Mac OS X
cSources :: [FilePath],
hsSourceDirs :: [FilePath], -- ^ where to look for the haskell module hierarchy
otherModules :: [String], -- ^ non-exposed or non-main modules
extensions :: [Extension],
extraLibs :: [String], -- ^ what libraries to link with when compiling a program that uses your package
extraLibDirs :: [String],
includeDirs :: [FilePath], -- ^directories to find .h files
includes :: [FilePath], -- ^ The .h files to be found in includeDirs
installIncludes :: [FilePath], -- ^ .h files to install with the package
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] }
-- | The 'BuildInfo' for the library (if there is one and it's buildable) and
-- all the buildable executables. Useful for gathering dependencies.
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 ]
type HookedBuildInfo = (Maybe BuildInfo, [(String, BuildInfo)])
emptyHookedBuildInfo :: HookedBuildInfo
emptyHookedBuildInfo = (Nothing, [])
-- ------------------------------------------------------------
-- * Utils
-- ------------------------------------------------------------
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
--the lib only exists in the buildinfo file. FIX: Is this
--wrong? If there aren't any exposedModules, then the library
--won't build anyway. add to sanity checker?
updateLibrary (Just bi) Nothing = Just emptyLibrary{libBuildInfo=bi}
updateExecutables :: [(String, BuildInfo)] -- ^[(exeName, new buildinfo)]
-> [Executable] -- ^list of executables to update
-> [Executable] -- ^list with exeNames updated
updateExecutables exe_bi' executables' = foldr updateExecutable executables' exe_bi'
updateExecutable :: (String, BuildInfo) -- ^(exeName, new buildinfo)
-> [Executable] -- ^list of executables to update
-> [Executable] -- ^libst with exeName updated
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
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment