2015-06-06 16 views
7

Ho letto di plugin in Haskell ma non riesco a ottenere un modo soddisfacente per i miei scopi (idealmente da utilizzare in un ambiente di produzione).Out of the box Sistema di plugin Haskell

I miei obiettivi sistema di plugin sono:

  1. l'ambiente di produzione deve essere fuori dalla scatola (tutto precompilato).
  2. per caricare i plug-in è abilitato per reimpostare l'app/servizio ma idealmente caricare e aggiornare i plugin al volo.

Un esempio minimale potrebbe essere: l'interfaccia

L'applicazione/servizio ~ plugins

module SharedTypes (PluginInterface (..)) where 

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

qualche lista plug

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

import SharedTypes 

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

App/servizio

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

Penso che utilizzando dinamica compilazione libreria di collegamento (la compilazione di ogni Plugin{Nth} come libreria condivisa) potrebbe opere (come FFI), ma

  1. Come enumerare e caricare tutte le librerie condivise in fase di esecuzione? (ottenere ogni punto di funzione getPlugin)
  2. Esiste un modo migliore? (Eg. Alcune "magia" processo prima dell'applicazione run/servizio)

Grazie!

UPDATE

esempio in esecuzione completa

Dopo il grande @xnyhps risposta, un esempio di esecuzione completo utilizzando 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) 

compilazione e l'esecuzione

[[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 

risposta

5

C'è the dynamic-loader package, che permette di caricare file oggetto extra o librerie condivise nel vostro processo. (. La versione on Hackage non funziona con 7,10, ma la corrente version on GitHub does)

Con questo, si potrebbe fare:

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 

Tuttavia, è necessario tenere a mente che l'intero processo è molto pericoloso: se si modifica il tipo di dati PluginInterface e si prova a caricare un plug-in compilato con la versione precedente, l'applicazione si arresta in modo anomalo. Devi sperare che la funzione getPlugin abbia tipo PluginInterface, non ci sono controlli per questo. Infine, se il plugin proviene da una fonte non attendibile, potrebbe eseguire qualsiasi cosa, anche se la funzione che si tenta di chiamare dovrebbe essere pura in Haskel.

+0

Grazie! Penso sia perfetto! * "Tuttavia" * sì, naturalmente, molte cose aggiuntive potrebbero essere fatte per garantire la compatibilità ('getVersion') e la retrocompatibilità (' getPlugin :: Maybe (a -> a) ',' getPluginV2 :: Maybe (a -> a - > a) ', e così via). – josejuan

Problemi correlati