Make data file path search more reusable
authorTeemu Murtola <teemu.murtola@gmail.com>
Thu, 15 Jan 2015 19:53:16 +0000 (21:53 +0200)
committerGerrit Code Review <gerrit@gerrit.gromacs.org>
Tue, 20 Jan 2015 18:56:16 +0000 (19:56 +0100)
Instead of returning the path to share/top from ProgramContextInterface,
return the installation prefix.  Make it the responsibility of the
caller to append the relative path to the data files it needs.  Clean up
config.h defines related to this to make them more generic.

This makes it possible to reuse the same logic to find files also in
other locations than share/top.  DataFileFinder is not yet similarly
reusable, but that is a topic for another change.

Change-Id: I7c7fc3730417e71ff43d1b6fadf00a0eb465d794

CMakeLists.txt
src/config.h.cmakein
src/gromacs/commandline/cmdlineprogramcontext.cpp
src/gromacs/commandline/cmdlineprogramcontext.h
src/gromacs/gmxlib/copyrite.cpp
src/gromacs/utility/datafilefinder.cpp
src/gromacs/utility/programcontext.cpp
src/gromacs/utility/programcontext.h
src/gromacs/utility/stringutil.h
src/testutils/testinit.cpp

index 0d3e6d03f12a7c9e5cef22b2bdaed03390477edc..89c7354699228445830db665e228d91142b92d7a 100644 (file)
@@ -740,6 +740,8 @@ mark_as_advanced(GMX_LIB_INSTALL_DIR GMX_DATA_INSTALL_DIR)
 # customizing the install locations.
 set(LIB_INSTALL_DIR       ${GMX_LIB_INSTALL_DIR})
 set(BIN_INSTALL_DIR       bin)
+# This variable also gets written into config.h for use in finding the data
+# directories.
 set(DATA_INSTALL_DIR      share/${GMX_DATA_INSTALL_DIR})
 set(MAN_INSTALL_DIR       share/man)
 # If the nesting level wrt. the installation root is changed,
@@ -749,11 +751,6 @@ set(CMAKE_INSTALL_DIR     share/cmake)
 set(PKGCONFIG_INSTALL_DIR ${LIB_INSTALL_DIR}/pkgconfig)
 set(INCL_INSTALL_DIR      include)
 
-# These variables get written into config.h for use in finding the data
-# directories.
-set(GMXLIB_SEARCH_DIR share/${GMX_DATA_INSTALL_DIR}/top)
-set(GMXLIB_FALLBACK   ${CMAKE_INSTALL_PREFIX}/${DATA_INSTALL_DIR}/top)
-
 list(APPEND INSTALLED_HEADER_INCLUDE_DIRS ${INCL_INSTALL_DIR})
 
 # Binary and library suffix options
index 89b6d7aa9deda6a9f9b82c4b9c80184f73882152..2459207232c2eb3db60b9db0935323b5a58fae2f 100644 (file)
 /* TODO: For now, disable Doxygen warnings from here */
 /*! \cond */
 
-/* Default location of data files */
-#define GMXLIB_SEARCH_DIR "@GMXLIB_SEARCH_DIR@"
-
-/* Default location of data files */
-#define GMXLIB_FALLBACK "@GMXLIB_FALLBACK@"
-
 /* Binary suffix for the created binaries */
 #define GMX_BINARY_SUFFIX "@GMX_BINARY_SUFFIX@"
 
+/* Installation prefix (default location of data files) */
+#define CMAKE_INSTALL_PREFIX "@CMAKE_INSTALL_PREFIX@"
+
+/* Location of data files in the installation directory */
+#define DATA_INSTALL_DIR "@DATA_INSTALL_DIR@"
+
 /* Source directory for the build */
 #cmakedefine CMAKE_SOURCE_DIR "@CMAKE_SOURCE_DIR@"
 
index 520ca9fab50462a22fe63d5554d23de5f3f05afc..9368e3c9251bbd8728ceefc4784a0c64588f5373 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.
@@ -178,59 +178,57 @@ bool isAcceptableLibraryPath(const std::string &path)
  * Returns whether given path prefix contains files from `share/top/`.
  *
  * \param[in]  path   Path prefix to check.
- * \param[out] result If return value is `true`, the pointee is set to the
- *     actual data directory. Otherwise, the pointee is not modified.
  * \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 *result)
+bool isAcceptableLibraryPathPrefix(const std::string &path)
 {
-    std::string testPath = Path::join(path, GMXLIB_SEARCH_DIR);
+    std::string testPath = Path::join(path, DATA_INSTALL_DIR, "top");
     if (isAcceptableLibraryPath(testPath))
     {
-        *result = testPath;
         return true;
     }
     return false;
 }
 
 /*! \brief
- * Returns a fallback data path.
+ * 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 findFallbackLibraryDataPath()
+std::string findFallbackInstallationPrefixPath()
 {
 #ifndef GMX_NATIVE_WINDOWS
-    if (!isAcceptableLibraryPath(GMXLIB_FALLBACK))
+    if (!isAcceptableLibraryPathPrefix(CMAKE_INSTALL_PREFIX))
     {
-        std::string foundPath;
-        if (isAcceptableLibraryPathPrefix("/usr/local", &foundPath))
+        if (isAcceptableLibraryPathPrefix("/usr/local"))
         {
-            return foundPath;
+            return "/usr/local";
         }
-        if (isAcceptableLibraryPathPrefix("/usr", &foundPath))
+        if (isAcceptableLibraryPathPrefix("/usr"))
         {
-            return foundPath;
+            return "/usr";
         }
-        if (isAcceptableLibraryPathPrefix("/opt", &foundPath))
+        if (isAcceptableLibraryPathPrefix("/opt"))
         {
-            return foundPath;
+            return "/opt";
         }
     }
 #endif
-    return GMXLIB_FALLBACK;
+    return CMAKE_INSTALL_PREFIX;
 }
 
 /*! \brief
  * Finds the library data files based on path of the binary.
  *
- * \param[in] binaryPath  Absolute path to 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
@@ -241,8 +239,10 @@ std::string findFallbackLibraryDataPath()
  * 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 findDefaultLibraryDataPath(const std::string &binaryPath)
+std::string findInstallationPrefixPath(const std::string &binaryPath,
+                                       bool              *bSourceLayout)
 {
+    *bSourceLayout = false;
     // If the input path is not absolute, the binary could not be found.
     // Don't search anything.
     if (Path::isAbsolute(binaryPath))
@@ -263,18 +263,19 @@ std::string findDefaultLibraryDataPath(const std::string &binaryPath)
             std::string testPath = Path::join(CMAKE_SOURCE_DIR, "share/top");
             if (isAcceptableLibraryPath(testPath))
             {
-                return 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())
         {
-            std::string testPath = Path::join(searchPath, GMXLIB_SEARCH_DIR);
-            if (isAcceptableLibraryPath(testPath))
+            if (isAcceptableLibraryPathPrefix(searchPath))
             {
-                return testPath;
+                return searchPath;
             }
             searchPath = Path::getParentPath(searchPath);
         }
@@ -282,7 +283,7 @@ std::string findDefaultLibraryDataPath(const std::string &binaryPath)
 
     // 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 findFallbackLibraryDataPath();
+    return findFallbackInstallationPrefixPath();
 }
 
 //! \}
@@ -316,18 +317,19 @@ class CommandLineProgramContext::Impl
         std::string                   displayName_;
         std::string                   commandLine_;
         mutable std::string           fullBinaryPath_;
-        mutable std::string           defaultLibraryDataPath_;
+        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_(env)
+    : executableEnv_(env), bSourceLayout_(false)
 {
     invokedName_ = (argc != 0 ? argv[0] : "");
     programName_ = Path::getFilename(invokedName_);
@@ -415,16 +417,19 @@ const char *CommandLineProgramContext::fullBinaryPath() const
     return impl_->fullBinaryPath_.c_str();
 }
 
-const char *CommandLineProgramContext::defaultLibraryDataPath() const
+InstallationPrefixInfo CommandLineProgramContext::installationPrefix() const
 {
     tMPI::lock_guard<tMPI::mutex> lock(impl_->binaryPathMutex_);
-    if (impl_->defaultLibraryDataPath_.empty())
+    if (impl_->installationPrefix_.empty())
     {
         impl_->findBinaryPath();
-        impl_->defaultLibraryDataPath_ =
-            Path::normalize(findDefaultLibraryDataPath(impl_->fullBinaryPath_));
+        impl_->installationPrefix_ =
+            Path::normalize(findInstallationPrefixPath(impl_->fullBinaryPath_,
+                                                       &impl_->bSourceLayout_));
     }
-    return impl_->defaultLibraryDataPath_.c_str();
+    return InstallationPrefixInfo(
+            impl_->installationPrefix_.c_str(),
+            impl_->bSourceLayout_);
 }
 
 } // namespace gmx
index a112c0cb2448326420d322bd46e2d6df781b1af6..e08f3508958a9eb234680abd8f2c2e61ccba1734 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.
@@ -196,7 +196,7 @@ class CommandLineProgramContext : public ProgramContextInterface
          */
         virtual const char *fullBinaryPath() const;
         /*! \brief
-         * Returns the default path for \Gromacs data files.
+         * Returns the installation prefix (for finding \Gromacs data files).
          *
          * \throws std::bad_alloc if out of memory.
          * \throws tMPI::system_error on thread synchronization errors.
@@ -204,7 +204,7 @@ class CommandLineProgramContext : public ProgramContextInterface
          * Returns a hardcoded path set during configuration time if there is
          * an error in finding the library data files.
          */
-        virtual const char *defaultLibraryDataPath() const;
+        virtual InstallationPrefixInfo installationPrefix() const;
         /*! \brief
          * Returns the full command line used to invoke the binary.
          *
index 73e6c45732f51bb2215c81638287f13860494b71..ab622947ca6c38d168f1c639875e0a1680577900 100644 (file)
@@ -68,6 +68,7 @@
 #include "gromacs/utility/gmxassert.h"
 #include "gromacs/utility/programcontext.h"
 #include "gromacs/utility/smalloc.h"
+#include "gromacs/utility/stringutil.h"
 
 static gmx_bool be_cool(void)
 {
@@ -806,17 +807,18 @@ void printBinaryInformation(FILE                            *fp,
     fprintf(fp, "%sGROMACS:      %s, %s%s%s\n", prefix, name,
             gmx_version(), precisionString, suffix);
     const char *const binaryPath = programContext.fullBinaryPath();
-    if (binaryPath != NULL && binaryPath[0] != '\0')
+    if (!gmx::isNullOrEmpty(binaryPath))
     {
         fprintf(fp, "%sExecutable:   %s%s\n", prefix, binaryPath, suffix);
     }
-    const char *const libraryPath = programContext.defaultLibraryDataPath();
-    if (libraryPath != NULL && libraryPath[0] != '\0')
+    const gmx::InstallationPrefixInfo installPrefix = programContext.installationPrefix();
+    if (!gmx::isNullOrEmpty(installPrefix.path))
     {
-        fprintf(fp, "%sLibrary dir:  %s%s\n", prefix, libraryPath, suffix);
+        fprintf(fp, "%sData prefix:  %s%s%s\n", prefix, installPrefix.path,
+                installPrefix.bSourceLayout ? " (source tree)" : "", suffix);
     }
     const char *const commandLine = programContext.commandLine();
-    if (commandLine != NULL && commandLine[0] != '\0')
+    if (!gmx::isNullOrEmpty(commandLine))
     {
         fprintf(fp, "%sCommand line:%s\n%s  %s%s\n",
                 prefix, suffix, prefix, commandLine, suffix);
index dfad443b4a3762e07125bc4678053a3802853656..bdf673df5d6b5adf6344d68643e7ef9940f17518 100644 (file)
@@ -43,6 +43,8 @@
 
 #include "datafilefinder.h"
 
+#include "config.h"
+
 #include <cstdlib>
 
 #include <string>
@@ -65,6 +67,8 @@ namespace gmx
 class DataFileFinder::Impl
 {
     public:
+        static std::string getDefaultPath();
+
         Impl() : envName_(NULL), bEnvIsSet_(false) {}
 
         const char               *envName_;
@@ -72,6 +76,19 @@ class DataFileFinder::Impl
         std::vector<std::string>  searchPath_;
 };
 
+std::string DataFileFinder::Impl::getDefaultPath()
+{
+    const InstallationPrefixInfo installPrefix
+        = getProgramContext().installationPrefix();
+    if (!isNullOrEmpty(installPrefix.path))
+    {
+        const char *const dataPath
+            = installPrefix.bSourceLayout ? "share" : DATA_INSTALL_DIR;
+        return Path::join(installPrefix.path, dataPath, "top");
+    }
+    return std::string();
+}
+
 /********************************************************************
  * DataFileFinder
  */
@@ -140,8 +157,8 @@ std::string DataFileFinder::findFile(const DataFileOptions &options) const
             }
         }
     }
-    const char *const defaultPath = getProgramContext().defaultLibraryDataPath();
-    if (defaultPath != NULL && defaultPath[0] != '\0')
+    const std::string &defaultPath = Impl::getDefaultPath();
+    if (!defaultPath.empty())
     {
         std::string testPath = Path::join(defaultPath, options.filename_);
         if (Path::exists(testPath))
@@ -179,7 +196,7 @@ std::string DataFileFinder::findFile(const DataFileOptions &options) const
                 message.append(*i);
             }
         }
-        if (defaultPath != NULL && defaultPath[0] != '\0')
+        if (!defaultPath.empty())
         {
             message.append("\n  ");
             message.append(defaultPath);
@@ -228,12 +245,12 @@ DataFileFinder::enumerateFiles(const DataFileOptions &options) const
             }
         }
     }
-    const char *const defaultPath = getProgramContext().defaultLibraryDataPath();
-    if (defaultPath != NULL && defaultPath[0] != '\0')
+    const std::string &defaultPath = Impl::getDefaultPath();
+    if (!defaultPath.empty())
     {
         std::vector<std::string> files
             = DirectoryEnumerator::enumerateFilesWithExtension(
-                        defaultPath, options.filename_, false);
+                        defaultPath.c_str(), options.filename_, false);
         for (i = files.begin(); i != files.end(); ++i)
         {
             result.push_back(DataFileInfo(defaultPath, *i, true));
index ac9bc13dd771acaf5fa671ca05fad0216f51b7e5..5edf9aa284c57da018f13e3315e523b61ccd7803 100644 (file)
@@ -1,7 +1,7 @@
 /*
  * This file is part of the GROMACS molecular simulation package.
  *
- * Copyright (c) 2013,2014, by the GROMACS development team, led by
+ * Copyright (c) 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.
@@ -70,7 +70,10 @@ class DefaultProgramContext : public ProgramContextInterface
         virtual const char *programName() const { return "GROMACS"; }
         virtual const char *displayName() const { return "GROMACS"; }
         virtual const char *fullBinaryPath() const { return ""; }
-        virtual const char *defaultLibraryDataPath() const { return ""; }
+        virtual InstallationPrefixInfo installationPrefix() const
+        {
+            return InstallationPrefixInfo("", false);
+        }
         virtual const char *commandLine() const { return ""; }
 };
 
index 82d349a2a473c14823a833ce766263f20d3f2726..d7aede1988c088f43f50e618ee4ccde90c25cac8 100644 (file)
@@ -1,7 +1,7 @@
 /*
  * This file is part of the GROMACS molecular simulation package.
  *
- * Copyright (c) 2013,2014, by the GROMACS development team, led by
+ * Copyright (c) 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.
@@ -49,6 +49,41 @@ namespace gmx
 //! \addtogroup module_utility
 //! \{
 
+/*! \brief
+ * Provides information about installation prefix (see
+ * ProgramContextInterface::installationPrefix()).
+ *
+ * \inpublicapi
+ */
+struct InstallationPrefixInfo
+{
+    //! Initializes the structure with given values.
+    InstallationPrefixInfo(const char *path, bool bSource)
+        : path(path), bSourceLayout(bSource)
+    {
+    }
+
+    /*! \brief
+     * Path to the installation prefix of the current \Gromacs instance.
+     *
+     * If this is `NULL` or empty, data files cannot be looked up from the
+     * install tree and \Gromacs functions that access such files may fail.
+     * This can also contain a path to the source tree (see \a bSourceLayout).
+     */
+    const char *const path;
+    /*! \brief
+     * Whether \a path points to a source tree -like layout.
+     *
+     * For testing, it is useful to read data files from the source tree.
+     * For such cases, the program context can return the source tree root path
+     * in \a path, and set this to `true` to indicate that the data files
+     * should be searched using the layout of the source tree instead of the
+     * installation.
+     */
+    const bool        bSourceLayout;
+};
+
+
 /*! \brief
  * Provides context information about the program that is calling the library.
  *
@@ -107,15 +142,19 @@ class ProgramContextInterface
          */
         virtual const char *fullBinaryPath() const = 0;
         /*! \brief
-         * Returns the default path for \Gromacs data files.
+         * Returns the installation prefix for \Gromacs.
          *
          * This path is used to locate the data files that are in `share/top/`
          * in the source directory.
          * The implementation can provide an empty string if the path is not
          * available; in such a case, functions that require data files may
          * fail.
+         *
+         * The returned structure also contains a flag to indicate whether the
+         * prefix actually points to the source tree.  This is used for tests
+         * and to support running binaries directly from the build tree.
          */
-        virtual const char *defaultLibraryDataPath() const = 0;
+        virtual InstallationPrefixInfo installationPrefix() const = 0;
         /*! \brief
          * Returns the full command line used to invoke the binary.
          *
index 5617fbb899a6503407d091f458d0a6db683026f4..e74d6c990532d0224f000322e9c0306daa9e4bf6 100644 (file)
@@ -1,7 +1,7 @@
 /*
  * This file is part of the GROMACS molecular simulation package.
  *
- * Copyright (c) 2011,2012,2013,2014, by the GROMACS development team, led by
+ * Copyright (c) 2011,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.
@@ -54,6 +54,16 @@ namespace gmx
 //! \addtogroup module_utility
 //! \{
 
+/*! \brief
+ * Tests whether a string is null or empty.
+ *
+ * Does not throw.
+ */
+bool inline isNullOrEmpty(const char *str)
+{
+    return str == NULL || str[0] == '\0';
+}
+
 /*! \brief
  * Tests whether a string starts with another string.
  *
index 8bf66f88c13408d031299327199d7eef4905538c..3969e4c363285e96426ba7d9395705aff3d59895 100644 (file)
@@ -82,7 +82,7 @@ namespace
 /*! \brief
  * Custom program context for test binaries.
  *
- * This context overrides the defaultLibraryDataPath() implementation to always
+ * This context overrides the installationPrefix() implementation to always
  * load data files from the source directory, as the test binaries are never
  * installed.  It also support overriding the directory through a command-line
  * option to the test binary.
@@ -98,8 +98,7 @@ class TestProgramContext : public ProgramContextInterface
          * \param[in] context  Current \Gromacs program context.
          */
         explicit TestProgramContext(const ProgramContextInterface &context)
-            : context_(context),
-              dataPath_(Path::join(CMAKE_SOURCE_DIR, "share/top"))
+            : context_(context), dataPath_(CMAKE_SOURCE_DIR)
         {
         }
 
@@ -108,7 +107,7 @@ class TestProgramContext : public ProgramContextInterface
          */
         void overrideSourceRoot(const std::string &sourceRoot)
         {
-            dataPath_ = Path::join(sourceRoot, "share/top");
+            dataPath_ = sourceRoot;
         }
 
         virtual const char *programName() const
@@ -123,9 +122,9 @@ class TestProgramContext : public ProgramContextInterface
         {
             return context_.fullBinaryPath();
         }
-        virtual const char *defaultLibraryDataPath() const
+        virtual InstallationPrefixInfo installationPrefix() const
         {
-            return dataPath_.c_str();
+            return InstallationPrefixInfo(dataPath_.c_str(), true);
         }
         virtual const char *commandLine() const
         {