How do you handle include dependencies passed as #defines in Bazel?

763 Views Asked by At

I am attempting to build a legacy C/C++ embedded code base using Bazel. The code is separated into software sets. Because the system is embedded, there is an environment header include passed as an argument to the compiler for each software set. The header path is defined using a #define:

The source file of software_set_b may begin with:

#include MY_ENV

The compile instruction would define MY_ENV to the absolute path of the environment header in software_set_a, e.g.:

gcc -DMY_ENV=/path/to/software_set_a/headers/MyEnvironmentHeader.h

Is it possible to achieve this using Bazel without explicitly passing /path/to/software_set_a/headers/MyEnvironment.h in the --define argument in bazel build, or hardcoding the value in software_set_b's BUILD file, e.g.:

cc_library(
  name = 'software_set_b',
  defines = [
    'MY_ENV=/path/to/software_set_a/headers/MyEnvironment.h'
  ],
  ...
)

Ideally, the programmer could select the package with an argument, e.g. bazel build //:software_set_b --//:from_env=software_set_a with a fragment similar to the following in the BUILD script:

File: software_set_b/BUILD

string_flag(
  name = 'from_env',
  build_setting_default = ''
)

def deps_from_env():
  from_env = get_flag_value('from_env') # A function that gets the value of the flag.
  return '@' + from_env + '//:env' # Evaluate to e.g. '@software_set_a//:env'

cc_library(
  name = 'software_set_b',
  deps = [
    deps_from_env()
  ]
)

File: software_set_a/BUILD

cc_library(
  name = 'env',
  defines = [
    # Something to give me '/path/to/software_set_a/headers/MyEnvironment.h'
    'MY_ENV=$(rootpath)/headers/MyEnvironment.h'
  ],
  ...
)
1

There are 1 best solutions below

1
On BEST ANSWER

This is relatively simple to do e.g. Create an env target under both software_set_a and software_set_b.

# File: //software_set_a:BUILD
cc_library(
  name = 'env',
  defines = [
    'MY_ENV=$(rootpath)/headers/MyEnvironment.h'
  ],
)

cc_library(
  name = 'software_set_a',
  deps = [
    # Top level switchable target. NOTE: This is NOT ":env".
    "//:from_env",
  ]
)
# File: //software_set_b:BUILD
cc_library(
  name = 'env',
  defines = [
    'MY_ENV=$(rootpath)/headers/MyEnvironment.h'
  ],
)

cc_library(
  name = 'software_set_b',
  deps = [
      # Top level switchable target. NOTE: This is NOT ":env".
      "//:from_env",
  ]
)

Now of course by itself this won't work. So we need to create a switchable top level env target.

# //:BUILD
label_flag(
   name = "from_env",
   build_setting_default = "//software_set_b:env",
)

Now by default the env defines will be pulled from software set b. But can be overridden with the command.

bazel build //:software_set_b --//:from_env=software_set_a:env

Note that you can have an empty default by simply creating a cc_library(name="empty") and pointing the default_build_setting at that. Further, you might find the pattern of configuration around constraint_setting/constraint_value/platform/select to be a more useful pattern for you, rather than your flag based approach. Also note that you can combine the two by pointing the label flag at a selectable multiplexer target. A more advanced example can be found in bazelembedded/rules_cc_toolchain under the files;

  • config/rules_cc_toolchain_config.BUILD
  • config/BUILD.bazel