Skip to content

Bazel

Multilingual benchmarking project => Bazel for advanced engineering

caption

In her one year, Molly saw many more exciting places than I did until I was about 28. She does pretty well :-D

When working on performance experiments across C++ and Go, you obviously need a multilingual project structure. There were two paths forward: create separate build systems under a shared repository, or consolidate everything under a single, coherent framework. Bazel made that decision easy.

Using Bazel to unify builds isn’t just convenient—it should be the default choice for any serious engineering effort that involves multiple languages. It eliminates the friction of managing isolated tools, brings deterministic builds, and handles dependencies, benchmarking, and cross-language coordination with minimal ceremony.

Here’s why Bazel makes sense for performance-critical, multilingual projects like this one—no fragile tooling, no redundant setups, just clean integration that scales.

How to compile C++ in 2025. Bazel or CMake?

caption

Today, we’re examining two modern build systems for C++: CMake, the industry favorite, and Bazel, a powerful alternative. While CMake is often the default choice, I believe that approach warrants a bit more scrutiny—after all, we’re focusing on modern tools here (yep, not counting Make, right?). To explore this, I’ve created a practical demo project showcasing how both systems manage a real-world scenario.

Using the maelstrom-challenges project as a starting point, I’ve extracted a C++ library called maelstrom-node. This library has been set up to work seamlessly with both Bazel and CMake, giving us a hands-on comparison of their approaches, strengths, and quirks.

The Project Structure

Here’s what the final directory layout for maelstrom-node looks like:

Managing Multi-Language Projects with Bazel

caption

In today’s software development landscape, it’s rare to encounter a project built with just one programming language or platform. Modern applications often require integrating multiple technologies to meet diverse requirements. This complexity is both a challenge and an opportunity, demanding robust tools to manage dependencies, builds, and integrations seamlessly. Bazel, a powerful build system, is one such tool that has proven invaluable for multi-language projects.

Recently, I decided to extend my Maelstrom challenges with a C++-based test to explore how Bazel can simplify managing multi-language dependencies and streamline development workflows.

Why Bazel for Multi-Language Projects?

Bazel’s design philosophy emphasizes performance and scalability, making it an excellent choice for projects that involve multiple languages. With its support for Bazel modules, adding dependencies is as simple as declaring them in a MODULE.bazel file. For example, integrating the popular logging library spdlog is straightforward:

Bazel and Rust: A Perfect Match for Scalable Development

caption

Bazel never fails to impress, and its support for Rust demonstrates its versatility and commitment to modern development. Two distinct dependency management modes—Cargo—based and pure Bazel—allow developers to tailor workflows to their projects' needs. This adaptability is particularly valuable for integrating Rust applications into monorepos or scaling complex systems. I decided to explore how Bazel supports Rust, including managing dependencies, migrating from Cargo.toml to BUILD.bazel, and streamlining integration testing.

Harnessing Cargo-Based Dependency Management

Bazel’s ability to integrate with Cargo, Rust’s native package manager, is a standout feature. This approach preserves compatibility with the Rust ecosystem while allowing projects to benefit from Bazel’s powerful build features. By using rules_rust, a Bazel module can seamlessly import dependencies defined in Cargo.toml and Cargo.lock into its build graph.

Returning to Rust: A Journey Through Tooling, Performance

When I started tackling the Maelstrom challenges, my initial thought was to use C++. It’s a language I know inside out, and its performance is hard to beat. However, as I contemplated setting up the project, I realized I couldn’t justify fighting with the C++ pipeline for free. Crafting a proper CMake or Bazel configuration might be worthwhile for large-scale projects or when compensated, but for personal experiments? It’s an unnecessary headache.

Why Go is My Default Choice

For most non-performance critical scenarios, Go is my default, no-brainer choice. It has a clean build system, excellent tooling, and a developer experience that doesn’t make me dread the setup process. Go’s simplicity allows me (and any level team) to focus on solving the problem rather than wrestling with the environment. Yet, this time, I decided to take a different path.