/hie-bios

Primary LanguageHaskellBSD 3-Clause "New" or "Revised" LicenseBSD-3-Clause

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: [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.

  1. A specific hie-bios file.
  2. A stack project
  3. A cabal project
  4. 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.