Development

Development server structure

Git and Gitflow

Описание последовательности разработки, взятое за основу в ExpertRoot, приведено здесь.

Стандарный ход работы над новым функционалом

_images/Git_workflow.png

Рисунок N. Основы ветвления git при разработке ExpertRoot.

  • Создать issue на redmine, получить номер (допустим, 1324).
  • В локальном репозитории перейти в ветку, о которой будет выполняться ответвление (чаще всего такой веткой будет dev).
git checkout dev
  • Создать в локальном репозитории новую ветку имеющую имя, составленное из номера issue и очень краткое описание задачи, разделённые символом подчёркивания.
git branch 1324_NDdigi
  • Перейти в новую ветку
git checkout 1324_NDdigi
  • Работать в этой ветке над задачей 1324 (дижитизация нейтронного дететора ND).
  • По мере работы выполнять коммиты и заливать их в соответствующую ветку в центральном репозитории на github.
git commit <flags>
git push

Note

Пишите максимально подробные комментарии, это сильно облегчит жизнь в будущем!

Note

При первой попытке залить коммиты на github система выдаст сообщение с правильной командой для указания upstream ветки.

  • По окончании работы над задачей делается последний commit+push, а затем на сайте github.com делается запрос на слияние (pull request), который обрабатывается ответственным лицом. При создании запроса на слияние следует внимательно выбрать ветки из которой и в которую будет происходить слияние. Также необходимо справа на странице выбрать reviewer’а.
  • Администратор обрабатывает pull request, удаляет исходную ветку.
  • Делаются соответствующие изменения в redmine, например, issue закрывается.

Самые полезные функции

Пример неплохой подсказки по работе с git: Git Cheat Sheet by Jan Krueger.

См. также google -> git cheat sheet

git stash

Перед тем как перейти на другую ветку, если есть какие-либо изменения, которые вы не хотите пока что коммитить, следует воспользоваться возможностью git откладывать изменения.

git stash
git stash list
git stash drop <name>

git cherry-piсk

Если при работе над issueX есть необходимость взять какое-то обновление, сделанное в другой ветке, следует воспользоваться возможностью git применения отдельных коммитов из других веток. Сначала необходимо получить номер коммита, например с помощью лога.

git log

Note

Используйте пробел, чтобы посмотреть следующую страницу, стрелки и PageUp/PageDown для навигации. Нажмите q, чтобы выйти.

Затем примените найденный коммит в своей ветке

git cherry-pick <commit>

Documenting

Документирование ER разделяется на документирование логики с помощью системы сборки документации sphinx-doc и автоматической генерации документации на класс по коду с помощью doxygen.

Исходники документации sphinx написаны в формате reStructedText и расположены в репозитории в папке docs. См. также reST and Sphinx CheatSheet.

По адресу er.jinr.ru расположена документация, собранная sphinx из ветки dev. Для обновления информации в ней необходимо отредактировать документацию в репозитории и запустить задачу обновления документации (update doc) в jenkins (er.jinr.ru:8080).

Redmine

Jenkins - builds and tests

QA monitors

Development stadarts

Лицензия

ExpertRoot распространяется под лицензией LGPL <https://www.gnu.org/licenses/lgpl-3.0.ru.html>. В шапке всех исходных файлов должно быть добавлено:

/********************************************************************************
 *              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"                       *
 ********************************************************************************/

Code convention / C++ для физиков

Язык программирования C++ предоставляет богатые возможности для разработки. Один и тот же функционал можно написать множеством различных способов. Тот факт, что код компилируется, ещё не значит, что он написан правильно. Даже тот факт, что он работает, ещё не значит, что он написан правильно. Более того, тот факт, что он работает правильно, ещё не значит, что он написан правильно. При написании больших программных пакетов обычно придерживаются некоторого соглашения. Перечисленные ниже инструкции ставят своей целью ограничить разработчика от использования разнородных возможностей для унификации кода, повышения его читаемости, переностимости, упрощения поддержки и т.д.

  • Идентификаторы классов должны начинаться с префикса ER (ERNeuRad, ERGadast, ...)
  • Должно выдерживаться соотношение: один класс - два файла (.h и .cxx)
  • Файлы включений должны быть расположены в следующей последовательности
    1. Заголовочный файл класса, если это .cxx
    2. Заголовочные файлы библиотеки STL
    3. Заголовочные файлы библиотеки Boost
    4. Заголовочные файлы фреймворка Root
    5. Заголовочные файлы фреймворка Geant
    6. Заголовочные файлы фреймворка FairRoot
    7. Заголовочные файлы пакета ExpertRoot
  • Между файлами включения разных библиотек должен быть отступ - пустая строка.
#include "ERNeuRad.h"

#include <iostream>

#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, ...) подключаются к разрабатываемому файлу косвенно через заголовочные файлы более высокого уровня (дочерние классы). Поэтому нередко бывает, что явно используется некоторый базовый класс, но никакого подключения именно для этого класса нет.
  • Объявления пространств имен должны быть расположены после включений заголовочных файлов, в той же последовательности, с теми же отступами и только в исходном файле. Объявления пространств имен в заголовочном файле не допускается.
  • Код заголовочного файла должен быть обернут в директивы (идентификатор директивы формируется из названия класса и _H):
#ifndef ERNeuRad_H
#define ERNeuRad_H

...

#endif // ERNeuRad_H
  • В конце любого файла с кодом должна быть пустая строка
  • Для объявления некоторого внешнего класса 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.
  • Имена всех переменных начинаются с буквы нижнего регистра.
  • Табулирование кода (отступы слева) выполняется двумя пробелами. Использование знака табуляции для разметки недопустимо. Для удобства можно настроить текстовый редактор так, чтобы он выполнял замену табуляции двумя пробелами.
  • После запятой должен стоять пробел (пример ниже).
  • Оператор присваивания = должен отделяться пробелами с обеих сторон (пример ниже).
  • Операторы сдвига/стриминга << должны отделяться пробелами с обеих сторон (пример ниже).
  • Открывающая фигурная скобка должна стоять в той же строке, что и оператор или сигнатура (и т.д.), и отделена от предыдущего символа пробелом. Исключением является скобка после списка инициализации. Закрывающая фигурная скобка - в отдельной строке.
void ERNeuRad::CopyClones(TClonesArray* cl1, TClonesArray* cl2, Int_t offset) {
  Int_t nEntries = cl1->GetEntriesFast();
  LOG(DEBUG) << "NeuRad: " << nEntries << " entries to add" << FairLogger::endl;
  ...
}
  • Ширина строки не должна превышать 100 символов. Это также можно настроить в текстовом редакторе.
  • Логирование необходимо осуществлять с помощью средств FairLogger и указанием уровней логирования: LOG(INFO), LOG(WARN), LOG(ERROR), LOG(DEBUG), LOG(DEBUG2). Аварийное завершение c выводом backtrace в core_dump файл вызывается с помощью LOG(FATAL). В качестве символа окончания строки следует использовать FairLogger::endl.

Note

Основное требование кода - его прозрачность. Не стоит жалеть символов на идентификаторы и строк на комментарии.

  • Базовым стандартом разработки является C++11. Но не весь. Не стоит использовать лямбда-функции, они действительно затрудняют чтение кода.
  • В качестве библиотеки контейнеров использовать STL.
  • Для итераторов использовать auto.
  • Синтаксис range-based циклов использовать следующий, если возможно:
std::map<std::string, std::vector<int>> map;
std::vector<int> 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 сценария для сборки библиотеки классов

Каждая директория проекта 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()