diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 3fed40f2..8f3229e8 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -14,9 +14,13 @@ defaults: run: shell: bash +env: + hipo_version: 94e2cfc1ab93388680ba07088d9b63b68347ba92 # before hipo::bank::get and put were removed + fmt_version: 9.1.0 + jobs: - # build + # dependencies ######################################################### build_hipo: @@ -27,9 +31,10 @@ jobs: uses: actions/checkout@v4 with: repository: gavalian/hipo + ref: ${{ env.hipo_version }} - name: build run: | - cmake -S . -B build -DCMAKE_INSTALL_PREFIX=hipo + cmake -S . -B build -DCMAKE_INSTALL_PREFIX=hipo -DCMAKE_POSITION_INDEPENDENT_CODE=ON cmake --build build -j2 cmake --install build - run: tree hipo @@ -41,14 +46,40 @@ jobs: retention-days: 1 path: hipo.tar.gz + build_fmt: + name: Build fmt + runs-on: ubuntu-latest + steps: + - name: checkout fmt + uses: actions/checkout@v4 + with: + repository: fmtlib/fmt + ref: ${{ env.fmt_version }} + - name: build + run: | + cmake -S . -B build -DCMAKE_INSTALL_PREFIX=fmt -DCMAKE_POSITION_INDEPENDENT_CODE=ON + cmake --build build -j2 + cmake --install build + - run: tree fmt + - name: tar + run: tar czvf fmt{.tar.gz,} + - uses: actions/upload-artifact@v3 + with: + name: build + retention-days: 1 + path: fmt.tar.gz + + # build + ######################################################### + build_iguana: name: Build Iguana - needs: [ build_hipo ] + needs: + - build_hipo + - build_fmt runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - name: setup fmt - run: sudo apt install -y libfmt-dev - name: setup meson run: python -m pip install meson ninja - name: summarize dependencies @@ -59,15 +90,17 @@ jobs: for dep in python ruby meson ninja ; do echo "| \`$dep\` | $($dep --version) |" >> $GITHUB_STEP_SUMMARY done - echo "| \`fmt\` | $(apt show libfmt-dev | grep -w Version) |" >> $GITHUB_STEP_SUMMARY + echo "| \`fmt\` | ${{ env.fmt_version }} |" >> $GITHUB_STEP_SUMMARY + echo "| \`hipo\` | ${{ env.hipo_version }} |" >> $GITHUB_STEP_SUMMARY - name: get build artifacts uses: actions/download-artifact@v3 with: name: build - name: untar build run: ls *.tar.gz | xargs -I{} tar xzvf {} + - run: tree - name: build iguana - run: ./install.rb --hipo hipo + run: ./install.rb --hipo hipo --fmt fmt - name: dump build log if: always() run: cat build-iguana/meson-logs/meson-log.txt @@ -108,11 +141,11 @@ jobs: test_iguana: name: Test Iguana - needs: [ download_validation_files, build_iguana ] + needs: + - download_validation_files + - build_iguana runs-on: ubuntu-latest steps: - - name: install dependencies - run: sudo apt install -y libfmt-dev - name: get build artifacts uses: actions/download-artifact@v3 with: diff --git a/.gitignore b/.gitignore index c8069431..761de869 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,7 @@ # build artifacts +*.ini /build*/ /iguana -/bin -/include -/lib # dependencies hipo diff --git a/README.md b/README.md index 78349895..ee4c30ca 100644 --- a/README.md +++ b/README.md @@ -25,3 +25,17 @@ install.rb --help # print the usage guide install.rb # default: install to ./iguana ``` Alternatively, use `meson` for more control. + +## Troubleshooting Notes + +- if you redirect `stdout` and `stderr` to a file, you may notice that `stderr` lines are out-of-order with respect to the `stdout` lines; for example: +```bash +myAnalysisProgram # stdout and stderr print when they happen; ordering appears correct + +myAnalysisProgram |& tee output.txt # stderr prints when it happens, but stdout only prints when its buffer is full; + # ordering appears mixed up +``` +To redirect output to a file with the ordering preserved, run your executable with `stdout` unbuffered: +```bash +stdbuf -o0 myAnalysisProgram |& tee output.txt +``` diff --git a/install.rb b/install.rb index 70fd2d17..0165522b 100755 --- a/install.rb +++ b/install.rb @@ -10,6 +10,7 @@ build: "#{Dir.pwd}/build-iguana", install: "#{Dir.pwd}/iguana", hipo: "#{Dir.pwd}/hipo", + fmt: nil, clean: false, purge: false, } @@ -22,20 +23,32 @@ parser.separator '' parser.on("-i", "--install [INSTALL DIR]", "Directory for installation", "Default: #{options[:install]}") parser.separator '' -parser.on("-h", "--hipo [HIPO DIR]", "Path to HIPO installation", "Default: #{options[:hipo]}", "if not found, tries env var $HIPO") +parser.on("--hipo [HIPO DIR]", "Path to HIPO installation", "Default: #{options[:hipo]}", "if not found, tries env var $HIPO") +parser.on("--fmt [FMT DIR]", "Path to fmt installation", "Default: assumes system installation" ) parser.separator 'TROUBLESHOOTING:' parser.on("--clean", "Remove the buildsystem at [BUILD DIR] beforehand") parser.on("--purge", "Remove the installation at [INSTALL DIR] beforehand") parser.parse!(into: options) +# check for HIPO installation, or fallback to $HIPO +options[:hipo] = ENV['HIPO'] unless Dir.exists? options[:hipo] + +# use realpaths for dependencies +[ :hipo, :fmt ].each do |dep| + unless options[dep].nil? + if Dir.exists? options[dep] + options[dep] = File.realpath options[dep] + else + $stderr.puts "ERROR: directory '#{options[dep]}' for option '--#{dep.to_s}' does not exist" + exit 1 + end + end +end + # print the options puts "SET OPTIONS:" options.each do |k,v| puts "#{k.to_s.rjust 15} => #{v}" end -# check for HIPO installation, or fallback to $HIPO -options[:hipo] = ENV['HIPO'] unless Dir.exists? options[:hipo] -options[:hipo] = File.realpath options[:hipo] if Dir.exists? options[:hipo] - # clean and purge def rmDir(dir,obj='files') print "\nRemove #{obj} at #{dir} ?\n[y/N] > " @@ -56,30 +69,42 @@ def rmDir(dir,obj='files') FileUtils.mkdir_p options[:install] prefix = File.realpath options[:install] +# set dependency package paths +cmake_prefix_path = [ options[:hipo] ].compact +pkg_config_path = [ options[:fmt] ].compact.map{ |path| path += '/lib/pkgconfig' } + +# generate native INI file +def singleQuotes(arr) + "#{arr}".gsub /"/, "'" +end +native_ini = options[:build] + '.ini' +native_file = File.open native_ini, 'w' +native_file.puts """[built-in options] +; dependency paths +cmake_prefix_path = #{singleQuotes cmake_prefix_path} +pkg_config_path = #{singleQuotes pkg_config_path} +; installation +prefix = '#{prefix}' +""" +native_file.close + # print and run a command def runCommand(cmd) puts "[+++] #{cmd}" system cmd or raise "FAILED: #{cmd}" end -# set a build option -def buildOpt(key,val) - val.nil? ? '' : "-D#{key}='#{val}'" -end - # meson commands meson = { :setup => [ 'meson setup', - "--prefix #{prefix}", - buildOpt('hipo', options[:hipo]), + "--native-file #{native_ini}", options[:build], SourceDir, ], :config => [ 'meson configure', - "--prefix #{prefix}", - buildOpt('hipo', options[:hipo]), + "--native-file #{native_ini}", options[:build], ], :install => [ diff --git a/meson.build b/meson.build index 6639e266..19d3ee2b 100644 --- a/meson.build +++ b/meson.build @@ -8,17 +8,16 @@ project( project_inc = include_directories('src') -project_lib_install_dir = 'lib' # to keep it the same for different Linux distributions -project_bin_install_dir = 'bin' +dep_lib_rpaths = [ ] +foreach path : get_option('cmake_prefix_path') + dep_lib_rpaths += path + '/lib' # TODO: this adds hipo4 libs to the rpath; consider instead linking against hipo4 static lib +endforeach project_lib_rpath = '$ORIGIN' -project_bin_rpath = ':'.join([ - '$ORIGIN/../' + project_lib_install_dir, - get_option('hipo') + '/lib' -]) +project_bin_rpath = ':'.join( dep_lib_rpaths + [ '$ORIGIN/../' + get_option('libdir') ] ) +hipo_dep = dependency('hipo4') fmt_dep = dependency('fmt') -hipo_dep = dependency('hipo4', method: 'cmake', cmake_args: '-DCMAKE_PREFIX_PATH=' + get_option('hipo')) subdir('src/services') subdir('src/algorithms') diff --git a/meson.options b/meson.options deleted file mode 100644 index 845f473d..00000000 --- a/meson.options +++ /dev/null @@ -1 +0,0 @@ -option('hipo', type: 'string', value: '/usr/local', description: 'HIPO installation location') diff --git a/src/algorithms/clas12/event_builder_filter/EventBuilderFilter.cc b/src/algorithms/clas12/event_builder_filter/EventBuilderFilter.cc index 50b1446b..64d44202 100644 --- a/src/algorithms/clas12/event_builder_filter/EventBuilderFilter.cc +++ b/src/algorithms/clas12/event_builder_filter/EventBuilderFilter.cc @@ -3,32 +3,31 @@ namespace iguana::clas12 { EventBuilderFilter::EventBuilderFilter() : Algorithm("event_builder_filter") { - m_requiredBanks = { - "REC::Particle", - "REC::Calorimeter" - }; + + // define required banks + m_requiredBanks = { "REC::Particle", "REC::Calorimeter" }; // TODO: remove calorimeter + } void EventBuilderFilter::Start(bank_index_cache_t &index_cache) { - // set configuration - m_log->SetLevel(Logger::Level::trace); - m_log->Debug("START {}", m_name); - m_opt.pids = {11, 211, -211}; + // define options, their default values, and cache them + CacheOption("pids", std::set{11, 211}, o_pids); + CacheOption("testInt", 8, o_testInt); // TODO: remove + CacheOption("testFloat", 7.0, o_testFloat); // TODO: remove // cache expected bank indices CacheBankIndex(index_cache, b_particle, "REC::Particle"); - CacheBankIndex(index_cache, b_calo, "REC::Calorimeter"); + CacheBankIndex(index_cache, b_calo, "REC::Calorimeter"); // TODO: remove } void EventBuilderFilter::Run(bank_vec_t banks) { - m_log->Debug("RUN {}", m_name); // get the banks auto particleBank = GetBank(banks, b_particle, "REC::Particle"); - auto caloBank = GetBank(banks, b_calo, "REC::Calorimeter"); + auto caloBank = GetBank(banks, b_calo, "REC::Calorimeter"); // TODO: remove // dump the bank ShowBank(particleBank, Logger::Header("INPUT PARTICLES")); @@ -48,12 +47,14 @@ namespace iguana::clas12 { bool EventBuilderFilter::Filter(int pid) { - return m_opt.pids.find(pid) != m_opt.pids.end(); + return o_pids.find(pid) != o_pids.end(); } void EventBuilderFilter::Stop() { - m_log->Debug("STOP {}", m_name); + m_log->Info("test info"); + m_log->Warn("test warn"); + m_log->Error("test error"); } } diff --git a/src/algorithms/clas12/event_builder_filter/EventBuilderFilter.h b/src/algorithms/clas12/event_builder_filter/EventBuilderFilter.h index 5a5f2046..08d02051 100644 --- a/src/algorithms/clas12/event_builder_filter/EventBuilderFilter.h +++ b/src/algorithms/clas12/event_builder_filter/EventBuilderFilter.h @@ -5,12 +5,6 @@ namespace iguana::clas12 { - class EventBuilderFilterOptions { - public: - std::set pids = {11, 211}; - }; - - class EventBuilderFilter : public Algorithm { public: @@ -25,8 +19,14 @@ namespace iguana::clas12 { bool Filter(int pid); private: - EventBuilderFilterOptions m_opt; - int b_particle, b_calo; + + /// `bank_vec_t` indices + int b_particle, b_calo; // TODO: remove calorimeter + + /// configuration options + std::set o_pids; + int o_testInt; // TODO: remove + double o_testFloat; // TODO: remove }; diff --git a/src/algorithms/meson.build b/src/algorithms/meson.build index fa9f2f32..b8d90cf2 100644 --- a/src/algorithms/meson.build +++ b/src/algorithms/meson.build @@ -13,7 +13,6 @@ algo_lib = shared_library( dependencies: services_dep, link_with: services_lib, install: true, - install_dir: project_lib_install_dir, install_rpath: project_lib_rpath, ) diff --git a/src/iguana/meson.build b/src/iguana/meson.build index a19dffef..03e8f40f 100644 --- a/src/iguana/meson.build +++ b/src/iguana/meson.build @@ -13,7 +13,6 @@ iguana_lib = shared_library( dependencies: [ fmt_dep, hipo_dep ], link_with: [ algo_lib, services_lib ], install: true, - install_dir: project_lib_install_dir, install_rpath: project_lib_rpath, ) diff --git a/src/services/Algorithm.cc b/src/services/Algorithm.cc index 21717a1e..a658f4a3 100644 --- a/src/services/Algorithm.cc +++ b/src/services/Algorithm.cc @@ -14,6 +14,15 @@ namespace iguana { Start(index_cache); } + void Algorithm::SetOption(std::string key, option_value_t val) { + m_opt[key] = val; + m_log->Debug("User set option '{}' = {}", key, PrintOptionValue(key)); + } + + std::shared_ptr Algorithm::Log() { + return m_log; + } + void Algorithm::CacheBankIndex(bank_index_cache_t index_cache, int &idx, std::string bankName) { try { idx = index_cache.at(bankName); @@ -23,6 +32,24 @@ namespace iguana { m_log->Debug("cached index of bank '{}' is {}", bankName, idx); } + std::string Algorithm::PrintOptionValue(std::string key) { + if(auto it{m_opt.find(key)}; it != m_opt.end()) { + auto val = it->second; + std::string format_str = "{} [{}]"; + if (const auto valPtr(std::get_if(&val)); valPtr) return fmt::format("{} [{}]", *valPtr, "int"); + else if (const auto valPtr(std::get_if(&val)); valPtr) return fmt::format("{} [{}]", *valPtr, "double"); + else if (const auto valPtr(std::get_if(&val)); valPtr) return fmt::format("{} [{}]", *valPtr, "string"); + else if (const auto valPtr(std::get_if>(&val)); valPtr) return fmt::format("({}) [{}]", fmt::join(*valPtr,", "), "set"); + else { + m_log->Error("option '{}' type has no printer defined in Algorithm::PrintOptionValue", key); + return "UNKNOWN"; + } + } + else + m_log->Error("option '{}' not found by Algorithm::PrintOptionValue", key); + return "UNKNOWN"; + } + bank_ptr Algorithm::GetBank(bank_vec_t banks, int idx, std::string expectedBankName) { bank_ptr result; try { diff --git a/src/services/Algorithm.h b/src/services/Algorithm.h index 59ec8258..d54dc1f7 100644 --- a/src/services/Algorithm.h +++ b/src/services/Algorithm.h @@ -31,6 +31,15 @@ namespace iguana { /// Finalize an algorithm after all events are processed virtual void Stop() = 0; + /// Set an option specified by the user + /// @param key the name of the option + /// @param val the value to set + void SetOption(std::string key, option_value_t val); + + /// Get the logger + /// @return the logger used by this algorithm + std::shared_ptr Log(); + protected: /// Cache the index of a bank in a `bank_vec_t`; throws an exception if the bank is not found @@ -39,10 +48,42 @@ namespace iguana { /// @param bankName the name of the bank void CacheBankIndex(bank_index_cache_t index_cache, int &idx, std::string bankName); + /// Cache an option specified by the user, and define its default value + /// @param key the name of the option + /// @param def the default value + /// @param val reference to the value of the option, to be cached by `Start` + template + void CacheOption(std::string key, OPTION_TYPE def, OPTION_TYPE &val) { + bool get_error = false; + if(auto it{m_opt.find(key)}; it != m_opt.end()) { // cache the user's option value + try { // get the expected type + val = std::get(it->second); + } catch(const std::bad_variant_access &ex1) { + m_log->Error("user option '{}' set to '{}', which is the wrong type...", key, PrintOptionValue(key)); + get_error = true; + val = def; + } + } + else { // cache the default option value + val = def; + } + // sync `m_opt` to match the cached value `val` (e.g., so we can use `PrintOptionValue` to print it) + m_opt[key] = val; + if(get_error) + m_log->Error("...using default value '{}' instead", PrintOptionValue(key)); + m_log->Debug("OPTION: {:>20} = {}", key, PrintOptionValue(key)); + } + + /// Return a string with the value of an option along with its type + /// @param key the name of the option + /// @return the string value and its type + std::string PrintOptionValue(std::string key); + /// Get the pointer to a bank from a `bank_vec_t`; optionally checks if the bank name matches the expectation /// @param banks the `bank_vec_t` from which to get the specified bank /// @param idx the index of `banks` of the specified bank /// @param expectedBankName if specified, checks that the specified bank has this name + /// @return the modified `bank_vec_t` bank_ptr GetBank(bank_vec_t banks, int idx, std::string expectedBankName=""); /// Copy a row from one bank to another, assuming their schemata are equivalent @@ -81,5 +122,8 @@ namespace iguana { /// Logger std::shared_ptr m_log; + + /// Configuration options + options_t m_opt; }; } diff --git a/src/services/Logger.cc b/src/services/Logger.cc index 2eec98a9..8844d5aa 100644 --- a/src/services/Logger.cc +++ b/src/services/Logger.cc @@ -2,7 +2,7 @@ namespace iguana { - Logger::Logger(std::string name, Level lev) : m_name(name) { + Logger::Logger(std::string name, Level lev, bool enable_style) : m_name(name), m_enable_style(enable_style) { m_level_names = { { trace, "trace" }, { debug, "debug" }, @@ -13,9 +13,19 @@ namespace iguana { SetLevel(lev); } + void Logger::SetLevel(std::string lev) { + for(auto &[lev_i, lev_n] : m_level_names) { + if(lev == lev_n) { + SetLevel(lev_i); + return; + } + } + Error("Log level '{}' is not a known log level; the log level will remain at '{}'", lev, m_level_names.at(m_level)); + } + void Logger::SetLevel(Level lev) { m_level = lev; - Debug("Logger '{}' set to '{}'", m_name, m_level_names.at(m_level)); + Debug("log level set to '{}'", m_level_names.at(m_level)); } Logger::Level Logger::GetLevel() { @@ -23,7 +33,7 @@ namespace iguana { } std::string Logger::Header(std::string message, int width) { - return fmt::format("{:=^{}}", message, width); + return fmt::format("{:=^{}}", " " + message + " ", width); } } diff --git a/src/services/Logger.h b/src/services/Logger.h index 21ed4f0e..ffce8a16 100644 --- a/src/services/Logger.h +++ b/src/services/Logger.h @@ -1,7 +1,9 @@ #pragma once #include +#include #include +#include namespace iguana { @@ -17,12 +19,17 @@ namespace iguana { warn, error }; - static const Level defaultLevel = info; + static const Level DEFAULT_LEVEL = info; - Logger(std::string name = "log", Level lev = defaultLevel); + Logger(std::string name = "log", Level lev = DEFAULT_LEVEL, bool enable_style = true); ~Logger() {} + void SetLevel(std::string lev); void SetLevel(Level lev); + + void EnableStyle() { m_enable_style = true; } + void DisableStyle() { m_enable_style = false; } + Level GetLevel(); static std::string Header(std::string message, int width=50); @@ -36,7 +43,16 @@ namespace iguana { void Print(Level lev, std::string message, VALUES... vals) { if(lev >= m_level) { if(auto it{m_level_names.find(lev)}; it != m_level_names.end()) { - auto prefix = fmt::format("[{}] [{}] ", it->second, m_name); + std::string prefix; + std::function style = [] (std::string s) { return fmt::format("[{}]", s); }; + if(m_enable_style) { + switch(lev) { + case warn: style = [] (std::string s) { return fmt::format("[{}]", fmt::styled(s, fmt::emphasis::bold | fmt::fg(fmt::terminal_color::magenta))); }; break; + case error: style = [] (std::string s) { return fmt::format("[{}]", fmt::styled(s, fmt::emphasis::bold | fmt::fg(fmt::terminal_color::red))); }; break; + default: style = [] (std::string s) { return fmt::format("[{}]", fmt::styled(s, fmt::emphasis::bold)); }; + } + } + prefix = fmt::format("{} {} ", style(it->second), style(m_name)); fmt::print( lev >= warn ? stderr : stdout, fmt::runtime(prefix + message + "\n"), @@ -53,6 +69,7 @@ namespace iguana { std::string m_name; Level m_level; std::unordered_map m_level_names; + bool m_enable_style; }; } diff --git a/src/services/TypeDefs.h b/src/services/TypeDefs.h index 906f0d74..d24d2f5d 100644 --- a/src/services/TypeDefs.h +++ b/src/services/TypeDefs.h @@ -1,6 +1,8 @@ #include #include +#include #include +#include #include @@ -15,4 +17,15 @@ namespace iguana { /// association between HIPO bank name and its index in a `bank_vec_t` using bank_index_cache_t = std::unordered_map; + /// option value variant type + using option_value_t = std::variant< + int, + double, + std::string, + std::set + >; + + /// data structure to hold configuration options + using options_t = std::unordered_map; + } diff --git a/src/services/meson.build b/src/services/meson.build index 854008f4..64ba9e4b 100644 --- a/src/services/meson.build +++ b/src/services/meson.build @@ -15,7 +15,6 @@ services_lib = shared_library( include_directories: project_inc, dependencies: [ fmt_dep, hipo_dep ], install: true, - install_dir: project_lib_install_dir, install_rpath: project_lib_rpath, ) diff --git a/src/tests/meson.build b/src/tests/meson.build index c7700773..e21cdccd 100644 --- a/src/tests/meson.build +++ b/src/tests/meson.build @@ -4,9 +4,8 @@ foreach test_exe : [ 'run_banks', 'run_rows' ] test_exe + '.cc', include_directories: project_inc, dependencies: [ fmt_dep, hipo_dep ], - link_with: [ iguana_lib, algo_lib ], + link_with: [ iguana_lib, algo_lib, services_lib ], install: true, - install_dir: project_bin_install_dir, install_rpath: project_bin_rpath, ) endforeach diff --git a/src/tests/run_banks.cc b/src/tests/run_banks.cc index 5673c02d..57de4acc 100644 --- a/src/tests/run_banks.cc +++ b/src/tests/run_banks.cc @@ -21,6 +21,11 @@ int main(int argc, char **argv) { */ iguana::Iguana I; auto algo = I.algo_map.at(iguana::Iguana::clas12_EventBuilderFilter); + algo->Log()->SetLevel("trace"); + // algo->Log()->DisableStyle(); + algo->SetOption("pids", std::set{11, 211, -211}); + algo->SetOption("testInt", 3); + algo->SetOption("testFloat", 11.0); algo->Start(); ///////////////////////////////////////////////////// diff --git a/src/tests/run_rows.cc b/src/tests/run_rows.cc index 555625f9..df6c516b 100644 --- a/src/tests/run_rows.cc +++ b/src/tests/run_rows.cc @@ -10,6 +10,7 @@ int main(int argc, char **argv) { // start the algorithm auto algo = std::make_shared(); + algo->SetOption("pids", std::set{11, 211, -211}); algo->Start(); /////////////////////////////////////////////////////