From 0206085662c58ae37e750ad0b8cc4b0f83003505 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Gr=C3=B6ber?= Date: Wed, 23 Apr 2014 04:08:54 +0200 Subject: [PATCH 01/17] Generate ghc package.cache before running spec --- test/Main.hs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/test/Main.hs b/test/Main.hs index 859ffd9..dbec67a 100644 --- a/test/Main.hs +++ b/test/Main.hs @@ -8,5 +8,10 @@ main = do let sandboxes = [ "test/data", "test/data/check-packageid" ] genSandboxCfg dir = withDirectory dir $ \cwd -> do system ("sed 's|@CWD@|" ++ cwd ++ "|g' cabal.sandbox.config.in > cabal.sandbox.config") - genSandboxCfg `mapM` sandboxes + pkgDirs = + [ "test/data/.cabal-sandbox/i386-osx-ghc-7.6.3-packages.conf.d" + , "test/data/check-packageid/.cabal-sandbox/i386-osx-ghc-7.6.3-packages.conf.d"] + genGhcPkgCache dir = system $ "ghc-pkg recache --force -f" ++ dir + genSandboxCfg `mapM_` sandboxes + genGhcPkgCache `mapM_` pkgDirs hspec spec From ee0135aac2917add3ed36186689f9813a1057693 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Gr=C3=B6ber?= Date: Wed, 23 Apr 2014 04:15:55 +0200 Subject: [PATCH 02/17] Correct check-packageid/cabal.sandbox.config.in --- test/data/check-packageid/cabal.sandbox.config.in | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/test/data/check-packageid/cabal.sandbox.config.in b/test/data/check-packageid/cabal.sandbox.config.in index 76ad788..3ba8fda 100644 --- a/test/data/check-packageid/cabal.sandbox.config.in +++ b/test/data/check-packageid/cabal.sandbox.config.in @@ -4,15 +4,15 @@ -- if you want to change the default settings for this sandbox. -local-repo: @CWD@/test/data/.cabal-sandbox/packages -logs-dir: @CWD@/test/data/.cabal-sandbox/logs -world-file: @CWD@/test/data/.cabal-sandbox/world +local-repo: @CWD@/test/data/check-packageid/.cabal-sandbox/packages +logs-dir: @CWD@/test/data/check-packageid/.cabal-sandbox/logs +world-file: @CWD@/test/data/check-packageid/.cabal-sandbox/world user-install: False -package-db: test/data/check-packageid/.cabal-sandbox/i386-osx-ghc-7.6.3-packages.conf.d -build-summary: @CWD@/test/data/.cabal-sandbox/logs/build.log +package-db: @CWD@/test/data/check-packageid/.cabal-sandbox/i386-osx-ghc-7.6.3-packages.conf.d +build-summary: @CWD@/test/data/check-packageid/.cabal-sandbox/logs/build.log install-dirs - prefix: @CWD@/test/data/.cabal-sandbox + prefix: @CWD@/test/data/check-packageid/.cabal-sandbox bindir: $prefix/bin libdir: $prefix/lib libsubdir: $arch-$os-$compiler/$pkgid From 759a6efab1410735ed40d1e43c8da9ce6c54553e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Gr=C3=B6ber?= Date: Mon, 28 Apr 2014 17:49:15 +0200 Subject: [PATCH 03/17] Add some test data ..for testing behaviour with multiple versions/ids of packges being available --- test/Main.hs | 6 +++-- ...-1.0-7c59d13f32294d1ef6dc6233c24df961.conf | 4 +++ ....0.0-14e543bdae2da4d2aeff5386892c9112.conf | 4 +++ ....0.0-32d4f24abdbb6bf41272b183b2e23e9c.conf | 4 +++ .../duplicate-pkgver/cabal.sandbox.config.in | 25 +++++++++++++++++++ 5 files changed, 41 insertions(+), 2 deletions(-) create mode 100644 test/data/duplicate-pkgver/.cabal-sandbox/i386-osx-ghc-7.6.3-packages.conf.d/template-haskell-1.0-7c59d13f32294d1ef6dc6233c24df961.conf create mode 100644 test/data/duplicate-pkgver/.cabal-sandbox/i386-osx-ghc-7.6.3-packages.conf.d/template-haskell-2.8.0.0-14e543bdae2da4d2aeff5386892c9112.conf create mode 100644 test/data/duplicate-pkgver/.cabal-sandbox/i386-osx-ghc-7.6.3-packages.conf.d/template-haskell-2.8.0.0-32d4f24abdbb6bf41272b183b2e23e9c.conf create mode 100644 test/data/duplicate-pkgver/cabal.sandbox.config.in diff --git a/test/Main.hs b/test/Main.hs index dbec67a..8eea411 100644 --- a/test/Main.hs +++ b/test/Main.hs @@ -5,12 +5,14 @@ import Test.Hspec import System.Process main = do - let sandboxes = [ "test/data", "test/data/check-packageid" ] + let sandboxes = [ "test/data", "test/data/check-packageid" + , "test/data/duplicate-pkgver/" ] genSandboxCfg dir = withDirectory dir $ \cwd -> do system ("sed 's|@CWD@|" ++ cwd ++ "|g' cabal.sandbox.config.in > cabal.sandbox.config") pkgDirs = [ "test/data/.cabal-sandbox/i386-osx-ghc-7.6.3-packages.conf.d" - , "test/data/check-packageid/.cabal-sandbox/i386-osx-ghc-7.6.3-packages.conf.d"] + , "test/data/check-packageid/.cabal-sandbox/i386-osx-ghc-7.6.3-packages.conf.d" + , "test/data/duplicate-pkgver/.cabal-sandbox/i386-osx-ghc-7.6.3-packages.conf.d"] genGhcPkgCache dir = system $ "ghc-pkg recache --force -f" ++ dir genSandboxCfg `mapM_` sandboxes genGhcPkgCache `mapM_` pkgDirs diff --git a/test/data/duplicate-pkgver/.cabal-sandbox/i386-osx-ghc-7.6.3-packages.conf.d/template-haskell-1.0-7c59d13f32294d1ef6dc6233c24df961.conf b/test/data/duplicate-pkgver/.cabal-sandbox/i386-osx-ghc-7.6.3-packages.conf.d/template-haskell-1.0-7c59d13f32294d1ef6dc6233c24df961.conf new file mode 100644 index 0000000..826e7e8 --- /dev/null +++ b/test/data/duplicate-pkgver/.cabal-sandbox/i386-osx-ghc-7.6.3-packages.conf.d/template-haskell-1.0-7c59d13f32294d1ef6dc6233c24df961.conf @@ -0,0 +1,4 @@ +name: template-haskell +version: 1.0 +id: template-haskell-1.0-7c59d13f32294d1ef6dc6233c24df961 +exposed: True diff --git a/test/data/duplicate-pkgver/.cabal-sandbox/i386-osx-ghc-7.6.3-packages.conf.d/template-haskell-2.8.0.0-14e543bdae2da4d2aeff5386892c9112.conf b/test/data/duplicate-pkgver/.cabal-sandbox/i386-osx-ghc-7.6.3-packages.conf.d/template-haskell-2.8.0.0-14e543bdae2da4d2aeff5386892c9112.conf new file mode 100644 index 0000000..9b9bbf2 --- /dev/null +++ b/test/data/duplicate-pkgver/.cabal-sandbox/i386-osx-ghc-7.6.3-packages.conf.d/template-haskell-2.8.0.0-14e543bdae2da4d2aeff5386892c9112.conf @@ -0,0 +1,4 @@ +name: template-haskell +version: 2.8.0.0 +id: template-haskell-2.8.0.0-14e543bdae2da4d2aeff5386892c9112 +exposed: True diff --git a/test/data/duplicate-pkgver/.cabal-sandbox/i386-osx-ghc-7.6.3-packages.conf.d/template-haskell-2.8.0.0-32d4f24abdbb6bf41272b183b2e23e9c.conf b/test/data/duplicate-pkgver/.cabal-sandbox/i386-osx-ghc-7.6.3-packages.conf.d/template-haskell-2.8.0.0-32d4f24abdbb6bf41272b183b2e23e9c.conf new file mode 100644 index 0000000..e8d6158 --- /dev/null +++ b/test/data/duplicate-pkgver/.cabal-sandbox/i386-osx-ghc-7.6.3-packages.conf.d/template-haskell-2.8.0.0-32d4f24abdbb6bf41272b183b2e23e9c.conf @@ -0,0 +1,4 @@ +name: template-haskell +version: 2.8.0.0 +id: template-haskell-2.8.0.0-32d4f24abdbb6bf41272b183b2e23e9c +exposed: True diff --git a/test/data/duplicate-pkgver/cabal.sandbox.config.in b/test/data/duplicate-pkgver/cabal.sandbox.config.in new file mode 100644 index 0000000..2eae397 --- /dev/null +++ b/test/data/duplicate-pkgver/cabal.sandbox.config.in @@ -0,0 +1,25 @@ +-- This is a Cabal package environment file. +-- THIS FILE IS AUTO-GENERATED. DO NOT EDIT DIRECTLY. +-- Please create a 'cabal.config' file in the same directory +-- if you want to change the default settings for this sandbox. + + +local-repo: @CWD@/test/data/duplicate-pkgver/.cabal-sandbox/packages +logs-dir: @CWD@/test/data/duplicate-pkgver/.cabal-sandbox/logs +world-file: @CWD@/test/data/duplicate-pkgver/.cabal-sandbox/world +user-install: False +package-db: @CWD@/test/data/duplicate-pkgver/.cabal-sandbox/i386-osx-ghc-7.6.3-packages.conf.d +build-summary: @CWD@/test/data/duplicate-pkgver/.cabal-sandbox/logs/build.log + +install-dirs + prefix: @CWD@/test/data/duplicate-pkgver/.cabal-sandbox + bindir: $prefix/bin + libdir: $prefix/lib + libsubdir: $arch-$os-$compiler/$pkgid + libexecdir: $prefix/libexec + datadir: $prefix/share + datasubdir: $arch-$os-$compiler/$pkgid + docdir: $datadir/doc/$arch-$os-$compiler/$pkgid + htmldir: $docdir/html + haddockdir: $htmldir + sysconfdir: $prefix/etc From 158c0bba168f9ee2aee77ba160f29d3dd677fdfd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Gr=C3=B6ber?= Date: Mon, 28 Apr 2014 17:50:33 +0200 Subject: [PATCH 04/17] Add package.cache to .gitignore and make .cabal-sandbox absolute --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 30bb9a5..0c36049 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,8 @@ dist/ elisp/*.elc *~ -.cabal-sandbox/ +/.cabal-sandbox/ +package.cache cabal.sandbox.config # Mac OS generates # .DS_Store From 82d1d6b982f8eac549d81b767df31832d216432c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Gr=C3=B6ber?= Date: Mon, 28 Apr 2014 17:51:55 +0200 Subject: [PATCH 05/17] Add cabal file to `duplicate-pkgver` test data It's not really valid but enough to make ghc-mod happy, without it findCradle doesn't work properly and without the empty `library` sections it crashes even. --- test/data/duplicate-pkgver/duplicate-pkgver.cabal | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 test/data/duplicate-pkgver/duplicate-pkgver.cabal diff --git a/test/data/duplicate-pkgver/duplicate-pkgver.cabal b/test/data/duplicate-pkgver/duplicate-pkgver.cabal new file mode 100644 index 0000000..66adf53 --- /dev/null +++ b/test/data/duplicate-pkgver/duplicate-pkgver.cabal @@ -0,0 +1,7 @@ +name: duplicate-pkgver +version: 0.1.0 +build-type: Simple +cabal-version: >= 1.8 + +library + build-depends: base == 4.* From 6ad386d31e1040053c938b6740735cac08ffc537 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Gr=C3=B6ber?= Date: Mon, 28 Apr 2014 19:41:28 +0200 Subject: [PATCH 06/17] Add test data to Extra-Source-Files --- ghc-mod.cabal | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ghc-mod.cabal b/ghc-mod.cabal index d3c4c45..293e365 100644 --- a/ghc-mod.cabal +++ b/ghc-mod.cabal @@ -37,6 +37,11 @@ Extra-Source-Files: ChangeLog test/data/check-test-subdir/test/Bar/*.hs test/data/check-packageid/cabal.sandbox.config.in test/data/check-packageid/.cabal-sandbox/i386-osx-ghc-7.6.3-packages.conf.d/template-haskell-2.8.0.0-32d4f24abdbb6bf41272b183b2e23e9c.conf + test/data/duplicate-pkgver/cabal.sandbox.config.in + test/data/duplicate-pkgver/duplicate-pkgver.cabal + test/data/duplicate-pkgver/.cabal-sandbox/i386-osx-ghc-7.6.3-packages.conf.d/template-haskell-1.0-7c59d13f32294d1ef6dc6233c24df961.conf + test/data/duplicate-pkgver/.cabal-sandbox/i386-osx-ghc-7.6.3-packages.conf.d/template-haskell-2.8.0.0-14e543bdae2da4d2aeff5386892c9112.conf + test/data/duplicate-pkgver/.cabal-sandbox/i386-osx-ghc-7.6.3-packages.conf.d/template-haskell-2.8.0.0-32d4f24abdbb6bf41272b183b2e23e9c.conf test/data/ghc-mod-check/*.cabal test/data/ghc-mod-check/*.hs test/data/ghc-mod-check/Data/*.hs From 77605c6daf54016cf73d156bb99e021d5e18971b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Gr=C3=B6ber?= Date: Thu, 1 May 2014 01:48:49 +0200 Subject: [PATCH 07/17] Add `fromInstalledPackageId` --- Language/Haskell/GhcMod/GhcPkg.hs | 10 ++++++++++ ghc-mod.cabal | 2 ++ 2 files changed, 12 insertions(+) diff --git a/Language/Haskell/GhcMod/GhcPkg.hs b/Language/Haskell/GhcMod/GhcPkg.hs index fa915f6..178afd9 100644 --- a/Language/Haskell/GhcMod/GhcPkg.hs +++ b/Language/Haskell/GhcMod/GhcPkg.hs @@ -6,6 +6,7 @@ module Language.Haskell.GhcMod.GhcPkg ( , ghcPkgDbStackOpts , ghcDbStackOpts , ghcDbOpt + , fromInstalledPackageId , getSandboxDb , getPackageDbStack ) where @@ -16,7 +17,9 @@ import Control.Exception (SomeException(..)) import qualified Control.Exception as E import Data.Char (isSpace,isAlphaNum) import Data.List (isPrefixOf, intercalate) +import Data.List.Split (splitOn) import Data.Maybe (listToMaybe, maybeToList) +import Distribution.Package (InstalledPackageId(..)) import Language.Haskell.GhcMod.Types import Language.Haskell.GhcMod.Utils import System.Exit (ExitCode(..)) @@ -92,6 +95,13 @@ packageLine l = Just ((Hidden,p),_) -> Just p _ -> Nothing +fromInstalledPackageId :: InstalledPackageId -> Maybe Package +fromInstalledPackageId pid = let + InstalledPackageId pkg = pid + in case reverse $ splitOn "-" pkg of + i:v:rest -> Just (intercalate "-" (reverse rest), v, i) + _ -> Nothing + data PackageState = Normal | Hidden | Broken deriving (Eq,Show) packageLineP :: ReadP (PackageState, Package) diff --git a/ghc-mod.cabal b/ghc-mod.cabal index 293e365..7141c4b 100644 --- a/ghc-mod.cabal +++ b/ghc-mod.cabal @@ -88,6 +88,7 @@ Library , syb , time , transformers + , split if impl(ghc < 7.7) Build-Depends: convertible , Cabal >= 1.10 && < 1.17 @@ -159,6 +160,7 @@ Test-Suite spec , time , transformers , hspec >= 1.8.2 + , split if impl(ghc < 7.7) Build-Depends: convertible , Cabal >= 1.10 && < 1.17 From aec46dbd51add71a1a3d80ada668a889659ef50e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Gr=C3=B6ber?= Date: Thu, 1 May 2014 01:48:03 +0200 Subject: [PATCH 08/17] Factor out `readProcess'` --- Language/Haskell/GhcMod/GhcPkg.hs | 13 +------------ Language/Haskell/GhcMod/Utils.hs | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/Language/Haskell/GhcMod/GhcPkg.hs b/Language/Haskell/GhcMod/GhcPkg.hs index 178afd9..90d04c3 100644 --- a/Language/Haskell/GhcMod/GhcPkg.hs +++ b/Language/Haskell/GhcMod/GhcPkg.hs @@ -22,10 +22,7 @@ import Data.Maybe (listToMaybe, maybeToList) import Distribution.Package (InstalledPackageId(..)) import Language.Haskell.GhcMod.Types import Language.Haskell.GhcMod.Utils -import System.Exit (ExitCode(..)) import System.FilePath (()) -import System.IO (hPutStrLn,stderr) -import System.Process (readProcessWithExitCode) import Text.ParserCombinators.ReadP (ReadP, char, between, sepBy1, many1, string, choice, eof) import qualified Text.ParserCombinators.ReadP as P @@ -61,7 +58,6 @@ getPackageDbStack cdir = (getSandboxDb cdir >>= \db -> return [GlobalDb, PackageDb db]) `E.catch` \(_ :: SomeException) -> return [GlobalDb, UserDb] - -- | List packages in one or more ghc package store ghcPkgList :: [GhcPkgDb] -> IO [PackageBaseName] ghcPkgList dbs = map fst3 <$> ghcPkgListEx dbs @@ -69,14 +65,7 @@ ghcPkgList dbs = map fst3 <$> ghcPkgListEx dbs ghcPkgListEx :: [GhcPkgDb] -> IO [Package] ghcPkgListEx dbs = do - (rv,output,err) <- readProcessWithExitCode "ghc-pkg" opts "" - case rv of - ExitFailure val -> do - hPutStrLn stderr err - fail $ "ghc-pkg " ++ unwords opts ++ " (exit " ++ show val ++ ")" - ExitSuccess -> return () - - return $ parseGhcPkgOutput $ lines output + parseGhcPkgOutput .lines <$> readProcess' "ghc-pkg" opts where opts = ["list", "-v"] ++ ghcPkgDbStackOpts dbs diff --git a/Language/Haskell/GhcMod/Utils.hs b/Language/Haskell/GhcMod/Utils.hs index 33af425..33a327f 100644 --- a/Language/Haskell/GhcMod/Utils.hs +++ b/Language/Haskell/GhcMod/Utils.hs @@ -1,5 +1,20 @@ module Language.Haskell.GhcMod.Utils where +import Control.Exception (bracket) +import System.Directory (getCurrentDirectory, setCurrentDirectory) +import System.Process (readProcessWithExitCode) +import System.Exit (ExitCode(..)) +import System.IO (hPutStrLn, stderr) + -- dropWhileEnd is not provided prior to base 4.5.0.0. dropWhileEnd :: (a -> Bool) -> [a] -> [a] dropWhileEnd p = foldr (\x xs -> if p x && null xs then [] else x : xs) [] +readProcess' :: String -> [String] -> IO String +readProcess' cmd opts = do + (rv,output,err) <- readProcessWithExitCode cmd opts "" + case rv of + ExitFailure val -> do + hPutStrLn stderr err + fail $ cmd ++ " " ++ unwords opts ++ " (exit " ++ show val ++ ")" + ExitSuccess -> + return output From 19b56738c6d8b06859aa5c0a5a57d773275abfe9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Gr=C3=B6ber?= Date: Thu, 1 May 2014 02:10:42 +0200 Subject: [PATCH 09/17] Add `withDirectory_` to Utils --- Language/Haskell/GhcMod/Utils.hs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Language/Haskell/GhcMod/Utils.hs b/Language/Haskell/GhcMod/Utils.hs index 33a327f..09a87a5 100644 --- a/Language/Haskell/GhcMod/Utils.hs +++ b/Language/Haskell/GhcMod/Utils.hs @@ -18,3 +18,8 @@ readProcess' cmd opts = do fail $ cmd ++ " " ++ unwords opts ++ " (exit " ++ show val ++ ")" ExitSuccess -> return output + +withDirectory_ :: FilePath -> IO a -> IO a +withDirectory_ dir action = + bracket getCurrentDirectory setCurrentDirectory + (\_ -> setCurrentDirectory dir >> action) From 0c859294a3b1776a8bf29a8beea09498bc4c50d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Gr=C3=B6ber?= Date: Thu, 1 May 2014 01:54:15 +0200 Subject: [PATCH 10/17] Parse cabal `setup-config` to get depencencies --- Language/Haskell/GhcMod/CabalApi.hs | 86 ++++++++++++++++++----------- Language/Haskell/GhcMod/Utils.hs | 13 +++++ test/CabalApiSpec.hs | 13 +++++ 3 files changed, 81 insertions(+), 31 deletions(-) diff --git a/Language/Haskell/GhcMod/CabalApi.hs b/Language/Haskell/GhcMod/CabalApi.hs index 05fee51..07bff93 100644 --- a/Language/Haskell/GhcMod/CabalApi.hs +++ b/Language/Haskell/GhcMod/CabalApi.hs @@ -7,20 +7,26 @@ module Language.Haskell.GhcMod.CabalApi ( , cabalDependPackages , cabalSourceDirs , cabalAllTargets + , cabalGetConfig + , cabalConfigPath + , cabalConfigDependencies ) where import Language.Haskell.GhcMod.Types import Language.Haskell.GhcMod.GhcPkg +import Language.Haskell.GhcMod.Utils import Control.Applicative ((<$>)) -import Control.Exception (throwIO) +import Control.Exception (throwIO,catch,SomeException) import Control.Monad (filterM) import CoreMonad (liftIO) -import Data.Maybe (maybeToList, catMaybes) +import Data.Maybe (maybeToList, mapMaybe) import Data.Set (fromList, toList) +import Data.List (find,tails,isPrefixOf) import Distribution.ModuleName (ModuleName,toFilePath) import Distribution.Package (Dependency(Dependency) - , PackageName(PackageName)) + , PackageName(PackageName) + , InstalledPackageId(..)) import qualified Distribution.Package as C import Distribution.PackageDescription (PackageDescription, BuildInfo, TestSuite, TestSuiteInterface(..), Executable) import qualified Distribution.PackageDescription as P @@ -29,12 +35,14 @@ import Distribution.PackageDescription.Parse (readPackageDescription) import Distribution.Simple.Compiler (CompilerId(..), CompilerFlavor(..)) import Distribution.Simple.Program (ghcProgram) import Distribution.Simple.Program.Types (programName, programFindVersion) +import Distribution.Simple.BuildPaths (defaultDistPref) +import Distribution.Simple.Configure (localBuildInfoFile) import Distribution.System (buildPlatform) import Distribution.Text (display) import Distribution.Verbosity (silent) import Distribution.Version (Version) import System.Directory (doesFileExist) -import System.FilePath (dropExtension, takeFileName, ()) +import System.FilePath (()) ---------------------------------------------------------------- @@ -45,38 +53,13 @@ getCompilerOptions :: [GHCOption] -> IO CompilerOptions getCompilerOptions ghcopts cradle pkgDesc = do gopts <- getGHCOptions ghcopts cradle rdir $ head buildInfos - dbPkgs <- ghcPkgListEx (cradlePkgDbStack cradle) - return $ CompilerOptions gopts idirs (depPkgs dbPkgs) + Just depPkgs <- cabalConfigDependencies <$> cabalGetConfig cradle + return $ CompilerOptions gopts idirs depPkgs where wdir = cradleCurrentDir cradle rdir = cradleRootDir cradle - Just cfile = cradleCabalFile cradle - thisPkg = dropExtension $ takeFileName cfile buildInfos = cabalAllBuildInfo pkgDesc idirs = includeDirectories rdir wdir $ cabalSourceDirs buildInfos - depPkgs ps = attachPackageIds ps - $ removeThem (problematicPackages ++ [thisPkg]) - $ cabalDependPackages buildInfos - ----------------------------------------------------------------- --- Dependent packages - -removeThem :: [PackageBaseName] -> [PackageBaseName] -> [PackageBaseName] -removeThem badpkgs = filter (`notElem` badpkgs) - -problematicPackages :: [PackageBaseName] -problematicPackages = [ - "base-compat" -- providing "Prelude" - ] - -attachPackageIds :: [Package] -> [PackageBaseName] -> [Package] -attachPackageIds pkgs = catMaybes . fmap (`lookup3` pkgs) - -lookup3 :: Eq a => a -> [(a,b,c)] -> Maybe (a,b,c) -lookup3 _ [] = Nothing -lookup3 k (t@(a,_,_):ls) - | k == a = Just t - | otherwise = lookup3 k ls ---------------------------------------------------------------- -- Include directories for modules @@ -220,3 +203,44 @@ cabalAllTargets pd = do getExecutableTarget exe = do let maybeExes = [p e | p <- P.hsSourceDirs $ P.buildInfo exe, e <- [P.modulePath exe]] liftIO $ filterM doesFileExist maybeExes + +---------------------------------------------------------------- + +type CabalConfig = String + +-- | Get file containing 'LocalBuildInfo' data. If it doesn't exist run @cabal +-- configure@ i.e. configure with default options like @cabal build@ would do. +cabalGetConfig :: Cradle -> IO CabalConfig +cabalGetConfig cradle = + readFile path `catch'` (\_ -> configure >> readFile path) + where + catch' = catch :: IO a -> (SomeException -> IO a) -> IO a + prjDir = cradleRootDir cradle + path = prjDir cabalConfigPath + configure = + withDirectory_ prjDir $ readProcess' "cabal" ["configure"] + + +-- | Path to 'LocalBuildInfo' file, usually @dist/setup-config@ +cabalConfigPath :: FilePath +cabalConfigPath = localBuildInfoFile defaultDistPref + +cabalConfigDependencies :: CabalConfig -> Maybe [Package] +cabalConfigDependencies config = + cfgDepends >>= return . mapMaybe (fromInstalledPackageId . snd) + where + cfgDepends :: Maybe [(PackageName, InstalledPackageId)] + cfgDepends = extractCabalSetupConfig "configDependencies" config + + +-- | Extract part of cabal's @setup-config@, this is done with a mix of manual +-- string processing and use of 'read'. This way we can extract a field from +-- 'LocalBuildInfo' without having to parse the whole thing which would mean +-- depending on the exact version of Cabal used to configure the project as it +-- is rather likley that some part of 'LocalBuildInfo' changed. +-- +-- Right now 'extractCabalSetupConfig' can only deal with Lists and Tupels in +-- the field! +extractCabalSetupConfig :: (Read r) => String -> CabalConfig -> Maybe r +extractCabalSetupConfig field config = do + read <$> extractParens <$> find (field `isPrefixOf`) (tails config) diff --git a/Language/Haskell/GhcMod/Utils.hs b/Language/Haskell/GhcMod/Utils.hs index 09a87a5..cd04a3e 100644 --- a/Language/Haskell/GhcMod/Utils.hs +++ b/Language/Haskell/GhcMod/Utils.hs @@ -9,6 +9,19 @@ import System.IO (hPutStrLn, stderr) -- dropWhileEnd is not provided prior to base 4.5.0.0. dropWhileEnd :: (a -> Bool) -> [a] -> [a] dropWhileEnd p = foldr (\x xs -> if p x && null xs then [] else x : xs) [] + +extractParens :: String -> String +extractParens str = extractParens' str 0 + where + extractParens' :: String -> Int -> String + extractParens' [] _ = [] + extractParens' (s:ss) level + | s `elem` "([{" = s : extractParens' ss (level+1) + | level == 0 = extractParens' ss 0 + | s `elem` "}])" && level == 1 = s:[] + | s `elem` "}])" = s : extractParens' ss (level-1) + | otherwise = s : extractParens' ss level + readProcess' :: String -> [String] -> IO String readProcess' cmd opts = do (rv,output,err) <- readProcessWithExitCode cmd opts "" diff --git a/test/CabalApiSpec.hs b/test/CabalApiSpec.hs index 6e4998c..6586ab7 100644 --- a/test/CabalApiSpec.hs +++ b/test/CabalApiSpec.hs @@ -19,6 +19,11 @@ import Config (cProjectVersionInt) -- ghc version ghcVersion :: Int ghcVersion = read cProjectVersionInt +unconfigure :: IO () +unconfigure = do + removeFile cabalConfigPath `catch` (\(_ :: SomeException) -> return ()) + +around' a f = a >> f >> a spec :: Spec spec = do @@ -61,3 +66,11 @@ spec = do it "extracts build info" $ do info <- cabalAllBuildInfo <$> parseCabalFile "test/data/cabalapi.cabal" show info `shouldBe` "[BuildInfo {buildable = True, buildTools = [], cppOptions = [], ccOptions = [], ldOptions = [], pkgconfigDepends = [], frameworks = [], cSources = [], hsSourceDirs = [\".\"], otherModules = [ModuleName [\"Browse\"],ModuleName [\"CabalApi\"],ModuleName [\"Cabal\"],ModuleName [\"CabalDev\"],ModuleName [\"Check\"],ModuleName [\"ErrMsg\"],ModuleName [\"Flag\"],ModuleName [\"GHCApi\"],ModuleName [\"GHCChoice\"],ModuleName [\"Gap\"],ModuleName [\"Info\"],ModuleName [\"Lang\"],ModuleName [\"Lint\"],ModuleName [\"List\"],ModuleName [\"Paths_ghc_mod\"],ModuleName [\"Types\"]], defaultLanguage = Nothing, otherLanguages = [], defaultExtensions = [], otherExtensions = [], oldExtensions = [], extraLibs = [], extraLibDirs = [], includeDirs = [], includes = [], installIncludes = [], options = [(GHC,[\"-Wall\"])], ghcProfOptions = [], ghcSharedOptions = [], customFieldsBI = [], targetBuildDepends = [Dependency (PackageName \"Cabal\") (UnionVersionRanges (ThisVersion (Version {versionBranch = [1,10], versionTags = []})) (LaterVersion (Version {versionBranch = [1,10], versionTags = []}))),Dependency (PackageName \"base\") (IntersectVersionRanges (UnionVersionRanges (ThisVersion (Version {versionBranch = [4,0], versionTags = []})) (LaterVersion (Version {versionBranch = [4,0], versionTags = []}))) (EarlierVersion (Version {versionBranch = [5], versionTags = []}))),Dependency (PackageName \"template-haskell\") AnyVersion]},BuildInfo {buildable = True, buildTools = [], cppOptions = [], ccOptions = [], ldOptions = [], pkgconfigDepends = [], frameworks = [], cSources = [], hsSourceDirs = [\"test\",\".\"], otherModules = [ModuleName [\"Expectation\"],ModuleName [\"BrowseSpec\"],ModuleName [\"CabalApiSpec\"],ModuleName [\"FlagSpec\"],ModuleName [\"LangSpec\"],ModuleName [\"LintSpec\"],ModuleName [\"ListSpec\"]], defaultLanguage = Nothing, otherLanguages = [], defaultExtensions = [], otherExtensions = [], oldExtensions = [], extraLibs = [], extraLibDirs = [], includeDirs = [], includes = [], installIncludes = [], options = [], ghcProfOptions = [], ghcSharedOptions = [], customFieldsBI = [], targetBuildDepends = [Dependency (PackageName \"Cabal\") (UnionVersionRanges (ThisVersion (Version {versionBranch = [1,10], versionTags = []})) (LaterVersion (Version {versionBranch = [1,10], versionTags = []}))),Dependency (PackageName \"base\") (IntersectVersionRanges (UnionVersionRanges (ThisVersion (Version {versionBranch = [4,0], versionTags = []})) (LaterVersion (Version {versionBranch = [4,0], versionTags = []}))) (EarlierVersion (Version {versionBranch = [5], versionTags = []})))]}]" + + describe "cabalGetConfig" $ do + it "can reconfigure a cabal package" $ do + withDirectory_ "test/data/check-test-subdir" + $ around' unconfigure $ do + cradle <- findCradle + cfg <- cabalGetConfig cradle + cfg `shouldSatisfy` not . null From 92d0eec2bcd510410fe6ef348f6951b5f638605d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Gr=C3=B6ber?= Date: Thu, 1 May 2014 02:30:36 +0200 Subject: [PATCH 11/17] Fix failing test --- test/CabalApiSpec.hs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/CabalApiSpec.hs b/test/CabalApiSpec.hs index 6586ab7..f122cda 100644 --- a/test/CabalApiSpec.hs +++ b/test/CabalApiSpec.hs @@ -44,9 +44,9 @@ spec = do } if ghcVersion < 706 then ghcOptions res' `shouldBe` ["-global-package-conf", "-no-user-package-conf","-package-conf",cwd "test/data/.cabal-sandbox/i386-osx-ghc-7.6.3-packages.conf.d","-XHaskell98"] - else ghcOptions res' `shouldBe` ["-global-package-db", "-no-user-package-db","-package-db",cwd "test/data/.cabal-sandbox/i386-osx-ghc-7.6.3-packages.conf.d","-XHaskell98"] + else ghcOptions res' `shouldBe` ["-global-package-db", "-no-user-package-db","-package-db",cwd "test/data/.cabal-sandbox/i386-osx-ghc-7.6.3-packages.conf.d","-XHaskell98","-optP-include","-optP" ++ cwd "test/data/dist/build/autogen/cabal_macros.h"] includeDirs res' `shouldBe` ["test/data","test/data/dist/build","test/data/dist/build/autogen","test/data/subdir1/subdir2","test/data/test"] - depPackages res' `shouldSatisfy` (("Cabal", "1.18.1.3", "2b161c6bf77657aa17e1681d83cb051b")`elem`) + (pkgName `map` depPackages res') `shouldContain` ["Cabal"] describe "cabalDependPackages" $ do From 2839d6b55785f4bce40c4c2786fb708bfa82e49b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Gr=C3=B6ber?= Date: Thu, 1 May 2014 02:32:10 +0200 Subject: [PATCH 12/17] Add UtilsSpec --- test/UtilsSpec.hs | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 test/UtilsSpec.hs diff --git a/test/UtilsSpec.hs b/test/UtilsSpec.hs new file mode 100644 index 0000000..75f61cc --- /dev/null +++ b/test/UtilsSpec.hs @@ -0,0 +1,11 @@ +module UtilsSpec where + +import Language.Haskell.GhcMod.Utils +import Test.Hspec + +spec :: Spec +spec = do + describe "extractParens" $ do + it "extracts the part of a string surrounded by parentheses" $ do + extractParens "asdasdasd ( hello [ world ] )()() kljlkjlkjlk" `shouldBe` "( hello [ world ] )" + extractParens "[(PackageName \"template-haskell\",InstalledPackageId \"template-haskell-2.9.0.0-8e2a49468f3b663b671c437d8579cd28\"),(PackageName \"base\",InstalledPackageId \"base-4.7.0.0-e4567cc9a8ef85f78696b03f3547b6d5\"),(PackageName \"Cabal\",InstalledPackageId \"Cabal-1.18.1.3-b9a44a5b15a8bce47d40128ac326e369\")][][]" `shouldBe` "[(PackageName \"template-haskell\",InstalledPackageId \"template-haskell-2.9.0.0-8e2a49468f3b663b671c437d8579cd28\"),(PackageName \"base\",InstalledPackageId \"base-4.7.0.0-e4567cc9a8ef85f78696b03f3547b6d5\"),(PackageName \"Cabal\",InstalledPackageId \"Cabal-1.18.1.3-b9a44a5b15a8bce47d40128ac326e369\")]" From 7b98cb0e85be9571cc1a74dd8f3ffee09f7306e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Gr=C3=B6ber?= Date: Thu, 1 May 2014 03:07:10 +0200 Subject: [PATCH 13/17] Add `cabal` to PATH in travis script --- .travis.yml | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/.travis.yml b/.travis.yml index 8c03593..993f57e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,26 +8,26 @@ before_install: - sudo add-apt-repository -y ppa:hvr/ghc - sudo apt-get update - sudo apt-get install cabal-install-1.18 ghc-$GHCVER - - export PATH=/opt/ghc/$GHCVER/bin:$PATH + - export PATH=/opt/ghc/$GHCVER/bin:/opt/cabal/1.18/bin:$PATH install: - - cabal-1.18 update - - cabal-1.18 install happy + - cabal update + - cabal install happy - export PATH=$HOME/.cabal/bin:$PATH - happy --version - - cabal-1.18 install --only-dependencies --enable-tests + - cabal install --only-dependencies --enable-tests script: - - cabal-1.18 configure --enable-tests - - cabal-1.18 build - - cabal-1.18 test - - cabal-1.18 check - - cabal-1.18 sdist + - cabal configure --enable-tests + - cabal build + - cabal test + - cabal check + - cabal sdist # The following scriptlet checks that the resulting source distribution can be built & installed - - export SRC_TGZ=$(cabal-1.18 info . | awk '{print $2 ".tar.gz";exit}'); + - export SRC_TGZ=$(cabal info . | awk '{print $2 ".tar.gz";exit}'); cd dist/; if [ -f "$SRC_TGZ" ]; then - cabal-1.18 install --enable-tests "$SRC_TGZ"; + cabal install --enable-tests "$SRC_TGZ"; else echo "expected '$SRC_TGZ' not found"; exit 1; From 96a54d214836275737f7e46bc2a0f0bdb323a6b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Gr=C3=B6ber?= Date: Sat, 3 May 2014 15:24:37 +0200 Subject: [PATCH 14/17] Remove test/*/setup-config before spec --- test/CabalApiSpec.hs | 9 +-------- test/Main.hs | 1 + 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/test/CabalApiSpec.hs b/test/CabalApiSpec.hs index f122cda..00e78ab 100644 --- a/test/CabalApiSpec.hs +++ b/test/CabalApiSpec.hs @@ -19,12 +19,6 @@ import Config (cProjectVersionInt) -- ghc version ghcVersion :: Int ghcVersion = read cProjectVersionInt -unconfigure :: IO () -unconfigure = do - removeFile cabalConfigPath `catch` (\(_ :: SomeException) -> return ()) - -around' a f = a >> f >> a - spec :: Spec spec = do describe "parseCabalFile" $ do @@ -69,8 +63,7 @@ spec = do describe "cabalGetConfig" $ do it "can reconfigure a cabal package" $ do - withDirectory_ "test/data/check-test-subdir" - $ around' unconfigure $ do + withDirectory_ "test/data/check-test-subdir" $ do cradle <- findCradle cfg <- cabalGetConfig cradle cfg `shouldSatisfy` not . null diff --git a/test/Main.hs b/test/Main.hs index 8eea411..bd0c8f8 100644 --- a/test/Main.hs +++ b/test/Main.hs @@ -16,4 +16,5 @@ main = do genGhcPkgCache dir = system $ "ghc-pkg recache --force -f" ++ dir genSandboxCfg `mapM_` sandboxes genGhcPkgCache `mapM_` pkgDirs + system "find test -name setup-config -exec rm {} \\;" hspec spec From f750d10a9a3b3894819947f8699e38c8481ab1dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Gr=C3=B6ber?= Date: Sat, 3 May 2014 15:25:21 +0200 Subject: [PATCH 15/17] Cabal needs empty 00-index.cache and 00-index.tar in sanxbox --- .../data/.cabal-sandbox/packages/00-index.cache | 0 test/data/.cabal-sandbox/packages/00-index.tar | Bin 0 -> 10240 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 test/data/.cabal-sandbox/packages/00-index.cache create mode 100644 test/data/.cabal-sandbox/packages/00-index.tar diff --git a/test/data/.cabal-sandbox/packages/00-index.cache b/test/data/.cabal-sandbox/packages/00-index.cache new file mode 100644 index 0000000..e69de29 diff --git a/test/data/.cabal-sandbox/packages/00-index.tar b/test/data/.cabal-sandbox/packages/00-index.tar new file mode 100644 index 0000000000000000000000000000000000000000..9df64990f7be3c1f7194a0c22852a1ab3a09f3c5 GIT binary patch literal 10240 zcmeIu0Sy2E0K%a6Pi+o2h(KY$fB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM P7%*VKfB^#r47?2tC;$Kf literal 0 HcmV?d00001 From 9d9f66e942ffd4220b2c2da09d93e10173880f25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Gr=C3=B6ber?= Date: Sat, 3 May 2014 14:51:58 +0200 Subject: [PATCH 16/17] Remove `Maybe` from `cabalConfigDependencies` and `fromInstalledPackageId` --- Language/Haskell/GhcMod/CabalApi.hs | 17 +++++++++++------ Language/Haskell/GhcMod/GhcPkg.hs | 12 ++++++++++-- 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/Language/Haskell/GhcMod/CabalApi.hs b/Language/Haskell/GhcMod/CabalApi.hs index 07bff93..da438b9 100644 --- a/Language/Haskell/GhcMod/CabalApi.hs +++ b/Language/Haskell/GhcMod/CabalApi.hs @@ -20,7 +20,7 @@ import Control.Applicative ((<$>)) import Control.Exception (throwIO,catch,SomeException) import Control.Monad (filterM) import CoreMonad (liftIO) -import Data.Maybe (maybeToList, mapMaybe) +import Data.Maybe (maybeToList) import Data.Set (fromList, toList) import Data.List (find,tails,isPrefixOf) import Distribution.ModuleName (ModuleName,toFilePath) @@ -53,7 +53,7 @@ getCompilerOptions :: [GHCOption] -> IO CompilerOptions getCompilerOptions ghcopts cradle pkgDesc = do gopts <- getGHCOptions ghcopts cradle rdir $ head buildInfos - Just depPkgs <- cabalConfigDependencies <$> cabalGetConfig cradle + depPkgs <- cabalConfigDependencies <$> cabalGetConfig cradle return $ CompilerOptions gopts idirs depPkgs where wdir = cradleCurrentDir cradle @@ -225,12 +225,17 @@ cabalGetConfig cradle = cabalConfigPath :: FilePath cabalConfigPath = localBuildInfoFile defaultDistPref -cabalConfigDependencies :: CabalConfig -> Maybe [Package] +cabalConfigDependencies :: CabalConfig -> [Package] cabalConfigDependencies config = - cfgDepends >>= return . mapMaybe (fromInstalledPackageId . snd) + (fromInstalledPackageId . snd) <$> cfgDepends where - cfgDepends :: Maybe [(PackageName, InstalledPackageId)] - cfgDepends = extractCabalSetupConfig "configDependencies" config + cfgDepends :: [(PackageName, InstalledPackageId)] + cfgDepends = + case extractCabalSetupConfig "configDependencies" config of + Just x -> x + Nothing -> error $ + "cabalConfigDependencies: Extracting field `configDependencies' from" + ++ " setup-config failed" -- | Extract part of cabal's @setup-config@, this is done with a mix of manual diff --git a/Language/Haskell/GhcMod/GhcPkg.hs b/Language/Haskell/GhcMod/GhcPkg.hs index 90d04c3..d63e806 100644 --- a/Language/Haskell/GhcMod/GhcPkg.hs +++ b/Language/Haskell/GhcMod/GhcPkg.hs @@ -7,6 +7,7 @@ module Language.Haskell.GhcMod.GhcPkg ( , ghcDbStackOpts , ghcDbOpt , fromInstalledPackageId + , fromInstalledPackageId' , getSandboxDb , getPackageDbStack ) where @@ -84,13 +85,20 @@ packageLine l = Just ((Hidden,p),_) -> Just p _ -> Nothing -fromInstalledPackageId :: InstalledPackageId -> Maybe Package -fromInstalledPackageId pid = let +fromInstalledPackageId' :: InstalledPackageId -> Maybe Package +fromInstalledPackageId' pid = let InstalledPackageId pkg = pid in case reverse $ splitOn "-" pkg of i:v:rest -> Just (intercalate "-" (reverse rest), v, i) _ -> Nothing +fromInstalledPackageId :: InstalledPackageId -> Package +fromInstalledPackageId pid = + case fromInstalledPackageId' pid of + Just p -> p + Nothing -> error $ + "fromInstalledPackageId: `"++show pid++"' is not a valid package-id" + data PackageState = Normal | Hidden | Broken deriving (Eq,Show) packageLineP :: ReadP (PackageState, Package) From 4e4d27e62d5bad13255bee8e86e7fc03dfb267ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Gr=C3=B6ber?= Date: Sun, 4 May 2014 04:56:07 +0200 Subject: [PATCH 17/17] Use `componentsConfigs` instead of `configDependencies` `configDependencies` was added in Cabal-1.20 but we're using 1.18 on travis. --- Language/Haskell/GhcMod/CabalApi.hs | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/Language/Haskell/GhcMod/CabalApi.hs b/Language/Haskell/GhcMod/CabalApi.hs index da438b9..50b6759 100644 --- a/Language/Haskell/GhcMod/CabalApi.hs +++ b/Language/Haskell/GhcMod/CabalApi.hs @@ -37,6 +37,7 @@ import Distribution.Simple.Program (ghcProgram) import Distribution.Simple.Program.Types (programName, programFindVersion) import Distribution.Simple.BuildPaths (defaultDistPref) import Distribution.Simple.Configure (localBuildInfoFile) +import Distribution.Simple.LocalBuildInfo (ComponentName(..),ComponentLocalBuildInfo(..)) import Distribution.System (buildPlatform) import Distribution.Text (display) import Distribution.Verbosity (silent) @@ -226,17 +227,22 @@ cabalConfigPath :: FilePath cabalConfigPath = localBuildInfoFile defaultDistPref cabalConfigDependencies :: CabalConfig -> [Package] -cabalConfigDependencies config = - (fromInstalledPackageId . snd) <$> cfgDepends +cabalConfigDependencies config = cfgDepends where - cfgDepends :: [(PackageName, InstalledPackageId)] - cfgDepends = - case extractCabalSetupConfig "configDependencies" config of - Just x -> x + lbi :: (ComponentName, ComponentLocalBuildInfo, [ComponentName]) + -> ComponentLocalBuildInfo + lbi (_,i,_) = i + components = case extractCabalSetupConfig "componentsConfigs" config of + Just comps -> lbi <$> comps Nothing -> error $ - "cabalConfigDependencies: Extracting field `configDependencies' from" + "cabalConfigDependencies: Extracting field `componentsConfigs' from" ++ " setup-config failed" + pids :: [InstalledPackageId] + pids = fst <$> componentPackageDeps `concatMap` components + cfgDepends = filter (("inplace" /=) . pkgId) + $ fromInstalledPackageId <$> pids + -- | Extract part of cabal's @setup-config@, this is done with a mix of manual -- string processing and use of 'read'. This way we can extract a field from @@ -244,7 +250,7 @@ cabalConfigDependencies config = -- depending on the exact version of Cabal used to configure the project as it -- is rather likley that some part of 'LocalBuildInfo' changed. -- --- Right now 'extractCabalSetupConfig' can only deal with Lists and Tupels in +-- Right now 'extractCabalSetupConfig' can only deal with Lists and Tuples in -- the field! extractCabalSetupConfig :: (Read r) => String -> CabalConfig -> Maybe r extractCabalSetupConfig field config = do