2 * This file is part of the GROMACS molecular simulation package.
4 * Copyright (c) 2014,2015,2016,2017,2018 by the GROMACS development team.
5 * Copyright (c) 2019,2020, by the GROMACS development team, led by
6 * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
7 * and including many others, as listed in the AUTHORS file in the
8 * top-level source directory and at http://www.gromacs.org.
10 * GROMACS is free software; you can redistribute it and/or
11 * modify it under the terms of the GNU Lesser General Public License
12 * as published by the Free Software Foundation; either version 2.1
13 * of the License, or (at your option) any later version.
15 * GROMACS is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 * Lesser General Public License for more details.
20 * You should have received a copy of the GNU Lesser General Public
21 * License along with GROMACS; if not, see
22 * http://www.gnu.org/licenses, or write to the Free Software Foundation,
23 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
25 * If you want to redistribute modifications to GROMACS, please
26 * consider that scientific software is very special. Version
27 * control is crucial - bugs must be traceable. We will be happy to
28 * consider code for inclusion in the official distribution, but
29 * derived work must not be called official GROMACS. Details are found
30 * in the README & COPYING files - if they are missing, get the
31 * official version at http://www.gromacs.org.
33 * To help us fund GROMACS development, we humbly ask that you cite
34 * the research papers on the package. Check out http://www.gromacs.org.
38 * Implements gmx::DataFileFinder.
40 * \author Teemu Murtola <teemu.murtola@gmail.com>
41 * \ingroup module_utility
45 #include "datafilefinder.h"
53 #include "buildinfo.h"
54 #include "gromacs/utility/directoryenumerator.h"
55 #include "gromacs/utility/exceptions.h"
56 #include "gromacs/utility/filestream.h"
57 #include "gromacs/utility/path.h"
58 #include "gromacs/utility/programcontext.h"
59 #include "gromacs/utility/stringutil.h"
64 /********************************************************************
65 * DataFileFinder::Impl
68 class DataFileFinder::Impl
71 static std::string
getDefaultPath();
73 Impl() : envName_(nullptr), bEnvIsSet_(false) {}
77 std::vector
<std::string
> searchPath_
;
80 std::string
DataFileFinder::Impl::getDefaultPath()
82 const InstallationPrefixInfo installPrefix
= getProgramContext().installationPrefix();
83 if (!isNullOrEmpty(installPrefix
.path
))
85 const char* const dataPath
= installPrefix
.bSourceLayout
? "share" : GMX_INSTALL_GMXDATADIR
;
86 return Path::join(installPrefix
.path
, dataPath
, "top");
91 /********************************************************************
95 DataFileFinder::DataFileFinder() : impl_(nullptr) {}
97 DataFileFinder::~DataFileFinder() {}
99 void DataFileFinder::setSearchPathFromEnv(const char* envVarName
)
103 impl_
.reset(new Impl());
105 impl_
->envName_
= envVarName
;
106 const char* const lib
= getenv(envVarName
);
107 if (!isNullOrEmpty(lib
))
109 std::vector
<std::string
>& path
= impl_
->searchPath_
; // convenience
110 const std::string defaultPath
= impl_
->getDefaultPath();
111 std::vector
<std::string
> tmpPath
;
112 Path::splitPathEnvironment(lib
, &tmpPath
);
113 std::set
<std::string
> pathsSeen
;
114 pathsSeen
.insert(defaultPath
);
115 for (auto& d
: tmpPath
)
117 if (!pathsSeen
.count(d
))
123 impl_
->bEnvIsSet_
= true;
127 FilePtr
DataFileFinder::openFile(const DataFileOptions
& options
) const
129 // TODO: There is a small race here, since there is some time between
130 // the exists() calls and actually opening the file. It would be better
131 // to leave the file open after a successful exists() if the desire is to
132 // actually open the file.
133 std::string filename
= findFile(options
);
134 if (filename
.empty())
141 fprintf(debug
, "Opening library file %s\n", fn
);
144 return TextInputFile::openRawHandle(filename
);
147 std::string
DataFileFinder::findFile(const DataFileOptions
& options
) const
149 if (options
.bCurrentDir_
&& Path::exists(options
.filename_
))
151 return options
.filename_
;
153 if (impl_
!= nullptr)
155 std::vector
<std::string
>::const_iterator i
;
156 for (i
= impl_
->searchPath_
.begin(); i
!= impl_
->searchPath_
.end(); ++i
)
158 // TODO: Deal with an empty search path entry more reasonably.
159 std::string testPath
= Path::join(*i
, options
.filename_
);
160 // TODO: Consider skipping directories.
161 if (Path::exists(testPath
))
167 const std::string
& defaultPath
= Impl::getDefaultPath();
168 if (!defaultPath
.empty())
170 std::string testPath
= Path::join(defaultPath
, options
.filename_
);
171 if (Path::exists(testPath
))
178 const char* const envName
= (impl_
!= nullptr ? impl_
->envName_
: nullptr);
179 const bool bEnvIsSet
= (impl_
!= nullptr ? impl_
->bEnvIsSet_
: false);
180 std::string
message(formatString("Library file '%s' not found", options
.filename_
));
181 if (options
.bCurrentDir_
)
183 message
.append(" in current dir nor");
187 message
.append(formatString(" in your %s path nor", envName
));
189 message
.append(" in the default directories.\nThe following paths were searched:");
190 if (options
.bCurrentDir_
)
192 message
.append("\n ");
193 message
.append(Path::getWorkingDirectory());
194 message
.append(" (current dir)");
196 if (impl_
!= nullptr)
198 std::vector
<std::string
>::const_iterator i
;
199 for (i
= impl_
->searchPath_
.begin(); i
!= impl_
->searchPath_
.end(); ++i
)
201 message
.append("\n ");
205 if (!defaultPath
.empty())
207 message
.append("\n ");
208 message
.append(defaultPath
);
209 message
.append(" (default)");
211 if (!bEnvIsSet
&& envName
!= nullptr)
214 formatString("\nYou can set additional directories to search "
215 "with the %s path variable.",
218 GMX_THROW(FileIOError(message
));
220 return std::string();
223 std::vector
<DataFileInfo
> DataFileFinder::enumerateFiles(const DataFileOptions
& options
) const
225 // TODO: Consider if not being able to list one of the directories should
226 // really be a fatal error. Or alternatively, check somewhere else that
227 // paths in GMXLIB are valid.
228 std::vector
<DataFileInfo
> result
;
229 std::vector
<std::string
>::const_iterator i
;
230 if (options
.bCurrentDir_
)
232 std::vector
<std::string
> files
=
233 DirectoryEnumerator::enumerateFilesWithExtension(".", options
.filename_
, false);
234 for (i
= files
.begin(); i
!= files
.end(); ++i
)
236 result
.emplace_back(".", *i
, false);
239 if (impl_
!= nullptr)
241 std::vector
<std::string
>::const_iterator j
;
242 for (j
= impl_
->searchPath_
.begin(); j
!= impl_
->searchPath_
.end(); ++j
)
244 std::vector
<std::string
> files
= DirectoryEnumerator::enumerateFilesWithExtension(
245 j
->c_str(), options
.filename_
, false);
246 for (i
= files
.begin(); i
!= files
.end(); ++i
)
248 result
.emplace_back(*j
, *i
, false);
252 const std::string
& defaultPath
= Impl::getDefaultPath();
253 if (!defaultPath
.empty())
255 std::vector
<std::string
> files
= DirectoryEnumerator::enumerateFilesWithExtension(
256 defaultPath
.c_str(), options
.filename_
, false);
257 for (i
= files
.begin(); i
!= files
.end(); ++i
)
259 result
.emplace_back(defaultPath
, *i
, true);
262 if (result
.empty() && options
.bThrow_
)
264 // TODO: Print the search path as is done in findFile().
266 formatString("Could not find any files ending on '%s' in the "
267 "current directory or the GROMACS library search path",
269 GMX_THROW(FileIOError(message
));