{-# LANGUAGE BangPatterns #-} {-# LANGUAGE ViewPatterns #-} {-# LANGUAGE CPP #-} import Distribution.PackageDescription.Configuration import Distribution.PackageDescription hiding (options) import Distribution.Package import Distribution.Compiler import Distribution.License import Distribution.System import Distribution.Version import Distribution.Text import Text.PrettyPrint import Control.Applicative import Control.Exception (bracket) import qualified Control.Exception as E import Control.Monad import System.IO.Error import System.Process import System.Exit import System.Directory import qualified Data.Map as M import Data.Either import Data.List import Data.Tuple (swap) import Data.String import Options import Graph import Env import Ver import Printing import Policy import Database import qualified Text.PrettyPrint.ANSI.Leijen as PP unPackageName :: PackageName -> String unPackageName (PackageName n) = n dependencyName :: Dependency -> PackageName dependencyName (Dependency n _) = n dependencyConstraints :: Dependency -> VersionRange dependencyConstraints (Dependency _ v) = v finPkgDesc :: GenericPackageDescription -> Either [Dependency] (PackageDescription, FlagAssignment) finPkgDesc = finalizePackageDescription [] (const True) buildPlatform (CompilerId buildCompilerFlavor (Version []{-[7, 6, 2]-} [])) [] showVerconstr c = render $ Distribution.Text.disp c getPackageDependencies :: AvailablePackages -> PackageName -> IO [Dependency] getPackageDependencies apkgs pn = do let desc = finPkgDesc <$> getPackageDescription apkgs pn Nothing case desc of Just (Right (d,_)) -> return $ buildDepends d _ -> return [] getPackageDependencyNames apkgs pn = map dependencyName <$> getPackageDependencies apkgs pn -- | partiion a list of arguments that refer to package -- either by their package name, or directly to a cabal file packageArgs :: [String] -> IO ([PackageName], [FilePath]) packageArgs args = partitionEithers <$> mapM classifyArgs args where classifyArgs :: String -> IO (Either PackageName FilePath) classifyArgs arg = do existingFile <- doesFileExist arg return $ if existingFile && hasCabalExtension then Right arg else Left $ PackageName arg where hasCabalExtension = ".cabal" `isSuffixOf` arg foldallLatest :: Monad m => AvailablePackages -> a -> (a -> PackageName -> PackageDescription -> m a) -> m a foldallLatest apkgs acc f = foldM process acc (getAllPackageName apkgs) where process a pn = case finPkgDesc <$> getPackageDescription apkgs pn Nothing of Just (Right (pd, _)) -> f a pn pd _ -> return a ----------------------------------------------------------------------- generateDotM boxToColor f = do (indexTable, depsTable) <- withGraph f putStrLn "digraph projects {" forM_ (M.toList indexTable) $ \((PackageName pn), i) -> do let extra = case boxToColor (PackageName pn) of Nothing -> "" Just c -> ", style=filled, fillcolor=" ++ c putStrLn (show i ++ " [label=\"" ++ pn ++ "\"" ++ extra ++ "];") forM_ (M.toList depsTable) $ \(src,dsts) -> mapM_ (\dst -> putStrLn (show src ++ " -> " ++ show dst ++ ";")) dsts putStrLn "}" unindexify :: (M.Map a GraphIndex, M.Map GraphIndex [GraphIndex]) -> [(a, [a])] unindexify (aTable, edgeTable) = map (resolveKeyValues resolveIndex) $ M.toList edgeTable where resolveKeyValues r (k,l) = (r k, map r l) resolveIndex i = maybe (error ("internal error: unknown index: " ++ show i)) id $ M.lookup i idxTable idxTable = M.fromList $ map swap $ M.toList aTable ----------------------------------------------------------------------- run apkgs hidePlatform hiddenPackages specifiedPackages = generateDotM colorize $ mapM_ (graphLoop getDeps) specifiedPackages where colorize pn | pn `elem` specifiedPackages = Just "red" | isPlatformPackage pn = Just "green" | otherwise = Nothing getDeps :: PackageName -> IO [PackageName] getDeps pn = do let desc = finPkgDesc <$> getPackageDescription apkgs pn Nothing case desc of Just (Right (d,_)) -> return $ (if hidePlatform then filter (not . isPlatformPackage) else id) $ filter (not . flip elem hiddenPackages) $ map (\(Dependency n _) -> n) $ buildDepends d _ -> do --liftIO $ putStrLn ("warning cannot handle: " ++ show pn ++ " : " ++ show desc) return [] ----------------------------------------------------------------------- runCmd (CmdGraph (map PackageName -> hidden) hidePlatform rawArgs) = do (pkgNames, pkgFileNames) <- packageArgs rawArgs availablePackages <- loadAvailablePackages pkgFileNames run availablePackages hidePlatform hidden pkgNames ----------------------------------------------------------------------- runCmd (CmdBumpable pkgs) = do apkgs <- loadAvailablePackages [] let getPkg n = (fmap fst . finPkgDesc) <$> getPackageDescription apkgs (PackageName n) Nothing bumpables = filter (not . null . snd) $ map checkBump pkgs checkBump pname = case getPkg pname of Nothing -> error ("no such package : " ++ show pname) Just resp -> (,) pname $ do Dependency (PackageName depname) verrange <- case resp of Left d -> d Right x -> buildDepends x v <- case getPkg depname of Just (Right a) -> return (pkgVersion (package a)) _ -> mzero -- might be an error ? guard (not (withinRange v verrange)) return $ PP.string depname PP.<+> PP.string "->" PP.<+> PP.cat (intersperse PP.dot (map PP.int (versionBranch v))) forM_ bumpables $ \(pname, desc) -> PP.putDoc (PP.string pname PP.<$> PP.indent 4 (PP.vcat desc) PP.<> PP.line) ----------------------------------------------------------------------- runCmd (CmdDiff (PackageName -> pname) (fromString -> v1) (fromString -> v2)) = runDiff where runDiff = do availablePackages <- loadAvailablePackages [] let mvers = getPackageVersions availablePackages pname case mvers of Nothing -> error ("no such package : " ++ show pname) Just vers -> do when (not $ elem v1 vers) $ error ("package doesn't have version " ++ show v1) when (not $ elem v2 vers) $ error ("package doesn't have version " ++ show v2) cd <- getCurrentDirectory bracket createTempDir (changeAndRemoveDirectory cd) $ \dir -> do putStrLn (cd ++ " " ++ dir) setCurrentDirectory dir cabalUnpack pname v1 cabalUnpack pname v2 diff pname createTempDir = do tmp <- getTemporaryDirectory loopCreateDir (tmp ++ "/cabal-db-diff-") (0 :: Int) where loopCreateDir prefix i = do let dir = prefix ++ show i r <- E.try (createDirectory dir) case r of Left e | isAlreadyExistsError e -> loopCreateDir prefix (i+1) | otherwise -> E.throwIO e Right () -> return dir changeAndRemoveDirectory cd dir = setCurrentDirectory cd >> removeDirectoryRecursive dir cabalUnpack :: PackageName -> Ver -> IO () cabalUnpack (PackageName pn) v = do ec <- rawSystem "cabal" ["unpack", pn ++ "-" ++ show v] case ec of ExitSuccess -> return () ExitFailure i -> error ("cabal unpack failed with error code: " ++ show i) diff (PackageName pn) = do let dir1 = pn ++ "-" ++ show v1 let dir2 = pn ++ "-" ++ show v2 _ <- rawSystem "diff" ["-Naur", dir1, dir2] return () ----------------------------------------------------------------------- runCmd (CmdRevdeps rawArgs) | null rawArgs = exitSuccess | otherwise = do (pkgNames, pkgFileNames) <- packageArgs rawArgs availablePackages <- loadAvailablePackages pkgFileNames founds <- foldallLatest availablePackages [] $ \a pkgname pkgDesc -> do let found = any (\(Dependency n _) -> n `elem` pkgNames) (buildDepends pkgDesc) if found then return ((pkgname, pkgDesc):a) else return a forM_ founds $ \(pkgname,pdesc) -> do let deps = filter (\(Dependency n _) -> n `elem` pkgNames) $ buildDepends pdesc putStrLn (unPackageName pkgname ++ ": " ++ intercalate ", " (map showDep deps)) where showDep (Dependency p v) = unPackageName p ++ " (" ++ showVerconstr v ++ ")" ----------------------------------------------------------------------- runCmd (CmdInfo (map PackageName -> args)) = do availablePackages <- loadAvailablePackages [] forM_ args $ \arg -> do let vers = maybe (error ("no package " ++ show arg)) id $ getPackageVersions availablePackages arg let pdesc = finPkgDesc <$> getPackageDescription availablePackages arg Nothing case pdesc of Just (Right (d,_)) -> do putStrLn (show arg) putStrLn (" synopsis: " ++ synopsis d) putStrLn (" versions: " ++ intercalate ", " (map show vers)) putStrLn (" dependencies for " ++ show (last vers) ++ ":") mapM_ (\(Dependency p v) -> putStrLn (" " ++ unPackageName p ++ " (" ++ showVerconstr v ++ ")")) (buildDepends d) _ -> error "cannot resolve package" ----------------------------------------------------------------------- runCmd (CmdLicense printTree printSummary rawArgs) = do (pkgNames, pkgFileNames) <- packageArgs rawArgs availablePackages <- loadAvailablePackages pkgFileNames t <- M.fromList . unindexify <$> withGraph (mapM_ (graphLoop (getPackageDependencyNames availablePackages)) pkgNames) foundLicenses <- foldM (loop availablePackages t 0) M.empty pkgNames when ((not printTree && not printSummary) || printSummary) $ do putStrLn "== license summary ==" forM_ (map nameAndLength $ group $ sortBy licenseCmp $ map snd $ M.toList foundLicenses) $ \(licenseName, licenseNumb) -> do let (lstr, ppComb) = ppLicense licenseName ppLine 0 (ppComb lstr PP.<> PP.colon PP.<+> PP.text (show licenseNumb)) where loop apkgs tbl indentSpaces founds pn@(PackageName name) | M.member pn founds = return founds | otherwise = do let desc = finPkgDesc <$> getPackageDescription apkgs pn Nothing case desc of Just (Right (d,_)) -> do let found = license d when printTree $ do let (lstr, ppComb) = ppLicense found ppLine 0 (PP.text (replicate indentSpaces ' ') PP.<> PP.text name PP.<> PP.colon PP.<+> ppComb lstr) case M.lookup pn tbl of Just l -> foldM (loop apkgs tbl (indentSpaces + 2)) (M.insert pn found founds) l Nothing -> error "internal error" _ -> return founds licenseCmp l1 l2 | l1 == l2 = EQ | otherwise = compare (show l1) (show l2) nameAndLength [] = error "empty group" nameAndLength l@(x:_) = (x, length l) ppLicense (GPL (Just (Version [v] []))) = ("GPLv" ++ show v, col Yellow) ppLicense (GPL Nothing) = ("GPL", col Yellow) #if MIN_VERSION_Cabal(1,18,0) ppLicense (AGPL (Just (Version [v] []))) = ("AGPLv" ++ show v, col Yellow) #endif ppLicense (LGPL (Just (Version [v] []))) = ("LGPLv" ++ show v, col Yellow) #if MIN_VERSION_Cabal(1,16,0) ppLicense (Apache (Just (Version [v] []))) = ("Apache" ++ show v, col Green) #endif ppLicense (UnknownLicense s) = (s, col Red) ppLicense BSD3 = ("BSD3", col Green) ppLicense BSD4 = ("BSD4", col Green) ppLicense MIT = ("MIT", col Green) ppLicense l = (show l, col Magenta) ----------------------------------------------------------------------- runCmd (CmdSearch term vals) = do availablePackages <- loadAvailablePackages [] founds <- foldallLatest availablePackages [] $ \a pkgname pkgDesc -> do let found = any (\arg -> contains arg (accessor pkgDesc)) vals if found then return ((pkgname, pkgDesc):a) else return a mapM_ (putStrLn . unPackageName . fst) founds where contains searching searched = maybe False (const True) $ find (isPrefixOf searching) $ tails searched accessor = toAccessor term toAccessor SearchMaintainer = maintainer toAccessor SearchAuthor = author ----------------------------------------------------------------------- runCmd (CmdCheckRevdepsPolicy rawArgs) = do (pkgNames, pkgFileNames) <- packageArgs rawArgs availablePackages <- loadAvailablePackages pkgFileNames ret <- foldallLatest availablePackages M.empty $ \accOuter pkgname pkgDesc -> do let deps = buildDepends pkgDesc foldM (accOnDep pkgNames availablePackages pkgname) accOuter deps forM_ (M.toList ret) $ \(p, z) -> do let sums = map (\l -> (head l, length l)) $ group $ sort $ map snd $ M.toList z putStrLn ("== " ++ show p) forM_ (M.toList z) $ \(revDep, policy) -> do ppLine 2 $ PP.hcat [PP.string (unPackageName revDep), PP.colon, showPolicy policy] forM_ sums $ \(policy, n) -> ppLine 0 $ PP.hcat [PP.string (show n), PP.string " packages have a constraint set to ", showPolicy policy ] return () where accOnDep pkgNames allPackages pkg a dep = case find (== dependencyName dep) pkgNames of Nothing -> return a Just packageMatching -> let vr = dependencyConstraints dep in return $ updatePolTree a packageMatching pkg (getPolicy allPackages vr) updatePolTree :: M.Map PackageName (M.Map PackageName Policy) -> PackageName -> PackageName -> Policy -> M.Map PackageName (M.Map PackageName Policy) updatePolTree a withPkg pkg pol = M.alter updateRoot withPkg a where updateRoot :: Maybe (M.Map PackageName Policy) -> Maybe (M.Map PackageName Policy) updateRoot Nothing = Just (M.alter x pkg M.empty) updateRoot (Just l) = Just (M.alter x pkg l) x :: Maybe Policy -> Maybe Policy x Nothing = Just pol x (Just actualPol) = case actualPol of Policy_Many ps | pol `elem` ps -> Just actualPol | otherwise -> Just $ Policy_Many (pol:ps) _ | actualPol == pol -> Just actualPol | otherwise -> Just $ Policy_Many (pol:[actualPol]) runCmd (CmdCheckPolicy rawArgs) = do (pkgNames, pkgFileNames) <- packageArgs rawArgs availablePackages <- loadAvailablePackages pkgFileNames matched <- foldallLatest availablePackages M.empty $ \acc name pkgDesc -> if name `elem` pkgNames then return $ M.insert name pkgDesc acc else return acc forM_ (M.toList matched) $ \(name, pkgDesc) -> do putStrLn ("== " ++ unPackageName name) let packageDepends = map (\d -> (dependencyName d, getPolicy availablePackages $ dependencyConstraints d)) $ buildDepends pkgDesc let sums = map (\l -> (head l, length l)) $ group $ sort $ map snd packageDepends forM_ packageDepends $ \(n,p) -> ppLine 4 $ PP.hcat [ PP.string "* ", PP.string (unPackageName n), PP.string " = ", showPolicy p] forM_ sums $ \(policy, n) -> ppLine 2 $ PP.hcat [ showPolicy policy, PP.string " = ", PP.string (show n), PP.string " packages"] runCmd (CmdForAll) = do return () ----------------------------------------------------------------------- main = getOptions >>= runCmd