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