2015-06-06 90 views
7

我已經閱讀過有關Haskell的插件,但我無法達到我的目的(理想情況下用於生產環境)。開箱即用的Haskell插件系統

我的插件系統的目標是:

  1. 生產環境必須要開箱(所有預編譯)的。
  2. 加載插件已啓用重置應用程序/服務,但理想情況下它會加載和更新插件。

一個最小的例子是:

的應用程序/服務〜插件接口

module SharedTypes (PluginInterface (..)) where 

data PluginInterface = 
    PluginInterface { pluginName :: String 
        , runPlugin :: Int -> Int } 

一些插件列表

-- ~/plugins/plugin{Nth}.?? (with N=1..) 
module Plugin{Nth}(getPlugin) where 

import SharedTypes 

getPlugin :: PluginInterface 
getPlugin = PluginInterface "{Nth}th plugin" $ \x -> {Nth} * x 

應用程序/服務

... 
loadPlugins :: FilePath -> IO [PluginInterface] 
loadPlugins = undefined 
... 

我認爲使用動態編譯鏈接庫(編譯每個Plugin{Nth}作爲共享庫)能工程(如FFI),但

  1. 如何枚舉並在運行時加載每個共享庫?(獲得每個getPlugin功能點)
  2. 存在一些更好的方法? (例如,一些「魔術師」過程之前運行應用程序/服務)

謝謝!

UPDATE

完整的運行示例

繼大@xnyhps回答,使用ghc 7.10

SharedTypes.hs

module SharedTypes (
    PluginInterface (..) 
) where 

data PluginInterface = 
    PluginInterface { pluginName :: String 
        , runPlugin :: Int -> Int 
        } 

Plugin1一個完整的運行實例。 HS

module Plugin1 (
    getPlugin 
) where 

import SharedTypes 

getPlugin :: PluginInterface 
getPlugin = PluginInterface "Plugin1" $ \x -> 1 * x 

Plugin2.hs

module Plugin2 (
    getPlugin 
) where 

import SharedTypes 

getPlugin :: PluginInterface 
getPlugin = PluginInterface "Plugin2" $ \x -> 2 * x 

應用。HS

import SharedTypes 
import System.Plugins.DynamicLoader 
import System.Directory 
import Data.Maybe 
import Control.Applicative 
import Data.List 
import System.FilePath 
import Control.Monad 

loadPlugins :: FilePath -> IO [PluginInterface] 
loadPlugins path = getDirectoryContents path >>= mapM loadPlugin . filter (".plugin" `isSuffixOf`) 
    where loadPlugin file = do 
      m <- loadModuleFromPath (combine path file) -- absolute path 
            (Just path)   -- base of qualified name (or you'll get not found) 
      resolveFunctions 
      getPlugin <- loadFunction m "getPlugin" 
      return getPlugin 

main = do 

    -- and others used by plugins 
    addDLL "/usr/lib/ghc-7.10.1/base_I5BErHzyOm07EBNpKBEeUv/libHSbase-4.8.0.0-I5BErHzyOm07EBNpKBEeUv-ghc7.10.1.so" 
    loadModuleFromPath "/srv/despierto/home/josejuan/Projects/Solveet/PluginSystem/SharedTypes.o" Nothing 

    plugins <- loadPlugins "/srv/despierto/home/josejuan/Projects/Solveet/PluginSystem/plugins" 

    forM_ plugins $ \plugin -> do 
    putStrLn $ "Plugin name: " ++ pluginName plugin 
    putStrLn $ "  Run := " ++ show (runPlugin plugin 34) 

編譯和執行

[[email protected] PluginSystem]$ ghc --make -dynamic -fPIC -O3 Plugin1.hs 
[1 of 2] Compiling SharedTypes  (SharedTypes.hs, SharedTypes.o) 
[2 of 2] Compiling Plugin1   (Plugin1.hs, Plugin1.o) 
[[email protected] PluginSystem]$ ghc --make -dynamic -fPIC -O3 Plugin2.hs 
[2 of 2] Compiling Plugin2   (Plugin2.hs, Plugin2.o) 
[[email protected] PluginSystem]$ mv Plugin1.o plugins/Plugin1.plugin 
[[email protected] PluginSystem]$ mv Plugin2.o plugins/Plugin2.plugin 
[[email protected] PluginSystem]$ ghc --make -dynamic -fPIC -O3 app.hs 
[2 of 2] Compiling Main    (app.hs, app.o) 
Linking app ... 
[[email protected] PluginSystem]$ ./app 
Plugin name: Plugin1 
    Run := 34 
Plugin name: Plugin2 
    Run := 68 

回答

5

the dynamic-loader package,它允許你額外的目標文件或共享庫加載到進程。 (。上Hackage版本不具有7.10的工作,但目前version on GitHub does

有了這個,你可以這樣做:

import System.Plugins.DynamicLoader 
import System.Directory 

loadPlugins :: FilePath -> IO [PluginInterface] 
loadPlugins path = do 
    files <- getDirectoryContents path 
    mapM (\plugin_path -> do 
     m <- loadModuleFromPath (path ++ "/" ++ plugin_path) (Just path) 
     resolveFunctions 
     plugin <- loadFunction m "getPlugin" 
     return plugin) files 

但是,你要記住,整個過程非常不安全:如果您更改了PluginInterface數據類型並嘗試加載使用舊版本編譯的插件,則您的應用程序將崩潰。您必須希望getPlugin函數的類型爲PluginInterface,沒有檢查。最後,如果插件來自不受信任的源,它可以執行任何操作,即使您嘗試調用的函數在Haskel中應該是純的。

+0

謝謝!我認爲是完美的! *「然而」*當然,爲了確保兼容性('getVersion')和逆向兼容性('getPlugin :: Maybe(a - > a)','getPluginV2 :: Maybe(a - > a - > a)',等等)。 – josejuan