{-# LANGUAGE NamedFieldPuns, RecordWildCards, DeriveDataTypeable #-} {-# OPTIONS_GHC -Wall #-} module Main where import Distribution.InstalledPackageInfo import Distribution.Package hiding (depends) import System.Process import System.IO import Data.List import Control.Applicative import System.FilePath import Data.Maybe import Data.List.Split import Control.Exception import System.Console.CmdArgs import PkgCheckParser data Opts = Opts { sandbox :: Maybe FilePath } deriving (Show, Eq, Typeable, Data) defOpts :: Opts defOpts = Opts { sandbox = Nothing &= help "Check cabal-dev's sandbox (default: cabal-dev)" &= typ "PATH" &= opt "cabal-dev" &= name "s" &= name "cabal-dev" } &= program "ghc-pkg-autofix" &= summary "ghc-pkg-autofix: An automatic broken dependency fixer for Cabal" main :: IO () main = do opts <- cmdArgs defOpts (_, _, hErr, _) <- case sandbox opts of Just path -> runInteractiveProcess "cabal-dev" ["-s", path, "ghc-pkg", "check"] Nothing Nothing Nothing -> runInteractiveProcess "ghc-pkg" ["check"] Nothing Nothing src <- hGetContents hErr if null src then putStrLn "Nothing to fix. exit." else case parseCheck src of Left err -> hPrint stderr err Right ps -> do print ps mapM_ (procPackage (sandbox opts)) ps _ <- case sandbox opts of Nothing -> readProcess "ghc-pkg" ["recache", "--user"] "" Just dv -> readProcess "cabal-dev" ["-s", dv, "ghc-pkg", "recache", "--user"] "" return () procPackage :: Maybe FilePath -> BrokenPackage -> IO () procPackage mfp P{packageID, brokenDeps} = do Just packID <- getPackageID mfp packageID newPIDs <- mapM (getPackageID mfp . getPackageName) brokenDeps let table = catMaybes $ zipWith (fmap.(,)) brokenDeps newPIDs infoPath <- getPackageInfoPath mfp packID rslt <- parseInstalledPackageInfo <$> readFile infoPath print (infoPath, rslt) case rslt of ParseFailed err -> hPrint stderr err ParseOk _ pack -> do let pack' = pack{depends=foldr (uncurry replace) (depends pack) table} writeFile infoPath $ showInstalledPackageInfo pack' init' :: [a] -> [a] init' = reverse . drop 1 . reverse getPackageName :: String -> String getPackageName pid = intercalate "-" $ init' $ splitOn "-" pid getPackageID :: Maybe FilePath -> String -> IO (Maybe String) getPackageID mfp pName = handle handler $ do rsl <- parseInstalledPackageInfo <$> case mfp of Nothing -> readProcess "ghc-pkg" ["describe", pName] "" Just fp -> readProcess "cabal-dev" ["-s", fp, "ghc-pkg", "describe", pName] "" case rsl of ParseFailed _ -> return Nothing ParseOk _ pack -> do let InstalledPackageId pid = installedPackageId pack return (Just pid) where handler :: SomeException -> IO (Maybe String) handler e = print e >> return Nothing replace :: String -> String -> [InstalledPackageId] -> [InstalledPackageId] replace old new = intercalate [ InstalledPackageId new ] . splitOn [InstalledPackageId old] getPackageInfoPath :: Maybe FilePath -> String -> IO FilePath getPackageInfoPath mfp pid = do dir <- getConfDir mfp return $ dir (pid++".conf") getConfDir :: Maybe FilePath -> IO FilePath getConfDir mfp = do src <- case mfp of Nothing -> readProcess "ghc-pkg" ["list", "--user"] "" Just fp -> readProcess "cabal-dev" ["-s", fp, "ghc-pkg", "list", "--user"] "" let ans = init $ head $ drop 1 $ filter ("/" `isPrefixOf`) $ lines src return ans