1{
2 lib,
3 stdenv,
4 haskellPackages,
5 haskell,
6
7 # Which GHC versions this hls can support.
8 # These are looked up in nixpkgs as `pkgs.haskell.packages."ghc${version}`.
9 # Run
10 # $ nix-instantiate --eval -E 'with import <nixpkgs> {}; builtins.attrNames pkgs.haskell.packages'
11 # to list for your nixpkgs version.
12 supportedGhcVersions ? [
13 (lib.strings.replaceStrings [ "." ] [ "" ] (lib.versions.majorMinor haskellPackages.ghc.version))
14 ],
15
16 # Whether to build hls with the dynamic run-time system.
17 # See https://haskell-language-server.readthedocs.io/en/latest/troubleshooting.html#static-binaries for more information.
18 dynamic ? true,
19
20 # Which formatters are supported. Pass `[]` to remove all formatters.
21 #
22 # Maintainers: if a new formatter is added, add it here and down in knownFormatters
23 supportedFormatters ? [
24 "ormolu"
25 "fourmolu"
26 "floskell"
27 "stylish-haskell"
28 ],
29}:
30
31# make sure the user only sets GHC versions that actually exist
32assert supportedGhcVersions != [ ];
33assert lib.asserts.assertEachOneOf "supportedGhcVersions" supportedGhcVersions (
34 lib.pipe haskell.packages [
35 lib.attrNames
36 (lib.filter (lib.hasPrefix "ghc"))
37 (map (lib.removePrefix "ghc"))
38 ]
39);
40
41let
42 # A mapping from formatter name to
43 # - cabal flag to disable
44 # - formatter-specific packages that can be stripped from the build of hls if it is disabled
45 knownFormatters = {
46 ormolu = {
47 cabalFlag = "ormolu";
48 packages = [
49 "hls-ormolu-plugin"
50 ];
51 };
52 fourmolu = {
53 cabalFlag = "fourmolu";
54 packages = [
55 "hls-fourmolu-plugin"
56 ];
57 };
58 floskell = {
59 cabalFlag = "floskell";
60 packages = [
61 "hls-floskell-plugin"
62 ];
63 };
64 stylish-haskell = {
65 cabalFlag = "stylishhaskell";
66 packages = [
67 "hls-stylish-haskell-plugin"
68 ];
69 };
70 };
71
72in
73
74# make sure any formatter that is set is actually supported by us
75assert lib.asserts.assertEachOneOf "supportedFormatters" supportedFormatters (
76 lib.attrNames knownFormatters
77);
78
79#
80# The recommended way to override this package is
81#
82# pkgs.haskell-language-server.override { supportedGhcVersions = [ "90" "92"]; }
83#
84# for example. Read more about this in the haskell-language-server section of the nixpkgs manual.
85#
86let
87 inherit (haskell.lib.compose)
88 justStaticExecutables
89 overrideCabal
90 enableCabalFlag
91 disableCabalFlag
92 ;
93
94 getPackages = version: haskell.packages."ghc${version}";
95
96 # Given the list of `supportedFormatters`, remove every formatter that we know of (knownFormatters)
97 # by disabling the cabal flag and also removing the formatter libraries.
98 removeUnnecessaryFormatters =
99 let
100 # only formatters that were not requested
101 unwanted = lib.pipe knownFormatters [
102 (lib.filterAttrs (fmt: _: !(lib.elem fmt supportedFormatters)))
103 lib.attrsToList
104 ];
105 # all flags to disable
106 flags = map (fmt: fmt.value.cabalFlag) unwanted;
107 # all dependencies to remove from hls
108 deps = lib.concatMap (fmt: fmt.value.packages) unwanted;
109
110 # remove nulls from a list
111 stripNulls = lib.filter (x: x != null);
112
113 # remove all unwanted dependencies of formatters we don’t want
114 stripDeps = overrideCabal (drv: {
115 libraryHaskellDepends = lib.pipe (drv.libraryHaskellDepends or [ ]) [
116 # the existing list may contain nulls, so let’s strip them first
117 stripNulls
118 (lib.filter (dep: !(lib.elem dep.pname deps)))
119 ];
120 });
121
122 in
123 drv: lib.pipe drv ([ stripDeps ] ++ map disableCabalFlag flags);
124
125 tunedHls =
126 hsPkgs:
127 lib.pipe hsPkgs.haskell-language-server (
128 [
129 (haskell.lib.compose.overrideCabal (old: {
130 enableSharedExecutables = dynamic;
131 ${if !dynamic then "postInstall" else null} = ''
132 ${old.postInstall or ""}
133
134 remove-references-to -t ${hsPkgs.ghc} $out/bin/haskell-language-server
135 '';
136 }))
137 ((if dynamic then enableCabalFlag else disableCabalFlag) "dynamic")
138 removeUnnecessaryFormatters
139 ]
140 ++ lib.optionals (!dynamic) [
141 justStaticExecutables
142 ]
143 );
144
145 targets =
146 version:
147 let
148 packages = getPackages version;
149 in
150 [ "haskell-language-server-${packages.ghc.version}" ];
151
152 makeSymlinks =
153 version:
154 lib.concatMapStringsSep "\n" (
155 x: "ln -s ${tunedHls (getPackages version)}/bin/haskell-language-server $out/bin/${x}"
156 ) (targets version);
157
158in
159stdenv.mkDerivation {
160 pname = "haskell-language-server";
161 version = haskellPackages.haskell-language-server.version;
162
163 buildCommand = ''
164 mkdir -p $out/bin
165 ln -s ${tunedHls (getPackages (builtins.head supportedGhcVersions))}/bin/haskell-language-server-wrapper $out/bin/haskell-language-server-wrapper
166 ${lib.concatMapStringsSep "\n" makeSymlinks supportedGhcVersions}
167 '';
168
169 meta = haskellPackages.haskell-language-server.meta // {
170 maintainers = [ lib.maintainers.maralorn ];
171 longDescription = ''
172 This package provides the executables ${
173 lib.concatMapStringsSep ", " (x: lib.concatStringsSep ", " (targets x)) supportedGhcVersions
174 } and haskell-language-server-wrapper.
175 You can choose for which ghc versions to install hls with pkgs.haskell-language-server.override { supportedGhcVersions = [ "90" "92" ]; }.
176 '';
177 };
178}