Skip to content

Commit 02e5f82

Browse files
feat: add haskell-language-server 2.14.0.0 (#214)
* feat: add haskell-language-server 2.14.0.0 Add package for the Haskell Language Server (HLS), the official LSP implementation for Haskell. Builds from source using GHC + Cabal with dynamic linking, copying the binary and all required shared libraries via ldd dependency resolution. - Source: GitHub tag 2.14.0.0 with automatic extraction - Build deps: base, cabal, ghc - Runtime deps: glibc - Network access enabled for cabal dependency index updates - Includes standalone version check test * fix: restrict dynamic library bundling to Haskell-specific libs * fix(haskell-language-server): add missing build deps and disable test/benchmark builds The HLS cabal.project ships with tests:True and benchmarks:True, which makes cabal resolve and compile test/benchmark deps for every transitive dependency — a major cost and failure surface for a 16+ minute build. Pass --disable-tests --disable-benchmarks to build only the executable. Also add alex, happy, and zlib as explicit build_deps. alex and happy are in ghc's build_deps (not runtime_deps), so they aren't injected into downstream builds — cabal would otherwise have to download and compile them from Hackage. zlib provides the C library needed by HLS's Haskell dependencies. * fix(haskell-language-server): set CABAL_DIR and capture build errors Without CABAL_DIR, cabal has no writable home for its config, package index, and build store in the sandboxed build environment — the most likely root cause of the 17-minute build failures. Every other Haskell package in the repo (tamarin-prover, stack) sets this; HLS was the only one that didn't. Also add --with-compiler for explicitness, -v1 for verbose output, and an error-capture block (matching tamarin-prover's pattern) that extracts the real compile/link error on failure instead of a bare "build failed" exit. * fix(haskell-language-server): use cabal-level parallelism to avoid OOM Replace --ghc-options="-j$(nproc)" (per-module GHC parallelism) with --jobs="$(nproc)" (per-package cabal parallelism). HLS is a massive project — running nproc GHC instances each with nproc internal module threads exhausts memory on the build host. Cabal-level parallelism compiles packages concurrently while keeping module compilation sequential within each package, matching the tamarin-prover build. * fix(haskell-language-server): add gmp, libffi, zlib to runtime_deps The HLS binary links against libgmp, libffi, and libz (GHC runtime dependencies), but runtime_deps only declared glibc — causing the missing-runtime-deps and standalone-test post-build checks to fail. Move zlib from build_deps to runtime_deps (runtime_deps are injected into the build env, so it covers both roles), and add gmp + libffi matching GHC's own runtime_deps. * fix(haskell-language-server): fix alex/happy version detection for cabal Cabal installs alex/happy from Hackage as build-tool-depends, but the installed binaries link against GHC's shared libraries (libHSrts etc.) which aren't on the default library search path — so they fail to run and cabal reports "version could not be determined" (Cabal-1008). Two fixes: - Add GHC's libdir to LD_LIBRARY_PATH so cabal-installed build tools can actually execute - Pass --with-alex/--with-happy pointing at the pre-installed system versions so cabal uses those for version checks instead of its own broken copies * fix(haskell-language-server): let cabal install its own alex/happy The system alex/happy were built with ghc-bootstrap 9.8.1, so their template files live in /usr/share/x86_64-linux-ghc-9.8.1/... — wrong GHC version. Passing --with-alex/--with-happy made cabal use those binaries, which then failed looking for templates in the 9.8.1 data dir. Remove the --with-* flags so cabal installs its own alex/happy from Hackage (built with 9.10.3, correct data dir). The LD_LIBRARY_PATH fix ensures those cabal-installed tools can actually execute. * fix(haskell): ship alex/happy template files and use them in HLS alex and happy only captured their binary (usr/bin/alex, usr/bin/happy) as OutputBin — the template files that ./Setup copy installs to usr/share/x86_64-linux-ghc-<version>/... were discarded. Without the templates, the tools fail with "openFile: does not exist" when any downstream package uses them. Add a data OutputData (usr/share/**) to both alex and happy so the template files ship with the package. Then use --with-alex/--with-happy in the HLS build to point cabal at the pre-packaged versions instead of letting it download and build its own copies from Hackage — those copies were the source of the "version could not be determined" (Cabal-1008) failures. alex/happy are standalone code generators; they don't need to match the downstream GHC version, they just need their template files at the hardcoded path. --------- Co-authored-by: edge-delta[bot] <226719692+edge-delta[bot]@users.noreply.github.com>
1 parent 436b57e commit 02e5f82

4 files changed

Lines changed: 142 additions & 2 deletions

File tree

packages/alex/build.ncl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
let { BuildSpec, Local, OutputBin, Source, .. } = import "minimal.ncl" in
1+
let { BuildSpec, Local, OutputBin, OutputData, Source, .. } = import "minimal.ncl" in
22
let base = import "../base/build.ncl" in
33
let bash = import "../bash/build.ncl" in
44
let ghc-bootstrap = import "../ghc-bootstrap/build.ncl" in
@@ -34,6 +34,7 @@ let version = "3.5.4.2" in
3434

3535
outputs = {
3636
alex = { glob = "usr/bin/alex" } | OutputBin,
37+
data = { glob = "usr/share/**" } | OutputData,
3738
},
3839

3940
attrs = {

packages/happy/build.ncl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
let { BuildSpec, Local, OutputBin, Source, .. } = import "minimal.ncl" in
1+
let { BuildSpec, Local, OutputBin, OutputData, Source, .. } = import "minimal.ncl" in
22
let base = import "../base/build.ncl" in
33
let bash = import "../bash/build.ncl" in
44
let ghc-bootstrap = import "../ghc-bootstrap/build.ncl" in
@@ -34,6 +34,7 @@ let version = "1.20.1.1" in
3434

3535
outputs = {
3636
happy = { glob = "usr/bin/happy" } | OutputBin,
37+
data = { glob = "usr/share/**" } | OutputData,
3738
},
3839

3940
attrs = {
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
let { Attrs, BuildSpec, Local, Needs, OutputBin, OutputLib, Source, Test, .. } = import "minimal.ncl" in
2+
3+
let alex = import "../alex/build.ncl" in
4+
let base = import "../base/build.ncl" in
5+
let cabal = import "../cabal/build.ncl" in
6+
let ghc = import "../ghc/build.ncl" in
7+
let glibc = import "../glibc/build.ncl" in
8+
let gmp = import "../gmp/build.ncl" in
9+
let happy = import "../happy/build.ncl" in
10+
let libffi = import "../libffi/build.ncl" in
11+
let zlib = import "../zlib/build.ncl" in
12+
13+
let version = "2.14.0.0" in
14+
15+
{
16+
name = "haskell-language-server",
17+
18+
build_deps = [
19+
{ file = "build.sh" } | Local,
20+
{
21+
url = "https://github.com/haskell/haskell-language-server/archive/refs/tags/%{version}.tar.gz",
22+
sha256 = "02fdd2ea8048cddce0872f78fcc4ba15558f751333c97d24b9abadac8ee27dcb",
23+
extract = true,
24+
strip_prefix = "haskell-language-server-%{version}",
25+
} | Source,
26+
base,
27+
ghc,
28+
cabal,
29+
alex,
30+
happy,
31+
],
32+
33+
runtime_deps = [glibc, gmp, libffi, zlib],
34+
35+
needs =
36+
{
37+
dns = {},
38+
internet = {},
39+
} | Needs,
40+
41+
cmd = "./build.sh",
42+
43+
build_args = {
44+
include version,
45+
},
46+
47+
outputs = {
48+
hls = { glob = "usr/bin/haskell-language-server" } | OutputBin,
49+
libs = { glob = "usr/lib/*.so*" } | OutputLib,
50+
},
51+
52+
attrs =
53+
{
54+
upstream_version = version,
55+
source_provenance = {
56+
category = 'GithubRepo,
57+
owner = "haskell",
58+
repo = "haskell-language-server",
59+
},
60+
build_cost_multiple = 4,
61+
} | Attrs,
62+
63+
tests = {
64+
version_check =
65+
{
66+
class = 'Standalone,
67+
test_deps = [],
68+
cmds = [["haskell-language-server", "--version"]],
69+
} | Test,
70+
},
71+
} | BuildSpec
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
#!/bin/bash
2+
set -euo pipefail
3+
4+
# The source tarball is already extracted with strip_prefix, so we're in the source root
5+
6+
# cabal needs a writable HOME for its config, package index, and build store.
7+
export CABAL_DIR="$PWD/.cabal-home"
8+
mkdir -p "$CABAL_DIR"
9+
10+
# Build HLS for the GHC version available in the sandbox
11+
export GHC="$(command -v ghc)"
12+
export CABAL="$(command -v cabal)"
13+
14+
# GHC's shared libraries (libHSrts, etc.) aren't on the default library search
15+
# path. The system alex/happy (from build_deps) link against GHC's shared libs
16+
# and need them to run. We use --with-alex/--with-happy to point cabal at the
17+
# pre-packaged versions instead of letting cabal download and build its own
18+
# from Hackage — those packages already ship their template files as a data
19+
# output, so they work with any GHC version.
20+
GHC_LIBDIR="$(ghc --print-libdir)"
21+
export LD_LIBRARY_PATH="${GHC_LIBDIR}:${LD_LIBRARY_PATH:-}"
22+
23+
# Update cabal package index
24+
cabal update
25+
26+
# Build HLS with the available GHC version. The cabal.project ships with
27+
# tests:True and benchmarks:True, which makes the solver consider test/benchmark
28+
# deps for every transitive dependency — pass --disable-tests/--disable-benchmarks
29+
# to build only the executable.
30+
set +e
31+
cabal build \
32+
--disable-tests \
33+
--disable-benchmarks \
34+
--with-compiler="$(command -v ghc)" \
35+
--with-alex="$(command -v alex)" \
36+
--with-happy="$(command -v happy)" \
37+
--jobs="$(nproc)" \
38+
-v1 \
39+
exe:haskell-language-server 2>&1 | tee /tmp/hls-build.log
40+
rc=${PIPESTATUS[0]}
41+
set -e
42+
if [ "$rc" -ne 0 ]; then
43+
echo "===== cabal build failed (rc=$rc) — real error: ====="
44+
grep -iE "\.hs:[0-9]+:[0-9]+: error|error:\s*\[GHC|error:\s*\[Cabal|undefined reference|cannot find -l|panic|internal error|cannot satisfy|conflict|could not be determined|Failed to build" /tmp/hls-build.log | tail -25 \
45+
|| echo "(no error text captured)"
46+
tail -25 /tmp/hls-build.log
47+
exit 1
48+
fi
49+
50+
# Install to OUTPUT_DIR
51+
mkdir -p "$OUTPUT_DIR"/usr/bin
52+
mkdir -p "$OUTPUT_DIR"/usr/lib
53+
54+
# Find and copy the built binary from cabal's build directory
55+
HLS_BIN=$(cabal list-bin exe:haskell-language-server)
56+
cp "$HLS_BIN" "$OUTPUT_DIR"/usr/bin/
57+
58+
# Copy all shared Haskell libraries the binary depends on
59+
for lib in $(ldd "$HLS_BIN" | grep '\.so' | awk '{print $3}'); do
60+
if [ -n "$lib" ] && [ -f "$lib" ]; then
61+
libname="${lib##*/}"
62+
# Only copy Haskell-specific libraries (libHS*) to avoid bundling standard system glibc/loader libraries
63+
if [[ "$libname" == libHS* ]]; then
64+
cp "$lib" "$OUTPUT_DIR"/usr/lib/
65+
fi
66+
fi
67+
done

0 commit comments

Comments
 (0)