Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions .ghcide
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#!/usr/bin/env bash
set -euo pipefail
build_ghcide() {
bazel build @ghcide//:ghcide \
--experimental_show_artifacts \
2>&1 \
| awk '
/^>>>/ { print substr($1, 4); next }
{ print $0 > "/dev/stderr" }
'
}
ghcide="$(build_ghcide)"
"$ghcide" "$@"
26 changes: 26 additions & 0 deletions .hie-bios
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#!/usr/bin/env bash
set -euo pipefail
hie_bios_flags() {
bazel build //tests:hie-bios \
--output_groups=hie_bios \
--experimental_show_artifacts \
2>&1 \
| awk '
/^>>>/ {
while ((getline line < substr($1, 4)) > 0) {
print line
}
next
}
{
print $0 > "/dev/stderr"
}
'
# Make warnings non-fatal
echo -Wwarn
}
if [[ -z "${HIE_BIOS_OUTPUT-}" ]]; then
hie_bios_flags
else
hie_bios_flags >"$HIE_BIOS_OUTPUT"
fi
94 changes: 91 additions & 3 deletions WORKSPACE
Original file line number Diff line number Diff line change
Expand Up @@ -120,11 +120,89 @@ stack_snapshot(
# In a separate repo because not all platforms support zlib.
stack_snapshot(
name = "stackage-zlib",
extra_deps = {"zlib": ["@zlib.win//:zlib" if is_windows else "@zlib.dev//:zlib"]},
extra_deps = {"zlib": ["@zlib.dev//:zlib" if is_nix_shell else "@zlib.hs//:zlib"]},
packages = ["zlib"],
snapshot = test_stack_snapshot,
)

stack_snapshot(
name = "stackage_ghcide",
extra_deps = {"zlib": ["@zlib.dev//:zlib" if is_nix_shell else "@zlib.hs//:zlib"]},
haddock = False,
local_snapshot = "//:ghcide-stack-snapshot.yaml",
packages = [
"aeson",
"base",
"base16-bytestring",
"binary",
"bytestring",
"containers",
"cryptohash-sha1",
"data-default",
"deepseq",
"directory",
"extra",
"filepath",
"ghc",
"ghc-check",
"ghc-paths",
"ghcide",
"gitrev",
"hashable",
"haskell-lsp",
"haskell-lsp-types",
"hie-bios",
"hslogger",
"optparse-applicative",
"shake",
"text",
"unordered-containers",
],
)

http_archive(
name = "ghcide",
build_file_content = """
load("@rules_haskell//haskell:cabal.bzl", "haskell_cabal_binary")
haskell_cabal_binary(
name = "ghcide",
srcs = glob(["**"]),
deps = [
"@stackage_ghcide//:hslogger",
"@stackage_ghcide//:aeson",
"@stackage_ghcide//:base",
"@stackage_ghcide//:binary",
"@stackage_ghcide//:base16-bytestring",
"@stackage_ghcide//:bytestring",
"@stackage_ghcide//:containers",
"@stackage_ghcide//:cryptohash-sha1",
"@stackage_ghcide//:data-default",
"@stackage_ghcide//:deepseq",
"@stackage_ghcide//:directory",
"@stackage_ghcide//:extra",
"@stackage_ghcide//:filepath",
"@stackage_ghcide//:ghc-check",
"@stackage_ghcide//:ghc-paths",
"@stackage_ghcide//:ghc",
"@stackage_ghcide//:gitrev",
"@stackage_ghcide//:hashable",
"@stackage_ghcide//:haskell-lsp",
"@stackage_ghcide//:haskell-lsp-types",
"@stackage_ghcide//:hie-bios",
"@stackage_ghcide//:ghcide",
"@stackage_ghcide//:optparse-applicative",
"@stackage_ghcide//:shake",
"@stackage_ghcide//:text",
"@stackage_ghcide//:unordered-containers",
],
visibility = ["//visibility:public"],
)
""",
sha256 = "fa1f0cfb0357e7bfa6c86076493f038e0ea5fcd75c470473f2ede7e32566cd9a",
strip_prefix = "ghcide-0c9a0961abbeef851b4117e6408f15a6d46eb1f1",
urls = ["https://github.com/digital-asset/ghcide/archive/0c9a0961abbeef851b4117e6408f15a6d46eb1f1.tar.gz"],
)

load(
"@io_tweag_rules_nixpkgs//nixpkgs:nixpkgs.bzl",
"nixpkgs_cc_configure",
Expand Down Expand Up @@ -308,8 +386,9 @@ filegroup(
)

http_archive(
name = "zlib.win",
name = "zlib.hs",
build_file_content = """
load("@os_info//:os_info.bzl", "is_darwin")
load("@rules_cc//cc:defs.bzl", "cc_library")
cc_library(
name = "zlib",
Expand All @@ -318,9 +397,18 @@ cc_library(
srcs = [":z"],
hdrs = glob(["*.h"]),
includes = ["."],
linkstatic = 1,
visibility = ["//visibility:public"],
)
cc_library(name = "z", srcs = glob(["*.c"]), hdrs = glob(["*.h"]))
cc_library(
name = "z",
srcs = glob(["*.c"]),
hdrs = glob(["*.h"]),
# Cabal packages depending on dynamic C libraries fail on MacOS
# due to `-rpath` flags being forwarded indiscriminately.
# See https://github.com/tweag/rules_haskell/issues/1317
linkstatic = is_darwin,
)
""",
sha256 = "c3e5e9fdd5004dcb542feda5ee4f0ff0744628baf8ed2dd5d66f8ca1197cb1a1",
strip_prefix = "zlib-1.2.11",
Expand Down
228 changes: 228 additions & 0 deletions docs/haskell-use-cases.rst
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,234 @@ This works for any ``haskell_binary`` or ``haskell_library`` target.
Modules of all libraries will be loaded in interpreted mode and can be
reloaded using the ``:r`` GHCi command when source files change.

Configuring IDE integration with ghcide
---------------------------------------

rules_haskell has preliminary support for IDE integration using `ghcide`_. The
ghcide project provides IDE features for Haskell projects through the Language
Server Protocol. To set this up you can define a `haskell_repl`_ target that
will collect the required compiler flags for your Haskell targets and pass them
to `hie-bios`_ which will then forward them to ghcide.

Let's set this up for the following example project::

haskell_toolchain_library(
name = "base",
)

haskell_library(
name = "library-a",
srcs = ["Lib/A.hs"],
deps = [":base"],
)

haskell_library(
name = "library-b",
srcs = ["Lib/B.hs"],
deps = [":base"],
)

haskell_binary(
name = "binary",
srcs = ["Main.hs"],
deps = [
":base",
":library-a",
":library-b",
],
)

We want to configure ghcide to provide IDE integration for all these three
targets. Start by defining a ``haskell_repl`` target as follows::

haskell_repl(
name = "hie-bios",
collect_data = False,
deps = [
":binary",
# ":library-a",
# ":library-b",
],
)

Note, that ``library-a`` and ``library-b`` do not have to be listed explicitly.
By default haskell_repl will include all transitive dependencies that are not
external dependencies. Refer to the API documentation of `haskell_repl`_ for
details.

We also disable building runtime dependencies using ``collect_data = False`` as
they are not required for an IDE session.

You can test if this provides the expected compiler flags by running the
following Bazel command and taking a look at the generated file::

bazel build //:hie-bios --output_groups=hie_bios

Next, we need to hook this up to `hie-bios`_ using the `bios cradle`_. To that
end, define a small shell script named ``.hie-bios`` that looks as follows::

#!/usr/bin/env bash
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is out-of-sync with the .hie-bios script in this repo.

I guess it's on purpose (to show a simple-but-working script in the docs), but maybe we could export the full-featured script as a bazel target and let the client-side .hie-bios script just be something like

#!/bin/bash

bazel run @rules_haskell//:hie-bios //:hie-bios

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed that was intentional to keep the documentation simple.

but maybe we could export the full-featured script as a bazel target

That's a very nice idea, thanks!

set -euo pipefail
bazel build //:hie-bios --output_groups=hie_bios
cat bazel-bin/hie-bios@hie-bios >"$HIE_BIOS_OUTPUT"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure where to put this, but I ran into some issues with targets building with -Werror because ghcide forcibly sets some warnings (like missing-signatures), so the combination of both might lead to spurious errors. The common way out of this is that most hie bioses seem to filter out -Werror or add -Wwarn, and that's what I also did by added an echo -Wwarn at the end of .hie_bios

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a good point, another way to deal with this is to add -Wwarn to the toolchain's GHCi flags, e.g. https://github.com/digital-asset/daml/blob/a47c734401a70e5b9dd94cd9717a2192bc28c703/WORKSPACE#L382.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nvm, I see that the hie-bios output group doesn't include those. Though maybe it should.

# Make warnings non-fatal
echo -Wwarn >>"$HIE_BIOS_OUTPUT"

Then configure `hie-bios`_ to use this script in the bios cradle with the
following ``hie.yaml`` file::

cradle:
bios:
program: ".hie-bios"

Now the hie-bios cradle is ready to use. The last step is to install ghcide.
Unfortunately, ghcide has to be compiled with the exact same GHC that you're
using to build your project. The easiest way to do this is in this context is
to build it with Bazel as part of your rules_haskell project.

First, define a custom stack snapshot that provides the package versions that
ghcide requires based on `ghcide's stack.yaml`_ file. Let's call it
``ghcide-stack-snapshot.yaml``. Copy the ``resolver`` field and turn the
``extra-deps`` field into a ``packages`` field. Then add another entry to
``packages`` for the ghcide library itself::

# Taken from ghcide's stack.yaml
resolver: nightly-2019-09-21
packages:
# Taken from the extra-deps field.
- haskell-lsp-0.21.0.0
- haskell-lsp-types-0.21.0.0
- lsp-test-0.10.2.0
- hie-bios-0.4.0
- fuzzy-0.1.0.0
- regex-pcre-builtin-0.95.1.1.8.43
- regex-base-0.94.0.0
- regex-tdfa-1.3.1.0
- shake-0.18.5
- parser-combinators-1.2.1
- haddock-library-1.8.0
- tasty-rerun-1.1.17
- ghc-check-0.1.0.3
# Point to the ghcide revision that you would like to use.
- github: digital-asset/ghcide
commit: "39605333c34039241768a1809024c739df3fb2bd"
sha256: "47cca96a6e5031b3872233d5b9ca14d45f9089da3d45a068e1b587989fec4364"

Then define a dedicated ``stack_snapshot`` for ghcide in your ``WORKSPACE``
file. In the ``packages`` attribute we expose all dependencies of the ghcide
executable::

stack_snapshot(
name = "stackage_ghcide",
# The rules_haskell example project shows how to import libz.
# https://github.com/tweag/rules_haskell/blob/123e3817156f9135dfa44dcb5a796c424df1f436/examples/WORKSPACE#L42-L63
extra_deps = {"zlib": ["@zlib.hs"]},
haddock = False,
local_snapshot = "//:ghcide-stack-snapshot.yaml",
packages = [
"hslogger",
"aeson",
"base",
"binary",
"base16-bytestring",
"bytestring",
"containers",
"cryptohash-sha1",
"data-default",
"deepseq",
"directory",
"extra",
"filepath",
"ghc-check",
"ghc-paths",
"ghc",
"gitrev",
"hashable",
"haskell-lsp",
"haskell-lsp-types",
"hie-bios",
"ghcide",
"optparse-applicative",
"shake",
"text",
"unordered-containers",
],
)

Finally, define a ``haskell_cabal_binary`` target for the ghcide executable
itself. (Unfortunately, ``stack_snapshot`` does not support building
executables)::

http_archive(
name = "ghcide",
build_file_content = """
load("@rules_haskell//haskell:cabal.bzl", "haskell_cabal_binary")
haskell_cabal_binary(
name = "ghcide",
srcs = glob(["**"]),
deps = [
# From build-depends field of executable section in ghcide.cabal
"@stackage_ghcide//:hslogger",
"@stackage_ghcide//:aeson",
"@stackage_ghcide//:base",
"@stackage_ghcide//:binary",
"@stackage_ghcide//:base16-bytestring",
"@stackage_ghcide//:bytestring",
"@stackage_ghcide//:containers",
"@stackage_ghcide//:cryptohash-sha1",
"@stackage_ghcide//:data-default",
"@stackage_ghcide//:deepseq",
"@stackage_ghcide//:directory",
"@stackage_ghcide//:extra",
"@stackage_ghcide//:filepath",
"@stackage_ghcide//:ghc-check",
"@stackage_ghcide//:ghc-paths",
"@stackage_ghcide//:ghc",
"@stackage_ghcide//:gitrev",
"@stackage_ghcide//:hashable",
"@stackage_ghcide//:haskell-lsp",
"@stackage_ghcide//:haskell-lsp-types",
"@stackage_ghcide//:hie-bios",
"@stackage_ghcide//:ghcide",
"@stackage_ghcide//:optparse-applicative",
"@stackage_ghcide//:shake",
"@stackage_ghcide//:text",
"@stackage_ghcide//:unordered-containers",
],
visibility = ["//visibility:public"],
)
""",
# Keep these in sync with ghcide-stack-snapshot.yaml
sha256 = "47cca96a6e5031b3872233d5b9ca14d45f9089da3d45a068e1b587989fec4364",
strip_prefix = "ghcide-39605333c34039241768a1809024c739df3fb2bd",
urls = ["https://github.com/digital-asset/ghcide/archive/39605333c34039241768a1809024c739df3fb2bd.tar.gz"],
)

You can test if this worked by building and executing ghcide as follows::

bazel build @ghcide//:ghcide
bazel-bin/external/ghcide/_install/bin/ghcide

Write a small shell script to make it easy to invoke ghcide from your editor::

#!/usr/bin/env bash
set -euo pipefail
bazel build @ghcide//:ghcide
bazel-bin/external/ghcide/_install/bin/ghcide "$@"

And, the last step, configure your editor to use ghcide. The upstream
documentation provides `ghcide setup instructions`_ for a few popular editors.
Be sure to configure your editor to invoke the above wrapper script instead of
another instance of `ghcide`. Also note, that if you are using Nix, then you
may need to invoke ghcide within a ``nix-shell``.

.. _ghcide: https://github.com/digital-asset/ghcide
.. _haskell_repl: https://api.haskell.build/haskell/defs.html#haskell_repl
.. _hie-bios: https://github.com/mpickering/hie-bios
.. _bios cradle: https://github.com/mpickering/hie-bios#bios
.. _ghcide's stack.yaml: https://github.com/digital-asset/ghcide/blob/39605333c34039241768a1809024c739df3fb2bd/stack.yaml
.. _ghcide setup instructions: https://github.com/digital-asset/ghcide#using-with-vs-code

Building Cabal packages
-----------------------

Expand Down
Loading