This is a gazelle extension that generates haskell_module
rules from haskell_library, haskell_binary, and haskell_test as
defined in Haskell rules for Bazel. Moreover,
it updates the dependencies of the generated rules whenever the import
declarations are changed in the source files.
For each haskell_library, haskell_binary, and haskell_test rule,
haskell_module rules are generated in the same BUILD file for all
modules listed in the srcs attribute. For instance,
haskell_library(
name = "lib",
srcs = [
"src/A/B.hs",
"src/C/D.hs",
],
deps = [":base"],
ghcopts = ["-threaded"],
)is updated to
haskell_library(
name = "lib",
modules = [
"lib.A.B",
"lib.C.D",
],
deps = [":base"],
ghcopts = ["-threaded"],
)
haskell_module(
name = "lib.A.B",
src = "src/A/B.hs",
src_strip_prefix = "src",
deps = ["lib.C.D"],
)
haskell_module(
name = "lib.C.D",
src = "src/C/D.hs",
src_strip_prefix = "src",
)This example project shows it in action.
Firstly, setup gazelle and rules_haskell.
Then import gazelle_haskell_modules.
http_archive(
name = "io_tweag_gazelle_haskell_modules",
strip_prefix = "gazelle_haskell_modules-main",
url = "https://github.com/tweag/gazelle_haskell_modules/archive/main.zip",
)Additionally, some Haskell packages(currently json) are needed to build
gazelle_haskell_modules. 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_haskell_modules//:defs.bzl", "gazelle_haskell_modules_dependencies")
gazelle_haskell_modules_dependencies()
stack_snapshot(
name = "stackage",
packages = [
"json",
],
# Most snapshots of your choice might do
snapshot = "lts-18.28",
)gazelle_haskell_modules implicitly depends on the stackage external workspace.
Should Haskell packages need to be grabbed from elsewhere, alternative
labels can be provided to gazelle_haskell_modules_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_haskell_modules//gazelle_haskell_modules"],
)Due to a regression in cabal, which badly handles relocatable build now,
one has to use a patched version of cabal.
Hence, one should declare a snapshot.yaml file:
resolver: nightly-2022-06-06
packages:
- git: https://github.com/tweag/cabal
commit: 42f04c3f639f10dc3c7981a0c663bfe08ad833cb
subdirs:
- CabalHowever, it then requires to explicit depend on the patched version of cabal for every module requiring it,
hence the WORKSPACE file should contain lines like:
load("@rules_haskell//haskell:cabal.bzl", "stack_snapshot")
load("@io_tweag_gazelle_haskell_modules//:defs.bzl", "gazelle_haskell_modules_dependencies")
gazelle_haskell_modules_dependencies()
stack_snapshot(
name = "stackage",
setup_deps = {
"transformers-compat": ["@stackage//:Cabal"],
"hspec-discover": ["@stackage//:Cabal"],
"call-stack": ["@stackage//:Cabal"],
"HUnit": ["@stackage//:Cabal"],
"quickcheck": ["@stackage//:Cabal"],
"hspec-expectations": ["@stackage//:Cabal"],
"quickcheck-io": ["@stackage//:Cabal"],
"tasty-discover": ["@stackage//:Cabal"],
"hspec-core": ["@stackage//:Cabal"],
"bifunctors": ["@stackage//:Cabal"],
"hspec": ["@stackage//:Cabal"],
},
packages = [
"Cabal",
"hspec",
],
local_snapshot = "//:snapshot.yaml",
)Build and run gazelle with
bazel run //:gazelleGazelle's fix command can be used to delete
haskell_module rules when they have no enclosing library, binary, or
test. At the moment, the fix command only looks for enclosing
rules in the same BUILD file containing the haskell_module rule.
Additionally, fix will also remove haskell_modules whose src files have been deleted, as well as
removing those modules from places where they are referenced.
There are two ways for gazelle_haskell_modules to find source files containing modules.
By attaching the gazelle_haskell_modules:srcs: <folders..> directive to a rule,
gazelle_haskell_modules will recursively search <folders..> to find Haskell source files
to generate haskell_modules with. After finding files, gazelle_haskell_modules will proceed
as if the files were manually specified, as documented below.
Example:
# gazelle_haskell_modules:srcs: src/
haskell_library(
name = "package-c",
...
)More examples of this usage can be found in package-c
If srcs are also explicitly specified in the rule, the results of the directive and the files
listed in the explicit srcs are combined.
Each module listed in the srcs attribute of a Haskell rule originates
a haskell_module rule with name <pkg>.<module>. The dependencies
of the haskell_module rule are populated with labels corresponding
to other haskell_module rules originating from the same library,
binary, or test.
The srcs attributes of haskell_library, haskell_binary, and
haskell_test are cleared. The modules attribute is populated with
the labels of the corresponding haskell_module rules.
When the imports in a module are changed, the corresponding
haskell_module rule might need to be updated. Removed imports are
removed from the deps attribute, and added imports might originate new
dependencies. Adding an import to a module that is defined in the current
repo, will add that module to the dependencies if the importer and the
imported come from the same library, binary, or test.
If no enclosing library, binary, or test can be found for a
haskell_module rule, then it won't be updated.
gazelle_haskell_modules extracts module imports from Haskell modules
using himportscan, 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_module rules. The most important functions are:
-
GenerateRules: calls thehimportscancommand-line tool and produces rules that contain no information about dependencies. -
Imports: indexes the rules for dependency resolution. -
Resolve: adds to the previously generated rules all the information about dependencies (deps,plugins, andtools).
Support for hidden_modules
Currently, the generator allows modules to depend on hidden modules of dependencies. This is something that should be changed eventually so the generator fails in these cases.
CPP directives in source files are ignored when scanning for imports.
That is, himportscan would always pick up both imports in the next example.
#if COND
import SomeModule
#else
import SomeOtherModule
#endifImports may not be possible to extract in files that need preprocessors which
generate those same imports (e.g. tasty-discover). In these cases, generation
of haskell_module rules can be avoided by using # gazelle_haskell_modules:keep
comments on the given rule.
# The contents of Spec.hs are generated by tasty-discover
# gazelle_haskell_modules:keep
haskell_test(
name = "tests"
srcs = [
"tests/Main.hs",
"tests/Spec.hs",
"tests/Other.hs"
]
deps = ...,
tools = ["@stackage-exe//tasty-discover"],
)In the above case, the rule won't originate any haskell_module rules.
haskell_library has attributes export and reexported_modules which
affect the dependencies of rules that depend on the Haskell library.
gazelle_haskell_modules makes no effort to honor those attributes when
generating rules or resolving imports.
gazelle_haskell_modules detects modules that use TemplateHaskell by
looking at the LANGUAGE pragmas and the ghcopts attribute of the
haskell_module rule. But the internal or external interpreter could be
activated by using ANN pragmas in the module source, or by using
-XTemplateHaskell in the ghcopts attribute of the enclosing library.
In these cases, enable_th won't be set on the haskell_module rule
and complains about missing libraries or object files will ensue.
To workaround this, you could set enable_th = True manually on
the haskell_module rule and use a #keep comment.
haskell_module(
name = "...",
...
#keep
enable_th = True,
...
)
gazelle_haskell_modules was funded by Symbiont
and is maintained by Tweag I/O.
Have questions? Need help? Tweet at @tweagio.

