Haskell, GHC 8: модуль динамической загрузки / импорта
Мне нужно что-то вроде
-- Main.hs
module Main where
main :: IO ()
main = do
<import Plugin>
print Plugin.computation
С плагином вроде
-- Plugin.hs
module Plugin where
computation :: Int
computation = 4
Тем не менее, мне нужно, чтобы плагин был скомпилирован вместе с основным приложением. Они должны быть развернуты вместе. Только импорт (не компиляция) модуля должен происходить динамически.
Я обнаружил, что динамически загружаемый скомпилированный модуль Haskell - GHC 7.6 попутно работает, и он прекрасно работает с GHC 8.0.2, за исключением того факта, что при запуске приложения требуется, чтобы исходный файл плагина находился в текущем рабочем каталоге.
Редактировать (07.12.2017)
Можно ли загрузить модуль из строки вместо файла, используя GHC API? http://hackage.haskell.org/package/ghc-8.2.1/docs/GHC.html предполагает, что это возможно, но в документации много дыр, и я не могу найти способ сделать это, Если это можно сделать, я могу использовать file-embed для включения исходного файла плагина в скомпилированный двоичный файл. Пример:
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
Это, однако, приводит к
<command-line>: panic! (the 'impossible' happened)
(GHC version 8.0.2 for x86_64-apple-darwin):
module ‘MyPlugin’ is a package module
Изменить (08.12.2017)
Я могу скомпилировать "плагин" непосредственно в окончательный двоичный файл, записав исходный код во временный файл, а затем загрузив его, как в связанном посте ( Динамическая загрузка скомпилированного модуля Haskell - GHC 7.6). Однако, это не очень хорошо, если плагин импортирует пакеты из 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
Есть ли способ загрузить пакеты, когда, например, MyPlugin
содержит утверждение import Data.Aeson
? Если я добавлю его в строку плагина, он потерпит неудачу с
/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
Причиной моего запроса является поддержка базы данных: мы используем Persistent для доступа к базе данных, а динамический импорт необходим для поддержки нескольких баз данных (MySQL, PostgreSQL и SQLite), при этом все же позволяя конечному пользователю установить только один из трех серверов баз данных (с Другими словами: не требуется, чтобы пользователь устанавливал их все, если они используют только, например, PostgreSQL). Модуль, зависящий от базы данных, должен загружаться только тогда, когда пользователь фактически настраивает основное приложение для использования этого модуля.
Если я не import Database.Persist.MySQL
, то приложение не требует установки MySQL. В противном случае приложение не работает, например, с
dyld: Library not loaded:
/usr/local/opt/mysql/lib/libmysqlclient.20.dylib
на macOS.
1 ответ
Похоже, файл с соответствующим именем модуля должен существовать - не имеет значения, каково содержимое файла.
В Linux я даже могу сделать это символической ссылкой на /dev/null, и все работает, но символическая ссылка на себя не работает.