This is a gazelle extension that generates and updates Haskell rules for Bazel from Cabal files.
For each Cabal file found, rules are generated in a sibling
BUILD
file for every buildable
component.
Additionally, it generates a stack_snapshot
rule in the
WORKSPACE
file with all of the necessary dependencies.
This example repo shows it in action.
Firstly, setup gazelle and rules_haskell.
Then import gazelle_cabal
.
http_archive(
name = "io_tweag_gazelle_cabal",
strip_prefix = "gazelle_cabal-main",
url = "https://github.com/tweag/gazelle_cabal/archive/main.zip",
)
Additionally, some Haskell packages are needed to build
gazelle_cabal
. The simplest way to bring them is to use the
stack_snapshot
rule in the WORKSPACE
file as follows.
load("@rules_haskell//haskell:cabal.bzl", "stack_snapshot")
load("@io_tweag_gazelle_cabal//:defs.bzl", "gazelle_cabal_dependencies")
gazelle_cabal_dependencies()
stack_snapshot(
name = "stackage",
packages = [
"json", # keep
"path", # keep
"path-io", # keep
],
# Most snapshots of your choice might do
snapshot = "lts-18.28",
)
Right now there are also some issues with gazelle
and as a temporary fix you need to include the following in your WORKSPACE
:
go_repository(
name = "org_golang_x_xerrors",
importpath = "golang.org/x/xerrors",
sum = "h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=",
version = "v0.0.0-20200804184101-5ec99f83aff1",
)
Should Haskell packages need to be grabbed from elsewhere, alternative labels can be provided to gazelle_cabal_dependencies.
You can generate or update build rules by adding the following to
one of your BUILD
files.
load(
"@bazel_gazelle//:def.bzl",
"DEFAULT_LANGUAGES",
"gazelle",
"gazelle_binary",
)
gazelle(
name = "gazelle",
gazelle = ":gazelle_binary",
)
gazelle_binary(
name = "gazelle_binary",
languages = DEFAULT_LANGUAGES + ["@io_tweag_gazelle_cabal//gazelle_cabal"],
)
Build and run gazelle with
bazel run //:gazelle
Gazelle's fix command can be used to delete rules when components are removed from the cabal file.
In order to generate or update the stack_snapshot
rule, add
the following to one of your build files
gazelle(
name = "gazelle-update-repos",
command = "update-repos",
extra_args = [
"-lang",
"gazelle_cabal",
"stackage",
],
gazelle = ":gazelle_binary",
)
Then build and run gazelle with
bazel run //:gazelle-update-repos
gazelle_cabal
only fills the packages
attribute of stack_snapshot
.
Either before or after the update, the other mandatory attributes of the
rule need to be written by the user.
# gazelle:resolve gazelle_cabal sodium @libsodium//:libsodium
Maps names in the Cabal file's extra-libraries
field to Bazel
labels. The labels are added to the deps
attribute of the
corresponding rule. Names not mentioned in this directive are
added to the ghcopts
attribute as -l<name>
.
# gazelle:cabal_haskell_package_repo stackage
Specifies the name of the repository from where Cabal packages
are provided. The default value is stackage
. The user is
responsible for setting up the repository. The repository for
tools is constructed automatically by appending -exe
to this.
In general, package names in the build-depends
field are mapped to
@stackage//:<package_name>
, except if there is a haskell_library
rule in the current repository with the same name, in which
case such a target is added to the deps
attribute instead.
Similar logic applies in case of internal libraries
(a.k.a sublibraries or named libraries). Additionally, please
beware of shadowing feature and the fact that internal libraries with
cabal's visibility:private
do not leak
outside of the package where they have been defined. However, their
bazel's visibility
attribute is by default set to public
.
If there is a ghc_plugin
rule named as <package name>-plugin
and
<package name>
is listed in the build-depends
field, the
corresponding label is added to the plugins
attribute and omitted
in deps
.
Tools in the build-tool-depends
field are mapped to
@stackage-exe//<package_name>:<executable_name>
and added to the
tools
attribute, unless there is a haskell_binary
rule in the
current repository named as the executable, in which case such a
target is added to the tools
attribute instead. The path to the
tool is defined as a CPP macro via the compiler flags of the
corresponding rule as PACKAGE_NAME_EXECUTABLE_NAME_PATH
(e.g. TASTY_DISCOVER_TASTY_DISCOVER_PATH
).
Currently, gazelle
is unable to merge the contents of the
components
attribute of the stack_snapshot
rule if
the attribute is already present. But it is still possible to have the
attribute automatically generated when it is not present yet.
Thus, refreshing the attribute requires to manually remove it first.
Files in the data-files
field are placed verbatim in the data
attribute of the haskell_library
rule if a library component is
present in the Cabal file. Otherwise, the data-files
field is
ignored.
gazelle_cabal
extracts data from Cabal files using cabalscan,
a command line tool written in Haskell, which presents the extracted data
in json format to the go
part.
The go part consists of a set of functions written in the
go programming language, which gazelle
will invoke to
generate Haskell rules. The most important functions are:
-
GenerateRules
: calls thecabalscan
command-line tool and produces rules that contain no information about dependencies. -
Resolve
: adds to the previously generated rules all the information about dependencies (deps
,plugins
, andtools
). -
UpdateRepos
: scans theBUILD
files for Haskell rules, extracts their dependencies, and puts them in astack_snapshot
rule in theWORKSPACE
file.
Despite that gazelle_cabal
can produce most of the build configuration
from Cabal files, Haskell dependencies brought with stack_snapshot
might fail to build if their Cabal files use internal libraries or some particular
custom Setup.hs
files. In these cases, the simpler route to adoption could
be to patch the problematic dependencies and add them to a local stack
snapshot (see the local_snapshot attribute of
stack_snapshot
).
Also, support for fields in Cabal files is added in as-needed fashion,
which means that when gazelle_cabal
is tried on new Cabal files it could
require patches to deal with some unsupported features. The Cabal files in
the example repo rehearse the range of features currently
supported.
If Cabal components use different dependencies depending on Cabal
flags, gazelle_cabal
will only generate the rules for the
configuration with default flag values.
If you are running NixOS or if you want to provision ghc
and other dependencies using nix
,
you'll need to set the host_platform
config option to @rules_nixpkgs_core//platforms:host
:
> bazel build --host_platform=@rules_nixpkgs_core//platforms:host ...
For ease of use, we recommend setting this in your .bazelrc.local
file:
echo "build --host_platform=@rules_nixpkgs_core//platforms:host" >> .bazelrc.local
gazelle_cabal
was funded by Symbiont
and is maintained by Tweag I/O.
Have questions? Need help? Tweet at @tweagio.