Skip to content

Commit af959d9

Browse files
Initial commit
0 parents  commit af959d9

40 files changed

+2084
-0
lines changed

.bazelrc

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
try-import .bazelrc.local

.gitignore

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
*.swp
2+
*.swo
3+
4+
# bazel-created symlinks
5+
bazel-*
6+
.bazelrc.local

BUILD.bazel

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
load("@com_github_bazelbuild_buildtools//buildifier:def.bzl", "buildifier")
2+
3+
exports_files(["defs.bzl"])
4+
5+
buildifier(
6+
name = "buildifier-diff",
7+
mode = "diff",
8+
)
9+
10+
buildifier(
11+
name = "buildifier",
12+
lint_mode = "warn",
13+
)

README.md

+247
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,247 @@
1+
# gazelle\_haskell\_modules
2+
3+
This is a [gazelle][gazelle] extension that generates `haskell_module`
4+
rules from `haskell_library`, `haskell_binary`, and `haskell_test` as
5+
defined in [Haskell rules][rules_haskell] for [Bazel][bazel].
6+
7+
For each `haskell_library` rule, `haskell_module` rules are generated
8+
in the same `BUILD` for all modules listed in the `srcs` attribute.
9+
10+
This [example repo][example] shows it in action.
11+
12+
Right now, this is work in progress. The features that are implemented
13+
include a scanner of imports in Haskell modules, and the generation of
14+
some `haskell_module` rules. See the **What's next** section below.
15+
16+
## Configuration
17+
18+
Firstly, setup [gazelle][gazelle] and [rules_haskell][rules_haskell].
19+
Then import `gazelle_haskell_modules`.
20+
21+
```python
22+
http_archive(
23+
name = "io_tweag_gazelle_haskell_modules",
24+
strip_prefix = "gazelle_haskell_modules-main",
25+
url = "https://github.com/tweag/gazelle_haskell_modules/archive/main.zip",
26+
)
27+
```
28+
29+
Additionally, some Haskell packages are needed to build
30+
`gazelle_haskell_modules`. The simplest way to bring them is to use the
31+
`stack_snapshot` rule in the `WORKSPACE` file as follows.
32+
33+
```python
34+
load("@rules_haskell//haskell:cabal.bzl", "stack_snapshot")
35+
load("@io_tweag_gazelle_haskell_modules//:defs.bzl", "gazelle_haskell_modules_dependencies")
36+
gazelle_haskell_modules_dependencies()
37+
38+
stack_snapshot(
39+
name = "stackage",
40+
packages = [
41+
"aeson", # keep
42+
"parsec", # keep
43+
],
44+
# Most snapshots of your choice might do
45+
snapshot = "lts-18.1",
46+
)
47+
```
48+
Should Haskell packages need to be grabbed from elsewhere, alternative
49+
labels can be provided to [gazelle_haskell_modules_dependencies][gazelle_haskell_modules_dependencies].
50+
51+
You can generate or update build rules by adding the following to
52+
one of your `BUILD` files.
53+
54+
```python
55+
load(
56+
"@bazel_gazelle//:def.bzl",
57+
"DEFAULT_LANGUAGES",
58+
"gazelle",
59+
"gazelle_binary",
60+
)
61+
62+
gazelle(
63+
name = "gazelle",
64+
data = ["@io_tweag_gazelle_haskell_modules//himportscan"],
65+
gazelle = ":gazelle_binary",
66+
)
67+
68+
gazelle_binary(
69+
name = "gazelle_binary",
70+
languages = DEFAULT_LANGUAGES + ["@io_tweag_gazelle_haskell_modules//gazelle_haskell_modules"],
71+
)
72+
```
73+
74+
## Running
75+
76+
Build and run gazelle with
77+
```bash
78+
bazel run //:gazelle
79+
```
80+
81+
## Rule generation
82+
83+
Each module listed in the `srcs` attribute of a Haskell rule originates
84+
a `haskell_module` rule with name `<pkg>.<module>`. Some attributes
85+
are copied from the originating rule to the `haskell_module` rule, like
86+
`ghcopts`, `tools`, and `extra_srcs`.
87+
88+
The `deps` attribute is managed in a special way though. Each dependency
89+
is considered for addition to the `deps` attribute of a `haskell_module`
90+
rule. If the dependency is defined in the current repository, and it is
91+
defined with a Haskell rule, then the dependency is dropped since the
92+
`haskell_module` rule is expected to depend directly on the modules of
93+
that dependency if at all.
94+
95+
If the dependency isn't defined in the local repo, or isn't defined with
96+
Haskell rules, then the dependcy is added since the source module might
97+
depend on it without `gazelle_haskell_modules` being able to determine it.
98+
99+
### Dependencies of non-haskell\_module rules
100+
101+
The `srcs` attributes of `haskell_library`, `haskell_binary`, and
102+
`haskell_test` are cleared. The `deps` attribute is augmented with
103+
the labels of the corresponding `haskell_module` rules.
104+
105+
Additionally, `gazelle_haskell_modules` removes from `deps` the
106+
dependencies defined with other `haskell_library` rules. These
107+
dependencies are unnecessary because the `haskell_module` rules will
108+
depend on modules from them if needed.
109+
110+
### Updating dependencies of haskell\_module rules
111+
112+
When the imports in a module are changed, the corresponding
113+
`haskell_module` rule might need to be updated. Removed imports are
114+
removed from the `deps` attribute, and added imports might originate new
115+
dependencies. Adding an import to a module that is defined in the current
116+
repo, will add that module to the dependencies as long as the name of the
117+
module uniquely identifies a rule that defines it. If two different rules
118+
defined the same module name, `gazelle_haskell_modules` will produce an
119+
error.
120+
121+
Note that `gazelle_haskell_modules` will always chose the right module
122+
when generating new `haskell_module` rules because the list of
123+
dependencies in the originating rule allows to determine from which
124+
library imports are coming from. Some of these dependencies are erased
125+
later on, though, so they aren't available when the `haskell_module`
126+
rules need to be updated.
127+
128+
## Implementation
129+
130+
`gazelle_haskell_modules` extracts module imports from Haskell modules
131+
using [himportscan][himportscan], a command line tool written in Haskell,
132+
which presents the extracted data in json format to the `go` part.
133+
134+
The [go part][go-part] consists of a set of functions written in the
135+
[go][go] programming language, which `gazelle` will invoke to
136+
generate `haskell_module` rules. The most important functions are:
137+
138+
* `GenerateRules`: calls the `himportscan` command-line tool and
139+
produces rules that contain no information about dependencies.
140+
141+
* `Imports`: indexes the rules for dependency resolution.
142+
143+
* `Resolve`: adds to the previously generated rules all the
144+
information about dependencies (`deps`, `plugins`, and `tools`).
145+
146+
## Limitations
147+
148+
### CPP support
149+
150+
CPP directives in source files are ignored when scanning for imports.
151+
That is, `himportscan` would always pick up both imports in the next example.
152+
153+
```Haskell
154+
#if COND
155+
import SomeModule
156+
#else
157+
import SomeOtherModule
158+
#endif
159+
```
160+
161+
### Preprocessor support
162+
163+
Imports may not be possible to extract in files that need preprocessors which
164+
generate those same imports (e.g. `tasty-discover`). In these cases, generation
165+
of `haskell_module` rules can be avoided by using `# keep` comments on the
166+
given files, and any sources that import them.
167+
168+
```python
169+
# The contents of Spec.hs are generated by tasty-discover
170+
haskell_test(
171+
name = "tests"
172+
srcs = [
173+
"tests/Main.hs", # keep
174+
"tests/Spec.hs", # keep
175+
"tests/Other.hs"
176+
]
177+
deps = ...,
178+
tools = ["@stackage-exe//tasty-discover"],
179+
)
180+
```
181+
182+
In the above case, only a `haskell_module` rule for `tests/Other.hs` would
183+
be produced.
184+
185+
At the moment, this only works on `haskell_binary` and `haskell_test` rules.
186+
If used in a `haskell_library`, rules for modules in other targets that
187+
import the preprocessed source files would miss them in the dependencies.
188+
189+
### export and reexported\_modules
190+
191+
`haskell_library` has attributes `export` and `reexported_modules` which
192+
affect the dependencies of rules that depend on the Haskell library.
193+
`gazelle_haskell_modules` makes no effort to honor those attributes when
194+
generating rules or resolving imports.
195+
196+
### Package imports
197+
198+
`gazelle_haskell_modules` ignores package imports as implemented in GHC
199+
with `-XPackageImports`.
200+
201+
## What's next
202+
203+
- [X] Have `haskell_module` rules depend on each other.
204+
- [X] Investigate how to index external repositories, which would be needed
205+
to recognize the provenance of modules coming from external dependencies.
206+
- [X] Have Haskell rules depend on `haskell_module` rules
207+
- [X] Copy `extra_srcs` from the originating rule
208+
- [X] Copy `tools` from the originating rule
209+
- [X] Copy `plugins` from the originating rule
210+
- [X] Skip CPP directives when scanning for imports
211+
- [X] Have a story for preprocessed modules like those needing tasty-discover
212+
- [X] Update `haskell_module` rules when imports change
213+
- [X] Solve interferences with `gazelle_cabal` (removes keep comments on srcs, libsodium label is not understood, deps attribute is reset)
214+
- [X] Lift restriction to name only `haskell_module` rules with dots.
215+
- [X] Lift restriction to name custom `haskell_module` rules as `<package>.<module>`
216+
- [X] Propagate compiler flags and other attributes of libraries, tests, and binaries, to `haskell_module` rules listed in the dependencies.
217+
- [X] Use the `modules` attribute of `haskell_library` instead of `deps`
218+
- [ ] Check if linker options need special treatment
219+
- [ ] Copy `data` from the originating rule (?)
220+
- [X] Document how `gazelle_haskell_modules` works
221+
- [ ] Support the fix command to remove outdated `haskell_module` rules
222+
- [X] See whether we can run `gazelle_haskell_modules` and `gazelle_cabal` in one pass
223+
- [ ] Implement tests
224+
- [ ] Setup CI
225+
226+
## Sponsors
227+
228+
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
229+
[![Symbiont](https://imgur.com/KPV3lTY.png)](https://symbiont.io)
230+
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
231+
[![Tweag I/O](https://i.imgur.com/KPEy44T.png)](http://tweag.io)
232+
233+
`gazelle_haskell_modules` was funded by [Symbiont](https://www.symbiont.io/)
234+
and is maintained by [Tweag I/O](http://tweag.io/).
235+
236+
Have questions? Need help? Tweet at
237+
[@tweagio](http://twitter.com/tweagio).
238+
239+
[bazel]: https://bazel.build
240+
[himportscan]: himportscan/exe/Main.hs
241+
[gazelle_haskell_module_dependencies]: defs.bzl
242+
[example]: example
243+
[fix-command]: https://github.com/bazelbuild/bazel-gazelle#fix-and-update
244+
[gazelle]: https://github.com/bazelbuild/bazel-gazelle
245+
[go-part]: gazelle_haskell_modules/lang.go
246+
[go]: https://golang.org
247+
[rules_haskell]: https://github.com/tweag/rules_haskell

0 commit comments

Comments
 (0)