Search code examples
c++buildc-preprocessorbazelbazel-rules

How to configure a header based on the sources that include it in bazel?


I want to have a target that has such defines attribute that changes based on the current package I'm in. Something along the lines:

A.BUILD:

cc_library(
  name = "A",
  hdrs = ["global_config.h"],
  defines = select({
    "???current package = B???": ["-DFOO"],
    "???current package = C???": ["-DBAR"],
  })
)

B.BUILD:

cc_library(
  name = "B",
  srcs = ["b_including_global_config.cpp"],
  deps = ["//some/path/to/A.BUILD:A"], # we define -DFOO for B
)

C.BUILD:

cc_library(
  name = "C",
  srcs = ["c_including_global_config.cpp"],
  deps = ["//some/path/to/A.BUILD:A"], # we define -DBAR for C
)

Is this possible in bazel?

The end goal is to have a global config header file that I can configure (#ifdef FOO) based on the location of the sources that include it. So B and C here contain sources that both include some common header from A, and this header from A contains #ifdef FOO and #ifdef BAR. Any other solution to the original problem is also welcome.


Solution

  • Well, that (as described) cannot be done in bazel (or I suppose any sane build system for that matter), because provider of the dependency (again generally) does not and should not really have any knowledge of the code that depends on it. Having such bi-directionality in dependencies graph would sort of defeat the purpose of individual packages in the first place. If A and B are tightly coupled in both direction, they should not really be A and B, but just one X. I gain little from the "granularity", while I add complexity to my setup.

    TBH I would question "globalness" of a config based essentially on inclusion in a single (global) file, but in fact still being separate configs maintained in different parts of said file and selected (e.g. here) with preprocessor conditionals.

    That said... if you are really sure about the design, I'd say, describe that in your sources. I.e. have b_including_global_config.cpp: #define FOO before inclusion of global_config.h. And similarly do #define BAR in c_including_global_config.cpp. It's not like these are really independent packages (sources) agnostic of each other anyways at this point? You could still do that as copts (that's another can of worms) at the point of consumption / inclusion (here in B and C) as well, but scattering sources across... well sources and build description would again seem to mostly just add complexity / confusion for no obvious gain?

    I would most likely leave just global / common parts in global_config.h and put whatever stuff is only relevant to B in B and what is relevant to C in C.

    If there is some level of reuse lost to a simplified example, e.g. let's assume three consumers B, C, and D where B and D share bits and C and D do share other bits. I could have a "truly" global_config.h and then there could be shared_config_b_and_d.h and shared_config_c_and_d.h... I would have three header cc_libraries (or one with three header files) and depend on them and include header files accordingly. Yes, it does add a line (or some) to the BUILD config / package source file(s), but that (description of dependencies) is mostly one time cost. And it does make the tree a lot easier to follow when I try to read it and understand what goes into which part (which is btw why I would also generally use select() sparingly as tempting as it may be to have a "concise" build description).