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