Elements  5.8
A C++ base framework for the Euclid Software.
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Groups Pages
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/filesystem/path.hpp> // for filesystem::path
38 #include <boost/program_options.hpp> // for program_options
39 
40 #include "ElementsKernel/Configuration.h" // for getConfigurationPath
41 #include "ElementsKernel/Path.h" // for Path::VARIABLE, multiPathAppend, PATH_SEP
42 #include "ElementsKernel/Exception.h" // for Exception
43 #include "ElementsKernel/Exit.h" // for ExitCode
44 #include "ElementsKernel/Logging.h" // for Logging
45 #include "ElementsKernel/ModuleInfo.h" // for getExecutablePath
46 #include "ElementsKernel/System.h" // for backTrace
47 #include "ElementsKernel/Unused.h" // for ELEMENTS_UNUSED
48 
49 #include "OptionException.h" // local exception for unrecognized options
50 
51 using std::vector;
52 using std::string;
53 using std::move;
54 using std::endl;
55 using std::cerr;
56 using log4cpp::Priority;
57 
59 using boost::program_options::variables_map;
60 
61 namespace Elements {
62 
63 namespace {
64  auto log = Logging::getLogger("ElementsProgram");
65 }
66 
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 
103 const path ProgramManager::getDefaultConfigFile(const path & program_name,
104  const string& module_name) {
105  path default_config_file{};
106 
107  // .conf is the standard extension for configuration file
108  path 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 {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 full_path = getExecutablePath();
137 
138  return full_path.filename();
139 }
140 
142 
143  path 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 { 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;
178  using boost::program_options::options_description;
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  variables_map var_map { };
188 
189  // default value for default_log_level option
190  string default_log_level = "INFO";
191 
192  // Get defaults
193  path default_config_file = getDefaultConfigFile(getProgramName(),
195 
196  // Define the options which can be given only at the command line
197  options_description 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>()->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  options_description 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>(), "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  options_description 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  options_description 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  options_description 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  options_description 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>();
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)) {
359  log_message << v.first << " = "
360  << v.second.as<path>();
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> local_search_paths(m_search_dirs.size());
421 
423  local_search_paths.begin(),
424  [](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 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 log_file_name;
474 
475  if (m_variables_map.count("log-file")) {
476  log_file_name = m_variables_map["log-file"].as<path>();
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 & exc) {
535  log.fatal() << "# ";
536  log.fatal() << "# Elements Exception : " << exc.what();
537  log.fatal() << "# ";
538  exit_code = exc.exitCode();
539  } catch (const std::exception & exc) {
542  log.fatal() << "# ";
543  log.fatal() << "# Standard Exception : " << exc.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
Macro to silence unused variables warnings from the compiler.
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:54
T empty(T...args)
void tearDown(const ExitCode &)
std::string m_parent_project_vcs_version
boost::program_options::variables_map m_variables_map
ELEMENTS_API std::vector< boost::filesystem::path > getConfigurationLocations(bool exist_only=false)
Logging facility.
void logAllOptions() const
Log all program options.
log4cpp::Priority::Value m_elements_loglevel
const boost::filesystem::path & getProgramName() const
Getter.
void bootstrapEnvironment(char *arg0)
Bootstrap the Environment from the executable location and the install path computed at install time...
const boost::filesystem::path & getProgramPath() const
Getter.
static const boost::filesystem::path setProgramPath(char *arg0)
Strip the name from argv[0] to set the program path.
T log(T...args)
Generic unknown failure.
constexpr double e
The base of the natural logarithm .
Definition: MathConstants.h:50
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.
constexpr double s
static void onTerminate() noexcept
This is the set_terminate handler that is used in the MAIN_FOR macro.
T endl(T...args)
T cend(T...args)
T current_exception(T...args)
boost::filesystem::path getConfigurationPath(const T &file_name, bool raise_exception)
boost::filesystem::path m_program_name
static const boost::filesystem::path setProgramName(char *arg0)
Strip the path from argv[0] to set the program name.
std::unique_ptr< Program > m_program_ptr
STL class.
static const boost::filesystem::path getDefaultConfigFile(const boost::filesystem::path &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...
boost::filesystem::path m_program_path
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...
virtual ~ProgramManager()
Destructor.
T exit(T...args)
T what(T...args)
std::string m_parent_project_name
This file is intended to iron out all the differences between systems (currently Linux and MacOSX) ...
std::string m_parent_module_name
boost::filesystem::path path
Definition: DataSyncUtils.h:33
std::vector< std::string > m_search_dirs
const boost::program_options::variables_map getProgramOptions(int argc, char *argv[])
Get the program options from the command line into thevariables_map.
configuration error
T _Exit(T...args)
command line usage error
Everything is OK.
STL class.
ELEMENTS_API const std::map< Type, const std::string > VARIABLE
map containing the name of the path variable for each type
Definition: Path.cpp:46
T move(T...args)
static void setLogFile(const boost::filesystem::path &fileName)
Sets the file to store the log messages.
Definition: Logging.cpp:88
void setup(int argc, char *argv[])
Program setup taking care of command line options and logging initialization.
std::vector< boost::filesystem::path > multiPathAppend(const std::vector< T > &initial_locations, const std::vector< U > &suffixes)
Definition: Path.icpp:116
T size(T...args)
ELEMENTS_API boost::filesystem::path getExecutablePath()
Get the full executable path.
Definition: ModuleInfo.cpp:248
ELEMENTS_API int backTrace(ELEMENTS_UNUSED std::shared_ptr< void * > addresses, ELEMENTS_UNUSED const int depth)
Definition: System.cpp:396
ExitCode
Strongly typed exit numbers.
Definition: Exit.h:97
STL class.
ExitCode exitCode() const noexcept
Definition: Exception.h:106
STL class.
define a list of standard exit codes for executables
std::string joinPath(const std::vector< T > path_list)
collate a vector of path into a string using PATH_SEP
Definition: Path.icpp:97
T cbegin(T...args)
define an exception for unrecognized commandline options and arguments
provide functions to retrieve resources pointed by environment variables
std::string getVersion() const
This function returns the version of the program computed at compile time. This is the same as the pr...
void logTheEnvironment() const
Log the program environment.
T transform(T...args)
OS specific details to access at run-time the module configuration of the process.
static void setLevel(std::string level)
Sets the global message level.
Definition: Logging.cpp:76
provide functions to retrieve configuration files
T rethrow_exception(T...args)
#define ELEMENTS_UNUSED
Definition: Unused.h:39
const char * what() const noexceptoverride
Definition: Exception.h:98
static Logging getLogger(const std::string &name="")
Definition: Logging.cpp:63
defines the base Elements exception class
ELEMENTS_API const std::string PATH_SEP
Separator of path entries. Usually &quot;:&quot; on Unix.
Definition: Path.cpp:44
void logFooter(std::string program_name) const
Log Footer.
STL class.
void logHeader(std::string program_name) const
Log Header.
ExitCode run(int argc, char *argv[])
This is the public entry point, i.e., the only method called from the main.