/*
* 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.
* \brief
* 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"
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
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_;
};
CommandLineProgramContext::Impl::Impl()
- : programName_("GROMACS")
+ : programName_("GROMACS"), bSourceLayout_(false)
{
}
CommandLineProgramContext::Impl::Impl(int argc, const char *const argv[],
ExecutableEnvironmentPointer env)
- : executableEnv_(move(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)
}
}
+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.
+ }
+}
+
/********************************************************************
* CommandLineProgramContext
*/
CommandLineProgramContext::CommandLineProgramContext(
int argc, const char *const argv[], ExecutableEnvironmentPointer env)
- : impl_(new Impl(argc, argv, move(env)))
+ : impl_(new Impl(argc, argv, env))
{
}
const char *CommandLineProgramContext::fullBinaryPath() const
{
tMPI::lock_guard<tMPI::mutex> lock(impl_->binaryPathMutex_);
- if (impl_->fullBinaryPath_.empty())
+ impl_->findBinaryPath();
+ return impl_->fullBinaryPath_.c_str();
+}
+
+InstallationPrefixInfo CommandLineProgramContext::installationPrefix() const
+{
+ tMPI::lock_guard<tMPI::mutex> lock(impl_->binaryPathMutex_);
+ 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