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:
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 compilation link library (compiling each Plugin{Nth}
as shared library) could works (as FFI) but
getPlugin
function point)Thank you!
UPDATE
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 the getPlugin
function has type PluginInterface
, 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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With