Detect module name from symlinks.
authorTeemu Murtola <teemu.murtola@gmail.com>
Wed, 9 May 2012 07:12:52 +0000 (10:12 +0300)
committerTeemu Murtola <teemu.murtola@gmail.com>
Wed, 9 May 2012 07:18:02 +0000 (10:18 +0300)
This makes it possible to create, e.g., a symlink named g_select and
point it to g_ana; executing the symlink will work exactly like 'g_ana
select'.

Closes #672.

Change-Id: Ia27069466bea19b1203370c35b9ecdacfe0cdd98

src/config.h.cmakein
src/gromacs/commandline/cmdlinemodulemanager.cpp
src/gromacs/commandline/cmdlinemodulemanager.h
src/gromacs/commandline/tests/cmdlinemodulemanager.cpp
src/gromacs/utility/path.cpp
src/gromacs/utility/path.h
src/programs/g_ana/g_ana.cpp

index 88ea0967c88b347cdbbfcf85978a8b241f87981d..2b5bdd6ff5fadef8064354db8199f19a42ecf59d 100644 (file)
@@ -31,6 +31,9 @@
 /* User doing build */
 #cmakedefine BUILD_USER "@BUILD_USER@"
 
+/* Binary suffix for the created binaries */
+#define GMX_BINARY_SUFFIX "@GMX_BINARY_SUFFIX@"
+
 /* Turn off water-water neighborlist optimization only */
 #cmakedefine DISABLE_WATERWATER_NLIST
 
index 13a5e96a0266100e8cec46a0f24cf94fefc9a31f..646c5d7b88849197261b74306de35b138dc8b349 100644 (file)
  */
 #include "cmdlinemodulemanager.h"
 
+// For GMX_BINARY_SUFFIX
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
 #include <cstdio>
+#include <cstring>
 
 #include <map>
 #include <string>
@@ -47,6 +53,7 @@
 
 #include "gromacs/commandline/cmdlinemodule.h"
 #include "gromacs/utility/gmxassert.h"
+#include "gromacs/utility/path.h"
 
 namespace gmx
 {
@@ -66,6 +73,37 @@ class CommandLineModuleManager::Impl
         //! Container for mapping module names to module objects.
         typedef std::map<std::string, CommandLineModulePointer> ModuleMap;
 
+        /*! \brief
+         * Initializes the implementation class.
+         *
+         * \param[in] realBinaryName  Name of the binary that this manager runs.
+         */
+        explicit Impl(const char *realBinaryName);
+
+        /*! \brief
+         * Finds a module that matches a name.
+         *
+         * \param[in] name  Module name to find.
+         * \returns   Iterator to the found module, or
+         *      \c modules_.end() if not found.
+         *
+         * Does not throw.
+         */
+        ModuleMap::const_iterator findModuleByName(const std::string &name) const;
+        /*! \brief
+         * Finds a module that the name of the binary.
+         *
+         * \param[in] argv0  argv[0] passed to the program.
+         * \throws    std::bad_alloc if out of memory.
+         * \returns   Iterator to the found module, or
+         *      \c modules_.end() if not found.
+         *
+         * Checks whether the program is invoked through a symlink whose name
+         * is different from \a realBinaryName_, and if so, checks if a module
+         * name matches the name of the symlink.
+         */
+        ModuleMap::const_iterator findModuleFromBinaryName(const std::string &argv0) const;
+
         //! Prints usage message to stderr.
         void printUsage(bool bModuleList) const;
         //! Prints the list of modules to stderr.
@@ -77,8 +115,54 @@ class CommandLineModuleManager::Impl
          * Owns the contained modules.
          */
         ModuleMap               modules_;
+        //! Real name of the binary that is running (without suffixes).
+        std::string             realBinaryName_;
 };
 
+CommandLineModuleManager::Impl::Impl(const char *realBinaryName)
+    : realBinaryName_(realBinaryName)
+{
+}
+
+CommandLineModuleManager::Impl::ModuleMap::const_iterator
+CommandLineModuleManager::Impl::findModuleByName(const std::string &name) const
+{
+    // TODO: Accept unambiguous prefixes?
+    return modules_.find(name);
+}
+
+CommandLineModuleManager::Impl::ModuleMap::const_iterator
+CommandLineModuleManager::Impl::findModuleFromBinaryName(const std::string &argv0) const
+{
+    // TODO: Move this logic into a common place in utility/ and remove
+    // dependency on config.h from this file.
+    // (most natural place would be in a location that wraps Program() etc.)
+    std::string binaryName = Path::splitToPathAndFilename(argv0).second;
+    if (binaryName.length() >= 4
+        && binaryName.compare(binaryName.length() - 4, 4, ".exe") == 0)
+    {
+        binaryName.erase(binaryName.length() - 4);
+    }
+#ifdef GMX_BINARY_SUFFIX
+    size_t suffixLength = std::strlen(GMX_BINARY_SUFFIX);
+    if (suffixLength > 0 && binaryName.length() >= suffixLength
+        && binaryName.compare(binaryName.length() - suffixLength, suffixLength,
+                              GMX_BINARY_SUFFIX) == 0)
+    {
+        binaryName.erase(binaryName.length() - suffixLength);
+    }
+#endif
+    if (binaryName == realBinaryName_)
+    {
+        return modules_.end();
+    }
+    if (binaryName.compare(0, 2, "g_") == 0)
+    {
+        binaryName.erase(0, 2);
+    }
+    return findModuleByName(binaryName);
+}
+
 void CommandLineModuleManager::Impl::printUsage(bool bModuleList) const
 {
     const char *program = ShortProgram();
@@ -174,8 +258,8 @@ int CommandLineHelpModule::run(int argc, char *argv[])
  * CommandLineModuleManager
  */
 
-CommandLineModuleManager::CommandLineModuleManager()
-    : impl_(new Impl)
+CommandLineModuleManager::CommandLineModuleManager(const char *realBinaryName)
+    : impl_(new Impl(realBinaryName))
 {
     addModule(CommandLineModulePointer(new internal::CommandLineHelpModule(*this)));
 }
@@ -194,14 +278,20 @@ void CommandLineModuleManager::addModule(CommandLineModulePointer module)
 
 int CommandLineModuleManager::run(int argc, char *argv[])
 {
-    if (argc < 2)
+    int argOffset = 0;
+    Impl::ModuleMap::const_iterator module
+        = impl_->findModuleFromBinaryName(argv[0]);
+    if (module == impl_->modules_.end())
     {
-        fprintf(stderr, "\n");
-        impl_->printUsage(false);
-        return 2;
+        if (argc < 2)
+        {
+            fprintf(stderr, "\n");
+            impl_->printUsage(false);
+            return 2;
+        }
+        module = impl_->findModuleByName(argv[1]);
+        argOffset = 1;
     }
-    // TODO: Accept unambiguous prefixes?
-    Impl::ModuleMap::const_iterator module = impl_->modules_.find(argv[1]);
     if (module == impl_->modules_.end())
     {
         fprintf(stderr, "\n");
@@ -209,7 +299,7 @@ int CommandLineModuleManager::run(int argc, char *argv[])
         impl_->printUsage(true);
         return 2;
     }
-    return module->second->run(argc - 1, argv + 1);
+    return module->second->run(argc - argOffset, argv + argOffset);
 }
 
 } // namespace gmx
index 2db11d3d2ed36fc8fa7d59eb1daeb448de8e7515..eb3499b9a6b107121f60f0f8c05025fc3ad38852 100644 (file)
@@ -67,7 +67,7 @@ main(int argc, char *argv[])
     CopyRight(stderr, argv[0]);
     try
     {
-        gmx::CommandLineModuleManager manager;
+        gmx::CommandLineModuleManager manager("g_ana");
         // <register all necessary modules>
         return manager.run(argc, argv);
     }
@@ -85,7 +85,17 @@ main(int argc, char *argv[])
 class CommandLineModuleManager
 {
     public:
-        CommandLineModuleManager();
+        /*! \brief
+         * Initializes a command-line module manager.
+         *
+         * \param[in] realBinaryName  Name of the binary that this manager runs
+         *     (without Gromacs binary suffix or .exe on Windows).
+         * \throws    std::bad_alloc if out of memory.
+         *
+         * The binary name is used to detect when the binary is run through a
+         * symlink, and automatically invoke a matching module in such a case.
+         */
+        explicit CommandLineModuleManager(const char *realBinaryName);
         ~CommandLineModuleManager();
 
         /*! \brief
index 19789066f7f1e20fc4e091d6a35997d76f0c9d00..85c2382b681de7ba561a448bd5642ee5c8169160 100644 (file)
  * \author Teemu Murtola <teemu.murtola@cbr.su.se>
  * \ingroup module_commandline
  */
+// For GMX_BINARY_SUFFIX
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
 #include <vector>
 
 #include <gmock/gmock.h>
@@ -84,11 +89,18 @@ MockModule::MockModule(const char *name, const char *description)
 class CommandLineModuleManagerTest : public ::testing::Test
 {
     public:
+        CommandLineModuleManagerTest();
+
         MockModule &addModule(const char *name, const char *description);
 
         gmx::CommandLineModuleManager manager_;
 };
 
+CommandLineModuleManagerTest::CommandLineModuleManagerTest()
+    : manager_("g_test")
+{
+}
+
 MockModule &
 CommandLineModuleManagerTest::addModule(const char *name, const char *description)
 {
@@ -104,7 +116,43 @@ CommandLineModuleManagerTest::addModule(const char *name, const char *descriptio
 TEST_F(CommandLineModuleManagerTest, RunsModule)
 {
     const char *const cmdline[] = {
-        "test", "module", "-flag", "yes"
+        "g_test", "module", "-flag", "yes"
+    };
+    gmx::test::CommandLine args(cmdline);
+    MockModule &mod1 = addModule("module", "First module");
+    addModule("other", "Second module");
+    using ::testing::_;
+    using ::testing::Args;
+    using ::testing::ElementsAreArray;
+    EXPECT_CALL(mod1, run(_, _))
+        .With(Args<1, 0>(ElementsAreArray(args.argv() + 1, args.argc() - 1)));
+    int rc = 0;
+    ASSERT_NO_THROW(rc = manager_.run(args.argc(), args.argv()));
+    ASSERT_EQ(0, rc);
+}
+
+TEST_F(CommandLineModuleManagerTest, RunsModuleBasedOnBinaryName)
+{
+    const char *const cmdline[] = {
+        "g_module", "-flag", "yes"
+    };
+    gmx::test::CommandLine args(cmdline);
+    MockModule &mod1 = addModule("module", "First module");
+    addModule("other", "Second module");
+    using ::testing::_;
+    using ::testing::Args;
+    using ::testing::ElementsAreArray;
+    EXPECT_CALL(mod1, run(_, _))
+        .With(Args<1, 0>(ElementsAreArray(args.argv(), args.argc())));
+    int rc = 0;
+    ASSERT_NO_THROW(rc = manager_.run(args.argc(), args.argv()));
+    ASSERT_EQ(0, rc);
+}
+
+TEST_F(CommandLineModuleManagerTest, RunsModuleBasedOnBinaryNameWithPathAndSuffix)
+{
+    const char *const cmdline[] = {
+        "/usr/local/gromacs/bin/g_module" GMX_BINARY_SUFFIX ".exe", "-flag", "yes"
     };
     gmx::test::CommandLine args(cmdline);
     MockModule &mod1 = addModule("module", "First module");
@@ -112,6 +160,24 @@ TEST_F(CommandLineModuleManagerTest, RunsModule)
     using ::testing::_;
     using ::testing::Args;
     using ::testing::ElementsAreArray;
+    EXPECT_CALL(mod1, run(_, _))
+        .With(Args<1, 0>(ElementsAreArray(args.argv(), args.argc())));
+    int rc = 0;
+    ASSERT_NO_THROW(rc = manager_.run(args.argc(), args.argv()));
+    ASSERT_EQ(0, rc);
+}
+
+TEST_F(CommandLineModuleManagerTest, HandlesConflictingBinaryAndModuleNames)
+{
+    const char *const cmdline[] = {
+        "g_test", "test", "-flag", "yes"
+    };
+    gmx::test::CommandLine args(cmdline);
+    MockModule &mod1 = addModule("test", "Test module");
+    addModule("other", "Second module");
+    using ::testing::_;
+    using ::testing::Args;
+    using ::testing::ElementsAreArray;
     EXPECT_CALL(mod1, run(_, _))
         .With(Args<1, 0>(ElementsAreArray(args.argv() + 1, args.argc() - 1)));
     int rc = 0;
index 9f822cd7e86e3ae8b483f9879a3dc323404f6ad4..6815151226ba1a4d3325dc55015808ac1f907df6 100644 (file)
@@ -47,6 +47,7 @@
 #endif
 
 static const char cDirSeparator = '/';
+static const char cDirSeparators[] = "/\\";
 
 namespace gmx
 {
@@ -67,6 +68,17 @@ std::string Path::join(const std::string &path1,
     return path1 + cDirSeparator + path2 + cDirSeparator + path3;
 }
 
+std::pair<std::string, std::string>
+Path::splitToPathAndFilename(const std::string &path)
+{
+    size_t pos = path.find_last_of(cDirSeparators);
+    if (pos == std::string::npos)
+    {
+        return std::make_pair(std::string(), path);
+    }
+    return std::make_pair(path.substr(0, pos), path.substr(pos+1));
+}
+
 
 int Directory::create(const char *path)
 {
index 27f22bb39e564696d6601b5297cb13a626f26b43..863abc4b4c7537b5b51571bc60d6badf9893cc41 100644 (file)
@@ -39,6 +39,7 @@
 #define GMX_UTILITY_PATH_H
 
 #include <string>
+#include <utility>
 
 namespace gmx
 {
@@ -51,6 +52,8 @@ class Path
         static std::string join(const std::string &path1,
                                 const std::string &path2,
                                 const std::string &path3);
+        static std::pair<std::string, std::string>
+        splitToPathAndFilename(const std::string &path);
 
     private:
         // Disallow instantiation.
index bb3021f9c2a67d9bb94c5ce9412dcb1fb7ccc0d1..7b5183acae429bbcb4c4ffa8c4c3511c48c8c5f0 100644 (file)
@@ -45,7 +45,7 @@ main(int argc, char *argv[])
     CopyRight(stderr, argv[0]);
     try
     {
-        gmx::CommandLineModuleManager manager;
+        gmx::CommandLineModuleManager manager("g_ana");
         registerTrajectoryAnalysisModules(&manager);
         return manager.run(argc, argv);
     }