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

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:

Continue reading

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.
Continue reading

Перенаправление временных файлов CMake

CMake, конечно, прекрасен, но мне совершенно не нравится его особенность “гадить” в директорию из которой он был запущен. Какого-либо явного способа сказать CMake – положи все свои промежуточные файлы в директорию XYZ нет, кроме не документированного ключа с кривоватым поведением. В итоге, почти все виденные мной основанные на CMake проекты просто не парятся и мирятся с тем мусором, что образуется у них в корне. Можно, конечно, мириться, но есть и варианты

По большому счету варианта два и оба они не кроссплатформенные: написать скрипты – запускалки (sh и bat) или написать Makefile. Вариант со скриптами на мой взгляд более кривой, так как требует реализации того функционала, который уже предлагается Make. Так что я остановился на Makefile следующего содержания:

SHELL := /bin/bash
RM    := rm -rf
MKDIR := mkdir -p

all: ./build/Makefile
    @ $(MAKE) -C build

./build/Makefile:
    @  ($(MKDIR) build > /dev/null)
    @  (cd build > /dev/null 2>&1 && cmake ..)

test:
    @  (cd $(BUILD_DIR) > /dev/null && ctest -L unit --verbose)

clean:
    @ $(MAKE) -C $(BUILD_DIR) clean

distclean:
    @  ($(MKDIR) build > /dev/null)
    @  (cd build > /dev/null 2>&1 && cmake .. > /dev/null 2>&1)
    @- $(MAKE) --silent -C build clean || true
    @- $(RM) ./build/Makefile
    @- $(RM) ./build/src
    @- $(RM) ./build/test
    @- $(RM) ./build/CMake*
    @- $(RM) ./build/cmake.*
    @- $(RM) ./build/*.cmake
    @- $(RM) ./build/*.txt

И теперь мне ни мусор не досаждает, ни проблем с вызовом тестов/перегенерацией основного рабочего Makefile нет. Ну и заодно добавил генерацию запускалки в свой gen-cmake.

Да, еще полезно будет поправить сам CMake файл, запретив генерацию временных файлов в корневую директорию следующим образом (добалять в самое начало CMakeLists.txt):

if ( ${CMAKE_SOURCE_DIR} STREQUAL ${CMAKE_BINARY_DIR} )
    message( FATAL_ERROR "In-source builds not allowed. Please make a new directory (called a build directory) and run CMake from there. You may need to remove CMakeCache.txt." )
endif()

Генератор CMakeLists.txt файлов

Довольно часто возникает необходимость быстренько написать тестовое приложение на C++ и опробовать в нем что-то. IDE я не слишком люблю, а каждый раз где-то выискивать завалявшийся шаблон к CMake-у довольно лениво. После очередных поисков запилил небольшой вспомогательный скриптик (само собой на Python) для генерации CMakeLists.txt.

На данный момент поддерживается только генерация приложений, как надоест конвертировать приложения в библиотеки, так будут и они генериться

Сам скриптик с руководством по использованию тут: https://github.com/astavonin/gen-cmake

C++XX в CMake

Вообще я очень люблю использовать CMake для создания различных небольших тестов. Собирается везде, ручной работы ощутимо меньше, чем если писать правила для Make, генерируется поддержка для любой IDE (если тестик в что-то более крупное перерастет и т.д). И как-то меня угораздило “проспать” как CMake 3.x так и довольно полезную фифу в нем – простое и понятное подключение поддержки C++11. Я всегда подключал C++11 по старинке:

list( APPEND CMAKE_CXX_FLAGS "-std=c++11")

Но, оказывается-то, прогресс шагнул далеко вперед! так что для тех кого так же как и меня “заморозили” сообщаю – все стало проще и понятнее:

cmake_minimum_required(VERSION 3.1 FATAL_ERROR) # (1)
project(cpp11_test)

add_executable(cpp11_test main.cpp)

set_property(TARGET ${PROJECT_NAME} PROPERTY CXX_STANDARD 11)          # (2)
set_property(TARGET ${PROJECT_NAME} PROPERTY CXX_STANDARD_REQUIRED ON) # (3)

Прекрасная фича доступна начиная с CMake 3.1 1 и включается она 2 очень просто. Если какие-то обходные пути при отсутствии у компилятора поддержки C++11 не планируются, то стоит объявить 3 наличие поддержки C++11 обязательной.

Сборка и тестирование проекта при помощи CMake и CTest.

Статья дико устарела. Новая, актуальная на 2025 год версия: How to compile C++ in 2025. Bazel or CMake?

Думаю что большинство C++ разработчиков так или иначе сталкивались с CMake, в то же время что такое CTest и как с его помощью можно автоматизировать модельное тестирование знают далеко не все.
Для того, что бы показать как можно использовать CMake и CTest вместе, я в качестве примера создал маленькую библиотечку, состоящую из пары строк кода, которое умеет складывать и делить Кроме приложений CMake и CTest, понадобится какая-либо UnitTest библиотека (в данном примере я взял Boost.Test, хотя, можно взять и GoogleTest). Тут стоит обратить внимание на то, что CTest не является фрэймворком для написания тестов. CTest – это приложение для запуска тестов и, если это необходимо, отправки результатов в какое-либо из поддерживаемых хранилищ. Continue reading

Внешние проекты в CMake

Я просто весь мозг сломал со следующей ситуцией. Есть CMake проект состоящий из нескольких библиотек и исполнимого модуля. Этот проект использует некую внешнюю по отношению к нему библиотеку. В идеале, в процессе сборки, эту библиотеку надо скачать, собрать, установить и заюзать.
Перечирав кучу документации и устав от эксперементов, я наткнулся на чудесную функцию externalproject_add.

externalproject_add(
    memtree_external
    GIT_REPOSITORY "git@github.com:astavonin/memtree.git"
    CMAKE_ARGS
        -DCMAKE_INSTALL_PREFIX:STRING=${PROJECT_BINARY_DIR}
    UPDATE_COMMAND ""
)
add_library(memtree SHARED IMPORTED)
set_property(TARGET memtree PROPERTY
    IMPORTED_LOCATION ${CMAKE_BINARY_DIR}/lib/libmemtree.a)
include_directories(${PROJECT_BINARY_DIR}/include)

Для того чтобы установка отрабатывала корректно, пришлось добавить несколько строк в CMakeLists.txt

install(FILES ../include/memtree/memtree.h DESTINATION include/memtree)
install(TARGETS memtree DESTINATION lib EXPORT memtree-targets)
install(EXPORT memtree-targets DESTINATION lib/memtree)

Уфф. Убил на это кучу времени, но оно того стоит – обновлять библиотеку теперь куда проще

CMake & Clang

Решил изменить сборку с идущего по умолчанию GCC на Clang для CMake проекта. Сходу наткнулся на граблю. Стандартная команда CMake:

set(CMAKE_CXX_COMPILER clang++)
set(CMAKE_C_COMPILER clang)

Вводит его в вечный цикл, который выглядит как-то так:

You have changed variables that require your cache to be deleted.
Configure will be re-run and you may have to reset some variables.
The following variables have changed:
CMAKE_C_COMPILER= /usr/bin/gcc
CMAKE_CXX_COMPILER= /usr/bin/c++

-- The C compiler identification is GNU
-- The CXX compiler identification is GNU
-- Checking whether C compiler has -isysroot
-- Checking whether C compiler has -isysroot - yes
-- Checking whether C compiler supports OSX deployment target flag
-- Checking whether C compiler supports OSX deployment target flag - yes
…и так до бесконечности…

Лечится не удобно, но юзабельно:

export CXX=/usr/bin/clang++
export CC=/usr/bin/clang
cmake CMakeLists.txt
make