Out of the box Haskell plugin system

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

My goals of the plugin system are:

  • the production environment must be outside the box (all precompiled).
  • To download plugins, an application / service reset is enabled, but ideally it will download and update plugins on the fly.

One of the minimal examples might be:

Application / Service Plugin Interface ~

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

List of plugins

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

application / service

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

I think using dynamic link library compilation (compiling each Plugin{Nth} as a shared library) may work (like FFI), but

  • How to list and load each shared library at runtime? (get every getPlugin function point)
  • Is there any better way? (For example, some kind of "magic" process before starting the application / service)

Thanks!

UPDATE

Full working example

Following @ xnyhps big answer, full 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 
+7
haskell
source share
1 answer

There is a dynamic loader package that allows you to load additional object files or shared libraries into your object. (The version on Hackage does not work since 7.10, but the current version on GitHub does .)

With this you can 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 should keep in mind that the whole process is very unsafe : if you change the PluginInterface data type and try to download the plugin compiled using the old version, your application will crash. You should hope that the getPlugin function is of type PluginInterface , there is no check for this. Finally, if the plugin comes from an untrusted source, it can do anything, even if the function you are trying to call must be clean in Haskel.

+5
source share

All Articles