简体   繁体   English

Haskell,GHC 8:动态加载/导入模块

[英]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中运行得很好,除了它在执行应用程序时需要插件的源文件在当前工作目录中。


Edit (07.12.2017) 编辑(07.12.2017)

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

Edit (08.12.2017) 编辑(08.12.2017)

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM