about summary refs log tree commit diff
path: root/third_party/bazel/rules_haskell/haskell/haskell.bzl
blob: 61994e4aca106df0e175039ec5a9b734b972edc6 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
"""Core Haskell rules"""

load(
    ":cc.bzl",
    _cc_haskell_import = "cc_haskell_import",
    _haskell_cc_import = "haskell_cc_import",
)
load(
    ":doctest.bzl",
    _haskell_doctest = "haskell_doctest",
    _haskell_doctest_toolchain = "haskell_doctest_toolchain",
)
load(
    ":ghc_bindist.bzl",
    _ghc_bindist = "ghc_bindist",
    _haskell_register_ghc_bindists = "haskell_register_ghc_bindists",
)
load(
    ":haddock.bzl",
    _haskell_doc = "haskell_doc",
    _haskell_doc_aspect = "haskell_doc_aspect",
)
load(
    ":lint.bzl",
    _haskell_lint = "haskell_lint",
    _haskell_lint_aspect = "haskell_lint_aspect",
)
load(
    ":private/haskell_impl.bzl",
    _haskell_binary_impl = "haskell_binary_impl",
    _haskell_library_impl = "haskell_library_impl",
    _haskell_test_impl = "haskell_test_impl",
    _haskell_toolchain_library_impl = "haskell_toolchain_library_impl",
)
load(
    ":repl.bzl",
    _haskell_repl = "haskell_repl",
    _haskell_repl_aspect = "haskell_repl_aspect",
)

# For re-exports:
load(
    ":protobuf.bzl",
    _haskell_proto_library = "haskell_proto_library",
    _haskell_proto_toolchain = "haskell_proto_toolchain",
)
load(
    ":toolchain.bzl",
    _haskell_register_toolchains = "haskell_register_toolchains",
    _haskell_toolchain = "haskell_toolchain",
)
load(
    ":plugins.bzl",
    _ghc_plugin = "ghc_plugin",
)

_haskell_common_attrs = {
    "src_strip_prefix": attr.string(
        doc = "Directory in which module hierarchy starts.",
    ),
    "srcs": attr.label_list(
        allow_files = [".hs", ".hsc", ".lhs", ".hs-boot", ".lhs-boot", ".h"],
        doc = "Haskell source files.",
    ),
    "extra_srcs": attr.label_list(
        allow_files = True,
        doc = "Extra (non-Haskell) source files that will be needed at compile time (e.g. by Template Haskell).",
    ),
    "deps": attr.label_list(
        doc = "List of other Haskell libraries to be linked to this target.",
    ),
    "data": attr.label_list(
        doc = "See [Bazel documentation](https://docs.bazel.build/versions/master/be/common-definitions.html#common.data).",
        allow_files = True,
    ),
    "compiler_flags": attr.string_list(
        doc = "Flags to pass to Haskell compiler.",
    ),
    "repl_ghci_args": attr.string_list(
        doc = "Arbitrary extra arguments to pass to GHCi. This extends `compiler_flags` and `repl_ghci_args` from the toolchain",
    ),
    "runcompile_flags": attr.string_list(
        doc = "Arbitrary extra arguments to pass to runghc. This extends `compiler_flags` and `repl_ghci_args` from the toolchain",
    ),
    "plugins": attr.label_list(
        doc = "Compiler plugins to use during compilation.",
    ),
    "_ghci_script": attr.label(
        allow_single_file = True,
        default = Label("@io_tweag_rules_haskell//haskell:assets/ghci_script"),
    ),
    "_ghci_repl_wrapper": attr.label(
        allow_single_file = True,
        default = Label("@io_tweag_rules_haskell//haskell:private/ghci_repl_wrapper.sh"),
    ),
    "_ls_modules": attr.label(
        executable = True,
        cfg = "host",
        default = Label("@io_tweag_rules_haskell//haskell:ls_modules"),
    ),
    "_version_macros": attr.label(
        executable = True,
        cfg = "host",
        default = Label("@io_tweag_rules_haskell//haskell:version_macros"),
    ),
    "_cc_toolchain": attr.label(
        default = Label("@bazel_tools//tools/cpp:current_cc_toolchain"),
    ),
}

def _mk_binary_rule(**kwargs):
    """Generate a rule that compiles a binary.

    This is useful to create variations of a Haskell binary compilation
    rule without having to copy and paste the actual `rule` invocation.

    Args:
      **kwargs: Any additional keyword arguments to pass to `rule`.

    Returns:
      Rule: Haskell binary compilation rule.
    """

    is_test = kwargs.get("test", False)

    attrs = dict(
        _haskell_common_attrs,
        linkstatic = attr.bool(
            default = True,
            doc = "Link dependencies statically wherever possible. Some system libraries may still be linked dynamically, as are libraries for which there is no static library. So the resulting executable will still be dynamically linked, hence only mostly static.",
        ),
        main_function = attr.string(
            default = "Main.main",
            doc = """A function with type `IO _`, either the qualified name of a function from any module or the bare name of a function from a `Main` module. It is also possible to give the qualified name of any module exposing a `main` function.""",
        ),
        version = attr.string(
            doc = "Executable version. If this is specified, CPP version macros will be generated for this build.",
        ),
    )

    # Tests have an extra fields regarding code coverage.
    if is_test:
        attrs.update({
            "expected_covered_expressions_percentage": attr.int(
                default = -1,
                doc = "The expected percentage of expressions covered by testing.",
            ),
            "expected_uncovered_expression_count": attr.int(
                default = -1,
                doc = "The expected number of expressions which are not covered by testing.",
            ),
            "strict_coverage_analysis": attr.bool(
                default = False,
                doc = "Requires that the coverage metric is matched exactly, even doing better than expected is not allowed.",
            ),
            "coverage_report_format": attr.string(
                default = "text",
                doc = """The format to output the coverage report in. Supported values: "text", "html". Default: "text".
                Report can be seen in the testlog XML file, or by setting --test_output=all when running bazel coverage.
                """,
            ),
            "experimental_coverage_source_patterns": attr.string_list(
                default = ["//..."],
                doc = """The path patterns specifying which targets to analyze for test coverage metrics.

                Wild-card targets such as //... or //:all are allowed. The paths must be relative to the workspace, which means they must start with "//".

                Note, this attribute may leave experimental status depending on the outcome of https://github.com/bazelbuild/bazel/issues/7763.
                """,
            ),
            "_coverage_wrapper_template": attr.label(
                allow_single_file = True,
                default = Label("@io_tweag_rules_haskell//haskell:private/coverage_wrapper.sh.tpl"),
            ),
            "_bash_runfiles": attr.label(
                allow_single_file = True,
                default = Label("@bazel_tools//tools/bash/runfiles:runfiles"),
            ),
        })

    return rule(
        # If _mk_binary_rule was called with test = True, we want to use the test binary implementation
        _haskell_test_impl if is_test else _haskell_binary_impl,
        executable = True,
        attrs = attrs,
        outputs = {
            "runghc": "%{name}@runghc",
            "repl": "%{name}@repl",
            "repl_deprecated": "%{name}-repl",
        },
        toolchains = [
            "@io_tweag_rules_haskell//haskell:toolchain",
        ],
        **kwargs
    )

haskell_test = _mk_binary_rule(test = True)
"""Build a test suite.

Additionally, it accepts [all common bazel test rule
fields][bazel-test-attrs]. This allows you to influence things like
timeout and resource allocation for the test.

[bazel-test-attrs]: https://docs.bazel.build/versions/master/be/common-definitions.html#common-attributes-tests
"""

haskell_binary = _mk_binary_rule()
"""Build an executable from Haskell source.

Example:
  ```bzl
  haskell_binary(
      name = "hello",
      srcs = ["Main.hs", "Other.hs"],
      deps = ["//lib:some_lib"]
  )
  ```

Every `haskell_binary` target also defines an optional REPL target that is
not built by default, but can be built on request. The name of the REPL
target is the same as the name of binary with `"@repl"` added at the end.
For example, the target above also defines `main@repl`.

You can call the REPL like this (requires Bazel 0.15 or later):

```
$ bazel run //:hello@repl
```

"""

haskell_library = rule(
    _haskell_library_impl,
    attrs = dict(
        _haskell_common_attrs,
        hidden_modules = attr.string_list(
            doc = "Modules that should be unavailable for import by dependencies.",
        ),
        exports = attr.label_keyed_string_dict(
            doc = "A dictionary mapping dependencies to module reexports that should be available for import by dependencies.",
        ),
        linkstatic = attr.bool(
            default = False,
            doc = "Create a static library, not both a static and a shared library.",
        ),
        version = attr.string(
            doc = """Library version. Not normally necessary unless to build a library
            originally defined as a Cabal package. If this is specified, CPP version macro will be generated.""",
        ),
    ),
    outputs = {
        "runghc": "%{name}@runghc",
        "repl": "%{name}@repl",
        "repl_deprecated": "%{name}-repl",
    },
    toolchains = [
        "@io_tweag_rules_haskell//haskell:toolchain",
    ],
)
"""Build a library from Haskell source.

Example:
  ```bzl
  haskell_library(
      name = "hello-lib",
      srcs = glob(["src/**/*.hs"]),
      src_strip_prefix = "src",
      deps = [
          "//hello-sublib:lib",
      ],
      exports = {
          "//hello-sublib:lib": "Lib1 as HelloLib1, Lib2",
      },
  )
  ```

Every `haskell_library` target also defines an optional REPL target that is
not built by default, but can be built on request. It works the same way as
for `haskell_binary`.
"""

haskell_toolchain_library = rule(
    _haskell_toolchain_library_impl,
    attrs = dict(
        package = attr.string(
            doc = "The name of a GHC package not built by Bazel. Defaults to the name of the rule.",
        ),
        _version_macros = attr.label(
            executable = True,
            cfg = "host",
            default = Label("@io_tweag_rules_haskell//haskell:version_macros"),
        ),
    ),
    toolchains = [
        "@io_tweag_rules_haskell//haskell:toolchain",
    ],
)
"""Import packages that are prebuilt outside of Bazel.

Example:
  ```bzl
  haskell_toolchain_library(
      name = "base_pkg",
      package = "base",
  )

  haskell_library(
      name = "hello-lib",
      srcs = ["Lib.hs"],
      deps = [
          ":base_pkg",
          "//hello-sublib:lib",
      ],
  )
  ```

Use this rule to make dependencies that are prebuilt (supplied as part
of the compiler toolchain) available as targets.
"""

haskell_doc = _haskell_doc

haskell_doc_aspect = _haskell_doc_aspect

haskell_lint = _haskell_lint

haskell_lint_aspect = _haskell_lint_aspect

haskell_doctest = _haskell_doctest

haskell_doctest_toolchain = _haskell_doctest_toolchain

haskell_register_toolchains = _haskell_register_toolchains

haskell_register_ghc_bindists = _haskell_register_ghc_bindists

haskell_repl = _haskell_repl

haskell_repl_aspect = _haskell_repl_aspect

haskell_toolchain = _haskell_toolchain

haskell_proto_library = _haskell_proto_library

haskell_proto_toolchain = _haskell_proto_toolchain

ghc_bindist = _ghc_bindist

haskell_cc_import = _haskell_cc_import

cc_haskell_import = _cc_haskell_import

ghc_plugin = _ghc_plugin