Мне нужно что-то вроде
-- 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)
Можно ли загрузить модуль из строки вместо файла с помощью API GHC? http://hackage.haskell.org/package/ghc-8.2.1/docs/GHC.html#t:Target предполагает, что это возможно, но в документации много дыр, и я не могу найти способ на самом деле сделать это. Если это можно сделать, я могу использовать 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.