{-# LANGUAGE OverloadedStrings #-} module Main where import qualified Data.Map as M import qualified Data.Set as S import qualified Data.ByteString.Char8 as BS import qualified Data.Text as T import Options.Applicative import Control.Monad import Data.List import Data.Maybe import Cabal.Plan import qualified Distribution.PackageDescription.Parsec as C import qualified Distribution.Package as C import qualified Distribution.Types.Version as C import qualified Distribution.Types.VersionRange as C import ReplaceDependencies main :: IO () main = join . customExecParser (prefs showHelpOnError) $ info (helper <*> parser) ( fullDesc <> header "Derives dependency bounds from build plans" -- <> progDesc "What does this thing do?" ) where parser :: Parser (IO ()) parser = work <$> many (argument (is ".json") (metavar "PLAN" <> help "plan file to read (.json)")) <*> many (strOption (short 'c' <> long "cabal" <> metavar "CABALFILE" <> help "cabal file to pdate (.cabal)")) is :: String -> ReadM FilePath is suffix = maybeReader $ \s -> do guard (suffix `isSuffixOf` s) pure s cabalPackageName :: BS.ByteString -> C.PackageName cabalPackageName contents = case C.runParseResult (C.parseGenericPackageDescription contents) of (_warn, Left err) -> error (show err) (_warn, Right gpd) -> C.packageName gpd depsOf :: C.PackageName -> PlanJson -> M.Map C.PackageName C.Version depsOf pname plan = M.fromList -- TODO: What if different units of the package have different deps? [ (C.mkPackageName (T.unpack depName), C.mkVersion depVersion) | unit <- M.elems (pjUnits plan) , let PkgId (PkgName pname') _ = uPId unit , pname' == T.pack (C.unPackageName pname) , comp <- M.elems (uComps unit) , depUid <- S.toList (ciLibDeps comp) , let depunit = pjUnits plan M.! depUid , let PkgId (PkgName depName) (Ver depVersion) = uPId depunit ] unionMajorBounds :: [C.Version] -> C.VersionRange unionMajorBounds [] = C.anyVersion unionMajorBounds vs = foldr1 C.unionVersionRanges (map C.majorBoundVersion vs) -- assumes sorted input pruneVersionRanges :: [C.Version] -> [C.Version] pruneVersionRanges [] = [] pruneVersionRanges [v] = [v] pruneVersionRanges (v1:v2:vs) | v2 `C.withinRange` C.majorBoundVersion v1 = pruneVersionRanges (v1 : vs) | otherwise = v1 : pruneVersionRanges (v2 : vs) work :: [FilePath] -> [FilePath] -> IO () work planfiles cabalfiles = do plans <- mapM decodePlanJson planfiles forM_ cabalfiles $ \cabalfile -> do contents <- BS.readFile cabalfile -- Figure out package name let pname = cabalPackageName contents let deps = fmap (unionMajorBounds . pruneVersionRanges . sort) $ M.unionsWith (++) $ map (fmap pure) $ map (depsOf pname) plans let contents' = replaceDependencies (\pn vr -> fromMaybe vr $ M.lookup pn deps) contents unless (contents == contents') $ -- TODO: Use atomic-write BS.writeFile cabalfile contents'