aihc-parser-3.0.0.0: common/HackageSupport.hs
{-# LANGUAGE OverloadedStrings #-}
-- | Thin adapter over "Aihc.Hackage" that converts string-typed results
-- to the rich types used by @aihc-parser@ executables and tests.
module HackageSupport
( downloadPackage,
downloadPackageQuiet,
downloadPackageQuietWithNetwork,
findPackageBuildToolDependencyNames,
findPackageDefaultsToHaskell98,
findPackageUsesCustomPreprocessor,
findTargetFilesFromCabal,
FileInfo (..),
readTextFileLenient,
resolveIncludeBestEffort,
diagToText,
prefixCppErrors,
)
where
import Aihc.Cpp (Diagnostic (..), IncludeRequest, Severity (..))
import Aihc.Hackage.Cabal qualified as HC
import Aihc.Hackage.Cpp qualified as HCpp
import Aihc.Hackage.Download qualified as HD
import Aihc.Hackage.Types qualified as HT
import Aihc.Hackage.Util qualified as HU
import Aihc.Parser.Syntax qualified as Syntax
import Data.ByteString qualified as BS
import Data.Maybe (mapMaybe)
import Data.Text (Text)
import Data.Text qualified as T
import Distribution.PackageDescription.Parsec (parseGenericPackageDescription, runParseResult)
import Distribution.Types.GenericPackageDescription (GenericPackageDescription)
import System.FilePath (takeDirectory)
-- | Download a Hackage package with verbose logging.
downloadPackage :: String -> String -> IO FilePath
downloadPackage name version =
HD.downloadPackage (HT.PackageSpec name version)
-- | Download a Hackage package silently.
downloadPackageQuiet :: String -> String -> IO FilePath
downloadPackageQuiet name version =
HD.downloadPackageQuiet (HT.PackageSpec name version)
-- | Download a Hackage package, controlling network access.
downloadPackageQuietWithNetwork :: Bool -> String -> String -> IO FilePath
downloadPackageQuietWithNetwork allowNetwork name version =
HD.downloadPackageWithOptions
HD.defaultDownloadOptions
{ HD.downloadVerbose = False,
HD.downloadAllowNetwork = allowNetwork
}
(HT.PackageSpec name version)
-- | Rich file info with parser-typed extensions and language.
data FileInfo = FileInfo
{ fileInfoPath :: FilePath,
fileInfoExtensions :: [Syntax.ExtensionSetting],
fileInfoCppOptions :: [String],
fileInfoIncludeDirs :: [FilePath],
fileInfoLanguage :: Maybe Syntax.LanguageEdition,
fileInfoDependencies :: [Text]
}
deriving (Show)
-- | Find target Haskell source files from a @.cabal@ file.
--
-- Delegates to 'Aihc.Hackage.Cabal.collectComponentFiles' and then converts
-- the string-typed results to the rich types used by @aihc-parser@.
findTargetFilesFromCabal :: FilePath -> IO [FileInfo]
findTargetFilesFromCabal extractedRoot = do
(gpd, cabalFile) <- parsePackageDescriptionFromRoot extractedRoot
rawFiles <- HC.collectComponentFiles gpd (takeDirectory cabalFile)
pure (map convertFileInfo rawFiles)
-- | Find active build-tool dependency names from a package's @.cabal@ file.
findPackageBuildToolDependencyNames :: FilePath -> IO [Text]
findPackageBuildToolDependencyNames extractedRoot = do
(gpd, _) <- parsePackageDescriptionFromRoot extractedRoot
pure (HC.buildToolDependencyNames gpd)
-- | Return whether any active library or executable defaults to Haskell98.
findPackageDefaultsToHaskell98 :: FilePath -> IO Bool
findPackageDefaultsToHaskell98 extractedRoot = do
(gpd, _) <- parsePackageDescriptionFromRoot extractedRoot
pure (HC.packageDefaultsToHaskell98 gpd)
-- | Find whether active package metadata requests a custom GHC preprocessor.
findPackageUsesCustomPreprocessor :: FilePath -> IO Bool
findPackageUsesCustomPreprocessor extractedRoot = do
(gpd, _) <- parsePackageDescriptionFromRoot extractedRoot
pure (HC.packageUsesCustomPreprocessor gpd)
parsePackageDescriptionFromRoot :: FilePath -> IO (GenericPackageDescription, FilePath)
parsePackageDescriptionFromRoot extractedRoot = do
cabalFiles <- HU.findCabalFiles extractedRoot
cabalFile <-
case cabalFiles of
[file] -> pure file
[] ->
ioError
( userError
("No .cabal file found under extracted package root: " ++ extractedRoot)
)
files -> pure (HU.chooseBestCabalFile extractedRoot files)
cabalBytes <- BS.readFile cabalFile
let (_, parseResult) = runParseResult (parseGenericPackageDescription cabalBytes)
gpd <-
case parseResult of
Right parsed -> pure parsed
Left (_, errs) ->
ioError
( userError
("Failed to parse cabal file " ++ cabalFile ++ ": " ++ show errs)
)
pure (gpd, cabalFile)
-- | Convert a string-typed 'HC.FileInfo' to the rich-typed local 'FileInfo'.
convertFileInfo :: HC.FileInfo -> FileInfo
convertFileInfo raw =
FileInfo
{ fileInfoPath = HC.fileInfoPath raw,
fileInfoExtensions = mapMaybe (Syntax.parseExtensionSettingName . T.pack) (HC.fileInfoExtensions raw),
fileInfoCppOptions = HC.fileInfoCppOptions raw,
fileInfoIncludeDirs = HC.fileInfoIncludeDirs raw,
fileInfoLanguage = HC.fileInfoLanguage raw >>= Syntax.parseLanguageEdition . T.pack,
fileInfoDependencies = HC.fileInfoDependencies raw
}
readTextFileLenient :: FilePath -> IO Text
readTextFileLenient = HU.readTextFileLenient
resolveIncludeBestEffort :: FilePath -> [FilePath] -> FilePath -> IncludeRequest -> IO (Maybe BS.ByteString)
resolveIncludeBestEffort = HCpp.resolveIncludeBestEffort
diagToText :: Diagnostic -> Text
diagToText diag =
T.pack (diagFile diag)
<> ":"
<> T.pack (show (diagLine diag))
<> ": "
<> sev
<> ": "
<> diagMessage diag
where
sev =
case diagSeverity diag of
Warning -> "warning"
Error -> "error"
prefixCppErrors :: Maybe Text -> Text -> Text
prefixCppErrors cppMsg msg =
case cppMsg of
Nothing -> msg
Just cppText -> "cpp diagnostics:\n" <> cppText <> "\n" <> msg