Elements  6.0.1
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 <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 
52 using log4cpp::Priority;
53 using std::cerr;
54 using std::endl;
55 using std::move;
56 using std::string;
57 using std::vector;
58 
59 namespace Elements {
60 
61 namespace {
62 auto log = Logging::getLogger("ElementsProgram");
63 }
64 
67 
68 ProgramManager::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 
83  return m_program_path;
84 }
85 
87  return m_program_name;
88 }
89 
95 const 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 
139 template <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  */
162 const 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
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 
281 void 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 
291 void 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
419 void 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 {
427  m_variables_map = getProgramOptions(argc, argv);
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
453  logAllOptions();
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
465 ExitCode 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 
478  string version = m_parent_project_name + " " + m_parent_project_vcs_version;
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)
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
ExitCode
Strongly typed exit numbers.
Definition: Exit.h:97
ELEMENTS_API std::string joinPath(const std::vector< T > &path_list)
collate a vector of path into a string using PATH_SEP
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
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
ELEMENTS_API const std::string PATH_SEP
Separator of path entries. Usually ":" on Unix.
Definition: Path.cpp:44
#define ELEMENTS_UNUSED
Definition: Unused.h:39
T insert(T... args)
T move(T... args)
boost::filesystem::path Item
Definition: Path.h:56
constexpr double e
The base of the natural logarithm .
Definition: MathConstants.h:51
constexpr double s
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:242
Program::OptionsDescription OptionsDescription
Definition: Program.cpp:28
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)