Elements 6.3.3
A C++ base framework for the Euclid Software.
Loading...
Searching...
No Matches
ProgramManager.cpp
Go to the documentation of this file.
1
21
23
24#include <algorithm> // for transform
25#include <cstdint> // for int64_t
26#include <cstdlib> // for exit, _Exit, abort
27#include <exception> // for exception, current_exception, rethrow_exception, exception_ptr
28#include <fstream> // IWYU pragma: keep
29#include <iostream> // for basic_ostream, operator<<, endl, basic_ostream::operator<<, cerr, cout, ifstream
30#include <map> // for operator!=, map, _Rb_tree_const_iterator
31#include <sstream> // for stringstream
32#include <string> // for basic_string, char_traits, operator<<, operator+, string, operator==
33#include <utility> // for pair, move
34#include <vector> // for vector
35
36#include <boost/algorithm/string.hpp> // for starts_with
37#include <boost/any.hpp> // for any
38#include <boost/filesystem/operations.hpp> // for canonical, complete, exists
39#include <boost/filesystem/path.hpp> // for path, operator<<, operator>>, operator!=, operator/
40#include <boost/program_options.hpp> // for variable_value, store, value, options_description_easy_init, typed_value, basic_command_line_parser, collect_unrecognized, notify, operator<<, options_description, parse_config_file, command_line_parser, variables_map, basic_parsed_options, collect_unrecognized_mode, include_positional
41#include <boost/smart_ptr.hpp> // for shared_ptr
42
43#include "ElementsKernel/Configuration.h" // for getConfigurationPath, getConfigurationLocations
44#include "ElementsKernel/Exception.h" // for Exception
45#include "ElementsKernel/Exit.h" // for ExitCode
46#include "ElementsKernel/Logging.h" // for Logging
47#include "ElementsKernel/ModuleInfo.h" // for getExecutablePath
48#include "ElementsKernel/Path.h" // for Item, joinPath, multiPathAppend, SUFFIXES, VARIABLE, PATH_SEP
49#include "ElementsKernel/Program.h" // for Program
50#include "ElementsKernel/System.h" // for backTrace
51#include "ElementsKernel/Unused.h" // for ELEMENTS_UNUSED
52
53#include "OptionException.h" // for OptionException
54
55using log4cpp::Priority;
56using std::cerr;
57using std::endl;
58using std::string;
59using std::vector;
60
61namespace Elements {
62
63namespace {
64auto log = Logging::getLogger("ElementsProgram");
65}
66
69
70ProgramManager::ProgramManager(std::unique_ptr<Program> program_ptr, const string& parent_project_version,
71 const string& parent_project_name, const string& parent_project_vcs_version,
72 const string& parent_module_version, const string& parent_module_name,
73 const vector<string>& search_dirs, const Priority::Value& elements_loglevel,
74 bool no_config_file)
75 : m_program_ptr(std::move(program_ptr))
76 , m_parent_project_version(std::move(parent_project_version))
77 , m_parent_project_name(std::move(parent_project_name))
78 , m_parent_project_vcs_version(std::move(parent_project_vcs_version))
79 , m_parent_module_version(std::move(parent_module_version))
80 , m_parent_module_name(std::move(parent_module_name))
81 , m_search_dirs(std::move(search_dirs))
82 , m_env{}
83 , m_elements_loglevel(std::move(elements_loglevel))
84 , m_no_config_file(std::move(no_config_file)) {}
85
86const Path::Item& ProgramManager::getProgramPath() const {
87 return m_program_path;
88}
89
90const Path::Item& ProgramManager::getProgramName() const {
91 return m_program_name;
92}
93
99const Path::Item ProgramManager::getDefaultConfigFile(const Path::Item& program_name, const string& module_name) {
100 Path::Item default_config_file{};
101
102 // .conf is the standard extension for configuration file
103 Path::Item conf_name(program_name);
104 conf_name.replace_extension("conf");
105
106 // Construct and return the full path
107 default_config_file = getConfigurationPath(conf_name.string(), false);
108 if (default_config_file.empty()) {
109 log.warn() << "The " << conf_name << " default configuration file cannot be found in:";
110 for (auto loc : getConfigurationLocations()) {
111 log.warn() << " " << loc;
112 }
113 if (not module_name.empty()) {
114 conf_name = Path::Item{module_name} / conf_name;
115 log.warn() << "Trying " << conf_name << ".";
116 default_config_file = getConfigurationPath(conf_name.string(), false);
117 }
118 }
119
120 if (default_config_file.empty()) {
121 log.debug() << "Couldn't find " << conf_name << " default configuration file.";
122 } else {
123 log.debug() << "Found " << conf_name << " default configuration file at " << default_config_file;
124 }
125
126 return default_config_file;
127}
128
130
131 Path::Item full_path = getExecutablePath();
132
133 return full_path.filename();
134}
135
137
138 Path::Item full_path = getExecutablePath();
139
140 return full_path.parent_path();
141}
142
143/*
144 * Get program options
145 */
146const VariablesMap ProgramManager::getProgramOptions(int argc, char* argv[]) {
147
148 using std::cout;
149 using std::exit;
151 using boost::program_options::collect_unrecognized;
152 using boost::program_options::command_line_parser;
153 using boost::program_options::include_positional;
154 using boost::program_options::notify;
155 using boost::program_options::parse_config_file;
156 using boost::program_options::store;
157 using boost::program_options::value;
158
159 VariablesMap var_map{};
160 Path::Item config_file;
161
162 // default value for default_log_level option
163 string default_log_level = "INFO";
164
165 // Define the options which can be given only at the command line
166 OptionsDescription cmd_only_generic_options{};
167 cmd_only_generic_options.add_options()("version", "Print version string")("help", "Produce help message");
168
169 if (not m_no_config_file) {
170 // Get defaults
171 Path::Item default_config_file = getDefaultConfigFile(getProgramName(), m_parent_module_name);
172 cmd_only_generic_options.add_options()("config-file", value<Path::Item>()->default_value(default_config_file),
173 "Name of a configuration file");
174 }
175
176 // Define the options which can be given both at command line and conf file
177 OptionsDescription cmd_and_file_generic_options{};
178 cmd_and_file_generic_options.add_options()("log-level", value<string>()->default_value(default_log_level),
179 "Log level: FATAL, ERROR, WARN, INFO (default), DEBUG")(
180 "log-file", value<Path::Item>(), "Name of a log file");
181
182 // Group all the generic options, for help output. Note that we add the
183 // options one by one to avoid having empty lines between the groups
184 OptionsDescription all_generic_options{"Generic options"};
185 for (auto o : cmd_only_generic_options.options()) {
186 all_generic_options.add(o);
187 }
188 for (auto o : cmd_and_file_generic_options.options()) {
189 all_generic_options.add(o);
190 }
191
192 // Get the definition of the specific options and arguments (positional
193 // options) from the derived class
194 auto specific_options = m_program_ptr->defineSpecificProgramOptions();
195 auto program_arguments = m_program_ptr->defineProgramArguments();
196 OptionsDescription all_specific_options{};
197 all_specific_options.add(specific_options).add(program_arguments.first);
198
199 // Put together all the options to parse from the cmd line and the file
200 OptionsDescription all_cmd_and_file_options{};
201 all_cmd_and_file_options.add(cmd_and_file_generic_options).add(all_specific_options);
202
203 // Put together all the options to use for the help message
204 OptionsDescription help_options{};
205 help_options.add(all_generic_options).add(all_specific_options);
206
207 // Perform a first parsing of the command line, to handle the cmd only options
208 auto cmd_parsed_options =
209 command_line_parser(argc, argv).options(cmd_only_generic_options).allow_unregistered().run();
210
211 checkCommandLineOptions(cmd_parsed_options);
212
213 store(cmd_parsed_options, var_map);
214
215 // Deal with the "help" option
216 if (var_map.count("help") > 0) {
217 cout << help_options << endl;
218 exit(static_cast<int>(ExitCode::OK));
219 }
220
221 // Deal with the "version" option
222 if (var_map.count("version") > 0) {
223 cout << getVersion() << endl;
224 exit(static_cast<int>(ExitCode::OK));
225 }
226
227 if (not m_no_config_file) {
228 // Get the configuration file. It is guaranteed to exist, because it has
229 // default value
230 config_file = var_map.at("config-file").as<Path::Item>();
231 }
232
233 // Parse from the command line the rest of the options. Here we also handle
234 // the positional arguments.
235 auto leftover_cmd_options = collect_unrecognized(cmd_parsed_options.options, include_positional);
236
237 try {
238
239 auto parsed_cmdline_options = command_line_parser(leftover_cmd_options)
240 .options(all_cmd_and_file_options)
241 .positional(program_arguments.second)
242 .run();
243
244 store(parsed_cmdline_options, var_map);
245
246 if (not m_no_config_file) {
247 // Parse from the configuration file if it exists
248 if (not config_file.empty() and boost::filesystem::exists(config_file)) {
249 std::ifstream ifs{config_file.string()};
250 if (ifs) {
251 auto parsed_cfgfile_options = parse_config_file(ifs, all_cmd_and_file_options);
252 store(parsed_cfgfile_options, var_map);
253 }
254 }
255 }
256
257 } catch (const std::exception& e) {
258 if (boost::starts_with(e.what(), "unrecognised option") or
259 boost::starts_with(e.what(), "too many positional options")) {
260 throw OptionException(e.what());
261 } else {
262 throw;
263 }
264 }
265 // After parsing both the command line and the conf file notify the variables
266 // map, so we can get any messages for missing parameters
267 notify(var_map);
268
269 // return the var_map loaded with all options
270 return var_map;
271}
272
273void ProgramManager::logHeader(string program_name) const {
274 log.log(m_elements_loglevel, "##########################################################");
275 log.log(m_elements_loglevel, "##########################################################");
276 log.log(m_elements_loglevel, "#");
277 log.log(m_elements_loglevel, "# C++ program: " + program_name + " starts ");
278 log.log(m_elements_loglevel, "#");
279 log.debug("# The Program Name: " + m_program_name.string());
280 log.debug("# The Program Path: " + m_program_path.string());
281}
282
283void ProgramManager::logFooter(string program_name) const {
284 log.log(m_elements_loglevel, "##########################################################");
285 log.log(m_elements_loglevel, "#");
286 log.log(m_elements_loglevel, "# C++ program: " + program_name + " stops ");
287 log.log(m_elements_loglevel, "#");
288 log.log(m_elements_loglevel, "##########################################################");
289 log.log(m_elements_loglevel, "##########################################################");
290}
291
292// Log all options with a header
294
295 using std::int64_t;
296 using std::stringstream;
297
298 log.log(m_elements_loglevel, "##########################################################");
299 log.log(m_elements_loglevel, "#");
300 log.log(m_elements_loglevel, "# List of all program options");
301 log.log(m_elements_loglevel, "# ---------------------------");
302 log.log(m_elements_loglevel, "#");
303
304 // Build a log message
305 stringstream log_message{};
306
307 // Loop over all options included in the variable_map
308 for (const auto& v : m_variables_map) {
309 // string option
310 if (v.second.value().type() == typeid(string)) {
311 log_message << v.first << " = " << v.second.as<string>();
312 // double option
313 } else if (v.second.value().type() == typeid(double)) {
314 log_message << v.first << " = " << v.second.as<double>();
315 // int64_t option
316 } else if (v.second.value().type() == typeid(int64_t)) {
317 log_message << v.first << " = " << v.second.as<int64_t>();
318 // int option
319 } else if (v.second.value().type() == typeid(int)) {
320 log_message << v.first << " = " << v.second.as<int>();
321 // bool option
322 } else if (v.second.value().type() == typeid(bool)) {
323 log_message << v.first << " = " << v.second.as<bool>();
324 // path option
325 } else if (v.second.value().type() == typeid(Path::Item)) {
326 log_message << v.first << " = " << v.second.as<Path::Item>();
327 // int vector option
328 } else if (v.second.value().type() == typeid(vector<int>)) {
329 vector<int> intVec = v.second.as<vector<int>>();
330 stringstream vecContent{};
331 for (const auto& i : intVec) {
332 vecContent << " " << i;
333 }
334 log_message << v.first << " = {" << vecContent.str() << " }";
335 // double vector option
336 } else if (v.second.value().type() == typeid(vector<double>)) {
337 vector<double> intVec = v.second.as<vector<double>>();
338 stringstream vecContent{};
339 for (const auto& i : intVec) {
340 vecContent << " " << i;
341 }
342 log_message << v.first << " = {" << vecContent.str() << " }";
343 // string vector option
344 } else if (v.second.value().type() == typeid(vector<string>)) {
345 vector<string> intVec = v.second.as<vector<string>>();
346 stringstream vecContent{};
347 for (const auto& i : intVec) {
348 vecContent << " " << i;
349 }
350 log_message << v.first << " = {" << vecContent.str() << " }";
351 // if nothing else
352 } else {
353 log_message << "Option " << v.first << " of type " << v.second.value().type().name()
354 << " not supported in logging !" << endl;
355 }
356 // write the log message
357 log.log(m_elements_loglevel, log_message.str());
358 log_message.str("");
359 }
360 log.log(m_elements_loglevel, "#");
361}
362
363// Log all options with a header
365
366 log.debug() << "##########################################################";
367 log.debug() << "#";
368 log.debug() << "# Environment of the Run";
369 log.debug() << "# ---------------------------";
370 log.debug() << "#";
371
372 for (const auto& v : Path::VARIABLE) {
373 log.debug() << v.second << ": " << m_env[v.second];
374 }
375
376 log.debug() << "#";
377}
378
380
383
384 vector<Path::Item> local_search_paths(m_search_dirs.size());
385
386 std::transform(m_search_dirs.cbegin(), m_search_dirs.cend(), local_search_paths.begin(), [](const string& s) {
387 return boost::filesystem::absolute(s);
388 });
389
390 // insert local parent dir if it is not already
391 // the first one of the list
392 const Path::Item this_parent_path = boost::filesystem::canonical(m_program_path.parent_path());
393 if (local_search_paths[0] != this_parent_path) {
394 auto b = local_search_paths.begin();
395 local_search_paths.insert(b, this_parent_path);
396 }
397
398 using Path::joinPath;
399 using Path::multiPathAppend;
400
401 for (const auto& v : Path::VARIABLE) {
402 if (m_env[v.second].exists()) {
403 m_env[v.second] += Path::PATH_SEP + joinPath(multiPathAppend(local_search_paths, Path::SUFFIXES.at(v.first)));
404 } else {
405 m_env[v.second] = joinPath(multiPathAppend(local_search_paths, Path::SUFFIXES.at(v.first)));
406 }
407 }
408}
409
410// Get the program options and setup logging
411void ProgramManager::setup(int argc, char* argv[]) {
412
413 // store the program name and path in class variable
414 // and retrieve the local environment
415 bootstrapEnvironment(argv[0]);
416
417 // get all program options into the varaiable_map
418 try {
420 } catch (const OptionException& e) {
421 auto exit_code = e.exitCode();
422 log.fatal() << "# Elements Exception : " << e.what();
423 std::_Exit(static_cast<int>(exit_code));
424 }
425
426 // get the program options related to the logging
427 string logging_level;
428 if (m_variables_map.count("log-level")) {
429 logging_level = m_variables_map["log-level"].as<string>();
430 } else {
431 throw Exception("Required option log-level is not provided!", ExitCode::CONFIG);
432 }
433 Path::Item log_file_name;
434
435 if (m_variables_map.count("log-file")) {
436 log_file_name = m_variables_map["log-file"].as<Path::Item>();
437 Logging::setLogFile(log_file_name);
438 }
439
440 // setup the logging
441 Logging::setLevel(logging_level);
442
443 logHeader(m_program_name.string());
444 // log all program options
447}
448
450
451 log.debug() << "# Exit Code: " << int(c);
452
453 logFooter(m_program_name.string());
454}
455
456// This is the method call from the main which does everything
457ExitCode ProgramManager::run(int argc, char* argv[]) {
458
459 setup(argc, argv);
460
461 ExitCode exit_code = m_program_ptr->mainMethod(m_variables_map);
462
463 tearDown(exit_code);
464
465 return exit_code;
466}
467
469
471
472 return version;
473}
474
476
478
479 ExitCode exit_code{ExitCode::NOT_OK};
480
481 if (auto exc = std::current_exception()) {
482
483 log.fatal() << "Crash detected";
484 log.fatal() << "This is the back trace:";
485 for (const auto& level : System::backTrace(21, 4)) {
486 log.fatal() << level;
487 }
488
489 // we have an exception
490 try {
491 std::rethrow_exception(exc); // throw to recognise the type
492 } catch (const Exception& exc1) {
493 log.fatal() << "# ";
494 log.fatal() << "# Elements Exception : " << exc1.what();
495 log.fatal() << "# ";
496 exit_code = exc1.exitCode();
497 } catch (const std::exception& exc2) {
500 log.fatal() << "# ";
501 log.fatal() << "# Standard Exception : " << exc2.what();
502 log.fatal() << "# ";
503 } catch (...) {
504 log.fatal() << "# ";
505 log.fatal() << "# An exception of unknown type occurred, "
506 << "i.e., an exception not deriving from std::exception ";
507 log.fatal() << "# ";
508 }
509
510 abort();
511 }
512
513 std::_Exit(static_cast<int>(exit_code));
514}
515
516} // namespace Elements
provide functions to retrieve configuration files
T endl(T... args)
defines the base Elements exception class
define a list of standard exit codes for executables
Logging facility.
OS specific details to access at run-time the module configuration of the process.
define an exception for unrecognized commandline options and arguments
provide functions to retrieve resources pointed by environment variables
define an abstract class for all Elements program
This file is intended to iron out all the differences between systems (currently Linux and MacOSX)
Macro to silence unused variables warnings from the compiler.
T _Exit(T... args)
T begin(T... args)
Elements base exception class.
Definition Exception.h:44
ExitCode exitCode() const noexcept
Definition Exception.cpp:39
const char * what() const noexcept override
Definition Exception.cpp:35
static Logging getLogger(const std::string &name="")
Definition Logging.cpp:75
static void setLogFile(const Path::Item &fileName)
Sets the file to store the log messages.
Definition Logging.cpp:99
static void setLevel(std::string level)
Sets the global message level.
Definition Logging.cpp:87
void setup(int argc, char *argv[])
Program setup taking care of command line options and logging initialization.
std::string m_parent_project_version
virtual ~ProgramManager()
Destructor.
std::unique_ptr< Program > m_program_ptr
static void onTerminate() noexcept
This is the set_terminate handler that is used in the MAIN_FOR macro.
const Path::Item & getProgramName() const
Getter.
ExitCode run(int argc, char *argv[])
This is the public entry point, i.e., the only method called from the main.
void bootstrapEnvironment(char *arg0)
Bootstrap the Environment from the executable location and the install path computed at install time.
static const Path::Item getDefaultConfigFile(const Path::Item &program_name, const std::string &module_name="")
Get a default configuration file name and path, to be used if not provided as a command line option.
void logTheEnvironment() const
Log the program environment.
std::vector< std::string > m_search_dirs
void logHeader(std::string program_name) const
Log Header.
void checkCommandLineOptions(const boost::program_options::basic_parsed_options< charT > &cmd_line_options)
check the explicit command line arguments. For the moment, it only checks if the configuration file b...
const Program::VariablesMap getProgramOptions(int argc, char *argv[])
Get the program options from the command line into thevariables_map.
static const Path::Item setProgramName(char *arg0)
Strip the path from argv[0] to set the program name.
std::string m_parent_module_version
std::string getVersion() const
This function returns the version of the program computed at compile time. This is the same as the pr...
log4cpp::Priority::Value m_elements_loglevel
std::string m_parent_project_vcs_version
const Path::Item & getProgramPath() const
Getter.
void tearDown(const ExitCode &)
void logFooter(std::string program_name) const
Log Footer.
ProgramManager(std::unique_ptr< Program > program_ptr, const std::string &parent_project_version="", const std::string &parent_project_name="", const std::string &parent_project_vcs_version="", const std::string &parent_module_version="", const std::string &parent_module_name="", const std::vector< std::string > &search_dirs={}, const log4cpp::Priority::Value &elements_loglevel=log4cpp::Priority::DEBUG, bool no_config_file=false)
Constructor.
Program::VariablesMap m_variables_map
static const Path::Item setProgramPath(char *arg0)
Strip the name from argv[0] to set the program path.
void logAllOptions() const
Log all program options.
options_description OptionsDescription
Definition Program.h:64
variables_map VariablesMap
Definition Program.h:67
STL class.
T current_exception(T... args)
T empty(T... args)
T endl(T... args)
T exit(T... args)
ExitCode
Strongly typed exit numbers.
Definition Exit.h:97
#define ELEMENTS_UNUSED
Definition Unused.h:39
@ NOT_OK
Generic unknown failure.
Definition Exit.h:101
@ CONFIG
configuration error
Definition Exit.h:117
@ OK
Everything is OK.
Definition Exit.h:99
T insert(T... args)
T log(T... args)
ELEMENTS_API Path::Item getConfigurationPath(const T &file_name, bool raise_exception=true)
ELEMENTS_API std::vector< Path::Item > getConfigurationLocations(bool exist_only=false)
ELEMENTS_API int backTrace(ELEMENTS_UNUSED std::shared_ptr< void * > addresses, ELEMENTS_UNUSED const int depth)
ELEMENTS_API Path::Item getExecutablePath()
Get the full executable path.
ELEMENTS_API Path::Item getExecutablePath()
Get the full executable path.
Program::VariablesMap VariablesMap
Program::OptionsDescription OptionsDescription
Definition Program.cpp:28
STL namespace.
T rethrow_exception(T... args)
T str(T... args)
T transform(T... args)
T what(T... args)