Factor out cabal-helper into a package

This commit is contained in:
Daniel Gröber
2015-03-15 20:48:55 +01:00
parent a97e07065e
commit 90d9577f8d
13 changed files with 90 additions and 1082 deletions

View File

@@ -15,21 +15,18 @@
-- along with this program. If not, see <http://www.gnu.org/licenses/>.
module Language.Haskell.GhcMod.CabalHelper (
CabalHelper(..)
, getComponents
, getGhcOptions
getComponents
, getGhcPkgOptions
, cabalHelper
) where
import Control.Applicative
import Control.Arrow
import Control.Monad
import Data.Monoid
import Data.List
import Language.Haskell.GhcMod.Types
import Distribution.Helper
import qualified Language.Haskell.GhcMod.Types as T
import Language.Haskell.GhcMod.Types hiding (ghcProgram, ghcPkgProgram,
cabalProgram)
import Language.Haskell.GhcMod.Monad.Types
import Language.Haskell.GhcMod.Error as E
import Language.Haskell.GhcMod.Utils
import Language.Haskell.GhcMod.World
import Language.Haskell.GhcMod.PathsAndFiles
@@ -37,26 +34,42 @@ import System.FilePath
-- | Only package related GHC options, sufficient for things that don't need to
-- access home modules
getGhcPkgOptions :: (MonadIO m, GmEnv m) => m [(GmComponentName, [GHCOption])]
getGhcPkgOptions = chGhcPkgOptions `liftM` cabalHelper
getGhcPkgOptions :: (MonadIO m, GmEnv m) => m [(ChComponentName, [GHCOption])]
getGhcPkgOptions = do
Cradle {..} <- cradle
let distdir = cradleRootDir </> "dist"
runQuery distdir ghcPkgOptions
getGhcOptions :: (MonadIO m, GmEnv m) => m [(GmComponentName, [GHCOption])]
getGhcOptions = chGhcOptions `liftM` cabalHelper
helperProgs :: Options -> Programs
helperProgs opts = Programs {
cabalProgram = T.cabalProgram opts,
ghcProgram = T.ghcProgram opts,
ghcPkgProgram = T.ghcPkgProgram opts
}
-- | Primary interface to cabal-helper and intended single entrypoint to
-- constructing 'GmComponent's
--
-- The Component\'s 'gmcHomeModuleGraph' will be empty and has to be resolved by
-- 'resolveGmComponents'.
getComponents :: (MonadIO m, GmEnv m)
=> m [GmComponent (Either FilePath [ModuleName])]
getComponents = cabalHelper >>= \CabalHelper {..} -> return $ let
([(scn, sep)], eps) = partition ((GmSetupHsName ==) . fst) chEntrypoints
sc = GmComponent scn [] [] sep sep ["."] mempty
cs = flip map (zip4 eps chGhcOptions chGhcSrcOptions chSourceDirs) $
\((cn, ep), (_, opts), (_, srcOpts), (_, srcDirs)) ->
getComponents :: (MonadIO m, GmEnv m) => m [GmComponent ChEntrypoint]
getComponents = withCabal $ do
Cradle {..} <- cradle
let distdir = cradleRootDir </> "dist"
opt <- options
runQuery' (helperProgs opt) distdir $ do
q <- liftM4 join4 ghcOptions ghcSrcOptions entrypoints sourceDirs
return $ flip map q $ \(cn, (opts, (srcOpts, (ep, srcDirs)))) ->
GmComponent cn opts srcOpts ep ep srcDirs mempty
in sc:cs
where
join4 a b c = join' a . join' b . join' c
join' :: Eq a => [(a,b)] -> [(a,c)] -> [(a,(b,c))]
join' lb lc = [ (a, (b, c))
| (a, b) <- lb
, (a', c) <- lc
, a == a'
]
withCabal :: (MonadIO m, GmEnv m) => m a -> m a
withCabal action = do
@@ -65,58 +78,11 @@ withCabal action = do
liftIO $ whenM (isSetupConfigOutOfDate <$> getCurrentWorld crdl) $
withDirectory_ (cradleRootDir crdl) $ do
let progOpts =
[ "--with-ghc=" ++ ghcProgram opts ]
[ "--with-ghc=" ++ T.ghcProgram opts ]
-- Only pass ghc-pkg if it was actually set otherwise we
-- might break cabal's guessing logic
++ if ghcPkgProgram opts /= ghcPkgProgram defaultOptions
then [ "--with-ghc-pkg=" ++ ghcPkgProgram opts ]
++ if T.ghcPkgProgram opts /= T.ghcPkgProgram defaultOptions
then [ "--with-ghc-pkg=" ++ T.ghcPkgProgram opts ]
else []
void $ readProcess (cabalProgram opts) ("configure":progOpts) ""
void $ readProcess (T.cabalProgram opts) ("configure":progOpts) ""
action
data CabalHelper = CabalHelper {
chEntrypoints :: [(GmComponentName, Either FilePath [ModuleName])],
chSourceDirs :: [(GmComponentName, [String])],
chGhcOptions :: [(GmComponentName, [String])],
chGhcSrcOptions :: [(GmComponentName, [String])],
chGhcPkgOptions :: [(GmComponentName, [String])]
} deriving (Show)
cabalHelper :: (MonadIO m, GmEnv m) => m CabalHelper
cabalHelper = withCabal $ do
Cradle {..} <- cradle
Options {..} <- options
let progArgs = [ "--with-ghc=" ++ ghcProgram
, "--with-ghc-pkg=" ++ ghcPkgProgram
, "--with-cabal=" ++ cabalProgram
]
let args = [ "entrypoints"
, "source-dirs"
, "ghc-options"
, "ghc-src-options"
, "ghc-pkg-options"
] ++ progArgs
distdir = cradleRootDir </> "dist"
res <- liftIO $ do
exe <- findLibexecExe "cabal-helper-wrapper"
hexe <- readProcess exe ([distdir, "print-exe"] ++ progArgs) ""
cached cradleRootDir (cabalHelperCache hexe args) $ do
out <- readProcess exe (distdir:args) ""
evaluate (read out) `E.catch`
\(SomeException _) -> error "cabalHelper: read failed"
let [ Just (GmCabalHelperEntrypoints eps),
Just (GmCabalHelperStrings srcDirs),
Just (GmCabalHelperStrings ghcOpts),
Just (GmCabalHelperStrings ghcSrcOpts),
Just (GmCabalHelperStrings ghcPkgOpts) ] = res
eps' = map (second $ fmap $ map md) eps
return $ CabalHelper eps' srcDirs ghcOpts ghcSrcOpts ghcPkgOpts
where
md (GmModuleName mn) = mkModuleName mn

View File

@@ -103,6 +103,7 @@ import Data.Map (Map, empty)
import Data.Maybe
import Data.Monoid
import Data.IORef
import Distribution.Helper
import MonadUtils (MonadIO(..))
@@ -128,7 +129,7 @@ data GmGhcSession = GmGhcSession {
data GhcModState = GhcModState {
gmGhcSession :: !(Maybe GmGhcSession)
, gmComponents :: !(Map GmComponentName (GmComponent (Set ModulePath)))
, gmComponents :: !(Map ChComponentName (GmComponent (Set ModulePath)))
, gmCompilerMode :: !CompilerMode
}

View File

@@ -23,7 +23,9 @@ import Control.Monad.Trans.Maybe
import Data.List
import Data.Char
import Data.Maybe
import Data.Version
import Data.Traversable (traverse)
import Distribution.Helper
import System.Directory
import System.FilePath
import System.IO.Unsafe
@@ -233,11 +235,11 @@ cabalBuildPlatform = dropWhileEnd isSpace $ unsafePerformIO $
packageCache :: String
packageCache = "package.cache"
cabalHelperCache ::
FilePath -> [String] -> Cached [String] [Maybe GmCabalHelperResponse]
cabalHelperCache cabalHelperExe cmds = Cached {
inputFiles = [cabalHelperExe, setupConfigPath],
inputData = cmds,
cabalHelperCache :: Version -> [String]
-> Cached (Version, [String]) [GmComponent ChEntrypoint]
cabalHelperCache cabalHelperVer cmds = Cached {
inputFiles = [setupConfigPath],
inputData = (cabalHelperVer, cmds),
cacheFile = setupConfigPath <.> "ghc-mod.cabal-helper"
}

View File

@@ -19,6 +19,7 @@ module Language.Haskell.GhcMod.Pretty where
import Control.Arrow hiding ((<+>))
import Data.Char
import Data.List
import Distribution.Helper
import Text.PrettyPrint
import Language.Haskell.GhcMod.Types
@@ -29,12 +30,12 @@ docStyle = style { ribbonsPerLine = 1.2 }
gmRenderDoc :: Doc -> String
gmRenderDoc = renderStyle docStyle
gmComponentNameDoc :: GmComponentName -> Doc
gmComponentNameDoc GmSetupHsName = text $ "Setup.hs"
gmComponentNameDoc GmLibName = text $ "library"
gmComponentNameDoc (GmExeName n) = text $ "exe:" ++ n
gmComponentNameDoc (GmTestName n) = text $ "test:" ++ n
gmComponentNameDoc (GmBenchName n) = text $ "bench:" ++ n
gmComponentNameDoc :: ChComponentName -> Doc
gmComponentNameDoc ChSetupHsName = text $ "Setup.hs"
gmComponentNameDoc ChLibName = text $ "library"
gmComponentNameDoc (ChExeName n) = text $ "exe:" ++ n
gmComponentNameDoc (ChTestName n) = text $ "test:" ++ n
gmComponentNameDoc (ChBenchName n) = text $ "bench:" ++ n
gmLogLevelDoc :: GmLogLevel -> Doc
gmLogLevelDoc GmPanic = text "PANIC"

View File

@@ -46,6 +46,7 @@ import Data.Map (Map)
import qualified Data.Map as Map
import Data.Set (Set)
import qualified Data.Set as Set
import Distribution.Helper
import System.Directory
import System.FilePath
@@ -185,9 +186,9 @@ targetGhcOptions crdl sefnmn = do
let cn = pickComponent candidates
return $ gmcGhcOpts $ fromJust $ Map.lookup cn mcs
moduleComponents :: Map GmComponentName (GmComponent (Set ModulePath))
moduleComponents :: Map ChComponentName (GmComponent (Set ModulePath))
-> Either FilePath ModuleName
-> Set GmComponentName
-> Set ChComponentName
moduleComponents m efnmn =
foldr' Set.empty m $ \c s ->
let
@@ -203,10 +204,9 @@ moduleComponents m efnmn =
foldr' b as f = Map.foldr f b as
pickComponent :: Set GmComponentName -> GmComponentName
pickComponent :: Set ChComponentName -> ChComponentName
pickComponent scn = Set.findMin scn
packageGhcOptions :: (MonadIO m, GmEnv m) => m [GHCOption]
packageGhcOptions = do
crdl <- cradle
@@ -223,14 +223,16 @@ sandboxOpts crdl = return $ ["-i" ++ d | d <- [wdir,rdir]] ++ pkgOpts
resolveGmComponent :: (IOish m, GmLog m, GmEnv m)
=> Maybe [Either FilePath ModuleName] -- ^ Updated modules
-> GmComponent (Either FilePath [ModuleName])
-> GmComponent ChEntrypoint
-> m (GmComponent (Set ModulePath))
resolveGmComponent mums c@GmComponent {..} =
withLightHscEnv gmcGhcSrcOpts $ \env -> do
let srcDirs = gmcSourceDirs
mg = gmcHomeModuleGraph
let eps = either (return . Left) (map Right) gmcEntrypoints
Cradle { cradleRootDir } <- cradle
eps <- liftIO $ resolveChEntrypoints cradleRootDir gmcEntrypoints
simp <- liftIO $ resolveEntrypoints env srcDirs eps
sump <- liftIO $ case mums of
Nothing -> return simp
@@ -263,11 +265,30 @@ resolveEntrypoints env srcDirs ms =
findFile' dirs file =
mconcat <$> mapM (mightExist . (</>file)) dirs
resolveChEntrypoints ::
FilePath -> ChEntrypoint -> IO [Either FilePath ModuleName]
resolveChEntrypoints _ (ChLibEntrypoint em om) =
return $ map (Right . chModToMod) (em ++ om)
resolveChEntrypoints _ (ChExeEntrypoint main om) =
return $ [Left main] ++ map (Right . chModToMod) om
resolveChEntrypoints srcDir ChSetupEntrypoint = do
shs <- doesFileExist (srcDir </> "Setup.hs")
slhs <- doesFileExist (srcDir </> "Setup.lhs")
return $ case (shs, slhs) of
(True, _) -> [Left "Setup.hs"]
(_, True) -> [Left "Setup.lhs"]
(False, False) -> []
chModToMod :: ChModuleName -> ModuleName
chModToMod (ChModuleName mn) = mkModuleName mn
resolveGmComponents :: (IOish m, GmState m, GmLog m, GmEnv m)
=> Maybe [Either FilePath ModuleName]
-- ^ Updated modules
-> [GmComponent (Either FilePath [ModuleName])]
-> m (Map GmComponentName (GmComponent (Set ModulePath)))
-> [GmComponent ChEntrypoint]
-> m (Map ChComponentName (GmComponent (Set ModulePath)))
resolveGmComponents mumns cs = do
s <- gmsGet
m' <- foldrM' (gmComponents s) cs $ \c m -> do

View File

@@ -2,7 +2,6 @@
{-# OPTIONS_GHC -fno-warn-orphans -fno-warn-deprecations #-}
module Language.Haskell.GhcMod.Types (
module Language.Haskell.GhcMod.Types
, module CabalHelper.Types
, ModuleName
, mkModuleName
, moduleNameString
@@ -18,13 +17,12 @@ import Data.Set (Set)
import qualified Data.Set as Set
import Data.Monoid
import Data.Typeable (Typeable)
import Distribution.Helper
import Exception (ExceptionMonad)
import MonadUtils (MonadIO)
import GHC (ModuleName, moduleNameString, mkModuleName)
import PackageConfig (PackageConfig)
import CabalHelper.Types
-- | A constraint alias (-XConstraintKinds) to make functions dealing with
-- 'GhcModT' somewhat cleaner.
--
@@ -168,10 +166,10 @@ instance Monoid GmModuleGraph where
GmModuleGraph (a <> a') (b <> b') (Map.unionWith Set.union c c')
data GmComponent eps = GmComponent {
gmcName :: GmComponentName,
gmcName :: ChComponentName,
gmcGhcOpts :: [GHCOption],
gmcGhcSrcOpts :: [GHCOption],
gmcRawEntrypoints :: Either FilePath [ModuleName],
gmcRawEntrypoints :: ChEntrypoint,
gmcEntrypoints :: eps,
gmcSourceDirs :: [FilePath],
gmcHomeModuleGraph :: GmModuleGraph
@@ -204,10 +202,10 @@ data GhcModError
| GMECabalFlags GhcModError
-- ^ Retrieval of the cabal configuration flags failed.
| GMECabalComponent GmComponentName
| GMECabalComponent ChComponentName
-- ^ Cabal component could not be found
| GMECabalCompAssignment [(Either FilePath ModuleName, Set GmComponentName)]
| GMECabalCompAssignment [(Either FilePath ModuleName, Set ChComponentName)]
-- ^ Could not find a consistent component assignment for modules
| GMEProcess String [String] (Either (String, String, Int) GhcModError)