From f723b8b878a3c4a4687b9e337a875500bebb39b1 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Thu, 4 Jul 2019 11:18:12 +0100 Subject: feat(third_party/bazel): Check in rules_haskell from Tweag --- .../bazel/rules_haskell/docs/haskell-use-cases.rst | 283 +++++++++++++++++++++ 1 file changed, 283 insertions(+) create mode 100644 third_party/bazel/rules_haskell/docs/haskell-use-cases.rst (limited to 'third_party/bazel/rules_haskell/docs/haskell-use-cases.rst') diff --git a/third_party/bazel/rules_haskell/docs/haskell-use-cases.rst b/third_party/bazel/rules_haskell/docs/haskell-use-cases.rst new file mode 100644 index 000000000000..a8c4340cf70f --- /dev/null +++ b/third_party/bazel/rules_haskell/docs/haskell-use-cases.rst @@ -0,0 +1,283 @@ +.. _use-cases: + +Common Haskell Build Use Cases +============================== + +Picking a compiler +------------------ + +Unlike Bazel's native C++ rules, rules_haskell does not auto-detect +a Haskell compiler toolchain from the environment. This is by design. +We require that you declare a compiler to use in your ``WORKSPACE`` +file. + +There are two common sources for a compiler. One is to use the +official binary distributions from `haskell.org`_. This is done using +the `ghc_bindist`_ rule. + +The compiler can also be pulled from Nixpkgs_, a set of package +definitions for the `Nix package manager`_. Pulling the compiler from +Nixpkgs makes the build more hermetic, because the transitive closure +of the compiler and all its dependencies is precisely defined in the +``WORKSPACE`` file. Use `rules_nixpkgs`_ to do so (where ``X.Y.Z`` +stands for any recent release):: + + load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") + + http_archive( + name = "io_tweag_rules_nixpkgs", + strip_prefix = "rules_nixpkgs-X.Y.Z", + urls = ["https://github.com/tweag/rules_nixpkgs/archive/vX.Y.Z.tar.gz"], + ) + + load( + "@io_tweag_rules_nixpkgs//nixpkgs:nixpkgs.bzl", + "nixpkgs_git_repository", + "nixpkgs_package" + ) + + nixpkgs_git_repository( + name = "nixpkgs", + revision = "18.09", # Any tag or commit hash + ) + + nixpkgs_package( + name = "ghc", + repositories = { "nixpkgs": "@nixpkgs//:default.nix" } + attribute_path = "haskell.compiler.ghc843", # Any compiler version + build_file = "@io_tweag_rules_haskell//haskell:ghc.BUILD", + ) + + register_toolchains("//:ghc") + +This workspace description specifies which Nixpkgs version to use, +then exposes a Nixpkgs package containing the GHC compiler. The +description assumes that there exists a ``BUILD`` file at the root of +the repository that includes the following:: + + haskell_toolchain( + name = "ghc", + # Versions here and in WORKSPACE must match. + version = "8.4.3", + # Use binaries from @ghc//:bin to define //:ghc toolchain. + tools = ["@ghc//:bin"], + ) + +.. _Bazel+Nix blog post: https://www.tweag.io/posts/2018-03-15-bazel-nix.html +.. _Nix package manager: https://nixos.org/nix +.. _Nixpkgs: https://nixos.org/nixpkgs/manual/ +.. _ghc_bindist: http://api.haskell.build/haskell/ghc_bindist.html#ghc_bindist +.. _haskell.org: https://haskell.org +.. _haskell_binary: http://api.haskell.build/haskell/haskell.html#haskell_binary +.. _haskell_library: http://api.haskell.build/haskell/haskell.html#haskell_library +.. _rules_nixpkgs: https://github.com/tweag/rules_nixpkgs + +Loading targets in a REPL +------------------------- + +Rebuilds are currently not incremental *within* a binary or library +target (rebuilds are incremental across targets of course). Any change +in any source file will trigger a rebuild of all source files listed +in a target. In Bazel, it is conventional to decompose libraries into +small units. In this way, libraries require less work to rebuild. +Still, for interactive development full incrementality and fast +recompilation times are crucial for a good developer experience. We +recommend making all development REPL-driven for fast feedback when +source files change. + +Every `haskell_binary`_ and every `haskell_library`_ target has an +optional executable output that can be run to drop you into an +interactive session. If the target's name is ``foo``, then the REPL +output is called ``foo@repl``. + +Consider the following binary target:: + + haskell_binary( + name = "hello", + srcs = ["Main.hs", "Other.hs"], + deps = ["//lib:some_lib"], + ) + +The target above also implicitly defines ``hello@repl``. You can call +the REPL like this (requires Bazel 0.15 or later):: + + $ bazel run //:hello@repl + +This works for any ``haskell_binary`` or ``haskell_library`` target. +Modules of all libraries will be loaded in interpreted mode and can be +reloaded using the ``:r`` GHCi command when source files change. + +Building code with Hackage dependencies (using Nix) +--------------------------------------------------- + +Each Haskell library or binary needs a simple build description to +tell Bazel what source files to use and what the dependencies are, if +any. Packages on Hackage don't usually ship with `BUILD.bazel` files. +So if your code depends on them, you either need to write a build +description for each package, generate one (see next section), or +decide not to use Bazel to build packages published on Hackage. This +section documents one way to do the latter. + +Nix is a package manager. The set of package definitions is called +Nixpkgs. This repository contains definitions for most actively +maintained Cabal packages published on Hackage. Where these packages +depend on system libraries like zlib, ncurses or libpng, Nixpkgs also +contains package descriptions for those, and declares those as +dependencies of the Cabal packages. Since these definitions already +exist, we can reuse them instead of rewriting these definitions as +build definitions in Bazel. See the `Bazel+Nix blog post`_ for a more +detailed rationale. + +To use Nixpkgs in Bazel, we need `rules_nixpkgs`_. See `Picking +a compiler`_ for how to import Nixpkgs rules into your workspace and +how to use a compiler from Nixpkgs. To use Cabal packages from +Nixpkgs, replace the compiler definition with the following:: + + nixpkgs_package( + name = "ghc", + repositories = { "nixpkgs": "@nixpkgs//:default.nix" }, + nix_file = "//:ghc.nix", + build_file = "@io_tweag_rules_haskell//haskell:ghc.BUILD", + ) + +This definition assumes a ``ghc.nix`` file at the root of the +repository. In this file, you can use the Nix expression language to +construct a compiler with all the packages you depend on in scope:: + + with (import {}); + + haskellPackages.ghcWithPackages (p: with p; [ + containers + lens + text + ]) + +Each package mentioned in ``ghc.nix`` can then be imported using +`haskell_toolchain_library`_ in ``BUILD`` files. + +.. _haskell_toolchain_library: http://api.haskell.build/haskell/haskell.html#haskell_toolchain_library + +Building code with Hackage dependencies (using Hazel) +----------------------------------------------------- + +.. todo:: + + Explain how to use Hazel instead of Nix + +Generating API documentation +---------------------------- + +The `haskell_doc`_ rule can be used to build API documentation for +a given library (using Haddock). Building a target called +``//my/pkg:mylib_docs`` would make the documentation available at +``bazel-bin/my/pkg/mylib_docs/index/index.html``. + +Alternatively, you can use the +``@io_tweag_rules_haskell//haskell:haskell.bzl%haskell_doc_aspect`` +aspect to ask Bazel from the command-line to build documentation for +any given target (or indeed all targets), like in the following: + +.. code-block:: console + + $ bazel build //my/pkg:mylib \ + --aspects @io_tweag_rules_haskell//haskell:haskell.bzl%haskell_doc_aspect + +.. _haskell_doc: http://api.haskell.build/haskell/haddock.html#haskell_doc + +Linting your code +----------------- + +The `haskell_lint`_ rule does not build code but runs the GHC +typechecker on all listed dependencies. Warnings are treated as +errors. + +Alternatively, you can directly check a target using + +.. code-block:: console + + $ bazel build //my/haskell:target \ + --aspects @io_tweag_rules_haskell//haskell:haskell.bzl%haskell_lint_aspect + +.. _haskell_lint: http://api.haskell.build/haskell/lint.html#haskell_lint + +Checking code coverage +---------------------- + +"Code coverage" is the name given to metrics that describe how much source +code is covered by a given test suite. A specific code coverage metric +implemented here is expression coverage, or the number of expressions in +the source code that are explored when the tests are run. + +Haskell's ``ghc`` compiler has built-in support for code coverage analysis, +through the hpc_ tool. The Haskell rules allow the use of this tool to analyse +``haskell_library`` coverage by ``haskell_test`` rules. To do so, you have a +few options. You can add +``expected_covered_expressions_percentage=`` to +the attributes of a ``haskell_test``, and if the expression coverage percentage +is lower than this amount, the test will fail. Alternatively, you can add +``expected_uncovered_expression_count=`` to +the attributes of a ``haskell_test``, and instead the test will fail if the +number of uncovered expressions is greater than this amount. Finally, you could +do both at once, and have both of these checks analyzed by the coverage runner. +To see the coverage details of the test suite regardless of if the test passes +or fails, add ``--test_output=all`` as a flag when invoking the test, and there +will be a report in the test output. You will only see the report if you +required a certain level of expression coverage in the rule attributes. + +For example, your BUILD file might look like this: :: + + haskell_library( + name = "lib", + srcs = ["Lib.hs"], + deps = [ + "//tests/hackage:base", + ], + ) + + haskell_test( + name = "test", + srcs = ["Main.hs"], + deps = [ + ":lib", + "//tests/hackage:base", + ], + expected_covered_expressions_percentage = 80, + expected_uncovered_expression_count = 10, + ) + +And if you ran ``bazel coverage //somepackage:test --test_output=all``, you +might see a result like this: :: + + INFO: From Testing //somepackage:test: + ==================== Test output for //somepackage:test: + Overall report + 100% expressions used (9/9) + 100% boolean coverage (0/0) + 100% guards (0/0) + 100% 'if' conditions (0/0) + 100% qualifiers (0/0) + 100% alternatives used (0/0) + 100% local declarations used (0/0) + 100% top-level declarations used (3/3) + ============================================================================= + +Here, the test passes because it actually has 100% expression coverage and 0 +uncovered expressions, which is even better than we expected on both counts. + +There is an optional ``haskell_test`` attribute called +``strict_coverage_analysis``, which is a boolean that changes the coverage +analysis such that even having better coverage than expected fails the test. +This can be used to enforce that developers must upgrade the expected test +coverage when they improve it. On the other hand, it requires changing the +expected coverage for almost any change. + +There a couple of notes regarding the coverage analysis functionality: + +- Coverage analysis currently is scoped to all source files and all + locally-built Haskell dependencies (both direct and transitive) for a given + test rule. +- Coverage-enabled build and execution for ``haskell_test`` targets may take + longer than regular. However, this has not effected regular ``run`` / + ``build`` / ``test`` performance. + +.. _hpc: -- cgit 1.4.1