about summary refs log tree commit diff
path: root/nixpkgs/pkgs/development/tools/haskell/haskell-language-server/withWrapper.nix
blob: 59a1303764bacd219af398cb9e72c4776633ef89 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
{ lib
, stdenv
, haskellPackages
, haskell

# Which GHC versions this hls can support.
# These are looked up in nixpkgs as `pkgs.haskell.packages."ghc${version}`.
# Run
#  $ nix-instantiate --eval -E 'with import <nixpkgs> {}; builtins.attrNames pkgs.haskell.packages'
# to list for your nixpkgs version.
, supportedGhcVersions ? [ "94" ]

# Whether to build hls with the dynamic run-time system.
# See https://haskell-language-server.readthedocs.io/en/latest/troubleshooting.html#static-binaries for more information.
, dynamic ? true

# Which formatters are supported. Pass `[]` to remove all formatters.
#
# Maintainers: if a new formatter is added, add it here and down in knownFormatters
, supportedFormatters ? [ "ormolu" "fourmolu" "floskell" "stylish-haskell" ]
}:

# make sure the user only sets GHC versions that actually exist
assert supportedGhcVersions != [];
assert
  lib.asserts.assertEachOneOf
    "supportedGhcVersions"
    supportedGhcVersions
    (lib.pipe haskell.packages [
      lib.attrNames
      (lib.filter (lib.hasPrefix "ghc"))
      (map (lib.removePrefix "ghc"))
    ]);

let
  # A mapping from formatter name to
  # - cabal flag to disable
  # - formatter-specific packages that can be stripped from the build of hls if it is disabled
  knownFormatters = {
    ormolu = {
      cabalFlag = "ormolu";
      packages = [
        "hls-ormolu-plugin"
      ];
    };
    fourmolu = {
      cabalFlag = "fourmolu";
      packages = [
        "hls-fourmolu-plugin"
      ];
    };
    floskell = {
      cabalFlag = "floskell";
      packages = [
        "hls-floskell-plugin"
      ];
    };
    stylish-haskell = {
      cabalFlag = "stylishhaskell";
      packages = [
        "hls-stylish-haskell-plugin"
      ];
    };
  };

in

# make sure any formatter that is set is actually supported by us
assert
  lib.asserts.assertEachOneOf
    "supportedFormatters"
    supportedFormatters
    (lib.attrNames knownFormatters);

#
# The recommended way to override this package is
#
# pkgs.haskell-language-server.override { supportedGhcVersions = [ "90" "92"]; }
#
# for example. Read more about this in the haskell-language-server section of the nixpkgs manual.
#
let
  inherit (haskell.lib.compose)
    justStaticExecutables
    overrideCabal
    enableCabalFlag
    disableCabalFlag
    ;

  getPackages = version: haskell.packages."ghc${version}";

  # Given the list of `supportedFormatters`, remove every formatter that we know of (knownFormatters)
  # by disabling the cabal flag and also removing the formatter libraries.
  removeUnnecessaryFormatters =
    let
      # only formatters that were not requested
      unwanted = lib.pipe knownFormatters [
        (lib.filterAttrs (fmt: _: ! (lib.elem fmt supportedFormatters)))
        lib.attrsToList
      ];
      # all flags to disable
      flags = map (fmt: fmt.value.cabalFlag) unwanted;
      # all dependencies to remove from hls
      deps = lib.concatMap (fmt: fmt.value.packages) unwanted;

      # remove nulls from a list
      stripNulls = lib.filter (x: x != null);

      # remove all unwanted dependencies of formatters we don’t want
      stripDeps = overrideCabal (drv: {
        libraryHaskellDepends = lib.pipe (drv.libraryHaskellDepends or []) [
          # the existing list may contain nulls, so let’s strip them first
          stripNulls
          (lib.filter (dep: ! (lib.elem dep.pname deps)))
        ];
      });

    in drv: lib.pipe drv ([stripDeps] ++ map disableCabalFlag flags);

  tunedHls = hsPkgs:
    lib.pipe hsPkgs.haskell-language-server ([
      (haskell.lib.compose.overrideCabal (old: {
        enableSharedExecutables = dynamic;
        ${if !dynamic then "postInstall" else null} = ''
          ${old.postInstall or ""}

          remove-references-to -t ${hsPkgs.ghc} $out/bin/haskell-language-server
        '';
      }))
      ((if dynamic then enableCabalFlag else disableCabalFlag) "dynamic")
      removeUnnecessaryFormatters
    ]
    ++ lib.optionals (!dynamic) [
      justStaticExecutables
    ]);

  targets = version:
    let packages = getPackages version;
    in [ "haskell-language-server-${packages.ghc.version}" ];

  makeSymlinks = version:
    lib.concatMapStringsSep "\n"
      (x:
        "ln -s ${
          tunedHls (getPackages version)
        }/bin/haskell-language-server $out/bin/${x}")
      (targets version);

in stdenv.mkDerivation {
  pname = "haskell-language-server";
  version = haskellPackages.haskell-language-server.version;

  buildCommand = ''
    mkdir -p $out/bin
    ln -s ${tunedHls (getPackages (builtins.head supportedGhcVersions))}/bin/haskell-language-server-wrapper $out/bin/haskell-language-server-wrapper
    ${lib.concatMapStringsSep "\n" makeSymlinks supportedGhcVersions}
  '';

  meta = haskellPackages.haskell-language-server.meta // {
    maintainers = [ lib.maintainers.maralorn ];
    longDescription = ''
      This package provides the executables ${
        lib.concatMapStringsSep ", " (x: lib.concatStringsSep ", " (targets x))
        supportedGhcVersions
      } and haskell-language-server-wrapper.
      You can choose for which ghc versions to install hls with pkgs.haskell-language-server.override { supportedGhcVersions = [ "90" "92" ]; }.
    '';
  };
}