Recommended way of using HLS with reflex-platform + obelisk?

I’d love to get HLS running to develop with reflex-frp. HLS installed with GHCup running inside my usual environment fails to find dependencies.

Could not find module ‘GHCJS.DOM’
It is not a module in the current program, or in any known package.

I have also tried installing via cabal inside ob shell with no luck (see log below).

Being a total nix noob I’m out of ideas about what the problem could be and how to fix it.

Does anyone have any relevant experience or recommendations on how to achieve this?

Failed to build hie-compat-0.3.1.2.
Build log (
[..]/.cabal/logs/ghc-8.10.7/hie-compat-0.3.1.2-6cd25c57f80a4565be3fe6faa473ae59b8a96c9dd34aa406aef36d991f3ccd54.log
):
Configuring library for hie-compat-0.3.1.2..
Preprocessing library for hie-compat-0.3.1.2..
cabal: can't find source for Compat/HieAst in ., dist/build/autogen,
dist/build/global-autogen

cabal: Failed to build hie-compat-0.3.1.2 (which is required by
exe:haskell-language-server-wrapper from haskell-language-server-2.2.0.0 and
exe:haskell-language-server from haskell-language-server-2.2.0.0). See the
build log above for details.

The contents of the log mentioned reproduces the same error with no extra information.

2 Likes

Some other questions about the combination of Obelisk and HLS:

https://discourse.haskell.org/search?q=obelisk%20hls

…but not many.

1 Like

My understanding is that HLS needs to be built with the exact same version of GHC being used. You should be able to configure it using shellToolOverrides in your default.nix. Example:

# ...

obelisk.project ./. ({ pkgs, ... }: {
  # ...

  shellToolOverrides = ghc: super: {
    inherit (pkgs.haskell.packages.ghc8107) haskell-language-server;
  };

  # ...
})
1 Like

This is correct, the only feasible way is to compile HLS with Nix in ghc. (There is actually the alternative to compile hls yourself with cabal inside your nix shell, but that is a bit brittle.) An expression like the tcards above is correct in principle. Yet, it might be broken or give you a terribly outdated hls.

For me and my colleagues at work I maintain haskellPackages overrides for the newest HLS compatible with our corresponding ghcs in our obelisk project. We apply the following overrides via:

      shellToolOverrides = obeliskHaskellPackages: defaultTools:
        let
          hash-nixpkgs = import ./tooling/dep/nixos-unstable-for-hashes { };
          toolHaskellPackages = obeliskHaskellPackages.override {
            # This overlay fixes haskell-language-server
            overrides = import ./tooling/overlay.nix { inherit pkgs; };
            # Use a newer hackage database than distributed with reflex-platform
            all-cabal-hashes = hash-nixpkgs.all-cabal-hashes;
          };
        in {
          # We take hlint and haskell-language-server from the same overrides so
          # that they stay compatible.
          inherit (toolHaskellPackages) haskell-language-server;
      };

For ghc 8.10, that is hls 2.2:

{ pkgs }:

final: prev:
let
  inherit (pkgs) lib;
  inherit (pkgs.haskell.lib)
    doJailbreak
    dontCheck
    disableCabalFlag
    unmarkBroken
    overrideCabal
  ;
  hlsVersion = "2.2.0.0";
  new =
    lib.mapAttrs (name: version: dontCheck (final.callHackage name version { }))
      {
        # This is generally then newest version of these packages which is
        # compatible with ghc 8.10.7 and hlsVersion.
        # Figured out via iterative testing.
        co-log-core = "0.3.2.0";
        ghc-check = "0.5.0.8";
        ghc-lib-parser = "9.2.8.20230729";
        ghc-lib-parser-ex = "9.2.1.1";
        ghc-source-gen = "0.4.3.0";
        ghcide = hlsVersion;
        githash = "0.1.6.1";
        haskell-language-server = hlsVersion;
        hie-bios = "0.12.0";
        hie-compat = "0.3.0.0";
        hiedb = "0.4.3.0";
        hlint = "3.4.1";
        hls-alternate-number-format-plugin = hlsVersion;
        hls-call-hierarchy-plugin = hlsVersion;
        hls-change-type-signature-plugin = hlsVersion;
        hls-class-plugin = hlsVersion;
        hls-code-range-plugin = hlsVersion;
        hls-eval-plugin = hlsVersion;
        hls-explicit-fixity-plugin = hlsVersion;
        hls-explicit-imports-plugin = hlsVersion;
        hls-gadt-plugin = hlsVersion;
        hls-graph = hlsVersion;
        hls-haddock-comments-plugin = hlsVersion;
        hls-hlint-plugin = hlsVersion;
        hls-module-name-plugin = hlsVersion;
        hls-plugin-api = hlsVersion;
        hls-pragmas-plugin = hlsVersion;
        hls-qualify-imported-names-plugin = hlsVersion;
        hls-refactor-plugin = hlsVersion;
        hls-refine-imports-plugin = hlsVersion;
        hls-rename-plugin = hlsVersion;
        hls-retrie-plugin = hlsVersion;
        hls-splice-plugin = hlsVersion;
        hls-tactics-plugin = hlsVersion;
        hls-cabal-fmt-plugin = hlsVersion;
        hls-cabal-plugin = hlsVersion;
        hls-explicit-record-fields-plugin = hlsVersion;
        implicit-hie = "0.1.2.7";
        implicit-hie-cradle = "0.5.0.1";
        lsp = hlsVersion;
        lsp-test = "0.16.0.0";
        lsp-types = "2.0.2.0";
        retrie = "0.1.1.1";
        text-rope = "0.2";
      };
in
new
// {
  retrie = doJailbreak new.retrie;
  extensions = unmarkBroken prev.extensions;
  trial-tomland = unmarkBroken prev.trial-tomland;

  # Formatters are disabled because we don’t need them.
  # ormolu is disabled because our ormolu version is not compatible with ghc 8.6.5
  hls-brittany-plugin = null;
  hls-floskell-plugin = null;
  hls-fourmolu-plugin = null;
  hls-ormolu-plugin = null;
  hls-stylish-haskell-plugin = null;

  # Unimportant plugins which would require more work to fix
  hls-stan-plugin = null;
  hls-retrie-plugin = null;
  hls-cabal-plugin = null;

  # We need no testing utils
  ghcide-bench = null;
  hls-test-utils = null;
  ghcide-test-utils = null;

  haskell-language-server = lib.pipe new.haskell-language-server (
    map (lib.flip disableCabalFlag) [
      "floskell"
      "fourmolu"
      "ormolu"
      "stylishhaskell"
      "brittany"

      "stan"
      "retrie"
      "cabal"
    ]
    ++ [
      (lib.flip overrideCabal {
        enableSharedExecutables = true;
        postInstall = ''
          mv "$out/bin/haskell-language-server" "$out/bin/haskell-language-server-${final.ghc.version}"
        '';
      })
    ]
  );
}

For ghc 8.6, that is hls 1.8:

{ pkgs }:

final: prev:
let
  inherit (pkgs) lib;
  inherit (pkgs.haskell.lib)
    doJailbreak
    dontCheck
    disableCabalFlag
    addSetupDepends
    overrideCabal
  ;
  newerCabal = lib.flip addSetupDepends [
    (final.callHackage "Cabal" "3.2.0.0" { })
  ];
  new =
    lib.mapAttrs (name: version: dontCheck (final.callHackage name version { }))
      {
        # This is generally then newest version of these packages which is
        # compatible with ghc 8.6.5 and hls 1.8.0.0.
        # Figured out via iterative testing.
        co-log-core = "0.3.2.0";
        ghc-check = "0.5.0.8";
        ghc-lib-parser = "8.10.4.20210206";
        ghc-lib-parser-ex = "8.10.0.24";
        ghc-source-gen = "0.4.3.0";
        ghcide = "1.8.0.0";
        githash = "0.1.6.1";
        haskell-language-server = "1.8.0.0";
        hie-bios = "0.11.0";
        hie-compat = "0.3.0.0";
        hiedb = "0.4.2.0";
        hlint = "3.2.8";
        hls-alternate-number-format-plugin = "1.2.0.0";
        hls-call-hierarchy-plugin = "1.1.0.0";
        hls-change-type-signature-plugin = "1.0.1.1";
        hls-class-plugin = "1.1.0.0";
        hls-code-range-plugin = "1.0.0.0";
        hls-eval-plugin = "1.3.0.0";
        hls-explicit-fixity-plugin = "1.0.0.0";
        hls-explicit-imports-plugin = "1.1.0.1";
        hls-gadt-plugin = "1.0.0.0";
        hls-graph = "1.8.0.0";
        hls-haddock-comments-plugin = "1.1.0.0";
        hls-hlint-plugin = "1.1.0.0";
        hls-module-name-plugin = "1.1.0.0";
        hls-plugin-api = "1.5.0.0";
        hls-pragmas-plugin = "1.0.4.0";
        hls-qualify-imported-names-plugin = "1.0.1.0";
        hls-refactor-plugin = "1.0.0.0";
        hls-refine-imports-plugin = "1.0.3.0";
        hls-rename-plugin = "1.0.1.0";
        hls-retrie-plugin = "1.0.2.2";
        hls-splice-plugin = "1.0.2.0";
        hls-tactics-plugin = "1.7.0.0";
        implicit-hie = "0.1.4.0";
        implicit-hie-cradle = "0.5.0.1";
        lsp = "1.6.0.0";
        lsp-test = "0.14.1.0";
        lsp-types = "1.6.0.0";
        retrie = "0.1.1.1";
        text-rope = "0.2";
      };
in
new
// {
  retrie = doJailbreak new.retrie;

  # These packages use a too new syntax in their .cabal file.
  ghcide = newerCabal new.ghcide;
  implicit-hie-cradle = newerCabal new.implicit-hie-cradle;
  hls-refactor-plugin = newerCabal new.hls-refactor-plugin;

  # Formatters are disabled because we don’t need them.
  # ormolu is disabled because our ormolu version is not compatible with ghc 8.6.5
  hls-brittany-plugin = null;
  hls-floskell-plugin = null;
  hls-fourmolu-plugin = null;
  hls-ormolu-plugin = null;
  hls-stylish-haskell-plugin = null;

  # We need no testing utils
  ghcide-bench = null;
  hls-test-utils = null;
  ghcide-test-utils = null;

  haskell-language-server = lib.pipe new.haskell-language-server (
    map (lib.flip disableCabalFlag) [
      "floskell"
      "fourmolu"
      "ormolu"
      "stylishhaskell"
      "brittany"
    ]
    ++ [
      newerCabal
      (lib.flip overrideCabal {
        enableSharedExecutables = true;
        postInstall = ''
          mv "$out/bin/haskell-language-server" "$out/bin/haskell-language-server-${final.ghc.version}"
        '';
      })
    ]
  );
}

I hope this is helpful (and compatible with the newest ob release, I assume so but haven’t tried). Have fun compiling!

2 Likes

Can you provide a complete default.nix example that works?

There’s a ton of people (I would guess much more than the current users of Obelisk) that wish to explore FRP via Reflex and HLS that are clueless about Nix and can’t set this up.

This @tcard solution worked for me. It gives you the context where to place the code and simply running ob shell gave me an environment where my vim (with coc.nvim) worked (mostly) fine and finally detected the HLS. As you can see there are still some errors in the import side but it looks mostly fine.

@maralorn answer I guess is more complete but there is no context as to where you should place the code and couldn’t make it work with ob shell.
From my understanding (as a complete noob) both fragments go into the default.nix which would look like this:

{ system ? builtins.currentSystem
, obelisk ? import ./.obelisk/impl {
    inherit system;
    iosSdkVersion = "16.1";

    # You must accept the Android Software Development Kit License Agreement at
    # https://developer.android.com/studio/terms in order to build Android apps.
    # Uncomment and set this to `true` to indicate your acceptance:
    # config.android_sdk.accept_license = false;

    # In order to use Let's Encrypt for HTTPS deployments you must accept
    # their terms of service at https://letsencrypt.org/repository/.
    # Uncomment and set this to `true` to indicate your acceptance:
    # terms.security.acme.acceptTerms = false;
  }
}:
with obelisk;
project ./. ({ ... }: {
  android.applicationId = "systems.obsidian.obelisk.examples.minimal";
  android.displayName = "Obelisk Minimal Example";
  ios.bundleIdentifier = "systems.obsidian.obelisk.examples.minimal";
  ios.bundleName = "Obelisk Minimal Example";
  shellToolOverrides = obeliskHaskellPackages: defaultTools:
  let
    hash-nixpkgs = import ./tooling/dep/nixos-unstable-for-hashes { };
    toolHaskellPackages = obeliskHaskellPackages.override {
      # This overlay fixes haskell-language-server
      overrides = import ./tooling/overlay.nix { inherit pkgs; };
      # Use a newer hackage database than distributed with reflex-platform
      all-cabal-hashes = hash-nixpkgs.all-cabal-hashes;
    };
  in {
    # We take hlint and haskell-language-server from the same overrides so
    # that they stay compatible.
    inherit (toolHaskellPackages) haskell-language-server;
  };
})

{ pkgs }:

final: prev:
let
  inherit (pkgs) lib;
  inherit (pkgs.haskell.lib)
    doJailbreak
    dontCheck
    disableCabalFlag
    unmarkBroken
    overrideCabal
  ;
  hlsVersion = "2.2.0.0";
  new =
    lib.mapAttrs (name: version: dontCheck (final.callHackage name version { }))
      {
        # This is generally then newest version of these packages which is
        # compatible with ghc 8.10.7 and hlsVersion.
        # Figured out via iterative testing.
        co-log-core = "0.3.2.0";
        ghc-check = "0.5.0.8";
        ghc-lib-parser = "9.2.8.20230729";
        ghc-lib-parser-ex = "9.2.1.1";
        ghc-source-gen = "0.4.3.0";
        ghcide = hlsVersion;
        githash = "0.1.6.1";
        haskell-language-server = hlsVersion;
        hie-bios = "0.12.0";
        hie-compat = "0.3.0.0";
        hiedb = "0.4.3.0";
        hlint = "3.4.1";
        hls-alternate-number-format-plugin = hlsVersion;
        hls-call-hierarchy-plugin = hlsVersion;
        hls-change-type-signature-plugin = hlsVersion;
        hls-class-plugin = hlsVersion;
        hls-code-range-plugin = hlsVersion;
        hls-eval-plugin = hlsVersion;
        hls-explicit-fixity-plugin = hlsVersion;
        hls-explicit-imports-plugin = hlsVersion;
        hls-gadt-plugin = hlsVersion;
        hls-graph = hlsVersion;
        hls-haddock-comments-plugin = hlsVersion;
        hls-hlint-plugin = hlsVersion;
        hls-module-name-plugin = hlsVersion;
        hls-plugin-api = hlsVersion;
        hls-pragmas-plugin = hlsVersion;
        hls-qualify-imported-names-plugin = hlsVersion;
        hls-refactor-plugin = hlsVersion;
        hls-refine-imports-plugin = hlsVersion;
        hls-rename-plugin = hlsVersion;
        hls-retrie-plugin = hlsVersion;
        hls-splice-plugin = hlsVersion;
        hls-tactics-plugin = hlsVersion;
        hls-cabal-fmt-plugin = hlsVersion;
        hls-cabal-plugin = hlsVersion;
        hls-explicit-record-fields-plugin = hlsVersion;
        implicit-hie = "0.1.2.7";
        implicit-hie-cradle = "0.5.0.1";
        lsp = hlsVersion;
        lsp-test = "0.16.0.0";
        lsp-types = "2.0.2.0";
        retrie = "0.1.1.1";
        text-rope = "0.2";
      };
in
new
// {
  retrie = doJailbreak new.retrie;
  extensions = unmarkBroken prev.extensions;
  trial-tomland = unmarkBroken prev.trial-tomland;

  # Formatters are disabled because we don’t need them.
  # ormolu is disabled because our ormolu version is not compatible with ghc 8.6.5
  hls-brittany-plugin = null;
  hls-floskell-plugin = null;
  hls-fourmolu-plugin = null;
  hls-ormolu-plugin = null;
  hls-stylish-haskell-plugin = null;

  # Unimportant plugins which would require more work to fix
  hls-stan-plugin = null;
  hls-retrie-plugin = null;
  hls-cabal-plugin = null;

  # We need no testing utils
  ghcide-bench = null;
  hls-test-utils = null;
  ghcide-test-utils = null;

  haskell-language-server = lib.pipe new.haskell-language-server (
    map (lib.flip disableCabalFlag) [
      "floskell"
      "fourmolu"
      "ormolu"
      "stylishhaskell"
      "brittany"

      "stan"
      "retrie"
      "cabal"
    ]
    ++ [
      (lib.flip overrideCabal {
        enableSharedExecutables = true;
        postInstall = ''
          mv "$out/bin/haskell-language-server" "$out/bin/haskell-language-server-${final.ghc.version}"
        '';
      })
    ]
  );
}

However I get a syntax error with the previous default.nix:

error: syntax error, unexpected '}', expecting '.' or '='

       at /home/user/Documents/Dev/obelisk-project/default.nix:39:8:

           38|
           39| { pkgs }:
             |        ^
           40|
1 Like

The @tcard solution also worked for me.

With regard

I used implicit-hie: Auto generate hie-bios cradles & hie.yaml

You can install it with nix-shell -p haskellPackages.implicit-hie

I used it to generate hie.yaml in the frontend folder and this addressed the issue. Basically just run gen-hie > hie.yaml in any directory that has a .cabal. Its was only needed in the frontend directory for my project, but I also ran it in common and backend for completeness sake.