summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--lib/asserts.nix29
-rw-r--r--pkgs/development/tools/haskell/haskell-language-server/withWrapper.nix138
2 files changed, 152 insertions, 15 deletions
diff --git a/lib/asserts.nix b/lib/asserts.nix
index 98e0b490acf..8d0a621f4c1 100644
--- a/lib/asserts.nix
+++ b/lib/asserts.nix
@@ -50,4 +50,33 @@ rec {
       lib.generators.toPretty {} xs}, but is: ${
         lib.generators.toPretty {} val}";
 
+  /* Specialized `assertMsg` for checking if every one of `vals` is one of the elements
+     of the list `xs`. Useful for checking lists of supported attributes.
+
+     Example:
+       let sslLibraries = [ "libressl" "bearssl" ];
+       in assertEachOneOf "sslLibraries" sslLibraries [ "openssl" "bearssl" ]
+       stderr> error: each element in sslLibraries must be one of [
+       stderr>   "openssl"
+       stderr>   "bearssl"
+       stderr> ], but is: [
+       stderr>   "libressl"
+       stderr>   "bearssl"
+       stderr> ]
+
+     Type:
+       assertEachOneOf :: String -> List ComparableVal -> List ComparableVal -> Bool
+  */
+  assertEachOneOf =
+    # The name of the variable the user entered `val` into, for inclusion in the error message
+    name:
+    # The list of values of what the user provided, to be compared against the values in `xs`
+    vals:
+    # The list of valid values
+    xs:
+    assertMsg
+    (lib.all (val: lib.elem val xs) vals)
+    "each element in ${name} must be one of ${
+      lib.generators.toPretty {} xs}, but is: ${
+        lib.generators.toPretty {} vals}";
 }
diff --git a/pkgs/development/tools/haskell/haskell-language-server/withWrapper.nix b/pkgs/development/tools/haskell/haskell-language-server/withWrapper.nix
index a6f287c37b3..59a1303764b 100644
--- a/pkgs/development/tools/haskell/haskell-language-server/withWrapper.nix
+++ b/pkgs/development/tools/haskell/haskell-language-server/withWrapper.nix
@@ -1,10 +1,77 @@
 { lib
 , stdenv
-, supportedGhcVersions ? [ "94" ]
-, dynamic ? true
 , 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
 #
@@ -13,9 +80,43 @@
 # for example. Read more about this in the haskell-language-server section of the nixpkgs manual.
 #
 let
-  inherit (lib) concatStringsSep concatMapStringsSep take splitString pipe optionals;
-  inherit (haskell.lib.compose) justStaticExecutables overrideCabal enableCabalFlag disableCabalFlag;
+  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: {
@@ -27,32 +128,39 @@ let
         '';
       }))
       ((if dynamic then enableCabalFlag else disableCabalFlag) "dynamic")
-    ] ++ optionals (!dynamic) [
+      removeUnnecessaryFormatters
+    ]
+    ++ lib.optionals (!dynamic) [
       justStaticExecutables
     ]);
+
   targets = version:
     let packages = getPackages version;
-    in [
-      "haskell-language-server-${packages.ghc.version}"
-    ];
+    in [ "haskell-language-server-${packages.ghc.version}" ];
+
   makeSymlinks = version:
-    concatMapStringsSep "\n" (x:
-      "ln -s ${
-        tunedHls (getPackages version)
-      }/bin/haskell-language-server $out/bin/${x}") (targets version);
-in assert supportedGhcVersions != []; stdenv.mkDerivation {
+    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
-    ${concatMapStringsSep "\n" makeSymlinks supportedGhcVersions}
+    ${lib.concatMapStringsSep "\n" makeSymlinks supportedGhcVersions}
   '';
+
   meta = haskellPackages.haskell-language-server.meta // {
     maintainers = [ lib.maintainers.maralorn ];
     longDescription = ''
       This package provides the executables ${
-        concatMapStringsSep ", " (x: concatStringsSep ", " (targets x))
+        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" ]; }.