packages feed

packunused 0.1.1.4 → 0.1.2

raw patch · 3 files changed

+101/−47 lines, 3 filesdep +processdep ~Cabaldep ~basedep ~directory

Dependencies added: process

Dependency ranges changed: Cabal, base, directory, haskell-src-exts, optparse-applicative

Files

changelog.md view
@@ -1,3 +1,8 @@+# 0.1.2++ - Add support for `Cabal-1.24` (and drop support for previous Cabal versions)+ - Add experimental support for Stack+ # 0.1.1.4   - Add support for `Cabal-1.22.*` and `base-4.8.*`
packunused.cabal view
@@ -1,5 +1,5 @@ name:                packunused-version:             0.1.1.4+version:             0.1.2 synopsis:            Tool for detecting redundant Cabal package dependencies homepage:            https://github.com/hvr/packunused bug-reports:         https://github.com/hvr/packunused/issues@@ -11,6 +11,7 @@ category:            Distribution build-type:          Simple cabal-version:       >=1.10+tested-with:         GHC==8.0.2, GHC==7.10.3, GHC==7.8.4, GHC==7.6.3, GHC==7.4.2 description:   This simple CLI tool allows to find out which of the packages listed as   @build-depends@ in a Cabal package description file are redundant.@@ -31,6 +32,13 @@   > cabal build --ghc-option=-ddump-minimal-imports   > packunused   .+  Experimental support for @stack@:+  .+  > stack setup --upgrade-cabal  # necessary only when stack's global Cabal installation is out of date+  > stack clean+  > stack build --ghc-options '-ddump-minimal-imports -O0'+  > packunused+  .   The @-O0 --disable-library-profiling@ options are just to speed up   compilation. In some cases you might want to pass additional options   to the @configure@ step, such as @--enable-benchmark@ or@@ -64,14 +72,15 @@  executable packunused   main-is:             packunused.hs+  other-modules:       Paths_packunused   default-language:    Haskell2010   other-extensions:    CPP, RecordWildCards   ghc-options:         -Wall -fwarn-tabs -fno-warn-unused-do-bind-  build-depends:-    base                 >=4.5  && <4.9,-    Cabal                >=1.14 && <1.23,-    optparse-applicative >=0.8  && <0.12,-    directory            >=1.1  && <1.3,-    filepath             ==1.3.*,-    haskell-src-exts     >=1.13 && <1.17,-    split                ==0.2.*+  build-depends: base                 >= 4.5    && < 4.10+               , Cabal                >= 1.24   && < 1.25+               , optparse-applicative >= 0.8    && < 0.14+               , directory            >= 1.1    && < 1.4+               , filepath             >= 1.3    && < 1.5+               , haskell-src-exts     >= 1.18.2 && < 1.19+               , process              >= 1.1    && < 1.5+               , split                >= 0.2    && < 0.3
packunused.hs view
@@ -9,27 +9,28 @@ import           Data.Maybe import           Data.Monoid import           Data.Version (Version(Version), showVersion)-import           Distribution.InstalledPackageInfo (exposedModules, installedPackageId)-#if MIN_VERSION_Cabal(1,21,0)+import           Distribution.InstalledPackageInfo (exposedModules, InstalledPackageInfo) import           Distribution.InstalledPackageInfo (exposedName)-#endif import           Distribution.ModuleName (ModuleName) import qualified Distribution.ModuleName as MN-import           Distribution.Package (InstalledPackageId(..), packageId, pkgName)+import           Distribution.Package (PackageIdentifier, packageId, pkgName)+import           Distribution.Package (UnitId(..), ComponentId(..), installedUnitId) import qualified Distribution.PackageDescription as PD import           Distribution.Simple.Compiler-import           Distribution.Simple.Configure (localBuildInfoFile, getPersistBuildConfig, checkPersistBuildConfigOutdated)+import           Distribution.Simple.Configure (localBuildInfoFile, checkPersistBuildConfigOutdated)+import           Distribution.Simple.Configure (tryGetPersistBuildConfig, ConfigStateFileError(..)) import           Distribution.Simple.LocalBuildInfo-import           Distribution.Simple.PackageIndex (lookupInstalledPackageId)+import           Distribution.Simple.PackageIndex (lookupUnitId, PackageIndex) import           Distribution.Simple.Utils (cabalVersion) import           Distribution.Text (display) import qualified Language.Haskell.Exts as H import           Options.Applicative import           Options.Applicative.Help.Pretty (Doc) import qualified Options.Applicative.Help.Pretty as P-import           System.Directory (getModificationTime, getDirectoryContents, doesDirectoryExist, doesFileExist)+import           System.Directory (getModificationTime, getDirectoryContents, doesDirectoryExist, doesFileExist, getCurrentDirectory) import           System.Exit (exitFailure)-import           System.FilePath ((</>))+import           System.FilePath ((</>), takeDirectory)+import           System.Process  import           Paths_packunused (version) @@ -54,6 +55,7 @@              "before executing 'packunused':", P.linebreak      , P.hardline+    , P.text "For cabal:"     , P.indent 2 $ P.vcat $ P.text <$>       [ "cabal clean"       , "rm *.imports        # (only needed for GHC<7.8)"@@ -61,7 +63,16 @@       , "cabal build --ghc-option=-ddump-minimal-imports"       , "packunused"       ]+     , P.linebreak, P.hardline+    , P.text "For stack:"+    , P.indent 2 $ P.vcat $ P.text <$>+      [ "stack clean"+      , "stack build --ghc-options=-ddump-minimal-imports"+      , "packunused"+      ]++    , P.linebreak, P.hardline     , P.text "Note:" P.<+> P.align       (para $ "The 'cabal configure' command above only tests the default package configuration. " ++               "You might need to repeat the process with different flags added to the 'cabal configure' step " ++@@ -77,6 +88,32 @@ helpHeader = "packunused " ++ showVersion version ++              " (using Cabal "++ showVersion cabalVersion ++ ")" +getInstalledPackageInfos :: [(UnitId, PackageIdentifier)] -> PackageIndex InstalledPackageInfo -> [InstalledPackageInfo]+getInstalledPackageInfos pkgs ipkgs =+    [ ipi+    | (ipkgid, _) <- pkgs+    , not (isInPlacePackage ipkgid)+    , Just ipi <- [lookupUnitId ipkgs ipkgid]+    ]+  where+    isInPlacePackage :: UnitId -> Bool+    isInPlacePackage (SimpleUnitId (ComponentId comp)) =+        "-inplace" `isSuffixOf` comp++chooseDistPref :: Bool -> IO String+chooseDistPref useStack = do+  if useStack+    then takeWhile (/= '\n') <$> readProcess "stack" (words "path --dist-dir") ""+    else return "dist"++getLbi :: Bool -> FilePath -> IO LocalBuildInfo+getLbi useStack distPref = either explainError id <$> tryGetPersistBuildConfig distPref+  where+    explainError :: ConfigStateFileError -> a+    explainError x@ConfigStateFileBadVersion{} | useStack = stackExplanation x+    explainError x = error ("Error: " ++ show x)+    stackExplanation x = error ("Error: " ++ show x ++ "\n\nYou can probably fix this by running:\n  stack setup --upgrade-cabal")+ main :: IO () main = do     Opts {..} <- execParser $@@ -87,14 +124,16 @@      -- print opts' +    useStack <- findRecursive "stack.yaml"+    distPref <- chooseDistPref useStack+     lbiExists <- doesFileExist (localBuildInfoFile distPref)     unless lbiExists $ do         putStrLn "*ERROR* package not properly configured yet or not in top-level CABAL project folder; see --help for more details"         exitFailure      lbiMTime <- getModificationTime (localBuildInfoFile distPref)-    lbi <- getPersistBuildConfig distPref-+    lbi <- getLbi useStack distPref      -- minory sanity checking     case pkgDescrFile lbi of@@ -157,22 +196,14 @@                                                     , (m,_) <- imps                                                     ] -            pkgs = componentPackageDeps clbi--            ipinfos = [ fromMaybe (error (show ipkgid)) $ lookupInstalledPackageId ipkgs ipkgid-                      | (ipkgid@(InstalledPackageId i), _) <- pkgs-                      , not ("-inplace" `isSuffixOf` i)-                      ]+            ipinfos = getInstalledPackageInfos (componentPackageDeps clbi) ipkgs              (ignored, unignored) = partition (\x -> display (pkgName $ packageId x) `elem` ignoredPackages) ipinfos -            unused = [ installedPackageId ipinfo+            unused :: [UnitId]+            unused = [ installedUnitId ipinfo                      | ipinfo <- unignored-#if MIN_VERSION_Cabal(1,21,0)                      , let expmods = map exposedName $ exposedModules ipinfo-#else-                     , let expmods = exposedModules ipinfo-#endif                      , not (any (`elem` allmods) expmods)                      ] @@ -213,8 +244,6 @@      whenM (not <$> readIORef ok) exitFailure   where-    distPref = "./dist"-     compIsLib CLib {} = True     compIsLib _       = False @@ -242,11 +271,7 @@          return files -#if MIN_VERSION_Cabal(1,18,0)     compBuildOrder = map fst . allComponentsInBuildOrder-#else-    withAllComponentsInBuildOrder = withComponentsLBI-#endif  componentNameAndModules :: Bool -> Component -> (String, String, [ModuleName]) componentNameAndModules addMainMod c  = (n, n2, m)@@ -261,10 +286,6 @@      mainModName = MN.fromString "Main" -#if !MIN_VERSION_Cabal(1,16,0)-    componentBuildInfo = foldComponent PD.libBuildInfo PD.buildInfo PD.testBuildInfo PD.benchmarkBuildInfo-#endif- putHeading :: String -> IO () putHeading s = do     putStrLn s@@ -281,18 +302,25 @@      contents <- readFile (outDir </> fn)     case parseImportsFile contents of-        (H.ParseOk (H.Module _ _ _ _ _ imps _)) -> do+        (H.ParseOk (H.Module _ _ _ imps _)) -> do             let imps' = [ (MN.fromString mn, extractSpecs (H.importSpecs imp))-                        | imp <- imps, let H.ModuleName mn = H.importModule imp ]+                        | imp <- imps, let H.ModuleName _ mn = H.importModule imp ]              return (m, imps')+        (H.ParseOk (H.XmlPage _ _ _ _ _ _ _)) -> do+            putStrLn "*ERROR* .imports file is invalid file type"+            exitFailure+        (H.ParseOk (H.XmlHybrid _ _ _ _ _ _ _ _ _)) -> do+            putStrLn "*ERROR* .imports file is invalid file type"+            exitFailure         H.ParseFailed loc msg -> do             putStrLn "*ERROR* failed to parse .imports file"             putStrLn $ H.prettyPrint loc ++ ": " ++ msg             exitFailure    where-    extractSpecs (Just (False, impspecs)) = map H.prettyPrint impspecs+    extractSpecs :: Maybe (H.ImportSpecList s) -> [String]+    extractSpecs (Just (H.ImportSpecList _ _ impspecs)) = map H.prettyPrint impspecs     extractSpecs _ = error "unexpected import specs"      parseImportsFile = H.parseFileContentsWithMode (H.defaultParseMode { H.extensions = exts, H.parseFilename = outDir </> fn }) . stripExplicitNamespaces . stripSafe@@ -301,12 +329,24 @@     stripExplicitNamespaces = unwords . splitOn " type "     stripSafe = unwords . splitOn " safe " -#if MIN_VERSION_haskell_src_exts(1,14,0)     exts = map H.EnableExtension [ H.MagicHash, H.PackageImports, H.CPP, H.TypeOperators, H.TypeFamilies {- , H.ExplicitNamespaces -} ]-#else-    exts = [ H.MagicHash, H.PackageImports, H.CPP, H.TypeOperators, H.TypeFamilies ]-#endif +-- | Find if a file exists in the current directory or any of its+-- parents.+findRecursive :: FilePath -> IO Bool+findRecursive f = do+  dir <- getCurrentDirectory+  go dir+  where+    go dir = do+      exists <- doesFileExist (dir </> f)+      if exists+      then return exists+      else+        let parent = takeDirectory dir+        in if parent == dir+           then return False+           else go parent  whenM :: Monad m => m Bool -> m () -> m () whenM test = (test >>=) . flip when