clang-tidy: override
[alexxy/gromacs.git] / src / gromacs / commandline / cmdlinemodulemanager.cpp
1 /*
2  * This file is part of the GROMACS molecular simulation package.
3  *
4  * Copyright (c) 2012,2013,2014,2015,2016,2017,2018, 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.
8  *
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.
13  *
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.
18  *
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.
23  *
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.
31  *
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.
34  */
35 /*! \internal \file
36  * \brief
37  * Implements gmx::CommandLineModuleManager.
38  *
39  * \author Teemu Murtola <teemu.murtola@gmail.com>
40  * \ingroup module_commandline
41  */
42 #include "gmxpre.h"
43
44 #include "cmdlinemodulemanager.h"
45
46 #include <cstdio>
47
48 #include <string>
49 #include <utility>
50
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"
67
68 #include "cmdlinehelpmodule.h"
69 #include "cmdlinemodulemanager-impl.h"
70
71 namespace gmx
72 {
73
74 namespace
75 {
76
77 //! \addtogroup module_commandline
78 //! \{
79
80 /********************************************************************
81  * CMainCommandLineModule
82  */
83
84 /*! \brief
85  * Implements a ICommandLineModule, given a function with C/C++ main()
86  * signature.
87  */
88 class CMainCommandLineModule : public ICommandLineModule
89 {
90     public:
91         //! \copydoc gmx::CommandLineModuleManager::CMainFunction
92         typedef CommandLineModuleManager::CMainFunction CMainFunction;
93         //! \copydoc gmx::CommandLineModuleManager::InitSettingsFunction
94         typedef CommandLineModuleManager::InitSettingsFunction InitSettingsFunction;
95
96         /*! \brief
97          * Creates a wrapper module for the given main function.
98          *
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).
103          *
104          * Does not throw.  This is essential for correct implementation of
105          * CommandLineModuleManager::runAsMainCMain().
106          */
107         CMainCommandLineModule(const char *name, const char *shortDescription,
108                                CMainFunction mainFunction,
109                                InitSettingsFunction settingsFunction)
110             : name_(name), shortDescription_(shortDescription),
111               mainFunction_(mainFunction), settingsFunction_(settingsFunction)
112         {
113         }
114
115         const char *name() const override
116         {
117             return name_;
118         }
119         const char *shortDescription() const override
120         {
121             return shortDescription_;
122         }
123
124         void init(CommandLineModuleSettings *settings) override
125         {
126             if (settingsFunction_ != nullptr)
127             {
128                 settingsFunction_(settings);
129             }
130         }
131         int run(int argc, char *argv[]) override
132         {
133             return mainFunction_(argc, argv);
134         }
135         void writeHelp(const CommandLineHelpContext &context) const override
136         {
137             writeCommandLineHelpCMain(context, name_, mainFunction_);
138         }
139
140     private:
141         const char             *name_;
142         const char             *shortDescription_;
143         CMainFunction           mainFunction_;
144         InitSettingsFunction    settingsFunction_;
145 };
146
147 //! \}
148
149 }   // namespace
150
151 /********************************************************************
152  * CommandLineCommonOptionsHolder
153  */
154
155 CommandLineCommonOptionsHolder::CommandLineCommonOptionsHolder()
156     : bHelp_(false), bHidden_(false),
157       bQuiet_(false), bVersion_(false), bCopyright_(true),
158       niceLevel_(19), bNiceSet_(false), bBackup_(true), bFpexcept_(false),
159       debugLevel_(0)
160 {
161     binaryInfoSettings_.copyright(true);
162 }
163
164 CommandLineCommonOptionsHolder::~CommandLineCommonOptionsHolder()
165 {
166 }
167
168 void CommandLineCommonOptionsHolder::initOptions()
169 {
170     options_.addOption(BooleanOption("h").store(&bHelp_)
171                            .description("Print help and quit"));
172     options_.addOption(BooleanOption("hidden").store(&bHidden_)
173                            .hidden()
174                            .description("Show hidden options in help"));
175     options_.addOption(BooleanOption("quiet").store(&bQuiet_)
176                            .description("Do not print common startup info or quotes"));
177     options_.addOption(BooleanOption("version").store(&bVersion_)
178                            .description("Print extended version information and quit"));
179     options_.addOption(BooleanOption("copyright").store(&bCopyright_)
180                            .description("Print copyright information on startup"));
181     options_.addOption(IntegerOption("nice").store(&niceLevel_).storeIsSet(&bNiceSet_)
182                            .description("Set the nicelevel (default depends on command)"));
183     options_.addOption(BooleanOption("backup").store(&bBackup_)
184                            .description("Write backups if output files exist"));
185     options_.addOption(BooleanOption("fpexcept").store(&bFpexcept_)
186                            .hidden().description("Enable floating-point exceptions"));
187     options_.addOption(IntegerOption("debug").store(&debugLevel_)
188                            .hidden().defaultValueIfSet(1)
189                            .description("Write file with debug information, "
190                                         "1: short (default), 2: also x and f"));
191 }
192
193 bool CommandLineCommonOptionsHolder::finishOptions()
194 {
195     options_.finish();
196     binaryInfoSettings_.extendedInfo(bVersion_);
197     // The latter condition suppresses the copyright with
198     // -quiet -version.
199     binaryInfoSettings_.copyright(bCopyright_ && !bQuiet_);
200     return !bVersion_;
201 }
202
203 void CommandLineCommonOptionsHolder::adjustFromSettings(
204         const CommandLineModuleSettings &settings)
205 {
206     if (!bNiceSet_)
207     {
208         niceLevel_ = settings.defaultNiceLevel();
209     }
210 }
211
212 /********************************************************************
213  * CommandLineModuleManager::Impl
214  */
215
216 /*! \internal \brief
217  * Private implementation class for CommandLineModuleManager.
218  *
219  * \ingroup module_commandline
220  */
221 class CommandLineModuleManager::Impl
222 {
223     public:
224         /*! \brief
225          * Initializes the implementation class.
226          *
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.
230          */
231         Impl(const char *binaryName, CommandLineProgramContext *programContext);
232
233         /*! \brief
234          * Helper method that adds a given module to the module manager.
235          *
236          * \throws    std::bad_alloc if out of memory.
237          */
238         void addModule(CommandLineModulePointer module);
239         /*! \brief
240          * Creates the help module if it does not yet exist.
241          *
242          * \throws    std::bad_alloc if out of memory.
243          *
244          * This method should be called before accessing \a helpModule_.
245          */
246         void ensureHelpModuleExists();
247
248         /*! \brief
249          * Finds a module that matches a name.
250          *
251          * \param[in] name  Module name to find.
252          * \returns   Iterator to the found module, or
253          *      \c modules_.end() if not found.
254          *
255          * Does not throw.
256          */
257         CommandLineModuleMap::const_iterator
258         findModuleByName(const std::string &name) const;
259
260         /*! \brief
261          * Processes command-line options for the wrapper binary.
262          *
263          * \param[in,out] optionsHolder Common options.
264          * \param[in,out] argc          On input, argc passed to run().
265          *     On output, argc to be passed to the module.
266          * \param[in,out] argv          On input, argv passed to run().
267          *     On output, argv to be passed to the module.
268          * \throws    InvalidInputError if there are invalid options.
269          * \returns   The module that should be run.
270          *
271          * Handles command-line options that affect the wrapper binary
272          * (potentially changing the members of \c this in response to the
273          * options).  Also finds the module that should be run and the
274          * arguments that should be passed to it.
275          */
276         ICommandLineModule *
277         processCommonOptions(CommandLineCommonOptionsHolder *optionsHolder,
278                              int *argc, char ***argv);
279
280         //! Prints the footer at the end of execution.
281         void printThanks(FILE *fp);
282
283         /*! \brief
284          * Maps module names to module objects.
285          *
286          * Owns the contained modules.
287          */
288         CommandLineModuleMap         modules_;
289         /*! \brief
290          * List of groupings for modules for help output.
291          *
292          * Owns the contained module group data objects.
293          * CommandLineModuleGroup objects point to the data objects contained
294          * here.
295          */
296         CommandLineModuleGroupList   moduleGroups_;
297         //! Information about the currently running program.
298         CommandLineProgramContext   &programContext_;
299         //! Name of the binary.
300         std::string                  binaryName_;
301         /*! \brief
302          * Module that implements help for the binary.
303          *
304          * The pointed module is owned by the \a modules_ container.
305          */
306         CommandLineHelpModule       *helpModule_;
307         //! If non-NULL, run this module in single-module mode.
308         ICommandLineModule          *singleModule_;
309         //! Stores the value set with setQuiet().
310         bool                         bQuiet_;
311
312     private:
313         GMX_DISALLOW_COPY_AND_ASSIGN(Impl);
314 };
315
316 CommandLineModuleManager::Impl::Impl(const char                *binaryName,
317                                      CommandLineProgramContext *programContext)
318     : programContext_(*programContext),
319       binaryName_(binaryName != nullptr ? binaryName : ""),
320       helpModule_(nullptr), singleModule_(nullptr),
321       bQuiet_(false)
322 {
323     GMX_RELEASE_ASSERT(binaryName_.find('-') == std::string::npos,
324                        "Help export does not currently work with binary names with dashes");
325 }
326
327 void CommandLineModuleManager::Impl::addModule(CommandLineModulePointer module)
328 {
329     GMX_ASSERT(modules_.find(module->name()) == modules_.end(),
330                "Attempted to register a duplicate module name");
331     ensureHelpModuleExists();
332     HelpTopicPointer helpTopic(helpModule_->createModuleHelpTopic(*module));
333     modules_.insert(std::make_pair(std::string(module->name()),
334                                    std::move(module)));
335     helpModule_->addTopic(std::move(helpTopic), false);
336 }
337
338 void CommandLineModuleManager::Impl::ensureHelpModuleExists()
339 {
340     if (helpModule_ == nullptr)
341     {
342         helpModule_ = new CommandLineHelpModule(programContext_, binaryName_,
343                                                 modules_, moduleGroups_);
344         addModule(CommandLineModulePointer(helpModule_));
345     }
346 }
347
348 CommandLineModuleMap::const_iterator
349 CommandLineModuleManager::Impl::findModuleByName(const std::string &name) const
350 {
351     // TODO: Accept unambiguous prefixes?
352     return modules_.find(name);
353 }
354
355 ICommandLineModule *
356 CommandLineModuleManager::Impl::processCommonOptions(
357         CommandLineCommonOptionsHolder *optionsHolder, int *argc, char ***argv)
358 {
359     // Check if we are directly invoking a certain module.
360     ICommandLineModule *module = singleModule_;
361
362     // TODO: It would be nice to propagate at least the -quiet option to
363     // the modules so that they can also be quiet in response to this.
364
365     if (module == nullptr)
366     {
367         // If not in single-module mode, process options to the wrapper binary.
368         // TODO: Ideally, this could be done by CommandLineParser.
369
370         // Find the module name (if any) in the arg list
371         int indexOfModuleName = 1;
372         while (indexOfModuleName < *argc && (*argv)[indexOfModuleName][0] == '-')
373         {
374             ++indexOfModuleName;
375         }
376         if (indexOfModuleName > 1)
377         {
378             // Process options that are provided to the wrapper
379             // binary. These precede the module name, if one exists.
380             int argcForWrapper = indexOfModuleName;
381             CommandLineParser(optionsHolder->options())
382                 .parse(&argcForWrapper, *argv);
383         }
384         // If no action requested and there is a module specified, process it.
385         if (indexOfModuleName < *argc && !optionsHolder->shouldIgnoreActualModule())
386         {
387             const char *moduleName = (*argv)[indexOfModuleName];
388             CommandLineModuleMap::const_iterator moduleIter
389                 = findModuleByName(moduleName);
390             if (moduleIter == modules_.end())
391             {
392                 std::string message =
393                     formatString("'%s' is not a GROMACS command.", moduleName);
394                 GMX_THROW(InvalidInputError(message));
395             }
396             module = moduleIter->second.get();
397             *argc -= indexOfModuleName;
398             *argv += indexOfModuleName;
399             // After this point, argc and argv are the same independent of
400             // which path is taken: (*argv)[0] is the module name.
401         }
402     }
403     if (module != nullptr)
404     {
405         if (singleModule_ == nullptr)
406         {
407             programContext_.setDisplayName(binaryName_ + " " + module->name());
408         }
409         // Recognize the common options also after the module name.
410         // TODO: It could be nicer to only recognize -h/-hidden if module is not
411         // null.
412         CommandLineParser(optionsHolder->options())
413             .allowPositionalArguments(true)
414             .skipUnknown(true).parse(argc, *argv);
415     }
416     if (!optionsHolder->finishOptions())
417     {
418         return nullptr;
419     }
420     // If no module specified and no other action, show the help.
421     // Also explicitly specifying -h for the wrapper binary goes here.
422     if (module == nullptr || optionsHolder->shouldShowHelp())
423     {
424         ensureHelpModuleExists();
425         if (module != nullptr)
426         {
427             helpModule_->setModuleOverride(*module);
428         }
429         *argc  = 1;
430         module = helpModule_;
431     }
432     if (module == helpModule_)
433     {
434         helpModule_->setShowHidden(optionsHolder->shouldShowHidden());
435     }
436     return module;
437 }
438
439 void CommandLineModuleManager::Impl::printThanks(FILE *fp)
440 {
441     fprintf(fp, "\n%s\n\n", getCoolQuote().c_str());
442 }
443
444 /********************************************************************
445  * CommandLineModuleManager
446  */
447
448 CommandLineModuleManager::CommandLineModuleManager(
449         const char *binaryName, CommandLineProgramContext *programContext)
450     : impl_(new Impl(binaryName, programContext))
451 {
452 }
453
454 CommandLineModuleManager::~CommandLineModuleManager()
455 {
456 }
457
458 void CommandLineModuleManager::setQuiet(bool bQuiet)
459 {
460     impl_->bQuiet_ = bQuiet;
461 }
462
463 void CommandLineModuleManager::setOutputRedirector(
464         IFileOutputRedirector *output)
465 {
466     impl_->ensureHelpModuleExists();
467     impl_->helpModule_->setOutputRedirector(output);
468 }
469
470 void CommandLineModuleManager::setSingleModule(ICommandLineModule *module)
471 {
472     impl_->singleModule_ = module;
473 }
474
475 void CommandLineModuleManager::addModule(CommandLineModulePointer module)
476 {
477     impl_->addModule(std::move(module));
478 }
479
480 void CommandLineModuleManager::addModuleCMain(
481         const char *name, const char *shortDescription,
482         CMainFunction mainFunction)
483 {
484     CommandLineModulePointer module(
485             new CMainCommandLineModule(name, shortDescription, mainFunction,
486                                        nullptr));
487     addModule(std::move(module));
488 }
489
490 void CommandLineModuleManager::addModuleCMainWithSettings(
491         const char *name, const char *shortDescription,
492         CMainFunction mainFunction, InitSettingsFunction settingsFunction)
493 {
494     CommandLineModulePointer module(
495             new CMainCommandLineModule(name, shortDescription, mainFunction,
496                                        settingsFunction));
497     addModule(std::move(module));
498 }
499
500 CommandLineModuleGroup CommandLineModuleManager::addModuleGroup(
501         const char *title)
502 {
503     const char *const                 binaryName = impl_->binaryName_.c_str();
504     CommandLineModuleGroupDataPointer group(
505             new CommandLineModuleGroupData(impl_->modules_, binaryName, title));
506     impl_->moduleGroups_.push_back(std::move(group));
507     return CommandLineModuleGroup(impl_->moduleGroups_.back().get());
508 }
509
510 void CommandLineModuleManager::addHelpTopic(HelpTopicPointer topic)
511 {
512     impl_->ensureHelpModuleExists();
513     impl_->helpModule_->addTopic(std::move(topic), true);
514 }
515
516 int CommandLineModuleManager::run(int argc, char *argv[])
517 {
518     ICommandLineModule            *module;
519     const bool                     bMaster = (gmx_node_rank() == 0);
520     bool                           bQuiet  = impl_->bQuiet_ || !bMaster;
521     CommandLineCommonOptionsHolder optionsHolder;
522     try
523     {
524         optionsHolder.initOptions();
525         module = impl_->processCommonOptions(&optionsHolder, &argc, &argv);
526     }
527     catch (const std::exception &)
528     {
529         bQuiet |= optionsHolder.shouldBeQuiet();
530         if (!bQuiet)
531         {
532             printBinaryInformation(stderr, impl_->programContext_,
533                                    optionsHolder.binaryInfoSettings());
534         }
535         throw;
536     }
537     bQuiet |= optionsHolder.shouldBeQuiet();
538     if (!bQuiet)
539     {
540         FILE *out = optionsHolder.startupInfoFile();
541         printBinaryInformation(out, impl_->programContext_,
542                                optionsHolder.binaryInfoSettings());
543         fprintf(out, "\n");
544     }
545     if (module == nullptr)
546     {
547         return 0;
548     }
549
550     CommandLineModuleSettings settings;
551     module->init(&settings);
552     optionsHolder.adjustFromSettings(settings);
553
554     gmx_set_max_backup_count(optionsHolder.shouldBackup() ? -1 : 0);
555
556     // Open the debug file.
557     if (optionsHolder.debugLevel() > 0)
558     {
559         std::string filename(impl_->programContext_.programName());
560         if (gmx_node_num() > 1)
561         {
562             filename.append(formatString("%d", gmx_node_rank()));
563         }
564         filename.append(".debug");
565
566         fprintf(stderr, "Will write debug log file: %s\n", filename.c_str());
567         gmx_init_debug(optionsHolder.debugLevel(), filename.c_str());
568     }
569     // Set the nice level unless disabled in the configuration.
570     if (optionsHolder.niceLevel() != 0)
571     {
572         static bool bNiceSet = false; // Only set it once.
573         if (!bNiceSet)
574         {
575             // TODO: Diagnostic if this fails and the user explicitly requested it.
576             gmx_set_nice(optionsHolder.niceLevel());
577             bNiceSet = true;
578         }
579     }
580     if (optionsHolder.enableFPExceptions())
581     {
582         //TODO: currently it is always enabled for mdrun (verlet) and tests.
583         gmx_feenableexcept();
584     }
585
586     int rc = 0;
587     if (!(module == impl_->helpModule_ && !bMaster))
588     {
589         rc = module->run(argc, argv);
590     }
591     if (!bQuiet)
592     {
593         impl_->printThanks(stderr);
594     }
595     return rc;
596 }
597
598 // static
599 int CommandLineModuleManager::runAsMainSingleModule(
600         int argc, char *argv[], ICommandLineModule *module)
601 {
602     CommandLineProgramContext &programContext = gmx::initForCommandLine(&argc, &argv);
603     try
604     {
605         CommandLineModuleManager manager(nullptr, &programContext);
606         manager.setSingleModule(module);
607         int rc = manager.run(argc, argv);
608         gmx::finalizeForCommandLine();
609         return rc;
610     }
611     catch (const std::exception &ex)
612     {
613         printFatalErrorMessage(stderr, ex);
614         return processExceptionAtExitForCommandLine(ex);
615     }
616 }
617
618 // static
619 int CommandLineModuleManager::runAsMainCMain(
620         int argc, char *argv[], CMainFunction mainFunction)
621 {
622     CMainCommandLineModule module(argv[0], nullptr, mainFunction, nullptr);
623     return runAsMainSingleModule(argc, argv, &module);
624 }
625
626 // static
627 int CommandLineModuleManager::runAsMainCMainWithSettings(
628         int argc, char *argv[], CMainFunction mainFunction,
629         InitSettingsFunction settingsFunction)
630 {
631     CMainCommandLineModule module(argv[0], nullptr, mainFunction, settingsFunction);
632     return runAsMainSingleModule(argc, argv, &module);
633 }
634
635 /********************************************************************
636  * CommandLineModuleGroupData
637  */
638
639 void CommandLineModuleGroupData::addModule(const char *name,
640                                            const char *description)
641 {
642     CommandLineModuleMap::const_iterator moduleIter = allModules_.find(name);
643     GMX_RELEASE_ASSERT(moduleIter != allModules_.end(),
644                        "Non-existent module added to a group");
645     if (description == nullptr)
646     {
647         description = moduleIter->second->shortDescription();
648         GMX_RELEASE_ASSERT(description != nullptr,
649                            "Module without a description added to a group");
650     }
651     std::string       tag(formatString("%s-%s", binaryName_, name));
652     modules_.push_back(std::make_pair(tag, description));
653 }
654
655 /********************************************************************
656  * CommandLineModuleGroup
657  */
658
659 void CommandLineModuleGroup::addModule(const char *name)
660 {
661     impl_->addModule(name, nullptr);
662 }
663
664 void CommandLineModuleGroup::addModuleWithDescription(const char *name,
665                                                       const char *description)
666 {
667     impl_->addModule(name, description);
668 }
669
670 } // namespace gmx