hs-tags-0.1.5: Main.hs
{-# LANGUAGE CPP #-}
{-# LANGUAGE PatternSynonyms #-}
{-# LANGUAGE ScopedTypeVariables #-}
module Main where
import Control.Arrow ((&&&))
import Control.Exception
import Control.Monad
import Control.Monad.Trans
import Data.Char
import qualified Data.Traversable as T
import Data.List
import Data.Maybe
import Data.Version ( showVersion )
import System.Environment
import System.IO
import qualified System.IO.Strict as Strict
import System.Exit
import System.FilePath
import System.Directory
import System.Process
import System.Console.GetOpt
import GHC
( DynFlags
, HsModule
, Located
, RealSrcLoc
, includePaths
, getSession
, getSessionDynFlags
, setSessionDynFlags
, runGhc
)
#if MIN_VERSION_ghc(8,4,0)
import GHC (GhcPs)
#else
import GHC (RdrName)
#endif
-- In ghc-9.0, hierarchical modules were introduced,
-- so most modules got renamed.
#if MIN_VERSION_ghc(9,0,0)
import GHC.Data.FastString ( mkFastString )
import GHC.Data.StringBuffer ( hGetStringBuffer )
import GHC.Driver.Monad ( GhcT(..), Ghc(..) )
import GHC.Driver.Phases ( pattern Cpp, pattern HsSrcFile )
import GHC.Driver.Pipeline ( preprocess )
import GHC.Driver.Session ( pattern IncludeSpecs, opt_P, parseDynamicFilePragma, sOpt_P, toolSettings )
import GHC.Parser ( parseModule )
import GHC.Parser.Lexer ( P(..), ParseResult(..), PState, mkPState, getErrorMessages )
import GHC.Settings ( toolSettings_opt_P )
import GHC.Types.SrcLoc ( mkRealSrcLoc, noLoc, unLoc )
-- THESE MODULES cannot be imported, it seems:
-- import GHC.Driver.Errors ( printBagOfErrors )
-- import GHC.Types.SourceError (throwErrors)
-- import GHC.Parser.Errors.Ppr (pprError)
-- import GHC.Utils.Error.ErrorMessages
#else
import DriverPhases ( pattern Cpp, pattern HsSrcFile )
import DriverPipeline ( preprocess )
import DynFlags ( opt_P, sOpt_P, settings, parseDynamicFilePragma )
import FastString ( mkFastString )
import GhcMonad ( GhcT(..), Ghc(..) )
import Lexer ( P(..), ParseResult(..), PState, mkPState )
import Parser ( parseModule )
import SrcLoc ( mkRealSrcLoc, noLoc, unLoc )
import StringBuffer ( hGetStringBuffer )
#if MIN_VERSION_ghc(8,6,1)
import DynFlags ( pattern IncludeSpecs )
#endif
#if MIN_VERSION_ghc(8,10,1)
import GHC ( toolSettings )
import ToolSettings ( toolSettings_opt_P )
import Lexer ( getErrorMessages )
import ErrUtils ( printBagOfErrors )
#else
import ErrUtils ( mkPlainErrMsg )
#endif
#endif
import Language.Haskell.Extension as LHE
import Distribution.PackageDescription.Configuration (flattenPackageDescription)
import Distribution.PackageDescription hiding (options)
#if MIN_VERSION_Cabal(2,2,0)
import qualified Distribution.PackageDescription.Parsec as PkgDescParse
import Distribution.PackageDescription.Parsec hiding (ParseResult)
#else
import qualified Distribution.PackageDescription.Parse as PkgDescParse
import Distribution.PackageDescription.Parse hiding (ParseResult)
#endif
import Tags
import Paths_hs_tags ( version )
instance MonadTrans GhcT where
lift m = GhcT $ const m
fileLoc :: FilePath -> RealSrcLoc
fileLoc file = mkRealSrcLoc (mkFastString file) 1 0
filePState :: DynFlags -> FilePath -> IO PState
filePState dflags file = do
buf <- hGetStringBuffer file
return $
mkPState dflags buf (fileLoc file)
#if MIN_VERSION_ghc(9,0,0)
pMod :: P (Located HsModule)
#elif MIN_VERSION_ghc(8,4,0)
pMod :: P (Located (HsModule GhcPs))
#else
pMod :: P (Located (HsModule RdrName))
#endif
pMod = parseModule
parse :: PState -> P a -> ParseResult a
parse st p = unP p st
goFile :: FilePath -> Ghc [Tag]
goFile file = do
liftIO $ hPutStrLn stderr $ "Processing " ++ file
env <- getSession
#if MIN_VERSION_ghc(8,8,1)
r <- liftIO $
preprocess env file Nothing (Just $ Cpp HsSrcFile)
let (dflags, srcFile) = case r of
Left _ -> error $ "preprocessing " ++ file
Right x -> x
#else
(dflags, srcFile) <- liftIO $
preprocess env (file, Just $ Cpp HsSrcFile)
#endif
st <- liftIO $ filePState dflags srcFile
case parse st pMod of
POk _ m -> return $ removeDuplicates $ tags $ unLoc m
#if MIN_VERSION_ghc(9,0,0)
PFailed pState -> liftIO $ do
-- Andreas, 2021-03-03, how can we print the errors with ghc-9.0?
-- @printBagOfErrors@ does not seem to be exported,
-- neither @pprError@...
-- throwErrors $ fmap pprError $ getErrorMessages pState dflags
hPutStrLn stderr "PARSE ERROR"
#elif MIN_VERSION_ghc(8,10,1)
PFailed pState -> liftIO $ do
printBagOfErrors dflags $ getErrorMessages pState dflags
#elif MIN_VERSION_ghc(8,4,0)
PFailed _ loc err -> liftIO $ do
print (mkPlainErrMsg dflags loc err)
#else
PFailed loc err -> liftIO $ do
print (mkPlainErrMsg dflags loc err)
#endif
exitWith $ ExitFailure 1
runCmd :: String -> IO String
runCmd cmd = do
(_, h, _, _) <- runInteractiveCommand cmd
hGetContents h
-- XXX This is a quick hack; it will certainly work if the language description
-- is not conditional. Otherwise we'll need to figure out both the flags and the
-- build configuration to call `finalizePackageDescriptionSource`.
configurePackageDescription ::
GenericPackageDescription -> PackageDescription
configurePackageDescription = flattenPackageDescription
extractLangSettings ::
GenericPackageDescription
-> ([Extension], Maybe LHE.Language)
extractLangSettings gpd =
maybe ([], Nothing)
((defaultExtensions &&& defaultLanguage) . libBuildInfo)
((library . configurePackageDescription) gpd)
extToOpt :: Extension -> String
extToOpt (UnknownExtension e) = "-X" ++ e
extToOpt (EnableExtension e) = "-X" ++ show e
extToOpt (DisableExtension e) = "-XNo" ++ show e
langToOpt :: LHE.Language -> String
langToOpt l = "-X" ++ show l
cabalConfToOpts :: GenericPackageDescription -> [String]
cabalConfToOpts desc = langOpts ++ extOpts
where
(exts, maybeLang) = extractLangSettings desc
extOpts = map extToOpt exts
langOpts = langToOpt <$> maybeToList maybeLang
main :: IO ()
main = do
opts <- getOptions
let agdaReadPackageDescription =
#if MIN_VERSION_Cabal(2,0,0)
readGenericPackageDescription
#else
readPackageDescription
#endif
pkgDesc <- T.mapM (agdaReadPackageDescription minBound) $ optCabalPath opts
let go | optHelp opts = do
printUsage stdout
exitSuccess
| otherwise = do
top : _ <- lines <$> runCmd "ghc --print-libdir"
ts <- runGhc (Just top) $ do
dynFlags <- getSessionDynFlags
let dynFlags' =
dynFlags {
#if MIN_VERSION_ghc(8,10,1)
toolSettings = (toolSettings dynFlags) {
toolSettings_opt_P = concatMap (\i -> [i, "-include"]) (optIncludes opts) ++
opt_P dynFlags
}
#else
settings = (settings dynFlags) {
sOpt_P = concatMap (\i -> [i, "-include"]) (optIncludes opts) ++
opt_P dynFlags
}
#endif
#if MIN_VERSION_ghc(8,6,1)
, includePaths = case includePaths dynFlags of
IncludeSpecs qs gs -> IncludeSpecs qs (optIncludePath opts ++ gs)
#else
, includePaths = optIncludePath opts ++ includePaths dynFlags
#endif
}
(dynFlags'', _, _) <- parseDynamicFilePragma dynFlags' $ map noLoc $ concatMap cabalConfToOpts (maybeToList pkgDesc)
setSessionDynFlags dynFlags''
mapM (\f -> liftM2 ((,,) f) (liftIO $ Strict.readFile f)
(goFile f)) $
optFiles opts
when (optCTags opts) $
let sts = sort $ concatMap (\(_, _, t) -> t) ts in
writeFile (optCTagsFile opts) $ unlines $ map show sts
when (optETags opts) $
writeFile (optETagsFile opts) $ showETags ts
go
getOptions :: IO Options
getOptions = do
args <- getArgs
case getOpt Permute options args of
([], [], []) -> do
printUsage stdout
exitSuccess
(opts, files, []) -> return $ foldr ($) (defaultOptions files) opts
(_, _, errs) -> do
hPutStr stderr $ unlines errs
printUsage stderr
exitWith $ ExitFailure 1
printUsage h = do
prog <- getProgName
let header = unwords [ prog, "version", showVersion version ]
hPutStrLn h $ usageInfo header options
data Options = Options
{ optCTags :: Bool
, optETags :: Bool
, optCTagsFile :: String
, optETagsFile :: String
, optHelp :: Bool
, optIncludes :: [FilePath]
, optFiles :: [FilePath]
, optIncludePath :: [FilePath]
, optCabalPath :: Maybe FilePath
}
defaultOptions :: [FilePath] -> Options
defaultOptions files = Options
{ optCTags = False
, optETags = False
, optCTagsFile = "tags"
, optETagsFile = "TAGS"
, optHelp = False
, optIncludes = []
, optFiles = files
, optIncludePath = []
, optCabalPath = Nothing
}
options :: [OptDescr (Options -> Options)]
options =
[ Option [] ["help"] (NoArg setHelp) "Show help."
, Option ['c'] ["ctags"] (OptArg setCTagsFile "FILE") "Generate ctags (default file=tags)"
, Option ['e'] ["etags"] (OptArg setETagsFile "FILE") "Generate etags (default file=TAGS)"
, Option ['i'] ["include"] (ReqArg addInclude "FILE") "File to #include"
, Option ['I'] [] (ReqArg addIncludePath "DIRECTORY") "Directory in the include path"
, Option [] ["cabal"] (ReqArg addCabal "CABAL FILE") "Cabal configuration to load additional language options from (library options are used)"
]
where
setHelp o = o { optHelp = True }
setCTags o = o { optCTags = True }
setETags o = o { optETags = True }
setCTagsFile file o = o { optCTagsFile = fromMaybe "tags" file, optCTags = True }
setETagsFile file o = o { optETagsFile = fromMaybe "TAGS" file, optETags = True }
addInclude file o = o { optIncludes = file : optIncludes o }
addIncludePath dir o = o { optIncludePath = dir : optIncludePath o}
addCabal file o = o { optCabalPath = Just file }