about summary refs log tree commit diff
path: root/third_party/bazel/rules_haskell/haskell/cc.bzl
blob: 1ec803764aab1e51dc35cfae499ca03c62c55ad1 (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
"""Interop with cc_* rules

These rules are deprecated.
"""

load("@bazel_tools//tools/cpp:toolchain_utils.bzl", "find_cpp_toolchain")
load(
    "@bazel_tools//tools/build_defs/cc:action_names.bzl",
    "CPP_LINK_DYNAMIC_LIBRARY_ACTION_NAME",
    "C_COMPILE_ACTION_NAME",
)
load(":private/path_utils.bzl", "ln")
load("@bazel_skylib//lib:paths.bzl", "paths")
load(":private/set.bzl", "set")
load(
    "@io_tweag_rules_haskell//haskell:providers.bzl",
    "HaskellInfo",
)

CcInteropInfo = provider(
    doc = "Information needed for interop with cc rules.",
    fields = {
        "tools": "Tools from the CC toolchain",
        # See the following for why this is needed:
        # https://stackoverflow.com/questions/52769846/custom-c-rule-with-the-cc-common-api
        "files": "Files for all tools (input to any action that uses tools)",
        "hdrs": "CC headers",
        "cpp_flags": "Preprocessor flags",
        "compiler_flags": "Flags for compilation",
        "linker_flags": "Flags to forward to the linker",
        "include_args": "Extra include dirs",
    },
)

def cc_interop_info(ctx):
    """Gather information from any CC dependencies.

    *Internal function - do not use.*

    Args:
      ctx: Rule context.

    Returns:
      CcInteropInfo: Information needed for CC interop.
    """
    ccs = [dep[CcInfo] for dep in ctx.attr.deps if CcInfo in dep and HaskellInfo not in dep]

    hdrs = []
    include_args = []
    cpp_flags = []
    for cc in ccs:
        cc_ctx = cc.compilation_context
        hdrs.append(cc_ctx.headers)
        include_args.extend(["-I" + include for include in cc_ctx.includes])
        cpp_flags.extend(
            [
                "-D" + define
                for define in cc_ctx.defines
            ] + [
                f
                for include in cc_ctx.quote_includes
                for f in ["-iquote", include]
            ] + [
                f
                for include in cc_ctx.system_includes
                for f in ["-isystem", include]
            ],
        )

    hdrs = depset(transitive = hdrs)

    # XXX Workaround https://github.com/bazelbuild/bazel/issues/6874.
    # Should be find_cpp_toolchain() instead.
    cc_toolchain = ctx.attr._cc_toolchain[cc_common.CcToolchainInfo]
    feature_configuration = cc_common.configure_features(
        cc_toolchain = cc_toolchain,
        requested_features = ctx.features,
        unsupported_features = ctx.disabled_features,
    )
    compile_variables = cc_common.create_compile_variables(
        feature_configuration = feature_configuration,
        cc_toolchain = cc_toolchain,
    )
    compiler_flags = cc_common.get_memory_inefficient_command_line(
        feature_configuration = feature_configuration,
        action_name = C_COMPILE_ACTION_NAME,
        variables = compile_variables,
    )
    link_variables = cc_common.create_link_variables(
        feature_configuration = feature_configuration,
        cc_toolchain = cc_toolchain,
        is_linking_dynamic_library = False,
        is_static_linking_mode = True,
    )
    linker_flags = cc_common.get_memory_inefficient_command_line(
        feature_configuration = feature_configuration,
        action_name = CPP_LINK_DYNAMIC_LIBRARY_ACTION_NAME,
        variables = link_variables,
    )

    # Generate cc wrapper script on Darwin that adjusts load commands.
    hs_toolchain = ctx.toolchains["@io_tweag_rules_haskell//haskell:toolchain"]
    if hs_toolchain.is_darwin:
        cc_wrapper = ctx.actions.declare_file("osx_cc_wrapper")
        cc = cc_wrapper.path
        ctx.actions.expand_template(
            template = hs_toolchain.osx_cc_wrapper_tpl,
            output = cc_wrapper,
            substitutions = {
                "%{cc}": cc_toolchain.compiler_executable(),
            },
        )
        cc_files = ctx.files._cc_toolchain + [
            cc_wrapper,
        ]
    else:
        cc = cc_toolchain.compiler_executable()
        cc_files = ctx.files._cc_toolchain

    # XXX Workaround https://github.com/bazelbuild/bazel/issues/6876.
    linker_flags = [flag for flag in linker_flags if flag not in ["-shared"]]

    tools = {
        "ar": cc_toolchain.ar_executable(),
        "cc": cc,
        "ld": cc_toolchain.ld_executable(),
        "cpp": cc_toolchain.preprocessor_executable(),
        "nm": cc_toolchain.nm_executable(),
    }

    # If running on darwin but XCode is not installed (i.e., only the Command
    # Line Tools are available), then Bazel will make ar_executable point to
    # "/usr/bin/libtool". Since we call ar directly, override it.
    # TODO: remove this if Bazel fixes its behavior.
    # Upstream ticket: https://github.com/bazelbuild/bazel/issues/5127.
    if tools["ar"].find("libtool") >= 0:
        tools["ar"] = "/usr/bin/ar"

    return CcInteropInfo(
        tools = struct(**tools),
        files = cc_files,
        hdrs = hdrs.to_list(),
        cpp_flags = cpp_flags,
        include_args = include_args,
        compiler_flags = compiler_flags,
        # XXX this might not be the right set of flags for all situations,
        # but this will anyways all be replaced (once implemented) by
        # https://github.com/bazelbuild/bazel/issues/4571.
        linker_flags = linker_flags,
    )

def _cc_import_impl(ctx):
    strip_prefix = ctx.attr.strip_include_prefix

    # cc_library's strip_include_prefix attribute accepts both absolute and
    # relative paths.  For simplicity we currently only implement absolute
    # paths.
    if strip_prefix.startswith("/"):
        prefix = strip_prefix[1:]
    else:
        prefix = paths.join(ctx.label.workspace_root, ctx.label.package, strip_prefix)

    roots = set.empty()
    for f in ctx.files.hdrs:
        # If it's a generated file, strip off the bin or genfiles prefix.
        path = f.path
        if path.startswith(ctx.bin_dir.path):
            path = paths.relativize(path, ctx.bin_dir.path)
        elif path.startswith(ctx.genfiles_dir.path):
            path = paths.relativize(path, ctx.genfiles_dir.path)

        if not path.startswith(prefix):
            fail("Header {} does not have expected prefix {}".format(
                path,
                prefix,
            ))
        roots = set.insert(roots, f.root.path if f.root.path else ".")

    include_directories = [paths.join(root, prefix) for root in set.to_list(roots)]

    cc_toolchain = ctx.attr._cc_toolchain[cc_common.CcToolchainInfo]
    feature_configuration = cc_common.configure_features(cc_toolchain = cc_toolchain)

    compilation_context = cc_common.create_compilation_context(
        headers = depset(transitive = [l.files for l in ctx.attr.hdrs]),
        includes = depset(direct = include_directories),
    )
    linking_context = cc_common.create_linking_context(
        libraries_to_link = [
            cc_common.create_library_to_link(
                actions = ctx.actions,
                feature_configuration = feature_configuration,
                cc_toolchain = cc_toolchain,
                dynamic_library = f,
            )
            for f in ctx.attr.shared_library.files
        ],
    )

    return [
        CcInfo(
            compilation_context = compilation_context,
            linking_context = linking_context,
        ),
    ]

haskell_cc_import = rule(
    _cc_import_impl,
    attrs = {
        "shared_library": attr.label(
            # NOTE We do not list all extensions here because .so libraries may
            # have numeric suffixes like foo.so.1.2.3, and if they also have
            # SONAME with numeric suffix, matching file must be provided, so this
            # attributes must accept libraries with almost arbitrary extensions.
            # It would be easier if Skylark supported regexps.
            allow_files = True,
            doc = """A single precompiled shared library.

Bazel ensures it is available to the binary that depends on it
during runtime.
""",
        ),
        "hdrs": attr.label_list(
            allow_files = [".h"],
            doc = """

The list of header files published by this precompiled library to be
directly included by sources in dependent rules.
""",
        ),
        "strip_include_prefix": attr.string(
            doc = """
The prefix to strip from the paths of the headers of this rule.
When set, the headers in the `hdrs` attribute of this rule are
accessible at their path (relative to the repository) with this
prefix cut off.

If it's a relative path, it's taken as a package-relative one. If it's an
absolute one, it's understood as a repository-relative path.
""",
        ),
        "_cc_toolchain": attr.label(
            default = Label("@bazel_tools//tools/cpp:current_cc_toolchain"),
        ),
    },
)
"""Imports a prebuilt shared library.

Use this to make `.so`, `.dll`, `.dylib` files residing in external
[external repositories][bazel-ext-repos] available to Haskell rules.

*This rule is temporary replacement for [cc_import][cc_import] and is
deprecated. Use [cc_library][cc_library] instead as shown in the example.*

Example:
  ```bzl
  # Deprecated, use cc_library instead.
  # haskell_cc_import(name = "zlib", shared_library = "@zlib//:lib")

  cc_library(name = "zlib", srcs = ["@zlib//:lib"])

  haskell_import(
    name = "base_pkg",
    package = "base",
  )

  haskell_binary(
    name = "crc32sum",
    srcs = ["Main.hs"],
    deps = [
      "bazel_pkg",
      ":zlib",
    ],
  )
  ```

[bazel-ext-repos]: https://docs.bazel.build/versions/master/external.html
[cc_import]: https://docs.bazel.build/versions/master/be/c-cpp.html#cc_import
[cc_library]: https://docs.bazel.build/versions/master/be/c-cpp.html#cc_library
"""

def _cc_haskell_import(ctx):
    dyn_libs = set.empty()

    if HaskellInfo in ctx.attr.dep:
        set.mutable_union(dyn_libs, ctx.attr.dep[HaskellInfo].dynamic_libraries)
    else:
        fail("{0} has to provide `HaskellInfo`".format(ctx.attr.dep.label.name))

    return [
        DefaultInfo(
            files = set.to_depset(dyn_libs),
            default_runfiles = ctx.runfiles(
                files = ctx.attr.dep.default_runfiles.files.to_list(),
                collect_default = True,
            ),
            data_runfiles = ctx.runfiles(
                files = ctx.attr.dep.data_runfiles.files.to_list(),
                collect_data = True,
            ),
        ),
    ]

cc_haskell_import = rule(
    _cc_haskell_import,
    attrs = {
        "dep": attr.label(
            doc = """
Target providing a `HaskellInfo` such as `haskell_library` or
`haskell_binary`.
""",
        ),
    },
    toolchains = ["@io_tweag_rules_haskell//haskell:toolchain"],
)
"""Exports a Haskell library as a CC library.

Given a [haskell_library](#haskell_library) or
[haskell_binary](#haskell_binary) input, outputs the shared object files
produced as well as the object files it depends on directly and
transitively. This is very useful if you want to link in a Haskell shared
library from `cc_library`.

There is a caveat: this will not provide any shared libraries that
aren't explicitly given to it. This means that if you're using
`prebuilt_dependencies` and relying on GHC to provide those objects,
they will not be present here. You will have to provide those
separately to your `cc_library`. If you're getting
`prebuilt_dependencies` from your toolchain, you will likely want to
extract those and pass them in as well.

*This rule is deprecated.*

Example:
  ```bzl
  haskell_library(
    name = "my-lib",
    ...
  )

  cc_haskell_import(
    name = "my-lib-objects",
    dep = ":my-lib",
  )

  cc_library(
    name = "my-cc",
    srcs = ["main.c", ":my-lib-objects"],
  )
  ```

[bazel-cpp-sandwich]: https://github.com/bazelbuild/bazel/issues/2163
"""