The compilation manager is the system by which Haskell source code is compiled to object code suitable for loading.
import System.Plugins make :: FilePath -> [Arg] -> IO MakeStatus makeAll :: FilePath -> [Arg] -> IO MakeStatus recompileAll :: Module -> [Arg] -> IO MakeStatus data MakeStatus = MakeSuccess MakeCode FilePath | MakeFailure Errors data MakeCode = ReComp | NotReq
Compile a Haskell source file to an object file, with any arguments specified in the argument list passed through to GHC. Returns the build status.
make generates a GHC .hi file containing a list of package and objects that the source depends on. Subsequent calls to load will use this interface file to load module and library dependencies prior to loading the object itself. makeAll also recursively compiles any dependencies it can find using GHC's -make flag.
recompileAll is like makeAll, but rather than relying on ghc -make, we explicitly check a module's dependencies.
merge :: FilePath -> FilePath -> IO MergeStatus mergeTo :: FilePath -> FilePath -> FilePath -> IO MergeStatus mergeToDir :: FilePath -> FilePath -> FilePath -> IO MergeStatus data MergeStatus = MergeSuccess MergeCode Args FilePath | MergeFailure Errors type MergeCode = MakeCode
The merging operation is extremely useful for providing extra default syntax. An EDSL user then need not worry about declaring module names, or having required imports. In this way, the stub file can also be used to provide syntax declarations that would be inconvenient to require of the plugin author. merge will include any import and export declarations written in the stub, as well as any module name, so that plugin author's need not worry about this compulsory syntax. Additionally, if a plugin requires some non-standard library, which must be provided as a -package flag to GHC, they may specify this using the non-standard GLOBALOPTIONS pragma. Options specified in the source this way will be added to the command line. This is useful for users who wish to use GHC flags that cannot be specified using the conventional OPTIONS pragma. The merging operation uses the HSX parser library to parse Haskell source files.
mergeTo behaves like merge, but we can specify the file in which to place output. mergeToDir lets you specify a directory in which to place merged files.
makeWith :: FilePath -> FilePath -> [Arg] -> IO MakeStatus
This is a variety of make that first calls merge to combine the plugin source with a syntax stub. The result is then compiled. This is the preferred interface to EDSL authors who wish to add extra syntax to a user's source. It is important to note that the module and types from the second file argument are used to override any of those that appear in the first argument. For example, consider the following source files:
module A where a :: Integer a = 1
module B where a :: Int
Calling makeWith "A" "B" [] will merge the module name and types from module B into module A, generating a third file:
{-# LINE 1 "A.hs" #-} module MxYz123 where {-# LINE 3 "B.hs" #-} a :: Int {-# LINE 4 "A.hs" #-} a = 1
Leading to the desired result that we can ignore user-supplied module names and types. Knowing the module name, in particular, is important for dynamic loading, which requires the module name be known when searching for symbols.
hasChanged :: Module -> IO Bool
hasChanged returns True if the module or any of its dependencies have older object files than source files. Defaults to True if some files couldn't be located.
The normal dynamic loader, using load on object files only, places full trust in the author of the plugin to provide a type-safe object file, containing valid code. This can be mitigated somewhat via the use of make to ensure that the plugin is at least Haskell code that is well-typed internally (if we trust GHC to compile it correctly).
If we trust the user to provide an interface of Dynamic type, we can check the plugin type at runtime, but the plugin's value must be Typeable, which restricts it to be a monomorphic type (or to using rank-N tricks).
The greatest safety can be obtained by using pdynload, at the cost of increased load times. pdynload essentially performs full type inference on the plugin interface at runtime. The type safety of the plugin, using pdynload, is then as safe as if the plugin was statically compiled into the application. It does not provide any further safety than exists in static compilation. For example, it does not preclude the use of (evil) unsafeCoerce# to defeat type-checking, either statically or at runtime. An extensive discussion of type safe plugin loading is available in the hs-plugins paper here.