/*
* 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.
#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"
//! 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)
{
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)
{
void finalizeForCommandLine()
{
gmx::finalize();
+ setLibraryFileFinder(NULL);
+ g_libFileFinder.reset();
setProgramContext(NULL);
g_commandLineContext.reset();
}
#
# 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.
basedefinitions.h
classhelpers.h
cstringutil.h
+ datafilefinder.h
errorcodes.h
exceptions.h
fatalerror.h
--- /dev/null
+/*
+ * 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
--- /dev/null
+/*
+ * 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
/*
* 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.
}
}
+// 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))
{
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)
/*
* 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.
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.
*
*
* 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.
#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"
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;
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)
*
* 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.
#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
#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"
{
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)
{