开箱即用的Haskell插件系统 [英] Out of the box Haskell plugin system
问题描述
我已经阅读了Haskell的插件,但是我无法达到我的目的(最好是在生产环境中使用)。
我的插件系统目标是:
- 生产环境必须开箱即用(全部预编译)。
- 加载插件已启用重置应用程序/服务,但理想情况下,它可以即时加载和更新插件。
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),但
- 如何在运行时枚举并加载每个共享库(获取每个
getPlugin
函数点) - 有更好的方法吗? (例如运行应用程序/服务之前的一些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
$ p $但是,您必须记住整个过程非常不安全:如果您更改了
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:
- the production environment must to be out of the box (all precompiled).
- 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
compilationlink library (compiling eachPlugin{Nth}
as shared library) could works (as FFI) but- How enumerate and load each shared library at runtime? (get every
getPlugin
function point) - 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 thegetPlugin
function has typePluginInterface
, 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屋!