cabal-clean-0.2.20230609: src/Main.hs
-- | cabal-clean
--
-- Remove build artefacts of outdated versions from `dist-newstyle`.
{-# LANGUAGE RecordWildCards #-}
module Main where
import Data.Monoid
( Sum(..) )
import Options.Applicative
( Parser
, action, execParser, footerDoc, headerDoc, help, helper, hidden
, info, infoOption, long, metavar, short, strArgument, switch, value
)
import Options.Applicative.Help.Pretty
( vcat, pretty )
import System.Console.Pretty
( supportsPretty )
import Util
import DiscoverGHCs
( discoverGHCs )
import License
( copyright, license )
import Options
import Structure
( Entry(..), foldMapEntry, getBuildTree, markObsolete, printBuildTree )
import Version
self :: String
self = "cabal-clean"
homepage :: String
homepage = concat [ "https://github.com/andreasabel/", self ]
defaultRoot :: FilePath
defaultRoot = "dist-newstyle"
main :: IO ()
main = do
-- Parse options.
opts@Options{..} <- options
-- print opts
let buildDir = optRoot </> "build"
-- Discover installed GHCs.
ghcs <- discoverGHCs opts
-- Get build tree.
chatLn opts $ unwords [ "Reading", buildDir, "..." ]
unlessM (doesDirectoryExist buildDir) $ do
die $ unwords [ "No such directory:", buildDir ]
(tree0, warns) <- getBuildTree buildDir
forM_ warns $ putStrLn . ("warning: " ++)
-- Mark superseded builds as obsolete.
-- If no installed GHCs could be found, assume a misconfiguration and
-- do not use installed GHCs as criterion.
let keep = if null ghcs then const True else hasElem ghcs
let tree = markObsolete keep tree0
printBuildTree opts tree
-- Count obsolete directories.
let nObs :: Integer
nObs = getSum $ foldMapEntry ((\ x -> if x then Sum 1 else Sum 0) . obsolete) tree
if nObs <= 0 then putStrLn ("Nothing to clean!")
else if not optDelete then putStrLn $ unwords
[ show nObs, "directories can be removed (supply option --remove)." ]
-- Remove obsolete directories.
else do
putStrLn $ unwords [ "Removing", show nObs, "obsolete directories..." ]
flip foldMapEntry tree $ \ (Entry dir obsolete) -> do
if obsolete then do
chatLn opts $ unwords [ "Removing", dir ]
removeDirectoryRecursive dir
else chatLn opts $ unwords [ "Keeping", dir ]
-- Done.
chatLn opts $ unwords [ self, "terminated successfully." ]
-- * Option parsing and handling
options :: IO Options
options = do
opts <- execParser $ info parser infoMod
supportsPretty >>= \case
True -> return opts
False -> return opts{ optNoColors = True }
where
parser = programOptions <**>
(versionOption <*> numericVersionOption <*> licenseOption <*> helper)
infoMod = headerDoc header <> footerDoc footer
versionOption =
infoOption versionLong
$ long "version"
<> short 'V'
<> help "Show version info."
<> hidden
versionText = unwords [ self, "version", version ]
versionLong = intercalate "\n" $
[ versionText
, copyright
, "This is free software under the BSD-3-clause license."
]
numericVersionOption =
infoOption version
$ long "numeric-version"
<> help "Show just version number."
<> hidden
-- Newline at the end:
-- <> helpDoc (Just $ text "Show just version number." <$$> text "")
licenseOption =
infoOption license
$ long "license"
-- <> long "licence"
<> help "Show the license text."
<> hidden
-- Obs: match the order with Options.Options!
programOptions = Options
<$> oDelete
<*> oNoColors
<*> oVerbose
<*> oRoot
oDelete =
switch
$ long "delete"
<> long "remove"
-- <> short 'D'
<> help "Actually clean up on disk."
oVerbose =
switch
$ long "verbose"
<> short 'v'
<> help "Comment on what is happening."
oNoColors =
switch
$ long "no-colors"
<> help "Disable colorized output. Automatic if terminal does not support colors."
oRoot :: Parser FilePath
oRoot = do
strArgument
$ metavar "ROOT"
<> value defaultRoot
<> action "directory"
<> help (concat ["The root of the build tree. Default: ", show defaultRoot, "."])
-- Note: @header@ does not respect line breaks, so we need @headerDoc@.
header = Just $ vcat $ map pretty
[ unwords [ versionText, homepage ]
, ""
, concat
[ "Clean superseded build artefacts from directory ROOT (typically "
, show defaultRoot
, ")."
]
, ""
, "A package build is considered superseded if one of the following conditions is met:"
, "- It was build with a Haskell compiler that cannot be found on the system PATH."
, "- There is a build of a newer version of the package."
, "- There is a build with a newer minor version of the Haskell compiler."
, ""
, "Limitation: Only GHC is recognized as Haskell compiler, and only in the form 'ghc-VERSION' (not just 'ghc')."
]
footer = Just $ vcat $ map (pretty . unwords)
[ [ unwords ["Without option --delete,", self, "does not actually clean out anything,"]
, "just shows prefixed with '---' and in red what would be removed and prefixed with '+++' and in green what is kept."
]
, [ "" ]
, [ "Warning: there is no check whether the to-be-deleted contents are actually garbage."
, "(E.g., there could be symlinks to executables stored there.)"
]
]