345 lines
11 KiB
Markdown
345 lines
11 KiB
Markdown
|
<p align="left"><img src="logo/horizontal.png" alt="rules_haskell" height="100px"></p>
|
|||
|
|
|||
|
# Haskell rules for [Bazel][bazel]
|
|||
|
|
|||
|
[](https://circleci.com/gh/tweag/rules_haskell)
|
|||
|
[](https://dev.azure.com/tweag/rules_haskell/_build/latest?definitionId=1?branchName=master)
|
|||
|
|
|||
|
Bazel automates building and testing software. It scales to very large
|
|||
|
multi-language projects. This project extends Bazel with build rules
|
|||
|
for Haskell. Get started building your own project using these rules
|
|||
|
wih the [setup script below](#setup).
|
|||
|
|
|||
|
[bazel]: https://bazel.build/
|
|||
|
[bazel-getting-started]: https://docs.bazel.build/versions/master/getting-started.html
|
|||
|
[bazel-cli]: https://docs.bazel.build/versions/master/command-line-reference.html
|
|||
|
[external-repositories]: https://docs.bazel.build/versions/master/external.html
|
|||
|
[nix]: https://nixos.org/nix
|
|||
|
|
|||
|
## Rule summary
|
|||
|
|
|||
|
The full reference documentation for rules is at https://haskell.build.
|
|||
|
|
|||
|
## Setup
|
|||
|
|
|||
|
You'll need [Bazel >= 0.24][bazel-getting-started] installed.
|
|||
|
|
|||
|
### The easy way
|
|||
|
|
|||
|
In a fresh directory, run:
|
|||
|
|
|||
|
```console
|
|||
|
$ curl https://haskell.build/start | sh
|
|||
|
```
|
|||
|
|
|||
|
This will generate initial `WORKSPACE` and `BUILD` files for you. See the
|
|||
|
[examples](./tests) and the [API reference](#Rules) below to adapt these for
|
|||
|
you project. Then,
|
|||
|
|
|||
|
```console
|
|||
|
$ bazel build //... # Build all targets
|
|||
|
$ bazel test //... # Run all tests
|
|||
|
```
|
|||
|
|
|||
|
You can learn more about Bazel's command line
|
|||
|
syntax [here][bazel-cli]. Common [commands][bazel-cli-commands] are
|
|||
|
`build`, `test`, `run` and `coverage`.
|
|||
|
|
|||
|
### Nixpkgs
|
|||
|
|
|||
|
This rule set supports [Nixpkgs][nixpkgs]. If you are on NixOS, or if
|
|||
|
you are using Nixpkgs on your project, consider passing the following
|
|||
|
argument on the command-line to select a Nixpkgs-based toolchain for
|
|||
|
the build:
|
|||
|
|
|||
|
```
|
|||
|
$ bazel build --host_platform=@io_tweag_rules_haskell//haskell/platforms:linux_x86_64_nixpkgs //...
|
|||
|
```
|
|||
|
|
|||
|
See [below](#saving-common-command-line-flags-to-a-file) to
|
|||
|
permanently set that flag.
|
|||
|
|
|||
|
[bazel-cli-commands]: https://docs.bazel.build/versions/master/command-line-reference.html#commands
|
|||
|
[nixpkgs]: https://nixos.org/nixpkgs/
|
|||
|
|
|||
|
### Doing it manually
|
|||
|
|
|||
|
Add the following to your `WORKSPACE` file, and select a `$VERSION`
|
|||
|
(or even an arbitrary commit hash) accordingly.
|
|||
|
|
|||
|
```bzl
|
|||
|
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
|
|||
|
|
|||
|
http_archive(
|
|||
|
name = "io_tweag_rules_haskell",
|
|||
|
strip_prefix = "rules_haskell-$VERSION",
|
|||
|
urls = ["https://github.com/tweag/rules_haskell/archive/v$VERSION.tar.gz"],
|
|||
|
)
|
|||
|
|
|||
|
load(
|
|||
|
"@io_tweag_rules_haskell//haskell:haskell.bzl",
|
|||
|
"haskell_repositories",
|
|||
|
"haskell_register_toolchains",
|
|||
|
)
|
|||
|
|
|||
|
haskell_repositories()
|
|||
|
|
|||
|
haskell_register_toolchains()
|
|||
|
```
|
|||
|
|
|||
|
You will then need to write one `BUILD` file for each "package" you
|
|||
|
want to define. See below for examples.
|
|||
|
|
|||
|
## Tutorial and Examples
|
|||
|
|
|||
|
We provide a [tutorial for writing your first rules][tutorial].
|
|||
|
The corresponding source code is in [./tutorial](./tutorial).
|
|||
|
|
|||
|
A collection of example rules is in [./examples](./examples).
|
|||
|
|
|||
|
[tutorial]: https://rules-haskell.readthedocs.io/en/latest/
|
|||
|
|
|||
|
## Rules
|
|||
|
|
|||
|
See https://api.haskell.build for the reference documentation on provided
|
|||
|
rules. Using [./serve-docs.sh](./serve-docs.sh), you can also view
|
|||
|
this documentation locally.
|
|||
|
|
|||
|
## Language interop
|
|||
|
|
|||
|
We may be supporting interop with other languages in one way or
|
|||
|
another. Please see languages listed below about how.
|
|||
|
|
|||
|
### C/C++
|
|||
|
|
|||
|
C/C++ libraries can be specified as dependencies. Exporting Haskell libraries
|
|||
|
as C/C++ dependencies currently requires the `cc_haskell_import` rule. This is
|
|||
|
a temporary workaround to Bazel limitations.
|
|||
|
|
|||
|
### Java
|
|||
|
|
|||
|
You can supply `java_*` rule targets in `deps` of
|
|||
|
[haskell_binary](#haskell_binary) and
|
|||
|
[haskell_library](#haskell_library). This will make jars produced by
|
|||
|
those dependencies available during Haskell source compilation phase
|
|||
|
(i.e. not during linking &c. but it's subject to change) and set the
|
|||
|
CLASSPATH for that phase as well.
|
|||
|
|
|||
|
## Troubleshooting
|
|||
|
|
|||
|
### No such file or directory
|
|||
|
|
|||
|
If you see error messages complaining about missing `as` (`ld` or indeed
|
|||
|
some other executable):
|
|||
|
|
|||
|
```
|
|||
|
cc: error trying to exec 'as': execvp: No such file or directory
|
|||
|
`cc' failed in phase `Assembler'. (Exit code: 1)
|
|||
|
```
|
|||
|
|
|||
|
It means that your `gcc` cannot find `as` by itself. This happens only on
|
|||
|
certain operating systems which have `gcc` compiled without `--with-as` and
|
|||
|
`--with-ld` flags. We need to make `as` visible manually in that case:
|
|||
|
|
|||
|
```bzl
|
|||
|
# Create a symlink to system executable 'as'
|
|||
|
genrule(
|
|||
|
name = "toolchain_as",
|
|||
|
outs = ["as"],
|
|||
|
cmd = "ln -s /usr/bin/as $@",
|
|||
|
)
|
|||
|
|
|||
|
# Make it visible to rules_haskell rules:
|
|||
|
haskell_toolchain(
|
|||
|
name = "ghc",
|
|||
|
tools = ["@ghc//:bin"],
|
|||
|
version = "8.4.1",
|
|||
|
extra_binaries = [":toolchain_as"], # <----
|
|||
|
)
|
|||
|
```
|
|||
|
|
|||
|
### `__STDC_VERSION__` does not advertise C99 or later
|
|||
|
|
|||
|
If you see an error message like this:
|
|||
|
|
|||
|
```
|
|||
|
/root/.cache/bazel/_bazel_root/b8b1b1d6144a88c698a010767d2217af/external/ghc/lib/ghc-8.4.1/include/Stg.h:29:3: error:
|
|||
|
error: #error __STDC_VERSION__ does not advertise C99 or later
|
|||
|
# error __STDC_VERSION__ does not advertise C99 or later
|
|||
|
^
|
|||
|
|
|
|||
|
29 | # error __STDC_VERSION__ does not advertise C99 or later
|
|||
|
| ^
|
|||
|
```
|
|||
|
|
|||
|
It means that your `gcc` selects incorrect flavor of C by default. We need
|
|||
|
C99 or later, as the error message says, so try this:
|
|||
|
|
|||
|
```bzl
|
|||
|
haskell_toolchain(
|
|||
|
name = "ghc",
|
|||
|
tools = ["@ghc//:bin"],
|
|||
|
version = "8.4.1",
|
|||
|
compiler_flags = ["-optc-std=c99"], # <----
|
|||
|
)
|
|||
|
```
|
|||
|
|
|||
|
### `bazel` fails because some executable cannot be found
|
|||
|
|
|||
|
Make sure you run your build in a pure nix shell
|
|||
|
(`nix-shell --pure shell.nix`). If it still doesn’t build,
|
|||
|
it is likely a bug.
|
|||
|
|
|||
|
### A Haskell dependency fails with strange error messages
|
|||
|
|
|||
|
If you get cabal error messages the likes of:
|
|||
|
|
|||
|
```
|
|||
|
CallStack (from HasCallStack):
|
|||
|
dieNoWrap, called at libraries/Cabal/Cabal/Distribution/Utils/LogProgress.hs:61:9 in Cabal-2.0.1.0:Distribution.Utils.LogProgress
|
|||
|
Error:
|
|||
|
The following packages are broken because other packages they depend on are missing. These broken packages must be rebuilt before they can be used.
|
|||
|
installed package lens-labels-0.2.0.1 is broken due to missing package profunctors-5.2.2-HzcVdviprlKb7Ap1woZu4, tagged-0.8.5-HviTdonkllN1ZD6he1Zn8I
|
|||
|
```
|
|||
|
|
|||
|
you’ve most likely hit GHC’s
|
|||
|
[infamous non-deterministic library ID bug](https://nixos.org/nixpkgs/manual/#how-to-recover-from-ghcs-infamous-non-deterministic-library-id-bug).
|
|||
|
|
|||
|
### Warning about home modules during non-sandboxed builds
|
|||
|
|
|||
|
Say you have a folder that mixes source files for two different
|
|||
|
libraries or for a library and an executable. If you build with
|
|||
|
sandboxing turned off, it is possible that GHC will use the source
|
|||
|
files for one library during the build of the other. The danger in
|
|||
|
this situation is that because GHC used inputs that Bazel didn't know
|
|||
|
about, incremental rebuilds might not be correct. This is why you get
|
|||
|
a warning of the following form if this happens:
|
|||
|
|
|||
|
```
|
|||
|
<no location info>: warning: [-Wmissing-home-modules]
|
|||
|
Modules are not listed in command line but needed for compilation: Foo
|
|||
|
```
|
|||
|
|
|||
|
Turning sandboxing on (this is Bazel's default on Linux and macOS)
|
|||
|
protects against this problem. If sandboxing is not an option, simply
|
|||
|
put the source files for each target in a separate directory (you can
|
|||
|
still use a single `BUILD` file to define all targets).
|
|||
|
|
|||
|
## For `rules_haskell` developers
|
|||
|
|
|||
|
### Saving common command-line flags to a file
|
|||
|
|
|||
|
If you find yourself constantly passing the same flags on the
|
|||
|
command-line for certain commands (such as `--host_platform` or
|
|||
|
`--compiler`), you can augment the [`.bazelrc`](./.bazelrc) file in
|
|||
|
this repository with a `.bazelrc.local` file. This file is ignored by
|
|||
|
Git.
|
|||
|
|
|||
|
### Reference a local checkout of `rules_haskell`
|
|||
|
|
|||
|
When you develop on `rules_haskell`, you usually do it in the context
|
|||
|
of a different project that has `rules_haskell` as a `WORKSPACE`
|
|||
|
dependency, like so:
|
|||
|
|
|||
|
```
|
|||
|
http_archive(
|
|||
|
name = "io_tweag_rules_haskell",
|
|||
|
strip_prefix = "rules_haskell-" + version,
|
|||
|
sha256 = …,
|
|||
|
urls = …,
|
|||
|
)
|
|||
|
```
|
|||
|
|
|||
|
To reference a local checkout instead, use the
|
|||
|
[`--override_repository`][override_repository] command line option:
|
|||
|
|
|||
|
```
|
|||
|
bazel build/test/run/sync \
|
|||
|
--override_repository io_tweag_rules_haskell=/path/to/checkout
|
|||
|
```
|
|||
|
|
|||
|
If you don’t want to type that every time, [temporarily add it to
|
|||
|
`.bazelrc`][bazelrc].
|
|||
|
|
|||
|
[override_repository]: https://docs.bazel.build/versions/master/command-line-reference.html#flag--override_repository
|
|||
|
[local_repository]: https://docs.bazel.build/versions/master/be/workspace.html#local_repository
|
|||
|
[bazelrc]: https://docs.bazel.build/versions/master/best-practices.html#bazelrc
|
|||
|
|
|||
|
### Test Suite
|
|||
|
|
|||
|
To run the test suite for these rules, you'll need [Nix][nix]
|
|||
|
installed. First, from the project’s folder start a pure nix shell:
|
|||
|
|
|||
|
```
|
|||
|
$ nix-shell --pure shell.nix
|
|||
|
```
|
|||
|
|
|||
|
This will make sure that bazel has the exact same environment
|
|||
|
on every development system (`python`, `ghc`, `go`, …).
|
|||
|
|
|||
|
To build and run tests locally, execute:
|
|||
|
|
|||
|
```
|
|||
|
$ bazel test //...
|
|||
|
```
|
|||
|
|
|||
|
Skylark code in this project is formatted according to the output of
|
|||
|
[buildifier]. You can check that the formatting is correct using:
|
|||
|
|
|||
|
```
|
|||
|
$ bazel run //:buildifier
|
|||
|
```
|
|||
|
|
|||
|
If tests fail then run the following to fix the formatting:
|
|||
|
|
|||
|
```
|
|||
|
$ git rebase --exec "bazel run //:buildifier-fix" <first commit>
|
|||
|
```
|
|||
|
|
|||
|
where `<first commit>` is the first commit in your pull request.
|
|||
|
This fixes formatting for each of your commits separately, to keep
|
|||
|
the history clean.
|
|||
|
|
|||
|
[buildifier]: https://github.com/bazelbuild/buildtools/tree/master/buildifier
|
|||
|
|
|||
|
### <a name="nixpkgs-pin" />How to update the nixpkgs pin
|
|||
|
|
|||
|
You have to find a new git commit where all our `shell.nix`
|
|||
|
dependencies are available from the official NixOS Hydra binary cache.
|
|||
|
|
|||
|
At least for `x86-linux` this is guaranteed for the `unstable`
|
|||
|
channels. You can find the `nixpkgs` git commit of current `unstable`
|
|||
|
here:
|
|||
|
|
|||
|
https://nixos.org/channels/nixos-unstable/git-revision
|
|||
|
|
|||
|
That might be too old for your use-case (because all tests have to
|
|||
|
pass for that channel to be updated), so as a fallback there is:
|
|||
|
|
|||
|
https://nixos.org/channels/nixos-unstable-small/git-revision
|
|||
|
|
|||
|
You copy that hash to `url` in
|
|||
|
[`./nixpkgs/default.nix`](./nixpkgs/default.nix). Don’t forget to
|
|||
|
change the `sha256` or it will use the old version. Please update the
|
|||
|
date comment to the date of the `nixpkgs` commit you are pinning to.
|
|||
|
|
|||
|
### CircleCI
|
|||
|
|
|||
|
Pull Requests are checked by CircleCI.
|
|||
|
|
|||
|
If a check fails and you cannot reproduce it locally (e.g. it failed on Darwin
|
|||
|
and you only run Linux), you can [ssh into CircleCI to aid debugging][ci-ssh].
|
|||
|
|
|||
|
[ci-ssh]: https://circleci.com/docs/2.0/ssh-access-jobs/
|
|||
|
|
|||
|
#### “unable to start any build”
|
|||
|
|
|||
|
```
|
|||
|
error: unable to start any build; either increase '--max-jobs' or enable remote builds
|
|||
|
```
|
|||
|
|
|||
|
We set `--builders ""` and `--max-jobs 0` on CI to be sure all
|
|||
|
dependencies are coming from binary caches. You might need to add an
|
|||
|
exception (TODO: where to add exception) or [switch to a different
|
|||
|
nixpkgs pin](#nixpkgs-pin).
|