This repository provides an overlay which adds support for running Haskell plugins via nix.
There are three new elements to the nixpkgs API.
- A new function
haskell.lib.addPluginwhich adds a plugin to a package. - A new attribute
haskell.pluginswhich is parameterised by a Haskell package set and contains a set of plugins. - A new
with*function,haskellPackages.withPluginwhich takes a function expecting two arguments, the first being a set of plugins for that package set and the second being a list of packages for that package set. The result of the function should be a Haskell package.
See example.nix:
let
plugin-overlay-git = builtins.fetchGit
{ url = https://github.com/mpickering/haskell-nix-plugin.git;} ;
plugin-overlay = import "${plugin-overlay-git}/overlay.nix";
nixpkgs = import <nixpkgs> { overlays = [plugin-overlay]; };
hl = nixpkgs.haskell.lib;
hp = nixpkgs.haskellPackages;
in
(hp.withPlugin(plugs: ps: hl.addPlugin plugs.dump-core ps.either)).DumpCore
The haskell.plugins attribute is a set of plugins parameterised by a normal
Haskell package set. It is designed in this manner so the same plugin definitions
can be used with different compilers.
hp:
{
dump-core = { ... };
graphmod-plugin = { ... };
}Each attribute is a different plugin which we might want to use with our program.
A plugin is a Haskell package which provides the plugin with four additional
attributes which describe how to run it. For example, here is the definition
for the dump-core plugin.
dump-core = { pluginPackage = hp.dump-core ;
pluginName = "DumpCore";
pluginOpts = ({outpath, pkg}: [outpath]);
pluginDepends = [];
initPhase = _: "";
finalPhase = _: "";
} ;pluginPackage
: The Haskell package which provides the plugin.
pluginName
: The module name where the plugin is defined.
pluginOpts
: Additional options to pass to the plugin. The path where it places its output
is passed as an argument.
pluginDepends
: Any additional system dependencies the plugin needs for the finalPhase.
initPhase
: An action to run in the preBuild phase, after the plugin has run. The standard
arguments are passed.
finalPhase
: An action to run in the postBuild phase, after the plugin has run. The standard
arguments are passed.
The standard arguments are the output path and the current package being
overriden with attributes outpath and pkg.
In most cases, pluginDepends and finalPhase can be omitted (they then take
these default values) but they are useful for when a plugin emits information
as it compiles each module which is then summarised at the end.
An example of this architecture is the graphmod-plugin. As each module is
compiled, the import information is serialised. Then, at the end we read all
the serialised files and create a dot graph of the module import structure.
Here is how we specify the final phase of the plugin:
graphmod = { pluginPackage = hp.graphmod-plugin;
pluginName = "GraphMod";
pluginOpts = ({outpath,...}: ["${outpath}/output"]);
pluginDepends = [ nixpkgs.graphviz ];
finalPhase = {outpath,...}: ''
graphmod-plugin --indir ${outpath}/output > ${outpath}/out.dot
cat ${outpath}/out.dot | tred | dot -Tpdf > ${outpath}/modules.pdf
''; } ;The first three fields are standard, however we now populate the final two
arguments as well. We firstly add a dependency on graphviz which we will
use to render the module graph and then specify the invocations needed
to firstly summarise and then render the information.
In this architecture, the plugin package provides a library interface which
exposes the plugin and an executable which is invoked to collect the information
output by the plugin. This is what the call to graphmod-plugin achieves.
We also provide the withPlugin attribute which supplies both the
plugins and packages already applied to a specific package set. The reason
for this is that a plugin and a package must be both compiled by the same
compiler. Thus, unrestricted usage of addPlugin can lead to confusing errors
if the plugin and package are compiled with different compilers.
The withPlugin attribute ensures that the versions align
correctly.
core-either =
haskellPackages.withPlugin
(plugins: packages: addPlugin plugins.dump-core packages.either)
This infrastructure is provided as an overlay. Install the overlay as you would
normally, one suggested method can be see in the example.nix file.
let
plugin-overlay-git = builtins.fetchGit
{ url = https://github.com/mpickering/haskell-nix-plugin.git;} ;
plugin-overlay = import "${plugin-overlay-git}/overlay.nix";
nixpkgs = import <nixpkgs> { overlays = [plugin-overlay]; };
in ...
The overlay is also distributed using NUR. Here is an example of how to use it:
overlay = (import <nixpkgs> {}).nur.repos.mpickering.overlays.haskell-plugins;
nixpkgs = import <nixpkgs> { overlays = [ overlay ]; };