Podczas programowania lubię zmieniać kompilatory, tryby budowania, wersje zależności, przeprowadzać analizę statyczną, mierzyć wydajność, zbierać pokrycie, generować dokumentację itp. I naprawdę kocham CMake, ponieważ pozwala mi robić wszystko, co chcę.
Wiele osób krytykuje CMake i często zasłużenie, ale jeśli się przyjrzeć, nie wszystko jest takie złe, a ostatnio tak ogólnie to nieźle, a kierunek rozwoju jest całkiem pozytywny.
W tej notatce chcę Ci powiedzieć, jak w prosty sposób zorganizować bibliotekę nagłówków w C++ w systemie CMake, aby uzyskać następującą funkcjonalność:
Montaż;
Autorun testy;
Pomiar pokrycia kodu;
Instalacja;
Autodokumentacja;
Generowanie piaskownicy online;
Analiza statyczna.
Każdy, kto już rozumie zalety i markę C, może po prostu pobierz szablon projektu i zacznij z niego korzystać.
Będziemy głównie rozmawiać o tym, jak zorganizować skrypty CMake, dlatego zostaną one omówione szczegółowo. Każdy może bezpośrednio przeglądać resztę plików na stronie szablonu projektu.
Przede wszystkim musisz zamówić wymaganą wersję systemu CMake. CMake ewoluuje, zmieniają się sygnatury poleceń i zachowanie w różnych warunkach. Aby CMake od razu zrozumiał, czego od niego oczekujemy, musimy od razu zapisać nasze wymagania wobec niego.
cmake_minimum_required(VERSION 3.13)
Następnie określimy nasz projekt, jego nazwę, wersję, używane języki itp. (patrz. команду project).
W tym przypadku wskazujemy język CXX (a to oznacza C++), aby CMake nie męczył się i nie szukał kompilatora języka C (domyślnie CMake zawiera dwa języki: C i C++).
project(Mylib VERSION 1.0 LANGUAGES CXX)
Tutaj możesz od razu sprawdzić, czy nasz projekt jest uwzględniony w innym projekcie jako podprojekt. To bardzo pomoże w przyszłości.
Pierwsza opcja to MYLIB_TESTING — aby wyłączyć testy jednostkowe. Może to być konieczne, jeśli mamy pewność, że z testami wszystko jest w porządku, a my chcemy jedynie np. zainstalować lub spakować nasz projekt. Lub też nasz projekt zostaje włączony jako podprojekt - w tym wypadku użytkownik naszego projektu nie jest zainteresowany przeprowadzaniem naszych testów. Nie testujesz zależności, których używasz, prawda?
Dodatkowo zrobimy osobną opcję MYLIB_COVERAGE do pomiaru pokrycia kodu testami, ale będzie to wymagało dodatkowych narzędzi, więc trzeba będzie je jawnie włączyć.
Oczywiście jesteśmy fajnymi programistami, więc chcemy od kompilatora maksymalnego poziomu diagnostyki w czasie kompilacji. Żadna mysz się nie prześlizgnie.
Nasza biblioteka składa się wyłącznie z plików nagłówkowych, co oznacza, że nie mamy żadnego wyczerpania w postaci bibliotek statycznych lub dynamicznych. Natomiast aby móc korzystać z naszej biblioteki zewnętrznie należy ją zainstalować, musi być ona wykrywalna w systemie i połączona z Twoim projektem, a przy tym mieć te same nagłówki i ewentualnie jakieś dodatkowe, są z nim powiązane właściwości.
W tym celu tworzymy bibliotekę interfejsu.
add_library(mylib INTERFACE)
Łączymy nagłówki z naszą biblioteką interfejsu.
Nowoczesne, modne i młodzieżowe wykorzystanie CMake oznacza, że nagłówki, właściwości itp. transmitowane przez jeden cel. Wystarczy więc powiedzieć target_link_libraries(target PRIVATE dependency)i wszystkie nagłówki powiązane z elementem docelowym dependency, będzie dostępny dla źródeł należących do celu target. I nie potrzebujesz żadnego [target_]include_directories. Zostanie to wykazane poniżej w analizie Skrypt CMake do testów jednostkowych.
To polecenie wiąże potrzebne nam nagłówki z naszą biblioteką interfejsu, a jeśli nasza biblioteka jest podłączona do dowolnego celu w tej samej hierarchii CMake, to nagłówki z katalogu zostaną z nią powiązane ${CMAKE_CURRENT_SOURCE_DIR}/include, oraz jeśli nasza biblioteka jest zainstalowana w systemie i podłączona do innego projektu za pomocą polecenia find_package, wówczas nagłówki z katalogu zostaną z nim powiązane include względem katalogu instalacyjnego.
Ustalmy standard językowy. Oczywiście, że ostatni. Jednocześnie nie tylko uwzględniamy standard, ale także rozszerzamy go na tych, którzy będą korzystać z naszej biblioteki. Osiąga się to dzięki temu, że ustawiona właściwość ma kategorię INTERFACE (Zob. polecenie target_compile_features).
Stwórzmy alias dla naszej biblioteki. Co więcej, dla urody, będzie on znajdował się w specjalnej „przestrzeni nazw”. Przyda się to gdy w naszej bibliotece pojawią się różne moduły i przystąpimy do łączenia ich niezależnie od siebie. Jak na przykład w Buscie.
Instalowanie naszych nagłówków w systemie. Tutaj wszystko jest proste. Mówimy, że folder ze wszystkimi nagłówkami powinien trafić do katalogu include w stosunku do miejsca instalacji.
Tak należy rozumieć kolejne zaklęcie. Kiedy w projekcie innej firmy wywołujemy polecenie find_package(Mylib 1.2.3 REQUIRED), a rzeczywista wersja zainstalowanej biblioteki będzie z nią niekompatybilna 1.2.3CMake automatycznie wygeneruje błąd. Oznacza to, że nie będzie konieczne ręczne śledzenie wersji.
Jeśli testy są wyraźnie wyłączone przy użyciu odpowiednia opcja lub nasz projekt jest podprojektem, czyli jest połączony z innym projektem CMake za pomocą polecenia add_subdirectory, nie poruszamy się dalej w hierarchii, a skrypt opisujący polecenia służące do generowania i uruchamiania testów po prostu nie działa.
if(NOT MYLIB_TESTING)
message(STATUS "Тестирование проекта Mylib выключено")
elseif(IS_SUBPROJECT)
message(STATUS "Mylib не тестируется в режиме подмодуля")
else()
add_subdirectory(test)
endif()
Łączymy zależności. Pamiętaj, że do naszego pliku binarnego połączyliśmy tylko potrzebne cele CMake i nie wywołaliśmy polecenia target_include_directories. Nagłówki ze środowiska testowego i z naszego Mylib::mylib, a także parametry kompilacji (w naszym przypadku jest to standard języka C++) spełniły się wraz z tymi celami.
Na koniec tworzymy fikcyjny cel, którego „kompilacja” jest równoznaczna z uruchomieniem testów i dodajemy ten cel do domyślnej kompilacji (odpowiada za to atrybut ALL). Oznacza to, że domyślna kompilacja uruchamia testy, co oznacza, że nigdy nie zapomnimy ich uruchomić.
add_custom_target(check ALL COMMAND mylib-unit-tests)
Następnie włączamy pomiar pokrycia kodu, jeśli zostanie zaznaczona odpowiednia opcja. Nie będę się wdawał w szczegóły, bo dotyczą one bardziej narzędzia do pomiaru pokrycia niż CMake'a. Należy tylko pamiętać, że na podstawie wyników zostanie stworzony cel coverage, od którego wygodnie jest rozpocząć pomiar pokrycia.
find_program(GCOVR_EXECUTABLE gcovr)
if(MYLIB_COVERAGE AND GCOVR_EXECUTABLE)
message(STATUS "Измерение покрытия кода тестами включено")
target_compile_options(mylib-unit-tests PRIVATE --coverage)
target_link_libraries(mylib-unit-tests PRIVATE gcov)
add_custom_target(coverage
COMMAND
${GCOVR_EXECUTABLE}
--root=${PROJECT_SOURCE_DIR}/include/
--object-directory=${CMAKE_CURRENT_BINARY_DIR}
DEPENDS
check
)
elseif(MYLIB_COVERAGE AND NOT GCOVR_EXECUTABLE)
set(MYLIB_COVERAGE OFF)
message(WARNING "Для замеров покрытия кода тестами требуется программа gcovr")
endif()
Następnie sprawdzamy, czy użytkownik ustawił zmienną językową. Jeśli tak, to nie dotykamy tego, jeśli nie, to bierzemy rosyjski. Następnie konfigurujemy pliki systemowe Doxygen. Wszystkie niezbędne zmienne, łącznie z językiem, trafiają tam podczas procesu konfiguracji (patrz. команду configure_file).
Następnie tworzymy cel doc, który rozpocznie generowanie dokumentacji. Ponieważ generowanie dokumentacji nie jest największą potrzebą w procesie programowania, cel nie będzie domyślnie włączony; będzie musiał zostać uruchomiony jawnie.
if (Doxygen_FOUND)
if (NOT MYLIB_DOXYGEN_LANGUAGE)
set(MYLIB_DOXYGEN_LANGUAGE Russian)
endif()
message(STATUS "Doxygen documentation will be generated in ${MYLIB_DOXYGEN_LANGUAGE}")
configure_file(Doxyfile.in Doxyfile)
add_custom_target(doc COMMAND ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile)
endif ()
Tutaj znajdujemy trzeciego Pythona i tworzymy cel wandbox, który generuje żądanie odpowiadające API usługi różdżkai odsyła go. Odpowiedź zawiera łącze do gotowej piaskownicy.
find_program(PYTHON3_EXECUTABLE python3)
if(PYTHON3_EXECUTABLE)
set(WANDBOX_URL "https://wandbox.org/api/compile.json")
add_custom_target(wandbox
COMMAND
${PYTHON3_EXECUTABLE} wandbox.py mylib-example.cpp "${PROJECT_SOURCE_DIR}" include |
curl -H "Content-type: application/json" -d @- ${WANDBOX_URL}
WORKING_DIRECTORY
${CMAKE_CURRENT_SOURCE_DIR}
DEPENDS
mylib-unit-tests
)
else()
message(WARNING "Для создания онлайн-песочницы требуется интерпретатор ЯП python 3-й версии")
endif()
Zapewnia możliwość wyłączenia kompilacji i celu testów jednostkowych check. W rezultacie pomiar pokrycia kodu testami zostaje wyłączony (patrz. MYLIB_COVERAGE).
Testowanie jest również automatycznie wyłączane, jeśli projekt jest połączony z innym projektem jako podprojekt za pomocą polecenia add_subdirectory.
Zmienia język dokumentacji generowanej przez element docelowy doc do danego. Aby zapoznać się z listą dostępnych języków, zobacz Strona internetowa systemu Doxygen.
Tak naprawdę do uruchomienia niektórych poleceń konsoli opisanych w tej pomocy wymagana jest tylko wersja CMake 3.13. Z punktu widzenia składni skryptów CMake wersja 3.8 jest wystarczająca, jeśli generowanie jest wywoływane w inny sposób.
Następnie analiza statyczna będzie automatycznie uruchamiana przy każdej kompilacji i rekompilacji źródła. Nie ma potrzeby wykonywania niczego dodatkowego.
Szczęk
Za pomocą wspaniałego narzędzia scan-build Możesz także błyskawicznie przeprowadzić analizę statyczną:
CMake to bardzo wydajny i elastyczny system, który pozwala wdrożyć funkcjonalność dla każdego gustu i koloru. I choć składnia czasami pozostawia wiele do życzenia, diabeł nadal nie jest taki straszny, jak go malują. Korzystaj z systemu kompilacji CMake z korzyścią dla społeczeństwa i zdrowia.