Remove gmx::File (except for File::exists())
[alexxy/gromacs.git] / src / gromacs / commandline / cmdlineprogramcontext.cpp
index 5ca4807516b3216bbabd20fcff6bcceec9fa14aa..84204246873d172544060c5df7002ac111816541 100644 (file)
@@ -1,7 +1,7 @@
 /*
  * This file is part of the GROMACS molecular simulation package.
  *
- * Copyright (c) 2012,2013,2014, by the GROMACS development team, led by
+ * Copyright (c) 2012,2013,2014,2015, by the GROMACS development team, led by
  * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
  * and including many others, as listed in the AUTHORS file in the
  * top-level source directory and at http://www.gromacs.org.
  */
 /*! \internal \file
  * \brief
- * Implements gmx::ProgramInfo.
+ * Implements gmx::CommandLineProgramContext.
+ *
+ * See \linktodevmanual{relocatable-binaries,developer guide section on
+ * relocatable binaries} for explanation of the searching logic.
  *
  * \author Teemu Murtola <teemu.murtola@gmail.com>
  * \ingroup module_commandline
  */
+#include "gmxpre.h"
+
 #include "cmdlineprogramcontext.h"
 
-#ifdef HAVE_CONFIG_H
 #include "config.h"
-#endif
 
 #include <cstdlib>
 #include <cstring>
 
 #include <boost/scoped_ptr.hpp>
 
-#include "gromacs/legacyheaders/thread_mpi/mutex.h"
+#include "thread_mpi/mutex.h"
 
+#include "buildinfo.h"
 #include "gromacs/utility/exceptions.h"
-#include "gromacs/utility/file.h"
 #include "gromacs/utility/gmxassert.h"
 #include "gromacs/utility/path.h"
 #include "gromacs/utility/stringutil.h"
@@ -87,7 +90,7 @@ std::string quoteIfNecessary(const char *str)
  * Default implementation for ExecutableEnvironmentInterface.
  *
  * Used if ExecutableEnvironmentInterface is not explicitly provided when
- * constructing ProgramInfo.
+ * constructing CommandLineProgramContext.
  */
 class DefaultExecutableEnvironment : public ExecutableEnvironmentInterface
 {
@@ -164,42 +167,175 @@ std::string findFullBinaryPath(const std::string                    &invokedName
     return searchName;
 }
 
+/*! \brief
+ * Returns whether given path contains files from `share/top/`.
+ *
+ * Only checks for a single file that has an uncommon enough name.
+ */
+bool isAcceptableLibraryPath(const std::string &path)
+{
+    return Path::exists(Path::join(path, "gurgle.dat"));
+}
+
+/*! \brief
+ * Returns whether given path prefix contains files from `share/top/`.
+ *
+ * \param[in]  path   Path prefix to check.
+ * \returns  `true` if \p path contains the data files.
+ *
+ * Checks whether \p path could be the installation prefix where `share/top/`
+ * files have been installed:  appends the relative installation path of the
+ * data files and calls isAcceptableLibraryPath().
+ */
+bool isAcceptableLibraryPathPrefix(const std::string &path)
+{
+    std::string testPath = Path::join(path, DATA_INSTALL_DIR, "top");
+    if (isAcceptableLibraryPath(testPath))
+    {
+        return true;
+    }
+    return false;
+}
+
+/*! \brief
+ * Returns a fallback installation prefix path.
+ *
+ * Checks a few standard locations for the data files before returning a
+ * configure-time hard-coded path.  The hard-coded path is preferred if it
+ * actually contains the data files, though.
+ */
+std::string findFallbackInstallationPrefixPath()
+{
+#ifndef GMX_NATIVE_WINDOWS
+    if (!isAcceptableLibraryPathPrefix(CMAKE_INSTALL_PREFIX))
+    {
+        if (isAcceptableLibraryPathPrefix("/usr/local"))
+        {
+            return "/usr/local";
+        }
+        if (isAcceptableLibraryPathPrefix("/usr"))
+        {
+            return "/usr";
+        }
+        if (isAcceptableLibraryPathPrefix("/opt"))
+        {
+            return "/opt";
+        }
+    }
+#endif
+    return CMAKE_INSTALL_PREFIX;
+}
+
+/*! \brief
+ * Generic function to find data files based on path of the binary.
+ *
+ * \param[in]  binaryPath     Absolute path to the binary.
+ * \param[out] bSourceLayout  Set to `true` if the binary is run from
+ *     the build tree and the original source directory can be found.
+ * \returns  Path to the `share/top/` data files.
+ *
+ * The search based on the path only works if the binary is in the same
+ * relative path as the installed \Gromacs binaries.  If the binary is
+ * somewhere else, a hard-coded fallback is used.  This doesn't work if the
+ * binaries are somewhere else than the path given during configure time...
+ *
+ * Extra logic is present to allow running binaries from the build tree such
+ * that they use up-to-date data files from the source tree.
+ */
+std::string findInstallationPrefixPath(const std::string &binaryPath,
+                                       bool              *bSourceLayout)
+{
+    *bSourceLayout = false;
+    // Don't search anything if binary cannot be found.
+    if (Path::exists(binaryPath))
+    {
+        // Remove the executable name.
+        std::string searchPath = Path::getParentPath(binaryPath);
+        // If running directly from the build tree, try to use the source
+        // directory.
+#if (defined CMAKE_SOURCE_DIR && defined CMAKE_BINARY_DIR)
+        std::string buildBinPath;
+#ifdef CMAKE_INTDIR /*In multi-configuration build systems the output subdirectory*/
+        buildBinPath = Path::join(CMAKE_BINARY_DIR, "bin", CMAKE_INTDIR);
+#else
+        buildBinPath = Path::join(CMAKE_BINARY_DIR, "bin");
+#endif
+        if (Path::isEquivalent(searchPath, buildBinPath))
+        {
+            std::string testPath = Path::join(CMAKE_SOURCE_DIR, "share/top");
+            if (isAcceptableLibraryPath(testPath))
+            {
+                *bSourceLayout = true;
+                return CMAKE_SOURCE_DIR;
+            }
+        }
+#endif
+
+        // Use the executable path to (try to) find the library dir.
+        // TODO: Consider only going up exactly the required number of levels.
+        while (!searchPath.empty())
+        {
+            if (isAcceptableLibraryPathPrefix(searchPath))
+            {
+                return searchPath;
+            }
+            searchPath = Path::getParentPath(searchPath);
+        }
+    }
+
+    // End of smart searching. If we didn't find it in our parent tree,
+    // or if the program name wasn't set, return a fallback.
+    return findFallbackInstallationPrefixPath();
+}
+
 //! \}
 
 }   // namespace
 
 /********************************************************************
- * ProgramInfo::Impl
+ * CommandLineProgramContext::Impl
  */
 
-class ProgramInfo::Impl
+class CommandLineProgramContext::Impl
 {
     public:
         Impl();
         Impl(int argc, const char *const argv[],
              ExecutableEnvironmentPointer env);
 
+        /*! \brief
+         * Finds the full binary path if it isn't searched yet.
+         *
+         * Sets \a fullBinaryPath_ if it isn't set yet.
+         *
+         * The \a binaryPathMutex_ should be locked by the caller before
+         * calling this function.
+         */
+        void findBinaryPath() const;
+
         ExecutableEnvironmentPointer  executableEnv_;
         std::string                   invokedName_;
         std::string                   programName_;
         std::string                   displayName_;
         std::string                   commandLine_;
         mutable std::string           fullBinaryPath_;
+        mutable std::string           installationPrefix_;
+        mutable bool                  bSourceLayout_;
         mutable tMPI::mutex           binaryPathMutex_;
 };
 
-ProgramInfo::Impl::Impl()
-    : programName_("GROMACS")
+CommandLineProgramContext::Impl::Impl()
+    : programName_("GROMACS"), bSourceLayout_(false)
 {
 }
 
-ProgramInfo::Impl::Impl(int argc, const char *const argv[],
-                        ExecutableEnvironmentPointer env)
-    : executableEnv_(move(env))
+CommandLineProgramContext::Impl::Impl(int argc, const char *const argv[],
+                                      ExecutableEnvironmentPointer env)
+    : executableEnv_(env), bSourceLayout_(false)
 {
-    invokedName_          = (argc != 0 ? argv[0] : "");
-    programName_          = Path::splitToPathAndFilename(invokedName_).second;
-    programName_          = stripSuffixIfPresent(programName_, ".exe");
+    invokedName_ = (argc != 0 ? argv[0] : "");
+    programName_ = Path::getFilename(invokedName_);
+    programName_ = stripSuffixIfPresent(programName_, ".exe");
 
     commandLine_ = quoteIfNecessary(programName_.c_str());
     for (int i = 1; i < argc; ++i)
@@ -209,77 +345,93 @@ ProgramInfo::Impl::Impl(int argc, const char *const argv[],
     }
 }
 
+void CommandLineProgramContext::Impl::findBinaryPath() const
+{
+    if (fullBinaryPath_.empty())
+    {
+        fullBinaryPath_ = findFullBinaryPath(invokedName_, *executableEnv_);
+        fullBinaryPath_ = Path::normalize(Path::resolveSymlinks(fullBinaryPath_));
+        // TODO: Investigate/Consider using a dladdr()-based solution.
+        // Potentially less portable, but significantly simpler, and also works
+        // with user binaries even if they are located in some arbitrary location,
+        // as long as shared libraries are used.
+    }
+}
+
 /********************************************************************
- * ProgramInfo
+ * CommandLineProgramContext
  */
 
-ProgramInfo::ProgramInfo()
+CommandLineProgramContext::CommandLineProgramContext()
     : impl_(new Impl)
 {
 }
 
-ProgramInfo::ProgramInfo(const char *binaryName)
-    : impl_(new Impl(1, &binaryName,
-                     DefaultExecutableEnvironment::create()))
+CommandLineProgramContext::CommandLineProgramContext(const char *binaryName)
+    : impl_(new Impl(1, &binaryName, DefaultExecutableEnvironment::create()))
 {
 }
 
-ProgramInfo::ProgramInfo(int argc, const char *const argv[])
-    : impl_(new Impl(argc, argv,
-                     DefaultExecutableEnvironment::create()))
+CommandLineProgramContext::CommandLineProgramContext(
+        int argc, const char *const argv[])
+    : impl_(new Impl(argc, argv, DefaultExecutableEnvironment::create()))
 {
 }
 
-ProgramInfo::ProgramInfo(int argc, const char *const argv[],
-                         ExecutableEnvironmentPointer env)
-    : impl_(new Impl(argc, argv, move(env)))
+CommandLineProgramContext::CommandLineProgramContext(
+        int argc, const char *const argv[], ExecutableEnvironmentPointer env)
+    : impl_(new Impl(argc, argv, env))
 {
 }
 
-ProgramInfo::~ProgramInfo()
+CommandLineProgramContext::~CommandLineProgramContext()
 {
 }
 
-void ProgramInfo::setDisplayName(const std::string &name)
+void CommandLineProgramContext::setDisplayName(const std::string &name)
 {
     GMX_RELEASE_ASSERT(impl_->displayName_.empty(),
                        "Can only set display name once");
     impl_->displayName_ = name;
 }
 
-const char *ProgramInfo::programName() const
+const char *CommandLineProgramContext::programName() const
 {
     return impl_->programName_.c_str();
 }
 
-const char *ProgramInfo::displayName() const
+const char *CommandLineProgramContext::displayName() const
 {
     return impl_->displayName_.empty()
            ? impl_->programName_.c_str()
            : impl_->displayName_.c_str();
 }
 
-const char *ProgramInfo::commandLine() const
+const char *CommandLineProgramContext::commandLine() const
 {
     return impl_->commandLine_.c_str();
 }
 
-const char *ProgramInfo::fullBinaryPath() const
+const char *CommandLineProgramContext::fullBinaryPath() const
+{
+    tMPI::lock_guard<tMPI::mutex> lock(impl_->binaryPathMutex_);
+    impl_->findBinaryPath();
+    return impl_->fullBinaryPath_.c_str();
+}
+
+InstallationPrefixInfo CommandLineProgramContext::installationPrefix() const
 {
     tMPI::lock_guard<tMPI::mutex> lock(impl_->binaryPathMutex_);
-    if (impl_->fullBinaryPath_.empty())
+    if (impl_->installationPrefix_.empty())
     {
-        impl_->fullBinaryPath_ =
-            Path::normalize(
-                    Path::resolveSymlinks(
-                            findFullBinaryPath(impl_->invokedName_,
-                                               *impl_->executableEnv_)));
-        // TODO: Investigate/Consider using a dladdr()-based solution.
-        // Potentially less portable, but significantly simpler, and also works
-        // with user binaries even if they are located in some arbitrary location,
-        // as long as shared libraries are used.
+        impl_->findBinaryPath();
+        impl_->installationPrefix_ =
+            Path::normalize(findInstallationPrefixPath(impl_->fullBinaryPath_,
+                                                       &impl_->bSourceLayout_));
     }
-    return impl_->fullBinaryPath_.c_str();
+    return InstallationPrefixInfo(
+            impl_->installationPrefix_.c_str(),
+            impl_->bSourceLayout_);
 }
 
 } // namespace gmx