hie-bios
hie-bios
is the way to specify how
hie
and
ghcide
sets up a GHC API session.
Given a Haskell project that is managed by Stack, Cabal, or other package tools,
hie
needs to know the full set of flags to pass to GHC in order to build the
project. hie-bios
satisfies this need.
Its design is motivated by the guiding principle:
It is the responsibility of the build tool to describe the environment
which a package should be built in.
Using this principle, it is possible
to easily support a wide range of tools including cabal-install
, stack
,
rules_haskell
, hadrian
and obelisk
without major contortions.
hie-bios
does not depend on the Cabal
library nor does not
read any complicated build products and so on.
How does a tool specify a session? A session is fully specified by a set of
standard GHC flags. Most tools already produce this information if they support
a repl
command. Launching a repl is achieved by calling ghci
with the
right flags to specify the package database. hie-bios
needs a way to get
these flags and then it can set up GHC API session correctly.
Futher it means that any failure to set up the API session is the responsibility
of the build tool. It is up to them to provide the correct information if they
want the tool to work correctly.
Explicit Configuration
The user can place a hie.yaml
file in the root of the workspace which
describes how to setup the environment. For example, to explicitly state
that you want to use stack
then the configuration file would look like:
cradle: {stack: {component: "haskell-ide-engine:lib" }}
While the component is optional, this is recommended to make sure the correct
component is loaded.
To use cabal
, the explicit configuration looks similar.
Note that cabal
and stack
have different way of specifying their
components.
cradle: {cabal: {component: "lib:haskell-ide-engine"}}
Or you can explicitly state the program which should be used to collect
the options by supplying the path to the program. It is interpreted
relative to the current working directory if it is not an absolute path.
The bios program should consult the HIE_BIOS_OUTPUT
env var and write a list of
options to this file separated by newlines. Once the program finishes running hie-bios
reads this file and uses the arguments to set up the GHC session. This is how GHC's
build system is able to support hie-bios
.
cradle: {bios: {program: ".hie-bios"}}
The direct
cradle allows you to specify exactly the GHC options that should be used to load
a project. This is good for debugging but not a very good approach in general as the set of options
will quickly get out of sync with a cabal file.
cradle: {direct: { arguments: [arg1, arg2]} }
The none
cradle says that the IDE shouldn't even try to load the project. It
is most useful when combined with the multi-cradle which is specified in the next section.
cradle: {none: }
Multi-Cradle
For a multi-component project you can use the multi-cradle to specify how each
subdirectory of the project should be handled by the IDE.
The multi-cradle is a list of relative paths and cradle configurations.
The path is relative to the configuration file and specifies the scope of
the cradle. For example, this configuration specificies that files in the
src
subdirectory should be handled with the lib:hie-bios
component and
files in the test
directory using the test
component.
cradle:
multi:
- path: "./src"
config: { cradle: {cabal: {component: "lib:hie-bios"}} }
- path: "./test"
config: { cradle: {cabal: {component: "test"}} }
If a file matches multiple prefixes, the most specific one is chosen.
Once a prefix is matched, the selected cradle is used to find the options. This
is usually a specific cradle such as cabal
or stack
but it could be another
multi-cradle, in which case, matching works in exactly the same way until a
specific cradle is chosen.
This cradle type is experimental and may not be supported correctly by
some libraries which use hie-bios
. It requires some additional care to
correctly manage multiple components.
Note: Remember you can use the multi-cradle to declare that certain directories
shouldn't be loaded by an IDE, in conjunction with the none
cradle.
cradle:
multi:
- path: "./src"
config: { cradle: {cabal: {component: "lib:hie-bios"}} }
- path: "./test"
config: { cradle: {cabal: {component: "test"}} }
- path: "./test/test-files"
config: { cradle: { none: } }
For cabal and stack projects there is a shorthand to specify how to load each component.
cradle:
cabal:
- path: "./src"
component: "lib:hie-bios"
- path: "./test"
component: "test:bios-tests"
cradle:
stack:
- path: "./src"
component: "hie-bios:lib"
- path: "./test"
component: "hie-bios:test:bios-tests"
Remember you can combine this shorthand with more complicated configuration
as well.
cradle:
multi:
- path: "./test/testdata"
config: { cradle: { none: } }
- path: "./"
config: { cradle: { cabal:
[ { path: "./src", component: "lib:hie-bios" }
, { path: "./tests", component: "parser-tests" } ] } }
Cradle Dependencies
Sometimes it is necessary to reload a component, for example when a package
dependency is added to the project. Each type of cradle defines a list of
files that might cause an existing cradle to no longer provide accurate
diagnostics if changed. These are expected to be relative to the root of
the cradle.
This makes it possible to watch for changes to these files and reload the
cradle appropiately.
However, if there are files that are not covered by
the cradle dependency resolution, you can add these files explicitly to
hie.yaml
.
These files are not required to actually exist, since it can be useful
to know when these files are created, e.g. if there was no cabal.project
in the project before and now there is, it might change how a file in the
project is compiled.
Here's an example of how you would add cradle dependencies that may not be covered
by the cabal
cradle.
cradle:
cabal:
component: "lib:hie-bios"
dependencies:
- package.yaml
- shell.nix
- default.nix
For the Bios
cradle type, there is an optional field to specify a program
to obtain cradle dependencies from:
cradle:
bios:
program: ./flags.sh
dependency-program: ./dependency.sh
The program ./dependency.sh
is executed with no paramaters and it is
expected to output on stdout on each line exactly one filepath relative
to the root of the cradle, not relative to the location of the program.
Configuration specification
The complete configuration is a subset of
cradle:
cabal:
component: "optional component name"
stack:
component: "optional component name"
bios:
program: "program to run"
dependency-program: "optional program to run"
direct:
arguments: ["list","of","ghc","arguments"]
none:
multi: - path: ./
config: { cradle: ... }
dependencies:
- someDep
Testing your configuration
The provided hie-bios
executable is provided to test your configuration.
The flags
command will print out the options that hie-bios
thinks you will need to load a file.
hie-bios flags exe/Main.hs
The check
command will try to use these flags to load the module into the GHC API.
hie-bios check exe/Main.hs
Implicit Configuration
There are several built in modes which captures most common Haskell development
scenarios. If no hie.yaml
configuration file is found then an implicit
configuration is searched for. It is strongly recommended to just explicitly
configure your project.
Priority
The targets are searched for in following order.
- A specific
hie-bios
file.
- A
stack
project
- A
cabal
project
- The direct cradle which has no specific options.
cabal-install
The workspace root is the first folder containing a cabal.project
file.
The arguments are collected by running cabal v2-repl
.
If cabal v2-repl
fails, then the user needs to configure the correct
target to use by writing a hie.yaml
file.
stack
The workspace root is the first folder containing a stack.yaml
file.
The arguments are collected by executing stack repl
.
bios
The most general form is the bios
mode which allows a user to specify themselves
which flags to provide.
In this mode, an executable file called .hie-bios
is placed in the root
of the workspace directory. The script takes one argument, the filepath
to the current file we want to load into the session. The script returns
a list of GHC arguments separated by newlines which will setup the correct session.
A good guiding specification for this file is that the following command
should work for any file in your project.
ghci $(./.hie-bios /path/to/foo.hs | tr '\n' ' ') /path/to/foo.hs
This is useful if you are designing a new build system or the other modes
fail to setup the correct session for some reason. For example, this is
how hadrian (GHC's build system) is integrated into hie-bios
.
Supporting Bazel and Obelisk
In previous versions of hie-bios
there was also support for projects using rules_haskell
and obelisk
.
This was removed in the 0.3 release as they were unused and broken. There is no conceptual barrier to adding
back support but it requires a user of these two approaches to maintain them.