ghc-9.14.1: GHC/Driver/Session/Units.hs
{-# LANGUAGE CPP #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE NondecreasingIndentation #-}
{-# LANGUAGE TupleSections #-}
module GHC.Driver.Session.Units (initMake, initMulti) where
-- The official GHC API
import qualified GHC
import GHC (parseTargetFiles, Ghc, GhcMonad(..))
import GHC.Driver.Env
import GHC.Driver.Errors
import GHC.Driver.Errors.Types
import GHC.Driver.Phases
import GHC.Driver.Session
import GHC.Driver.Ppr
import GHC.Driver.Pipeline ( oneShot, compileFile )
import GHC.Driver.Config.Diagnostic
import GHC.Unit.Env
import GHC.Unit (UnitId)
import GHC.Unit.Home.PackageTable
import qualified GHC.Unit.Home.Graph as HUG
import GHC.Unit.State ( emptyUnitState )
import qualified GHC.Unit.State as State
import GHC.Types.SrcLoc
import GHC.Types.SourceError
import GHC.Utils.Misc
import GHC.Utils.Panic
import GHC.Utils.Outputable as Outputable
import GHC.Utils.Monad ( liftIO, mapMaybeM )
import GHC.Data.Maybe
import System.IO
import System.Exit
import System.FilePath
import Control.Monad
import Data.List ( partition, (\\) )
import qualified Data.Set as Set
import Prelude
import GHC.ResponseFile (expandResponse)
import Data.Bifunctor
import GHC.Data.Graph.Directed
import qualified Data.List.NonEmpty as NE
-- Strip out any ["+RTS", ..., "-RTS"] sequences in the command string list.
removeRTS :: [String] -> [String]
removeRTS ("+RTS" : xs) =
case dropWhile (/= "-RTS") xs of
[] -> []
(_ : ys) -> removeRTS ys
removeRTS (y:ys) = y : removeRTS ys
removeRTS [] = []
initMake :: [(String,Maybe Phase)] -> Ghc [(String, Maybe Phase)]
initMake srcs = do
let (hs_srcs, non_hs_srcs) = partition isHaskellishTarget srcs
hsc_env <- GHC.getSession
-- if we have no haskell sources from which to do a dependency
-- analysis, then just do one-shot compilation and/or linking.
-- This means that "ghc Foo.o Bar.o -o baz" links the program as
-- we expect.
if (null hs_srcs)
then liftIO (oneShot hsc_env NoStop srcs) >> return []
else do
o_files <- mapMaybeM (\x -> liftIO $ compileFile hsc_env NoStop x)
non_hs_srcs
dflags <- GHC.getSessionDynFlags
let dflags' = dflags { ldInputs = map (FileOption "") o_files
++ ldInputs dflags }
_ <- GHC.setSessionDynFlags dflags'
return hs_srcs
initMulti :: NE.NonEmpty String
-> (DynFlags -> [(String,Maybe Phase)] -> [String] -> [String] -> IO ())
-- ^ Function to lint initMulti DynFlags and sources.
-- In GHC, this is instanced to @checkOptions@.
-> Ghc ([(String, Maybe UnitId, Maybe Phase)])
initMulti unitArgsFiles lintDynFlagsAndSrcs = do
hsc_env <- GHC.getSession
let logger = hsc_logger hsc_env
initial_dflags <- GHC.getSessionDynFlags
dynFlagsAndSrcs <- forM unitArgsFiles $ \f -> do
when (verbosity initial_dflags > 2) (liftIO $ print f)
args <- liftIO $ expandResponse [f]
(dflags2, fileish_args, warns) <- parseDynamicFlagsCmdLine logger initial_dflags (map (mkGeneralLocated f) (removeRTS args))
handleSourceError (\e -> do
GHC.printException e
liftIO $ exitWith (ExitFailure 1)) $ do
liftIO $ printOrThrowDiagnostics logger (initPrintConfig dflags2) (initDiagOpts dflags2) (GhcDriverMessage <$> warns)
let (dflags3, srcs, objs) = parseTargetFiles dflags2 (map unLoc fileish_args)
dflags4 = offsetDynFlags dflags3
let (hs_srcs, non_hs_srcs) = partition isHaskellishTarget srcs
-- This is dubious as the whole unit environment won't be set-up correctly, but
-- that doesn't matter for what we use it for (linking and oneShot)
let dubious_hsc_env = hscSetFlags dflags4 hsc_env
-- if we have no haskell sources from which to do a dependency
-- analysis, then just do one-shot compilation and/or linking.
-- This means that "ghc Foo.o Bar.o -o baz" links the program as
-- we expect.
if (null hs_srcs)
then liftIO (oneShot dubious_hsc_env NoStop srcs) >> return (dflags4, [])
else do
o_files <- mapMaybeM (\x -> liftIO $ compileFile dubious_hsc_env NoStop x)
non_hs_srcs
let dflags5 = dflags4 { ldInputs = map (FileOption "") o_files
++ ldInputs dflags4 }
liftIO $ lintDynFlagsAndSrcs dflags5 srcs objs []
pure (dflags5, hs_srcs)
let
unitDflags = NE.map fst dynFlagsAndSrcs
srcs = NE.map (\(dflags, lsrcs) -> map (uncurry (,Just $ homeUnitId_ dflags,)) lsrcs) dynFlagsAndSrcs
(hs_srcs, _non_hs_srcs) = unzip (map (partition (\(file, _uid, phase) -> isHaskellishTarget (file, phase))) (NE.toList srcs))
checkDuplicateUnits initial_dflags (NE.toList (NE.zip unitArgsFiles unitDflags))
(initial_home_graph, mainUnitId) <- liftIO $ createUnitEnvFromFlags unitDflags
let home_units = HUG.allUnits initial_home_graph
home_unit_graph <- forM initial_home_graph $ \homeUnitEnv -> do
let cached_unit_dbs = homeUnitEnv_unit_dbs homeUnitEnv
hue_flags = homeUnitEnv_dflags homeUnitEnv
dflags = homeUnitEnv_dflags homeUnitEnv
(dbs,unit_state,home_unit,mconstants) <- liftIO $ State.initUnits logger hue_flags cached_unit_dbs home_units
updated_dflags <- liftIO $ updatePlatformConstants dflags mconstants
emptyHpt <- liftIO $ emptyHomePackageTable
pure $ HomeUnitEnv
{ homeUnitEnv_units = unit_state
, homeUnitEnv_unit_dbs = Just dbs
, homeUnitEnv_dflags = updated_dflags
, homeUnitEnv_hpt = emptyHpt
, homeUnitEnv_home_unit = Just home_unit
}
checkUnitCycles initial_dflags home_unit_graph
let dflags = homeUnitEnv_dflags $ HUG.unitEnv_lookup mainUnitId home_unit_graph
unitEnv <- assertUnitEnvInvariant <$> (liftIO $ initUnitEnv mainUnitId home_unit_graph (ghcNameVersion dflags) (targetPlatform dflags))
let final_hsc_env = hsc_env { hsc_unit_env = unitEnv }
GHC.setSession final_hsc_env
-- if we have no haskell sources from which to do a dependency
-- analysis, then just do one-shot compilation and/or linking.
-- This means that "ghc Foo.o Bar.o -o baz" links the program as
-- we expect.
if (null hs_srcs)
then do
liftIO $ hPutStrLn stderr $ "Multi Mode can not be used for one-shot mode."
liftIO $ exitWith (ExitFailure 1)
else do
{-
o_files <- liftIO $ mapMaybeM
(\(src, uid, mphase) ->
compileFile (hscSetActiveHomeUnit (ue_unitHomeUnit (fromJust uid) unitEnv) final_hsc_env) NoStop (src, mphase)
)
(concat non_hs_srcs)
-}
-- MP: This should probably modify dflags for each unit?
--let dflags' = dflags { ldInputs = map (FileOption "") o_files
-- ++ ldInputs dflags }
return $ concat hs_srcs
checkUnitCycles :: DynFlags -> HUG.HomeUnitGraph -> Ghc ()
checkUnitCycles dflags graph = processSCCs (HUG.hugSCCs graph)
where
processSCCs [] = return ()
processSCCs (AcyclicSCC _: other_sccs) = processSCCs other_sccs
processSCCs (CyclicSCC uids: _) = throwGhcException $ CmdLineError $ showSDoc dflags (cycle_err uids)
cycle_err uids =
hang (text "Units form a dependency cycle:")
2
(one_err uids)
one_err uids = vcat $
(map (\uid -> text "-" <+> ppr uid <+> text "depends on") start)
++ [text "-" <+> ppr final]
where
start = init uids
final = last uids
-- | Check that we don't have multiple units with the same UnitId.
checkDuplicateUnits :: DynFlags -> [(FilePath, DynFlags)] -> Ghc ()
checkDuplicateUnits dflags flags =
unless (null duplicate_ids)
(throwGhcException $ CmdLineError $ showSDoc dflags multi_err)
where
uids = map (second homeUnitId_) flags
deduplicated_uids = ordNubOn snd uids
duplicate_ids = Set.fromList (map snd uids \\ map snd deduplicated_uids)
duplicate_flags = filter (flip Set.member duplicate_ids . snd) uids
one_err (fp, home_uid) = text "-" <+> ppr home_uid <+> text "defined in" <+> text fp
multi_err =
hang (text "Multiple units with the same unit-id:")
2
(vcat (map one_err duplicate_flags))
offsetDynFlags :: DynFlags -> DynFlags
offsetDynFlags dflags =
dflags { hiDir = c hiDir
, objectDir = c objectDir
, stubDir = c stubDir
, hieDir = c hieDir
, dumpDir = c dumpDir }
where
c f = augment_maybe (f dflags)
augment_maybe Nothing = Nothing
augment_maybe (Just f) = Just (augment f)
augment f | isRelative f, Just offset <- workingDirectory dflags = offset </> f
| otherwise = f
createUnitEnvFromFlags :: NE.NonEmpty DynFlags -> IO (HomeUnitGraph, UnitId)
createUnitEnvFromFlags unitDflags = do
unitEnvList <- forM unitDflags $ \dflags -> do
emptyHpt <- emptyHomePackageTable
let newInternalUnitEnv =
HUG.mkHomeUnitEnv emptyUnitState Nothing dflags emptyHpt Nothing
return (homeUnitId_ dflags, newInternalUnitEnv)
let activeUnit = fst $ NE.head unitEnvList
return (HUG.hugFromList (NE.toList unitEnvList), activeUnit)