FairRoot bazuje na ROOT 6 który prekompiluje kod. Zalecaną metodą uruchamiania większych analiz jest jednak tworzenie tasku w formie skompilowanej klasy, czasem może się zdarzyć, że będziemy tworzyć kilkanaście klas, w tym wypadku warto taka klasę zwinąć do biblioteki. Jeśli nasza biblioteka będzie prawidłowo skompilowaną częścią frameworku dla danego eksperymentu będzie automatycznie ładowana.
Bibliotekę tworzymy robiąc nowy katalog np. mytask w katalogu frameworku. Jako przykład weźmiemy mpdroot. Tak więc tworzymy w katalogu mpdroota katalog mytask, a w nim 4 puste pliki:
- CMakeLists.txt - skrypt kompilujący naszą bibliotekę
- MyTaskCompiled.cxx - kod źródłowy naszego kodu do analizy
- MyTaskCompiled.h - plik nagłówkowy kodu do analizy
- MyTaskLinkDef.h - plik "linkdef".
Zacznijmy od CMakeLists.txt w środku wygląda następująco:
#sekcja headerow set(INCLUDE_DIRECTORIES ${BASE_INCLUDE_DIRECTORIES} ${CMAKE_SOURCE_DIR}/mpdbase ${CMAKE_SOURCE_DIR}/mcstack ${CMAKE_SOURCE_DIR}/mpddst ${CMAKE_SOURCE_DIR}/mpddst/mcDst ${CMAKE_SOURCE_DIR}/mpddst/MpdMiniEvent ${CMAKE_SOURCE_DIR}/mytask ) ##### Set(SYSTEM_INCLUDE_DIRECTORIES ${ROOT_INCLUDE_DIR} ) include_directories(${INCLUDE_DIRECTORIES}) Include_Directories(SYSTEM ${SYSTEM_INCLUDE_DIRECTORIES}) set(LINK_DIRECTORIES ${ROOT_LIBRARY_DIR} ${FAIRROOT_LIBRARY_DIR} ) link_directories(${LINK_DIRECTORIES}) # sekcja zrodel set(SRCS MyTaskCompiled.cxx ) ###### Set(HEADERS) # sekcja dodatkowych ustawien Set(LINKDEF MyTaskLinkDef.h) Set(LIBRARY_NAME MyTask) Set(DEPENDENCIES Core Base MpdDst MpdBase MpdMCStack) ###### GENERATE_LIBRARY()
Widzimy tu wydzielone kilka sekcji, które należy dostosować do naszego kodu (resztę się zwykle po prostu kopiuje tak żeby wyglądały podobnie jak w innych katalogach i odpowiednio modyfikuje). W sekcji headerów podajemy ścieżki do katalogów z plikami nagłówkowymi wymaganych przez nasze klasy. BASE_INCLUDE_DIRECTORIES ustawiają nam ścieżki do headerów ROOTa oraz FairRoota, dlatego tak naprawdę musimy podać tylko ścieżki do headerów z mpdroota. W sekcji źrodeł podaję względne ścieżki do kolejnych plików z kodem źródłowym (bez headerów!, CMake jest ustawiony tak, że sam zgaduje nazwy headerów zmieniając rozszerzenie, tak więc CMake dla klasy MyTaskCompiled.cxx będzie szukał pliku MyTaskCompiled.h). W sekcji dodatkowych ustawień ustawiamy kolejno: nazwę/ścieżkę linkdefa, nazwę biblioteki, zależności (tj. biblioteki które linkujemy do naszej biblioteki).
Plik z kodem źródłowym to zwykłe kody C++ z klasami kompilowanymi przez ROOTa przedstawiono poniżej, są one trochę bardziej rozbudowanym przykładem z poprzedniej strony - dotyczącej prostej klasy do analizy.
#include "MyTaskCompiled.h" #include "MpdMiniMcTrack.h" #include "MpdMiniTrack.h" #include void MyTaskCompiled::Exec(Option_t* opt) { for (int i = 0; i < fMcTracks->GetEntriesFast(); i++) { MpdMiniMcTrack* mc = (MpdMiniMcTrack*) fMcTracks->UncheckedAt(i); TVector3 p = mc->momentum(); if (mc->pdgId() == 2212) fYPtSim->Fill(p.Eta(), p.Pt()); } for (int i = 0; i < fRecoTracks->GetEntriesFast(); i++) { MpdMiniTrack* track = (MpdMiniTrack*) fRecoTracks->UncheckedAt(i); Int_t match = track->mcTrackIndex(); if (match >= 0 && match < fMcTracks->GetEntriesFast()) { MpdMiniMcTrack* mc = (MpdMiniMcTrack*) fMcTracks->UncheckedAt(match); TVector3 p = mc->momentum(); if (mc->pdgId() == 2212) fYPtReco->Fill(p.Eta(), p.Pt()); } } } InitStatus MyTaskCompiled::Init() { FairRootManager* mng = FairRootManager::Instance(); fRecoEvent = (TClonesArray*) mng->GetObject("Event"); fRecoTracks = (TClonesArray*) mng->GetObject("Track"); fMcEvent = (TClonesArray*) mng->GetObject("McEvent"); fMcTracks = (TClonesArray*) mng->GetObject("McTrack"); fTofData = (TClonesArray*) mng->GetObject("BTofPidTraits"); fYPtSim = new TH2D("specSimCompiled", "spec;#eta;p_{T} [GeV/c]", 20, -2, 2, 20, 0, 2); fYPtReco = new TH2D("specRecoCompiled", "spec;#eta;p_{T} [GeV/c]", 20, -2, 2, 20, 0, 2); return kSUCCESS; }
#ifndef MPDROOT_MYTASK_MYTASKCOMPILED_H_ #define MPDROOT_MYTASK_MYTASKCOMPILED_H_ #include "MpdEvent.h" #include "MpdMCTrack.h" #include "MpdTrack.h" #include <FairTask.h> #include <FairRootManager.h> #include <TClonesArray.h> #include <TH2D.h> class MyTaskCompiled : public FairTask { TClonesArray* fRecoTracks; TClonesArray* fRecoEvent; TClonesArray* fMcTracks; TClonesArray* fMcEvent; TClonesArray* fTofData; TH2D* fYPtSim; TH2D* fYPtReco; protected: virtual InitStatus Init(); virtual void Finish() { fYPtSim->Write(); fYPtReco->Write(); }; public: MyTaskCompiled() : fRecoTracks(nullptr), fRecoEvent(nullptr), fMcTracks(nullptr), fMcEvent(nullptr), fTofData(nullptr), fYPtSim(nullptr), fYPtReco(nullptr) {}; void Exec(Option_t* opt = ""); virtual ~MyTaskCompiled() {}; ClassDef(MyTaskCompiled, 1) }; #endif /* MPDROOT_MYTASK_MYTASKCOMPILED_H_ */
Na końcu tworzymy plik linkdef, jest on wymagany przez ROOTa. Zasadniczo w pliku tym dla każdej klasy dodaje się linię #pragma link C++ class nazwa_klasy +.
#ifdef __CINT__ #pragma link off all globals; #pragma link off all classes; #pragma link off all functions; #pragma link C++ class MyTaskCompiled + ; #endif
Na końcu musimy dodać nasz katalog do kompilacji. Ponieważ umieściliśmy nasz katalog w głównym katalogu to w tym katalogu musimy znaleźć CMakeLists.txt do którego dodamy linię add_subdirectory(mytask) w naszym przypadku mpdroot/CMakeLists.txt.
Najczęstsze błędy jakie się mogą pojawić to:
- błąd składniowy w CMakeLists.txt (błąd generowany przez cmake)
- pominięcie jakieś klasy w SRC (klasa niewidoczna w ROOT mimo poprawnej kompilacji)
- pominięcie jakiegoś katalogu z potrzebnymi headerami (błąd kompilacji)
- pominięcie klasy w LinkDef w takim przypadku klasa jest niewidoczna, a przy ręcznym ładowaniu biblioteki w ROOT np. poprzez gSystem->Load("libMyTask.so") wyrzuca błąd typu "undefined reference to "
Należy dodać, że opisane tu metody/terminologie mogą się w pewnym momencie zmienić. Co więcej obecnie FairROOT posiada makra w cmake, które umożliwiają znacznie więcej. Poza kompilacją biblioteki z klasami możliwe jest np. tworzenie wykonywalnego programu, czy też kompilacja kodu w innym języku np. Fortran.