packages feed

manatee-core-0.0.7: Manatee/Core/Dynload.hs

-- Author:     Andy Stewart <lazycat.manatee@gmail.com>
-- Maintainer: Andy Stewart <lazycat.manatee@gmail.com>
-- 
-- Copyright (C) 2010 Andy Stewart, all rights reserved.
-- 
-- This program is free software: you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation, either version 3 of the License, or
-- any later version.
-- 
-- This program is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-- GNU General Public License for more details.
-- 
-- You should have received a copy of the GNU General Public License
-- along with this program.  If not, see <http://www.gnu.org/licenses/>.

{-#Language ScopedTypeVariables#-}
{-# LANGUAGE DeriveDataTypeable #-}
module Manatee.Core.Dynload where

import Control.Concurrent
import Control.Monad
import Data.Maybe (fromMaybe)
import Data.Typeable
import DynFlags
import Exception
import GHC
import GHC.Paths (libdir)
import Manatee.Core.Config
import Manatee.Core.Types
import System.Directory
import System.FilePath

import Data.List
import System.Exit
import System.Process
import HscTypes
import OccName
import Module
import BinIface
import HscMain hiding (compileExpr)
import TcRnMonad

data EmptyCustomize = 
    EmptyCustomize 
    deriving Typeable

instance Customize EmptyCustomize where
  customizeConfigFile _ = ""
  customizeLoad       _ = []

-- | Empty customize new.
emptyCustomizeNew :: IO CustomizeWrap
emptyCustomizeNew = 
  return (CustomizeWrap EmptyCustomize)

-- | Cast customize.
castCustomize :: Typeable b => CustomizeWrap -> b
castCustomize (CustomizeWrap c) =
    fromMaybe (error "castCustomize : impossible failed.") (cast c)
 
-- | Dynamic loading module in running program.
dynload :: (String, String, [(String, HValue -> IO ())]) -> IO ()
dynload (filepath, moduleName, loadList) = 
   defaultErrorHandler defaultDynFlags $ 
      runGhc (Just libdir) $ 
        ghandle 
          -- Avoid exception when dynamic loading.
          (\ (e :: SomeException) -> liftIO $ putStrLn $ "# " ++ show e)
          (do                   
             -- Set DynFlags.
             dFlags <- getSessionDynFlags 
             setSessionDynFlags dFlags {ghcLink = LinkInMemory} -- this step is necessary
             -- Load module.
             target <- guessTarget filepath Nothing
             setTargets [target]
             r <- load (LoadUpTo (mkModuleName moduleName))
             -- Check.
             case r of
                 Failed ->
                     liftIO $ putStrLn $ "* Load module " ++ moduleName ++ " failed."
                 Succeeded -> do
                     liftIO $ putStrLn $ "* Load module " ++ moduleName ++ " success."
                     -- Find module.
                     m <- findModule (mkModuleName moduleName) Nothing
                     -- Set context.
                     setContext [] [m]
                     -- Load value.
                     forM_ loadList $ \ (symbolName, loadFun) -> 
                         ghandle 
                           -- Skip current symbol if get error when compileExpr.
                           (\ (e :: SomeException) -> 
                             liftIO $ putStrLn $ "# Get error and skip symbol `" ++ symbolName ++ "`\n    " ++ show e)
                           (do
                             hValue <- compileExpr (moduleName ++ "." ++ symbolName)
                             liftIO $ loadFun hValue))

-- | Load user config.
loadConfig :: CustomizeWrap -> Bool -> IO ()
loadConfig (CustomizeWrap customize) isAsync = do
  -- Get configure directory.
  configDir <- getConfigDirectory
  -- Init.
  let configFile = customizeConfigFile customize
      filepath = configDir </> configPath </> configFile
      configLoad = customizeLoad customize
      load = do
        putStrLn $ "* Loading configure file : " ++ filepath
        dynload (filepath, "Config.User", configLoad)
  isExist <- doesFileExist filepath

  -- Try load user's configure file.
  if null configLoad
     -- Skip load if no customize option. 
     then putStrLn "No customize option, skip load configure file."
     else if isExist
             -- Load config file.
             then if isAsync then forkIO load >> return () else load
             -- Skip load if no configure file exist.
             else putStrLn $ "Config file `" ++ filepath ++ "` no exist, use default customize option."

-- | Generate config file.
generateConfig :: String -> [String] -> IO ()
generateConfig configFile scanList = do
  -- Get directory.
  configDir <- getConfigDirectory
  let configHsFile = configDir </> configPath </> configFile
      configHiFile = replaceExtension configHsFile ".hi"
  isHsFileExist <- doesFileExist configHsFile
  isHiFileExist <- doesFileExist configHiFile
  -- Detect user configure file.
  scanUser <- 
      if isHsFileExist
         then do 
           processHandle <- runCommand ("ghc --make " ++ configHsFile)
           exitCode <- waitForProcess processHandle
           case exitCode of
             ExitSuccess -> return True
             ExitFailure _ -> do
                 putStrLn $ configHsFile ++ " compile failed, skip user's configure file."
                 return False
         else do 
           putStrLn $ configHsFile ++ " no exist, skip scan user's configure file."
           return False
  -- Generate User.hs and Import.hs.
  if scanUser && isHiFileExist
     then do
       -- Copy user's configure file.
       readFile configHsFile >>= writeFile "./Config/User.hs"  
       -- Get export list.
       moduleInterface <- readBinIface' configHiFile
       let ifaceExport = mi_exports moduleInterface
           exports = map (\ (mod, items) -> 
                            (Module.moduleNameString $ Module.moduleName mod
                            ,concatMap (\item -> 
                                            case item of
                                              Avail name -> [occNameString name]
                                              AvailTC _ list -> 
                                                  map occNameString list
                                       ) items)
                         ) ifaceExport
           (((_, exportList):_), _) = partition (\ (mName, _) -> mName == "Config.User") exports
       -- Generate Import.hs
       writeFile "./Config/Import.hs" $ generateImportString (filter (\x -> x `elem` scanList) exportList) scanList
     else do
       -- Generate User.hs and Import.hs
       writeFile "./Config/User.hs" emptyUserString
       writeFile "./Config/Import.hs" $ emptyImportString scanList

-- | Read binary interface file to get module information.
readBinIface' :: FilePath -> IO ModIface
readBinIface' hi_path = do
    e <- newHscEnv defaultCallbacks undefined
    initTcRnIf 'r' e undefined undefined (readBinIface IgnoreHiWay QuietBinIFaceReading hi_path)

-- | Concat improt, convert ["a","b","c"] to (a, b, c)
concatImport :: [String] -> String
concatImport []  = ""
concatImport [x] = x
concatImport (x:xs) = 
  (x ++ ", ") ++ concatImport xs

-- | Emtpy user string.
emptyUserString :: String
emptyUserString = 
    copyrightString
 ++ "module Config.User where"

-- | Emtpy import string.
emptyImportString :: [String] -> String
emptyImportString scanList = 
    copyrightString
 ++ "module Config.Import "
 ++ "(" ++ concatImport scanList ++ ") where \n\n"
 ++ "import Config.Default"

-- | Generate import string.
generateImportString :: [String] -> [String] -> String
generateImportString [] scanList = 
    emptyImportString scanList
generateImportString exportList scanList =
    copyrightString
 ++ "module Config.Import "
 ++ "(" ++ concatImport scanList ++ ") where \n\n"
 ++ "import Config.User (" ++ concatImport exportList ++ ")\n"
 ++ "import Config.Default hiding (" ++ concatImport exportList ++ ")\n"

-- | Copyright string.
copyrightString :: String
copyrightString = 
     "-- Author:     Andy Stewart <lazycat.manatee@gmail.com>"
  ++ "\n-- Maintainer: Andy Stewart <lazycat.manatee@gmail.com>"
  ++ "\n-- "
  ++ "\n-- Copyright (C) 2010 Andy Stewart, all rights reserved."
  ++ "\n-- "
  ++ "\n-- This program is free software: you can redistribute it and/or modify"
  ++ "\n-- it under the terms of the GNU General Public License as published by"
  ++ "\n-- the Free Software Foundation, either version 3 of the License, or"
  ++ "\n-- any later version."
  ++ "\n-- "
  ++ "\n-- This program is distributed in the hope that it will be useful,"
  ++ "\n-- but WITHOUT ANY WARRANTY; without even the implied warranty of"
  ++ "\n-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the"
  ++ "\n-- GNU General Public License for more details."
  ++ "\n-- "
  ++ "\n-- You should have received a copy of the GNU General Public License"
  ++ "\n-- along with this program.  If not, see <http://www.gnu.org/licenses/>.\n\n"