Refactor logic for finding share/top/ files
authorTeemu Murtola <teemu.murtola@gmail.com>
Thu, 1 Jan 2015 06:28:29 +0000 (08:28 +0200)
committerGerrit Code Review <gerrit@gerrit.gromacs.org>
Thu, 8 Jan 2015 19:00:27 +0000 (20:00 +0100)
Refactor the logic of how gmxlibfn() finds the library files into a
separate C++ class that provides some support for customization.

By itself, this change only rewrites the existing logic into C++ with a
few minor functional changes:
 - unit tests no longer get confused if GMXLIB is set, and
 - the default data directory is searched even if GMXLIB is set,
   removing the need to duplicate all the files into custom directories
   and/or to manually specify the default directory.
However, it also lays the groundwork for further refactoring:
 - consolidating the other direct use of GMXLIB env.var. from
   fflibutil.cpp into the same place
 - more reusable logic for finding the files, e.g., for JIT compilation
   or for structuring the data files into more than one directory
 - removing one-off uses of functions like low_libopen

Change-Id: I4e14a7e7f11846b3828265c1c735da6bc57f97ff

src/gromacs/commandline/cmdlineinit.cpp
src/gromacs/utility/CMakeLists.txt
src/gromacs/utility/datafilefinder.cpp [new file with mode: 0644]
src/gromacs/utility/datafilefinder.h [new file with mode: 0644]
src/gromacs/utility/file.cpp
src/gromacs/utility/file.h
src/gromacs/utility/futil.cpp
src/gromacs/utility/futil.h
src/testutils/testinit.cpp

index a7d23b3755474c70d2e7b63da645fe94b6fc5179..49786a1033c78dce1cce4dda3b3fc1120311ca5d 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.
@@ -54,7 +54,9 @@
 #include "gromacs/commandline/cmdlineprogramcontext.h"
 #include "gromacs/legacyheaders/network.h"
 #include "gromacs/legacyheaders/types/commrec.h"
+#include "gromacs/utility/datafilefinder.h"
 #include "gromacs/utility/exceptions.h"
+#include "gromacs/utility/futil.h"
 #include "gromacs/utility/gmxassert.h"
 #include "gromacs/utility/init.h"
 #include "gromacs/utility/programcontext.h"
@@ -71,6 +73,8 @@ namespace
 
 //! Global context instance initialized in initForCommandLine().
 boost::scoped_ptr<CommandLineProgramContext> g_commandLineContext;
+//! Global library data file finder that respects GMXLIB.
+boost::scoped_ptr<DataFileFinder>            g_libFileFinder;
 
 #ifdef GMX_LIB_MPI
 void broadcastArguments(const t_commrec *cr, int *argc, char ***argv)
@@ -125,6 +129,9 @@ CommandLineProgramContext &initForCommandLine(int *argc, char ***argv)
     {
         g_commandLineContext.reset(new CommandLineProgramContext(*argc, *argv));
         setProgramContext(g_commandLineContext.get());
+        g_libFileFinder.reset(new DataFileFinder());
+        g_libFileFinder->setSearchPathFromEnv("GMXLIB");
+        setLibraryFileFinder(g_libFileFinder.get());
     }
     catch (const std::exception &ex)
     {
@@ -137,6 +144,8 @@ CommandLineProgramContext &initForCommandLine(int *argc, char ***argv)
 void finalizeForCommandLine()
 {
     gmx::finalize();
+    setLibraryFileFinder(NULL);
+    g_libFileFinder.reset();
     setProgramContext(NULL);
     g_commandLineContext.reset();
 }
index d1abc0659cda45cda53b63ec75cc27d03f4a929c..88a0c2678dc8d8880d82f03aad4f62798197ea06 100644 (file)
@@ -1,7 +1,7 @@
 #
 # This file is part of the GROMACS molecular simulation package.
 #
-# Copyright (c) 2010,2011,2012,2013,2014, by the GROMACS development team, led by
+# Copyright (c) 2010,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.
@@ -40,6 +40,7 @@ gmx_install_headers(
     basedefinitions.h
     classhelpers.h
     cstringutil.h
+    datafilefinder.h
     errorcodes.h
     exceptions.h
     fatalerror.h
diff --git a/src/gromacs/utility/datafilefinder.cpp b/src/gromacs/utility/datafilefinder.cpp
new file mode 100644 (file)
index 0000000..da1e3e0
--- /dev/null
@@ -0,0 +1,198 @@
+/*
+ * This file is part of the GROMACS molecular simulation package.
+ *
+ * Copyright (c) 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.
+ *
+ * GROMACS is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1
+ * of the License, or (at your option) any later version.
+ *
+ * GROMACS is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with GROMACS; if not, see
+ * http://www.gnu.org/licenses, or write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA.
+ *
+ * If you want to redistribute modifications to GROMACS, please
+ * consider that scientific software is very special. Version
+ * control is crucial - bugs must be traceable. We will be happy to
+ * consider code for inclusion in the official distribution, but
+ * derived work must not be called official GROMACS. Details are found
+ * in the README & COPYING files - if they are missing, get the
+ * official version at http://www.gromacs.org.
+ *
+ * To help us fund GROMACS development, we humbly ask that you cite
+ * the research papers on the package. Check out http://www.gromacs.org.
+ */
+/*! \internal \file
+ * \brief
+ * Implements gmx::DataFileFinder.
+ *
+ * \author Teemu Murtola <teemu.murtola@gmail.com>
+ * \ingroup module_utility
+ */
+#include "gmxpre.h"
+
+#include "datafilefinder.h"
+
+#include <cstdlib>
+
+#include <string>
+#include <vector>
+
+#include "gromacs/utility/exceptions.h"
+#include "gromacs/utility/file.h"
+#include "gromacs/utility/path.h"
+#include "gromacs/utility/programcontext.h"
+#include "gromacs/utility/stringutil.h"
+
+namespace gmx
+{
+
+/********************************************************************
+ * DataFileFinder::Impl
+ */
+
+class DataFileFinder::Impl
+{
+    public:
+        Impl() : envName_(NULL), bEnvIsSet_(false) {}
+
+        const char               *envName_;
+        bool                      bEnvIsSet_;
+        std::vector<std::string>  searchPath_;
+};
+
+/********************************************************************
+ * DataFileFinder
+ */
+
+DataFileFinder::DataFileFinder()
+    : impl_(NULL)
+{
+}
+
+DataFileFinder::~DataFileFinder()
+{
+}
+
+void DataFileFinder::setSearchPathFromEnv(const char *envVarName)
+{
+    if (!impl_.get())
+    {
+        impl_.reset(new Impl());
+    }
+    impl_->envName_ = envVarName;
+    const char *const lib = getenv(envVarName);
+    if (lib != NULL)
+    {
+        impl_->bEnvIsSet_ = true;
+        Path::splitPathEnvironment(lib, &impl_->searchPath_);
+    }
+}
+
+FILE *DataFileFinder::openFile(const DataFileOptions &options) const
+{
+    // TODO: There is a small race here, since there is some time between
+    // the exists() calls and actually opening the file.  It would be better
+    // to leave the file open after a successful exists() if the desire is to
+    // actually open the file.
+    std::string filename = findFile(options);
+    if (filename.empty())
+    {
+        return NULL;
+    }
+#if 0
+    if (debug)
+    {
+        fprintf(debug, "Opening library file %s\n", fn);
+    }
+#endif
+    return File::openRawHandle(filename, "r");
+}
+
+std::string DataFileFinder::findFile(const DataFileOptions &options) const
+{
+    if (options.bCurrentDir_ && Path::exists(options.filename_))
+    {
+        return options.filename_;
+    }
+    if (impl_.get())
+    {
+        std::vector<std::string>::const_iterator i;
+        for (i = impl_->searchPath_.begin(); i != impl_->searchPath_.end(); ++i)
+        {
+            // TODO: Deal with an empty search path entry more reasonably.
+            std::string testPath = Path::join(*i, options.filename_);
+            // TODO: Consider skipping directories.
+            if (Path::exists(testPath))
+            {
+                return testPath;
+            }
+        }
+    }
+    const char *const defaultPath = getProgramContext().defaultLibraryDataPath();
+    if (defaultPath != NULL && defaultPath[0] != '\0')
+    {
+        std::string testPath = Path::join(defaultPath, options.filename_);
+        if (Path::exists(testPath))
+        {
+            return testPath;
+        }
+    }
+    if (options.bThrow_)
+    {
+        const char *const envName   = (impl_.get() ? impl_->envName_ : NULL);
+        const bool        bEnvIsSet = (impl_.get() ? impl_->bEnvIsSet_ : false);
+        std::string       message(
+                formatString("Library file %s not found", options.filename_));
+        if (options.bCurrentDir_)
+        {
+            message.append(" in current dir nor");
+        }
+        if (bEnvIsSet)
+        {
+            message.append(formatString(" in your %s path nor", envName));
+        }
+        message.append(" in the default directories.\nThe following paths were searched:");
+        if (options.bCurrentDir_)
+        {
+            message.append("\n  ");
+            message.append(Path::getWorkingDirectory());
+            message.append(" (current dir)");
+        }
+        if (impl_.get())
+        {
+            std::vector<std::string>::const_iterator i;
+            for (i = impl_->searchPath_.begin(); i != impl_->searchPath_.end(); ++i)
+            {
+                message.append("\n  ");
+                message.append(*i);
+            }
+        }
+        if (defaultPath != NULL && defaultPath[0] != '\0')
+        {
+            message.append("\n  ");
+            message.append(defaultPath);
+            message.append(" (default)");
+        }
+        if (!bEnvIsSet && envName != NULL)
+        {
+            message.append(
+                    formatString("\nYou can set additional directories to search "
+                                 "with the %s path variable.", envName));
+        }
+        GMX_THROW(FileIOError(message));
+    }
+    return std::string();
+}
+
+} // namespace gmx
diff --git a/src/gromacs/utility/datafilefinder.h b/src/gromacs/utility/datafilefinder.h
new file mode 100644 (file)
index 0000000..383ccfb
--- /dev/null
@@ -0,0 +1,196 @@
+/*
+ * This file is part of the GROMACS molecular simulation package.
+ *
+ * Copyright (c) 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.
+ *
+ * GROMACS is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1
+ * of the License, or (at your option) any later version.
+ *
+ * GROMACS is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with GROMACS; if not, see
+ * http://www.gnu.org/licenses, or write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA.
+ *
+ * If you want to redistribute modifications to GROMACS, please
+ * consider that scientific software is very special. Version
+ * control is crucial - bugs must be traceable. We will be happy to
+ * consider code for inclusion in the official distribution, but
+ * derived work must not be called official GROMACS. Details are found
+ * in the README & COPYING files - if they are missing, get the
+ * official version at http://www.gromacs.org.
+ *
+ * To help us fund GROMACS development, we humbly ask that you cite
+ * the research papers on the package. Check out http://www.gromacs.org.
+ */
+/*! \file
+ * \brief
+ * Declares gmx::DataFileFinder and related classes.
+ *
+ * \author Teemu Murtola <teemu.murtola@gmail.com>
+ * \inpublicapi
+ * \ingroup module_utility
+ */
+#ifndef GMX_UTILITY_LIBFILEFINDER_H
+#define GMX_UTILITY_LIBFILEFINDER_H
+
+#include <cstdio>
+
+#include <string>
+
+#include "gromacs/utility/classhelpers.h"
+
+namespace gmx
+{
+
+class DataFileFinder;
+
+/*! \brief
+ * Search parameters for DataFileFinder.
+ *
+ * This class implements a named parameter idiom for DataFileFinder::findFile()
+ * and DataFileFinder::openFile() to support an easily readable and
+ * customizable way of searching data files.
+ *
+ * By default, the search first considers the current directory, followed by
+ * specified and default data directories, and an exception is thrown if the
+ * file could not be found.
+ * To skip searching in the current directory, use includeCurrentDir().
+ *
+ * Methods in this class do not throw.
+ *
+ * \inpublicapi
+ * \ingroup module_utility
+ */
+class DataFileOptions
+{
+    public:
+        /*! \brief
+         * Constructs default options for searching for a file with the
+         * specified name.
+         *
+         * \param[in] filename  File name to search for.
+         *
+         * This constructor is not explicit to allow passing a simple string to
+         * DataFileFinder methods to search for the string with the default
+         * parameters.
+         */
+        DataFileOptions(const char *filename)
+            : filename_(filename), bCurrentDir_(true), bThrow_(true)
+        {
+        }
+        //! \copydoc DataFileOptions(const char *)
+        DataFileOptions(const std::string &filename)
+            : filename_(filename.c_str()), bCurrentDir_(true), bThrow_(true)
+        {
+        }
+
+        //! Sets whether to search in the current (working) directory.
+        DataFileOptions &includeCurrentDir(bool bInclude)
+        {
+            bCurrentDir_ = bInclude;
+            return *this;
+        }
+        //! Sets whether an exception is thrown if the file could not be found.
+        DataFileOptions &throwIfNotFound(bool bThrow)
+        {
+            bThrow_ = bThrow;
+            return *this;
+        }
+
+    private:
+        const char *filename_;
+        bool        bCurrentDir_;
+        bool        bThrow_;
+
+        /*! \brief
+         * Needed to access the members without otherwise unnecessary accessors.
+         */
+        friend class DataFileFinder;
+};
+
+/*! \brief
+ * Searches data files from a set of paths.
+ *
+ * \inpublicapi
+ * \ingroup module_utility
+ */
+class DataFileFinder
+{
+    public:
+        /*! \brief
+         * Constructs a default data file finder.
+         *
+         * The constructed finder searches only in the directory specified by
+         * the global program context (see ProgramContextInterface), and
+         * optionally in the current directory.
+         *
+         * Does not throw.
+         */
+        DataFileFinder();
+        ~DataFileFinder();
+
+        /*! \brief
+         * Adds search path from an environment variable.
+         *
+         * \param[in] envVarName  Name of the environment variable to use.
+         * \throws std::bad_alloc if out of memory.
+         *
+         * If the specified environment variable is set, it is interpreted like
+         * a `PATH` environment variable on the platform (split at appropriate
+         * separators), and each path found is added to the search path this
+         * finder searches.  The added paths take precedence over the default
+         * directory specified by the global program context, but the current
+         * directory is searched first.
+         */
+        void setSearchPathFromEnv(const char *envVarName);
+
+        /*! \brief
+         * Opens a data file if found.
+         *
+         * \param[in] options  Identifies the file to be searched for.
+         * \returns The opened file handle, or `NULL` if the file could not be
+         *     found and exceptions were turned off.
+         * \throws  FileIOError if
+         *   - no such file can be found, and \p options specifies that an
+         *     exception should be thrown, or
+         *   - there is an error opening the file (note that a file is skipped
+         *     during the search if the user does not have rights to open the
+         *     file at all).
+         *
+         * See findFile() for more details.
+         */
+        FILE *openFile(const DataFileOptions &options) const;
+        /*! \brief
+         * Finds a full path to a data file if found.
+         *
+         * \param[in] options  Identifies the file to be searched for.
+         * \returns Full path to the data file, or an empty string if the file
+         *     could not be found and exceptions were turned off.
+         * \throws  FileIOError if no such file can be found, and \p options
+         *     specifies that an exception should be thrown.
+         *
+         * Searches for a data file in the search paths configured for the
+         * finder, as well as in the current directory if so required.
+         * Returns the full path to the first file found.
+         */
+        std::string findFile(const DataFileOptions &options) const;
+
+    private:
+        class Impl;
+
+        PrivateImplPointer<Impl> impl_;
+};
+
+} // namespace gmx
+
+#endif
index a194992bfd8e82198e3d5f09f9d70c0ac0ecef09..dbd809c38b2bf5a4f1c56737c9a8e88ab8c14871 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.
@@ -109,6 +109,25 @@ File::Impl::~Impl()
     }
 }
 
+// static
+FILE *File::openRawHandle(const char *filename, const char *mode)
+{
+    FILE *fp = fopen(filename, mode);
+    if (fp == NULL)
+    {
+        GMX_THROW_WITH_ERRNO(
+                FileIOError(formatString("Could not open file '%s'", filename)),
+                "fopen", errno);
+    }
+    return fp;
+}
+
+// static
+FILE *File::openRawHandle(const std::string &filename, const char *mode)
+{
+    return openRawHandle(filename.c_str(), mode);
+}
+
 File::File(const char *filename, const char *mode)
     : impl_(new Impl(NULL, true))
 {
@@ -135,13 +154,7 @@ void File::open(const char *filename, const char *mode)
     GMX_RELEASE_ASSERT(impl_->fp_ == NULL,
                        "Attempted to open the same file object twice");
     // TODO: Port all necessary functionality from gmx_ffopen() here.
-    impl_->fp_ = fopen(filename, mode);
-    if (impl_->fp_ == NULL)
-    {
-        GMX_THROW_WITH_ERRNO(
-                FileIOError(formatString("Could not open file '%s'", filename)),
-                "fopen", errno);
-    }
+    impl_->fp_ = openRawHandle(filename, mode);
 }
 
 void File::open(const std::string &filename, const char *mode)
index 37a55cb03ceb65bd4671ea041a04f106789daed9..d1f354c9a44b20745ae3c68587fbe96c35e208cd 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.
@@ -64,6 +64,19 @@ namespace gmx
 class File
 {
     public:
+        /*! \brief
+         * Opens a file and returns a `FILE` handle.
+         *
+         * \param[in] filename  Path of the file to open.
+         * \param[in] mode      Mode to open the file in (for fopen()).
+         * \throws    FileIOError on any I/O error.
+         *
+         * Instead of returning `NULL` on errors, throws an exception with
+         * additional details (including the file name and `errno`).
+         */
+        static FILE *openRawHandle(const char *filename, const char *mode);
+        //! \copydoc openRawHandle(const char *, const char *)
+        static FILE *openRawHandle(const std::string &filename, const char *mode);
         /*! \brief
          * Creates a file object and opens a file.
          *
index 9e464ea1a2736f54c7f624146d7e55a267ffb49e..6a45173cd4963fe9f14ac9207c1cac97bc5acef7 100644 (file)
@@ -3,7 +3,7 @@
  *
  * Copyright (c) 1991-2000, University of Groningen, The Netherlands.
  * Copyright (c) 2001-2004, The GROMACS development team.
- * 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.
@@ -64,6 +64,7 @@
 #include "thread_mpi/threads.h"
 
 #include "gromacs/utility/cstringutil.h"
+#include "gromacs/utility/datafilefinder.h"
 #include "gromacs/utility/exceptions.h"
 #include "gromacs/utility/fatalerror.h"
 #include "gromacs/utility/path.h"
@@ -88,6 +89,32 @@ static int          s_maxBackupCount = 0;
    to protect it with mutexes */
 static tMPI_Thread_mutex_t pstack_mutex = TMPI_THREAD_MUTEX_INITIALIZER;
 
+namespace gmx
+{
+namespace
+{
+//! Global library file finder; stores the object set with setLibraryFileFinder().
+const DataFileFinder *g_libFileFinder;
+//! Default library file finder if nothing is set.
+const DataFileFinder  g_defaultLibFileFinder;
+}   // namespace
+
+const DataFileFinder &getLibraryFileFinder()
+{
+    if (g_libFileFinder != NULL)
+    {
+        return *g_libFileFinder;
+    }
+    return g_defaultLibFileFinder;
+}
+
+void setLibraryFileFinder(const DataFileFinder *finder)
+{
+    g_libFileFinder = finder;
+}
+
+} // namespace gmx
+
 void gmx_disable_file_buffering(void)
 {
     bUnbuffered = true;
@@ -689,83 +716,35 @@ gmx_directory_close(gmx_directory_t gmxdir)
 
 char *low_gmxlibfn(const char *file, gmx_bool bAddCWD, gmx_bool bFatal)
 {
-    bool bEnvIsSet = false;
     try
     {
-        if (bAddCWD && gmx_fexist(file))
+        const gmx::DataFileFinder &finder = gmx::getLibraryFileFinder();
+        std::string                result =
+            finder.findFile(gmx::DataFileOptions(file)
+                                .includeCurrentDir(bAddCWD)
+                                .throwIfNotFound(bFatal));
+        if (!result.empty())
         {
-            return gmx_strdup(file);
-        }
-        else
-        {
-            std::string  libpath;
-            // GMXLIB can be a path.
-            const char  *lib = getenv("GMXLIB");
-            if (lib != NULL)
-            {
-                bEnvIsSet = true;
-                libpath   = lib;
-            }
-            else
-            {
-                libpath = gmx::getProgramContext().defaultLibraryDataPath();
-            }
-
-            std::vector<std::string>                 pathEntries;
-            gmx::Path::splitPathEnvironment(libpath, &pathEntries);
-            std::vector<std::string>::const_iterator i;
-            for (i = pathEntries.begin(); i != pathEntries.end(); ++i)
-            {
-                std::string testPath = gmx::Path::join(*i, file);
-                if (gmx::Path::exists(testPath))
-                {
-                    return gmx_strdup(testPath.c_str());
-                }
-            }
+            return gmx_strdup(result.c_str());
         }
     }
     GMX_CATCH_ALL_AND_EXIT_WITH_FATAL_ERROR;
-    if (bFatal)
-    {
-        if (bEnvIsSet)
-        {
-            gmx_fatal(FARGS,
-                      "Library file %s not found %sin your GMXLIB path.",
-                      file, bAddCWD ? "in current dir nor " : "");
-        }
-        else
-        {
-            gmx_fatal(FARGS,
-                      "Library file %s not found %sin default directories.\n"
-                      "(You can set the directories to search with the GMXLIB path variable)",
-                      file, bAddCWD ? "in current dir nor " : "");
-        }
-    }
     return NULL;
 }
 
 FILE *low_libopen(const char *file, gmx_bool bFatal)
 {
-    FILE *ff;
-    char *fn;
-
-    fn = low_gmxlibfn(file, TRUE, bFatal);
-
-    if (fn == NULL)
-    {
-        ff = NULL;
-    }
-    else
+    try
     {
-        if (debug)
-        {
-            fprintf(debug, "Opening library file %s\n", fn);
-        }
-        ff = fopen(fn, "r");
+        const gmx::DataFileFinder &finder = gmx::getLibraryFileFinder();
+        FILE *fp =
+            finder.openFile(gmx::DataFileOptions(file)
+                                .includeCurrentDir(true)
+                                .throwIfNotFound(bFatal));
+        return fp;
     }
-    sfree(fn);
-
-    return ff;
+    GMX_CATCH_ALL_AND_EXIT_WITH_FATAL_ERROR;
+    return NULL;
 }
 
 char *gmxlibfn(const char *file)
index c5368586091404697484ff192d17d85d209c5b69..0b5c1eeadf7807d2fdd642eb3a48b34ed8f83df9 100644 (file)
@@ -3,7 +3,7 @@
  *
  * Copyright (c) 1991-2000, University of Groningen, The Netherlands.
  * Copyright (c) 2001-2004, The GROMACS development team.
- * 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.
@@ -269,6 +269,50 @@ void gmx_getcwd(char *buffer, size_t size);
 
 #ifdef __cplusplus
 }
+
+namespace gmx
+{
+
+class DataFileFinder;
+
+/*! \brief
+ * Gets a finder for locating data files from share/top/.
+ *
+ * \returns Finder set with setLibraryFileFinder(), or a default finder.
+ *
+ * If setLibraryFileFinder() has not been called (or a `NULL` finder has been
+ * set), a default finder is returned.
+ * The default finder searches data files from the directory identified by the
+ * global program context; it does not respect GMXLIB environment variable.
+ * Calling initForCommandLine() sets a finder that respects GMXLIB.
+ *
+ * Does not throw.
+ *
+ * See setLibraryFileFinder() for thread safety.
+ *
+ * \ingroup module_utility
+ */
+const DataFileFinder &getLibraryFileFinder();
+/*! \brief
+ * Sets a finder for location data files from share/top/.
+ *
+ * \param[in] finder  finder to set
+ *     (can be NULL to restore the default finder).
+ *
+ * The library does not take ownership of \p finder.
+ * The provided object must remain valid until the global instance is changed
+ * by another call to setLibraryFileFinder().
+ *
+ * The global instance is used by gmxlibfn() and libopen().
+ *
+ * This method is not thread-safe.  See setProgramContext(); the same
+ * constraints apply here as well.
+ *
+ * Does not throw.
+ */
+void setLibraryFileFinder(const DataFileFinder *finder);
+
+} // namespace gmx
 #endif
 
 #endif
index 20e5f2eaaeb656a2f8ac6c710500faea6b56825f..8bf66f88c13408d031299327199d7eef4905538c 100644 (file)
@@ -62,6 +62,7 @@
 #include "gromacs/utility/errorcodes.h"
 #include "gromacs/utility/exceptions.h"
 #include "gromacs/utility/file.h"
+#include "gromacs/utility/futil.h"
 #include "gromacs/utility/path.h"
 #include "gromacs/utility/programcontext.h"
 
@@ -164,6 +165,9 @@ void initTestUtils(const char *dataPath, const char *tempPath, int *argc, char *
     {
         g_testContext.reset(new TestProgramContext(context));
         setProgramContext(g_testContext.get());
+        // Use the default finder that does not respect GMXLIB, since the tests
+        // generally can only get confused by a different set of data files.
+        setLibraryFileFinder(NULL);
         ::testing::InitGoogleMock(argc, *argv);
         if (dataPath != NULL)
         {