summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSylvain Henry <sylvain@haskus.fr>2022-01-14 14:19:11 +0100
committerMarge Bot <ben+marge-bot@smart-cactus.org>2022-08-10 09:44:46 -0400
commitf95bbdcae3e6710a92dd8244321677eef91890de (patch)
tree214a46a6f0549baf14d2749bdd60124eb03c591e
parent823fe5b56450a7eefbf41ce8ece34095bf2217ee (diff)
downloadhaskell-f95bbdcae3e6710a92dd8244321677eef91890de.tar.gz
Add support for external static plugins (#20964)
This patch adds a new command-line flag: -fplugin-library=<file-path>;<unit-id>;<module>;<args> used like this: -fplugin-library=path/to/plugin.so;package-123;Plugin.Module;["Argument","List"] It allows a plugin to be loaded directly from a shared library. With this approach, GHC doesn't compile anything for the plugin and doesn't load any .hi file for the plugin and its dependencies. As such GHC doesn't need to support two environments (one for plugins, one for target code), which was the more ambitious approach tracked in #14335. Fix #20964 Co-authored-by: Josh Meredith <joshmeredith2008@gmail.com>
-rw-r--r--compiler/GHC/Driver/Plugins.hs99
-rw-r--r--compiler/GHC/Driver/Plugins/External.hs79
-rw-r--r--compiler/GHC/Driver/Session.hs13
-rw-r--r--compiler/GHC/Runtime/Loader.hs37
-rw-r--r--compiler/ghc.cabal.in1
-rw-r--r--docs/users_guide/extending_ghc.rst14
-rw-r--r--testsuite/tests/count-deps/CountDepsAst.stdout1
-rw-r--r--testsuite/tests/count-deps/CountDepsParser.stdout1
-rw-r--r--testsuite/tests/plugins/Makefile15
-rw-r--r--testsuite/tests/plugins/all.T5
-rw-r--r--testsuite/tests/plugins/plugins-external.hs4
-rw-r--r--testsuite/tests/plugins/plugins-external.stderr2
-rw-r--r--testsuite/tests/plugins/plugins-external.stdout1
-rw-r--r--testsuite/tests/plugins/shared-plugin/LICENSE10
-rw-r--r--testsuite/tests/plugins/shared-plugin/Makefile20
-rw-r--r--testsuite/tests/plugins/shared-plugin/Setup.hs3
-rw-r--r--testsuite/tests/plugins/shared-plugin/Simple/Plugin.hs26
-rw-r--r--testsuite/tests/plugins/shared-plugin/shared-plugin.cabal21
18 files changed, 341 insertions, 11 deletions
diff --git a/compiler/GHC/Driver/Plugins.hs b/compiler/GHC/Driver/Plugins.hs
index 9a5bfefc6f..d260c9c206 100644
--- a/compiler/GHC/Driver/Plugins.hs
+++ b/compiler/GHC/Driver/Plugins.hs
@@ -1,4 +1,11 @@
{-# LANGUAGE RankNTypes #-}
+{-# LANGUAGE CPP #-}
+
+#if defined(HAVE_INTERNAL_INTERPRETER)
+{-# LANGUAGE MagicHash #-}
+{-# LANGUAGE LambdaCase #-}
+{-# LANGUAGE UnboxedTuples #-}
+#endif
-- | Definitions for writing /plugins/ for GHC. Plugins can hook into
@@ -14,6 +21,10 @@ module GHC.Driver.Plugins (
, CommandLineOption
, PsMessages(..)
, ParsedResult(..)
+
+ -- * External plugins
+ , loadExternalPlugins
+
-- ** Recompilation checking
, purePlugin, impurePlugin, flagRecompile
, PluginRecompile(..)
@@ -52,6 +63,7 @@ module GHC.Driver.Plugins (
, PluginWithArgs(..), pluginsWithArgs, pluginRecompile'
, LoadedPlugin(..), lpModuleName
, StaticPlugin(..)
+ , ExternalPlugin(..)
, mapPlugins, withPlugins, withPlugins_
) where
@@ -60,6 +72,7 @@ import GHC.Prelude
import GHC.Driver.Env
import GHC.Driver.Monad
import GHC.Driver.Phases
+import GHC.Driver.Plugins.External
import GHC.Unit.Module
import GHC.Unit.Module.ModIface
@@ -75,8 +88,12 @@ import GHC.Core.Opt.Monad ( CoreM )
import GHC.Core.Opt.Pipeline.Types ( CoreToDo )
import GHC.Hs
import GHC.Types.Error (Messages)
+import GHC.Linker.Types
+import GHC.Types.Unique.DFM
+
import GHC.Utils.Fingerprint
-import GHC.Utils.Outputable (Outputable(..), text, (<+>))
+import GHC.Utils.Outputable
+import GHC.Utils.Panic
import Data.List (sort)
@@ -85,8 +102,13 @@ import Data.List (sort)
import qualified Data.Semigroup
import Control.Monad
-import GHC.Linker.Types
-import GHC.Types.Unique.DFM
+
+#if defined(HAVE_INTERNAL_INTERPRETER)
+import GHCi.ObjLink
+import GHC.Exts (addrToAny#, Ptr(..))
+import GHC.Utils.Encoding
+#endif
+
-- | Command line options gathered from the -PModule.Name:stuff syntax
-- are given to you as this type
@@ -196,6 +218,14 @@ data LoadedPlugin = LoadedPlugin
-- ^ the module containing the plugin
}
+-- | External plugin loaded directly from a library without loading module
+-- interfaces
+data ExternalPlugin = ExternalPlugin
+ { epPlugin :: PluginWithArgs -- ^ Plugin with its arguments
+ , epUnit :: String -- ^ UnitId
+ , epModule :: String -- ^ Module name
+ }
+
-- | A static plugin with its arguments. For registering compiled-in plugins
-- through the GHC API.
data StaticPlugin = StaticPlugin
@@ -285,6 +315,10 @@ data Plugins = Plugins
-- To add dynamically loaded plugins through the GHC API see
-- 'addPluginModuleName' instead.
+ , externalPlugins :: ![ExternalPlugin]
+ -- ^ External plugins loaded directly from libraries without loading
+ -- module interfaces.
+
, loadedPlugins :: ![LoadedPlugin]
-- ^ Plugins dynamically loaded after processing arguments. What
-- will be loaded here is directed by DynFlags.pluginModNames.
@@ -299,12 +333,17 @@ data Plugins = Plugins
}
emptyPlugins :: Plugins
-emptyPlugins = Plugins [] [] ([], emptyUDFM)
-
+emptyPlugins = Plugins
+ { staticPlugins = []
+ , externalPlugins = []
+ , loadedPlugins = []
+ , loadedPluginDeps = ([], emptyUDFM)
+ }
pluginsWithArgs :: Plugins -> [PluginWithArgs]
pluginsWithArgs plugins =
map lpPlugin (loadedPlugins plugins) ++
+ map epPlugin (externalPlugins plugins) ++
map spPlugin (staticPlugins plugins)
-- | Perform an operation by using all of the plugins in turn.
@@ -328,3 +367,53 @@ data FrontendPlugin = FrontendPlugin {
}
defaultFrontendPlugin :: FrontendPlugin
defaultFrontendPlugin = FrontendPlugin { frontend = \_ _ -> return () }
+
+
+-- | Load external plugins
+loadExternalPlugins :: [ExternalPluginSpec] -> IO [ExternalPlugin]
+loadExternalPlugins [] = return []
+#if !defined(HAVE_INTERNAL_INTERPRETER)
+loadExternalPlugins _ = do
+ panic "loadExternalPlugins: can't load external plugins with GHC built without internal interpreter"
+#elif !defined(CAN_LOAD_DLL)
+loadExternalPlugins _ = do
+ panic "loadExternalPlugins: loading shared libraries isn't supported by this compiler"
+#else
+loadExternalPlugins ps = do
+ -- initialize the linker
+ initObjLinker RetainCAFs
+ -- load plugins
+ forM ps $ \(ExternalPluginSpec path unit mod_name opts) -> do
+ loadExternalPluginLib path
+ -- lookup symbol
+ let ztmp = zEncodeString mod_name ++ "_plugin_closure"
+ symbol
+ | null unit = ztmp
+ | otherwise = zEncodeString unit ++ "_" ++ ztmp
+ plugin <- lookupSymbol symbol >>= \case
+ Nothing -> pprPanic "loadExternalPlugins"
+ (vcat [ text "Symbol not found"
+ , text " Library path: " <> text path
+ , text " Symbol : " <> text symbol
+ ])
+ Just (Ptr addr) -> case addrToAny# addr of
+ (# a #) -> pure a
+
+ pure $ ExternalPlugin (PluginWithArgs plugin opts) unit mod_name
+
+loadExternalPluginLib :: FilePath -> IO ()
+loadExternalPluginLib path = do
+ -- load library
+ loadDLL path >>= \case
+ Just errmsg -> pprPanic "loadExternalPluginLib"
+ (vcat [ text "Can't load plugin library"
+ , text " Library path: " <> text path
+ , text " Error : " <> text errmsg
+ ])
+ Nothing -> do
+ -- resolve objects
+ resolveObjs >>= \case
+ True -> return ()
+ False -> pprPanic "loadExternalPluginLib" (text "Unable to resolve objects for library: " <> text path)
+
+#endif
diff --git a/compiler/GHC/Driver/Plugins/External.hs b/compiler/GHC/Driver/Plugins/External.hs
new file mode 100644
index 0000000000..9800b1073b
--- /dev/null
+++ b/compiler/GHC/Driver/Plugins/External.hs
@@ -0,0 +1,79 @@
+-- | External plugins
+--
+-- GHC supports two kinds of "static" plugins:
+-- 1. internal: setup with GHC-API
+-- 2. external: setup as explained below and loaded from shared libraries
+--
+-- The intended use case for external static plugins is with cross compilers: at
+-- the time of writing, GHC is mono-target and a GHC cross-compiler (i.e. when
+-- host /= target) can't build nor load plugins for the host using the
+-- "non-static" plugin approach. Fixing this is tracked in #14335. If you're not
+-- using a cross-compiler, you'd better use non-static plugins which are easier
+-- to build and and safer to use (see below).
+--
+-- External static plugins can be configured via the command-line with
+-- the -fplugin-library flag. Syntax is:
+--
+-- -fplugin-library=⟨file-path⟩;⟨unit-id⟩;⟨module⟩;⟨args⟩
+--
+-- Example:
+-- -fplugin-library=path/to/plugin;package-123;Plugin.Module;["Argument","List"]
+--
+-- Building the plugin library:
+-- 1. link with the libraries used to build the compiler you target. If you
+-- target a cross-compiler (stage2), you can't directly use it to build the
+-- plugin library. Use the stage1 compiler instead.
+--
+-- 2. if you use cabal to build the library, its unit-id will be set by cabal
+-- and will contain a hash (e.g. "my-plugin-unit-1345656546ABCDEF"). To force
+-- the unit id, use GHC's `-this-unit-id` command line flag:
+-- e.g. -this-unit-id my-plugin-unit
+-- You can set this in the .cabal file of your library with the following
+-- stanza: `ghc-options: -this-unit-id my-plugin-unit`
+--
+-- 3. To make your plugin easier to distribute, you may want to link it
+-- statically with all its dependencies. You would need to use `-shared`
+-- without `-dynamic` when building your library.
+--
+-- However, all the static dependencies have to be built with `-fPIC` and it's
+-- not done by default. See
+-- https://www.hobson.space/posts/haskell-foreign-library/ for a way to modify
+-- the compiler to do it.
+--
+-- In any case, don't link your plugin library statically with the RTS (e.g.
+-- use `-fno-link-rts`) as there are some global variables in the RTS that must
+-- be shared between the plugin and the compiler.
+--
+-- With external static plugins we don't check the type of the `plugin` closure
+-- we look up. If it's not a valid `Plugin` value, it will probably crash badly.
+--
+
+module GHC.Driver.Plugins.External
+ ( ExternalPluginSpec (..)
+ , parseExternalPluginSpec
+ )
+where
+
+import GHC.Prelude
+import Text.Read
+
+-- | External plugin spec
+data ExternalPluginSpec = ExternalPluginSpec
+ { esp_lib :: !FilePath
+ , esp_unit_id :: !String
+ , esp_module :: !String
+ , esp_args :: ![String]
+ }
+
+-- | Parser external static plugin specification from command-line flag
+parseExternalPluginSpec :: String -> Maybe ExternalPluginSpec
+parseExternalPluginSpec optflag =
+ case break (== ';') optflag of
+ (libPath, _:rest) -> case break (== ';') rest of
+ (libName, _:pack) -> case break (== ';') pack of
+ (modName, _:args) -> case readMaybe args of
+ Just as -> Just (ExternalPluginSpec libPath libName modName as)
+ Nothing -> Nothing
+ _ -> Nothing
+ _ -> Nothing
+ _ -> Nothing
diff --git a/compiler/GHC/Driver/Session.hs b/compiler/GHC/Driver/Session.hs
index 0407952c33..887cfa10f0 100644
--- a/compiler/GHC/Driver/Session.hs
+++ b/compiler/GHC/Driver/Session.hs
@@ -229,6 +229,7 @@ import GHC.Builtin.Names ( mAIN_NAME )
import GHC.Driver.Phases ( Phase(..), phaseInputExt )
import GHC.Driver.Flags
import GHC.Driver.Backend
+import GHC.Driver.Plugins.External
import GHC.Settings.Config
import GHC.Utils.CliOption
import GHC.Core.Unfold
@@ -590,6 +591,9 @@ data DynFlags = DynFlags {
-- ^ the @-ffrontend-opt@ flags given on the command line, in *reverse*
-- order that they're specified on the command line.
+ externalPluginSpecs :: [ExternalPluginSpec],
+ -- ^ External plugins loaded from shared libraries
+
-- For ghc -M
depMakefile :: FilePath,
depIncludePkgDeps :: Bool,
@@ -1176,6 +1180,8 @@ defaultDynFlags mySettings =
pluginModNameOpts = [],
frontendPluginOpts = [],
+ externalPluginSpecs = [],
+
outputFile_ = Nothing,
dynOutputFile_ = Nothing,
outputHi = Nothing,
@@ -1715,6 +1721,11 @@ addPluginModuleNameOption optflag d = d { pluginModNameOpts = (mkModuleName m, o
[] -> "" -- should probably signal an error
(_:plug_opt) -> plug_opt -- ignore the ':' from break
+addExternalPlugin :: String -> DynFlags -> DynFlags
+addExternalPlugin optflag d = case parseExternalPluginSpec optflag of
+ Just r -> d { externalPluginSpecs = r : externalPluginSpecs d }
+ Nothing -> cmdLineError $ "Couldn't parse external plugin specification: " ++ optflag
+
addFrontendPluginOption :: String -> DynFlags -> DynFlags
addFrontendPluginOption s d = d { frontendPluginOpts = s : frontendPluginOpts d }
@@ -2695,6 +2706,8 @@ dynamic_flags_deps = [
, make_ord_flag defGhcFlag "fclear-plugins" (noArg clearPluginModuleNames)
, make_ord_flag defGhcFlag "ffrontend-opt" (hasArg addFrontendPluginOption)
+ , make_ord_flag defGhcFlag "fplugin-library" (hasArg addExternalPlugin)
+
------ Optimisation flags ------------------------------------------
, make_dep_flag defGhcFlag "Onot" (noArgM $ setOptLevel 0 )
"Use -O0 instead"
diff --git a/compiler/GHC/Runtime/Loader.hs b/compiler/GHC/Runtime/Loader.hs
index 393573fd24..5189e5bec4 100644
--- a/compiler/GHC/Runtime/Loader.hs
+++ b/compiler/GHC/Runtime/Loader.hs
@@ -26,6 +26,7 @@ import GHC.Driver.Session
import GHC.Driver.Ppr
import GHC.Driver.Hooks
import GHC.Driver.Plugins
+import GHC.Driver.Plugins.External
import GHC.Linker.Loader ( loadModule, loadName )
import GHC.Runtime.Interpreter ( wormhole )
@@ -75,22 +76,48 @@ import Data.List (unzip4)
-- pluginModNames or pluginModNameOpts changes.
initializePlugins :: HscEnv -> IO HscEnv
initializePlugins hsc_env
- -- plugins not changed
+ -- check that plugin specifications didn't change
+
+ -- dynamic plugins
| loaded_plugins <- loadedPlugins (hsc_plugins hsc_env)
, map lpModuleName loaded_plugins == reverse (pluginModNames dflags)
- -- arguments not changed
, all same_args loaded_plugins
- = return hsc_env -- no need to reload plugins FIXME: doesn't take static plugins into account
+
+ -- external plugins
+ , external_plugins <- externalPlugins (hsc_plugins hsc_env)
+ , check_external_plugins external_plugins (externalPluginSpecs dflags)
+
+ -- FIXME: we should check static plugins too
+
+ = return hsc_env -- no change, no need to reload plugins
+
| otherwise
= do (loaded_plugins, links, pkgs) <- loadPlugins hsc_env
- let plugins' = (hsc_plugins hsc_env) { loadedPlugins = loaded_plugins, loadedPluginDeps = (links, pkgs) }
+ external_plugins <- loadExternalPlugins (externalPluginSpecs dflags)
+ let plugins' = (hsc_plugins hsc_env) { staticPlugins = staticPlugins (hsc_plugins hsc_env)
+ , externalPlugins = external_plugins
+ , loadedPlugins = loaded_plugins
+ , loadedPluginDeps = (links, pkgs)
+ }
let hsc_env' = hsc_env { hsc_plugins = plugins' }
withPlugins (hsc_plugins hsc_env') driverPlugin hsc_env'
where
+ dflags = hsc_dflags hsc_env
+ -- dynamic plugins
plugin_args = pluginModNameOpts dflags
same_args p = paArguments (lpPlugin p) == argumentsForPlugin p plugin_args
argumentsForPlugin p = map snd . filter ((== lpModuleName p) . fst)
- dflags = hsc_dflags hsc_env
+ -- external plugins
+ check_external_plugin p spec = and
+ [ epUnit p == esp_unit_id spec
+ , epModule p == esp_module spec
+ , paArguments (epPlugin p) == esp_args spec
+ ]
+ check_external_plugins eps specs = case (eps,specs) of
+ ([] , []) -> True
+ (_ , []) -> False -- some external plugin removed
+ ([] , _ ) -> False -- some external plugin added
+ (p:ps,s:ss) -> check_external_plugin p s && check_external_plugins ps ss
loadPlugins :: HscEnv -> IO ([LoadedPlugin], [Linkable], PkgsLoaded)
loadPlugins hsc_env
diff --git a/compiler/ghc.cabal.in b/compiler/ghc.cabal.in
index b5d1b8d571..48a5fa11ed 100644
--- a/compiler/ghc.cabal.in
+++ b/compiler/ghc.cabal.in
@@ -441,6 +441,7 @@ Library
GHC.Driver.Pipeline.Phases
GHC.Driver.Pipeline.Monad
GHC.Driver.Plugins
+ GHC.Driver.Plugins.External
GHC.Driver.Ppr
GHC.Driver.Session
GHC.Hs
diff --git a/docs/users_guide/extending_ghc.rst b/docs/users_guide/extending_ghc.rst
index acc8791fb0..3f8a4a5000 100644
--- a/docs/users_guide/extending_ghc.rst
+++ b/docs/users_guide/extending_ghc.rst
@@ -268,7 +268,6 @@ option. The list of enabled plugins can be reset with the
the command line is not possible. Instead ``:set -fclear-plugins`` can be
used.
-
As an example, in order to load the plugin exported by ``Foo.Plugin`` in
the package ``foo-ghc-plugin``, and give it the parameter "baz", we
would invoke GHC like this:
@@ -286,6 +285,19 @@ would invoke GHC like this:
Linking Test ...
$
+
+Plugins can be also be loaded from libraries directly. It allows plugins to be
+loaded in cross-compilers (as a workaround for #14335).
+
+.. ghc-flag:: -fplugin-library=⟨file-path⟩;⟨unit-id⟩;⟨module⟩;⟨args⟩
+ :shortdesc: Load a pre-compiled static plugin from an external library
+ :type: dynamic
+ :category: plugins
+
+ Arguments are specified in a list form, so a plugin specified to
+ :ghc-flag:`-fplugin-library=⟨file-path⟩;⟨unit-id⟩;⟨module⟩;⟨args⟩` will look
+ like ``'path/to/plugin;package-123;Plugin.Module;["Argument","List"]'``.
+
Alternatively, core plugins can be specified with Template Haskell.
::
diff --git a/testsuite/tests/count-deps/CountDepsAst.stdout b/testsuite/tests/count-deps/CountDepsAst.stdout
index 0705d70d75..549958f44b 100644
--- a/testsuite/tests/count-deps/CountDepsAst.stdout
+++ b/testsuite/tests/count-deps/CountDepsAst.stdout
@@ -116,6 +116,7 @@ GHC.Driver.Phases
GHC.Driver.Pipeline.Monad
GHC.Driver.Pipeline.Phases
GHC.Driver.Plugins
+GHC.Driver.Plugins.External
GHC.Driver.Ppr
GHC.Driver.Session
GHC.Hs
diff --git a/testsuite/tests/count-deps/CountDepsParser.stdout b/testsuite/tests/count-deps/CountDepsParser.stdout
index 3ab7ace8df..62e71fbea3 100644
--- a/testsuite/tests/count-deps/CountDepsParser.stdout
+++ b/testsuite/tests/count-deps/CountDepsParser.stdout
@@ -117,6 +117,7 @@ GHC.Driver.Phases
GHC.Driver.Pipeline.Monad
GHC.Driver.Pipeline.Phases
GHC.Driver.Plugins
+GHC.Driver.Plugins.External
GHC.Driver.Ppr
GHC.Driver.Session
GHC.Hs
diff --git a/testsuite/tests/plugins/Makefile b/testsuite/tests/plugins/Makefile
index 482d543c6a..986acba472 100644
--- a/testsuite/tests/plugins/Makefile
+++ b/testsuite/tests/plugins/Makefile
@@ -205,3 +205,18 @@ test-echo-in-turn-many-args:
.PHONY: test-echo-in-line-many-args
test-echo-in-line-many-args:
"$(TEST_HC)" $(TEST_HC_OPTS) $(ghcPluginWayFlags) -v0 test-echo-in-line-many-args.hs -package-db echo-plugin/pkg.test-echo-in-line-many-args/local.package.conf
+
+
+ifeq "$(WINDOWS)" "YES"
+DLL = $1.dll
+else ifeq "$(DARWIN)" "YES"
+DLL = lib$1.dylib
+else
+DLL = lib$1.so
+endif
+
+.PHONY: plugins-external
+plugins-external:
+ cp shared-plugin/pkg.plugins01/dist/build/$(call DLL,HSsimple-plugin*) $(call DLL,HSsimple-plugin)
+ "$(TEST_HC)" $(TEST_HC_OPTS) $(ghcPluginWayFlags) --make -v0 -fplugin-library "$(PWD)/$(call DLL,HSsimple-plugin);simple-plugin-1234;Simple.Plugin;[\"Plugin\",\"loaded\",\"from\",\"a shared lib\"]" plugins-external.hs
+ ./plugins-external
diff --git a/testsuite/tests/plugins/all.T b/testsuite/tests/plugins/all.T
index dca05d885c..c503397e78 100644
--- a/testsuite/tests/plugins/all.T
+++ b/testsuite/tests/plugins/all.T
@@ -311,3 +311,8 @@ test('test-echo-in-line-many-args',
[extra_files(['echo-plugin/']),
pre_cmd('$MAKE -s --no-print-directory -C echo-plugin package.test-echo-in-line-many-args TOP={top}')],
makefile_test, [])
+
+test('plugins-external',
+ [extra_files(['shared-plugin/']),
+ pre_cmd('$MAKE -s --no-print-directory -C shared-plugin package.plugins01 TOP={top}')],
+ makefile_test, [])
diff --git a/testsuite/tests/plugins/plugins-external.hs b/testsuite/tests/plugins/plugins-external.hs
new file mode 100644
index 0000000000..9626667e59
--- /dev/null
+++ b/testsuite/tests/plugins/plugins-external.hs
@@ -0,0 +1,4 @@
+-- Intended to test that we can load plugins as external shared libraries
+module Main where
+
+main = putStrLn "Hello World"
diff --git a/testsuite/tests/plugins/plugins-external.stderr b/testsuite/tests/plugins/plugins-external.stderr
new file mode 100644
index 0000000000..ed60f9afab
--- /dev/null
+++ b/testsuite/tests/plugins/plugins-external.stderr
@@ -0,0 +1,2 @@
+Simple Plugin Passes Queried
+Got options: Plugin loaded from a shared lib
diff --git a/testsuite/tests/plugins/plugins-external.stdout b/testsuite/tests/plugins/plugins-external.stdout
new file mode 100644
index 0000000000..557db03de9
--- /dev/null
+++ b/testsuite/tests/plugins/plugins-external.stdout
@@ -0,0 +1 @@
+Hello World
diff --git a/testsuite/tests/plugins/shared-plugin/LICENSE b/testsuite/tests/plugins/shared-plugin/LICENSE
new file mode 100644
index 0000000000..6297f71b3f
--- /dev/null
+++ b/testsuite/tests/plugins/shared-plugin/LICENSE
@@ -0,0 +1,10 @@
+Copyright (c) 2008, Max Bolingbroke
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
+ * Neither the name of Max Bolingbroke nor the names of other contributors may be used to endorse or promote products derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/testsuite/tests/plugins/shared-plugin/Makefile b/testsuite/tests/plugins/shared-plugin/Makefile
new file mode 100644
index 0000000000..ae5c24e87f
--- /dev/null
+++ b/testsuite/tests/plugins/shared-plugin/Makefile
@@ -0,0 +1,20 @@
+TOP=../../..
+include $(TOP)/mk/boilerplate.mk
+include $(TOP)/mk/test.mk
+
+clean.%:
+ rm -rf pkg.$*
+
+HERE := $(abspath .)
+$(eval $(call canonicalise,HERE))
+
+package.%:
+ $(MAKE) -s --no-print-directory clean.$*
+ mkdir pkg.$*
+ "$(TEST_HC)" -outputdir pkg.$* --make -v0 -o pkg.$*/setup Setup.hs
+
+ "$(GHC_PKG)" init pkg.$*/local.package.conf
+
+ pkg.$*/setup configure --distdir pkg.$*/dist -v0 $(CABAL_PLUGIN_BUILD) --prefix="$(HERE)/pkg.$*/install" --with-compiler="$(TEST_HC)" --with-hc-pkg="$(GHC_PKG)" --package-db=pkg.$*/local.package.conf $(if $(findstring YES,$(HAVE_PROFILING)), --enable-library-profiling)
+ pkg.$*/setup build --distdir pkg.$*/dist -v0
+ pkg.$*/setup install --distdir pkg.$*/dist -v0
diff --git a/testsuite/tests/plugins/shared-plugin/Setup.hs b/testsuite/tests/plugins/shared-plugin/Setup.hs
new file mode 100644
index 0000000000..e8ef27dbba
--- /dev/null
+++ b/testsuite/tests/plugins/shared-plugin/Setup.hs
@@ -0,0 +1,3 @@
+import Distribution.Simple
+
+main = defaultMain
diff --git a/testsuite/tests/plugins/shared-plugin/Simple/Plugin.hs b/testsuite/tests/plugins/shared-plugin/Simple/Plugin.hs
new file mode 100644
index 0000000000..09ff028e33
--- /dev/null
+++ b/testsuite/tests/plugins/shared-plugin/Simple/Plugin.hs
@@ -0,0 +1,26 @@
+{-# LANGUAGE TemplateHaskell #-}
+
+module Simple.Plugin(plugin) where
+
+import GHC.Types.Unique.FM
+import GHC.Plugins
+import qualified GHC.Utils.Error
+
+import Control.Monad
+import Data.Monoid hiding (Alt)
+import Data.Dynamic
+import qualified Language.Haskell.TH as TH
+
+plugin :: Plugin
+plugin = defaultPlugin {
+ installCoreToDos = install,
+ pluginRecompile = purePlugin
+ }
+
+install :: [CommandLineOption] -> [CoreToDo] -> CoreM [CoreToDo]
+install options todos = do
+ putMsgS $ "Simple Plugin Passes Queried"
+ putMsgS $ "Got options: " ++ unwords options
+
+ -- Create some actual passes to continue the test.
+ return todos
diff --git a/testsuite/tests/plugins/shared-plugin/shared-plugin.cabal b/testsuite/tests/plugins/shared-plugin/shared-plugin.cabal
new file mode 100644
index 0000000000..85d8532973
--- /dev/null
+++ b/testsuite/tests/plugins/shared-plugin/shared-plugin.cabal
@@ -0,0 +1,21 @@
+Name: simple-plugin
+Version: 0.1
+Synopsis: A demonstration of the GHC plugin system.
+Cabal-Version: >= 1.2
+Build-Type: Simple
+License: BSD3
+License-File: LICENSE
+Author: Max Bolingbroke
+Homepage: http://blog.omega-prime.co.uk
+
+Library
+ Extensions: CPP
+ Build-Depends:
+ base,
+ template-haskell,
+ ghc >= 6.11
+ Exposed-Modules:
+ Simple.Plugin
+
+ -- explicitly set the unit-id to allow loading from a shared library
+ ghc-options: -this-unit-id simple-plugin-1234