IDE и C++

Я довольно давний поклонник IDE от Jetbrains. Тем же IntelliJ IDEA и PyCharm просто нет сопоставимых альтернатив с точки зрения скорости работы и удобства. Собственно говоря у меня у меня даже подписка на них оформлена из соображений “нравится проект – поддержи финансово”, но вот с CLion что-то пошло не так…

На мой взгляд основная проблема в том, что разработчики и менеджеры продукта пришедшие с платформ .NET и JVM просто не понимают что такое разработка на C/C++. В итоге идёт попытка переложить модель разработки с той же Java на C++. В крайне случае у меня сложилось именно такое ощущение как после личного общения с JetBrain-овцами на CppCon, так и после вчерашнего диалога в Twitter.

Собственно, проблема и непонимание состоит в том, что команда CLion упорно хочет видеть “проектную модель”. Я не знаю чем это вызвано, толи наличием libClang под капотом, толи архитектурными проблемами собственного парсера, но без реальной проектной модели CLion превращается в тыкву. Хотя, он даже и с реальной проектной моделью иногда превращается в тыкву, например спотыкаясь на сгенерированном через Protobuf коде.

К сожалению, такой подход не может работать в мире C++ на многих проектах сложнее Hello World. За исключением счастливчиков, которым доступно писать всё в Visual Studio, у разработчиков огромный зоопарк используемых редакторов и причина у этого довольно грустная, в теории CLion и должен был исправить эту ситуацию. Так вот, мои коллеги пишут в Sublime, Emacs, Vim, Eclipce CDT, Studio Code и многих других редакторах по большому счету потому, что все они практически одинаково плохи в роли C++ IDE. Ну может за исключением Emacs/Vim, за счёт его интеграции с Globals/CTags и Eclipse, за счёт наличия грамотно реализованного парсера C++. Но в Emacs/Vim ещё нужно уметь писать – это свой отдельный мир, а Eclipse даже на Xeon-ах с горой памяти на борту умудряется тормозить.

Если же говорить о проектной модели и почему привязка к ней делает редактор не пригодным к промышленному использованию – то её часто просто нет. К примеру на данный момент я периодически переключаюсь между несколькими проектами: один на базе CMake, второй на некой дикой смеси Make, CMake и SCons обвешанной sh/bat скриптами сверху. Так же я иногда заглядываю в проекты которые представляют из себя кучу файлов просто “для консультации”. Таким образом, в теории, я мог бы писать 1 проект из всех над которыми работаю в CLion, но есть ли в этом хоть какой-то смысл если мне нужно писать и другие проекты и иметь пусть приблизительную, но быструю навигацию по коду и хоть какую-то автоподстановку? По мне так никакого, так как привыкать к особенностям 2-х редакторов куда как менее удобно нежели всегда работать в одном. Жаль только надежда на то, что на менеджеров продукта CLion сойдет понимание проблемы угасла.

Go и контроль качества проекта

А начну я с громкого заявления: Go – это один из самых лучших языков программирования для командной разработки на данный момент. И дело тут ни в простоте языка, ни в простоте инфраструктуры, хотя и эти пункты очень важны. Основная же причина для такого заявления кроется в инструментах для статического анализа кода. При этом я не знаю ни одного другого языка, который позволял бы провести такой дотошный статический анализ по всем направлениям, начиная от стилистики кода, заканчивая потенциально опасными конструкциями и обработкой ошибок.

Основная проблема в командной разработке – разнородность уровня и, как следствие, разное качество производимого командой кода. Ревью, безусловно, позволяют в той или иной степени сгладить последствия разнородности и даже подтянуть уровень разработчиков, но работают они не так хорошо как хотелось бы. Кому-то может быть лень, кто-то устал, кто-то не заметил и в репозитории оказывается нечто, которому там не место. Можно ли это хоть как-то исправить в C++? Нет, нельзя. Можно ли эту проблему минимизировать в Go? Довольно легко, что я вчера и сделал.

В Git есть замечательный механизм хуков, которые отрабатывают в зависимости от внутренних событий. Меня интересовал pre-commit хук, позволяющий заблокировать коммит по результату выполнения скрипта. Использование этого хука купе с Go Meta Linter, агрегатором линтеров для Go, позволяет автоматически заблокировать коммит до исправления ошибок.

#!/bin/sh

if ! [ -x "$(command -v gometalinter)" ]; then
echo 'Error: gometalinter is not installed. Please install it first and execute `gometalinter -i`'
exit 1
fi

lint_errors=$(gometalinter ./... --vendor -j 5)
if [[ $? != 0 ]]; then
echo 'Error: gometalinter checks had failed. Please execute `gometalinter ./... --vendor` first and fix ALL issues'
exit 1
fi

Дело остается за малым – поместить файл с хуком в директорию .git/hooks проекта, что я сделал при помощи нашего сборочного скрипта. Да, нам пришлось написать такой скрипт поверх стандартных Go команд типа go build так как есть пре- и пост- шаги которые необходимо предпринять в процессе сборки, а никакого CMake-подобного механизма в Go нет. Хотя, что уж тут, поверх CMake писать аналогичный скрипт тоже приходится.

CppCon 2017

oznor

Дни на CppCon 2017 пролетели очень быстро и незаметно. Конференция действительно потрясающая и на неё однозначно стоит ехать, если есть такая возможность, любому кто любит C++ и хочет писать качественный код. Желающих выступить на конференции так много, что практически всё время параллельно идут около 5 сессий. К сожалению, это приводит к тому, что иногда приходится жертвовать интересным выступлением в пользу еще более интересного. Именно так я пропустил выступление Гора Нишанова, посвященное корутинам, которое теперь придется смотреть на Ютубе. Вообще, казалось бы, можно все то же самое посмотреть на Ютубе или 9-ом канале… но это не то, не создается того ощущения погружения в тему, как от личного присутствия, живого общения и разбора материалов в течении 5 дней. Теперь еще несколько месяцев ковыряться, думать и систематизировать услышанное.

Очень порадовал и удивил тот факт, что со всех сторон на конференции была слышна русская речь. Встретил много бывших коллег из Лаборатории Касперского, познакомился с народом из JetBrains. На конференции была великолепная подборка книг по C++, парой из которых я обзавелся: Advanced Metaprogramming in Classic C++ и C++ Templates the Complete Guide. Со второй книгой вообще вышло занятно: после того, как её порекомендовал Саттер на своем выступлении, за ней выстроилась довольно большая очередь, брали по несколько экземпляров

Назвать одно самое-самое выступление в этот раз я не могу, так как на мой взгляд их два:

Еще 5 докладов, уже без вау-эффекта, но невероятно интересные и полезные:

  • Faster Delivery of Large C/C++ Projects with Conan Package Manager and Efficient Continuous Integration. Если вы работаете не в продуктовой команде, а в SDK, то это то, что нужно обязательно смотреть! Если ничего нового для себя не открыли, то у вас просто невероятно продвинутая команда.
  • C++17 ParallelSTL: A Standardization Experience Report for CPU and GPU on SYCL. Тут вроде всё из названия понятно, презентация обзорная, скорее конспект для дальнейшего самостоятельного изучения.
  • C++ as a “Live at Head” Language. Презентация наделала много шуму, в принципе, оправданно… но только если вы работаете в компании размера Гугл. Я даже для Автодеска полезность Abseil с трудом могу оценить как положительную, но сама идея интересная, стоит послушать как минимум для того, чтобы осознать масштаб проблем, с которыми приходится сталкиваться другим.
  • A Tour of Deep Learning With C++. Для того, кто занимается машинным обучением, эта презентация, скорее всего, покажется банальностью уровня “давайте я вам расскажу зачем нужны умные указатели”. А вот мне, далекому от этой области человеку, было невероятно интересно. Новая, интересная предметная область вкупе с любимым языком – что может быть лучше?!
  • Postmodern immutable data structures. Сюда я даже думал не идти изначально, но был сильно-сильно неправ. Невероятно интересная презентация про иммутабельные структуры данных в мире C++.

Довольно хорошие доклады, не пожалел что сходил:

  • Boost Your Program’s Health by Adding Fibers to Your Coroutine. Так как я имею довольно поверхностное представление о fiber-ах и сoroutine-ах, мне было интересно и полезно.
  • Tools and Techniques To Stay Up-to-date With Modern C++. Мысли о том, как стоит учиться программировать на C++, полезные источники с информацией и т.п. Внезапно оказалось интересно.
  • Curiously Recurring Bug Patterns in C++ at Facebook. Самые злобные грабли C++ по версии Facebook. Сделать конспект и раздавать новичкам
  • Unbolting the Compiler’s Lid: What Has My Compiler Done for Me Lately? Доклад от автора широкоизвестного в узких кругах Compiler Explorer. Немного про чудеса оптимизации компилятора, в особенности Clang, и немного про архитектуру самого сайта.

Посмотрел на Ютуб, понравилось:

  • A modern formatting library for C++. Библиотеки форматирования текста, надо признать, довольно больная тема для C++. Рассказ о fmt из первых рук.
  • Deconstructing the OS: The devil’s In the side effects. А тут уже автор bootOS, рассказывает о том, что лежит в основе разработки.
  • Using Modern CMake Patterns to Enforce a Good Modular Design. Многие разработчики не любят или боятся CMake. Да, синтаксис CMake-скриптов ужасен, но плюсы несомненны, и если его “правильно готовить”, то инструмент начинает восприниматься сильно иначе.
  • Fuzz or lose…. Стыдно сказать, но до этого выступления я не задумывался о фаззинге как таковом. Тем кто в таком же положении, очень рекомендую глянуть.
  • 10 Core Guidelines You Need to Start Using Now. Название тут говорит само за себя. Человек, который хотя бы краем глаза заглядывал в C++ Core Guidelines, из этого выступление много нового не подчерпнет, но в целом полезно.
  • Undefined Behavior in 2017 и вторая часть выступления посвящены проделкам оптимизатора на -O2. Интересно и познавательно, хотя докладчик несколько тараторит.

Внезапно не понравились доклады от Бьёрна Страуструпа и Энтони Уильямса: очень тягомотно и, как мне кажется, довольно бесполезно. При том, какие у них отличные книги, я ожидал большего. Если уж говорить про разочарования от конференции, я бы добавил еще два: ни Майерс, ни Александреску на конференции снова не появились, а так хотелось послушать их вживую.

А еще меня разочаровало то, что на конференции не было ни одного доклада или стенда про статический анализ кода, хотя на мой взгляд, в современном C++ это одна из самых серьезных проблем. Гора отладчиков, множество приложений для разбора крэш-дампов, доклады про отладку и борьбу с багами… и чуть-чуть санитарайзеров времени исполнения, а так же практически полное отсутствие доступных средств для статического анализа кода и поиска уязвимостей. И при этом, по моим наблюдениям, эта ситуация вообще мало кого заботит

P.S. Линки на доклады добавлю по мере их появления.

Эксперименты с Rust

В течении последних лет я время от времени с интересом поглядывал на Rust, наверное, с версии 0.3. За это время язык претерпел много изменений, за счет чего он то нравился, то совсем не нравился, то вызывал сомнения. Но не смотреть на него было никак нельзя, так как языков, подходящих для “коробочной” разработки и не генерирующих байт-код, по пальцам одной руки пересчитать можно и еще свободные пальцы останутся.

Плотно поработав с Go в течении последнего года, я вновь подумал о том, что Rust довольно интересен, и стоит попробовать решить с его помощью практическую задачу. Мне как раз нужно было приложение для генерации оператора вывода всех членов класса/структуры в поток, на этой задаче я и решил остановиться. Мелочь, конечно, но полезно, нужно лично мне и позволяет сделать как минимум поверхностные выводы о пригодности языка для повседневного использования.

Подготовка к работе

Rust, конечно, не может похвастаться таким изобилием редакторов, как C++ или Go, но для относительно квалифицированного разработчика, привыкшего к командной строке, того, что есть, более чем достаточно. При этом управление зависимостями, создание проектов, установка внешних утилит выше всяких похвал.

Установка

Сам компилятор и набор утилит типа cargo устанавливается и обновляется очень легко и удобно, особенно на UNIX-ах. Всего одна консольная команда в соответствии с официальной документацией. Windows никогда особо дружелюбным для разработчиков не был, хотя и тут разработчики Rust постарались и сделали как поддержку GNU toolchains, так и поддержку MSVC. Всё заработало “из коробки”, что несказанно меня удивило. Ощущение зрелости как минимум системы доставки и развертывания однозначно присутствует.

Редактор

В моём случае с редактором было всё просто: взять Vim да установить пару-тройку дополнений к нему. Подсветка и форматирование работают отлично, автодополнение, базирующееся на racer, работает сносно. Как я понял, все остальные редакторы и IDE также используют racer для автодополнения и ожидать какого-то чуда за пределами VIM не следует. Понадобилось всего два плагина: rust.vim и vim-racer. В обоих случаях они снабжены достаточно подробной документацией и никакой сложности установка и настройка не вызывает.

Особо хотелось бы отметить наличие rustfmt, который позволяет форматировать код в едином стиле. Впервые я проникся любовью к такого рода приложениям, начав работать с Go, где весь код автоматически форматируется одним единственным правильным способом. В командной работе практика оказалась невероятно удобной, так как из “стандарта кодирования” команды полностью исчезает пункт про форматирование, при этом весь код выглядит одинаково, что облегчает понимание и работу с чужими фрагментами/модулями. После этого я внедрил у нас использование clangformat на C++ проектах (правда уже с единым набором правил для форматирования), что также сделало разработку ощутимо комфортнее.

Также в Vim большой популярностью пользуется плагин Tagbar, который не умеет работать с Rust из коробки, но это очень легко исправляется.

Еще одним потенциально полезным компонентом экосистемы Rust мне показался cargo-outdated, позволяющий отслеживать устаревшие зависимости. Ну просто мечта разработчика C++ какая-то, особенно учитывая то, что сторонние компоненты (дополнения) Rust устанавливаются при помощи cargo очень простым способом:

cargo install rustfmt
cargo install racer
cargo install cargo-outdated

Создание проекта

Я не раз сталкивался с мнением, что Rust можно полюбить хотя бы за наличие Cargo в нём. Попробовал и не могу не согласиться: всё очень просто и удобно.

cargo new --bin ddump-gen

И у меня есть новый проект, в котором, путем добавления всего двух строчек, я могу работать с libclang:

[dependencies]
clang = "0.16.0"

Это интересно само по себе хотя бы тем, что изначально я порывался написать тот же проект на C++, но бросил уже на стадии написания cmake-файла для этой же самой libclang.

Разработка

Теперь о самом главном: насколько удобно писать и поддерживать код на Rust. Для меня вопрос показался относительно сложным и полностью зависящим от текущих привычек и рабочих инструментов.

Доступные библиотеки

То, что Rust активно пиарится Mozilla и Samsung в течении последних лет, очень положительно сказалось на количестве доступных новых библиотек, а также оберток для уже существующих библиотек из мира C/C++. В результате очень быстро подобралась подходящая обертка над libClang, был найден удобный парсер командной строки и подходящий логгер. Довольно удивительно, но на решение тех же вопросов на C++ я бы потратил даже больше времени, чем в совершенно незнакомой экосистеме.

Про язык

С самим языком всё не столь однозначно и прекрасно. Наверное, если бы не опыт последних месяцев с Go, я бы просто подивился тому, почему же так неудобно обрабатывать ошибки, и отложил бы эксперименты до “лучших времен”. Но вот после того, как желание обязательно иметь в языке исключение прошло, оказалось, что с кодами возврата, особенно при наличии сопоставления с образцом, жить можно довольно комфортно.

Собственно говоря, к чему весь этот разговор про обработку ошибок. При отсутствии исключений в языке размер кода растет, так как, во-первых, появляется необходимость проверять коды возврата и не ожидать, что “исполнилось корректно либо выполнение было прервано”, а во-вторых, теряется возможность сделать обобщенную обработку нескольких потенциальных ошибок в одном блоке. Если задуматься над причиной, то её можно сформулировать как “мне нужно писать больше кода, но лень”. Есть ли в такой необходимости что-то хорошее? Опыт с Go подсказывает, что есть! Адресная обработка ошибок, т.е. сразу после возникновения ошибочной ситуации, обычно более качественно продумана и лучше покрыта диагностикой по сравнению с обобщенными исключениями. Кроме того, в стандартной библиотеке Rust пошли чуть дальше и добавили немного синтаксического сахара. Особо полезными, на мой взгляд, являются функции семейства unwrap(). Например, я точно знаю, что библиотека разбора аргументов командной строки не пропустит пустого значения для параметра SRC_FILE, и могу смело писать:

let path = matches.value_of("SRC_FILE").unwrap();

Смело потому, что попытка unwrap() для None обернется паникой и аварийным завершением приложения. Но для работы с такими ситуациями есть не менее удобный unwrap_or()

let std = matches.value_of("STD_VER").unwrap_or("11");

Среди относительно новых изменений языка числится `?`-синтаксис, которым я так и не решился воспользоваться, так как, на мой взгляд, он делает код просто невероятно сложно поддерживаемым и является злом по определению. Лучше уж отдать предпочтение макросу try!(), который делает то же самое, но более очевидным путем, а еще лучше либо явно звать unwrap(), либо писать полную обработку на базе match.

Ужас прошлых версий с временем жизни объектов разработчики спрятали, и теперь код можно, обычно, писать, особо не задумываясь о том, как удовлетворить компилятор. В версиях около 1.0 это был невероятно сильный отталкивающий фактор, так как вместо решения проблемы ты занимался удовлетворением хотелок компилятора буквально с первой линии кода. Само собой, правила никуда не делись, но компилятор стал ощутимо умнее.

В то же время мне сильно не нравится количество смысловой нагрузки на одну линию кода. Даже в простейшем примере выше с unwrap_or() видно, что тут упаковано и создание переменной с присвоением ей результат вызова функции, и условная проверка с возвратом значения по умолчанию, если функция ничего не вернула. Многовато для одной строки далеко не самого сложного кода на Rust.

Кому может быть интересен

В интернете можно найти довольно много мнений и споров на тему того, является ли Rust “убийцей C++” или нет. Смешные дискуссии, особенно с учетом того, сколько своих “убийц” C++ пережил. Поэтому я не буду говорить о том, кого же Rust вытеснит, только время рассудит, но вот кому и когда он может пригодиться, уже более интересно.

Во-первых, на современном Rust писать удобно и комфортно, инфраструктура хороша и проблем не вызывает. Во-вторых, появилось много библиотек в разной степени готовности (зачастую полностью готовых к коммерческому использованию), которые можно использовать и не заниматься решением утилитарных задач. В-третьих, детские болезни компилятора и языковой стилистики в основном исправлены. В-четвертых, сообщество Rust очень-очень активное и доступно большое количество вводных сайтов и полезной документации.

По моим ощущениям, при помощи современного Rust проблемы могут быть решены как минимум не хуже, чем при помощи современного C++, и более качественно, чем на современном Go. Так, для C++ программиста, активно использующего C++11 с STL и BOOST в повседневной работе, Rust может показаться ограниченным языком, особенно за счет куда как более бедной системы метапрограммирования и особенностей обработки ошибок. В то же время, для разработчика, активно использующего C++ с Qt или тот же Go, всё должно быть наоборот, приятно и комфортно. Если говорить про порог входа, то, с одной стороны, он однозначно выше, чем в случае с Go, начать писать на котором можно через 3-4 дня активного изучения документации и лучших практик. С другой стороны, информация, с которой придется ознакомиться, куда менее объемная и запутанная, чем даже минимальное вводное чтиво по C++.

Для себя я сделал вывод, что буду и дальше использовать Rust в “домашних проектах” и, при возможности, протолкну его продуктовую разработку в команде. Жизнь будет проще, жизнь будет веселее!

Устанавливаем OpenCV 3 для Python 3 на macOS

Конфигурация OpenCV 3 + Python 3 на macOS внезапно оказалась немного неожиданной в настройке. Изначально я ожидал что надо будет пару раз вызвать Brew, но оказалось несколько сложнее. Последовательность:

Смотрим где находятся site-packages для интерпретатора, которым собираемся пользоваться, понадобится ниже для создания симлинки на OpenCV:

>>> import site; site.getsitepackages()
['/usr/local/Cellar/python3/3.6.0/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages', '/Library/Python/3.6/site-packages']
>>>

Ставим, собираем, делаем линку (если нет TBB и/или Qt5, ий стоит либо поставить заранее, либо убрать соответствующие флаги):

brew install numpy --with-python3
brew install opencv3 --with-tbb --with-qt5 --with-python3 --with-examples --with-contrib --c++11
sudo mkdir -p /Library/Python/3.6/site-packages
sudo ln -s /usr/local/opt/opencv3/lib/python3.6/site-packages/cv2.cpython-36m-darwin.so /Library/Python/3.6/site-packages/cv2.so

Почему нужно отдельно создавать линку да и еще с таким загадочным именем как cv2 я так и не разгадал. Проверить что все работает довольно просто:

>>> import cv2; cv2.__version__
'3.2.0'
>>>

Vim и проверка орфографии

Так как мой основной рабочий инструмент – Vim (в комбинации с ZSH) я давно хотел прикрутить к нему проверку орфографии. Да и что там, собственно, прикручивать – все идет “из коробки” и остается только подключить то, что нужно. Оказалось всё и вправду проще не придумать.

Во-первых, нужно слегка подправить .vimrc для автоматического включения проверки орфографии на определенных типах файлов/буфферов:

autocmd BufRead,BufNewFile *.md setlocal spell
autocmd BufRead,BufNewFile *.txt setlocal spell
autocmd FileType gitcommit setlocal spell

Ну и потом не забыть о паре комбинаций:
z= – отобразить список замен;
zg – добавить слово в список корректных слов.

И работать как-то приятнее стало

Позиция ведущего разработчика в Сингапуре

В нашей команде в Autodesk Singapore есть вакансия ведущего разработчика на C++. Требований совсем чуть-чуть: хорошее знание C++ с навыком работы как с C++98, так и C++11/14, опыт в кроссплатформенной разработке (Win/Lin/OSX) и английский (разговорный + письменный) достаточный для работы в англоязычном коллективе. График работы гибкий, в разумных пределах. Удаленная работа невозможна. Оплата 85-120K SGD в год до налогов (коих тут будет около 7%).

С учетом того, что у нас довольно большой зоопарк технологий, знание Python, Java и C# будут плюсом (перечислены в порядке важности).

Немного о нас: пишем кроссплатформенную систему лицензирования для всех продуктов компании, в основном клиентскую и немного серверные части.

Контактное лицо: Vinod Babu, vinod.babu@autodesk.com с копией мне: alex@sysdev.me.

CppCon 2016

Наконец-то сбылась мечта и я побывал на CppCon. Очень надеюсь что теперь это будет на регулярной основе

Крайне рекомендую к просмотру следующие выступления:

  • High Performance Code 201: Hybrid Data Structures;
  • Rich Code For Tiny Machines: A Simple Commodore 64 Game In C++17;
  • Using weakly ordered C++ atomics correctly;
  • Lifetime Safety… By Default: Making Code Leak-Free by Construction.

Просто рекомендую. Время зря потерянным однозначно не будет:

  • C++ Coroutines: Under the covers;
  • The Guideline Support Library: One Year Later;
  • Deploying C++ modules to 100s of millions of lines of code;
  • Keynote: Developing Blockchain Software.

async && GCD

std::async из C++11 хорош практически всем: прост, удобен, универсален. И только одна особенность стандарта несколько портит этот праздник – нет четкой регламентации того, где и как должна выполниться асинхронная задача; задача может быть выполнена как в отдельном потоке, так и в пуле потоков. В итоге это приводит к тому, что разработчики STL не утруждают себя пулом потоков (даже при наличии оного в ОС по-умолчанию) и плодят по протоку на каждый std::async вызванный с флагом std::launch::async. В случае с macOS, как мне кажется, это довольно большая оплошность, так как в системе уже есть готовый пул потоков, которым остается только воспользоваться!
В итоге я немного поковырялся в стандарте, доступных реализациях и вышло у меня следующее: 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()