"""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
"""