SYCL: Avoid using no_init read accessor in rocFFT
[alexxy/gromacs.git] / src / gromacs / commandline / cmdlineprogramcontext.cpp
1 /*
2  * This file is part of the GROMACS molecular simulation package.
3  *
4  * Copyright (c) 2012,2013,2014,2015,2016 by the GROMACS development team.
5  * Copyright (c) 2017,2018,2019,2020,2021, 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.
9  *
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.
14  *
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.
19  *
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.
24  *
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.
32  *
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.
35  */
36 /*! \internal \file
37  * \brief
38  * Implements gmx::CommandLineProgramContext.
39  *
40  * See \linktodevmanual{relocatable-binaries,developer guide section on
41  * relocatable binaries} for explanation of the searching logic.
42  *
43  * \author Teemu Murtola <teemu.murtola@gmail.com>
44  * \ingroup module_commandline
45  */
46 #include "gmxpre.h"
47
48 #include "cmdlineprogramcontext.h"
49
50 #include "config.h"
51
52 #include <cstdlib>
53 #include <cstring>
54
55 #include <mutex>
56 #include <string>
57 #include <vector>
58
59 #include "buildinfo.h"
60 #include "gromacs/utility/exceptions.h"
61 #include "gromacs/utility/gmxassert.h"
62 #include "gromacs/utility/path.h"
63 #include "gromacs/utility/stringutil.h"
64
65 namespace gmx
66 {
67
68 namespace
69 {
70
71 //! \addtogroup module_commandline
72 //! \{
73
74 /*! \brief
75  * Quotes a string if it contains spaces.
76  */
77 std::string quoteIfNecessary(const char* str)
78 {
79     const bool bSpaces = (std::strchr(str, ' ') != nullptr);
80     if (bSpaces)
81     {
82         return formatString("'%s'", str);
83     }
84     return str;
85 }
86
87 /*! \brief
88  * Default implementation for IExecutableEnvironment.
89  *
90  * Used if IExecutableEnvironment is not explicitly provided when
91  * constructing CommandLineProgramContext.
92  */
93 class DefaultExecutableEnvironment : public IExecutableEnvironment
94 {
95 public:
96     //! Allocates a default environment.
97     static ExecutableEnvironmentPointer create()
98     {
99         return ExecutableEnvironmentPointer(new DefaultExecutableEnvironment());
100     }
101
102     DefaultExecutableEnvironment() : initialWorkingDirectory_(Path::getWorkingDirectory()) {}
103
104     std::string getWorkingDirectory() const override { return initialWorkingDirectory_; }
105     std::vector<std::string> getExecutablePaths() const override
106     {
107         return Path::getExecutablePaths();
108     }
109
110 private:
111     std::string initialWorkingDirectory_;
112 };
113
114 /*! \brief
115  * Finds the absolute path of the binary from \c argv[0].
116  *
117  * \param[in] invokedName \c argv[0] the binary was invoked with.
118  * \param[in] env         Executable environment.
119  * \returns   The full path of the binary.
120  *
121  * If a binary with the given name cannot be located, \p invokedName is
122  * returned.
123  */
124 std::string findFullBinaryPath(const std::string& invokedName, const IExecutableEnvironment& env)
125 {
126     std::string searchName = invokedName;
127     // On Windows & Cygwin we need to add the .exe extension,
128     // or we wont be able to detect that the file exists.
129 #if GMX_NATIVE_WINDOWS || GMX_CYGWIN
130     if (!endsWith(searchName, ".exe"))
131     {
132         searchName.append(".exe");
133     }
134 #endif
135     if (!Path::containsDirectory(searchName))
136     {
137         // No directory in name means it must be in the path - search it!
138         std::vector<std::string>                 pathEntries = env.getExecutablePaths();
139         std::vector<std::string>::const_iterator i;
140         for (i = pathEntries.begin(); i != pathEntries.end(); ++i)
141         {
142             const std::string& dir      = i->empty() ? env.getWorkingDirectory() : *i;
143             std::string        testPath = Path::join(dir, searchName);
144             if (File::exists(testPath, File::returnFalseOnError))
145             {
146                 return testPath;
147             }
148         }
149     }
150     else if (!Path::isAbsolute(searchName))
151     {
152         // Name contains directories, but is not absolute, i.e.,
153         // it is relative to the current directory.
154         std::string cwd      = env.getWorkingDirectory();
155         std::string testPath = Path::join(cwd, searchName);
156         return testPath;
157     }
158     return searchName;
159 }
160
161 /*! \brief
162  * Returns whether given path contains files from `share/top/`.
163  *
164  * Only checks for a single file that has an uncommon enough name.
165  */
166 bool isAcceptableLibraryPath(const std::string& path)
167 {
168     return Path::exists(Path::join(path, "residuetypes.dat"));
169 }
170
171 /*! \brief
172  * Returns whether given path prefix contains files from `share/top/`.
173  *
174  * \param[in]  path   Path prefix to check.
175  * \returns  `true` if \p path contains the data files.
176  *
177  * Checks whether \p path could be the installation prefix where `share/top/`
178  * files have been installed:  appends the relative installation path of the
179  * data files and calls isAcceptableLibraryPath().
180  */
181 bool isAcceptableLibraryPathPrefix(const std::string& path)
182 {
183     std::string testPath = Path::join(path, GMX_INSTALL_GMXDATADIR, "top");
184     return isAcceptableLibraryPath(testPath);
185 }
186
187 /*! \brief
188  * Returns a fallback installation prefix path.
189  *
190  * Checks a few standard locations for the data files before returning a
191  * configure-time hard-coded path.  The hard-coded path is preferred if it
192  * actually contains the data files, though.
193  */
194 std::string findFallbackInstallationPrefixPath()
195 {
196 #if !GMX_NATIVE_WINDOWS
197     if (!isAcceptableLibraryPathPrefix(CMAKE_INSTALL_PREFIX))
198     {
199         if (isAcceptableLibraryPathPrefix("/usr/local"))
200         {
201             return "/usr/local";
202         }
203         if (isAcceptableLibraryPathPrefix("/usr"))
204         {
205             return "/usr";
206         }
207         if (isAcceptableLibraryPathPrefix("/opt"))
208         {
209             return "/opt";
210         }
211     }
212 #endif
213     return CMAKE_INSTALL_PREFIX;
214 }
215
216 /*! \brief
217  * Generic function to find data files based on path of the binary.
218  *
219  * \param[in]  binaryPath     Absolute path to the binary.
220  * \param[out] bSourceLayout  Set to `true` if the binary is run from
221  *     the build tree and the original source directory can be found.
222  * \returns  Path to the `share/top/` data files.
223  *
224  * The search based on the path only works if the binary is in the same
225  * relative path as the installed \Gromacs binaries.  If the binary is
226  * somewhere else, a hard-coded fallback is used.  This doesn't work if the
227  * binaries are somewhere else than the path given during configure time...
228  *
229  * Extra logic is present to allow running binaries from the build tree such
230  * that they use up-to-date data files from the source tree.
231  */
232 std::string findInstallationPrefixPath(const std::string& binaryPath, bool* bSourceLayout)
233 {
234     *bSourceLayout = false;
235     // Don't search anything if binary cannot be found.
236     if (Path::exists(binaryPath))
237     {
238         // Remove the executable name.
239         std::string searchPath = Path::getParentPath(binaryPath);
240         // If running directly from the build tree, try to use the source
241         // directory.
242 #if (defined CMAKE_SOURCE_DIR && defined CMAKE_BINARY_DIR)
243         std::string buildBinPath;
244 #    ifdef CMAKE_INTDIR /*In multi-configuration build systems the output subdirectory*/
245         buildBinPath = Path::join(CMAKE_BINARY_DIR, "bin", CMAKE_INTDIR);
246 #    else
247         buildBinPath = Path::join(CMAKE_BINARY_DIR, "bin");
248 #    endif
249         if (Path::isEquivalent(searchPath, buildBinPath))
250         {
251             std::string testPath = Path::join(CMAKE_SOURCE_DIR, "share/top");
252             if (isAcceptableLibraryPath(testPath))
253             {
254                 *bSourceLayout = true;
255                 return CMAKE_SOURCE_DIR;
256             }
257         }
258 #endif
259
260         // Use the executable path to (try to) find the library dir.
261         // TODO: Consider only going up exactly the required number of levels.
262         while (!searchPath.empty())
263         {
264             if (isAcceptableLibraryPathPrefix(searchPath))
265             {
266                 return searchPath;
267             }
268             searchPath = Path::getParentPath(searchPath);
269         }
270     }
271
272     // End of smart searching. If we didn't find it in our parent tree,
273     // or if the program name wasn't set, return a fallback.
274     return findFallbackInstallationPrefixPath();
275 }
276
277 //! \}
278
279 } // namespace
280
281 /********************************************************************
282  * CommandLineProgramContext::Impl
283  */
284
285 class CommandLineProgramContext::Impl
286 {
287 public:
288     Impl();
289     Impl(int argc, const char* const argv[], ExecutableEnvironmentPointer env);
290
291     /*! \brief
292      * Finds the full binary path if it isn't searched yet.
293      *
294      * Sets \a fullBinaryPath_ if it isn't set yet.
295      *
296      * The \a binaryPathMutex_ should be locked by the caller before
297      * calling this function.
298      */
299     void findBinaryPath() const;
300
301     ExecutableEnvironmentPointer executableEnv_;
302     std::string                  invokedName_;
303     std::string                  programName_;
304     std::string                  displayName_;
305     std::string                  commandLine_;
306     mutable std::string          fullBinaryPath_;
307     mutable std::string          installationPrefix_;
308     mutable bool                 bSourceLayout_;
309     mutable std::mutex           binaryPathMutex_;
310 };
311
312 CommandLineProgramContext::Impl::Impl() : programName_("GROMACS"), bSourceLayout_(false) {}
313
314 CommandLineProgramContext::Impl::Impl(int argc, const char* const argv[], ExecutableEnvironmentPointer env) :
315     executableEnv_(std::move(env)), invokedName_(argc != 0 ? argv[0] : ""), bSourceLayout_(false)
316 {
317     programName_ = Path::getFilename(invokedName_);
318     programName_ = stripSuffixIfPresent(programName_, ".exe");
319
320     commandLine_ = quoteIfNecessary(programName_.c_str());
321     for (int i = 1; i < argc; ++i)
322     {
323         commandLine_.append(" ");
324         commandLine_.append(quoteIfNecessary(argv[i]));
325     }
326 }
327
328 void CommandLineProgramContext::Impl::findBinaryPath() const
329 {
330     if (fullBinaryPath_.empty())
331     {
332         fullBinaryPath_ = findFullBinaryPath(invokedName_, *executableEnv_);
333         fullBinaryPath_ = Path::normalize(Path::resolveSymlinks(fullBinaryPath_));
334         // TODO: Investigate/Consider using a dladdr()-based solution.
335         // Potentially less portable, but significantly simpler, and also works
336         // with user binaries even if they are located in some arbitrary location,
337         // as long as shared libraries are used.
338     }
339 }
340
341 /********************************************************************
342  * CommandLineProgramContext
343  */
344
345 CommandLineProgramContext::CommandLineProgramContext() : impl_(new Impl) {}
346
347 CommandLineProgramContext::CommandLineProgramContext(const char* binaryName) :
348     impl_(new Impl(1, &binaryName, DefaultExecutableEnvironment::create()))
349 {
350 }
351
352 CommandLineProgramContext::CommandLineProgramContext(int argc, const char* const argv[]) :
353     impl_(new Impl(argc, argv, DefaultExecutableEnvironment::create()))
354 {
355 }
356
357 CommandLineProgramContext::CommandLineProgramContext(int                          argc,
358                                                      const char* const            argv[],
359                                                      ExecutableEnvironmentPointer env) :
360     impl_(new Impl(argc, argv, move(env)))
361 {
362 }
363
364 CommandLineProgramContext::~CommandLineProgramContext() {}
365
366 void CommandLineProgramContext::setDisplayName(const std::string& name)
367 {
368     GMX_RELEASE_ASSERT(impl_->displayName_.empty(), "Can only set display name once");
369     impl_->displayName_ = name;
370 }
371
372 const char* CommandLineProgramContext::programName() const
373 {
374     return impl_->programName_.c_str();
375 }
376
377 const char* CommandLineProgramContext::displayName() const
378 {
379     return impl_->displayName_.empty() ? impl_->programName_.c_str() : impl_->displayName_.c_str();
380 }
381
382 const char* CommandLineProgramContext::commandLine() const
383 {
384     return impl_->commandLine_.c_str();
385 }
386
387 const char* CommandLineProgramContext::fullBinaryPath() const
388 {
389     std::lock_guard<std::mutex> lock(impl_->binaryPathMutex_);
390     impl_->findBinaryPath();
391     return impl_->fullBinaryPath_.c_str();
392 }
393
394 InstallationPrefixInfo CommandLineProgramContext::installationPrefix() const
395 {
396     std::lock_guard<std::mutex> lock(impl_->binaryPathMutex_);
397     if (impl_->installationPrefix_.empty())
398     {
399         impl_->findBinaryPath();
400         impl_->installationPrefix_ = Path::normalize(
401                 findInstallationPrefixPath(impl_->fullBinaryPath_, &impl_->bSourceLayout_));
402     }
403     return InstallationPrefixInfo(impl_->installationPrefix_.c_str(), impl_->bSourceLayout_);
404 }
405
406 } // namespace gmx