2015-06-06 2 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 

App/услуги

... 
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. вс

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 

приложение.вс

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

Смежные вопросы