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