Skip to content

Conversation

@PikachuHyA
Copy link
Contributor

No description provided.

@PikachuHyA
Copy link
Contributor Author

hi @comius @trybka @fmeum @ChuanqiXu9

I would like to invite you to review and discuss a prototype implementation for C++23 standard library module (import std;) support in rules_cc. This is an important feature for modern C++ development, and I believe it would benefit from community input on the design approach.

Overview

This implementation enables Bazel projects to use C++23's standard library modules (import std;) by automatically exposing the std module from libc++ as a Bazel target (@local_config_cc//:std_modules). The approach integrates with the existing C++20 modules infrastructure in Bazel and rules_cc.

Implementation Details

The implementation consists of modifications to rules_cc's toolchain configuration:

1. Toolchain Template Changes (cc/private/toolchain/BUILD.tpl)

Added a template placeholder %{std_module} that will be substituted with a cc_library target definition for std modules:

%{std_module}

2. Toolchain Configuration Changes (cc/private/toolchain/unix_cc_configure.bzl)

a) Added helper function to locate libc++ std module files:

def _get_share_libcxx_v1_dir(repository_ctx, cc, additional_flags = []):
    return repository_ctx.execute([cc, "-print-resource-dir"] + additional_flags).stdout.strip() + "/../../../share/libc++/v1"

b) Symlink std module files during toolchain setup:
When configuring the toolchain for Clang, the implementation:

  • Locates the libc++ v1 directory (where std module files are typically installed)
  • Symlinks all std module files (including std.cppm) into the repository
  • Makes these files available for the build system

c) Template substitution for std module target:
The %{std_module} placeholder is replaced with:

cc_library(
    name = "std_modules",
    srcs = glob(["std/**"]),
    module_interfaces = ["std.cppm"],
    features = ["cpp_modules"],
)

This creates a @local_config_cc//:std_modules target that users can depend on in their BUILD files.

Usage

Once the toolchain is configured, users can use standard library modules by:

  1. Adding @local_config_cc//:std_modules as a dependency
  2. Enabling the cpp_modules feature
  3. Using import std; in their C++ code

Example BUILD file:

cc_binary(
    name = "hello",
    srcs = ["main.cc"],
    features = ["cpp_modules"],
    deps = ["@local_config_cc//:std_modules"],
)

Example C++ code:

import std;

int main() {
    std::println("Hello, world!");
    return 0;
}

Demo Repository

I've created a comprehensive demo repository showcasing various use cases:

Repository: https://github.com/PikachuHyA/bazel_cxx20_modules_demo/tree/main/hello_std_module

The demo includes examples for:

  • Basic import std; usage
  • Modules that import std and are imported by other code
  • Transitive std module dependencies through module chains
  • Template modules using std algorithms
  • Multi-source modules (separate interface/implementation files)
  • Mixing modules with traditional header files

Each example includes working BUILD files and source code, demonstrating the integration works across different scenarios.

Design Considerations and Questions

I'd appreciate feedback on several design aspects:

  1. Module file location: The current implementation assumes std module files are in $RESOURCE_DIR/../../../share/libc++/v1. Is this the right approach, or should we support other locations/configurations?

  2. Symlink strategy: Currently, all files from the libc++ v1 directory are symlinked. Should we be more selective, or is this approach acceptable?

  3. Target naming: Is @local_config_cc//:std_modules an appropriate name, or would something like @local_config_cc//:std be better?

  4. Feature flags: Should std module support be gated behind a feature flag, or enabled automatically when cpp_modules is enabled?

  5. Cross-compilation: How should this work for cross-compilation scenarios? Are there additional considerations needed?

  6. GCC support: The current implementation is Clang-specific. What would be needed to support GCC's std module implementation?

  7. Error handling: Should we add validation to ensure the std module files exist and are accessible?

Testing

The implementation has been tested with:

  • Clang 20 (required for C++23 import std; support)
  • Ubuntu 24.04 LTS
  • Bazel with C++20 modules support (commit 60b1e19 or later)

Next Steps

I'm open to suggestions on:

  • Refining the implementation approach
  • Adding additional features or safeguards
  • Improving error handling and diagnostics
  • Documentation requirements
  • Testing strategies

I believe this feature would be valuable for the C++ community using Bazel, and I'm happy to iterate on the design based on your feedback. Please feel free to review the code changes and demo, and let me know your thoughts on the approach.

Thank you for your time and consideration!

write_builtin_include_directory_paths(repository_ctx, cc, builtin_include_directories)
if is_clang:
libcxx_v1_dir = _get_share_libcxx_v1_dir(repository_ctx, cc)
files = repository_ctx.execute(["ls", libcxx_v1_dir]).stdout.strip().split("\n")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can use repository_ctx.path(...).readdir() instead to list files within Starlark.

),
"%{std_module}": """
cc_library(
name = "std_modules",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that the std module has to be passed into the cc_toolchain in some way so that users can get the module for their current toolchain, not the host toolchain. That's why exposing this target directly wouldn't work well.

If it's common for folks to import std, could we add this dependency automatically to all module_interfaces? Or should this be toggleable on the toolchain? Requiring an explicit dep on std could become tedious.

@ChuanqiXu9
Copy link

Build system should find the std module by -print-library-module-manifest-path or -print-file-name=libc++.modules.json or -print-file-name=libstdc++.modules.json .

The model of cmake is, (and the model of SG15 in my mind), if the build system find any user's file import std (or std.compat), the build system will create the std module target implicitly (from the above mechanism) if the std module target haven't been created. Then the build system will append it to the dependent list of the target of that file.

A tricky part is, given there are two targets, both of them import std, but one target is compiled with -std=c++23 and the other is compiled with -std=c++26, ideally the build system should provide different BMIs compiled with -std=c++23 and -std=c++26 and send them to the different targets. This is tricky as it asks the build systems to parse and analyze options.

So it might be fine enough to provide a doc for users to tell them how to wrap a std module target by themselves. And bazel can provide a special value (maybe std_module_paths, a list) to represent the path to the std module (by the above mechanism) so the users can save a lot of work. It may look like:

cc_binary(
    name = "hello",
    srcs = ["main.cc"],
    module_interfaces = @std_module_paths, # or anything else, I am not familiar with bazel's grammar 
    features = ["cpp_modules"],
)

or

cc_library(
     name = "std_module",
     srcs = [],
     module_interfaces = @std_module_paths,
     features = ["cpp_modules"],
)

cc_binary(
    name = "hello",
    srcs = ["main.cc"],
    features = ["cpp_modules"],
    deps = [":std_module"],
)

This is not super wonderful. But it saves the work for build system guys and leave the flexity and complexity to users.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants