Replace gmx::Mutex with std::mutex
[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)),
316     invokedName_(argc != 0 ? argv[0] : ""),
317     bSourceLayout_(false)
318 {
319     programName_ = Path::getFilename(invokedName_);
320     programName_ = stripSuffixIfPresent(programName_, ".exe");
321
322     commandLine_ = quoteIfNecessary(programName_.c_str());
323     for (int i = 1; i < argc; ++i)
324     {
325         commandLine_.append(" ");
326         commandLine_.append(quoteIfNecessary(argv[i]));
327     }
328 }
329
330 void CommandLineProgramContext::Impl::findBinaryPath() const
331 {
332     if (fullBinaryPath_.empty())
333     {
334         fullBinaryPath_ = findFullBinaryPath(invokedName_, *executableEnv_);
335         fullBinaryPath_ = Path::normalize(Path::resolveSymlinks(fullBinaryPath_));
336         // TODO: Investigate/Consider using a dladdr()-based solution.
337         // Potentially less portable, but significantly simpler, and also works
338         // with user binaries even if they are located in some arbitrary location,
339         // as long as shared libraries are used.
340     }
341 }
342
343 /********************************************************************
344  * CommandLineProgramContext
345  */
346
347 CommandLineProgramContext::CommandLineProgramContext() : impl_(new Impl) {}
348
349 CommandLineProgramContext::CommandLineProgramContext(const char* binaryName) :
350     impl_(new Impl(1, &binaryName, DefaultExecutableEnvironment::create()))
351 {
352 }
353
354 CommandLineProgramContext::CommandLineProgramContext(int argc, const char* const argv[]) :
355     impl_(new Impl(argc, argv, DefaultExecutableEnvironment::create()))
356 {
357 }
358
359 CommandLineProgramContext::CommandLineProgramContext(int                          argc,
360                                                      const char* const            argv[],
361                                                      ExecutableEnvironmentPointer env) :
362     impl_(new Impl(argc, argv, move(env)))
363 {
364 }
365
366 CommandLineProgramContext::~CommandLineProgramContext() {}
367
368 void CommandLineProgramContext::setDisplayName(const std::string& name)
369 {
370     GMX_RELEASE_ASSERT(impl_->displayName_.empty(), "Can only set display name once");
371     impl_->displayName_ = name;
372 }
373
374 const char* CommandLineProgramContext::programName() const
375 {
376     return impl_->programName_.c_str();
377 }
378
379 const char* CommandLineProgramContext::displayName() const
380 {
381     return impl_->displayName_.empty() ? impl_->programName_.c_str() : impl_->displayName_.c_str();
382 }
383
384 const char* CommandLineProgramContext::commandLine() const
385 {
386     return impl_->commandLine_.c_str();
387 }
388
389 const char* CommandLineProgramContext::fullBinaryPath() const
390 {
391     std::lock_guard<std::mutex> lock(impl_->binaryPathMutex_);
392     impl_->findBinaryPath();
393     return impl_->fullBinaryPath_.c_str();
394 }
395
396 InstallationPrefixInfo CommandLineProgramContext::installationPrefix() const
397 {
398     std::lock_guard<std::mutex> lock(impl_->binaryPathMutex_);
399     if (impl_->installationPrefix_.empty())
400     {
401         impl_->findBinaryPath();
402         impl_->installationPrefix_ = Path::normalize(
403                 findInstallationPrefixPath(impl_->fullBinaryPath_, &impl_->bSourceLayout_));
404     }
405     return InstallationPrefixInfo(impl_->installationPrefix_.c_str(), impl_->bSourceLayout_);
406 }
407
408 } // namespace gmx