Я много работаю с CMake. И периодически сталкиваюсь с довольно сложными и запутанными CMake-скриптами. Долгое время я использовал логи, чтобы разобраться в их работе и выполнить отладку. Позже обнаружил, что в CMake есть встроенный отладчик и профилировщик, которые сильно упрощают процесс отладки. Кажется, не все знают об их существовании и о том, как с ними работать, поэтому я решил написать эту статью.

Отладка CMake-файлов
Начиная с версии 3.27 и выше, в CMake появилась возможность отладки.
Скачать нужную версию CMake можно отсюда (или собрать из исходников). Более новую версию CMake допустимо использовать, даже если ваши CMake-файлы написаны для старой версии (но будьте осторожны).
Если по какой-то причине вы не можете использовать CMake 3.27 и выше, то читайте вторую половину статьи.
Как настроить и использовать отладчик, я покажу на примере VSCode. Но полученную информацию вы сможете применить и в других IDE (например, CLion и Visual Studio)
Чтобы запустить отладку в VSCode:
Установите расширение CMake Tools.
Откройте необходимый вам проект.
Нажмите
Ctrl+Shift+P.В появившемся окне введите
cmake debugger.-
После чего вы должны увидеть два пункта меню:

Разница между пунктами только в том, что
Delete Cache…удаляет файлCMakeCache.txtи выполняет повторную конфигурацию проекта. Выберите необходимый вариант и нажмите
Enter.VSCode может дополнительно попросить указать компилятор и путь к исходникам. После этого отладчик должен запуститься.
Теперь вы можете пошагово отлаживать код, устанавливать breakpoint’ы и просматривать значения переменных:

К сожалению, с помощью отладчика нельзя отладить генераторные выражения.
Если отладка не запустилась
Если отладка не запускается или завершается с непонятной ошибкой, откройте вкладку OUTPUT в VSCode. Там выводится CMake-команда, которую использует отладчик:

По сути, плагин просто запускает cmake с необходимыми для отладки флагами, а затем подключается к его отладчику по DAP.
Обратите внимание на пути после флагов -S и -B:
-Sуказывает на папку с исходным кодом проекта;-Bуказывает на папку, куда помещаются файлы сборки.
Если эти пути отличаются от ваших, то их можно исправить в настройках VSCode. Для этого установите значения cmake.sourceDirectory и cmake.buildDirectory соответственно.
Также обратите внимание на флаг -G. Он определяет, какой используется генератор сборочной системы (Unix Makefiles, Ninja и другие). Его можно настроить с помощью опции cmake.generator в VSCode.
Если сборка вашего CMake-проекта должна запускаться с дополнительными параметрами, то необходимо перечислить их в cmake.configureArgs:

Или собрать проект привычным способом, а затем запустить отладчик, выбрав CMake: Configure with CMake Debugger. В этом случае отладчик будет работать с уже сконфигурированным проектом и переменными, сохраненными в CMakeCache.txt.
Если все настроено правильно, но отладка так и не заработала, сообщите об этом в комментариях — попробуем разобраться вместе.
Профилирование CMake
С версии CMake 3.18 также доступен профилировщик. С его помощью можно проанализировать последовательность выполнения команд, просмотреть вложенные вызовы и измерить время их работы.
Чтобы включить профилирование, добавьте в вашу команду генерации сборочной системы CMake следующие опции: --profiling-format=google-trace и --profiling-output=fileName.
Например, в результате вызова этой команды будет сгенерирован файл с именем trace:
cmake -B my_build --profiling-format=google-trace --profiling-output=trace
Для просмотра trace-файла можно использовать любой chrome-based браузер или сайт ui.perfetto.dev.
Я покажу на примере Chrome. Откройте браузер, введите в адресной строке about:tracing и нажмите Enter. На открывшейся странице, нажмите кнопку Load и выберите trace-файл.
После этого файл должен открыться, и вы сможете просмотреть трейс:

Если есть темы в CMake, которые вызывают у вас боль, пишите в комментариях, и я постараюсь рассказать о них в следующих статьях.
Комментарии (24)

user-book
26.09.2025 08:30Ого, не знал даже о таком, хотя это и выглядит как "мы добавили компилятор в компилятор.."
Из личного опыта cMake уже давно шагает куда-то не туда. Он должен удобно описывать сборку, а не превращаться в надстройку и еще один язык программирования со своими особенностями и подводными
Для себя сформировал такую линейку сложности сборки:
bash - все просто и понятно. Даже если чего то не хватает или криво, то ты это поймешь. Проблемы могут быть но очень редко нерешаемые.
Makefile - эдакий наследник баш-скриптов. Сложности могут быть и первым камнем преткновения может вылезти то, как и откуда запускается этот самый мейкфайл в системе (что проблема потому как даже явное прописывание параметров запуска может не помочь по причинам). Даже если какие-то проблемы с мейкфайлом, всегда можно его "руками" упростить до простых консольных команд
cMake - оставь надежду всяк сюда всходящий. Версии, особенности, подпараметры и прочая срань. А еще подводные зависящие от того, как и где запускают и в каком именно режиме. А еще они очень легко превращаются в огромную запутанную вермишель кода, вермишель которая очень легко позволяет подключать файлы зависимостей из-за чего запутанность только преумножается
Жаль (и иронично) что лучшее на рынке, что придумали для сборки сложных и разноплановых сишников это обертки на питоне.

alexey_lukyanchyk Автор
26.09.2025 08:30Он должен удобно описывать сборку, а не превращаться в надстройку и еще один язык программирования со своими особенностями и подводными
Отчасти я согласен. Но CMake изначально был надстройкой для упрощения кроссплатформенной сборки (аналог automake/autoconf).
Сейчас в CMake и правда добавили очень много всего. Но я не сказал бы, что это проблема. Ведь необязательно использовать весь функционал CMake. Что бы сделать простейший C/C++ проект, хватит CMake скрипта из трех команд: `project()`, `cmake_minimum_required()` и `add_executable()`.
Версии, особенности, подпараметры и прочая срань.
Да, с обратной совместимостью и правда бывают нюансы. Еще есть проблема того, что концепции использования CMake периодически меняются. И одни рекомендованные практики заменяются другими.
А еще подводные зависящие от того, как и где запускают и в каком именно режиме. А еще они очень легко превращаются в огромную запутанную вермишель кода, вермишель которая очень легко позволяет подключать файлы зависимостей из-за чего запутанность только преумножается
Думаю это сильно зависит от того, кто пишет CMake скрипты. На любом языке можно написать плохой код =)
Жаль (и иронично) что лучшее на рынке, что придумали для сборки сложных и разноплановых сишников это обертки на питоне.
Интересно, с таким я не сталкивался. Может есть примеры "оберток на Python для сборки сишников"?

user-book
26.09.2025 08:30Интересно, с таким я не сталкивался. Может есть примеры "оберток на Python для сборки сишников"?
то же platformio это чистый питон
Китайцы если соизволяют выложить в открытый доступ какие-то средства для сборки и отладки, то они на питоне в 9 случаев из 10 (исключения это чисто сишные тулзы)
По работе много раз сталкивался с тем что версионность накручивали на питоне как самый быстрый и безболезненный способ раскатывать сборки на разных платформах и под разные версии железок
Думаю это сильно зависит от того, кто пишет CMake скрипты. На любом языке можно написать плохой код =)
Я до сих пор вспоминаю SDK для poketbook которое не удалось завести даже в режиме отладки просто потому что иди нафиг, вот почему) Нигде больше не встречал настолько запутанных макарон для сборки (благо есть готовые докер образы для работы с этой сранью)

alexey_lukyanchyk Автор
26.09.2025 08:30platformio
Точно, забыл про него. Там они используют SCons. Он конечно написан на Python, но все же это не совсем "python скрипты для сборки". Хотя штука интересная, правда я никогда не использовал ее вне `platformio`.
Мне просто интересно, что именно делают в python скриптах. Делают подобие bash скриптов для вызова компилятора?
Китайцы если соизволяют выложить в открытый доступ какие-то средства для сборки и отладки
Вполне может быть. Хотя что касается китайских микроконтроллеров, то там чаще выкладывают проекты для Keil/IAR/CubeIDE.

user-book
26.09.2025 08:30при всей моей нелюбви к питону (и в VSC), platformio получился неплохим. Его возможности встраивания скриптов прямо в сборку и не только (логи например) прям киллер-фича.
в целом на питоне "рожают" мекйфал. То есть что бы одна команда на сборку вызывала сборку, сама определяя все что надо и в случае проблем говоря о них прямым текстом. На втором месте всякие тулзы для проверок, первичных заливок и тд, но опять таки все в одном месте и в рамках одного исходника который одинаково запускается на любой платформе без сюрпризов.

alexey_lukyanchyk Автор
26.09.2025 08:30при всей моей нелюбви к питону (и в VSC), platformio получился неплохим.
Тут я полностью солидарен =)

TimurZhoraev
26.09.2025 08:30Проблема в CubeIDE и прочих визуалках без обратной связи из исходного кода "наверх" - невозможность динамической реконфигурации. Иными словами, допустим, в какой-то момент необходимо переключить ШИМ в шестишаговый режим, пробросить UART сделав Remap пинов, по ходу дела менять режимы DMA например. То есть система программируется изначально как нечто статическое. Конечно для большинства простых задач этого хватает но на специализированных применениях это может создать больше проблем. Плюс HAL/LL вместо регистров это отдельная тема, попытка языковыми средствами из 70-х с надеждой на упорство компилятора создать абстракцию, вроде как ООП на С, и да и нет, в итоге проще сделать какой-нибудь кодогенератор и единственный заголовочник.

TimurZhoraev
26.09.2025 08:30Что касается сборки на С, когда надоело каждый раз переписывать в h-файлах прототипы функций, смотреть на ворнинги и эрроры, сделал perl-скрипт, который сканирует все C, достаёт оттуда прототипы структур/функций (написанных в едином стиле дабы не усложнять парсер до уровня bison/flex) и кладёт в единый заголовочник. Перед сборкой выполняется этот скрипт который вытаскивает из исходников необходимое. Для идентификатора функции использовал пустой макрос DEFUN. Именно в этом русле языки программирования до сих пор не обзавелись встроенными средствами для системы построения. Ничего кроме #pragma и /*-*тут мои настройки-*-*/, всяких __меня_не_трогать__ не подвезли. Хотя вполне могли были быть ключевые слова вроде link, make, debug, version, compile_unit с указанием что эту функцию оптимизировать, эту для отладки, у той одна версия, у этой другая, вот там имя функции юнит теста (а не файла) итд
Как-то так
my ($statick, $curlopenbr) = (0,0,0,0,0,0,0); my $strtag = "";my $strname = "";my $curlword = ""; my $curlclword = ""; my @statarr = (); my $str; my $stracc; foreach my $stra (@raws) { $str = $stra; #search for DEFUN keyword #skip definition if ($str =~ /DEFUN.+\;/g) { $statick = 0; $stracc = ""; next; } if ($str =~ s/(DEFUN.+)\{// and $statick eq 0) { $str = $1; push @statarr,$str; $statick = 0; $stracc = ""; } if ($str =~ s/(DEFUN.+)// and $statick eq 0) { $statick = 1; $stracc = $1." "; next; } if ($str =~ s/(.*)\{// and $statick eq 1) { $stracc = $stracc." ".$1; push @statarr,$stracc; $statick = 0; $stracc = ""; } #accumulate brackets and DEFUN content if ($statick eq 1) { $stracc = $stracc.$str." "; } }
user-book
26.09.2025 08:30у меня за годы работы собралась целая библиотека баш-файлов которые я встраиваю в автоматизации. Чисто по коду очень много что делаю генерируемым и по комит-хукам, например версионность и прочее это с нуля генерируемый *.h файл. Работа с памятью тоже через генератор, где саму структуру я описываю в csv файле.
Руками все писать вы далеко не уедите. За перл хз, собсвено потому и и баш, а не питон, что бы меньше головняка с переносами и развертываниями.

rivo
26.09.2025 08:30Собирал всякую opensource дичь и там часто встречается Meson. Он проще чем СMake и кросс-компиляция проходила всегда легче. Можно его поставить между 2)Makefie и 3)CMake в списке.

equeim
26.09.2025 08:30Он изначально был таким. В последние годы (ну так, как минимум лет десять) его наоборот пытаются повернуть в сторону большей декларативности. Проблема в том что сделать это с сохранением обратной совместимости совсем непросто, и выходит какой-то монстр.
Растовский cargo в этом плане интересен тем что там есть четкое разделение между простой декларативной частью в виде toml конфига, и опциональной кастомной логикой на самом расте. В cmake же пытаются это объединить в одного монстра.

alexey_lukyanchyk Автор
26.09.2025 08:30Проблема в том что сделать это с сохранением обратной совместимости совсем непросто, и выходит какой-то монстр.
Думаю это болезнь любого развивающегося проекта. В целом,политики не плохо справляются с сохранением обратной совместимости.
CMake и правда "в последние годы" стал понятнее и логичнее.

Gordon01
26.09.2025 08:30Все изначально так и было. Мейк, симейк и прочее были разработаны в другую эру, когда сложность была совсем другой, а для установки зависимостей хватало системного пакетного менеджера.
И вот с функцией пакетника или сложной, многоязыковой сборки они не справляются.
И это не лучшее что есть на рынке. Есть bazel, nix, meson. Это для любых языков.
Для раста есть cargo который лучше любого на голову, но любой шаг за пределы растовой экосистемы - и ты уже пишешь свой nix или bazel.
В 2025 без владения инструментами для современных задач сложной сборки никуда. А попытки натянуть старье на новые задачи, а особенно написать сборку на питоне "потому что он простой, понятный и доступный всем" ВСЕГДА заканчиваются переизобретением nix и bazel, которые работают быстрее и решают задачу в несколько строк кода на своём языке, который изначально спроектирован под управление пакетами.
Особенно когда дела доходят до сборки и тестирования в ВМ.
TimurZhoraev
26.09.2025 08:30Тут возникает довольно серьёзная проблема, когда скомпилированный пакет не имеет каких-либо идентификаторов от системы сборки. И нет никаких средств проверить целостность, атрибуты и единство пакета, который был собран самостоятельно и внедрён в систему или локальное окружение помимо репозитория. В основном упор идёт на дату и номер версии, а также то что если изменить 1 байт - это уже новый пакет. Поэтому в системе разрастается зоопарк версий, особенно весело когда программа тянет за собой двоичники необходимых зависимостей, что для .NET что для MSVC redistributable, .deb/.rpm могут притянуть различные только им нужные .so, Python 2.7/3 - уже стало именем нарицательным, если песочница использует символические ссылки - уже достижение. В подавляющем большинстве случаев весь этот довесок летит в Program Files или bin. Вообщем по-всей видимости без инфраструктурных изменений вряд ли что-то можно улучшить существенным образом.

SilverTrouse
26.09.2025 08:30По другому наверное проще сказать в сторону cmake. Он пытается стать пакетным менеджером универсиальным в стиле С++. Поэтому он добавляет куча винтиков для этого, а каждый потом пилит свой пакетный менеджер ( Избыточно), вместо того чтобы быть просто пакетным менеджером. С точки зрения написания простого проекта и привинтить к нему пару тройку зависимостей - очень просто

viordash
26.09.2025 08:30Спасибо, теперь знаю что и vscode это можно, а то раньше сложные cmake в плагине visualgdb отлаживал

alexey_lukyanchyk Автор
26.09.2025 08:30Я сам был сильно удивлен, когда узнал про эти фитчи =)
Про плагин не знал, спасибо. Очень интересно, что статья по вашей ссылке 2018 года. Хотя поддержка отладчика в CMake была добавлена только в 2023 году. Получается Visual Studio сделали какой-то свой отладчик. Хитро.

TimurZhoraev
26.09.2025 08:30CMake медленно но верно как было замечено превращается в уже почти в графическое IDE. Наверняка уже завезли вещи связанные с Git, проверкой версий пакетов онлайн, среды окружения, тестирование самого компилятора на правильность. Но опять-таки краеугольный камень - что он не заходит внутрь файла забрать пользовательские идентификаторы, ориентируется на время/дату и прочие системные штуки при обновлении, не создаёт атрибутов файлов или дампов этапов построения с обработкой исключений при ошибках компиляции с целью не прервать процесс а максимально скомпилировать всё что без ошибок. Ну и разумеется танцы с макросами и зависимыми переменными. Вообщем скорее всего этот инструмент поглотит некий perl/python с явной кодогенерацией по шаблону из мета-языка, пригодного для конкретного применения, включая тот же bash/bat для сборки (локальной/тест/релиз). CMake же пытается объять необъятное и уже требует ненулевой порог входа для обучения.

alexey_lukyanchyk Автор
26.09.2025 08:30ориентируется на время/дату и прочие системные штуки при обновлении, не создаёт атрибутов файлов или дампов этапов построения с обработкой исключений при ошибках компиляции с целью не прервать процесс а максимально скомпилировать всё что без ошибок.
Кажется это не зона ответственности CMake. Он ведь не занимается сборкой проектов. CMake - это генератор сборочных систем. Он просто генерирует файлы той сборочной системы которую вы ему укажете (Make, Ninja и т.д.). А дальше сборкой занимается указанная сборочная система.
Ну и разумеется танцы с макросами и зависимыми переменными.
А что тут имеется ввиду?

TimurZhoraev
26.09.2025 08:30Имеется в виду то что до сих пор в качестве аргументов используются различного рода вещи, которые тянутся из того что нужно собрать в систему сборки, не важно какого высокого она уровня. Иными словами включить/выключить CUDA/AVX/SSE, сделать море #define-ов, сгенерировать какой-либо .pm итд. То есть до сих пор нет каких-то встроенных в сам язык средств, поддерживающих сборку, иными словами требуется пересмотр самого принципа текст-компилятор-редактор связей-отладчик с наличием утилиты в этом механизме, отвечающей за сборку, включая данные о распределении памяти, опции подключаемых библиотек, версий, причём, инвариантно к именам/расширениям файлов, собирая непосредственно объекты и функции но это уже не задача make. До сих пор системы сборки и контроля версий по-сути обёртка на принципы работы и тулкиты, которые были ещё с середины 80-х. В даже в настоящее время проект - это просто папки и набор файлов в них с описанием того что нужно с этим сделать, вместо указания того, что нужно сделать непосредственно с их содержимым.

alohaeee
26.09.2025 08:30С профилированием нужно быть аккуратным. На моем проекте оверхед был почти в два раза. Для дебага этапов и понимания на верхнем уровне, что происходит - хорошая вещь. А так по итогу пришлось расставлять таймеры вручную на функции.

alexey_lukyanchyk Автор
26.09.2025 08:30С профилированием нужно быть аккуратным. На моем проекте оверхед был почти в два раза.
Вы наверное имеете ввиду профилирование C программ? Просто я не совсем понимаю о каком оверхеде в CMake файлах идет речь.
dejkunelena
первый раз про такое слышу, очень полезно!