[Solved] Haskell language server - basic setup for Emacs eglot

New cabal project with config as below. Cabal builds and runs my code fine. Language server reports error when importing Data.Map.Strict. “Could not load module… It is a member of the hidden package containers-0.7”.

After the basic setup didn’t work, following the instructions from the link below, I also tried cabal --lib install containers --package-env . But, can someone please tell me how do I set this up so it works, set and forget? It worked last year, and the year before, something has changed.

Followed everything here First steps - GHCup, it simply does not work.

HLS 2.9.0.0 cabal 3.12.1.0 GHC 9.10.1, using ghcup.

executable aoc2024
main-is:          Main.hs
-- Modules included in this executable, other than Main.
-- other-modules:
other-modules:
Day1,
Day2,
Day3
build-depends:    base ^>=4.20.0.0,
                  extra ^>= 1.8,
                  containers ^>= 0.7
-- LANGUAGE extensions used by modules in this package.
-- other-extensions:
hs-source-dirs: .
default-language: Haskell2010

Thanks for any pointers.

1 Like

You need to add containers to your build-depends. I’m actually very surprised to hear that it works under cabal without that additional build-depends.

EDIT: Oh I see, I completely missed that it was there all along.

Sorry, formatting was bad above, I fixed it. It is indeed there.

2 Likes

Which editor are you using? VSCode?

Can you post the full output of running haskell-language-server-wrapper manually on the command line in your project directory?

Perhaps use the <details> element, e.g.:

<details><summary>Log output:</summary>

```
[the log here]
```

</details>

That renders like this:

Log output:
[the log here]
1 Like

Thanks for your response. I am using emacs eglot.

Log output: haskell-language-server-wrapper No 'hie.yaml' found. Try to discover the project type! Run entered for haskell-language-server-wrapper(haskell-language-server-wrapper) Version 2.9.0.1 x86_64 ghc-9.10.1 Current directory: /home/xyz/haskell-hacking/aoc/2024 Operating system: linux Arguments: [] Cradle directory: /home/xyz/haskell-hacking/aoc/2024 Cradle type: Cabal

Tool versions found on the $PATH
cabal: 3.12.1.0
stack: 3.1.1
ghc: 9.10.1

Consulting the cradle to get project GHC version…
2024-12-07T09:47:51.984702Z | Debug | cabal exec -v0 – ghc --print-libdir
2024-12-07T09:47:52.180808Z | Debug | cabal exec -v0 – ghc -package-env=- -ignore-dot-ghci -e Control.Monad.join (Control.Monad.fmap System.IO.putStr System.Environment.getExecutablePath)
2024-12-07T09:47:55.112321Z | Debug | cabal --builddir=/home/xyz/.cache/hie-bios/dist-2024-9cc904d41d088735fd6d6709632c45d6 v2-exec --with-compiler /home/xyz/.cache/hie-bios/wrapper-b54f81dea4c0e6d1626911c526bc4e36 --with-hc-pkg /home/xyz/.cache/hie-bios/ghc-pkg-2203497f65d11580578fadcf45e6cf1d ghc -v0 – --numeric-version
Environment Variables
HIE_BIOS_GHC: /home/xyz/.ghcup/ghc/9.10.1/lib/ghc-9.10.1/bin/ghc-9.10.1
HIE_BIOS_GHC_ARGS: -B/home/xyz/.ghcup/ghc/9.10.1/lib/ghc-9.10.1/lib
Project GHC version: 9.10.1
haskell-language-server exe candidates: [“haskell-language-server-9.10.1”,“haskell-language-server”]
Launching haskell-language-server exe at:/home/xyz/.ghcup/bin/haskell-language-server-9.10.1
2024-12-07T09:47:55.207967Z | Debug | cabal exec -v0 – ghc --print-libdir
2024-12-07T09:47:55.394639Z | Debug | cabal exec -v0 – ghc -package-env=- -ignore-dot-ghci -e Control.Monad.join (Control.Monad.fmap System.IO.putStr System.Environment.getExecutablePath)
2024-12-07T09:47:55.592401Z | Debug | cabal --builddir=/home/xyz/.cache/hie-bios/dist-2024-9cc904d41d088735fd6d6709632c45d6 v2-exec --with-compiler /home/xyz/.cache/hie-bios/wrapper-b54f81dea4c0e6d1626911c526bc4e36 --with-hc-pkg /home/xyz/.cache/hie-bios/ghc-pkg-2203497f65d11580578fadcf45e6cf1d ghc -v0 – -v0 -package-env=- -ignore-dot-ghci -e Control.Monad.join (Control.Monad.fmap System.IO.putStr System.Environment.getExecutablePath)
Environment Variables
HIE_BIOS_GHC: /home/xyz/.ghcup/ghc/9.10.1/lib/ghc-9.10.1/bin/ghc-9.10.1
HIE_BIOS_GHC_ARGS: -B/home/xyz/.ghcup/ghc/9.10.1/lib/ghc-9.10.1/lib
2024-12-07T09:47:55.681311Z | Debug | cabal exec -v0 – ghc --print-libdir
2024-12-07T09:47:55.873128Z | Debug | cabal exec -v0 – ghc -package-env=- -ignore-dot-ghci -e Control.Monad.join (Control.Monad.fmap System.IO.putStr System.Environment.getExecutablePath)
2024-12-07T09:47:55.960621Z | Debug | cabal --builddir=/home/xyz/.cache/hie-bios/dist-2024-9cc904d41d088735fd6d6709632c45d6 v2-exec --with-compiler /home/xyz/.cache/hie-bios/wrapper-b54f81dea4c0e6d1626911c526bc4e36 --with-hc-pkg /home/xyz/.cache/hie-bios/ghc-pkg-2203497f65d11580578fadcf45e6cf1d ghc -v0 – --print-libdir
Environment Variables
HIE_BIOS_GHC: /home/xyz/.ghcup/ghc/9.10.1/lib/ghc-9.10.1/bin/ghc-9.10.1
HIE_BIOS_GHC_ARGS: -B/home/xyz/.ghcup/ghc/9.10.1/lib/ghc-9.10.1/lib
2024-12-07T09:47:57.680372Z | Info | haskell-language-server version: 2.9.0.1 (GHC: 9.10.1) (PATH: /home/xyz/.ghcup/hls/2.9.0.1/lib/haskell-language-server-2.9.0.1/bin/haskell-language-server-9.10.1)
2024-12-07T09:47:57.682086Z | Info | Directory: /home/xyz/haskell-hacking/aoc/2024
2024-12-07T09:47:57.682766Z | Info | Logging heap statistics every 60.00s
ghcide setup tester in /home/xyz/haskell-hacking/aoc/2024.
Report bugs at Issues · haskell/haskell-language-server · GitHub

Step 1/4: Finding files to test in /home/xyz/haskell-hacking/aoc/2024
Found 5 files

Step 2/4: Looking for hie.yaml files that control setup
Found 1 cradle
()

Step 3/4: Initializing the IDE

Step 4/4: Type checking the files
2024-12-07T09:47:57.745040Z | Info | Cradle path: Main.hs
2024-12-07T09:47:57.745384Z | Warning | No cradle found for Main.hs.
Proceeding with implicit cradle.
You should ignore this message, unless you see a ‘Multi Cradle: No prefixes matched’ error.
2024-12-07T09:47:57.752790Z | Info | invoking build tool to determine build flags (this may take some time depending on the cache)
2024-12-07T09:47:58.137756Z | Info | Load cabal cradle using single file
2024-12-07T09:47:58.413965Z | Info | cabal --builddir=/home/xyz/.cache/hie-bios/dist-2024-9cc904d41d088735fd6d6709632c45d6 v2-repl --with-compiler /home/xyz/.cache/hie-bios/wrapper-b54f81dea4c0e6d1626911c526bc4e36 --with-hc-pkg /home/xyz/.cache/hie-bios/ghc-pkg-2203497f65d11580578fadcf45e6cf1d /home/xyz/haskell-hacking/aoc/2024/Main.hs
Environment Variables
HIE_BIOS_OUTPUT: /tmp/HIE_BIOS_OUTPUT8358-0
HIE_BIOS_GHC: /home/xyz/.ghcup/ghc/9.10.1/lib/ghc-9.10.1/bin/ghc-9.10.1
HIE_BIOS_GHC_ARGS: -B/home/xyz/.ghcup/ghc/9.10.1/lib/ghc-9.10.1/lib
2024-12-07T09:48:00.288765Z | Info | Interface files cache directory: /home/xyz/.cache/ghcide/aoc2024-0.1.0.0-inplace-aoc2024-b34cd97e9c5eddf4d82de020d760980159ecd54c
2024-12-07T09:48:00.306533Z | Info | Making new HscEnv. In-place unit ids: [aoc2024-0.1.0.0-inplace-aoc2024]

Completed (5 files worked, 0 files failed

That log output is not showing the error. If you remove the containers dependency (introducing an error), you’d see this:

2024-12-07T09:57:51.944309Z | Info | updateFileDiagnostics published different from new diagnostics - file diagnostics: File:     /tmp/aoc2024/Main.hs
Hidden:   no
Range:    3:8-3:23
Source:   not found
Severity: DiagnosticSeverity_Error
Message: 
  Could not load module ‘Data.Map.Strict’.
  It is a member of the hidden package ‘containers-0.7’.
Files that failed:
 * /tmp/aoc2024/Main.hs

So, it seems the issue is not with HLS itself.

1 Like

Thanks, figured it out for anyone else coming here who persists with emacs, eglot makes a really dumb assumption that your .git is located right where your .cabal file is. If those two are in the same directory, everything works.

3 Likes

I’m glad that you somehow got it to work. However, I don’t think your analysis is accurate; I’m using eglot in a multi-repo in which every subdir is a separate cabal package. That just seems to work.

1 Like

In mine, I have a cabal.project file at the root, which is what seems to allow eglot to find the true locations of my packages.

Mine is a monorepo, not a multi-repo. So top level dir aoc has the .git, subdirectories 2023, 2024 contain the .cabal files. Eglot was confused until I put a phantom .git in 2024, then everything worked.

I’m very surprised that eglot even knows anything about .git! What if you’re not using git at all? Does it just not work?

1 Like

Eglot has some rules of thumb around how to find (guess) the project root. My guess is if no .git then it would default to the directory where you opened emacs, or it would throw an error complaining about no :rootDirectory, and that it must be manually configured. But I have not tried this.

From the eglot manal:

When Eglot starts a server program, it does so in the project’s root directory, which is usually the top-level directory of the project’s directory hierarchy. This ensures the language server has the same comprehensive view of the project’s files as you do.

For example, if you visit the file ~/projects/fooey/lib/x.foo and x.foo belongs to a project rooted at ~/projects/fooey (perhaps because a .git directory exists there), then M-x eglot causes the server program to start with that root as the current working directory. The server then will analyze not only the file lib/x.foo you visited, but likely also all the other *.foo files under the ~/projects/fooey directory.

https://joaotavora.github.io/eglot/

1 Like

You probably want to add a cabal.project file at the root with packages: 2023 2024 etc. Then all components will use the same dependency versions, you’ll be able to do things like cabal build all, and IDE support will probably work better (it’s similar in VSCode).

I don’t believe I do. Each year I do Advent of Code, the dependencies change. For example, since last year, forM_ moved from Control.Monad.State to Control.Monad. If I do what you suggest, which is to have one .cabal file, when I bump the .cabal dependencies each year to be up to date, I have to go and port all the previous code from previous years over up to the recent package versions I am using in the current year to make everything build again. Alternatively I could lock this proposed one .cabal file down and not bump any of the dependency versions year after year, but then it eventually gets ancient and out of sync with the default doc search on hoogle.

1 Like

Fair enough, that’s why I went with “probably”! Personally that’s the point where I’d abandon the monorepo, but I get that different things work for different people.

1 Like

It seems like eglot uses the project package (calling project-root) to find the root. So in theory you should be able to just add it to project-vc-extra-root-markers instead of the fake .git.

I sadly have

  (setq project-vc-extra-root-markers '("*.csproj"
                                        ".project"
                                        "package.json"
                                        "*.cabal"
                                        "pom.xml"
                                        "requirements.txt"
                                        "Gemfile"
                                        "*.gemspec"
                                        "autogen.sh"))
1 Like