module Distribution.Simple.Program.GHC (
GhcOptions(..),
GhcMode(..),
GhcOptimisation(..),
GhcDynLinkMode(..),
GhcProfAuto(..),
ghcInvocation,
renderGhcOptions,
runGHC,
packageDbArgsDb,
normaliseGhcArgs
) where
import Prelude ()
import Distribution.Compat.Prelude
import Distribution.Backpack
import Distribution.Compat.Semigroup (First'(..), Last'(..), Option'(..))
import Distribution.Simple.GHC.ImplInfo
import Distribution.PackageDescription hiding (Flag)
import Distribution.ModuleName
import Distribution.Simple.Compiler hiding (Flag)
import qualified Distribution.Simple.Compiler as Compiler (Flag)
import Distribution.Simple.Flag
import Distribution.Simple.Program.Types
import Distribution.Simple.Program.Run
import Distribution.System
import Distribution.Pretty
import Distribution.Types.ComponentId
import Distribution.Verbosity
import Distribution.Version
import Distribution.Utils.NubList
import Language.Haskell.Extension
import Data.List (stripPrefix)
import qualified Data.Map as Map
import Data.Monoid (All(..), Any(..), Endo(..))
import Data.Set (Set)
import qualified Data.Set as Set
normaliseGhcArgs :: Maybe Version -> PackageDescription -> [String] -> [String]
normaliseGhcArgs (Just ghcVersion) PackageDescription{..} ghcArgs
| ghcVersion `withinRange` supportedGHCVersions
= argumentFilters . filter simpleFilters . filterRtsOpts $ ghcArgs
where
supportedGHCVersions :: VersionRange
supportedGHCVersions = intersectVersionRanges
(orLaterVersion (mkVersion [8,0]))
(earlierVersion (mkVersion [8,7]))
from :: Monoid m => [Int] -> m -> m
from version flags
| ghcVersion `withinRange` orLaterVersion (mkVersion version) = flags
| otherwise = mempty
to :: Monoid m => [Int] -> m -> m
to version flags
| ghcVersion `withinRange` earlierVersion (mkVersion version) = flags
| otherwise = mempty
checkGhcFlags :: forall m . Monoid m => ([String] -> m) -> m
checkGhcFlags fun = mconcat
[ fun ghcArgs
, checkComponentFlags libBuildInfo pkgLibs
, checkComponentFlags buildInfo executables
, checkComponentFlags testBuildInfo testSuites
, checkComponentFlags benchmarkBuildInfo benchmarks
]
where
pkgLibs = maybeToList library ++ subLibraries
checkComponentFlags :: (a -> BuildInfo) -> [a] -> m
checkComponentFlags getInfo = foldMap (checkComponent . getInfo)
where
checkComponent :: BuildInfo -> m
checkComponent = foldMap fun . filterGhcOptions . allGhcOptions
allGhcOptions :: BuildInfo -> [(CompilerFlavor, [String])]
allGhcOptions = foldMap (perCompilerFlavorToList .)
[options, profOptions, sharedOptions, staticOptions]
filterGhcOptions :: [(CompilerFlavor, [String])] -> [[String]]
filterGhcOptions l = [opts | (GHC, opts) <- l]
safeToFilterWarnings :: Bool
safeToFilterWarnings = getAll $ checkGhcFlags checkWarnings
where
checkWarnings :: [String] -> All
checkWarnings = All . Set.null . foldr alter Set.empty
alter :: String -> Set String -> Set String
alter flag = appEndo $ mconcat
[ \s -> Endo $ if s == "-Werror" then Set.insert s else id
, \s -> Endo $ if s == "-Wwarn" then const Set.empty else id
, \s -> from [8,6] . Endo $
if s == "-Werror=compat"
then Set.union compatWarningSet else id
, \s -> from [8,6] . Endo $
if s == "-Wno-error=compat"
then (`Set.difference` compatWarningSet) else id
, \s -> from [8,6] . Endo $
if s == "-Wwarn=compat"
then (`Set.difference` compatWarningSet) else id
, from [8,4] $ markFlag "-Werror=" Set.insert
, from [8,4] $ markFlag "-Wwarn=" Set.delete
, from [8,4] $ markFlag "-Wno-error=" Set.delete
] flag
markFlag
:: String
-> (String -> Set String -> Set String)
-> String
-> Endo (Set String)
markFlag name update flag = Endo $ case stripPrefix name flag of
Just rest | not (null rest) && rest /= "compat" -> update rest
_ -> id
flagArgumentFilter :: [String] -> [String] -> [String]
flagArgumentFilter flags = go
where
makeFilter :: String -> String -> Option' (First' ([String] -> [String]))
makeFilter flag arg = Option' $ First' . filterRest <$> stripPrefix flag arg
where
filterRest leftOver = case dropEq leftOver of
[] -> drop 1
_ -> id
checkFilter :: String -> Maybe ([String] -> [String])
checkFilter = fmap getFirst' . getOption' . foldMap makeFilter flags
go :: [String] -> [String]
go [] = []
go (arg:args) = case checkFilter arg of
Just f -> go (f args)
Nothing -> arg : go args
argumentFilters :: [String] -> [String]
argumentFilters = flagArgumentFilter
["-ghci-script", "-H", "-interactive-print"]
filterRtsOpts :: [String] -> [String]
filterRtsOpts = go False
where
go :: Bool -> [String] -> [String]
go _ [] = []
go _ ("+RTS":opts) = go True opts
go _ ("-RTS":opts) = go False opts
go isRTSopts (opt:opts) = addOpt $ go isRTSopts opts
where
addOpt | isRTSopts = id
| otherwise = (opt:)
simpleFilters :: String -> Bool
simpleFilters = not . getAny . mconcat
[ flagIn simpleFlags
, Any . isPrefixOf "-ddump-"
, Any . isPrefixOf "-dsuppress-"
, Any . isPrefixOf "-dno-suppress-"
, flagIn $ invertibleFlagSet "-" ["ignore-dot-ghci"]
, flagIn . invertibleFlagSet "-f" . mconcat $
[ [ "reverse-errors", "warn-unused-binds", "break-on-error"
, "break-on-exception", "print-bind-result"
, "print-bind-contents", "print-evld-with-show"
, "implicit-import-qualified", "error-spans"
]
, from [8,2]
[ "diagnostics-show-caret", "local-ghci-history"
, "show-warning-groups", "hide-source-paths"
, "show-hole-constraints"
]
, from [8,4] ["show-loaded-modules"]
, from [8,6] [ "ghci-leak-check", "no-it" ]
]
, flagIn . invertibleFlagSet "-d" $ [ "ppr-case-as-let", "ppr-ticks" ]
, isOptIntFlag
, isIntFlag
, if safeToFilterWarnings
then isWarning <> (Any . ("-w"==))
else mempty
, from [8,6] $
if safeToFilterHoles
then isTypedHoleFlag
else mempty
]
flagIn :: Set String -> String -> Any
flagIn set flag = Any $ Set.member flag set
isWarning :: String -> Any
isWarning = mconcat $ map ((Any .) . isPrefixOf)
["-fwarn-", "-fno-warn-", "-W", "-Wno-"]
simpleFlags :: Set String
simpleFlags = Set.fromList . mconcat $
[ [ "-n", "-#include", "-Rghc-timing", "-dsuppress-all", "-dstg-stats"
, "-dth-dec-file", "-dsource-stats", "-dverbose-core2core"
, "-dverbose-stg2stg", "-dcore-lint", "-dstg-lint", "-dcmm-lint"
, "-dasm-lint", "-dannot-lint", "-dshow-passes", "-dfaststring-stats"
, "-fno-max-relevant-binds", "-recomp", "-no-recomp", "-fforce-recomp"
, "-fno-force-recomp"
]
, from [8,2]
[ "-fno-max-errors", "-fdiagnostics-color=auto"
, "-fdiagnostics-color=always", "-fdiagnostics-color=never"
, "-dppr-debug", "-dno-debug-output"
]
, from [8,4] [ "-ddebug-output" ]
, from [8,4] $ to [8,6] [ "-fno-max-valid-substitutions" ]
, from [8,6] [ "-dhex-word-literals" ]
]
isOptIntFlag :: String -> Any
isOptIntFlag = mconcat . map (dropIntFlag True) $ ["-v", "-j"]
isIntFlag :: String -> Any
isIntFlag = mconcat . map (dropIntFlag False) . mconcat $
[ [ "-fmax-relevant-binds", "-ddpr-user-length", "-ddpr-cols"
, "-dtrace-level", "-fghci-hist-size" ]
, from [8,2] ["-fmax-uncovered-patterns", "-fmax-errors"]
, from [8,4] $ to [8,6] ["-fmax-valid-substitutions"]
]
dropIntFlag :: Bool -> String -> String -> Any
dropIntFlag isOpt flag input = Any $ case stripPrefix flag input of
Nothing -> False
Just rest | isOpt && null rest -> True
| otherwise -> case parseInt rest of
Just _ -> True
Nothing -> False
where
parseInt :: String -> Maybe Int
parseInt = readMaybe . dropEq
dropEq :: String -> String
dropEq ('=':s) = s
dropEq s = s
invertibleFlagSet :: String -> [String] -> Set String
invertibleFlagSet prefix flagNames =
Set.fromList $ (++) <$> [prefix, prefix ++ "no-"] <*> flagNames
compatWarningSet :: Set String
compatWarningSet = Set.fromList $ mconcat
[ from [8,6]
[ "missing-monadfail-instances", "semigroup"
, "noncanonical-monoid-instances", "implicit-kind-vars" ]
]
safeToFilterHoles :: Bool
safeToFilterHoles = getAll . checkGhcFlags $
All . fromMaybe True . fmap getLast' . getOption' . foldMap notDeferred
where
notDeferred :: String -> Option' (Last' Bool)
notDeferred "-fdefer-typed-holes" = Option' . Just . Last' $ False
notDeferred "-fno-defer-typed-holes" = Option' . Just . Last' $ True
notDeferred _ = Option' Nothing
isTypedHoleFlag :: String -> Any
isTypedHoleFlag = mconcat
[ flagIn . invertibleFlagSet "-f" $
[ "show-hole-constraints", "show-valid-substitutions"
, "show-valid-hole-fits", "sort-valid-hole-fits"
, "sort-by-size-hole-fits", "sort-by-subsumption-hole-fits"
, "abstract-refinement-hole-fits", "show-provenance-of-hole-fits"
, "show-hole-matches-of-hole-fits", "show-type-of-hole-fits"
, "show-type-app-of-hole-fits", "show-type-app-vars-of-hole-fits"
, "unclutter-valid-hole-fits"
]
, flagIn . Set.fromList $
[ "-fno-max-valid-hole-fits", "-fno-max-refinement-hole-fits"
, "-fno-refinement-level-hole-fits" ]
, mconcat . map (dropIntFlag False) $
[ "-fmax-valid-hole-fits", "-fmax-refinement-hole-fits"
, "-frefinement-level-hole-fits" ]
]
normaliseGhcArgs _ _ args = args
data GhcOptions = GhcOptions {
ghcOptMode :: Flag GhcMode,
ghcOptExtra :: [String],
ghcOptExtraDefault :: [String],
ghcOptInputFiles :: NubListR FilePath,
ghcOptInputModules :: NubListR ModuleName,
ghcOptOutputFile :: Flag FilePath,
ghcOptOutputDynFile :: Flag FilePath,
ghcOptSourcePathClear :: Flag Bool,
ghcOptSourcePath :: NubListR FilePath,
ghcOptThisUnitId :: Flag String,
ghcOptThisComponentId :: Flag ComponentId,
ghcOptInstantiatedWith :: [(ModuleName, OpenModule)],
ghcOptNoCode :: Flag Bool,
ghcOptPackageDBs :: PackageDBStack,
ghcOptPackages ::
NubListR (OpenUnitId, ModuleRenaming),
ghcOptHideAllPackages :: Flag Bool,
ghcOptWarnMissingHomeModules :: Flag Bool,
ghcOptNoAutoLinkPackages :: Flag Bool,
ghcOptLinkLibs :: [FilePath],
ghcOptLinkLibPath :: NubListR FilePath,
ghcOptLinkOptions :: [String],
ghcOptLinkFrameworks :: NubListR String,
ghcOptLinkFrameworkDirs :: NubListR String,
ghcOptNoLink :: Flag Bool,
ghcOptLinkNoHsMain :: Flag Bool,
ghcOptLinkModDefFiles :: NubListR FilePath,
ghcOptCcOptions :: [String],
ghcOptCxxOptions :: [String],
ghcOptAsmOptions :: [String],
ghcOptCppOptions :: [String],
ghcOptCppIncludePath :: NubListR FilePath,
ghcOptCppIncludes :: NubListR FilePath,
ghcOptFfiIncludes :: NubListR FilePath,
ghcOptLanguage :: Flag Language,
ghcOptExtensions :: NubListR Extension,
ghcOptExtensionMap :: Map Extension (Maybe Compiler.Flag),
ghcOptOptimisation :: Flag GhcOptimisation,
ghcOptDebugInfo :: Flag DebugInfoLevel,
ghcOptProfilingMode :: Flag Bool,
ghcOptProfilingAuto :: Flag GhcProfAuto,
ghcOptSplitSections :: Flag Bool,
ghcOptSplitObjs :: Flag Bool,
ghcOptNumJobs :: Flag (Maybe Int),
ghcOptHPCDir :: Flag FilePath,
ghcOptGHCiScripts :: [FilePath],
ghcOptHiSuffix :: Flag String,
ghcOptObjSuffix :: Flag String,
ghcOptDynHiSuffix :: Flag String,
ghcOptDynObjSuffix :: Flag String,
ghcOptHiDir :: Flag FilePath,
ghcOptObjDir :: Flag FilePath,
ghcOptOutputDir :: Flag FilePath,
ghcOptStubDir :: Flag FilePath,
ghcOptDynLinkMode :: Flag GhcDynLinkMode,
ghcOptStaticLib :: Flag Bool,
ghcOptShared :: Flag Bool,
ghcOptFPic :: Flag Bool,
ghcOptDylibName :: Flag String,
ghcOptRPaths :: NubListR FilePath,
ghcOptVerbosity :: Flag Verbosity,
ghcOptExtraPath :: NubListR FilePath,
ghcOptCabal :: Flag Bool
} deriving (Show, Generic)
data GhcMode = GhcModeCompile
| GhcModeLink
| GhcModeMake
| GhcModeInteractive
| GhcModeAbiHash
deriving (Show, Eq)
data GhcOptimisation = GhcNoOptimisation
| GhcNormalOptimisation
| GhcMaximumOptimisation
| GhcSpecialOptimisation String
deriving (Show, Eq)
data GhcDynLinkMode = GhcStaticOnly
| GhcDynamicOnly
| GhcStaticAndDynamic
deriving (Show, Eq)
data GhcProfAuto = GhcProfAutoAll
| GhcProfAutoToplevel
| GhcProfAutoExported
deriving (Show, Eq)
runGHC :: Verbosity -> ConfiguredProgram -> Compiler -> Platform -> GhcOptions
-> IO ()
runGHC verbosity ghcProg comp platform opts = do
runProgramInvocation verbosity (ghcInvocation ghcProg comp platform opts)
ghcInvocation :: ConfiguredProgram -> Compiler -> Platform -> GhcOptions
-> ProgramInvocation
ghcInvocation prog comp platform opts =
(programInvocation prog (renderGhcOptions comp platform opts)) {
progInvokePathEnv = fromNubListR (ghcOptExtraPath opts)
}
renderGhcOptions :: Compiler -> Platform -> GhcOptions -> [String]
renderGhcOptions comp _platform@(Platform _arch os) opts
| compilerFlavor comp `notElem` [GHC, GHCJS] =
error $ "Distribution.Simple.Program.GHC.renderGhcOptions: "
++ "compiler flavor must be 'GHC' or 'GHCJS'!"
| otherwise =
concat
[ case flagToMaybe (ghcOptMode opts) of
Nothing -> []
Just GhcModeCompile -> ["-c"]
Just GhcModeLink -> []
Just GhcModeMake -> ["--make"]
Just GhcModeInteractive -> ["--interactive"]
Just GhcModeAbiHash -> ["--abi-hash"]
, ghcOptExtraDefault opts
, [ "-no-link" | flagBool ghcOptNoLink ]
, maybe [] verbosityOpts (flagToMaybe (ghcOptVerbosity opts))
, [ "-fbuilding-cabal-package" | flagBool ghcOptCabal ]
, case flagToMaybe (ghcOptOptimisation opts) of
Nothing -> []
Just GhcNoOptimisation -> ["-O0"]
Just GhcNormalOptimisation -> ["-O"]
Just GhcMaximumOptimisation -> ["-O2"]
Just (GhcSpecialOptimisation s) -> ["-O" ++ s]
, case flagToMaybe (ghcOptDebugInfo opts) of
Nothing -> []
Just NoDebugInfo -> []
Just MinimalDebugInfo -> ["-g1"]
Just NormalDebugInfo -> ["-g2"]
Just MaximalDebugInfo -> ["-g3"]
, [ "-prof" | flagBool ghcOptProfilingMode ]
, case flagToMaybe (ghcOptProfilingAuto opts) of
_ | not (flagBool ghcOptProfilingMode)
-> []
Nothing -> []
Just GhcProfAutoAll
| flagProfAuto implInfo -> ["-fprof-auto"]
| otherwise -> ["-auto-all"]
Just GhcProfAutoToplevel
| flagProfAuto implInfo -> ["-fprof-auto-top"]
| otherwise -> ["-auto-all"]
Just GhcProfAutoExported
| flagProfAuto implInfo -> ["-fprof-auto-exported"]
| otherwise -> ["-auto"]
, [ "-split-sections" | flagBool ghcOptSplitSections ]
, [ "-split-objs" | flagBool ghcOptSplitObjs ]
, case flagToMaybe (ghcOptHPCDir opts) of
Nothing -> []
Just hpcdir -> ["-fhpc", "-hpcdir", hpcdir]
, if parmakeSupported comp
then case ghcOptNumJobs opts of
NoFlag -> []
Flag n -> ["-j" ++ maybe "" show n]
else []
, [ "-staticlib" | flagBool ghcOptStaticLib ]
, [ "-shared" | flagBool ghcOptShared ]
, case flagToMaybe (ghcOptDynLinkMode opts) of
Nothing -> []
Just GhcStaticOnly -> ["-static"]
Just GhcDynamicOnly -> ["-dynamic"]
Just GhcStaticAndDynamic -> ["-static", "-dynamic-too"]
, [ "-fPIC" | flagBool ghcOptFPic ]
, concat [ ["-dylib-install-name", libname] | libname <- flag ghcOptDylibName ]
, concat [ ["-osuf", suf] | suf <- flag ghcOptObjSuffix ]
, concat [ ["-hisuf", suf] | suf <- flag ghcOptHiSuffix ]
, concat [ ["-dynosuf", suf] | suf <- flag ghcOptDynObjSuffix ]
, concat [ ["-dynhisuf",suf] | suf <- flag ghcOptDynHiSuffix ]
, concat [ ["-outputdir", dir] | dir <- flag ghcOptOutputDir ]
, concat [ ["-odir", dir] | dir <- flag ghcOptObjDir ]
, concat [ ["-hidir", dir] | dir <- flag ghcOptHiDir ]
, concat [ ["-stubdir", dir] | dir <- flag ghcOptStubDir ]
, [ "-i" | flagBool ghcOptSourcePathClear ]
, [ "-i" ++ dir | dir <- flags ghcOptSourcePath ]
, [ "-I" ++ dir | dir <- flags ghcOptCppIncludePath ]
, [ "-optP" ++ opt | opt <- ghcOptCppOptions opts]
, concat [ [ "-optP-include", "-optP" ++ inc]
| inc <- flags ghcOptCppIncludes ]
, [ "-optc" ++ opt | opt <- ghcOptCcOptions opts]
, [ "-optc" ++ opt | opt <- ghcOptCxxOptions opts]
, [ "-opta" ++ opt | opt <- ghcOptAsmOptions opts]
, [ "-optl" ++ opt | opt <- ghcOptLinkOptions opts]
, ["-l" ++ lib | lib <- ghcOptLinkLibs opts]
, ["-L" ++ dir | dir <- flags ghcOptLinkLibPath ]
, if isOSX
then concat [ ["-framework", fmwk]
| fmwk <- flags ghcOptLinkFrameworks ]
else []
, if isOSX
then concat [ ["-framework-path", path]
| path <- flags ghcOptLinkFrameworkDirs ]
else []
, [ "-no-hs-main" | flagBool ghcOptLinkNoHsMain ]
, [ "-dynload deploy" | not (null (flags ghcOptRPaths)) ]
, concat [ [ "-optl-Wl,-rpath," ++ dir]
| dir <- flags ghcOptRPaths ]
, [ modDefFile | modDefFile <- flags ghcOptLinkModDefFiles ]
, concat [ [ case () of
_ | unitIdSupported comp -> "-this-unit-id"
| packageKeySupported comp -> "-this-package-key"
| otherwise -> "-package-name"
, this_arg ]
| this_arg <- flag ghcOptThisUnitId ]
, concat [ ["-this-component-id", prettyShow this_cid ]
| this_cid <- flag ghcOptThisComponentId ]
, if null (ghcOptInstantiatedWith opts)
then []
else "-instantiated-with"
: intercalate "," (map (\(n,m) -> prettyShow n ++ "="
++ prettyShow m)
(ghcOptInstantiatedWith opts))
: []
, concat [ ["-fno-code", "-fwrite-interface"] | flagBool ghcOptNoCode ]
, [ "-hide-all-packages" | flagBool ghcOptHideAllPackages ]
, [ "-Wmissing-home-modules" | flagBool ghcOptWarnMissingHomeModules ]
, [ "-no-auto-link-packages" | flagBool ghcOptNoAutoLinkPackages ]
, packageDbArgs implInfo (ghcOptPackageDBs opts)
, concat $ let space "" = ""
space xs = ' ' : xs
in [ ["-package-id", prettyShow ipkgid ++ space (prettyShow rns)]
| (ipkgid,rns) <- flags ghcOptPackages ]
, if supportsHaskell2010 implInfo
then [ "-X" ++ prettyShow lang | lang <- flag ghcOptLanguage ]
else []
, [ ext'
| ext <- flags ghcOptExtensions
, ext' <- case Map.lookup ext (ghcOptExtensionMap opts) of
Just (Just arg) -> [arg]
Just Nothing -> []
Nothing ->
error $ "Distribution.Simple.Program.GHC.renderGhcOptions: "
++ prettyShow ext ++ " not present in ghcOptExtensionMap."
]
, concat [ [ "-ghci-script", script ] | script <- ghcOptGHCiScripts opts
, flagGhciScript implInfo ]
, [ prettyShow modu | modu <- flags ghcOptInputModules ]
, flags ghcOptInputFiles
, concat [ [ "-o", out] | out <- flag ghcOptOutputFile ]
, concat [ [ "-dyno", out] | out <- flag ghcOptOutputDynFile ]
, ghcOptExtra opts
]
where
implInfo = getImplInfo comp
isOSX = os == OSX
flag flg = flagToList (flg opts)
flags flg = fromNubListR . flg $ opts
flagBool flg = fromFlagOrDefault False (flg opts)
verbosityOpts :: Verbosity -> [String]
verbosityOpts verbosity
| verbosity >= deafening = ["-v"]
| verbosity >= normal = []
| otherwise = ["-w", "-v0"]
packageDbArgsConf :: PackageDBStack -> [String]
packageDbArgsConf dbstack = case dbstack of
(GlobalPackageDB:UserPackageDB:dbs) -> concatMap specific dbs
(GlobalPackageDB:dbs) -> ("-no-user-package-conf")
: concatMap specific dbs
_ -> ierror
where
specific (SpecificPackageDB db) = [ "-package-conf", db ]
specific _ = ierror
ierror = error $ "internal error: unexpected package db stack: "
++ show dbstack
packageDbArgsDb :: PackageDBStack -> [String]
packageDbArgsDb dbstack = case dbstack of
(GlobalPackageDB:UserPackageDB:dbs)
| all isSpecific dbs -> concatMap single dbs
(GlobalPackageDB:dbs)
| all isSpecific dbs -> "-no-user-package-db"
: concatMap single dbs
dbs -> "-clear-package-db"
: concatMap single dbs
where
single (SpecificPackageDB db) = [ "-package-db", db ]
single GlobalPackageDB = [ "-global-package-db" ]
single UserPackageDB = [ "-user-package-db" ]
isSpecific (SpecificPackageDB _) = True
isSpecific _ = False
packageDbArgs :: GhcImplInfo -> PackageDBStack -> [String]
packageDbArgs implInfo
| flagPackageConf implInfo = packageDbArgsConf
| otherwise = packageDbArgsDb
instance Monoid GhcOptions where
mempty = gmempty
mappend = (<>)
instance Semigroup GhcOptions where
(<>) = gmappend