├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── Setup.hs ├── cabal.project ├── changelog.md ├── packunused.cabal └── packunused.hs /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | /dist/ 3 | /dist-newstyle/ 4 | /.stack-work/ 5 | stack.yaml 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # This file has been generated by `make_travis_yml_2.hs` 2 | # see https://github.com/hvr/multi-ghc-travis for more information 3 | language: c 4 | sudo: false 5 | 6 | git: 7 | submodules: false # whether to recursively clone submodules 8 | 9 | cache: 10 | directories: 11 | - $HOME/.cabal/packages 12 | - $HOME/.cabal/store 13 | 14 | before_cache: 15 | - rm -fv $HOME/.cabal/packages/hackage.haskell.org/build-reports.log 16 | # remove files that are regenerated by 'cabal update' 17 | - rm -fv $HOME/.cabal/packages/hackage.haskell.org/00-index.* 18 | - rm -fv $HOME/.cabal/packages/hackage.haskell.org/*.json 19 | - rm -fv $HOME/.cabal/packages/hackage.haskell.org/01-index.cache 20 | - rm -fv $HOME/.cabal/packages/hackage.haskell.org/01-index.tar 21 | - rm -fv $HOME/.cabal/packages/hackage.haskell.org/01-index.tar.idx 22 | 23 | matrix: 24 | include: 25 | - env: CABALVER=head GHCVER=7.4.2 26 | compiler: ": #GHC 7.4.2" 27 | addons: {apt: {packages: [cabal-install-head,ghc-7.4.2], sources: [hvr-ghc]}} 28 | - env: CABALVER=head GHCVER=7.6.3 29 | compiler: ": #GHC 7.6.3" 30 | addons: {apt: {packages: [cabal-install-head,ghc-7.6.3], sources: [hvr-ghc]}} 31 | - env: CABALVER=head GHCVER=7.8.4 32 | compiler: ": #GHC 7.8.4" 33 | addons: {apt: {packages: [cabal-install-head,ghc-7.8.4], sources: [hvr-ghc]}} 34 | - env: CABALVER=head GHCVER=7.10.3 35 | compiler: ": #GHC 7.10.3" 36 | addons: {apt: {packages: [cabal-install-head,ghc-7.10.3], sources: [hvr-ghc]}} 37 | - env: CABALVER=head GHCVER=8.0.2 38 | compiler: ": #GHC 8.0.2" 39 | addons: {apt: {packages: [cabal-install-head,ghc-8.0.2], sources: [hvr-ghc]}} 40 | 41 | before_install: 42 | - unset CC 43 | - export PATH=/opt/ghc/$GHCVER/bin:/opt/cabal/$CABALVER/bin:$PATH 44 | 45 | install: 46 | - cabal --version 47 | - BENCH=${BENCH---enable-benchmarks} 48 | - TEST=${TEST---enable-tests} 49 | - echo "$(ghc --version) [$(ghc --print-project-git-commit-id 2> /dev/null || echo '?')]" 50 | - travis_retry cabal update -v 51 | - sed -i 's/^jobs:/-- jobs:/' ${HOME}/.cabal/config 52 | - cabal new-build ${TEST} ${BENCH} --dep -j2 53 | - cabal new-build ${TEST} ${BENCH} --dep -j2 --constraint 'optparse-applicative == 0.12.*' 54 | 55 | # Here starts the actual work to be performed for the package under test; 56 | # any command which exits with a non-zero exit code causes the build to fail. 57 | script: 58 | - if [ -f configure.ac ]; then autoreconf -i; fi 59 | # this builds all libraries and executables (including tests/benchmarks) 60 | - cabal new-build ${TEST} ${BENCH} 61 | - cabal new-build ${TEST} ${BENCH} --constraint 'optparse-applicative == 0.12.*' 62 | 63 | # there's no 'cabal new-test' yet, so let's emulate for now 64 | - SRC_BASENAME=$(cabal info . | awk '{print $2;exit}') 65 | - TESTS=( $(awk 'tolower($0) ~ /^test-suite / { print $2 }' *.cabal) ); 66 | shopt -s globstar; 67 | RC=true; for T in ${TESTS[@]}; do echo "== $T =="; 68 | if dist-newstyle/build/**/$SRC_BASENAME/**/build/$T/$T; then echo "= $T OK ="; 69 | else echo "= $T FAILED ="; RC=false; fi; done; $RC 70 | - cabal sdist # test that a source-distribution can be generated 71 | 72 | # Check that the resulting source distribution can be built w/o and w tests 73 | - tar -C dist/ -xf dist/$SRC_BASENAME.tar.gz 74 | - "echo 'packages: .' > dist/$SRC_BASENAME/cabal.project" 75 | - cd dist/$SRC_BASENAME/ 76 | - cabal new-build --disable-tests --disable-benchmarks 77 | - rm -rf ./dist-newstyle 78 | - cabal new-build ${TEST} ${BENCH} 79 | 80 | # EOF 81 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012, Herbert Valerio Riedel 2 | 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above 12 | copyright notice, this list of conditions and the following 13 | disclaimer in the documentation and/or other materials provided 14 | with the distribution. 15 | 16 | * Neither the name of Herbert Valerio Riedel nor the names of other 17 | contributors may be used to endorse or promote products derived 18 | from this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 23 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 24 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 25 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 26 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 28 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | `packunused` [![Hackage](https://img.shields.io/hackage/v/packunused.svg)](https://hackage.haskell.org/package/packunused) [![Build Status](https://travis-ci.org/hvr/packunused.svg?branch=master)](https://travis-ci.org/hvr/packunused) 2 | ============ 3 | 4 | See http://hackage.haskell.org/package/packunused for documentation 5 | -------------------------------------------------------------------------------- /Setup.hs: -------------------------------------------------------------------------------- 1 | import Distribution.Simple 2 | main = defaultMain 3 | -------------------------------------------------------------------------------- /cabal.project: -------------------------------------------------------------------------------- 1 | -- used by cabal 1.24+ 2 | packages: . 3 | -------------------------------------------------------------------------------- /changelog.md: -------------------------------------------------------------------------------- 1 | # 0.1.2 2 | 3 | - Add support for `Cabal-1.24` (and drop support for previous Cabal versions) 4 | - Add experimental support for Stack 5 | 6 | # 0.1.1.4 7 | 8 | - Add support for `Cabal-1.22.*` and `base-4.8.*` 9 | 10 | # 0.1.1.3 11 | 12 | - Allow `optparse-applicative-0.11.*` and `haskell-src-exts-1.16.*` 13 | 14 | # 0.1.1.2 15 | 16 | - New option `--ignore-package` to white-list redundant packages by name 17 | - Exit with a non-zero status code if (non-ignored) redundant dependencies are found 18 | - Fake support for parsing `-XSafeHaskell` and `-XExplicitNamespaces` in .imports files 19 | - Switched from `cmdargs` to `optparse-applicative` 20 | 21 | # 0.1.1.1 22 | 23 | - Minor typo in output messages fixed 24 | 25 | # 0.1.1.0 26 | 27 | - Update to support `Cabal-1.18`/`Cabal-1.20`, GHC 7.8.1+, and `haskell-src-exts-1.15` 28 | 29 | # 0.1.0.1 30 | 31 | - Add support for `haskell-src-exts-1.14.0` 32 | 33 | # 0.1.0.0 34 | 35 | - Initial release 36 | -------------------------------------------------------------------------------- /packunused.cabal: -------------------------------------------------------------------------------- 1 | name: packunused 2 | version: 0.1.2 3 | synopsis: Tool for detecting redundant Cabal package dependencies 4 | homepage: https://github.com/hvr/packunused 5 | bug-reports: https://github.com/hvr/packunused/issues 6 | license: BSD3 7 | license-file: LICENSE 8 | author: Herbert Valerio Riedel 9 | maintainer: Herbert Valerio Riedel 10 | copyright: © 2014 Herbert Valerio Riedel 11 | category: Distribution 12 | build-type: Simple 13 | cabal-version: >=1.10 14 | tested-with: GHC==8.0.2, GHC==7.10.3, GHC==7.8.4, GHC==7.6.3, GHC==7.4.2 15 | description: 16 | This simple CLI tool allows to find out which of the packages listed as 17 | @build-depends@ in a Cabal package description file are redundant. 18 | . 19 | @packunused@ works by taking advantage of GHC's 20 | @-ddump-minimal-imports@ feature which creates @.import@ files for 21 | each compiled module containing a /minimal/ set of explicit import 22 | declarations. These @.import@ files together with Cabal's generated 23 | package configuration meta-data is analyzed by @packunused@ to 24 | detect potentially redundant package dependencies. 25 | . 26 | In order to use @packunused@ you have to configure the package as 27 | usual. See the example session below: 28 | . 29 | > cabal clean 30 | > rm *.imports 31 | > cabal configure -O0 --disable-library-profiling 32 | > cabal build --ghc-option=-ddump-minimal-imports 33 | > packunused 34 | . 35 | Experimental support for @stack@: 36 | . 37 | > stack setup --upgrade-cabal # necessary only when stack's global Cabal installation is out of date 38 | > stack clean 39 | > stack build --ghc-options '-ddump-minimal-imports -O0' 40 | > packunused 41 | . 42 | The @-O0 --disable-library-profiling@ options are just to speed up 43 | compilation. In some cases you might want to pass additional options 44 | to the @configure@ step, such as @--enable-benchmark@ or 45 | @--enable-tests@. 46 | . 47 | When run, @packunused@ prints a report to standard output listing 48 | its findings for each component built. The following is an example 49 | for the @packunused@ package itself, with a redundant dependency 50 | added for demonstration on purpose: 51 | . 52 | > detected package components 53 | > ~~~~~~~~~~~~~~~~~~~~~~~~~~~ 54 | > 55 | > - executable(s): packunused 56 | > 57 | > (component names suffixed with '*' are not configured to be built) 58 | > 59 | > executable(packunused) 60 | > ~~~~~~~~~~~~~~~~~~~~~~ 61 | > 62 | > The following package dependencies seem redundant: 63 | > 64 | > - criterion-0.6.2.0-9dd4d764629a47662168743b2dfda9bc 65 | > 66 | 67 | extra-source-files: changelog.md 68 | 69 | source-repository head 70 | type: git 71 | location: https://github.com/hvr/packunused.git 72 | 73 | executable packunused 74 | main-is: packunused.hs 75 | other-modules: Paths_packunused 76 | default-language: Haskell2010 77 | other-extensions: CPP, RecordWildCards 78 | ghc-options: -Wall -fwarn-tabs -fno-warn-unused-do-bind 79 | build-depends: base >= 4.5 && < 4.11 80 | , Cabal >= 2.0 && < 2.1 81 | , optparse-applicative >= 0.8 && < 0.15 82 | , directory >= 1.1 && < 1.4 83 | , filepath >= 1.3 && < 1.5 84 | , haskell-src-exts >= 1.18.2 && < 1.21 85 | , process >= 1.1 && < 1.7 86 | , split >= 0.2 && < 0.3 87 | -------------------------------------------------------------------------------- /packunused.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE CPP, RecordWildCards #-} 2 | 3 | module Main where 4 | 5 | import Control.Monad 6 | import Data.IORef 7 | import Data.List 8 | import Data.List.Split (splitOn) 9 | import Data.Maybe 10 | import Data.Monoid 11 | import qualified Data.Version as V(showVersion) 12 | import Distribution.InstalledPackageInfo (exposedName, exposedModules, InstalledPackageInfo) 13 | import Distribution.ModuleName (ModuleName) 14 | import qualified Distribution.ModuleName as MN 15 | import Distribution.Package (UnitId, unUnitId, installedUnitId, packageId, pkgName) 16 | import qualified Distribution.PackageDescription as PD 17 | import Distribution.Simple.Compiler 18 | import Distribution.Simple.Configure (tryGetPersistBuildConfig, ConfigStateFileError(..) 19 | ,localBuildInfoFile, checkPersistBuildConfigOutdated) 20 | import Distribution.Simple.LocalBuildInfo 21 | import Distribution.Simple.PackageIndex (lookupUnitId, PackageIndex) 22 | import Distribution.Simple.Utils (cabalVersion) 23 | import Distribution.Text (display) 24 | import Distribution.Types.ForeignLib(ForeignLib(..)) 25 | import Distribution.Types.MungedPackageId(MungedPackageId(..)) 26 | import Distribution.Types.UnqualComponentName(unUnqualComponentName) 27 | import Distribution.Version(showVersion, mkVersion) 28 | import qualified Language.Haskell.Exts as H 29 | import Options.Applicative 30 | import Options.Applicative.Help.Pretty (Doc) 31 | import qualified Options.Applicative.Help.Pretty as P 32 | import System.Directory (getModificationTime, getDirectoryContents, doesDirectoryExist, doesFileExist, getCurrentDirectory) 33 | import System.Exit (exitFailure) 34 | import System.FilePath ((), takeDirectory) 35 | import System.Process 36 | 37 | import Paths_packunused (version) 38 | 39 | -- | CLI Options 40 | data Opts = Opts 41 | { ignoreEmptyImports :: Bool 42 | , ignoreMainModule :: Bool 43 | , ignoredPackages :: [String] 44 | } deriving (Show) 45 | 46 | opts :: Parser Opts 47 | opts = Opts <$> switch (long "ignore-empty-imports" <> help "ignore empty .imports files") 48 | <*> switch (long "ignore-main-module" <> help "ignore Main modules") 49 | <*> many (strOption (long "ignore-package" <> metavar "PKG" <> 50 | help "ignore the specfied package in the report")) 51 | 52 | usageFooter :: Doc 53 | usageFooter = mconcat 54 | [ P.text "Tool to help find redundant build-dependencies in CABAL projects", P.linebreak 55 | , P.hardline 56 | , para $ "In order to use this tool you should set up the package to be analyzed as follows, " ++ 57 | "before executing 'packunused':", P.linebreak 58 | 59 | , P.hardline 60 | , P.text "For cabal:" 61 | , P.indent 2 $ P.vcat $ P.text <$> 62 | [ "cabal clean" 63 | , "rm *.imports # (only needed for GHC<7.8)" 64 | , "cabal configure -O0 --disable-library-profiling" 65 | , "cabal build --ghc-option=-ddump-minimal-imports" 66 | , "packunused" 67 | ] 68 | 69 | , P.linebreak, P.hardline 70 | , P.text "For stack:" 71 | , P.indent 2 $ P.vcat $ P.text <$> 72 | [ "stack clean" 73 | , "stack build --ghc-options=-ddump-minimal-imports" 74 | , "packunused" 75 | ] 76 | 77 | , P.linebreak, P.hardline 78 | , P.text "Note:" P.<+> P.align 79 | (para $ "The 'cabal configure' command above only tests the default package configuration. " ++ 80 | "You might need to repeat the process with different flags added to the 'cabal configure' step " ++ 81 | "(such as '--enable-tests' or '--enable-benchmark' or custom cabal flags) " ++ 82 | "to make sure to check all configurations") 83 | , P.linebreak, P.hardline 84 | , P.text "Report bugs to https://github.com/hvr/packunused/issues" 85 | ] 86 | where 87 | para = P.fillSep . map P.text . words 88 | 89 | usageHeader :: String 90 | usageHeader = "packunused " ++ V.showVersion version ++ 91 | " (using Cabal "++ showVersion cabalVersion ++ ")" 92 | 93 | getInstalledPackageInfos :: [(UnitId, MungedPackageId)] -> PackageIndex InstalledPackageInfo -> [InstalledPackageInfo] 94 | getInstalledPackageInfos pkgs ipkgs = 95 | [ ipi 96 | | (ipkgid, _) <- pkgs 97 | , not (isInPlacePackage ipkgid) 98 | , Just ipi <- [lookupUnitId ipkgs ipkgid] 99 | ] 100 | where 101 | isInPlacePackage :: UnitId -> Bool 102 | isInPlacePackage u = 103 | "-inplace" `isSuffixOf` unUnitId u 104 | 105 | chooseDistPref :: Bool -> IO String 106 | chooseDistPref useStack = 107 | if useStack 108 | then takeWhile (/= '\n') <$> readProcess "stack" (words "path --dist-dir") "" 109 | else return "dist" 110 | 111 | getLbi :: Bool -> FilePath -> IO LocalBuildInfo 112 | getLbi useStack distPref = either explainError id <$> tryGetPersistBuildConfig distPref 113 | where 114 | explainError :: ConfigStateFileError -> a 115 | explainError x@ConfigStateFileBadVersion{} | useStack = stackExplanation x 116 | explainError x = error ("Error: " ++ show x) 117 | stackExplanation x = error ("Error: " ++ show x ++ "\n\nYou can probably fix this by running:\n stack setup --upgrade-cabal") 118 | 119 | main :: IO () 120 | main = do 121 | Opts {..} <- execParser $ 122 | info (helper <*> opts) 123 | (header usageHeader <> 124 | fullDesc <> 125 | footerDoc (Just usageFooter)) 126 | 127 | -- print opts' 128 | 129 | useStack <- findRecursive "stack.yaml" 130 | distPref <- chooseDistPref useStack 131 | 132 | lbiExists <- doesFileExist (localBuildInfoFile distPref) 133 | unless lbiExists $ do 134 | putStrLn "*ERROR* package not properly configured yet or not in top-level CABAL project folder; see --help for more details" 135 | exitFailure 136 | 137 | lbiMTime <- getModificationTime (localBuildInfoFile distPref) 138 | lbi <- getLbi useStack distPref 139 | 140 | -- minory sanity checking 141 | case pkgDescrFile lbi of 142 | Nothing -> fail "could not find .cabal file" 143 | Just pkgDescFile -> do 144 | res <- checkPersistBuildConfigOutdated distPref pkgDescFile 145 | when res $ putStrLn "*WARNING* outdated config-data -- please re-configure" 146 | 147 | let cbo = map 148 | (\x -> case x of 149 | LibComponentLocalBuildInfo {componentLocalName = n} -> n 150 | FLibComponentLocalBuildInfo {componentLocalName = n} -> n 151 | ExeComponentLocalBuildInfo {componentLocalName = n} -> n 152 | TestComponentLocalBuildInfo {componentLocalName = n} -> n 153 | BenchComponentLocalBuildInfo {componentLocalName = n} -> n 154 | ) $ allComponentsInBuildOrder lbi 155 | pkg = localPkgDescr lbi 156 | ipkgs = installedPkgs lbi 157 | 158 | importsInOutDir <- case compilerId (compiler lbi) of 159 | CompilerId GHC v | v >= mkVersion [7,8] -> return True 160 | CompilerId GHC _ -> return False 161 | CompilerId _ _ -> putStrLn "*WARNING* non-GHC compiler detected" >> return False 162 | 163 | putHeading "detected package components" 164 | 165 | when (isJust $ PD.library pkg) $ 166 | putStrLn $ " - library" ++ [ '*' | CLibName `notElem` cbo ] 167 | 168 | unless (null $ PD.subLibraries pkg) $ 169 | putStrLn $ " - sub lib(s): " ++ unwords [ maybe "Nothing" unUnqualComponentName mayN ++ [ '*' | maybe True (flip notElem cbo . CSubLibName) mayN ] 170 | | PD.Library { libName = mayN } <- PD.subLibraries pkg ] 171 | unless (null $ PD.foreignLibs pkg) $ 172 | putStrLn $ " - foreign lib(s): " ++ unwords [ unUnqualComponentName n ++ [ '*' | CFLibName n `notElem` cbo ] 173 | | ForeignLib { foreignLibName = n } <- PD.foreignLibs pkg ] 174 | unless (null $ PD.executables pkg) $ 175 | putStrLn $ " - executable(s): " ++ unwords [ unUnqualComponentName n ++ [ '*' | CExeName n `notElem` cbo ] 176 | | PD.Executable { exeName = n } <- PD.executables pkg ] 177 | unless (null $ PD.testSuites pkg) $ 178 | putStrLn $ " - testsuite(s): " ++ unwords [ unUnqualComponentName n ++ [ '*' | CTestName n `notElem` cbo ] 179 | | PD.TestSuite { testName = n } <- PD.testSuites pkg ] 180 | unless (null $ PD.benchmarks pkg) $ 181 | putStrLn $ " - benchmark(s): " ++ unwords [ unUnqualComponentName n ++ [ '*' | CBenchName n `notElem` cbo ] 182 | | PD.Benchmark { benchmarkName = n} <- PD.benchmarks pkg ] 183 | 184 | putStrLn "" 185 | putStrLn "(component names suffixed with '*' are not configured to be built)" 186 | putStrLn "" 187 | 188 | ---------------------------------------------------------------------------- 189 | 190 | -- GHC prior to 7.8.1 emitted .imports file in $PWD and therefore would risk overwriting files 191 | let multiMainIssue = not importsInOutDir && length (filter (/= CLibName) cbo) > 1 192 | 193 | 194 | ok <- newIORef True 195 | 196 | -- handle stanzas 197 | withAllComponentsInBuildOrder pkg lbi $ \c clbi -> do 198 | let (n,n2,cmods) = componentNameAndModules (not ignoreMainModule) c 199 | outDir = if null n2 then buildDir lbi else buildDir lbi n2 n2++"-tmp" 200 | outDir' = if importsInOutDir then outDir else "." 201 | 202 | -- import dependancy graph read in via '.imports' files 203 | mods <- mapM (readImports outDir') =<< findImportsFiles outDir' lbiMTime 204 | 205 | -- imported modules by component 206 | let allmods | ignoreEmptyImports = nub [ m | (mn, imps) <- mods 207 | , mn `elem` cmods 208 | , (m,_:_) <- imps 209 | ] 210 | | otherwise = nub [ m | (mn, imps) <- mods 211 | , mn `elem` cmods 212 | , (m,_) <- imps 213 | ] 214 | 215 | ipinfos = getInstalledPackageInfos (componentPackageDeps clbi) ipkgs 216 | 217 | (ignored, unignored) = partition (\x -> display (pkgName $ packageId x) `elem` ignoredPackages) ipinfos 218 | 219 | unused :: [UnitId] 220 | unused = [ installedUnitId ipinfo 221 | | ipinfo <- unignored 222 | , let expmods = map exposedName $ exposedModules ipinfo 223 | , not (any (`elem` allmods) expmods) 224 | ] 225 | 226 | missingMods = cmods \\ map fst mods 227 | 228 | -- print out redundant package ids (if any) 229 | putHeading n 230 | 231 | unless (null missingMods) $ do 232 | putStrLn "*WARNING* dependency information for the following component module(s) is missing: " 233 | forM_ missingMods $ \m -> putStrLn $ " - " ++ display m 234 | putStrLn "" 235 | 236 | when (not ignoreMainModule && multiMainIssue && not (compIsLib c)) $ do 237 | putStrLn "*WARNING* multiple non-library components detected" 238 | putStrLn " result may be unreliable if there are multiple non-library components because the 'Main.imports' file gets overwritten with GHC prior to version 7.8.1, try" 239 | putStrLn "" 240 | putStrLn $ " rm "++(outDir "Main.h")++"; cabal build --ghc-option=-ddump-minimal-imports; packunused" 241 | putStrLn "" 242 | putStrLn " to get a more accurate result for this component." 243 | putStrLn "" 244 | 245 | unless (null ignored) $ do 246 | let k = length ignored 247 | putStrLn $ "Ignoring " ++ show k ++ " package" ++ (if k == 1 then "" else "s") 248 | putStrLn "" 249 | 250 | if null unused 251 | then do 252 | putStrLn "no redundant packages dependencies found" 253 | putStrLn "" 254 | else do 255 | putStrLn "The following package dependencies seem redundant:" 256 | putStrLn "" 257 | forM_ unused $ \pkg' -> putStrLn $ " - " ++ display pkg' 258 | putStrLn "" 259 | writeIORef ok False 260 | 261 | whenM (not <$> readIORef ok) exitFailure 262 | where 263 | compIsLib CLib {} = True 264 | compIsLib _ = False 265 | 266 | findImportsFiles outDir lbiMTime = do 267 | whenM (not `fmap` doesDirectoryExist outDir) $ 268 | fail $"output-dir " ++ show outDir ++ " does not exist; -- has 'cabal build' been performed yet? (see also 'packunused --help')" 269 | 270 | files <- filterM (doesFileExist . (outDir)) =<< 271 | liftM (sort . filter (isSuffixOf ".imports")) 272 | (getDirectoryContents outDir) 273 | 274 | when (null files) $ 275 | fail $ "no .imports files found in " ++ show outDir ++ " -- has 'cabal build' been performed yet? (see also 'packunused --help')" 276 | 277 | -- .import files generated after lbi 278 | files' <- filterM (liftM (> lbiMTime) . getModificationTime . (outDir)) files 279 | 280 | unless (files' == files) $ do 281 | putStrLn "*WARNING* some possibly outdated .imports were found (please consider removing/rebuilding them):" 282 | forM_ (files \\ files') $ \fn -> putStrLn $ " - " ++ fn 283 | putStrLn "" 284 | 285 | -- when (null files') $ 286 | -- fail "no up-to-date .imports files found -- please perform 'cabal build'" 287 | 288 | return files 289 | 290 | componentNameAndModules :: Bool -> Component -> (String, String, [ModuleName]) 291 | componentNameAndModules addMainMod c = (n, n2, m) 292 | where 293 | m = nub $ sort $ m0 ++ PD.otherModules (componentBuildInfo c) 294 | 295 | (n, n2, m0) = case c of 296 | CLib ci -> ("library", "", PD.exposedModules ci) 297 | CFLib ci -> let name = unUnqualComponentName (foreignLibName ci) 298 | in ("foreignLib("++name++")", name, [mainModName | addMainMod ]) 299 | CExe ci -> let name = unUnqualComponentName (PD.exeName ci) 300 | in ("executable("++name++")", name, [mainModName | addMainMod ]) 301 | CBench ci -> let name = unUnqualComponentName (PD.benchmarkName ci) 302 | in ("benchmark("++name++")", name, [mainModName | addMainMod ]) 303 | CTest ci -> let name = unUnqualComponentName (PD.testName ci) 304 | in ("testsuite("++name++")", name, [mainModName | addMainMod ]) 305 | 306 | mainModName = MN.fromString "Main" 307 | 308 | putHeading :: String -> IO () 309 | putHeading s = do 310 | putStrLn s 311 | putStrLn (replicate (length s) '~') 312 | putStrLn "" 313 | 314 | -- empty symbol list means '()' 315 | readImports :: FilePath -> FilePath -> IO (ModuleName, [(ModuleName, [String])]) 316 | readImports outDir fn = do 317 | unless (".imports" `isSuffixOf` fn) $ 318 | fail ("argument "++show fn++" doesn't have .imports extension") 319 | 320 | let m = MN.fromString $ take (length fn - length ".imports") fn 321 | 322 | contents <- readFile (outDir fn) 323 | case parseImportsFile contents of 324 | (H.ParseOk (H.Module _ _ _ imps _)) -> do 325 | let imps' = [ (MN.fromString mn, extractSpecs (H.importSpecs imp)) 326 | | imp <- imps, let H.ModuleName _ mn = H.importModule imp ] 327 | 328 | return (m, imps') 329 | (H.ParseOk (H.XmlPage _ _ _ _ _ _ _)) -> do 330 | putStrLn "*ERROR* .imports file is invalid file type" 331 | exitFailure 332 | (H.ParseOk (H.XmlHybrid _ _ _ _ _ _ _ _ _)) -> do 333 | putStrLn "*ERROR* .imports file is invalid file type" 334 | exitFailure 335 | H.ParseFailed loc msg -> do 336 | putStrLn "*ERROR* failed to parse .imports file" 337 | putStrLn $ H.prettyPrint loc ++ ": " ++ msg 338 | exitFailure 339 | 340 | where 341 | extractSpecs :: Maybe (H.ImportSpecList s) -> [String] 342 | extractSpecs (Just (H.ImportSpecList _ _ impspecs)) = map H.prettyPrint impspecs 343 | extractSpecs _ = error "unexpected import specs" 344 | 345 | parseImportsFile = H.parseFileContentsWithMode (H.defaultParseMode { H.extensions = exts, H.parseFilename = outDir fn }) . stripExplicitNamespaces . stripSafe 346 | 347 | -- hack to remove -XExplicitNamespaces until haskell-src-exts supports that 348 | stripExplicitNamespaces = unwords . splitOn " type " 349 | stripSafe = unwords . splitOn " safe " 350 | 351 | exts = map H.EnableExtension [ H.MagicHash, H.PackageImports, H.CPP, H.TypeOperators, H.TypeFamilies {- , H.ExplicitNamespaces -} ] 352 | 353 | -- | Find if a file exists in the current directory or any of its 354 | -- parents. 355 | findRecursive :: FilePath -> IO Bool 356 | findRecursive f = do 357 | dir <- getCurrentDirectory 358 | go dir 359 | where 360 | go dir = do 361 | exists <- doesFileExist (dir f) 362 | if exists 363 | then return exists 364 | else 365 | let parent = takeDirectory dir 366 | in if parent == dir 367 | then return False 368 | else go parent 369 | 370 | whenM :: Monad m => m Bool -> m () -> m () 371 | whenM test = (test >>=) . flip when 372 | --------------------------------------------------------------------------------