rules_haskell

# Haskell rules for [Bazel][bazel] [![CircleCI](https://circleci.com/gh/tweag/rules_haskell.svg?style=svg)](https://circleci.com/gh/tweag/rules_haskell) [![Build Status](https://dev.azure.com/tweag/rules_haskell/_apis/build/status/tweag.rules_haskell?branchName=master)](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: ``` : 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" ``` where `` 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 ### 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).