Is it possible to use C++20 modules in bazel with custom rules?

1.8k views Asked by At

Bazel does not have direct support for modules (See Issue #4005).

However, it is possible to provide bazel with a custom CROSSTOOL.

From https://docs.bazel.build/versions/0.22.0/crosstool-reference.html:

By default, Bazel automatically configures CROSSTOOL for your build, but you have the option to configure it manually.

And it's possible to extend bazel with custom rules.

From https://docs.bazel.build/versions/master/skylark/rules.html:

A few rules are built into Bazel itself. These native rules, such as cc_library and java_binary, provide some core support for certain languages. By defining your own rules, you can add similar support for languages and tools that Bazel does not support natively.

And this comment on Bazel's module issue suggests that you can use a custom CROSSTOOL to support modules even without native support:

everything regarding modules (only for clang) is open sourced already. The only missing piece is a CROSSTOOL that makes use of them and provides all necessary features.

Could anyone show how to write a custom CROSSTOOL for clang and how you can use it to write a custom C++ rule for modules (e.g. cc_module) so that you can do things like this:

Write a basic module

// helloworld.cc
module;
#include <stdio.h>

export module helloworld;
export void hello();

module :private;
void hello() { puts("Hello world!"); }

Use the module

// main.cc
import helloworld;
int main() { hello(); }

Integrate the parts into the build system

cc_module(
   name = "helloworld",
   srcs = ["helloworld.cc"],
) # Compiles to a precomiled module file (PCM)

cc_binary(
  name = "main",
  srcs = [
    "main.cc",
  ],
  deps = [
     ":helloworld",
   ],
) # Compiles against the helloworld PCM
1

There are 1 answers

0
Ryan Burn On BEST ANSWER

Yes, it is. Here's an outline of how to do it.

Add a custom provider for tracking module information:

ModuleCompileInfo = provider(doc = "", fields = [
    "module_name", 
    "module_file",
    "module_dependencies",
])

Add a custom rule cc_module for producing C++20 modules. Then you can write something like

cc_module(
   name = "A",
   src = "a.cc", # a.cc exports the module A
   impl_srcs = [
     "a_impl1.cc", # optionally you can provide additional implementation sources for A
     "a_impl2.cc",
   ],
   deps = [
     ":B", # dependencies can be either other modules or other cc_libraries
     ":C",
   ],
)

The custom rule will

  1. Create a module map for the module dependencies of A
  2. Produce both a static library packaging A's objects and a cmi file for A's module
  3. Return both a CcInfo provider for A and a ModuleCompileInfo provider to track the module information.

Because other standard bazel rules (e.g. cc_binary, cc_library), aren't aware of c++20 modules, you also need to provide custom cc_module_binary and cc_module_library rules so that you can make use of modules with other C++ constructs. For example,

cc_module_binary(
   name = "exe",
   srcs = ["main.cc"],  # main.cc can import A
   deps = [":A"],       # we can depend on modules
)

See https://github.com/rnburn/rules_cc_module for a project that provides c++20 module rules. And see here for examples of how to use it.