2 * This file is part of the GROMACS molecular simulation package.
4 * Copyright (c) 2012,2013,2014,2015,2016 by the GROMACS development team.
5 * Copyright (c) 2017,2018,2019,2020, by the GROMACS development team, led by
6 * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
7 * and including many others, as listed in the AUTHORS file in the
8 * top-level source directory and at http://www.gromacs.org.
10 * GROMACS is free software; you can redistribute it and/or
11 * modify it under the terms of the GNU Lesser General Public License
12 * as published by the Free Software Foundation; either version 2.1
13 * of the License, or (at your option) any later version.
15 * GROMACS is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 * Lesser General Public License for more details.
20 * You should have received a copy of the GNU Lesser General Public
21 * License along with GROMACS; if not, see
22 * http://www.gnu.org/licenses, or write to the Free Software Foundation,
23 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
25 * If you want to redistribute modifications to GROMACS, please
26 * consider that scientific software is very special. Version
27 * control is crucial - bugs must be traceable. We will be happy to
28 * consider code for inclusion in the official distribution, but
29 * derived work must not be called official GROMACS. Details are found
30 * in the README & COPYING files - if they are missing, get the
31 * official version at http://www.gromacs.org.
33 * To help us fund GROMACS development, we humbly ask that you cite
34 * the research papers on the package. Check out http://www.gromacs.org.
38 * Implements gmx::CommandLineModuleManager.
40 * \author Teemu Murtola <teemu.murtola@gmail.com>
41 * \ingroup module_commandline
45 #include "cmdlinemodulemanager.h"
52 #include "gromacs/commandline/cmdlinehelpcontext.h"
53 #include "gromacs/commandline/cmdlineinit.h"
54 #include "gromacs/commandline/cmdlinemodule.h"
55 #include "gromacs/commandline/cmdlineparser.h"
56 #include "gromacs/commandline/cmdlineprogramcontext.h"
57 #include "gromacs/math/utilities.h"
58 #include "gromacs/options/basicoptions.h"
59 #include "gromacs/options/options.h"
60 #include "gromacs/utility/basenetwork.h"
61 #include "gromacs/utility/coolstuff.h"
62 #include "gromacs/utility/exceptions.h"
63 #include "gromacs/utility/fatalerror.h"
64 #include "gromacs/utility/futil.h"
65 #include "gromacs/utility/gmxassert.h"
66 #include "gromacs/utility/stringutil.h"
67 #include "gromacs/utility/sysinfo.h"
69 #include "cmdlinehelpmodule.h"
70 #include "cmdlinemodulemanager_impl.h"
78 //! \addtogroup module_commandline
81 /********************************************************************
82 * CMainCommandLineModule
86 * Implements a ICommandLineModule, given a function with C/C++ main()
89 class CMainCommandLineModule
: public ICommandLineModule
92 //! \copydoc gmx::CommandLineModuleManager::CMainFunction
93 typedef CommandLineModuleManager::CMainFunction CMainFunction
;
94 //! \copydoc gmx::CommandLineModuleManager::InitSettingsFunction
95 typedef CommandLineModuleManager::InitSettingsFunction InitSettingsFunction
;
98 * Creates a wrapper module for the given main function.
100 * \param[in] name Name for the module.
101 * \param[in] shortDescription One-line description for the module.
102 * \param[in] mainFunction Main function to wrap.
103 * \param[in] settingsFunction Initializer for settings (can be null).
105 * Does not throw. This is essential for correct implementation of
106 * CommandLineModuleManager::runAsMainCMain().
108 CMainCommandLineModule(const char* name
,
109 const char* shortDescription
,
110 CMainFunction mainFunction
,
111 InitSettingsFunction settingsFunction
) :
113 shortDescription_(shortDescription
),
114 mainFunction_(mainFunction
),
115 settingsFunction_(settingsFunction
)
119 const char* name() const override
{ return name_
; }
120 const char* shortDescription() const override
{ return shortDescription_
; }
122 void init(CommandLineModuleSettings
* settings
) override
124 if (settingsFunction_
!= nullptr)
126 settingsFunction_(settings
);
129 int run(int argc
, char* argv
[]) override
{ return mainFunction_(argc
, argv
); }
130 void writeHelp(const CommandLineHelpContext
& context
) const override
132 writeCommandLineHelpCMain(context
, name_
, mainFunction_
);
137 const char* shortDescription_
;
138 CMainFunction mainFunction_
;
139 InitSettingsFunction settingsFunction_
;
146 /********************************************************************
147 * CommandLineCommonOptionsHolder
150 CommandLineCommonOptionsHolder::CommandLineCommonOptionsHolder() :
162 binaryInfoSettings_
.copyright(true);
165 CommandLineCommonOptionsHolder::~CommandLineCommonOptionsHolder() {}
167 void CommandLineCommonOptionsHolder::initOptions()
169 options_
.addOption(BooleanOption("h").store(&bHelp_
).description("Print help and quit"));
170 options_
.addOption(BooleanOption("hidden").store(&bHidden_
).hidden().description(
171 "Show hidden options in help"));
172 options_
.addOption(BooleanOption("quiet").store(&bQuiet_
).description(
173 "Do not print common startup info or quotes"));
175 BooleanOption("version").store(&bVersion_
).description("Print extended version information and quit"));
177 BooleanOption("copyright").store(&bCopyright_
).description("Print copyright information on startup"));
178 options_
.addOption(IntegerOption("nice")
180 .storeIsSet(&bNiceSet_
)
181 .description("Set the nicelevel (default depends on command)"));
182 options_
.addOption(BooleanOption("backup").store(&bBackup_
).description(
183 "Write backups if output files exist"));
185 BooleanOption("fpexcept").store(&bFpexcept_
).hidden().description("Enable floating-point exceptions"));
186 options_
.addOption(IntegerOption("debug")
189 .defaultValueIfSet(1)
190 .description("Write file with debug information, "
191 "1: short (default), 2: also x and f"));
194 bool CommandLineCommonOptionsHolder::finishOptions()
197 binaryInfoSettings_
.extendedInfo(bVersion_
);
198 // The latter condition suppresses the copyright with
200 binaryInfoSettings_
.copyright(bCopyright_
&& !bQuiet_
);
204 void CommandLineCommonOptionsHolder::adjustFromSettings(const CommandLineModuleSettings
& settings
)
208 niceLevel_
= settings
.defaultNiceLevel();
212 /********************************************************************
213 * CommandLineModuleManager::Impl
217 * Private implementation class for CommandLineModuleManager.
219 * \ingroup module_commandline
221 class CommandLineModuleManager::Impl
225 * Initializes the implementation class.
227 * \param[in] binaryName Name of the running binary
228 * (without Gromacs binary suffix or .exe on Windows).
229 * \param programContext Program information for the running binary.
231 Impl(const char* binaryName
, CommandLineProgramContext
* programContext
);
234 * Helper method that adds a given module to the module manager.
236 * \throws std::bad_alloc if out of memory.
238 void addModule(CommandLineModulePointer module
);
240 * Creates the help module if it does not yet exist.
242 * \throws std::bad_alloc if out of memory.
244 * This method should be called before accessing \a helpModule_.
246 void ensureHelpModuleExists();
249 * Finds a module that matches a name.
251 * \param[in] name Module name to find.
252 * \returns Iterator to the found module, or
253 * \c modules_.end() if not found.
257 CommandLineModuleMap::const_iterator
findModuleByName(const std::string
& name
) const;
260 * Processes command-line options for the wrapper binary.
262 * \param[in,out] optionsHolder Common options.
263 * \param[in,out] argc On input, argc passed to run().
264 * On output, argc to be passed to the module.
265 * \param[in,out] argv On input, argv passed to run().
266 * On output, argv to be passed to the module.
267 * \throws InvalidInputError if there are invalid options.
268 * \returns The module that should be run.
270 * Handles command-line options that affect the wrapper binary
271 * (potentially changing the members of \c this in response to the
272 * options). Also finds the module that should be run and the
273 * arguments that should be passed to it.
275 ICommandLineModule
* processCommonOptions(CommandLineCommonOptionsHolder
* optionsHolder
,
279 //! Prints the footer at the end of execution.
280 static void printThanks(FILE* fp
);
283 * Maps module names to module objects.
285 * Owns the contained modules.
287 CommandLineModuleMap modules_
;
289 * List of groupings for modules for help output.
291 * Owns the contained module group data objects.
292 * CommandLineModuleGroup objects point to the data objects contained
295 CommandLineModuleGroupList moduleGroups_
;
296 //! Information about the currently running program.
297 CommandLineProgramContext
& programContext_
;
298 //! Name of the binary.
299 std::string binaryName_
;
301 * Module that implements help for the binary.
303 * The pointed module is owned by the \a modules_ container.
305 CommandLineHelpModule
* helpModule_
;
306 //! If non-NULL, run this module in single-module mode.
307 ICommandLineModule
* singleModule_
;
308 //! Stores the value set with setQuiet().
312 GMX_DISALLOW_COPY_AND_ASSIGN(Impl
);
315 CommandLineModuleManager::Impl::Impl(const char* binaryName
, CommandLineProgramContext
* programContext
) :
316 programContext_(*programContext
),
317 binaryName_(binaryName
!= nullptr ? binaryName
: ""),
318 helpModule_(nullptr),
319 singleModule_(nullptr),
322 GMX_RELEASE_ASSERT(binaryName_
.find('-') == std::string::npos
,
323 "Help export does not currently work with binary names with dashes");
326 void CommandLineModuleManager::Impl::addModule(CommandLineModulePointer module
)
328 GMX_ASSERT(modules_
.find(module
->name()) == modules_
.end(),
329 "Attempted to register a duplicate module name");
330 ensureHelpModuleExists();
331 HelpTopicPointer
helpTopic(helpModule_
->createModuleHelpTopic(*module
));
332 modules_
.insert(std::make_pair(std::string(module
->name()), std::move(module
)));
333 helpModule_
->addTopic(std::move(helpTopic
), false);
336 void CommandLineModuleManager::Impl::ensureHelpModuleExists()
338 if (helpModule_
== nullptr)
340 helpModule_
= new CommandLineHelpModule(programContext_
, binaryName_
, modules_
, moduleGroups_
);
341 addModule(CommandLineModulePointer(helpModule_
));
345 CommandLineModuleMap::const_iterator
CommandLineModuleManager::Impl::findModuleByName(const std::string
& name
) const
347 // TODO: Accept unambiguous prefixes?
348 return modules_
.find(name
);
351 ICommandLineModule
* CommandLineModuleManager::Impl::processCommonOptions(CommandLineCommonOptionsHolder
* optionsHolder
,
355 // Check if we are directly invoking a certain module.
356 ICommandLineModule
* module
= singleModule_
;
358 // TODO: It would be nice to propagate at least the -quiet option to
359 // the modules so that they can also be quiet in response to this.
361 if (module
== nullptr)
363 // If not in single-module mode, process options to the wrapper binary.
364 // TODO: Ideally, this could be done by CommandLineParser.
366 // Find the module name (if any) in the arg list
367 int indexOfModuleName
= 1;
368 while (indexOfModuleName
< *argc
&& (*argv
)[indexOfModuleName
][0] == '-')
372 if (indexOfModuleName
> 1)
374 // Process options that are provided to the wrapper
375 // binary. These precede the module name, if one exists.
376 int argcForWrapper
= indexOfModuleName
;
377 CommandLineParser(optionsHolder
->options()).parse(&argcForWrapper
, *argv
);
379 // If no action requested and there is a module specified, process it.
380 if (indexOfModuleName
< *argc
&& !optionsHolder
->shouldIgnoreActualModule())
382 const char* moduleName
= (*argv
)[indexOfModuleName
];
383 CommandLineModuleMap::const_iterator moduleIter
= findModuleByName(moduleName
);
384 if (moduleIter
== modules_
.end())
386 std::string message
= formatString("'%s' is not a GROMACS command.", moduleName
);
387 GMX_THROW(InvalidInputError(message
));
389 module
= moduleIter
->second
.get();
390 *argc
-= indexOfModuleName
;
391 *argv
+= indexOfModuleName
;
392 // After this point, argc and argv are the same independent of
393 // which path is taken: (*argv)[0] is the module name.
396 if (module
!= nullptr)
398 if (singleModule_
== nullptr)
400 programContext_
.setDisplayName(binaryName_
+ " " + module
->name());
402 // Recognize the common options also after the module name.
403 // TODO: It could be nicer to only recognize -h/-hidden if module is not
405 CommandLineParser(optionsHolder
->options()).allowPositionalArguments(true).skipUnknown(true).parse(argc
, *argv
);
407 if (!optionsHolder
->finishOptions())
411 // If no module specified and no other action, show the help.
412 // Also explicitly specifying -h for the wrapper binary goes here.
413 if (module
== nullptr || optionsHolder
->shouldShowHelp())
415 ensureHelpModuleExists();
416 if (module
!= nullptr)
418 helpModule_
->setModuleOverride(*module
);
421 module
= helpModule_
;
423 if (module
== helpModule_
)
425 helpModule_
->setShowHidden(optionsHolder
->shouldShowHidden());
430 void CommandLineModuleManager::Impl::printThanks(FILE* fp
)
432 fprintf(fp
, "\n%s\n\n", getCoolQuote().c_str());
435 /********************************************************************
436 * CommandLineModuleManager
439 CommandLineModuleManager::CommandLineModuleManager(const char* binaryName
,
440 CommandLineProgramContext
* programContext
) :
441 impl_(new Impl(binaryName
, programContext
))
445 CommandLineModuleManager::~CommandLineModuleManager() {}
447 void CommandLineModuleManager::setQuiet(bool bQuiet
)
449 impl_
->bQuiet_
= bQuiet
;
452 void CommandLineModuleManager::setOutputRedirector(IFileOutputRedirector
* output
)
454 impl_
->ensureHelpModuleExists();
455 impl_
->helpModule_
->setOutputRedirector(output
);
458 void CommandLineModuleManager::setSingleModule(ICommandLineModule
* module
)
460 impl_
->singleModule_
= module
;
463 void CommandLineModuleManager::addModule(CommandLineModulePointer module
)
465 impl_
->addModule(std::move(module
));
468 void CommandLineModuleManager::addModuleCMain(const char* name
, const char* shortDescription
, CMainFunction mainFunction
)
470 CommandLineModulePointer
module(
471 new CMainCommandLineModule(name
, shortDescription
, mainFunction
, nullptr));
472 addModule(std::move(module
));
475 void CommandLineModuleManager::addModuleCMainWithSettings(const char* name
,
476 const char* shortDescription
,
477 CMainFunction mainFunction
,
478 InitSettingsFunction settingsFunction
)
480 CommandLineModulePointer
module(
481 new CMainCommandLineModule(name
, shortDescription
, mainFunction
, settingsFunction
));
482 addModule(std::move(module
));
485 CommandLineModuleGroup
CommandLineModuleManager::addModuleGroup(const char* title
)
487 const char* const binaryName
= impl_
->binaryName_
.c_str();
488 CommandLineModuleGroupDataPointer
group(
489 new CommandLineModuleGroupData(impl_
->modules_
, binaryName
, title
));
490 impl_
->moduleGroups_
.push_back(std::move(group
));
491 return CommandLineModuleGroup(impl_
->moduleGroups_
.back().get());
494 void CommandLineModuleManager::addHelpTopic(HelpTopicPointer topic
)
496 impl_
->ensureHelpModuleExists();
497 impl_
->helpModule_
->addTopic(std::move(topic
), true);
500 int CommandLineModuleManager::run(int argc
, char* argv
[])
502 ICommandLineModule
* module
;
503 const bool bMaster
= (gmx_node_rank() == 0);
504 bool bQuiet
= impl_
->bQuiet_
|| !bMaster
;
505 CommandLineCommonOptionsHolder optionsHolder
;
508 optionsHolder
.initOptions();
509 module
= impl_
->processCommonOptions(&optionsHolder
, &argc
, &argv
);
511 catch (const std::exception
&)
513 bQuiet
|= optionsHolder
.shouldBeQuiet();
516 printBinaryInformation(stderr
, impl_
->programContext_
, optionsHolder
.binaryInfoSettings());
520 bQuiet
|= optionsHolder
.shouldBeQuiet();
523 FILE* out
= optionsHolder
.startupInfoFile();
524 printBinaryInformation(out
, impl_
->programContext_
, optionsHolder
.binaryInfoSettings());
527 if (module
== nullptr)
532 CommandLineModuleSettings settings
;
533 module
->init(&settings
);
534 optionsHolder
.adjustFromSettings(settings
);
536 gmx_set_max_backup_count(optionsHolder
.shouldBackup() ? -1 : 0);
538 // Open the debug file.
539 if (optionsHolder
.debugLevel() > 0)
541 std::string
filename(impl_
->programContext_
.programName());
542 if (gmx_node_num() > 1)
544 filename
.append(formatString("%d", gmx_node_rank()));
546 filename
.append(".debug");
548 fprintf(stderr
, "Will write debug log file: %s\n", filename
.c_str());
549 gmx_init_debug(optionsHolder
.debugLevel(), filename
.c_str());
551 // Set the nice level unless disabled in the configuration.
552 if (optionsHolder
.niceLevel() != 0)
554 static bool bNiceSet
= false; // Only set it once.
557 // TODO: Diagnostic if this fails and the user explicitly requested it.
558 gmx_set_nice(optionsHolder
.niceLevel());
562 if (optionsHolder
.enableFPExceptions())
564 // TODO: currently it is always enabled for mdrun (verlet) and tests.
565 gmx_feenableexcept();
569 if (!(module
== impl_
->helpModule_
&& !bMaster
))
571 rc
= module
->run(argc
, argv
);
575 impl_
->printThanks(stderr
);
581 int CommandLineModuleManager::runAsMainSingleModule(int argc
, char* argv
[], ICommandLineModule
* module
)
583 CommandLineProgramContext
& programContext
= gmx::initForCommandLine(&argc
, &argv
);
586 CommandLineModuleManager
manager(nullptr, &programContext
);
587 manager
.setSingleModule(module
);
588 int rc
= manager
.run(argc
, argv
);
589 gmx::finalizeForCommandLine();
592 catch (const std::exception
& ex
)
594 printFatalErrorMessage(stderr
, ex
);
595 return processExceptionAtExitForCommandLine(ex
);
600 int CommandLineModuleManager::runAsMainCMain(int argc
, char* argv
[], CMainFunction mainFunction
)
602 CMainCommandLineModule
module(argv
[0], nullptr, mainFunction
, nullptr);
603 return runAsMainSingleModule(argc
, argv
, &module
);
607 int CommandLineModuleManager::runAsMainCMainWithSettings(int argc
,
609 CMainFunction mainFunction
,
610 InitSettingsFunction settingsFunction
)
612 CMainCommandLineModule
module(argv
[0], nullptr, mainFunction
, settingsFunction
);
613 return runAsMainSingleModule(argc
, argv
, &module
);
616 /********************************************************************
617 * CommandLineModuleGroupData
620 void CommandLineModuleGroupData::addModule(const char* name
, const char* description
)
622 CommandLineModuleMap::const_iterator moduleIter
= allModules_
.find(name
);
623 GMX_RELEASE_ASSERT(moduleIter
!= allModules_
.end(), "Non-existent module added to a group");
624 if (description
== nullptr)
626 description
= moduleIter
->second
->shortDescription();
627 GMX_RELEASE_ASSERT(description
!= nullptr, "Module without a description added to a group");
629 std::string
tag(formatString("%s-%s", binaryName_
, name
));
630 modules_
.push_back(std::make_pair(tag
, description
));
633 /********************************************************************
634 * CommandLineModuleGroup
637 void CommandLineModuleGroup::addModule(const char* name
)
639 impl_
->addModule(name
, nullptr);
642 void CommandLineModuleGroup::addModuleWithDescription(const char* name
, const char* description
)
644 impl_
->addModule(name
, description
);