{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TupleSections #-} module Options.Cabal where import qualified Data.Set as S import Control.Monad.M import Data.Monoid import qualified Data.Map as M import qualified Data.List as L import Control.Applicative import Control.Monad import qualified Distribution.PackageDescription.Parse as C import qualified Distribution.Package as C import qualified Distribution.PackageDescription as C import Data.Either import Data.String.Util import Control.Monad.IO.Class import qualified Options.CabalConstraints as OC type Package = String newtype Targets = Targets { mapping :: M.Map String (S.Set Package) } fromTargets :: Targets -> S.Set Package fromTargets = S.unions . M.elems . mapping data DependencyDescription = DependencyDescription { library :: Maybe (S.Set Package), execs :: Targets, suites :: Targets, benchmarks :: Targets } -- | Return the packages to use from the target type, or fail with unfound targets. fromTargetType :: S.Set String -> Targets -> Either [String] (S.Set String) fromTargetType narrowing tgts = do used_targets <- -- return a list of used targets if S.null narrowing then return . M.keysSet . mapping $ tgts else -- error check narrowed packages let found = S.intersection narrowing $ M.keysSet (mapping tgts) unfound = S.difference narrowing found in if not . S.null $ unfound then Left . S.toList $ unfound else return found -- accumulate dependencies from found keys return $ S.unions . map snd . M.toList . M.intersection (mapping tgts) . M.fromList $ zip (S.toList used_targets) (replicate (S.size used_targets) ()) toPackages :: FilePath -> DependencyDescription -> OC.CabalConstraints -> M (S.Set Package) toPackages cabal desc constraints = case toPkgs pairings of Left unfound -> err $ preposition "failed to find targets" "in" "cabal file" cabal unfound Right set -> let matched_excluded = S.intersection set (OC.excluded constraints) remainder = S.difference (OC.excluded constraints) matched_excluded in do unless (S.null remainder) . warning . L.intercalate "\n" $ "packages to exclude were not found:" : S.toList remainder return $ S.difference set (OC.excluded constraints) where -- | Produce a list of targets to evaluate based off selection, e.g. -- if any fst member of tuple is non-empty, the subset is returned. -- if all are non-empty, all are considered toPkgs :: [(S.Set String, Targets)] -> Either [String] (S.Set Package) toPkgs list = case L.partition (S.null . fst) list of (non_selections, []) -> Right . S.unions . map (fromTargets . snd) $ non_selections (_, selections) -> case partitionEithers (L.map (uncurry fromTargetType) selections) of ([],sets) -> Right . S.unions $ sets (unfound,_) -> Left . concat $ unfound -- | Return pairings of expected cabal targets to actual cabal targets pairings :: [(S.Set String, Targets)] pairings = let lib_pairing :: (S.Set Package, Targets) lib_pairing = (if OC.lib constraints then S.singleton "library" else mempty, Targets $ maybe mempty (M.singleton "library") (library desc)) in lib_pairing : zip (map ($ constraints) [OC.execs, OC.suites, OC.benchmarks]) (map ($ desc) [execs, suites, benchmarks]) toStrName :: C.Dependency -> String toStrName (C.Dependency (C.PackageName name) _) = name toTargets :: [(String, C.CondTree a [C.Dependency] b)] -> Targets toTargets = let toStrDeps target tree = (target, S.fromList . map toStrName $ C.condTreeConstraints tree) in Targets . M.fromList . map (uncurry toStrDeps) -- | Given the defined constraints, return packages satisfying from the cabal file. readPackages :: FilePath -> OC.CabalConstraints -> M (S.Set Package) readPackages cabal constraints = do parse_result <- liftIO $ C.parsePackageDescription <$> readFile cabal case parse_result of (C.ParseFailed fail_msg) -> err . show $ fail_msg (C.ParseOk warnings desc) -> do unless (L.null warnings) . warning $ preposition "warnings during parse" "of" "cabal file" "warnings" (map show warnings) toPackages cabal (toDescription desc) constraints where -- Produce a simplified description of the cabal file for processing. toDescription :: C.GenericPackageDescription -> DependencyDescription toDescription gpd = DependencyDescription (S.fromList . map toStrName . C.condTreeConstraints <$> C.condLibrary gpd) (toTargets . C.condExecutables $ gpd) (toTargets . C.condTestSuites $ gpd) (toTargets . C.condBenchmarks $ gpd)