Search code examples
c++cbazel

In BAZEL, Is there a way to prevent dependency C/C++ headers from being propagated to dependent library?


For example: lib_a is an internal library. It exposes interface header alpha.h.

lib_b is an API library. It includes alpha.h in beta.c and exposes interface header beta.h which does not include alpha.h.

exe_c is a test app that utilizes lib_b. Since it is "external" to API library lib_b, it should not have access to internal library lib_a's headers, namely alpha.h.

Now, is there a way to prevent the include path of alpha.h from being added to exe_c 's compilation command-line?


Solution

  • Update 5/24/22: Bazel added an implementation_deps feature.

    This is possible with a custom rule and macro.

    Here's how you can write a macro my_cc_library so that you can do something like this

    my_cc_library(
        name = "b",
        hdrs = [
            "b.h",
        ],
        srcs = [
            "b.cc",
        ]
        impl_deps = [
            "//a:a",
        ],
    )
    

    And targets that depend on b will not be able to include headers from dependency a.

    Step 1

    Add a custom rule that takes a base library and forms a new CcInfo provider with only the headers you want to expose.

    def _my_cc_slim_library(ctx):
      cc_info = ctx.attr.base_library[CcInfo]
      compilation_ctx = cc_info.compilation_context
    
      compilation_ctx_p = cc_common.create_compilation_context(
          headers = depset(direct=ctx.files.hdrs),
          includes = compilation_ctx.includes,
          quote_includes = compilation_ctx.quote_includes,
      )
    
      dep_cc_infos = [dep[CcInfo] for dep in ctx.attr.deps]
    
      cc_info_p = cc_common.merge_cc_infos(
          cc_infos = [
              CcInfo(
                  compilation_context = compilation_ctx_p,
                  linking_context = cc_info.linking_context,
              )
          ] + dep_cc_infos,
      )
      
      return [cc_info_p]
    
    my_cc_slim_library = rule(
        implementation = _my_cc_slim_library,
        attrs = {
            "base_library": attr.label(mandatory=True, providers = [CcInfo]),
            "hdrs": attr.label_list(allow_files=True),
            "deps": attr.label_list(providers = [CcInfo]),
        },
    )
    

    Step 2

    Add the custom macro my_cc_library. This macro will create a base library and then use the rule my_cc_slim_library to strip out the unwanted header dependencies.

    def my_cc_library(
        name,
        hdrs = [],
        deps = [],
        impl_deps = [],
        **kwargs):
      native.cc_library(
          name = name + "-base",
          hdrs = hdrs,
          deps = deps + impl_deps,
          **kwargs)
      my_cc_slim_library(
          name = name,
          hdrs = hdrs,
          base_library = name + "-base",
          deps = deps,
      )
    

    Example

    Now here's how to use it in the example.

    bazel/my_cc_library.bzl <- contains the custom rule and macro

    a/a.h

    int do_a();
    

    a/a.cc

    #include "a/a.h"
    
    int do_a() {
      return 1;
    }
    

    a/BUILD

    package(default_visibility = ["//visibility:public"])
    
    cc_library(
        name = "a",
        hdrs = [
            "a.h",
        ],
        srcs = [
            "a.cc",
        ],
    )
    

    b/b.h

    int do_b();
    

    b/b.cc

    #include "a/a.h"
    
    int do_b() {
      return do_a() + 2;
    }
    

    b/BUILD

    load(
        "//bazel:my_cc_library.bzl",
        "my_cc_library",
    )
    
    package(default_visibility = ["//visibility:public"])
    
    my_cc_library(
        name = "b",
        hdrs = [
            "b.h",
        ],
        srcs = [
            "b.cc",
        ],
        impl_deps = [
            "//a:a",
        ],
    )
    

    main.cc

    #include <iostream>
    
    // #include "a/a.h"   <- this would cause an error
    #include "b/b.h"
    
    int main() {
      std::cout << do_b() << std::endl;
      return 0;
    }
    

    BUILD

    cc_binary(
        name = "exe",
        srcs = [
            "main.cc",
        ],
        deps = [
            "//b:b",
        ],
    )