2 * This file is part of the GROMACS molecular simulation package.
4 * Copyright (c) 2012,2013,2014,2015,2016,2017,2018,2019, by the GROMACS development team, led by
5 * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
6 * and including many others, as listed in the AUTHORS file in the
7 * top-level source directory and at http://www.gromacs.org.
9 * GROMACS is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU Lesser General Public License
11 * as published by the Free Software Foundation; either version 2.1
12 * of the License, or (at your option) any later version.
14 * GROMACS is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 * Lesser General Public License for more details.
19 * You should have received a copy of the GNU Lesser General Public
20 * License along with GROMACS; if not, see
21 * http://www.gnu.org/licenses, or write to the Free Software Foundation,
22 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
24 * If you want to redistribute modifications to GROMACS, please
25 * consider that scientific software is very special. Version
26 * control is crucial - bugs must be traceable. We will be happy to
27 * consider code for inclusion in the official distribution, but
28 * derived work must not be called official GROMACS. Details are found
29 * in the README & COPYING files - if they are missing, get the
30 * official version at http://www.gromacs.org.
32 * To help us fund GROMACS development, we humbly ask that you cite
33 * the research papers on the package. Check out http://www.gromacs.org.
37 * Implements gmx::CommandLineModuleManager.
39 * \author Teemu Murtola <teemu.murtola@gmail.com>
40 * \ingroup module_commandline
44 #include "cmdlinemodulemanager.h"
51 #include "gromacs/commandline/cmdlinehelpcontext.h"
52 #include "gromacs/commandline/cmdlineinit.h"
53 #include "gromacs/commandline/cmdlinemodule.h"
54 #include "gromacs/commandline/cmdlineparser.h"
55 #include "gromacs/commandline/cmdlineprogramcontext.h"
56 #include "gromacs/math/utilities.h"
57 #include "gromacs/options/basicoptions.h"
58 #include "gromacs/options/options.h"
59 #include "gromacs/utility/basenetwork.h"
60 #include "gromacs/utility/coolstuff.h"
61 #include "gromacs/utility/exceptions.h"
62 #include "gromacs/utility/fatalerror.h"
63 #include "gromacs/utility/futil.h"
64 #include "gromacs/utility/gmxassert.h"
65 #include "gromacs/utility/stringutil.h"
66 #include "gromacs/utility/sysinfo.h"
68 #include "cmdlinehelpmodule.h"
69 #include "cmdlinemodulemanager_impl.h"
77 //! \addtogroup module_commandline
80 /********************************************************************
81 * CMainCommandLineModule
85 * Implements a ICommandLineModule, given a function with C/C++ main()
88 class CMainCommandLineModule : public ICommandLineModule
91 //! \copydoc gmx::CommandLineModuleManager::CMainFunction
92 typedef CommandLineModuleManager::CMainFunction CMainFunction;
93 //! \copydoc gmx::CommandLineModuleManager::InitSettingsFunction
94 typedef CommandLineModuleManager::InitSettingsFunction InitSettingsFunction;
97 * Creates a wrapper module for the given main function.
99 * \param[in] name Name for the module.
100 * \param[in] shortDescription One-line description for the module.
101 * \param[in] mainFunction Main function to wrap.
102 * \param[in] settingsFunction Initializer for settings (can be null).
104 * Does not throw. This is essential for correct implementation of
105 * CommandLineModuleManager::runAsMainCMain().
107 CMainCommandLineModule(const char* name,
108 const char* shortDescription,
109 CMainFunction mainFunction,
110 InitSettingsFunction settingsFunction) :
112 shortDescription_(shortDescription),
113 mainFunction_(mainFunction),
114 settingsFunction_(settingsFunction)
118 const char* name() const override { return name_; }
119 const char* shortDescription() const override { return shortDescription_; }
121 void init(CommandLineModuleSettings* settings) override
123 if (settingsFunction_ != nullptr)
125 settingsFunction_(settings);
128 int run(int argc, char* argv[]) override { return mainFunction_(argc, argv); }
129 void writeHelp(const CommandLineHelpContext& context) const override
131 writeCommandLineHelpCMain(context, name_, mainFunction_);
136 const char* shortDescription_;
137 CMainFunction mainFunction_;
138 InitSettingsFunction settingsFunction_;
145 /********************************************************************
146 * CommandLineCommonOptionsHolder
149 CommandLineCommonOptionsHolder::CommandLineCommonOptionsHolder() :
161 binaryInfoSettings_.copyright(true);
164 CommandLineCommonOptionsHolder::~CommandLineCommonOptionsHolder() {}
166 void CommandLineCommonOptionsHolder::initOptions()
168 options_.addOption(BooleanOption("h").store(&bHelp_).description("Print help and quit"));
169 options_.addOption(BooleanOption("hidden").store(&bHidden_).hidden().description(
170 "Show hidden options in help"));
171 options_.addOption(BooleanOption("quiet").store(&bQuiet_).description(
172 "Do not print common startup info or quotes"));
174 BooleanOption("version").store(&bVersion_).description("Print extended version information and quit"));
176 BooleanOption("copyright").store(&bCopyright_).description("Print copyright information on startup"));
177 options_.addOption(IntegerOption("nice")
179 .storeIsSet(&bNiceSet_)
180 .description("Set the nicelevel (default depends on command)"));
181 options_.addOption(BooleanOption("backup").store(&bBackup_).description(
182 "Write backups if output files exist"));
184 BooleanOption("fpexcept").store(&bFpexcept_).hidden().description("Enable floating-point exceptions"));
185 options_.addOption(IntegerOption("debug")
188 .defaultValueIfSet(1)
189 .description("Write file with debug information, "
190 "1: short (default), 2: also x and f"));
193 bool CommandLineCommonOptionsHolder::finishOptions()
196 binaryInfoSettings_.extendedInfo(bVersion_);
197 // The latter condition suppresses the copyright with
199 binaryInfoSettings_.copyright(bCopyright_ && !bQuiet_);
203 void CommandLineCommonOptionsHolder::adjustFromSettings(const CommandLineModuleSettings& settings)
207 niceLevel_ = settings.defaultNiceLevel();
211 /********************************************************************
212 * CommandLineModuleManager::Impl
216 * Private implementation class for CommandLineModuleManager.
218 * \ingroup module_commandline
220 class CommandLineModuleManager::Impl
224 * Initializes the implementation class.
226 * \param[in] binaryName Name of the running binary
227 * (without Gromacs binary suffix or .exe on Windows).
228 * \param programContext Program information for the running binary.
230 Impl(const char* binaryName, CommandLineProgramContext* programContext);
233 * Helper method that adds a given module to the module manager.
235 * \throws std::bad_alloc if out of memory.
237 void addModule(CommandLineModulePointer module);
239 * Creates the help module if it does not yet exist.
241 * \throws std::bad_alloc if out of memory.
243 * This method should be called before accessing \a helpModule_.
245 void ensureHelpModuleExists();
248 * Finds a module that matches a name.
250 * \param[in] name Module name to find.
251 * \returns Iterator to the found module, or
252 * \c modules_.end() if not found.
256 CommandLineModuleMap::const_iterator findModuleByName(const std::string& name) const;
259 * Processes command-line options for the wrapper binary.
261 * \param[in,out] optionsHolder Common options.
262 * \param[in,out] argc On input, argc passed to run().
263 * On output, argc to be passed to the module.
264 * \param[in,out] argv On input, argv passed to run().
265 * On output, argv to be passed to the module.
266 * \throws InvalidInputError if there are invalid options.
267 * \returns The module that should be run.
269 * Handles command-line options that affect the wrapper binary
270 * (potentially changing the members of \c this in response to the
271 * options). Also finds the module that should be run and the
272 * arguments that should be passed to it.
274 ICommandLineModule* processCommonOptions(CommandLineCommonOptionsHolder* optionsHolder,
278 //! Prints the footer at the end of execution.
279 void printThanks(FILE* fp);
282 * Maps module names to module objects.
284 * Owns the contained modules.
286 CommandLineModuleMap modules_;
288 * List of groupings for modules for help output.
290 * Owns the contained module group data objects.
291 * CommandLineModuleGroup objects point to the data objects contained
294 CommandLineModuleGroupList moduleGroups_;
295 //! Information about the currently running program.
296 CommandLineProgramContext& programContext_;
297 //! Name of the binary.
298 std::string binaryName_;
300 * Module that implements help for the binary.
302 * The pointed module is owned by the \a modules_ container.
304 CommandLineHelpModule* helpModule_;
305 //! If non-NULL, run this module in single-module mode.
306 ICommandLineModule* singleModule_;
307 //! Stores the value set with setQuiet().
311 GMX_DISALLOW_COPY_AND_ASSIGN(Impl);
314 CommandLineModuleManager::Impl::Impl(const char* binaryName, CommandLineProgramContext* programContext) :
315 programContext_(*programContext),
316 binaryName_(binaryName != nullptr ? binaryName : ""),
317 helpModule_(nullptr),
318 singleModule_(nullptr),
321 GMX_RELEASE_ASSERT(binaryName_.find('-') == std::string::npos,
322 "Help export does not currently work with binary names with dashes");
325 void CommandLineModuleManager::Impl::addModule(CommandLineModulePointer module)
327 GMX_ASSERT(modules_.find(module->name()) == modules_.end(),
328 "Attempted to register a duplicate module name");
329 ensureHelpModuleExists();
330 HelpTopicPointer helpTopic(helpModule_->createModuleHelpTopic(*module));
331 modules_.insert(std::make_pair(std::string(module->name()), std::move(module)));
332 helpModule_->addTopic(std::move(helpTopic), false);
335 void CommandLineModuleManager::Impl::ensureHelpModuleExists()
337 if (helpModule_ == nullptr)
339 helpModule_ = new CommandLineHelpModule(programContext_, binaryName_, modules_, moduleGroups_);
340 addModule(CommandLineModulePointer(helpModule_));
344 CommandLineModuleMap::const_iterator CommandLineModuleManager::Impl::findModuleByName(const std::string& name) const
346 // TODO: Accept unambiguous prefixes?
347 return modules_.find(name);
350 ICommandLineModule* CommandLineModuleManager::Impl::processCommonOptions(CommandLineCommonOptionsHolder* optionsHolder,
354 // Check if we are directly invoking a certain module.
355 ICommandLineModule* module = singleModule_;
357 // TODO: It would be nice to propagate at least the -quiet option to
358 // the modules so that they can also be quiet in response to this.
360 if (module == nullptr)
362 // If not in single-module mode, process options to the wrapper binary.
363 // TODO: Ideally, this could be done by CommandLineParser.
365 // Find the module name (if any) in the arg list
366 int indexOfModuleName = 1;
367 while (indexOfModuleName < *argc && (*argv)[indexOfModuleName][0] == '-')
371 if (indexOfModuleName > 1)
373 // Process options that are provided to the wrapper
374 // binary. These precede the module name, if one exists.
375 int argcForWrapper = indexOfModuleName;
376 CommandLineParser(optionsHolder->options()).parse(&argcForWrapper, *argv);
378 // If no action requested and there is a module specified, process it.
379 if (indexOfModuleName < *argc && !optionsHolder->shouldIgnoreActualModule())
381 const char* moduleName = (*argv)[indexOfModuleName];
382 CommandLineModuleMap::const_iterator moduleIter = findModuleByName(moduleName);
383 if (moduleIter == modules_.end())
385 std::string message = formatString("'%s' is not a GROMACS command.", moduleName);
386 GMX_THROW(InvalidInputError(message));
388 module = moduleIter->second.get();
389 *argc -= indexOfModuleName;
390 *argv += indexOfModuleName;
391 // After this point, argc and argv are the same independent of
392 // which path is taken: (*argv)[0] is the module name.
395 if (module != nullptr)
397 if (singleModule_ == nullptr)
399 programContext_.setDisplayName(binaryName_ + " " + module->name());
401 // Recognize the common options also after the module name.
402 // TODO: It could be nicer to only recognize -h/-hidden if module is not
404 CommandLineParser(optionsHolder->options()).allowPositionalArguments(true).skipUnknown(true).parse(argc, *argv);
406 if (!optionsHolder->finishOptions())
410 // If no module specified and no other action, show the help.
411 // Also explicitly specifying -h for the wrapper binary goes here.
412 if (module == nullptr || optionsHolder->shouldShowHelp())
414 ensureHelpModuleExists();
415 if (module != nullptr)
417 helpModule_->setModuleOverride(*module);
420 module = helpModule_;
422 if (module == helpModule_)
424 helpModule_->setShowHidden(optionsHolder->shouldShowHidden());
429 void CommandLineModuleManager::Impl::printThanks(FILE* fp)
431 fprintf(fp, "\n%s\n\n", getCoolQuote().c_str());
434 /********************************************************************
435 * CommandLineModuleManager
438 CommandLineModuleManager::CommandLineModuleManager(const char* binaryName,
439 CommandLineProgramContext* programContext) :
440 impl_(new Impl(binaryName, programContext))
444 CommandLineModuleManager::~CommandLineModuleManager() {}
446 void CommandLineModuleManager::setQuiet(bool bQuiet)
448 impl_->bQuiet_ = bQuiet;
451 void CommandLineModuleManager::setOutputRedirector(IFileOutputRedirector* output)
453 impl_->ensureHelpModuleExists();
454 impl_->helpModule_->setOutputRedirector(output);
457 void CommandLineModuleManager::setSingleModule(ICommandLineModule* module)
459 impl_->singleModule_ = module;
462 void CommandLineModuleManager::addModule(CommandLineModulePointer module)
464 impl_->addModule(std::move(module));
467 void CommandLineModuleManager::addModuleCMain(const char* name, const char* shortDescription, CMainFunction mainFunction)
469 CommandLineModulePointer module(
470 new CMainCommandLineModule(name, shortDescription, mainFunction, nullptr));
471 addModule(std::move(module));
474 void CommandLineModuleManager::addModuleCMainWithSettings(const char* name,
475 const char* shortDescription,
476 CMainFunction mainFunction,
477 InitSettingsFunction settingsFunction)
479 CommandLineModulePointer module(
480 new CMainCommandLineModule(name, shortDescription, mainFunction, settingsFunction));
481 addModule(std::move(module));
484 CommandLineModuleGroup CommandLineModuleManager::addModuleGroup(const char* title)
486 const char* const binaryName = impl_->binaryName_.c_str();
487 CommandLineModuleGroupDataPointer group(
488 new CommandLineModuleGroupData(impl_->modules_, binaryName, title));
489 impl_->moduleGroups_.push_back(std::move(group));
490 return CommandLineModuleGroup(impl_->moduleGroups_.back().get());
493 void CommandLineModuleManager::addHelpTopic(HelpTopicPointer topic)
495 impl_->ensureHelpModuleExists();
496 impl_->helpModule_->addTopic(std::move(topic), true);
499 int CommandLineModuleManager::run(int argc, char* argv[])
501 ICommandLineModule* module;
502 const bool bMaster = (gmx_node_rank() == 0);
503 bool bQuiet = impl_->bQuiet_ || !bMaster;
504 CommandLineCommonOptionsHolder optionsHolder;
507 optionsHolder.initOptions();
508 module = impl_->processCommonOptions(&optionsHolder, &argc, &argv);
510 catch (const std::exception&)
512 bQuiet |= optionsHolder.shouldBeQuiet();
515 printBinaryInformation(stderr, impl_->programContext_, optionsHolder.binaryInfoSettings());
519 bQuiet |= optionsHolder.shouldBeQuiet();
522 FILE* out = optionsHolder.startupInfoFile();
523 printBinaryInformation(out, impl_->programContext_, optionsHolder.binaryInfoSettings());
526 if (module == nullptr)
531 CommandLineModuleSettings settings;
532 module->init(&settings);
533 optionsHolder.adjustFromSettings(settings);
535 gmx_set_max_backup_count(optionsHolder.shouldBackup() ? -1 : 0);
537 // Open the debug file.
538 if (optionsHolder.debugLevel() > 0)
540 std::string filename(impl_->programContext_.programName());
541 if (gmx_node_num() > 1)
543 filename.append(formatString("%d", gmx_node_rank()));
545 filename.append(".debug");
547 fprintf(stderr, "Will write debug log file: %s\n", filename.c_str());
548 gmx_init_debug(optionsHolder.debugLevel(), filename.c_str());
550 // Set the nice level unless disabled in the configuration.
551 if (optionsHolder.niceLevel() != 0)
553 static bool bNiceSet = false; // Only set it once.
556 // TODO: Diagnostic if this fails and the user explicitly requested it.
557 gmx_set_nice(optionsHolder.niceLevel());
561 if (optionsHolder.enableFPExceptions())
563 // TODO: currently it is always enabled for mdrun (verlet) and tests.
564 gmx_feenableexcept();
568 if (!(module == impl_->helpModule_ && !bMaster))
570 rc = module->run(argc, argv);
574 impl_->printThanks(stderr);
580 int CommandLineModuleManager::runAsMainSingleModule(int argc, char* argv[], ICommandLineModule* module)
582 CommandLineProgramContext& programContext = gmx::initForCommandLine(&argc, &argv);
585 CommandLineModuleManager manager(nullptr, &programContext);
586 manager.setSingleModule(module);
587 int rc = manager.run(argc, argv);
588 gmx::finalizeForCommandLine();
591 catch (const std::exception& ex)
593 printFatalErrorMessage(stderr, ex);
594 return processExceptionAtExitForCommandLine(ex);
599 int CommandLineModuleManager::runAsMainCMain(int argc, char* argv[], CMainFunction mainFunction)
601 CMainCommandLineModule module(argv[0], nullptr, mainFunction, nullptr);
602 return runAsMainSingleModule(argc, argv, &module);
606 int CommandLineModuleManager::runAsMainCMainWithSettings(int argc,
608 CMainFunction mainFunction,
609 InitSettingsFunction settingsFunction)
611 CMainCommandLineModule module(argv[0], nullptr, mainFunction, settingsFunction);
612 return runAsMainSingleModule(argc, argv, &module);
615 /********************************************************************
616 * CommandLineModuleGroupData
619 void CommandLineModuleGroupData::addModule(const char* name, const char* description)
621 CommandLineModuleMap::const_iterator moduleIter = allModules_.find(name);
622 GMX_RELEASE_ASSERT(moduleIter != allModules_.end(), "Non-existent module added to a group");
623 if (description == nullptr)
625 description = moduleIter->second->shortDescription();
626 GMX_RELEASE_ASSERT(description != nullptr, "Module without a description added to a group");
628 std::string tag(formatString("%s-%s", binaryName_, name));
629 modules_.push_back(std::make_pair(tag, description));
632 /********************************************************************
633 * CommandLineModuleGroup
636 void CommandLineModuleGroup::addModule(const char* name)
638 impl_->addModule(name, nullptr);
641 void CommandLineModuleGroup::addModuleWithDescription(const char* name, const char* description)
643 impl_->addModule(name, description);