7cf4f002d757eca5df0dc5ed580bfcc5d5a298e0
[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,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.
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,
108                            const char*          shortDescription,
109                            CMainFunction        mainFunction,
110                            InitSettingsFunction settingsFunction) :
111         name_(name),
112         shortDescription_(shortDescription),
113         mainFunction_(mainFunction),
114         settingsFunction_(settingsFunction)
115     {
116     }
117
118     const char* name() const override { return name_; }
119     const char* shortDescription() const override { return shortDescription_; }
120
121     void init(CommandLineModuleSettings* settings) override
122     {
123         if (settingsFunction_ != nullptr)
124         {
125             settingsFunction_(settings);
126         }
127     }
128     int  run(int argc, char* argv[]) override { return mainFunction_(argc, argv); }
129     void writeHelp(const CommandLineHelpContext& context) const override
130     {
131         writeCommandLineHelpCMain(context, name_, mainFunction_);
132     }
133
134 private:
135     const char*          name_;
136     const char*          shortDescription_;
137     CMainFunction        mainFunction_;
138     InitSettingsFunction settingsFunction_;
139 };
140
141 //! \}
142
143 } // namespace
144
145 /********************************************************************
146  * CommandLineCommonOptionsHolder
147  */
148
149 CommandLineCommonOptionsHolder::CommandLineCommonOptionsHolder() :
150     bHelp_(false),
151     bHidden_(false),
152     bQuiet_(false),
153     bVersion_(false),
154     bCopyright_(true),
155     niceLevel_(19),
156     bNiceSet_(false),
157     bBackup_(true),
158     bFpexcept_(false),
159     debugLevel_(0)
160 {
161     binaryInfoSettings_.copyright(true);
162 }
163
164 CommandLineCommonOptionsHolder::~CommandLineCommonOptionsHolder() {}
165
166 void CommandLineCommonOptionsHolder::initOptions()
167 {
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"));
173     options_.addOption(
174             BooleanOption("version").store(&bVersion_).description("Print extended version information and quit"));
175     options_.addOption(
176             BooleanOption("copyright").store(&bCopyright_).description("Print copyright information on startup"));
177     options_.addOption(IntegerOption("nice")
178                                .store(&niceLevel_)
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"));
183     options_.addOption(
184             BooleanOption("fpexcept").store(&bFpexcept_).hidden().description("Enable floating-point exceptions"));
185     options_.addOption(IntegerOption("debug")
186                                .store(&debugLevel_)
187                                .hidden()
188                                .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(const CommandLineModuleSettings& settings)
204 {
205     if (!bNiceSet_)
206     {
207         niceLevel_ = settings.defaultNiceLevel();
208     }
209 }
210
211 /********************************************************************
212  * CommandLineModuleManager::Impl
213  */
214
215 /*! \internal \brief
216  * Private implementation class for CommandLineModuleManager.
217  *
218  * \ingroup module_commandline
219  */
220 class CommandLineModuleManager::Impl
221 {
222 public:
223     /*! \brief
224      * Initializes the implementation class.
225      *
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.
229      */
230     Impl(const char* binaryName, CommandLineProgramContext* programContext);
231
232     /*! \brief
233      * Helper method that adds a given module to the module manager.
234      *
235      * \throws    std::bad_alloc if out of memory.
236      */
237     void addModule(CommandLineModulePointer module);
238     /*! \brief
239      * Creates the help module if it does not yet exist.
240      *
241      * \throws    std::bad_alloc if out of memory.
242      *
243      * This method should be called before accessing \a helpModule_.
244      */
245     void ensureHelpModuleExists();
246
247     /*! \brief
248      * Finds a module that matches a name.
249      *
250      * \param[in] name  Module name to find.
251      * \returns   Iterator to the found module, or
252      *      \c modules_.end() if not found.
253      *
254      * Does not throw.
255      */
256     CommandLineModuleMap::const_iterator findModuleByName(const std::string& name) const;
257
258     /*! \brief
259      * Processes command-line options for the wrapper binary.
260      *
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.
268      *
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.
273      */
274     ICommandLineModule* processCommonOptions(CommandLineCommonOptionsHolder* optionsHolder,
275                                              int*                            argc,
276                                              char***                         argv);
277
278     //! Prints the footer at the end of execution.
279     void printThanks(FILE* fp);
280
281     /*! \brief
282      * Maps module names to module objects.
283      *
284      * Owns the contained modules.
285      */
286     CommandLineModuleMap modules_;
287     /*! \brief
288      * List of groupings for modules for help output.
289      *
290      * Owns the contained module group data objects.
291      * CommandLineModuleGroup objects point to the data objects contained
292      * here.
293      */
294     CommandLineModuleGroupList moduleGroups_;
295     //! Information about the currently running program.
296     CommandLineProgramContext& programContext_;
297     //! Name of the binary.
298     std::string binaryName_;
299     /*! \brief
300      * Module that implements help for the binary.
301      *
302      * The pointed module is owned by the \a modules_ container.
303      */
304     CommandLineHelpModule* helpModule_;
305     //! If non-NULL, run this module in single-module mode.
306     ICommandLineModule* singleModule_;
307     //! Stores the value set with setQuiet().
308     bool bQuiet_;
309
310 private:
311     GMX_DISALLOW_COPY_AND_ASSIGN(Impl);
312 };
313
314 CommandLineModuleManager::Impl::Impl(const char* binaryName, CommandLineProgramContext* programContext) :
315     programContext_(*programContext),
316     binaryName_(binaryName != nullptr ? binaryName : ""),
317     helpModule_(nullptr),
318     singleModule_(nullptr),
319     bQuiet_(false)
320 {
321     GMX_RELEASE_ASSERT(binaryName_.find('-') == std::string::npos,
322                        "Help export does not currently work with binary names with dashes");
323 }
324
325 void CommandLineModuleManager::Impl::addModule(CommandLineModulePointer module)
326 {
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);
333 }
334
335 void CommandLineModuleManager::Impl::ensureHelpModuleExists()
336 {
337     if (helpModule_ == nullptr)
338     {
339         helpModule_ = new CommandLineHelpModule(programContext_, binaryName_, modules_, moduleGroups_);
340         addModule(CommandLineModulePointer(helpModule_));
341     }
342 }
343
344 CommandLineModuleMap::const_iterator CommandLineModuleManager::Impl::findModuleByName(const std::string& name) const
345 {
346     // TODO: Accept unambiguous prefixes?
347     return modules_.find(name);
348 }
349
350 ICommandLineModule* CommandLineModuleManager::Impl::processCommonOptions(CommandLineCommonOptionsHolder* optionsHolder,
351                                                                          int*    argc,
352                                                                          char*** argv)
353 {
354     // Check if we are directly invoking a certain module.
355     ICommandLineModule* module = singleModule_;
356
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.
359
360     if (module == nullptr)
361     {
362         // If not in single-module mode, process options to the wrapper binary.
363         // TODO: Ideally, this could be done by CommandLineParser.
364
365         // Find the module name (if any) in the arg list
366         int indexOfModuleName = 1;
367         while (indexOfModuleName < *argc && (*argv)[indexOfModuleName][0] == '-')
368         {
369             ++indexOfModuleName;
370         }
371         if (indexOfModuleName > 1)
372         {
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);
377         }
378         // If no action requested and there is a module specified, process it.
379         if (indexOfModuleName < *argc && !optionsHolder->shouldIgnoreActualModule())
380         {
381             const char*                          moduleName = (*argv)[indexOfModuleName];
382             CommandLineModuleMap::const_iterator moduleIter = findModuleByName(moduleName);
383             if (moduleIter == modules_.end())
384             {
385                 std::string message = formatString("'%s' is not a GROMACS command.", moduleName);
386                 GMX_THROW(InvalidInputError(message));
387             }
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.
393         }
394     }
395     if (module != nullptr)
396     {
397         if (singleModule_ == nullptr)
398         {
399             programContext_.setDisplayName(binaryName_ + " " + module->name());
400         }
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
403         // null.
404         CommandLineParser(optionsHolder->options()).allowPositionalArguments(true).skipUnknown(true).parse(argc, *argv);
405     }
406     if (!optionsHolder->finishOptions())
407     {
408         return nullptr;
409     }
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())
413     {
414         ensureHelpModuleExists();
415         if (module != nullptr)
416         {
417             helpModule_->setModuleOverride(*module);
418         }
419         *argc  = 1;
420         module = helpModule_;
421     }
422     if (module == helpModule_)
423     {
424         helpModule_->setShowHidden(optionsHolder->shouldShowHidden());
425     }
426     return module;
427 }
428
429 void CommandLineModuleManager::Impl::printThanks(FILE* fp)
430 {
431     fprintf(fp, "\n%s\n\n", getCoolQuote().c_str());
432 }
433
434 /********************************************************************
435  * CommandLineModuleManager
436  */
437
438 CommandLineModuleManager::CommandLineModuleManager(const char*                binaryName,
439                                                    CommandLineProgramContext* programContext) :
440     impl_(new Impl(binaryName, programContext))
441 {
442 }
443
444 CommandLineModuleManager::~CommandLineModuleManager() {}
445
446 void CommandLineModuleManager::setQuiet(bool bQuiet)
447 {
448     impl_->bQuiet_ = bQuiet;
449 }
450
451 void CommandLineModuleManager::setOutputRedirector(IFileOutputRedirector* output)
452 {
453     impl_->ensureHelpModuleExists();
454     impl_->helpModule_->setOutputRedirector(output);
455 }
456
457 void CommandLineModuleManager::setSingleModule(ICommandLineModule* module)
458 {
459     impl_->singleModule_ = module;
460 }
461
462 void CommandLineModuleManager::addModule(CommandLineModulePointer module)
463 {
464     impl_->addModule(std::move(module));
465 }
466
467 void CommandLineModuleManager::addModuleCMain(const char* name, const char* shortDescription, CMainFunction mainFunction)
468 {
469     CommandLineModulePointer module(
470             new CMainCommandLineModule(name, shortDescription, mainFunction, nullptr));
471     addModule(std::move(module));
472 }
473
474 void CommandLineModuleManager::addModuleCMainWithSettings(const char*          name,
475                                                           const char*          shortDescription,
476                                                           CMainFunction        mainFunction,
477                                                           InitSettingsFunction settingsFunction)
478 {
479     CommandLineModulePointer module(
480             new CMainCommandLineModule(name, shortDescription, mainFunction, settingsFunction));
481     addModule(std::move(module));
482 }
483
484 CommandLineModuleGroup CommandLineModuleManager::addModuleGroup(const char* title)
485 {
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());
491 }
492
493 void CommandLineModuleManager::addHelpTopic(HelpTopicPointer topic)
494 {
495     impl_->ensureHelpModuleExists();
496     impl_->helpModule_->addTopic(std::move(topic), true);
497 }
498
499 int CommandLineModuleManager::run(int argc, char* argv[])
500 {
501     ICommandLineModule*            module;
502     const bool                     bMaster = (gmx_node_rank() == 0);
503     bool                           bQuiet  = impl_->bQuiet_ || !bMaster;
504     CommandLineCommonOptionsHolder optionsHolder;
505     try
506     {
507         optionsHolder.initOptions();
508         module = impl_->processCommonOptions(&optionsHolder, &argc, &argv);
509     }
510     catch (const std::exception&)
511     {
512         bQuiet |= optionsHolder.shouldBeQuiet();
513         if (!bQuiet)
514         {
515             printBinaryInformation(stderr, impl_->programContext_, optionsHolder.binaryInfoSettings());
516         }
517         throw;
518     }
519     bQuiet |= optionsHolder.shouldBeQuiet();
520     if (!bQuiet)
521     {
522         FILE* out = optionsHolder.startupInfoFile();
523         printBinaryInformation(out, impl_->programContext_, optionsHolder.binaryInfoSettings());
524         fprintf(out, "\n");
525     }
526     if (module == nullptr)
527     {
528         return 0;
529     }
530
531     CommandLineModuleSettings settings;
532     module->init(&settings);
533     optionsHolder.adjustFromSettings(settings);
534
535     gmx_set_max_backup_count(optionsHolder.shouldBackup() ? -1 : 0);
536
537     // Open the debug file.
538     if (optionsHolder.debugLevel() > 0)
539     {
540         std::string filename(impl_->programContext_.programName());
541         if (gmx_node_num() > 1)
542         {
543             filename.append(formatString("%d", gmx_node_rank()));
544         }
545         filename.append(".debug");
546
547         fprintf(stderr, "Will write debug log file: %s\n", filename.c_str());
548         gmx_init_debug(optionsHolder.debugLevel(), filename.c_str());
549     }
550     // Set the nice level unless disabled in the configuration.
551     if (optionsHolder.niceLevel() != 0)
552     {
553         static bool bNiceSet = false; // Only set it once.
554         if (!bNiceSet)
555         {
556             // TODO: Diagnostic if this fails and the user explicitly requested it.
557             gmx_set_nice(optionsHolder.niceLevel());
558             bNiceSet = true;
559         }
560     }
561     if (optionsHolder.enableFPExceptions())
562     {
563         // TODO: currently it is always enabled for mdrun (verlet) and tests.
564         gmx_feenableexcept();
565     }
566
567     int rc = 0;
568     if (!(module == impl_->helpModule_ && !bMaster))
569     {
570         rc = module->run(argc, argv);
571     }
572     if (!bQuiet)
573     {
574         impl_->printThanks(stderr);
575     }
576     return rc;
577 }
578
579 // static
580 int CommandLineModuleManager::runAsMainSingleModule(int argc, char* argv[], ICommandLineModule* module)
581 {
582     CommandLineProgramContext& programContext = gmx::initForCommandLine(&argc, &argv);
583     try
584     {
585         CommandLineModuleManager manager(nullptr, &programContext);
586         manager.setSingleModule(module);
587         int rc = manager.run(argc, argv);
588         gmx::finalizeForCommandLine();
589         return rc;
590     }
591     catch (const std::exception& ex)
592     {
593         printFatalErrorMessage(stderr, ex);
594         return processExceptionAtExitForCommandLine(ex);
595     }
596 }
597
598 // static
599 int CommandLineModuleManager::runAsMainCMain(int argc, char* argv[], CMainFunction mainFunction)
600 {
601     CMainCommandLineModule module(argv[0], nullptr, mainFunction, nullptr);
602     return runAsMainSingleModule(argc, argv, &module);
603 }
604
605 // static
606 int CommandLineModuleManager::runAsMainCMainWithSettings(int                  argc,
607                                                          char*                argv[],
608                                                          CMainFunction        mainFunction,
609                                                          InitSettingsFunction settingsFunction)
610 {
611     CMainCommandLineModule module(argv[0], nullptr, mainFunction, settingsFunction);
612     return runAsMainSingleModule(argc, argv, &module);
613 }
614
615 /********************************************************************
616  * CommandLineModuleGroupData
617  */
618
619 void CommandLineModuleGroupData::addModule(const char* name, const char* description)
620 {
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)
624     {
625         description = moduleIter->second->shortDescription();
626         GMX_RELEASE_ASSERT(description != nullptr, "Module without a description added to a group");
627     }
628     std::string tag(formatString("%s-%s", binaryName_, name));
629     modules_.push_back(std::make_pair(tag, description));
630 }
631
632 /********************************************************************
633  * CommandLineModuleGroup
634  */
635
636 void CommandLineModuleGroup::addModule(const char* name)
637 {
638     impl_->addModule(name, nullptr);
639 }
640
641 void CommandLineModuleGroup::addModuleWithDescription(const char* name, const char* description)
642 {
643     impl_->addModule(name, description);
644 }
645
646 } // namespace gmx