[Haskell-cafe] How to reload module that package has linked in memory?
Andy Stewart
lazycat.manatee at gmail.com
Sat Dec 25 16:47:04 CET 2010
Hi all,
I'm working on how to dynamic loading library into *running* program.
I can dynamic loading/update module into *running* program if have *new*
package installed.
Example, when program *startup* and loading package foo-0.0.1 with function :
api :: String -> String
api =
reverse
then, i change `api` and install by foo-0.0.2:
api :: String -> String
api =
show . length
and dynamic-linking foo-0.0.2 works fine, i change api's value in
*runtime*.
And problem is, my solution just work when you install *new* package in
Cabal/GHC database (foo-0.0.1 => foo-0.0.2),
if i just change `api` value and don't install *new* package, my code can't
work.
I use Linker.linkPackages and Linker.getHValue to get symbol value, but
looks Linker.getHValue can't get *update* value once current package has
linked in memory.
So how to make Linker.getHValue can get *update* value and don't need
install *new* package in Cabal/GHC database?
How to *reload* module with current version package?
Or any GHC-API missing?
Below is my source code, you just need look function `load`.
Thanks! :)
-- Andy
------------------------------> source code start <------------------------------
module System.Dynload (
PackageVersion (..),
dynload
) where
import Control.Monad (forM_)
import Data.IORef
import GHC.Paths (libdir)
import MonadUtils (liftIO)
import Data.List
import Data.Ord
import Distribution.Package
import qualified DynFlags
import qualified Exception
import qualified GHC
import qualified HscTypes
import qualified IOEnv
import qualified Linker
import qualified LoadIface
import qualified Maybes
import qualified Module
import qualified Name
import qualified OccName
import qualified Outputable
import qualified PackageConfig as PC
import qualified Packages
import qualified SrcLoc
import qualified TcRnTypes
import qualified UniqSupply
import qualified Unique
data PackageVersion = Newest
| Version String
deriving (Ord, Show, Eq)
dynload :: (String, PackageVersion, String, [(String, Linker.HValue -> IO ())]) -> IO ()
dynload (packageName, packageVersion, moduleName, loadList) =
GHC.defaultErrorHandler DynFlags.defaultDynFlags $
GHC.runGhc (Just libdir) $ do
-- Update Flags of session.
sessionFlags <- GHC.getSessionDynFlags
GHC.setSessionDynFlags sessionFlags -- this is ncessary, otherwise get GHC error
-- Initialise package information.
(flags, _) <- liftIO $ Packages.initPackages sessionFlags
-- Search packages that export modules.
let packages = map fst
$ filter snd
$ Packages.lookupModuleInAllPackages flags (Module.mkModuleName moduleName)
exportError pName mName =
"# Package " ++ pName ++ " not exist or not export module " ++ mName
result =
case packages of
[] -> Left $ "# No package export module : " ++ moduleName
_ ->
let matchPackages = filter (\x -> packageConfigName x == packageName) packages
in case matchPackages of
[] -> Left $ exportError packageName moduleName
_ ->
case packageVersion of
Version pv ->
let pName = packageName ++ "-" ++ pv
versions = map packageConfigVersion matchPackages
in if pv `elem` versions
then Right pName
else Left $ exportError pName moduleName
Newest ->
case findNewestPackage matchPackages of
Nothing ->
Left "# dynload : Impossible reach here"
Just packageConfig ->
Right $ packageConfigIdString packageConfig
case result of
Left err ->
liftIO $ putStrLn err
Right packageNameStr -> do
liftIO $ putStrLn $ "* Use package : " ++ packageNameStr
-- Initialise the dynamic linker.
liftIO $ Linker.initDynLinker flags
hscEnv <- GHC.getSession
forM_ loadList $ \ (symbolName, loadFun) -> do
-- Because symbol perhaps re-export from external module.
-- So we need parse symbol to find define location.
parseResult <- liftIO $ parseSymbol (packageNameStr, moduleName, symbolName) hscEnv flags
case parseResult of
Just args -> load args flags loadFun
Nothing -> return ()
-- | Internal load function for pdynload.
load :: (GHC.GhcMonad m)
=> (String, String, String)
-> GHC.DynFlags
-> (Linker.HValue -> IO ())
-> m ()
load (packageName, moduleName, symbolName) flags loadFun =
Exception.ghandle
(\(GHC.CmdLineError _) ->
-- Catch package error.
liftIO $ putStrLn $ "# Unknown package " ++ packageName)
(do
-- Debug code.
-- liftIO $ Linker.showLinkerState
-- Link exactly the specified packages, and their dependents
-- (unless of course they are already linked).
-- The dependents are linked automatically,
-- and it doesn't matter what order you specify the input packages.
let packageId = Module.stringToPackageId packageName
liftIO $ Linker.linkPackages flags [packageId]
Exception.ghandle
(\(GHC.ProgramError err) -> liftIO $ putStrLn $ "# " ++ err)
(do
liftIO $ putStrLn $ "* Linking " ++ packageName ++ ":" ++ moduleName ++ "." ++ symbolName ++ " ..."
-- Get current session.
session <- GHC.getSession
-- Create a name which definitely originates in the given module.
let name = Name.mkExternalName
(Unique.mkBuiltinUnique 0)
(Module.mkModule packageId
(Module.mkModuleName moduleName))
(OccName.mkVarOcc symbolName)
SrcLoc.noSrcSpan
-- Get the HValue associated with the given name.
-- May cause loading the module that contains the name.
result <- liftIO $ Linker.getHValue session name
liftIO $ loadFun result))
-- | Parse symbol whether defined in current module.
-- If symbol is re-export other module, parse recursively,
-- until found the define location of symbol.
parseSymbol :: (String, String, String)
-> HscTypes.HscEnv
-> GHC.DynFlags
-> IO (Maybe (String, String, String))
parseSymbol (packageName, moduleName, symbolName) hscEnv flags = do
putStrLn $ "* Parse " ++ packageName ++ ":" ++ moduleName ++ "." ++ symbolName ++ " ..."
-- Build unique supply to build environment.
uniqueSupply <- UniqSupply.mkSplitUniqSupply 'a'
uniqueSupplyIORef <- newIORef uniqueSupply
-- Initialise.
let packageId = Module.stringToPackageId packageName
module' = Module.mkModule packageId $ Module.mkModuleName moduleName
environment = TcRnTypes.Env {
TcRnTypes.env_top = hscEnv,
TcRnTypes.env_us = uniqueSupplyIORef,
TcRnTypes.env_gbl = (),
TcRnTypes.env_lcl = ()}
-- Find and read interface file.
iface <- IOEnv.runIOEnv environment
$ LoadIface.findAndReadIface Outputable.empty module' False
case iface of
-- Return Nothing if can't found interface file.
Maybes.Failed _ -> do
putStrLn $ "# Can't found interface file of " ++ packageName ++ ":" ++ moduleName ++ "." ++ symbolName
return Nothing
-- Parse symbol.
Maybes.Succeeded (moduleInterface, hiFile) -> do
putStrLn $ "* Scan interface file " ++ hiFile ++ " ..."
-- Export list of current module
let ifaceExport = HscTypes.mi_exports moduleInterface
-- [(ModuleName, [type])]
exports = map (\ (mod, items) ->
(Module.moduleNameString $ Module.moduleName mod
,concatMap (\item ->
case item of
HscTypes.Avail name -> [OccName.occNameString name]
HscTypes.AvailTC _ list ->
map OccName.occNameString list
) items)
) ifaceExport
-- Partition current module and external module.
(currentExports, otherExports) = partition (\ (mName, _) -> mName == moduleName) exports
case findSymbolInExportList currentExports symbolName of
-- Return current module if found symbol in export list of current module.
Just _ -> do
putStrLn $ "* '" ++ symbolName ++ "' defined in " ++ packageName ++ ":" ++ moduleName
return $ Just (packageName, moduleName, symbolName)
Nothing ->
-- Parse recursively if symbol is re-export from external module.
case findSymbolInExportList otherExports symbolName of
Just mn -> do
putStrLn $ "* '" ++ symbolName ++ "' is re-export from module " ++ mn
-- Lookup new package of external module.
newPackageName <- do
putStrLn $ "* Lookup package of module " ++ mn
lookupPackageName flags mn
case newPackageName of
-- Parse symbol in new package.
Just npn -> parseSymbol (npn, mn, symbolName) hscEnv flags
-- Return Nothing if package not found.
Nothing -> return Nothing
-- Return Nothing if can't found symbol in interface file.
-- If reach this, interface file is incorrect.
Nothing -> do
putStrLn $ "# Can't found symbol " ++ symbolName ++ " in " ++ hiFile
return Nothing
-- | Lookup package name from ghc database.
lookupPackageName :: DynFlags.DynFlags -> String -> IO (Maybe String)
lookupPackageName flags moduleName
-- Return Nothing when no package found.
| packageNum == 0
= do
putStrLn $ "# Can't found module " ++ show moduleName
return Nothing
| packageNum == 1
= do
let (packageConfig, isExpose) = head packages
pName = packageConfigIdString packageConfig
if isExpose
-- Return package name when package expose module.
then return $ Just pName
-- Return Nothing if module hide in package.
else do
putStrLn $ "# Module " ++ show moduleName ++ " hide in package " ++ pName
return Nothing
-- Return Nothing if no package expose module.
| null exposePackages
= do
putStrLn $ "# Can't found module " ++ show moduleName
return Nothing
-- Return first match package when found module in multiple packages.
| otherwise
=
case findNewestPackage exposePackages of
Nothing -> do
putStrLn "# lookupPackageName: Impossible reach here"
return Nothing
Just packageConfig -> do
let pName = packageConfigIdString packageConfig
putStrLn $ "# Module " ++ moduleName ++ " expose in multiple packages."
putStrLn $ "* Use package : " ++ pName
return $ Just pName
where packages -- search package that *contain* module.
= Packages.lookupModuleInAllPackages flags (Module.mkModuleName moduleName)
packageNum -- length of package list
= length packages
exposePackages -- filter package that *export* module
= map fst $ filter snd packages
-- | Find symbol in export list of module.
-- Return module name when found symbol in export list.
-- Otherwise return Nothing.
findSymbolInExportList :: Eq b => [(a, [b])] -> b -> Maybe a
findSymbolInExportList [] _ = Nothing
findSymbolInExportList ((moduleName, symList) :xs) sym
| sym `elem` symList
= Just moduleName
| otherwise
= findSymbolInExportList xs sym
-- | Get PackageConfig id string.
packageConfigIdString :: PC.PackageConfig -> String
packageConfigIdString = Module.packageIdString . PC.packageConfigId
-- | The package name of PC.
packageConfigName :: PC.PackageConfig -> String
packageConfigName packageConfig = packageName
where (PackageName packageName) =
PC.pkgName $ PC.sourcePackageId packageConfig
-- | The package version of PC.
packageConfigVersion :: PC.PackageConfig -> String
packageConfigVersion =
showVersion . PC.versionBranch . PC.pkgVersion . PC.sourcePackageId
-- | Find newest package.
findNewestPackage :: [PC.PackageConfig] -> Maybe PC.PackageConfig
findNewestPackage [] = Nothing
findNewestPackage [a] = Just a
findNewestPackage xs = Just $ maximumBy (comparing (PC.pkgVersion . PC.sourcePackageId)) xs
-- | Convert version [x,y,z] to "x.y.z".
showVersion :: [Int] -> String
showVersion [] = ""
showVersion [x] = show x
showVersion (x:xs) =
(show x ++ ".") ++ showVersion xs
------------------------------> source code end <------------------------------
More information about the Haskell-Cafe
mailing list