четверг, 18 мая 2017 г.

[prog] Почему-то сильно не хочется делать CMake основным build-tool-ом

Под влиянием вот этого доклада с C++ CoreHard Spring 2017 решил попробовать еще раз посмотреть в сторону CMake как основного своего build-tool-а. Сам CMake, как по мне, говном был, говном и остался. Но, поскольку люди всерьез интересуются такими построенными поверх CMake вещами, как Conan.io и Hunter, то может пришло время зажать нос и научиться обмазываться CMake-ом?

В чем у меня специфика? В том, что под Windows у меня сейчас, например, десять(!) вполне себе актуальных версий GCC под x64: 4.8, 4.9, 5.1, 5.2, 5.3, 5.4, 6.1, 6.2, 6.3 и 7.1. Плюс к тому три версии Visual Studio (каждая из которых как в x86, так и в x64). Плюс две версии clang (3.9 и 4.0).

Под каждую версию компилятора у меня есть свой bat-файл, в котором настраиваются все пути и все окружение. И для каждого батничка свой ярлык на рабочем столе. Нужен мне, скажем, gcc-4.8, я кликаю на ярлык, попадаю в нужную среду, захожу в нужный мне каталог, запускаю ruby build.rb или ruby some/project/prj.rb. И все.

Зачастую бывает нужно проверить работу какого-то теста под определенной версией определенного компилятора. Опять же, кликаю на ярлык, захожу в нужный каталог, набираю в командной строке ruby test/so_5/some/test/prj.ut.rb. При этом для того, чтобы из командной строки мне было удобно работать и пользоваться, скажем, автодополнением, я к себе в PATH добавил bin из cygwin-а. И в качестве командного процессора использую cygwin-овский bash. Все просто и приятно. Работаешь в Windows как в Unix-е.

Одна из составляющей этой простоты в том, что в проектах, как правило, делается специальная настройка путей для результатов компиляции. Что-то вроде:

MxxRu::Cpp::composite_target do
  global_obj_placement MxxRu::Cpp::PrjAwareRuntimeSubdirObjPlacement.new(
    'target'MxxRu::Cpp::PrjAwareRuntimeSubdirObjPlacement::USE_COMPILER_ID )

Эта настройка указывает, что все объектники и прочие промежуточные файлы должны закидываться в отдельные каталоги, построенные с учетом режима сборки (release или debug), имени проекта и типа+версии используемого компилятора. Что означает, что если я сперва собираю проект some/prj/prj.rb компилятором gcc-4.8, а затем компилятором gcc-7.1, то объектные файлы для этого проекта будут лежать в разных местах. Делается такая настройка один раз и затем мне не нужно менять ее при переходе на другую машину или другую ОС.

Теперь, допустим, я хочу вместо MxxRu использовать CMake. Это значит, что под каждый тулсет мне нужно сгенерировать отдельный набор CMake-овской требухи. Ибо, если я сделаю что-то вроде для gcc-4.8:

mkdir cmake_build
cd cmake_build
cmake -DCMAKE_INSTALL_PREFIX=target -DBUILD_ALL=ON -G "MinGW Makefiles" ../dev

А потом, уже для gcc-7.1, войду в cmake_build, то при запуске компиляции никто меня не предупредит, что я пытаюсь сделать сборку gcc-7.1 на файлах, сгенерированных для gcc-4.8.

Как по мне, то это уже пичалька. Ибо вручную строить build-каталоги для разных типов окружений... Ну не в XXI-ом же веке.

Однако, некоторой пикантности придает то, что сам CMake пытается быть умнее, чем он есть. Например, т.к. у меня в PATH-е есть пути к cygwin-у, то при попытке сгенерировать build-файлы под GCC, я получаю следующее:

...\so_5\5.5\cmake_build>cmake -DCMAKE_INSTALL_PREFIX=gcc_target -DBUILD_ALL=ON -G "MinGW Makefiles" ../dev
CMake Error at C:/cmake-3.8.1-win64-x64/share/cmake-3.8/Modules/CMakeMinGWFindMake.cmake:12 (message):
  sh.exe was found in your PATH, here:

  C:/cygwin64/bin/sh.exe

  For MinGW make to work correctly sh.exe must NOT be in your path.

  Run cmake from a shell that does not have sh.exe in your PATH.

  If you want to use a UNIX shell, then use MSYS Makefiles.

Call Stack (most recent call first):
  CMakeLists.txt


CMake Error: CMAKE_C_COMPILER not set, after EnableLanguage
CMake Error: CMAKE_CXX_COMPILER not set, after EnableLanguage
-- Configuring incomplete, errors occurred!

Есть запустить CMake с такими же параметрами повторно, то свои build-файлы он таки генерирует:

...\so_5\5.5\cmake_build>cmake -DCMAKE_INSTALL_PREFIX=gcc_target -DBUILD_ALL=ON -G "MinGW Makefiles" ../dev
-- The C compiler identification is GNU 6.3.0
-- The CXX compiler identification is GNU 6.3.0
-- Check for working C compiler: C:/mingw64-posix-6.3.0/bin/gcc.exe
-- Check for working C compiler: C:/mingw64-posix-6.3.0/bin/gcc.exe -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Detecting C compile features
-- Detecting C compile features - done
-- Check for working CXX compiler: C:/mingw64-posix-6.3.0/bin/g++.exe
-- Check for working CXX compiler: C:/mingw64-posix-6.3.0/bin/g++.exe -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done

Но не так уж, чтобы гладко. Ибо:

CMake Warning in test/so_5/env_infrastructure/simple_not_mtsafe_st/autoshutdown_disabled/CMakeLists.txt:
The object file directory

D:/home/eao197/sandboxes/sobjectizer.sourceforge.net/branches/so_5/5.5/cmake_build/test/so_5/env_infrastructure/simple_not_mtsafe_st/autoshutdown_disabled/CMakeFiles/_unit.test.env_infrastructure.simple_not_mtsafe_st.autoshutdown_disabled.dir/

has 243 characters. The maximum full path to an object file is 250
characters (see CMAKE_OBJECT_PATH_MAX). Object file

main.cpp.obj

cannot be safely placed under this directory. The build may not work
correctly.


CMake Error: Could not create D:/home/eao197/sandboxes/sobjectizer.sourceforge.net/branches/so_5/5.5/cmake_build/test/so_5/env_infrastructure/simple_not_mtsafe_st/autoshutdown_disabled/CMakeFiles/_unit.test.env_infrastructure.simple_not_mtsafe_st.autoshutdown_disabled.dir/cmake_clean.cmake
CMake Error: Cannot open file for write: D:/home/eao197/sandboxes/sobjectizer.sourceforge.net/branches/so_5/5.5/cmake_build/test/so_5/env_infrastructure/simple_not_mtsafe_st/autoshutdown_disabled/CMakeFiles/_unit.test.env_infrastructure.simple_not_mtsafe_st.autoshutdown_disabled.dir/DependInfo.cmake.tmp
CMake Error: : System Error: No such file or directory

Сборка-то при этом пойдет, но осадочек остается.

Однако, поскольку мне зачастую нужно проверить работоспособность отдельного теста, то как это можно сделать в CMake?

Можно выполнить ctest -P имя_моего_теста. Только вот я знаю, что тест лежит в каталоге test/so_5/some/test, а вот какое у него имя TARGET-а -- вот этого уже не помню. В случае с MxxRu имя TARGET-а меня не парит вообще, т.к. я набираю путь к prj.ut.rb-файлу через bash с автоподстановками. А в случае с CMake нужно идти в проектный файл, брать оттуда имя TARGET-а и вставлять в командную строку.

Есть и другой способ -- задать номер теста, вроде ctest -I номер, а получить список всех тестов можно через ctest -N. Ну, пробуем. Получаем охренительную простыню текста вот такого вида:

Could not find executable D:/home/eao197/sandboxes/sobjectizer.sourceforge.net/branches/so_5/5.5/cmake_build/test/so_5/env_infrastructure/simple_not_mtsafe_st/stats_wt_activity/_unit.test.env_infrastructure.simple_not_mtsafe_st.stats_wt_activity.exe
Looked in the following places:
D:/home/eao197/sandboxes/sobjectizer.sourceforge.net/branches/so_5/5.5/cmake_build/test/so_5/env_infrastructure/simple_not_mtsafe_st/stats_wt_activity/_unit.test.env_infrastructure.simple_not_mtsafe_st.stats_wt_activity.exe
D:/home/eao197/sandboxes/sobjectizer.sourceforge.net/branches/so_5/5.5/cmake_build/test/so_5/env_infrastructure/simple_not_mtsafe_st/stats_wt_activity/_unit.test.env_infrastructure.simple_not_mtsafe_st.stats_wt_activity.exe.exe
D:/home/eao197/sandboxes/sobjectizer.sourceforge.net/branches/so_5/5.5/cmake_build/test/so_5/env_infrastructure/simple_not_mtsafe_st/stats_wt_activity/Release/_unit.test.env_infrastructure.simple_not_mtsafe_st.stats_wt_activity.exe
D:/home/eao197/sandboxes/sobjectizer.sourceforge.net/branches/so_5/5.5/cmake_build/test/so_5/env_infrastructure/simple_not_mtsafe_st/stats_wt_activity/Release/_unit.test.env_infrastructure.simple_not_mtsafe_st.stats_wt_activity.exe.exe
D:/home/eao197/sandboxes/sobjectizer.sourceforge.net/branches/so_5/5.5/cmake_build/test/so_5/env_infrastructure/simple_not_mtsafe_st/stats_wt_activity/Debug/_unit.test.env_infrastructure.simple_not_mtsafe_st.stats_wt_activity.exe
D:/home/eao197/sandboxes/sobjectizer.sourceforge.net/branches/so_5/5.5/cmake_build/test/so_5/env_infrastructure/simple_not_mtsafe_st/stats_wt_activity/Debug/_unit.test.env_infrastructure.simple_not_mtsafe_st.stats_wt_activity.exe.exe
D:/home/eao197/sandboxes/sobjectizer.sourceforge.net/branches/so_5/5.5/cmake_build/test/so_5/env_infrastructure/simple_not_mtsafe_st/stats_wt_activity/MinSizeRel/_unit.test.env_infrastructure.simple_not_mtsafe_st.stats_wt_activity.exe
D:/home/eao197/sandboxes/sobjectizer.sourceforge.net/branches/so_5/5.5/cmake_build/test/so_5/env_infrastructure/simple_not_mtsafe_st/stats_wt_activity/MinSizeRel/_unit.test.env_infrastructure.simple_not_mtsafe_st.stats_wt_activity.exe.exe
D:/home/eao197/sandboxes/sobjectizer.sourceforge.net/branches/so_5/5.5/cmake_build/test/so_5/env_infrastructure/simple_not_mtsafe_st/stats_wt_activity/RelWithDebInfo/_unit.test.env_infrastructure.simple_not_mtsafe_st.stats_wt_activity.exe
D:/home/eao197/sandboxes/sobjectizer.sourceforge.net/branches/so_5/5.5/cmake_build/test/so_5/env_infrastructure/simple_not_mtsafe_st/stats_wt_activity/RelWithDebInfo/_unit.test.env_infrastructure.simple_not_mtsafe_st.stats_wt_activity.exe.exe
D:/home/eao197/sandboxes/sobjectizer.sourceforge.net/branches/so_5/5.5/cmake_build/test/so_5/env_infrastructure/simple_not_mtsafe_st/stats_wt_activity/Deployment/_unit.test.env_infrastructure.simple_not_mtsafe_st.stats_wt_activity.exe
D:/home/eao197/sandboxes/sobjectizer.sourceforge.net/branches/so_5/5.5/cmake_build/test/so_5/env_infrastructure/simple_not_mtsafe_st/stats_wt_activity/Deployment/_unit.test.env_infrastructure.simple_not_mtsafe_st.stats_wt_activity.exe.exe
D:/home/eao197/sandboxes/sobjectizer.sourceforge.net/branches/so_5/5.5/cmake_build/test/so_5/env_infrastructure/simple_not_mtsafe_st/stats_wt_activity/Development/_unit.test.env_infrastructure.simple_not_mtsafe_st.stats_wt_activity.exe
D:/home/eao197/sandboxes/sobjectizer.sourceforge.net/branches/so_5/5.5/cmake_build/test/so_5/env_infrastructure/simple_not_mtsafe_st/stats_wt_activity/Development/_unit.test.env_infrastructure.simple_not_mtsafe_st.stats_wt_activity.exe.exe
Test #245: _unit.test.env_infrastructure.simple_not_mtsafe_st.stats_wt_activity

Total Tests: 245

Что за Х? Видимо, это потому, что ничего не было построено предварительно. Отсюда и столько предупреждений.

Если же попробуем запустить cmake -I 245, то получим похожую простыню вместо компиляции и запуска теста:

D:\home\eao197\sandboxes\sobjectizer.sourceforge.net\branches\so_5\5.5\cmake_build>ctest -I 245
Test project D:/home/eao197/sandboxes/sobjectizer.sourceforge.net/branches/so_5/5.5/cmake_build
Start 245: _unit.test.env_infrastructure.simple_not_mtsafe_st.stats_wt_activity
Could not find executable D:/home/eao197/sandboxes/sobjectizer.sourceforge.net/branches/so_5/5.5/cmake_build/test/so_5/env_infrastructure/simple_not_mtsafe_st/stats_wt_activity/_unit.test.env_infrastructure.simple_not_mtsafe_st.stats_wt_activity.exe
Looked in the following places:
D:/home/eao197/sandboxes/sobjectizer.sourceforge.net/branches/so_5/5.5/cmake_build/test/so_5/env_infrastructure/simple_not_mtsafe_st/stats_wt_activity/_unit.test.env_infrastructure.simple_not_mtsafe_st.stats_wt_activity.exe
D:/home/eao197/sandboxes/sobjectizer.sourceforge.net/branches/so_5/5.5/cmake_build/test/so_5/env_infrastructure/simple_not_mtsafe_st/stats_wt_activity/_unit.test.env_infrastructure.simple_not_mtsafe_st.stats_wt_activity.exe.exe
D:/home/eao197/sandboxes/sobjectizer.sourceforge.net/branches/so_5/5.5/cmake_build/test/so_5/env_infrastructure/simple_not_mtsafe_st/stats_wt_activity/Release/_unit.test.env_infrastructure.simple_not_mtsafe_st.stats_wt_activity.exe
D:/home/eao197/sandboxes/sobjectizer.sourceforge.net/branches/so_5/5.5/cmake_build/test/so_5/env_infrastructure/simple_not_mtsafe_st/stats_wt_activity/Release/_unit.test.env_infrastructure.simple_not_mtsafe_st.stats_wt_activity.exe.exe
D:/home/eao197/sandboxes/sobjectizer.sourceforge.net/branches/so_5/5.5/cmake_build/test/so_5/env_infrastructure/simple_not_mtsafe_st/stats_wt_activity/Debug/_unit.test.env_infrastructure.simple_not_mtsafe_st.stats_wt_activity.exe
D:/home/eao197/sandboxes/sobjectizer.sourceforge.net/branches/so_5/5.5/cmake_build/test/so_5/env_infrastructure/simple_not_mtsafe_st/stats_wt_activity/Debug/_unit.test.env_infrastructure.simple_not_mtsafe_st.stats_wt_activity.exe.exe
D:/home/eao197/sandboxes/sobjectizer.sourceforge.net/branches/so_5/5.5/cmake_build/test/so_5/env_infrastructure/simple_not_mtsafe_st/stats_wt_activity/MinSizeRel/_unit.test.env_infrastructure.simple_not_mtsafe_st.stats_wt_activity.exe
D:/home/eao197/sandboxes/sobjectizer.sourceforge.net/branches/so_5/5.5/cmake_build/test/so_5/env_infrastructure/simple_not_mtsafe_st/stats_wt_activity/MinSizeRel/_unit.test.env_infrastructure.simple_not_mtsafe_st.stats_wt_activity.exe.exe
D:/home/eao197/sandboxes/sobjectizer.sourceforge.net/branches/so_5/5.5/cmake_build/test/so_5/env_infrastructure/simple_not_mtsafe_st/stats_wt_activity/RelWithDebInfo/_unit.test.env_infrastructure.simple_not_mtsafe_st.stats_wt_activity.exe
D:/home/eao197/sandboxes/sobjectizer.sourceforge.net/branches/so_5/5.5/cmake_build/test/so_5/env_infrastructure/simple_not_mtsafe_st/stats_wt_activity/RelWithDebInfo/_unit.test.env_infrastructure.simple_not_mtsafe_st.stats_wt_activity.exe.exe
D:/home/eao197/sandboxes/sobjectizer.sourceforge.net/branches/so_5/5.5/cmake_build/test/so_5/env_infrastructure/simple_not_mtsafe_st/stats_wt_activity/Deployment/_unit.test.env_infrastructure.simple_not_mtsafe_st.stats_wt_activity.exe
D:/home/eao197/sandboxes/sobjectizer.sourceforge.net/branches/so_5/5.5/cmake_build/test/so_5/env_infrastructure/simple_not_mtsafe_st/stats_wt_activity/Deployment/_unit.test.env_infrastructure.simple_not_mtsafe_st.stats_wt_activity.exe.exe
D:/home/eao197/sandboxes/sobjectizer.sourceforge.net/branches/so_5/5.5/cmake_build/test/so_5/env_infrastructure/simple_not_mtsafe_st/stats_wt_activity/Development/_unit.test.env_infrastructure.simple_not_mtsafe_st.stats_wt_activity.exe
D:/home/eao197/sandboxes/sobjectizer.sourceforge.net/branches/so_5/5.5/cmake_build/test/so_5/env_infrastructure/simple_not_mtsafe_st/stats_wt_activity/Development/_unit.test.env_infrastructure.simple_not_mtsafe_st.stats_wt_activity.exe.exe
Unable to find executable: D:/home/eao197/sandboxes/sobjectizer.sourceforge.net/branches/so_5/5.5/cmake_build/test/so_5/env_infrastructure/simple_not_mtsafe_st/stats_wt_activity/_unit.test.env_infrastructure.simple_not_mtsafe_st.stats_wt_activity.exe
1/1 Test #245: _unit.test.env_infrastructure.simple_not_mtsafe_st.stats_wt_activity ...***Not Run 0.00 sec

0% tests passed, 1 tests failed out of 1

Total Test time (real) = 0.25 sec

The following tests FAILED:
245 - _unit.test.env_infrastructure.simple_not_mtsafe_st.stats_wt_activity (Not Run)
Errors while running CTest

Наверное, с точки зрения пользователей CMake здесь все правильно. Сначала билд, потом запуск тестов. А вот если ты привык к хорошему, и надеешься, что твой build-tool сможет определить, что тест сперва следует скомпилировать (или перекомпилировать), а уже потом запустить, то в случае CMake тебя ждет приличный обломс :(

Вполне возможно, все эти шероховатости просто с непривычки и неумения готовить CMake. Но откуда этому умению взяться, если мне до сих пор не попалось ни одного нормального обучающего пособия по CMake, в котором бы описывались принципы работы CMake. Где бы говорилось, какие файлы и в каком порядке CMake подчитывает, что он в них смотрит, какие переменные и/или команды являются самыми важными, что CMake делает с полученными описаниями и т.д. Чтобы прочитав такое пособие у меня сложилось в голове четкая картинка. Чтобы было понимание, для чего нужен CMAKE_BUILD_DIR, какое значение и когда будет у CMAKE_SOURCE_DIR, чем он отличается от CMAKE_CURRENT_SOURCE_DIR и т.д.

Вот таких вменяемых руководств не встречалось пока. Все больше тысяча и один пересказ на тему "вот мы делаем простую статическую библиотеку без внешних зависимостей и одним исходным файлом, а вот мы ее добавляем в зависимости к простому исполняемому файлу, это и все, что вам нужно знать, дабы начать работать с CMake". Так что если кто-то сможет поделиться ссылкой на хорошее описание принципов работы CMake, то буду признателен.


Очень не хотелось бы, чтобы данный пост восприняли как попытку сказать, что MxxRu лучше, чем CMake. Это не так, возможности у MxxRu намного более скудные, да и сам MxxRu уже старый, а переделать его под новые веяния, вероятно, так руки никогда и не дойдут. Однако, в качестве инструмента для облегчения сборки проекта на разных платформах и разными тулсетами MxxRu для меня гораздо удобнее CMake. Такое ощущение, что CMake хорош только когда программист работает с каким-то прикладным софтом и всего с одним компилятором. Ну и когда все тесты вместо программиста CI запускает, а программист довольствуется только успешной компиляцией кода. Тогда с прелестями CMake можно и не столкнуться.

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

PS. Внутренний голос настойчиво нашептывает: heavy templates and header-only libraries... К чему бы это? ;)

Отправить комментарий