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