CMake da C++ 'yan'uwa ne har abada

CMake da C++ 'yan'uwa ne har abada

A lokacin haɓakawa, Ina son canza masu tarawa, gina hanyoyi, sigogin dogaro, yin nazari mai mahimmanci, auna aiki, tattara ɗaukar hoto, samar da takardu, da sauransu. Kuma ina matukar son CMake saboda yana ba ni damar yin duk abin da nake so.

Mutane da yawa suna sukar CMake, kuma sau da yawa sun cancanci haka, amma idan kun dubi shi, ba duk abin da yake da kyau ba, kuma kwanan nan. ba sharri ko kadan, da kuma shugabanci na ci gaba ne quite tabbatacce.

A cikin wannan bayanin, ina so in gaya muku yadda ake tsara ɗakin karatu kawai a cikin C++ a cikin tsarin CMake don samun ayyuka masu zuwa:

  1. Majalisar;
  2. Gwajin Autorun;
  3. Ma'aunin ɗaukar hoto;
  4. Shigarwa;
  5. Takardun atomatik;
  6. Ƙirƙirar akwatin sandbox na kan layi;
  7. Bincike a tsaye.

Duk wanda ya riga ya fahimci fa'idodin da C-make zai iya sauƙi zazzage samfurin aikin kuma fara amfani da shi.


Abubuwa

  1. Project daga ciki
    1. Tsarin aikin
    2. Babban fayil ɗin CMake (./CMakeLists.txt)
      1. Bayanin aikin
      2. Zaɓuɓɓukan Ayyuka
      3. Zaɓuɓɓukan tattarawa
      4. babban burin
      5. saitin
      6. Gwaje-gwaje
      7. Rubutun
      8. Sandbox na kan layi
    3. Rubutun gwaji (gwaji/CMakeLists.txt)
      1. Gwaji
      2. Ɗaukar hoto
    4. Rubutun don takardu (doc/CMakeLists.txt)
    5. Rubutun don akwatin sandbox na kan layi (kan layi/CmakeLists.txt)
  2. Aikin waje
    1. Majalisar
      1. Zamani
      2. Majalisar
    2. Zaɓuɓɓuka
      1. MYLIB_COVERAGE
      2. MYLIB_TESTING
      3. MYLIB_DOXYGEN_LANGUAGE
    3. Makasudin taro
      1. da default
      2. mylib-unit-tests
      3. duba
      4. ɗaukar hoto
      5. doc
      6. wandbox
    4. misalai
  3. Kayan aiki
  4. Bincike a tsaye
  5. Bayanword

Project daga ciki

Tsarin aikin

.
├── CMakeLists.txt
├── README.en.md
├── README.md
├── doc
│   ├── CMakeLists.txt
│   └── Doxyfile.in
├── include
│   └── mylib
│       └── myfeature.hpp
├── online
│   ├── CMakeLists.txt
│   ├── mylib-example.cpp
│   └── wandbox.py
└── test
    ├── CMakeLists.txt
    ├── mylib
    │   └── myfeature.cpp
    └── test_main.cpp

Za mu yi magana game da yadda ake tsara rubutun CMake, don haka za a tattauna su dalla-dalla. Kowa na iya duba sauran fayilolin kai tsaye akan shafin aikin samfuri.

Babban fayil ɗin CMake (./CMakeLists.txt)

Bayanin aikin

Da farko, kuna buƙatar buƙatar sigar da ake buƙata na tsarin CMake. CMake yana haɓakawa, sa hannun umarni da ɗabi'a a cikin yanayi daban-daban suna canzawa. Domin CMake ya fahimci abin da muke so daga gare ta, muna buƙatar yin rikodin buƙatun mu nan da nan.

cmake_minimum_required(VERSION 3.13)

Sannan za mu zayyana aikinmu, sunansa, sigarsa, harsunan da ake amfani da su, da sauransu. (duba. команду project).

A wannan yanayin muna nuna harshe CXX (kuma wannan yana nufin C++) don kada CMake ya takura ya nemo mai tara harshen C (ta tsohuwa, CMake ya ƙunshi harsuna biyu: C da C++).

project(Mylib VERSION 1.0 LANGUAGES CXX)

Anan za ku iya bincika nan da nan ko an haɗa aikin mu a cikin wani aikin a matsayin babban aikin. Wannan zai taimaka sosai a nan gaba.

get_directory_property(IS_SUBPROJECT PARENT_DIRECTORY)

Zaɓuɓɓukan Ayyuka

Za mu samar da zaɓuɓɓuka biyu.

Zabin farko shine MYLIB_TESTING - don musaki gwajin naúrar. Wannan yana iya zama dole idan mun tabbata cewa komai yana cikin tsari tare da gwaje-gwajen, amma muna so kawai, misali, shigar ko kunshin aikin mu. Ko kuma an haɗa aikin mu a matsayin wani yanki - a wannan yanayin, mai amfani da aikinmu ba ya sha'awar gudanar da gwaje-gwajenmu. Ba ku gwada abubuwan dogaro da kuke amfani da su ba, kuna?

option(MYLIB_TESTING "Включить модульное тестирование" ON)

Bugu da kari, za mu yi wani zaɓi dabam MYLIB_COVERAGE don auna kewayon lambar ta gwaje-gwaje, amma zai buƙaci ƙarin kayan aiki, don haka yana buƙatar kunna shi a sarari.

option(MYLIB_COVERAGE "Включить измерение покрытия кода тестами" OFF)

Zaɓuɓɓukan tattarawa

Tabbas, mu masu shirye-shirye ne masu kyau, don haka muna son matsakaicin matakin tantance-lokaci daga mai tarawa. Babu linzamin kwamfuta guda daya da zai zamewa.

add_compile_options(
    -Werror

    -Wall
    -Wextra
    -Wpedantic

    -Wcast-align
    -Wcast-qual
    -Wconversion
    -Wctor-dtor-privacy
    -Wenum-compare
    -Wfloat-equal
    -Wnon-virtual-dtor
    -Wold-style-cast
    -Woverloaded-virtual
    -Wredundant-decls
    -Wsign-conversion
    -Wsign-promo
)

Za mu kuma musaki kari don cika cikakken cika ka'idojin C++. Ana kunna su ta tsohuwa a cikin CMake.

if(NOT CMAKE_CXX_EXTENSIONS)
    set(CMAKE_CXX_EXTENSIONS OFF)
endif()

babban burin

Laburaren mu ya ƙunshi fayilolin rubutun kai kawai, wanda ke nufin ba mu da wani abin sha a cikin sigar dakunan karatu na tsaye ko masu ƙarfi. A gefe guda kuma, don amfani da ɗakin karatu a waje, yana buƙatar shigar da shi, yana buƙatar a gano shi a cikin tsarin kuma a haɗa shi da aikin ku, kuma a lokaci guda waɗannan masu rubutun kai ɗaya, da yiwuwar wasu ƙarin. suna makale da shi kaddarorin.

Don wannan dalili, mun ƙirƙiri ɗakin karatu na dubawa.

add_library(mylib INTERFACE)

Muna ɗaure kanun labarai zuwa ɗakin karatu na mu'amala.

Na zamani, na zamani, amfani da matasa na CMake yana nuna cewa masu kai, kadarori, da sauransu. ana watsa ta hanyar manufa guda ɗaya. Don haka ya isa a ce target_link_libraries(target PRIVATE dependency), da duk rubutun da ke da alaƙa da manufa dependency, zai kasance samuwa ga kafofin na abin da aka hari target. Kuma ba kwa buƙatar ko ɗaya [target_]include_directories. Za a nuna wannan a ƙasa a cikin bincike CMake rubutun don gwajin raka'a.

Har ila yau, yana da daraja kula da abin da ake kira. выражения-генераторы: $<...>.

Wannan umarnin yana danganta kanun da muke buƙata tare da ɗakin karatu na mu'amala, kuma idan ɗakin ɗakin karatu namu yana da alaƙa da kowane manufa a cikin tsarin CMake iri ɗaya, to za a haɗa masu kai daga kundin adireshi da shi. ${CMAKE_CURRENT_SOURCE_DIR}/include, kuma idan an shigar da ɗakin karatu a kan tsarin kuma an haɗa shi zuwa wani aikin ta amfani da umarnin find_package, sa'an nan headers daga directory za a hade tare da shi include dangane da kundin shigarwa.

target_include_directories(mylib INTERFACE
    $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
    $<INSTALL_INTERFACE:include>
)

Bari mu saita ma'aunin harshe. Tabbas, na ƙarshe. Har ila yau, ba kawai mun haɗa da ma'auni ba, amma kuma muna mika shi ga waɗanda za su yi amfani da ɗakin karatu. Ana samun wannan saboda gaskiyar cewa kadarar da aka saita tana da nau'i INTERFACE (duba.) target_compile_features order).

target_compile_features(mylib INTERFACE cxx_std_17)

Bari mu ƙirƙiri wani laƙabi don ɗakin karatu. Bugu da ƙari, don kyakkyawa, zai kasance a cikin "spacespace" na musamman. Wannan zai zama da amfani lokacin da nau'o'i daban-daban suka bayyana a cikin ɗakin karatu, kuma za mu je mu haɗa su ba tare da juna ba. Kamar a Busta, alal misali.

add_library(Mylib::mylib ALIAS mylib)

saitin

Shigar da kawunan mu a cikin tsarin. Komai yana da sauki a nan. Mun ce babban fayil ɗin tare da duk masu kai ya kamata ya shiga cikin kundin adireshi include dangane da wurin shigarwa.

install(DIRECTORY include/mylib DESTINATION include)

Na gaba, muna sanar da tsarin ginawa cewa muna so mu iya kiran umarni a cikin ayyukan ɓangare na uku find_package(Mylib) kuma samun manufa Mylib::mylib.

install(TARGETS mylib EXPORT MylibConfig)
install(EXPORT MylibConfig NAMESPACE Mylib:: DESTINATION share/Mylib/cmake)

Ya kamata a fahimci sihiri na gaba ta wannan hanyar. Lokacin a cikin aikin ɓangare na uku muna kiran umarni find_package(Mylib 1.2.3 REQUIRED), kuma ainihin sigar ɗakin karatu da aka shigar ba zai dace da sigar ba 1.2.3CMake zai haifar da kuskure ta atomatik. Wato, ba za ku buƙaci bin sigogi da hannu ba.

include(CMakePackageConfigHelpers)
write_basic_package_version_file("${PROJECT_BINARY_DIR}/MylibConfigVersion.cmake"
    VERSION
        ${PROJECT_VERSION}
    COMPATIBILITY
        AnyNewerVersion
)
install(FILES "${PROJECT_BINARY_DIR}/MylibConfigVersion.cmake" DESTINATION share/Mylib/cmake)

Gwaje-gwaje

Idan an kashe gwaje-gwaje a bayyane ta amfani madaidaicin zaɓi ko kuma aikinmu wani yanki ne, wato, an haɗa shi da wani aikin CMake ta amfani da umarnin add_subdirectory, ba ma matsawa gaba tare da matsayi, kuma rubutun, wanda ke bayyana umarnin don samarwa da gudanar da gwaje-gwaje, kawai ba ya gudana.

if(NOT MYLIB_TESTING)
    message(STATUS "Тестирование проекта Mylib выключено")
elseif(IS_SUBPROJECT)
    message(STATUS "Mylib не тестируется в режиме подмодуля")
else()
    add_subdirectory(test)
endif()

Rubutun

Har ila yau, ba za a samar da daftarin aiki ba a yanayin aiki.

if(NOT IS_SUBPROJECT)
    add_subdirectory(doc)
endif()

Sandbox na kan layi

Hakanan, aikin ba zai sami akwatin sandbox na kan layi ba.

if(NOT IS_SUBPROJECT)
    add_subdirectory(online)
endif()

Rubutun gwaji (gwaji/CMakeLists.txt)

Gwaji

Da farko, mun sami kunshin tare da tsarin gwajin da ake buƙata (maye gurbin da wanda kuka fi so).

find_package(doctest 2.3.3 REQUIRED)

Bari mu ƙirƙiri fayil ɗin mu mai aiwatarwa tare da gwaje-gwaje. Yawancin lokaci ina ƙara kai tsaye zuwa binary mai aiwatarwa kawai fayil ɗin da zai ƙunshi aikin main.

add_executable(mylib-unit-tests test_main.cpp)

Kuma ina ƙara fayilolin da aka kwatanta gwaje-gwajen da kansu daga baya. Amma ba lallai ne ku yi hakan ba.

target_sources(mylib-unit-tests PRIVATE mylib/myfeature.cpp)

Muna haɗa abubuwan dogaro. Da fatan za a lura cewa mun haɗu kawai abubuwan CMake da muke buƙata zuwa binary ɗin mu kuma ba mu kira umarnin ba target_include_directories. Jagora daga tsarin gwajin kuma daga namu Mylib::mylib, da kuma gina sigogi (a cikin yanayinmu, wannan shine ma'aunin harshen C ++) ya zo tare da waɗannan manufofin.

target_link_libraries(mylib-unit-tests
    PRIVATE
        Mylib::mylib
        doctest::doctest
)

A ƙarshe, mun ƙirƙiri maƙasudi mai ƙima, “gini” wanda yayi daidai da gwaje-gwaje masu gudana, kuma muna ƙara wannan manufa zuwa ginin da aka saba (sifa ce ke da alhakin wannan). ALL). Wannan yana nufin cewa tsoho ginin yana haifar da gwaje-gwaje don gudana, ma'ana ba za mu taɓa mantawa da gudanar da su ba.

add_custom_target(check ALL COMMAND mylib-unit-tests)

Ɗaukar hoto

Na gaba, muna kunna ma'aunin ɗaukar hoto idan an ƙayyade zaɓin da ya dace. Ba zan shiga cikin cikakkun bayanai ba, saboda suna da alaƙa da kayan aiki don auna ɗaukar hoto fiye da CMake. Yana da mahimmanci kawai a lura cewa bisa ga sakamakon za a ƙirƙiri manufa coverage, wanda ya dace don fara auna ɗaukar hoto.

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()

Rubutun don takardu (doc/CMakeLists.txt)

An samo Doxygen.

find_package(Doxygen)

Na gaba, muna duba ko mai amfani ya saita canjin harshe. Idan eh, to, ba mu taɓa shi ba, idan ba haka ba, to muna ɗaukar Rashanci. Sannan muna saita fayilolin tsarin Doxygen. Duk masu canjin da ake buƙata, gami da harshe, suna zuwa wurin yayin tsarin daidaitawa (duba. команду configure_file).

Sa'an nan kuma mu ƙirƙira manufa doc, wanda zai fara samar da takardu. Tunda samar da takaddun ba shine babban buƙatu ba a cikin tsarin haɓakawa, ba za a kunna manufa ta tsohuwa ba; dole ne a ƙaddamar da shi a sarari.

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 ()

Rubutun don akwatin sandbox na kan layi (kan layi/CmakeLists.txt)

Anan mun sami Python na uku kuma mun ƙirƙiri manufa wandbox, wanda ke haifar da buƙatar da ta dace da API ɗin sabis Wandbox, ya sallame shi. Amsar ta zo tare da hanyar haɗi zuwa akwatin yashi da aka gama.

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()

Aikin waje

Yanzu bari mu dubi yadda za a yi amfani da duk wannan.

Majalisar

Gina wannan aikin, kamar kowane aiki akan tsarin ginin CMake, ya ƙunshi matakai biyu:

Zamani

cmake -S путь/к/исходникам -B путь/к/сборочной/директории [опции ...]

Idan umarnin da ke sama bai yi aiki ba saboda tsohuwar sigar CMake, gwada tsallakewa -S:

cmake путь/к/исходникам -B путь/к/сборочной/директории [опции ...]

Ƙari game da zaɓuɓɓuka.

Gina aikin

cmake --build путь/к/сборочной/директории [--target target]

Ƙari game da manufofin taro.

Zaɓuɓɓuka

MYLIB_COVERAGE

cmake -S ... -B ... -DMYLIB_COVERAGE=ON [прочие опции ...]

Ya haɗa da manufa coverage, wanda da shi zaku iya fara auna kewayon lambar ta gwaji.

MYLIB_TESTING

cmake -S ... -B ... -DMYLIB_TESTING=OFF [прочие опции ...]

Yana ba da ikon musaki ginin gwajin naúrar da manufa check. Sakamakon haka, ana kashe ma'aunin ɗaukar hoto ta gwaje-gwaje (duba. MYLIB_COVERAGE).

Hakanan ana kashe gwajin ta atomatik idan an haɗa aikin zuwa wani aikin azaman aikin jigo ta amfani da umarni add_subdirectory.

MYLIB_DOXYGEN_LANGUAGE

cmake -S ... -B ... -DMYLIB_DOXYGEN_LANGUAGE=English [прочие опции ...]

Yana canza yaren takaddun da manufa ta haifar doc ga wanda aka bayar. Don jerin harsunan da ake da su, duba Gidan yanar gizon tsarin Doxygen.

Rashanci yana kunna ta tsohuwa.

Makasudin taro

da default

cmake --build path/to/build/directory
cmake --build path/to/build/directory --target all

Idan ba a bayyana maƙasudin ba (wanda ya yi daidai da abin da aka sa a gaba all), tattara duk abin da zai iya, kuma ya kira manufa check.

mylib-unit-tests

cmake --build path/to/build/directory --target mylib-unit-tests

Yana tattara gwaje-gwajen naúrar. An kunna ta tsohuwa.

duba

cmake --build путь/к/сборочной/директории --target check

Yana gudanar da gwaje-gwajen naúrar da aka tattara (da aka tattara, idan ba a riga ba). An kunna ta tsohuwa.

Duba kuma mylib-unit-tests.

ɗaukar hoto

cmake --build путь/к/сборочной/директории --target coverage

Yana nazarin gwajin gudana (gudu, idan ba a rigaya ba) gwaje-gwajen naúrar don ɗaukar lamba ta gwaje-gwaje ta amfani da shirin gcvr.

Shaye shaye zai yi kama da haka:

------------------------------------------------------------------------------
                           GCC Code Coverage Report
Directory: /path/to/cmakecpptemplate/include/
------------------------------------------------------------------------------
File                                       Lines    Exec  Cover   Missing
------------------------------------------------------------------------------
mylib/myfeature.hpp                            2       2   100%   
------------------------------------------------------------------------------
TOTAL                                          2       2   100%
------------------------------------------------------------------------------

Makasudin yana samuwa ne kawai lokacin da zaɓin ya kunna MYLIB_COVERAGE.

Duba kuma check.

doc

cmake --build путь/к/сборочной/директории --target doc

Yana fara ƙirƙirar takaddun lambobi ta amfani da tsarin Oxygen.

wandbox

cmake --build путь/к/сборочной/директории --target wandbox

Amsa daga sabis ɗin yayi kama da haka:

{
    "permlink" :    "QElvxuMzHgL9fqci",
    "status" :  "0",
    "url" : "https://wandbox.org/permlink/QElvxuMzHgL9fqci"
}

Ana amfani da sabis don wannan Wandbox. Ban san yadda sabobin su ke sassauƙa ba, amma ina ganin bai kamata a yi amfani da wannan damar ba.

misalai

Gina aikin a cikin yanayin lalata tare da ma'aunin ɗaukar hoto

cmake -S путь/к/исходникам -B путь/к/сборочной/директории -DCMAKE_BUILD_TYPE=Debug -DMYLIB_COVERAGE=ON
cmake --build путь/к/сборочной/директории --target coverage --parallel 16

Shigar da aikin ba tare da taro na farko da gwaji ba

cmake -S путь/к/исходникам -B путь/к/сборочной/директории -DMYLIB_TESTING=OFF -DCMAKE_INSTALL_PREFIX=путь/к/установойной/директории
cmake --build путь/к/сборочной/директории --target install

Gina a yanayin saki tare da mai tarawa da aka bayar

cmake -S путь/к/исходникам -B путь/к/сборочной/директории -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_COMPILER=g++-8 -DCMAKE_PREFIX_PATH=путь/к/директории/куда/установлены/зависимости
cmake --build путь/к/сборочной/директории --parallel 4

Samar da takardu cikin Turanci

cmake -S путь/к/исходникам -B путь/к/сборочной/директории -DCMAKE_BUILD_TYPE=Release -DMYLIB_DOXYGEN_LANGUAGE=English
cmake --build путь/к/сборочной/директории --target doc

Kayan aiki

  1. CMake 3.13

    A zahiri, sigar CMake 3.13 ana buƙatar kawai don gudanar da wasu umarni na wasan bidiyo da aka kwatanta a cikin wannan taimako. Daga ra'ayi na syntax na rubutun CMake, sigar 3.8 ya isa idan an kira tsara ta wasu hanyoyi.

  2. Laburaren gwaji likita

    Za a iya kashe gwaji (duba опцию MYLIB_TESTING).

  3. Oxygen

    Don canza yaren da za a samar da takaddun, an ba da zaɓi MYLIB_DOXYGEN_LANGUAGE.

  4. Mai fassara harshe Python 3

    Don tsara ta atomatik kan layi sandboxes.

Bincike a tsaye

Tare da CMake da wasu kayan aiki masu kyau, zaku iya samar da ingantaccen bincike tare da ƙaramin ƙoƙari.

Cppcheck

CMake yana da ginanniyar tallafi don kayan aikin bincike a tsaye Cppcheck.

Don yin wannan kuna buƙatar amfani da zaɓi CMAKE_CXX_CPPCHECK:

cmake -S путь/к/исходникам -B путь/к/сборочной/директории -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_CPPCHECK="cppcheck;--enable=all;-Iпуть/к/исходникам/include"

Bayan haka, za a ƙaddamar da bincike ta atomatik a duk lokacin da aka haɗa tushen da sake tattarawa. Babu buƙatar yin ƙarin wani abu.

Yin magana

Tare da taimakon kayan aiki mai ban mamaki scan-build Hakanan zaka iya gudanar da bincike a tsaye ba tare da wani lokaci ba:

scan-build cmake -S путь/к/исходникам -B путь/к/сборочной/директории -DCMAKE_BUILD_TYPE=Debug
scan-build cmake --build путь/к/сборочной/директории

Anan, ba kamar yanayin Cppcheck ba, kuna buƙatar aiwatar da ginin kowane lokaci ta hanyar scan-build.

Bayanword

CMake wani tsari ne mai ƙarfi da sassauci wanda ke ba ku damar aiwatar da ayyuka ga kowane dandano da launi. Kuma, ko da yake ma'auni a wasu lokuta yakan bar abubuwa da yawa don so, shaidan har yanzu ba shi da muni kamar yadda ake fentin shi. Yi amfani da tsarin ginin CMake don amfanin al'umma da lafiya.

Zazzage samfurin aikin

source: www.habr.com

Add a comment