[英]Haskell, GHC 8: dynamically load/import module
I need to have something like 我需要有类似的东西
-- Main.hs
module Main where
main :: IO ()
main = do
<import Plugin>
print Plugin.computation
With a Plugin like 有插件就好
-- Plugin.hs
module Plugin where
computation :: Int
computation = 4
However, I need the plugin to be compiled alongside the main application. 但是,我需要将插件与主应用程序一起编译。 They need to be deployed together. 他们需要一起部署。 Only the import (not the compilation) of the module should happen dynamically. 只有模块的导入(而不是编译)才能动态发生。
I found Dynamically loading compiled Haskell module - GHC 7.6 along the way and it works just fine with GHC 8.0.2 except for the fact that it requires the source file of the plugin to be in the current working directory when executing the application. 我发现动态加载已编译的Haskell模块 - GHC 7.6 ,它在GHC 8.0.2中运行得很好,除了它在执行应用程序时需要插件的源文件在当前工作目录中。
Is it possible to load a module from a String instead of a file using the GHC API? 是否可以使用GHC API从String而不是文件加载模块? http://hackage.haskell.org/package/ghc-8.2.1/docs/GHC.html#t:Target suggests that it's possible, but the documentation has many holes and I can't find a way to actually do this. http://hackage.haskell.org/package/ghc-8.2.1/docs/GHC.html#t:Target表明这是可能的,但文档有很多漏洞,我找不到实际做到这一点的方法。 If this can be accomplished, I can use file-embed to include the plugin source file into the compiled binary. 如果可以实现,我可以使用file-embed将插件源文件包含到已编译的二进制文件中。 Example: 例:
module Main where
-- Dynamic loading of modules
import GHC
import GHC.Paths ( libdir )
import DynFlags
import Unsafe.Coerce
import Data.Time.Clock (getCurrentTime)
import StringBuffer
pluginModuleNameStr :: String
pluginModuleNameStr = "MyPlugin"
pluginSourceStr :: String
pluginSourceStr = unlines
[ "module MyPlugin where"
, "computation :: Int"
, "computation = 4"
]
pluginModuleName :: ModuleName
pluginModuleName = mkModuleName pluginModuleNameStr
pluginSource :: StringBuffer
pluginSource = stringToStringBuffer pluginSourceStr
main :: IO ()
main = do
currentTime <- getCurrentTime
defaultErrorHandler defaultFatalMessager defaultFlushOut $ do
result <- runGhc (Just libdir) $ do
dflags <- getSessionDynFlags
setSessionDynFlags dflags
let target = Target { targetId = TargetModule $ pluginModuleName
, targetAllowObjCode = True
, targetContents = Just ( pluginSource
, currentTime
)
}
setTargets [target]
r <- load LoadAllTargets
case r of
Failed -> error "Compilation failed"
Succeeded -> do
setContext [IIDecl $ simpleImportDecl pluginModuleName]
result <- compileExpr ("MyPlugin.computation")
let result' = unsafeCoerce result :: Int
return result'
print result
This, however, results in 然而,这导致了
<command-line>: panic! (the 'impossible' happened)
(GHC version 8.0.2 for x86_64-apple-darwin):
module ‘MyPlugin’ is a package module
I can compile the "plugin" directly into the final binary by writing the source to a temp file and then loading it like in the linked post ( Dynamically loading compiled Haskell module - GHC 7.6 ). 我可以通过将源写入临时文件然后像链接的帖子( 动态加载编译的Haskell模块 - GHC 7.6 )一样加载它,将“插件”直接编译成最终的二进制文件。 However, this does not play well if the plugin imports packages from Hackage: 但是,如果插件从Hackage导入包,则这不会很好:
module Main where
import Control.Monad.IO.Class (liftIO)
import DynFlags
import GHC
import GHC.Paths (libdir)
import System.Directory (getTemporaryDirectory, removePathForcibly)
import Unsafe.Coerce (unsafeCoerce)
pluginModuleNameStr :: String
pluginModuleNameStr = "MyPlugin"
pluginSourceStr :: String
pluginSourceStr = unlines
[ "module MyPlugin where"
, "import Data.Aeson"
, "computation :: Int"
, "computation = 4"
]
writeTempFile :: IO FilePath
writeTempFile = do
dir <- getTemporaryDirectory
let file = dir ++ "/" ++ pluginModuleNameStr ++ ".hs"
writeFile file pluginSourceStr
return file
main :: IO ()
main = do
moduleFile <- writeTempFile
defaultErrorHandler defaultFatalMessager defaultFlushOut $ do
result <- runGhc (Just libdir) $ do
dflags <- getSessionDynFlags
setSessionDynFlags dflags
target <- guessTarget moduleFile Nothing
setTargets [target]
r <- load LoadAllTargets
liftIO $ removePathForcibly moduleFile
case r of
Failed -> error "Compilation failed"
Succeeded -> do
setContext [IIDecl $ simpleImportDecl $ mkModuleName pluginModuleNameStr]
result <- compileExpr "MyPlugin.computation"
let result' = unsafeCoerce result :: Int
return result'
print result
Is there a way to load packages when, for instance, MyPlugin
contains the statement import Data.Aeson
? 有没有办法加载包,例如, MyPlugin
包含语句import Data.Aeson
? If I add it to the plugin string, it fails with 如果我将它添加到插件字符串,它将失败
/var/folders/t2/hp9y8x6s6rs7zg21hdzvhbf40000gn/T/MyPlugin.hs:2:1: error:
Failed to load interface for ‘Data.Aeson’
Perhaps you meant Data.Version (from base-4.9.1.0)
Use -v to see a list of the files searched for.
haskell-loader-exe: panic! (the 'impossible' happened)
(GHC version 8.0.2 for x86_64-apple-darwin):
Compilation failed
CallStack (from HasCallStack):
error, called at app/Main.hs:40:19 in main:Main
The reason for my request is database support: We use Persistent to access a database and the dynamic import is needed to support multiple databases (MySQL, PostgreSQL and SQLite) while still allowing the end user to only install one of the three database servers (with other words: not requiring the user to install all of them if they only use, for instance, PostgreSQL). 我的请求的原因是数据库支持:我们使用Persistent来访问数据库,并且需要动态导入来支持多个数据库(MySQL,PostgreSQL和SQLite),同时仍然允许最终用户只安装三个数据库服务器中的一个(带有换句话说:如果用户仅使用PostgreSQL,则不要求用户安装所有这些内容。 The module that is database-specific should only be loaded when the user actually configures the main application to use that module. 只有在用户实际配置主应用程序以使用该模块时,才应加载特定于数据库的模块。
If I don't import Database.Persist.MySQL
, then the application does not require MySQL to be installed. 如果我不import Database.Persist.MySQL
,那么应用程序不需要安装MySQL。 Otherwise, the application fails with, for instance, 否则,应用程序失败,例如,
dyld: Library not loaded:
/usr/local/opt/mysql/lib/libmysqlclient.20.dylib
on macOS. 在macOS上。
A file with a matching module name must exist by the looks of it - it doesn't seem to matter what the file's content is. 具有匹配模块名称的文件必须存在于其外观中 - 文件的内容似乎并不重要。
On Linux I can even make it be a symlink to /dev/null and things work but a symlink to itself doesn't. 在Linux上,我甚至可以使它成为/ dev / null的符号链接,但事情可行,但符号链接本身却没有。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.