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:
- Cabal
However, 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 //:gazelle
Gazelle'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_module
s 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_module
s 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 thehimportscan
command-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
#endif
Imports 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.