hrepl
is a tool that lets you interactively develop Haskell code using
Bazel. It uses Bazel to compile your code's
dependencies, and then loads your modules into an interpreter.
This is not an officially supported Google product.
To use hrepl
, first cd
into this repository, build the //hrepl
target and
save the resulting binary somewhere convenient:
$ bazel build //hrepl
$ cp --no-preserve=mode bazel-bin/hrepl/hrepl ~/.local/bin
$ chmod +x ~/.local/bin/hrepl
You should also use a recent enough verson of rules_haskell
in your
project's WORKSPACE
file. (See this repository's WORKSPACE
file for an
example.)
Then, to load one or more targets in the interpreter, run hrepl
within
your own Bazel project. You may specify the Bazel label(s) of any Haskell libraries,
binaries or tests. For example, in the rules_haskell
repository itself:
$ hrepl tests/binary-with-lib:lib
...
*Lib Lib>
Or, within a subdirectory and with multiple targets:
$ cd tests/binary-with-lib
$ hrepl :lib :binary-with-lib
*Lib Lib Main>
You may also specify individual source files, which will cause hrepl to load targets that declare those files in their srcs. (If there is more than one possibility, it will choose arbitrarily.)
$ hrepl tests/binary-with-lib/Lib.hs
*Lib Lib>
After you modify the interpreted module(s), the :reload
command will pick up
any changes. This approach is much faster than rebuilding with Bazel each time.
For more information about hrepl
's command-line flags, run hrepl --help
.
By default, hrepl
will compile any dependencies of your target(s) with Bazel
before starting the interpreter. You may load those dependencies with :module
or import
. For example:
$ hrepl //some:library # depends on the "split" package
Prelude Library> import Data.List.Split
Prelude Library Data.List.Split>
However, since those modules are compiled, the interpreter will not be aware of
the source files of those dependencies, and will not pick up changes to them on
:reload
. Instead, you will need to :quit
and restart gghci
. The same is
true for changes to BUILD
and .bzl
files that affect your targets.
Note: hrepl
will not let you load (compiled) modules from transitive dependencies
automatically. This behavior is similar to the build rules, which only expose
modules from targets listed directly in their deps
. To expose a transitive
dependency in the interpreter, pass --package //label/of:dep
.
Alternately, you can tell hrepl
to interpret (not compile) certain
dependencies. The --interpret-deps=PACKAGE
flag specifies any dependencies
that are under the given PACKAGE
(either directly, or as a subpackage). For
example:
$ hrepl //some/project:target --interpret-deps=//some/project
That will load not just :target
into the interpreter, but also any source
files from dependencies of :target
that are in some/project/BUILD
or
any other BUILD
file in a subdirectory of some/project
.
You may pass the flag more than once to combine the dependencies from different subdirectories.
Warning: hrepl
will combine the compiler_flags
attributes of interpreted targets
into a single list, and apply all of them to each source file it loads. If two
targets have conflicting compiler_flags
, for example enabling and disabling the same
GHC extension, it may not be possible to interpret both of them at once.
You may load zero or more Bazel targets in the interpeter at once. For example, to load two targets:
$ hrepl //your:target1 //another:target2
Prelude Target1 Target2>
hrepl
will also interpret (i.e., not compile) any "intermediate" targets. For
example, suppose that :target1
depends on :dep
and :dep
depends on
:target2
. Then hrepl
will interpret :dep
as well, and :reload
will pick
up any changes to :dep
as well as to :target1
and :target2
. However,
hrepl
will not expose the definitions in :dep
by default. If you want to use
them, either specify those targets on the command-line or call import
. For
example:
$ hrepl //your:target1 //another:target2
Prelude Target1 Target2> import Dep
Prelude Target1 Target2 Dep>
Alternately, you may tell hrepl
to compile an unrelated target with the
--package
flag. For example:
$ hrepl //your/haskell:target --package @stackage//:split
Prelude Target>
In that case, @stackage//:split
will be compiled and available for
import
in the interpreter:
Prelude Target> import Data.List.Split
Prelude Target Data.List.Split>
Similar to any dependencies of :target
, it won't be reloaded unless you
manually :quit
and restart the interpreter.
You may also use this flag to expose a dependency of a target without also compiling it.
hrepl
supports forwarding flags to its subprocesses in several different ways.
You may pass compiler flags directly to hrepl
. For example:
$ hrepl -XPackageImports -freverse-errors //some:target
To pass RTS options to GHC, use the --with-rtsopts
flag, which takes a
space-separated list of flags. For example:
$ hrepl --with-rtsopts='-t -S' //some:target
does the equivalent of ghc +RTS -t -S -RTS
.
You can use --bazel-args=--some-bazel-params
to make hrepl
pass certain
flags in each call to bazel
.
--bazel-args
takes a space-separated list of arguments. If it's specified
multiple times, the values will accumulate. For example, --bazel-args='-c opt'
is equivalent to --bazel-args=-c --bazel-args=-opt
. As a special shortcut,
hrepl
supports directly passing the Bazel -c
flag to it.
For example:
$ hrepl --bazel-args='-c opt' //your/haskell:library
$ hrepl -c opt //your/haskell:library`