Development =========== Development server structure ---------------------------- Git and Gitflow ------------- Описание последовательности разработки, взятое за основу в ExpertRoot, приведено |location link 1|. .. |location link 1| raw:: html здесь Стандарный ход работы над новым функционалом ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. figure:: _images/Git_workflow.png :scale: 100 % :align: center Рисунок N. Основы ветвления git при разработке ExpertRoot. .. TODO дать ссылку на редмайн * Создать issue на redmine, получить номер (допустим, 1324). * В локальном репозитории перейти в ветку, о которой будет выполняться ответвление (чаще всего такой веткой будет dev). :: git checkout dev * Создать в локальном репозитории новую ветку имеющую имя, составленное из номера issue и очень краткое описание задачи, разделённые символом подчёркивания. :: git branch 1324_NDdigi * Перейти в новую ветку :: git checkout 1324_NDdigi * Работать в этой ветке над задачей 1324 (дижитизация нейтронного дететора ND). * По мере работы выполнять коммиты и заливать их в соответствующую ветку в центральном репозитории на github. :: git commit git push .. note:: Пишите максимально подробные комментарии, это сильно облегчит жизнь в будущем! .. note:: При первой попытке залить коммиты на github система выдаст сообщение с правильной командой для указания upstream ветки. * По окончании работы над задачей делается последний commit+push, а затем на сайте github.com делается запрос на слияние (pull request), который обрабатывается ответственным лицом. При создании запроса на слияние следует внимательно выбрать ветки из которой и в которую будет происходить слияние. Также необходимо справа на странице выбрать reviewer'а. * Администратор обрабатывает pull request, удаляет исходную ветку. * Делаются соответствующие изменения в redmine, например, issue закрывается. Самые полезные функции ~~~~~~~~~~~~~~~~~~~~~~ Пример неплохой подсказки по работе с git: |location link 4|. .. |location link 4| raw:: html Git Cheat Sheet by Jan Krueger См. также google -> git cheat sheet git stash ~~~~~~~~~ Перед тем как перейти на другую ветку, если есть какие-либо изменения, которые вы не хотите пока что коммитить, следует воспользоваться возможностью git откладывать изменения. :: git stash git stash list git stash drop .. TODO дописать, расширить git cherry-piсk ~~~~~~~~~~~~~~~ Если при работе над issueX есть необходимость взять какое-то обновление, сделанное в другой ветке, следует воспользоваться возможностью git применения отдельных коммитов из других веток. Сначала необходимо получить номер коммита, например с помощью лога. :: git log .. note:: Используйте пробел, чтобы посмотреть следующую страницу, стрелки и PageUp/PageDown для навигации. Нажмите q, чтобы выйти. Затем примените найденный коммит в своей ветке :: git cherry-pick .. TODO дописать, проверить Documenting ----------- Документирование ER разделяется на документирование логики с помощью системы сборки документации sphinx-doc и автоматической генерации документации на класс по коду с помощью doxygen. Исходники документации sphinx написаны в формате |location link 2| и расположены в репозитории в папке docs. См. также |location link 3|. .. |location link 2| raw:: html reStructedText .. |location link 3| raw:: html reST and Sphinx CheatSheet По адресу er.jinr.ru расположена документация, собранная sphinx из ветки dev. Для обновления информации в ней необходимо отредактировать документацию в репозитории и запустить задачу обновления документации (update doc) в jenkins (er.jinr.ru:8080). Redmine ------- Gitlab-CI builds and tests -------------------------- .. figure:: _images/er_ci.png :scale: 100 % :align: center Сборка и тесты er Для сборки и тестов используем gitlab-ci cервиса er.jinr.ru/git. Ежедневно происходит синхронизация репозитория в github и его зеркала для тестов с помощью cron-задачи (планировщика) на хосте er.jinr.ru. Сценарий тестирования прописан в ``.gitlab-ci.yml``, в корне репозитория. Тесты выполняются с помощью gitlab runner (type shell) на преднастроенной виртуальной машине в облаке ОИЯИ. Все необходимые зависимости уже установлены и пути к ним прописаны в разделе variables ``.gitlab-ci.yml``. Если требуется использовать входной файл для тестов (например lmd), можно указать прямую ссылку на файл или загрузить его на виртуальную машину, отправив письмо с такой просьбой на schetinin@jinr.ru. Dashboard c текущим состоянием тестов доступен тут: http://er.jinr.ru/git/ExpertRootGroup/er/pipelines/ QA monitors ----------- Development stadarts -------------------- Лицензия ~~~~~~~~ ExpertRoot распространяется под лицензией LGPL . В шапке всех исходных файлов должно быть добавлено: :: /******************************************************************************** * Copyright (C) Joint Institute for Nuclear Research * * * * This software is distributed under the terms of the * * GNU Lesser General Public Licence version 3 (LGPL) version 3, * * copied verbatim in the file "LICENSE" * ********************************************************************************/ .. _ide_setup: Среда разработки ~~~~~~~~~~~~~~~~ Пользователь имеет полное право сам выбрать среду, в которой он будет выполнять редактирование исходного кода ExpertRoot. Мы советуем применять Sublime - простой, дружественный для пользователя, но в то же время достаточно мощный текстовый редактор. Настройка Sublime выполняется редактированием текстового файла, открывающегося через меню Preferences -> Settings. Ниже приведен пример такого файла. :: { "color_scheme": "Packages/Color Scheme - Default/Monokai.tmTheme", "font_size": 16, "tab_size": 2, "translate_tabs_to_spaces": true, "trim_trailing_white_space_on_save": true, "ensure_newline_at_eof_on_save": true } В Sublime есть возможность открыть для редактирования всю папку. Для этого следует воспользоваться меню File -> Open Folder... либо указать путь к папке при запуске Sublime из командной строки. Например: :: cd ~/expertroot/ subl . Открытая папка отображает на боковой панели, которую можно показать/спрятать с помощью пункта меню View -> Side bar -> Show/Hide side bar. Code convention / C++ для физиков ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. TODO причесать и расширить вводное слово Язык программирования C++ предоставляет богатые возможности для разработки. Один и тот же функционал можно написать множеством различных способов. Тот факт, что код компилируется, ещё не значит, что он написан правильно. Даже тот факт, что он работает, ещё не значит, что он написан правильно. Более того, тот факт, что он работает правильно, ещё не значит, что он написан правильно. При написании больших программных пакетов обычно придерживаются некоторого соглашения. Перечисленные ниже инструкции ставят своей целью ограничить разработчика от использования разнородных возможностей для унификации кода, повышения его читаемости, переностимости, упрощения поддержки и т.д. * Идентификаторы классов должны начинаться с префикса ER (``ERNeuRad``, ``ERGadast``, ...) * Должно выдерживаться соотношение: один класс - два файла (``.h`` и ``.cxx``) * Файлы включений должны быть расположены в следующей последовательности 1. Заголовочный файл класса, если это ``.cxx`` 2. Заголовочные файлы библиотеки ``STL`` 3. Заголовочные файлы библиотеки ``Boost`` 4. Заголовочные файлы фреймворка ``Root`` 5. Заголовочные файлы фреймворка ``Geant`` 6. Заголовочные файлы фреймворка ``FairRoot`` 7. Заголовочные файлы пакета ``ExpertRoot`` .. TODO пояснить пункт1! Я то понял, а вот сторонний читатель - вряд ли * Между файлами включения разных библиотек должен быть отступ - пустая строка. :: #include "ERNeuRad.h" #include #include "TClonesArray.h" #include "TParticle.h" #include "TVirtualMC.h" #include "TGeoMatrix.h" #include "TString.h" #include "FairRootManager.h" #include "FairRun.h" #include "FairRunSim.h" #include "FairRuntimeDb.h" * В заголовочном файле должны быть подключены только другие заголовочные файлы (т.е. ``.h``, либо без расширения, как это принято, например, в стандартной библиотеке C++), причём только те внешние заголовочные файлы, которые необходимы. Запрещено подключать файлы реализации (то есть ``.cxx``). Следует понимать, что при работе с ROOT часто возникает ситуация, когда какие-то базовые заголовочные файлы (типа ``TObject.h``, ``TNamed.h``, ...) подключаются к разрабатываемому файлу косвенно через заголовочные файлы более высокого уровня (дочерние классы). Поэтому нередко бывает, что явно используется некоторый базовый класс, но никакого подключения именно для этого класса нет. .. TODO выработать политику по данному вопросу * Объявления пространств имен должны быть расположены после включений заголовочных файлов, в той же последовательности, с теми же отступами и только в исходном файле. Объявления пространств имен в заголовочном файле не допускается. * Код заголовочного файла должен быть обернут в директивы (идентификатор директивы формируется из названия класса и ``_H``): .. TODO может быть полностью буквами верхнего регистра? :: #ifndef ERNeuRad_H #define ERNeuRad_H ... #endif // ERNeuRad_H * В конце любого файла с кодом должна быть пустая строка. Некоторые редакторы позволяют включить настройку, которая автоматически добавляет пустую строку, если её нет, при сохранении файла (см. :ref:`ide_setup`). * Для объявления некоторого внешнего класса ERXXX в заголовочном файле некоторого другого класса ERZZZ следует использовать не подключение типа :: #include "ERXXX.h" а предварительную декларацию :: class ERXXX; Такой подход работает, т.е. компилятор корректно отрабатывает, если от подключаемого класса ERXXX не происходит наследования и нет вызовов его методов прямо в заголовочном файле. Наиболее частый случай - когда в заголовочном файле класс ERXXX фигурирует только для задания указателя в списке членов данных описываемого класса. :: class ERXXX; class ERZZZ : public ERVVV { private: ERXXX* fErxxObject; ... } Однако :: #include "ERXXX.h" class ERZZZ : public ERVVV { private: ERXXX fErxxObject; ... } и :: #include "ERXXX.h" class ERZZZ : public ERXXX { ... } Такая практика позволяет избежать избыточной многократной компиляции, что в случае большого проекта заметно сокращает общее время компиляции. .. TODO разобраться получше и сформулировать чётко и ясно. Пока что здесь всё очень обтекаемо. * Вся реализация (в рамках ExpertRoot это практически всегда означает "реализация методов") должна располагаться в файлах ``.cxx``, а не в заголовочных файлах ``.h``. Исключение составляют только очень короткие методы, реализацию которых можно поместить в ту же строку, что и объявление в файле ``.h``. Настоятельно рекомендуется использовать такой подход только для так называемых accessor'ов (getter/setter/modifier). Однако даже для них, можно смело писать реализацию в ``.cxx``, пусть и однострочную, не загромождая заголовочный файл. В целом, реализация в 'хедерах' сильно повышает читаемость (пока она умещается в ту же строку, что и сигнатура), и лишь незначительно замедляет компиляцию. :: /** Accessors **/ Int_t GetEventID() const { return fEventID; } Int_t GetMot0TrackID() const { return fMot0TrackID; } Double_t GetXIn() const { return fX; } Double_t GetYIn() const { return fY; } Double_t GetZIn() const { return fZ; } Double_t GetXInLocal() const { return fXlocal; } Double_t GetYInLocal() const { return fYlocal; } Double_t GetZInLocal() const { return fZlocal; } * В заголовочном файле следует отличать атрибуты (члены данных) и методы класса. Член данных не может иметь реализации - он представляет собой обычную переменную того или иного типа. Часто требуется выполнять динамическое распределение памяти с помощью оператора ``new`` для атрибутов класса и/или его инициализацию. Эти операции должны выполняться в конструкторе, а соответствующее освобождение памяти - в деструкторе. * В определении класса последовательно должны быть введены следующие блоки: * Первый public: * Конструктор по умолчанию - конструктор без параметров. (Требование интерпретатора Root) * Конструкторы с параметрами * Деструктор (виртуальный, если класс является наследником) * Конструктор копирования и оператор присваиваивания. Обязательны для классов данных, для остальных - по необходимости. * Блок методов модификаторов, интерфейсы для изменения данных, настройки класса. Должен начинаться с комментария ``/* Modifiers */``. Каждый метод должен начинаться с префикса ``Set``. * Блок методов аксессоров, для получения данных класса. Должен начинаться с комментария ``/*Accessors*/``. Методы должны быть константными. * Второй public: * Виртуальные публичные методы класса. * Остальные публичные методы класса. Атрибут класса не может находится в блоке public. Каждому атрибуту класса данных необходимо предоставть аксессор и модификатор. В случае остальных классов - по необходимости. * protected методы (возникают исключительно в тот момент, когда оказались необходимы) * protected данные (так же как и в предыдущем пункте) * private методы * private данные * В файле исходного кода реализации методов расположены в той же последовательности, что и в определении класса. * Между методами необходимо добавлять разделитель в виде одной строки: :: //-------------------------------------------------------------------------------------------------- * Имена всех методов класса начинаются с буквы верхнего регистра. Нижние подчеркивания в названиях методов не допускаются. В аббревиатуре только первая буква пишется заглавной (``ER`` является исключением). * Имена всех атрибутов (членов данных) класса начинаются с префикса ``f``. * Имена всех переменных начинаются с буквы нижнего регистра. .. note:: Основное требование кода - его прозрачность. Не стоит жалеть символов на идентификаторы и строк на комментарии. * Ширина строки не должна превышать 100 символов. Это также можно настроить в текстовом редакторе. .. TODO А точно ли мы хотим вводить такое правило? Актуально ли оно? * Табулирование кода (отступы слева) выполняется двумя пробелами. Использование знака табуляции для разметки недопустимо. Для удобства можно настроить текстовый редактор так, чтобы он выполнял замену табуляции двумя пробелами (см. :ref:`ide_setup`). * Не допускатся пробелы и знаки табуляции в конце строки (также см. :ref:`ide_setup`). * После запятой должен стоять пробел (пример ниже). * Оператор присваивания ``=`` должен отделяться пробелами с обеих сторон (пример ниже). * Операторы сдвига/стриминга ``<<`` должны отделяться пробелами с обеих сторон (пример ниже). * Открывающая фигурная скобка должна стоять в той же строке, что и оператор или сигнатура (и т.д.), и отделена от предыдущего символа пробелом. Исключением является скобка после списка инициализации. Закрывающая фигурная скобка - в отдельной строке. :: void ERNeuRad::CopyClones(TClonesArray* cl1, TClonesArray* cl2, Int_t offset) { Int_t nEntries = cl1->GetEntriesFast(); LOG(DEBUG) << "NeuRad: " << nEntries << " entries to add" << FairLogger::endl; ... } * Логирование необходимо осуществлять с помощью средств FairLogger и указанием уровней логирования: ``LOG(INFO), LOG(WARN), LOG(ERROR), LOG(DEBUG), LOG(DEBUG2)``. Аварийное завершение c выводом backtrace в core_dump файл вызывается с помощью ``LOG(FATAL)``. В качестве символа окончания строки следует использовать ``FairLogger::endl``. Следует избегать применения библиотеки iostream, т.е. вывода с помощью cout и cerr. Отсюда же следует, что в коде **не должны появляться** конструкции вида :: using std::cout; using std::cerr; using std::endl; * Базовым стандартом разработки является C++11. Но не весь. Не стоит использовать лямбда-функции, они действительно затрудняют чтение кода. * В качестве библиотеки контейнеров использовать STL. * Для итераторов использовать ``auto``. * Синтаксис range-based циклов использовать следующий, если возможно: :: std::map> map; std::vector v; v.push_back(1); v.push_back(2); v.push_back(3); map["one"] = v; for(const auto &kvp: map) { std::cout << kvp.first << std::endl; for(auto v: kvp.second) std::cout << v << std::endl; } int arr[] = {1,2,3,4,5}; for(int &e: arr) e *= e; .. _cmake_struct: Структура cmake сценария для сборки библиотеки классов ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Каждая директория проекта ER, кроме служебных - docs, gconfig, geometry, macro, parameters, templates - является директорией исходных кодов одной библиотеки. Стандартный cmake сценарий сборки библиотеки выглядит так: :: # Create a library called "libNeuRad" which includes the source files given in # the array . # The extension is already found. Any number of sources could be listed here. set(INCLUDE_DIRECTORIES ${BASE_INCLUDE_DIRECTORIES} ${ROOT_INCLUDE_DIR} ${Boost_INCLUDE_DIRS} ${CMAKE_SOURCE_DIR}/ERData/NeuRadData/ ${CMAKE_SOURCE_DIR}/ERData/ ${CMAKE_SOURCE_DIR}/NeuRad/ ${CMAKE_SOURCE_DIR}/ERBase/ ) include_directories( ${INCLUDE_DIRECTORIES}) set(LINK_DIRECTORIES ${BASE_LINK_DIRECTORIES} ${FAIRROOT_LIBRARY_DIR} ) link_directories( ${LINK_DIRECTORIES}) set(SRCS ERNeuRad.cxx ERNeuRadDigitizer.cxx ERNeuRadContFact.cxx ERNeuRadDigiPar.cxx ERNeuRadGeoPar.cxx ERNeuRadSetup.cxx ERNeuRadHitFinder.cxx ERNeuRadHitFinderMF.cxx ERNeuRadHitFinderWBT.cxx ERNeuRadMatcher.cxx ) # fill list of header files from list of source files # by exchanging the file extension CHANGE_FILE_EXTENSION(*.cxx *.h HEADERS "${SRCS}") Set(LINKDEF ERNeuRadLinkDef.h) Set(LIBRARY_NAME NeuRad) Set(DEPENDENCIES ERBase ERData Base Core Geom) GENERATE_LIBRARY() Для использования библиотеки в макросах ROOT ее нужно собрать с помощью `специального инструмента и процедуры сборки. `_ Данный процесс автоматизирован с помощью функции ``GENERATE_LIBRARY()``, которая находится в cmake модулях пакета ``FAIRroot``. Сценарий начинается с инициализации списка директорий include файлов: :: set(INCLUDE_DIRECTORIES ${BASE_INCLUDE_DIRECTORIES} ${ROOT_INCLUDE_DIR} ${Boost_INCLUDE_DIRS} ${CMAKE_SOURCE_DIR}/ERData/NeuRadData/ ${CMAKE_SOURCE_DIR}/ERData/ ${CMAKE_SOURCE_DIR}/NeuRad/ ${CMAKE_SOURCE_DIR}/ERBase/ ) include_directories( ${INCLUDE_DIRECTORIES}) Переменные ``BASE_INCLUDE_DIRECTORIES, ROOT_INCLUDE_DIR, Boost_INCLUDE_DIRS`` определены в корневом cmake сценарии проекта и модулях, отвечающих за поиск соответствующих пакетов в системе. Например ``~/fair_install/fairroot_inst/share/fairbase/cmake/modules/FindROOT.cmake``. Далее инициализируется список директорий с библиотеками для линковки. :: set(LINK_DIRECTORIES ${BASE_LINK_DIRECTORIES} ${FAIRROOT_LIBRARY_DIR} ) link_directories(${LINK_DIRECTORIES}) Далее инициализируется список исходников, которые будут включены в библиотеку. :: set(SRCS ERNeuRad.cxx ERNeuRadDigitizer.cxx ERNeuRadContFact.cxx ERNeuRadDigiPar.cxx ERNeuRadGeoPar.cxx ERNeuRadSetup.cxx ERNeuRadHitFinder.cxx ERNeuRadHitFinderMF.cxx ERNeuRadHitFinderWBT.cxx ERNeuRadMatcher.cxx ) # fill list of header files from list of source files # by exchanging the file extension CHANGE_FILE_EXTENSION(*.cxx *.h HEADERS "${SRCS}") Назначается LinkDef файл, имя библиотеки и список библиотек для линковки. :: Set(LINKDEF ERNeuRadLinkDef.h) Set(LIBRARY_NAME NeuRad) Set(DEPENDENCIES ERBase ERData Base Core Geom) Вызывается функция ``GENERATE_LIBRARY()``. :: GENERATE_LIBRARY()