0ebd30a4f503902839fd5485185a25b396f9e21e
[alexxy/gromacs.git] / src / gromacs / utility / datafilefinder.cpp
1 /*
2  * This file is part of the GROMACS molecular simulation package.
3  *
4  * Copyright (c) 2014,2015,2016,2017,2018, by the GROMACS development team, led by
5  * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
6  * and including many others, as listed in the AUTHORS file in the
7  * top-level source directory and at http://www.gromacs.org.
8  *
9  * GROMACS is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU Lesser General Public License
11  * as published by the Free Software Foundation; either version 2.1
12  * of the License, or (at your option) any later version.
13  *
14  * GROMACS is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17  * Lesser General Public License for more details.
18  *
19  * You should have received a copy of the GNU Lesser General Public
20  * License along with GROMACS; if not, see
21  * http://www.gnu.org/licenses, or write to the Free Software Foundation,
22  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA.
23  *
24  * If you want to redistribute modifications to GROMACS, please
25  * consider that scientific software is very special. Version
26  * control is crucial - bugs must be traceable. We will be happy to
27  * consider code for inclusion in the official distribution, but
28  * derived work must not be called official GROMACS. Details are found
29  * in the README & COPYING files - if they are missing, get the
30  * official version at http://www.gromacs.org.
31  *
32  * To help us fund GROMACS development, we humbly ask that you cite
33  * the research papers on the package. Check out http://www.gromacs.org.
34  */
35 /*! \internal \file
36  * \brief
37  * Implements gmx::DataFileFinder.
38  *
39  * \author Teemu Murtola <teemu.murtola@gmail.com>
40  * \ingroup module_utility
41  */
42 #include "gmxpre.h"
43
44 #include "datafilefinder.h"
45
46 #include <cstdlib>
47
48 #include <set>
49 #include <string>
50 #include <vector>
51
52 #include "buildinfo.h"
53 #include "gromacs/utility/directoryenumerator.h"
54 #include "gromacs/utility/exceptions.h"
55 #include "gromacs/utility/filestream.h"
56 #include "gromacs/utility/path.h"
57 #include "gromacs/utility/programcontext.h"
58 #include "gromacs/utility/stringutil.h"
59
60 namespace gmx
61 {
62
63 /********************************************************************
64  * DataFileFinder::Impl
65  */
66
67 class DataFileFinder::Impl
68 {
69     public:
70         static std::string getDefaultPath();
71
72         Impl() : envName_(nullptr), bEnvIsSet_(false) {}
73
74         const char               *envName_;
75         bool                      bEnvIsSet_;
76         std::vector<std::string>  searchPath_;
77 };
78
79 std::string DataFileFinder::Impl::getDefaultPath()
80 {
81     const InstallationPrefixInfo installPrefix
82         = getProgramContext().installationPrefix();
83     if (!isNullOrEmpty(installPrefix.path))
84     {
85         const char *const dataPath
86             = installPrefix.bSourceLayout ? "share" : DATA_INSTALL_DIR;
87         return Path::join(installPrefix.path, dataPath, "top");
88     }
89     return std::string();
90 }
91
92 /********************************************************************
93  * DataFileFinder
94  */
95
96 DataFileFinder::DataFileFinder()
97     : impl_(nullptr)
98 {
99 }
100
101 DataFileFinder::~DataFileFinder()
102 {
103 }
104
105 void DataFileFinder::setSearchPathFromEnv(const char *envVarName)
106 {
107     if (!impl_.get())
108     {
109         impl_.reset(new Impl());
110     }
111     impl_->envName_ = envVarName;
112     const char *const lib = getenv(envVarName);
113     if (!isNullOrEmpty(lib))
114     {
115         std::vector<std::string>   &path        = impl_->searchPath_; // convenience
116         const std::string           defaultPath = impl_->getDefaultPath();
117         std::vector<std::string>    tmpPath;
118         Path::splitPathEnvironment(lib, &tmpPath);
119         std::set<std::string>       pathsSeen;
120         pathsSeen.insert(defaultPath);
121         for (auto &d : tmpPath)
122         {
123             if (!pathsSeen.count(d))
124             {
125                 path.push_back(d);
126                 pathsSeen.insert(d);
127             }
128         }
129         impl_->bEnvIsSet_ = true;
130     }
131 }
132
133 FILE *DataFileFinder::openFile(const DataFileOptions &options) const
134 {
135     // TODO: There is a small race here, since there is some time between
136     // the exists() calls and actually opening the file.  It would be better
137     // to leave the file open after a successful exists() if the desire is to
138     // actually open the file.
139     std::string filename = findFile(options);
140     if (filename.empty())
141     {
142         return nullptr;
143     }
144 #if 0
145     if (debug)
146     {
147         fprintf(debug, "Opening library file %s\n", fn);
148     }
149 #endif
150     return TextInputFile::openRawHandle(filename);
151 }
152
153 std::string DataFileFinder::findFile(const DataFileOptions &options) const
154 {
155     if (options.bCurrentDir_ && Path::exists(options.filename_))
156     {
157         return options.filename_;
158     }
159     if (impl_ != nullptr)
160     {
161         std::vector<std::string>::const_iterator i;
162         for (i = impl_->searchPath_.begin(); i != impl_->searchPath_.end(); ++i)
163         {
164             // TODO: Deal with an empty search path entry more reasonably.
165             std::string testPath = Path::join(*i, options.filename_);
166             // TODO: Consider skipping directories.
167             if (Path::exists(testPath))
168             {
169                 return testPath;
170             }
171         }
172     }
173     const std::string &defaultPath = Impl::getDefaultPath();
174     if (!defaultPath.empty())
175     {
176         std::string testPath = Path::join(defaultPath, options.filename_);
177         if (Path::exists(testPath))
178         {
179             return testPath;
180         }
181     }
182     if (options.bThrow_)
183     {
184         const char *const envName   = (impl_ != nullptr ? impl_->envName_ : nullptr);
185         const bool        bEnvIsSet = (impl_ != nullptr ? impl_->bEnvIsSet_ : false);
186         std::string       message(
187                 formatString("Library file '%s' not found", options.filename_));
188         if (options.bCurrentDir_)
189         {
190             message.append(" in current dir nor");
191         }
192         if (bEnvIsSet)
193         {
194             message.append(formatString(" in your %s path nor", envName));
195         }
196         message.append(" in the default directories.\nThe following paths were searched:");
197         if (options.bCurrentDir_)
198         {
199             message.append("\n  ");
200             message.append(Path::getWorkingDirectory());
201             message.append(" (current dir)");
202         }
203         if (impl_ != nullptr)
204         {
205             std::vector<std::string>::const_iterator i;
206             for (i = impl_->searchPath_.begin(); i != impl_->searchPath_.end(); ++i)
207             {
208                 message.append("\n  ");
209                 message.append(*i);
210             }
211         }
212         if (!defaultPath.empty())
213         {
214             message.append("\n  ");
215             message.append(defaultPath);
216             message.append(" (default)");
217         }
218         if (!bEnvIsSet && envName != nullptr)
219         {
220             message.append(
221                     formatString("\nYou can set additional directories to search "
222                                  "with the %s path variable.", envName));
223         }
224         GMX_THROW(FileIOError(message));
225     }
226     return std::string();
227 }
228
229 std::vector<DataFileInfo>
230 DataFileFinder::enumerateFiles(const DataFileOptions &options) const
231 {
232     // TODO: Consider if not being able to list one of the directories should
233     // really be a fatal error. Or alternatively, check somewhere else that
234     // paths in GMXLIB are valid.
235     std::vector<DataFileInfo>                result;
236     std::vector<std::string>::const_iterator i;
237     if (options.bCurrentDir_)
238     {
239         std::vector<std::string> files
240             = DirectoryEnumerator::enumerateFilesWithExtension(
241                         ".", options.filename_, false);
242         for (i = files.begin(); i != files.end(); ++i)
243         {
244             result.emplace_back(".", *i, false);
245         }
246     }
247     if (impl_ != nullptr)
248     {
249         std::vector<std::string>::const_iterator j;
250         for (j = impl_->searchPath_.begin(); j != impl_->searchPath_.end(); ++j)
251         {
252             std::vector<std::string> files
253                 = DirectoryEnumerator::enumerateFilesWithExtension(
254                             j->c_str(), options.filename_, false);
255             for (i = files.begin(); i != files.end(); ++i)
256             {
257                 result.emplace_back(*j, *i, false);
258             }
259         }
260     }
261     const std::string &defaultPath = Impl::getDefaultPath();
262     if (!defaultPath.empty())
263     {
264         std::vector<std::string> files
265             = DirectoryEnumerator::enumerateFilesWithExtension(
266                         defaultPath.c_str(), options.filename_, false);
267         for (i = files.begin(); i != files.end(); ++i)
268         {
269             result.emplace_back(defaultPath, *i, true);
270         }
271     }
272     if (result.empty() && options.bThrow_)
273     {
274         // TODO: Print the search path as is done in findFile().
275         std::string message(
276                 formatString("Could not find any files ending on '%s' in the "
277                              "current directory or the GROMACS library search path",
278                              options.filename_));
279         GMX_THROW(FileIOError(message));
280     }
281     return result;
282 }
283
284 } // namespace gmx