├── README.md ├── add-plugin.nix ├── default.nix ├── example.nix ├── overlay.nix ├── plugins.nix └── with-plugin.nix /README.md: -------------------------------------------------------------------------------- 1 | This repository provides an overlay which adds support for running Haskell 2 | plugins via nix. 3 | 4 | There are three new elements to the nixpkgs API. 5 | 6 | 1. A new function `haskell.lib.addPlugin` which adds a plugin to a package. 7 | 2. A new attribute `haskell.plugins` which is parameterised by 8 | a Haskell package set and contains a set of plugins. 9 | 3. A new `with*` function, `haskellPackages.withPlugin` which takes a function 10 | expecting two arguments, the first being a set of plugins for that package set 11 | and the second being a list of packages for that package set. The 12 | result of the function should be a Haskell package. 13 | 14 | ## Example 15 | 16 | See `example.nix`: 17 | 18 | ``` 19 | let 20 | plugin-overlay-git = builtins.fetchGit 21 | { url = https://github.com/mpickering/haskell-nix-plugin.git;} ; 22 | plugin-overlay = import "${plugin-overlay-git}/overlay.nix"; 23 | nixpkgs = import { overlays = [plugin-overlay]; }; 24 | 25 | hl = nixpkgs.haskell.lib; 26 | hp = nixpkgs.haskellPackages; 27 | in 28 | (hp.withPlugin(plugs: ps: hl.addPlugin plugs.dump-core ps.either)).DumpCore 29 | ``` 30 | 31 | ## The plugin set 32 | 33 | The `haskell.plugins` attribute is a set of plugins parameterised by a normal 34 | Haskell package set. It is designed in this manner so the same plugin definitions 35 | can be used with different compilers. 36 | 37 | ```nix 38 | hp: 39 | { 40 | dump-core = { ... }; 41 | graphmod-plugin = { ... }; 42 | } 43 | ``` 44 | 45 | Each attribute is a different plugin which we might want to use with our program. 46 | 47 | 48 | ## A plugin 49 | 50 | A plugin is a Haskell package which provides the plugin with four additional 51 | attributes which describe how to run it. For example, here is the definition 52 | for the `dump-core` plugin. 53 | 54 | ```nix 55 | dump-core = { pluginPackage = hp.dump-core ; 56 | pluginName = "DumpCore"; 57 | pluginOpts = ({outpath, pkg}: [outpath]); 58 | pluginDepends = []; 59 | initPhase = _: ""; 60 | finalPhase = _: ""; 61 | } ; 62 | ``` 63 | 64 | `pluginPackage` 65 | : The Haskell package which provides the plugin. 66 | 67 | `pluginName` 68 | : The module name where the plugin is defined. 69 | 70 | `pluginOpts` 71 | : Additional options to pass to the plugin. The path where it places its output 72 | is passed as an argument. 73 | 74 | `pluginDepends` 75 | : Any additional system dependencies the plugin needs for the finalPhase. 76 | 77 | `initPhase` 78 | : An action to run in the `preBuild` phase, after the plugin has run. The standard 79 | arguments are passed. 80 | 81 | `finalPhase` 82 | : An action to run in the `postBuild` phase, after the plugin has run. The standard 83 | arguments are passed. 84 | 85 | The standard arguments are the output path and the current package being 86 | overriden with attributes `outpath` and `pkg`. 87 | 88 | In most cases, `pluginDepends` and `finalPhase` can be omitted (they then take 89 | these default values) but they are useful for when a plugin emits information 90 | as it compiles each module which is then summarised at the end. 91 | 92 | An example of this architecture is the [`graphmod-plugin`](https://github.com/mpickering/graphmod-plugin). As each module is 93 | compiled, the import information is serialised. Then, at the end we read all 94 | the serialised files and create a dot graph of the module import structure. 95 | Here is how we specify the final phase of the plugin: 96 | 97 | ```nix 98 | graphmod = { pluginPackage = hp.graphmod-plugin; 99 | pluginName = "GraphMod"; 100 | pluginOpts = ({outpath,...}: ["${outpath}/output"]); 101 | pluginDepends = [ nixpkgs.graphviz ]; 102 | finalPhase = {outpath,...}: '' 103 | graphmod-plugin --indir ${outpath}/output > ${outpath}/out.dot 104 | cat ${outpath}/out.dot | tred | dot -Tpdf > ${outpath}/modules.pdf 105 | ''; } ; 106 | ``` 107 | 108 | The first three fields are standard, however we now populate the final two 109 | arguments as well. We firstly add a dependency on `graphviz` which we will 110 | use to render the module graph and then specify the invocations needed 111 | to firstly summarise and then render the information. 112 | 113 | In this architecture, the plugin package provides a library interface which 114 | exposes the plugin and an executable which is invoked to collect the information 115 | output by the plugin. This is what the call to `graphmod-plugin` achieves. 116 | 117 | ## `withPlugin` 118 | 119 | We also provide the `withPlugin` attribute which supplies both the 120 | plugins and packages already applied to a specific package set. The reason 121 | for this is that **a plugin and a package must be both compiled by the same 122 | compiler**. Thus, unrestricted usage of `addPlugin` can lead to confusing errors 123 | if the plugin and package are compiled with different compilers. 124 | The `withPlugin` attribute ensures that the versions align 125 | correctly. 126 | 127 | ``` 128 | core-either = 129 | haskellPackages.withPlugin 130 | (plugins: packages: addPlugin plugins.dump-core packages.either) 131 | ``` 132 | 133 | ## How can I use it? 134 | 135 | This infrastructure is provided as an overlay. Install the overlay as you would 136 | normally, one suggested method can be see in the [`example.nix`](https://github.com/mpickering/haskell-nix-plugin/blob/master/example.nix) file. 137 | 138 | ``` 139 | let 140 | plugin-overlay-git = builtins.fetchGit 141 | { url = https://github.com/mpickering/haskell-nix-plugin.git;} ; 142 | plugin-overlay = import "${plugin-overlay-git}/overlay.nix"; 143 | nixpkgs = import { overlays = [plugin-overlay]; }; 144 | in ... 145 | ``` 146 | 147 | ### Using NUR 148 | 149 | The overlay is also distributed using [NUR](https://github.com/nix-community/NUR). Here is an example of how to use it: 150 | 151 | ``` 152 | overlay = (import {}).nur.repos.mpickering.overlays.haskell-plugins; 153 | nixpkgs = import { overlays = [ overlay ]; }; 154 | ``` 155 | 156 | -------------------------------------------------------------------------------- /add-plugin.nix: -------------------------------------------------------------------------------- 1 | {haskell, lib 2 | }: 3 | { pluginPackage #The package the plugin is from 4 | , pluginName #The module name of the plugin 5 | # Options to pass to the plugin, the first argument is the directoy the plugin 6 | # should place any files it creates. 7 | , pluginOpts ? (out-path: []) 8 | # Additional system dependencies to run the finalPhase 9 | , pluginDepends ? [] 10 | # The script to run before the plugin has compiled 11 | , initPhase ? (out-path: []) 12 | # The script to run after the plugin has compiled 13 | , finalPhase ? (out-path: []) 14 | }: 15 | pkg: 16 | let 17 | args = { outpath = pluginOutputDir; inherit pkg; }; 18 | hlib = haskell.lib; 19 | 20 | normPluginName = "plugin" + builtins.replaceStrings ["."] [""] pluginName; 21 | 22 | # normPluginName = "plugin"; 23 | 24 | # The bash string which will expand to the output directory when 25 | # the builder runs. 26 | pluginOutputDir = "$" + normPluginName; 27 | 28 | # Create a new output for the plugin to put files into. 29 | # For example, if the plugin is called `DumpCore` and we run 30 | # it on the `either` package then we can access its output at 31 | # the attribute `either.DumpCore`. 32 | addOutput = drv: drv.overrideAttrs(oldAttrs: 33 | { outputs = (oldAttrs.outputs ++ [ normPluginName ]); 34 | }); 35 | 36 | phases = drv: hlib.overrideCabal drv (drv: { 37 | # Make the output even if the plugin doesn't output 38 | # anything. 39 | postUnpack = '' 40 | echo ${pluginOutputDir} 41 | echo ${normPluginName} 42 | mkdir -p ${pluginOutputDir} 43 | echo Plugin output directory: ${pluginOutputDir}''; 44 | # Give the plugin some chance to collate the results. 45 | preBuild = initPhase args; 46 | postBuild = finalPhase args; 47 | }); 48 | 49 | # Build the plugin options. 50 | string-opt = arg: " -fplugin-opt=${pluginName}:${arg}"; 51 | string-opts = lib.concatMapStrings string-opt (pluginOpts args); 52 | 53 | additionalDepends = [pluginPackage] ++ pluginDepends; 54 | in 55 | with hlib; 56 | phases ( 57 | addOutput ( 58 | (addBuildDepends 59 | (appendBuildFlag pkg "--ghc-options=\"-fno-safe-haskell -fno-safe-infer -fplugin=${pluginName} -plugin-package=${pluginPackage.pname} ${string-opts}\"") additionalDepends))) 60 | 61 | -------------------------------------------------------------------------------- /default.nix: -------------------------------------------------------------------------------- 1 | let 2 | # Using NUR 3 | overlay = (import {}).nur.repos.mpickering.overlays.haskell-plugins; 4 | nixpkgs = import { overlays = [ overlay ]; }; 5 | 6 | 7 | hp = nixpkgs.haskellPackages; 8 | plugins = nixpkgs.haskell.plugins hp; 9 | dump-core-plugin = plugins.dump-core; 10 | in 11 | (nixpkgs.haskell.lib.addPlugin dump-core-plugin hp.either).DumpCore 12 | 13 | -------------------------------------------------------------------------------- /example.nix: -------------------------------------------------------------------------------- 1 | let 2 | plugin-overlay-git = builtins.fetchGit 3 | { url = https://github.com/mpickering/haskell-nix-plugin.git;} ; 4 | plugin-overlay = import "${plugin-overlay-git}/overlay.nix"; 5 | nixpkgs = import { overlays = [plugin-overlay]; }; 6 | 7 | hl = nixpkgs.haskell.lib; 8 | hp = nixpkgs.haskellPackages; 9 | in 10 | (hp.withPlugin(plugs: ps: hl.addPlugin plugs.dump-core ps.either)).DumpCore 11 | 12 | -------------------------------------------------------------------------------- /overlay.nix: -------------------------------------------------------------------------------- 1 | # An overlay which adds the new attributes to the right places 2 | self: super: { 3 | haskell = 4 | super.haskell // ({ 5 | plugins = import ./plugins.nix; 6 | lib = super.haskell.lib // 7 | { addPlugin = super.callPackage ./add-plugin.nix {}; }; 8 | overrides = 9 | sel: sup: 10 | super.haskell.overrides // 11 | { withPlugin = import ./with-plugin.nix {hp = sel; haskell = self.haskell; }; }; }); 12 | } 13 | -------------------------------------------------------------------------------- /plugins.nix: -------------------------------------------------------------------------------- 1 | # Plugin definitions 2 | hp # The Haskell package set we want to define the plugin set for 3 | : 4 | { # The dump-core plugin outputs HTML of the core of a module. 5 | dump-core = { pluginPackage = hp.dump-core ; 6 | pluginName = "DumpCore"; 7 | pluginOpts = (args: [args.outpath]); } ; 8 | } 9 | -------------------------------------------------------------------------------- /with-plugin.nix: -------------------------------------------------------------------------------- 1 | {hp, haskell 2 | }: 3 | func: 4 | func (haskell.plugins hp) hp 5 | --------------------------------------------------------------------------------