summary refs log blame commit diff
path: root/third_party/bazel/rules_haskell/haskell/cc.bzl
blob: 1ec803764aab1e51dc35cfae499ca03c62c55ad1 (plain) (tree)
































































































































































































































































































































































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