{-# LANGUAGE CPP #-} module Main (main) where #if !MIN_VERSION_simple_cmd_args(0,1,3) import Control.Applicative ((<|>)) #endif import Control.Monad import qualified Data.List as L import Data.List.Extra import Data.Maybe import Data.Version.Extra import Numeric.Natural import SimpleCmd import SimpleCmdArgs import System.Directory import System.FilePath import System.IO (BufferMode(NoBuffering), hSetBuffering, stdout) import GHC import Paths_stack_clean_old (version) import Snapshots import Types data Mode = Default | Project | Snapshots | Compilers | GHC data Recursion = Subdirs | Recursive deriving Eq main :: IO () main = do hSetBuffering stdout NoBuffering simpleCmdArgs (Just version) "Stack clean up tool" "Cleans away old stack-work builds (and pending: stack snapshots) to recover diskspace. Use the --delete option to perform actual removals. https://github.com/juhp/stack-clean-old#readme" $ subcommands [ Subcommand "size" "Total size" $ sizeCmd <$> modeOpt <*> recursionOpt <*> notHumanOpt , Subcommand "list" "List sizes per ghc version" $ listCmd <$> modeOpt <*> recursionOpt <*> optional ghcVerArg <*> optional systemOpt , Subcommand "remove" "Remove for a ghc version" $ removeCmd <$> deleteOpt <*> modeOpt <*> recursionOpt <*> ghcVerArg <*> optional systemOpt , Subcommand "keep-minor" "Remove for previous ghc minor versions" $ removeMinorsCmd <$> deleteOpt <*> modeOpt <*> recursionOpt <*> optional ghcVerArg <*> optional systemOpt , Subcommand "purge-older" "Purge older builds in .stack-work/install" $ purgeOlderCmd <$> deleteOpt <*> keepOption <*> recursionOpt <*> optional systemOpt , Subcommand "delete-work" "Remove project's .stack-work/ (optionally recursively)" $ deleteWorkCmd <$> deleteOpt <*> recursionOpt ] where modeOpt = flagWith' Project 'P' "project" "Act on current project's .stack-work/ [default in project dir]" <|> flagWith' GHC 'G' "global" "Act on both ~/.stack/{programs,snapshots}/ [default outside project dir]" <|> flagWith' Snapshots 'S' "snapshots" "Act on ~/.stack/snapshots/" <|> flagWith Default Compilers 'C' "compilers" "Act on ~/.stack/programs/" deleteOpt = flagWith Dryrun Delete 'd' "delete" "Do deletion [default is dryrun]" recursionOpt = optional ( flagWith' Subdirs 's' "subdirs" "List subdirectories" <|> flagWith' Recursive 'r' "recursive" "List subdirectories") notHumanOpt = switchWith 'H' "not-human-size" "Do not use du --human-readable" ghcVerArg = readVersion <$> strArg "GHCVER" keepOption = optionalWith auto 'k' "keep" "INT" "number of project builds per ghc version [default 5]" 5 systemOpt = strOptionWith 'o' "os-system" "SYSTEM" "Specify which of the OS platforms to work on (eg 'x86_64-linux-tinfo6' or 'aarch64-linux-nix', etc)" withRecursion :: Bool -> Maybe Recursion -> IO () -> IO () withRecursion needinstall mrecursion = withRecursion' True needinstall mrecursion . const withRecursion' :: Bool -> Bool -> Maybe Recursion -> (FilePath -> IO ()) -> IO () withRecursion' changedir needinstall mrecursion act = do case mrecursion of Just recursion -> do dirs <- (if recursion == Recursive then map (dropPrefix "./" . takeDirectory) <$> findStackWorks else listStackSubdirs) >>= if needinstall then filterM (doesDirectoryExist . ( "install")) else return forM_ dirs $ \dir -> if changedir then withCurrentDirectory dir $ do putStrLn $ "\n" ++ takeFileName dir ++ "/" act dir else act dir Nothing -> act "" sizeCmd :: Mode -> Maybe Recursion -> Bool -> IO () sizeCmd mode mrecursion notHuman = case mode of Project -> withRecursion' False False mrecursion $ sizeStackWork notHuman Snapshots -> sizeSnapshots notHuman Compilers -> sizeGhcInstalls notHuman GHC -> do sizeCmd Snapshots Nothing notHuman sizeCmd Compilers Nothing notHuman Default -> do isProject <- doesDirectoryExist ".stack-work" if isProject || isJust mrecursion then sizeCmd Project mrecursion notHuman else sizeCmd GHC Nothing notHuman listCmd :: Mode -> Maybe Recursion -> Maybe Version -> Maybe String -> IO () listCmd mode mrecursion mver msystem = withRecursion True mrecursion $ case mode of Project -> setStackWorkInstallDir msystem >> listGhcSnapshots mver Snapshots -> setStackSnapshotsDir msystem >> listGhcSnapshots mver Compilers -> listGhcInstallation mver msystem GHC -> do listCmd Snapshots Nothing mver msystem listCmd Compilers Nothing mver msystem Default -> do isProject <- doesDirectoryExist ".stack-work" if isProject then listCmd Project Nothing mver msystem else listCmd GHC Nothing mver msystem removeCmd :: Deletion -> Mode -> Maybe Recursion -> Version -> Maybe String -> IO () removeCmd deletion mode mrecursion ghcver msystem = do withRecursion True mrecursion $ case mode of Project -> do cwd <- getCurrentDirectory setStackWorkInstallDir msystem cleanGhcSnapshots deletion cwd ghcver Snapshots -> do cwd <- getCurrentDirectory setStackSnapshotsDir msystem cleanGhcSnapshots deletion cwd ghcver Compilers -> removeGhcVersionInstallation deletion ghcver msystem GHC -> do removeCmd deletion Compilers Nothing ghcver msystem removeCmd deletion Snapshots Nothing ghcver msystem Default -> do isProject <- doesDirectoryExist ".stack-work" if isProject then removeCmd deletion Project Nothing ghcver msystem else removeCmd deletion GHC Nothing ghcver msystem remindDelete deletion removeMinorsCmd :: Deletion -> Mode -> Maybe Recursion -> Maybe Version -> Maybe String -> IO () removeMinorsCmd deletion mode mrecursion mver msystem = do withRecursion True mrecursion $ case mode of Project -> do cwd <- getCurrentDirectory setStackWorkInstallDir msystem cleanMinorSnapshots deletion cwd mver Snapshots -> do cwd <- getCurrentDirectory setStackSnapshotsDir msystem cleanMinorSnapshots deletion cwd mver Compilers -> removeGhcMinorInstallation deletion mver msystem GHC -> do removeMinorsCmd deletion Compilers Nothing mver msystem removeMinorsCmd deletion Snapshots Nothing mver msystem Default -> do isProject <- doesDirectoryExist ".stack-work" if isProject then removeMinorsCmd deletion Project Nothing mver msystem else removeMinorsCmd deletion GHC Nothing mver msystem remindDelete deletion purgeOlderCmd :: Deletion -> Natural -> Maybe Recursion -> Maybe String -> IO () purgeOlderCmd deletion keep mrecursion msystem = do withRecursion True mrecursion $ cleanOldStackWork deletion keep msystem remindDelete deletion deleteWorkCmd :: Deletion -> Maybe Recursion -> IO () deleteWorkCmd deletion mrecursion = do withRecursion False mrecursion $ removeStackWork deletion remindDelete deletion findStackWorks :: IO [FilePath] findStackWorks = -- ignore find errors (e.g. access rights) L.sort . lines <$> cmdIgnoreErr "find" [".", "-type", "d", "-name", ".stack-work", "-prune"] [] listStackSubdirs :: IO [FilePath] listStackSubdirs = listDirectory "." >>= filterM (\f -> doesDirectoryExist (f ".stack-work")) . L.sort remindDelete :: Deletion -> IO () remindDelete deletion = unless (isDelete deletion) $ putStrLn "\n(use --delete for removal)"