From 811db8c616bd17836fdbd99b0e62f20fa0efde8c Mon Sep 17 00:00:00 2001 From: Profpatsch Date: Wed, 18 Oct 2023 16:23:42 +0200 Subject: [PATCH 1/5] haskell-language-server: reformat code a bit & document better --- .../haskell-language-server/withWrapper.nix | 53 +++++++++++++------ 1 file changed, 38 insertions(+), 15 deletions(-) diff --git a/pkgs/development/tools/haskell/haskell-language-server/withWrapper.nix b/pkgs/development/tools/haskell/haskell-language-server/withWrapper.nix index a6f287c37b3f..ea5fe88d11f6 100644 --- a/pkgs/development/tools/haskell/haskell-language-server/withWrapper.nix +++ b/pkgs/development/tools/haskell/haskell-language-server/withWrapper.nix @@ -1,9 +1,18 @@ { 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 {}; 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 }: # # The recommended way to override this package is @@ -13,9 +22,15 @@ # 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}"; + tunedHls = hsPkgs: lib.pipe hsPkgs.haskell-language-server ([ (haskell.lib.compose.overrideCabal (old: { @@ -27,32 +42,40 @@ let ''; })) ((if dynamic then enableCabalFlag else disableCabalFlag) "dynamic") - ] ++ optionals (!dynamic) [ + ] + ++ 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 +assert supportedGhcVersions != []; 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" ]; }. From 2099ebdf593da8babffb96f3909fb8568ce483fb Mon Sep 17 00:00:00 2001 From: Profpatsch Date: Wed, 18 Oct 2023 16:50:59 +0200 Subject: [PATCH 2/5] lib: add asserts.assertEachOneOf Along the lines of `assertOneOf`, but expects a list of values to be compared. This gives a good error message and is useful for lists of values, like `supportedGhcVersions` in the arguments of `haskell-language-server`. --- lib/asserts.nix | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/lib/asserts.nix b/lib/asserts.nix index 98e0b490acf2..8d0a621f4c1c 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}"; } From 529cbf1a032b58c99e560d976722c03b3e51ae56 Mon Sep 17 00:00:00 2001 From: Profpatsch Date: Wed, 18 Oct 2023 16:52:15 +0200 Subject: [PATCH 3/5] haskell-language-server: check `supportedGhcVersions` in assert This produces a better error message than just returning the error of the `haskell.packages."ghc${version}"` interpolation. --- .../haskell/haskell-language-server/withWrapper.nix | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/pkgs/development/tools/haskell/haskell-language-server/withWrapper.nix b/pkgs/development/tools/haskell/haskell-language-server/withWrapper.nix index ea5fe88d11f6..bcfde333e9bc 100644 --- a/pkgs/development/tools/haskell/haskell-language-server/withWrapper.nix +++ b/pkgs/development/tools/haskell/haskell-language-server/withWrapper.nix @@ -14,6 +14,16 @@ # See https://haskell-language-server.readthedocs.io/en/latest/troubleshooting.html#static-binaries for more information. , dynamic ? true }: + +assert + lib.asserts.assertEachOneOf + "supportedGhcVersions" + supportedGhcVersions + (lib.pipe haskell.packages [ + lib.attrNames + (lib.filter (lib.hasPrefix "ghc")) + (map (lib.removePrefix "ghc")) + ]); # # The recommended way to override this package is # From 8e803f2fb90f96943f69c145e312ad15f07f9b16 Mon Sep 17 00:00:00 2001 From: Profpatsch Date: Wed, 18 Oct 2023 19:12:22 +0200 Subject: [PATCH 4/5] haskell-language-server: keep only formatters the user wants This introduces a list of `supportedFormatters` that are the ones that should be compiled into hls. Removes a nontrivial amount of transitive dependencies if only one formatter is used in a project. --- .../haskell-language-server/withWrapper.nix | 79 ++++++++++++++++++- 1 file changed, 76 insertions(+), 3 deletions(-) diff --git a/pkgs/development/tools/haskell/haskell-language-server/withWrapper.nix b/pkgs/development/tools/haskell/haskell-language-server/withWrapper.nix index bcfde333e9bc..d9f74b493350 100644 --- a/pkgs/development/tools/haskell/haskell-language-server/withWrapper.nix +++ b/pkgs/development/tools/haskell/haskell-language-server/withWrapper.nix @@ -13,8 +13,13 @@ # 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. An empty list means “all”. +, supportedFormatters ? [ ] }: +# make sure the user only sets GHC versions that actually exist +assert supportedGhcVersions != []; assert lib.asserts.assertEachOneOf "supportedGhcVersions" @@ -24,6 +29,47 @@ assert (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 # @@ -41,6 +87,34 @@ let 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: { @@ -52,6 +126,7 @@ let ''; })) ((if dynamic then enableCabalFlag else disableCabalFlag) "dynamic") + (if supportedFormatters != [] then removeUnnecessaryFormatters else lib.id) ] ++ lib.optionals (!dynamic) [ justStaticExecutables @@ -69,9 +144,7 @@ let }/bin/haskell-language-server $out/bin/${x}") (targets version); -in -assert supportedGhcVersions != []; stdenv.mkDerivation -{ +in stdenv.mkDerivation { pname = "haskell-language-server"; version = haskellPackages.haskell-language-server.version; From 77908eaac0b36702a6a054218309efad34955163 Mon Sep 17 00:00:00 2001 From: Profpatsch Date: Thu, 9 Nov 2023 17:26:29 +0100 Subject: [PATCH 5/5] haskell-language-server: empty list removes all formatters Based on the discussion in https://github.com/NixOS/nixpkgs/pull/261848 --- .../tools/haskell/haskell-language-server/withWrapper.nix | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/pkgs/development/tools/haskell/haskell-language-server/withWrapper.nix b/pkgs/development/tools/haskell/haskell-language-server/withWrapper.nix index d9f74b493350..59a1303764ba 100644 --- a/pkgs/development/tools/haskell/haskell-language-server/withWrapper.nix +++ b/pkgs/development/tools/haskell/haskell-language-server/withWrapper.nix @@ -14,8 +14,10 @@ # See https://haskell-language-server.readthedocs.io/en/latest/troubleshooting.html#static-binaries for more information. , dynamic ? true -# which formatters are supported. An empty list means “all”. -, supportedFormatters ? [ ] +# 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 @@ -126,7 +128,7 @@ let ''; })) ((if dynamic then enableCabalFlag else disableCabalFlag) "dynamic") - (if supportedFormatters != [] then removeUnnecessaryFormatters else lib.id) + removeUnnecessaryFormatters ] ++ lib.optionals (!dynamic) [ justStaticExecutables