开箱即用的Haskell插件系统 [英] Out of the box Haskell plugin system

查看:122
本文介绍了开箱即用的Haskell插件系统的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我已经阅读了Haskell的插件,但是我无法达到我的目的(最好是在生产环境中使用)。



我的插件系统目标是:


  1. 生产环境必须开箱即用(全部预编译)。
  2. 加载插件已启用重置应用程序/服务,但理想情况下,它可以即时加载和更新插件。

be:

app / service〜插件界面

  module SharedTypes(PluginInterface(..))其中

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

某些插件列表

   - 〜/ plugins / plugin {Nth}。 (N = 1 ..)
模块插件{Nth}(getPlugin)其中

导入SharedTypes

getPlugin :: PluginInterface
getPlugin = PluginInterface {第N}个插件$ \x - > {Nth} * x

应用/服务

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

动态编译链接库(编译每个插件{Nth} 作为共享库)可以工作(如FFI),但


  1. 如何在运行时枚举并加载每个共享库(获取每个 getPlugin 函数点)

  2. 有更好的方法吗? (例如运行应用程序/服务之前的一些magic过程) >

    更新



    完整运行示例



    遵循伟大的@xnyhps回答,一个使用 ghc 7.10



    SharedTypes的完整运行示例。 hs

     模块SharedTypes(
    PluginInterface(..)
    )其中

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

    Plugin1.hs

     模块插件1(
    getPlugin
    )其中

    导入SharedTypes

    getPlugin :: PluginInterface
    getPlugin = PluginInterfacePlugin1$ \x - > 1 * x

    Plugin2.hs $ b

     模块插件2(
    getPlugin
    )其中

    导入SharedTypes

    getPlugin: :PluginInterface
    getPlugin = PluginInterfacePlugin2$ \x - > 2 * x

    app.hs $ b

      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`)
    其中loadPlugin file = do
    m< - loadModuleFromPath(合并路径文件) - 绝对路径
    (Just path) - 限定名(或者你没有找到)
    resolveFunctions
    getPlugin< - loadFunction mgetPlugin
    return getPlugin

    main = do

    - 其他插件使用的
    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.oNothing

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

    forM_插件$ \plugin - > do
    putStrLn $插件名称:++插件名称插件
    putStrLn $运行:=++ show(runPlugin插件34)

    汇编和执行
    $ b

      [josejuan @centerla PluginSystem] $ ghc --make -dynamic -fPIC -O3 Plugin1.hs 
    [1 of 2]编译SharedTypes(SharedTypes.hs,SharedTypes.o)
    [编译2]编译Plugin1 Plugin1.hs,Plugin1.o)
    [josejuan @ centella PluginSystem] $ ghc --make -dynamic -fPIC -O3 Plugin2.hs
    [2 of 2]编译Plugin2(Plugin2.hs,Plugin2。 o)
    [josejuan @ centella PluginSystem] $ mv Plugin1.o plugins / Plugin1.plugin
    [josejuan @ centella PluginSystem] $ mv Plugin2.o plugins / Plugin2.plugin
    [josejuan @ centella插件系统] $ ghc --make -dynamic -fPIC -O3 app.hs
    [2的2]编译主(app.hs,app.o)
    链接应用程序...
    [ josejuan @ centella PluginSystem] $ ./app
    插件名称:Plugin1
    运行:= 34
    Plugi n名称:Plugin2
    运行:= 68


    解决方案

    动态加载程序包,它允许您加载额外的目标文件或共享图书馆进入你的过程。 (Hackage上的版本不支持7.10,但是当前 GitHub上的版本)。



    有了这个,你可以这样做:

      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 mgetPlugin
    return plugin)files
    非常不安全:如果您更改了 PluginInterface 数据类型并尝试加载使用旧版本编译的插件,您的应用程序将崩溃。你必须希望 getPlugin 函数的类型为 PluginInterface ,但没有检查。最后,如果插件来自不受信任的源,它可以执行任何操作,即使您试图调用的函数在Haskel中应该是纯的。


    I've read about plugins in Haskell but I can't get a satisfactory way to my purposes (ideally to use in a production environment).

    My plugin system goals are:

    1. the production environment must to be out of the box (all precompiled).
    2. to load plugins is enabled reset app/service but ideally it would load and update plugins on the fly.

    One minimal example could be:

    The app/service ~ plugins interface

    module SharedTypes (PluginInterface (..)) where
    
    data PluginInterface =
         PluginInterface { pluginName :: String
                         , runPlugin  :: Int -> Int }
    

    Some plugin list

    -- ~/plugins/plugin{Nth}.??   (with N=1..)
    module Plugin{Nth}( getPlugin ) where
    
    import SharedTypes
    
    getPlugin :: PluginInterface
    getPlugin = PluginInterface "{Nth}th plugin" $ \x -> {Nth} * x
    

    App/service

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

    I think using dynamic compilation link library (compiling each Plugin{Nth} as shared library) could works (as FFI) but

    1. How enumerate and load each shared library at runtime? (get every getPlugin function point)
    2. Exists some better way? (Eg. some "magic" process before run application/service)

    Thank you!

    UPDATE

    Full running example

    Following the great @xnyhps answer, a full running example using 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
    

    app.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)
    

    Compilation and execution

    [josejuan@centella 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 )
    [josejuan@centella PluginSystem]$ ghc --make -dynamic -fPIC -O3 Plugin2.hs
    [2 of 2] Compiling Plugin2          ( Plugin2.hs, Plugin2.o )
    [josejuan@centella PluginSystem]$ mv Plugin1.o plugins/Plugin1.plugin
    [josejuan@centella PluginSystem]$ mv Plugin2.o plugins/Plugin2.plugin
    [josejuan@centella PluginSystem]$ ghc --make -dynamic -fPIC -O3 app.hs
    [2 of 2] Compiling Main             ( app.hs, app.o )
    Linking app ...
    [josejuan@centella PluginSystem]$ ./app
    Plugin name: Plugin1
         Run := 34
    Plugin name: Plugin2
         Run := 68
    

    解决方案

    There is the dynamic-loader package, which allows you to load extra object files or shared libraries into your process. (The version on Hackage doesn't work with 7.10, but the current version on GitHub does.)

    With this, you could do:

    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
    

    However, you have to keep in mind that the entire process is very unsafe: if you change your PluginInterface data type and try to load a plugin compiled with the old version, your application will crash. You have to hope that the getPlugin function has type PluginInterface, there's no check for that. Lastly, if the plugin comes from an untrusted source, it could execute anything, even though the function you try to call should be pure in Haskel.

    这篇关于开箱即用的Haskell插件系统的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆