Update instructions in containers.rst
[gromacs.git] / src / gromacs / commandline / cmdlineprogramcontext.cpp
blobdd573cc1abe4ffbae0bc606aff98f9a68e46c4ce
1 /*
2 * This file is part of the GROMACS molecular simulation package.
4 * Copyright (c) 2012,2013,2014,2015,2016 by the GROMACS development team.
5 * Copyright (c) 2017,2018,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.
36 /*! \internal \file
37 * \brief
38 * Implements gmx::CommandLineProgramContext.
40 * See \linktodevmanual{relocatable-binaries,developer guide section on
41 * relocatable binaries} for explanation of the searching logic.
43 * \author Teemu Murtola <teemu.murtola@gmail.com>
44 * \ingroup module_commandline
46 #include "gmxpre.h"
48 #include "cmdlineprogramcontext.h"
50 #include "config.h"
52 #include <cstdlib>
53 #include <cstring>
55 #include <string>
56 #include <vector>
58 #include "buildinfo.h"
59 #include "gromacs/utility/exceptions.h"
60 #include "gromacs/utility/gmxassert.h"
61 #include "gromacs/utility/mutex.h"
62 #include "gromacs/utility/path.h"
63 #include "gromacs/utility/stringutil.h"
65 namespace gmx
68 namespace
71 //! \addtogroup module_commandline
72 //! \{
74 /*! \brief
75 * Quotes a string if it contains spaces.
77 std::string quoteIfNecessary(const char* str)
79 const bool bSpaces = (std::strchr(str, ' ') != nullptr);
80 if (bSpaces)
82 return formatString("'%s'", str);
84 return str;
87 /*! \brief
88 * Default implementation for IExecutableEnvironment.
90 * Used if IExecutableEnvironment is not explicitly provided when
91 * constructing CommandLineProgramContext.
93 class DefaultExecutableEnvironment : public IExecutableEnvironment
95 public:
96 //! Allocates a default environment.
97 static ExecutableEnvironmentPointer create()
99 return ExecutableEnvironmentPointer(new DefaultExecutableEnvironment());
102 DefaultExecutableEnvironment() : initialWorkingDirectory_(Path::getWorkingDirectory()) {}
104 std::string getWorkingDirectory() const override { return initialWorkingDirectory_; }
105 std::vector<std::string> getExecutablePaths() const override
107 return Path::getExecutablePaths();
110 private:
111 std::string initialWorkingDirectory_;
114 /*! \brief
115 * Finds the absolute path of the binary from \c argv[0].
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.
121 * If a binary with the given name cannot be located, \p invokedName is
122 * returned.
124 std::string findFullBinaryPath(const std::string& invokedName, const IExecutableEnvironment& env)
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"))
132 searchName.append(".exe");
134 #endif
135 if (!Path::containsDirectory(searchName))
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)
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))
146 return testPath;
150 else if (!Path::isAbsolute(searchName))
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;
158 return searchName;
161 /*! \brief
162 * Returns whether given path contains files from `share/top/`.
164 * Only checks for a single file that has an uncommon enough name.
166 bool isAcceptableLibraryPath(const std::string& path)
168 return Path::exists(Path::join(path, "residuetypes.dat"));
171 /*! \brief
172 * Returns whether given path prefix contains files from `share/top/`.
174 * \param[in] path Path prefix to check.
175 * \returns `true` if \p path contains the data files.
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().
181 bool isAcceptableLibraryPathPrefix(const std::string& path)
183 std::string testPath = Path::join(path, GMX_INSTALL_GMXDATADIR, "top");
184 return isAcceptableLibraryPath(testPath);
187 /*! \brief
188 * Returns a fallback installation prefix path.
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.
194 std::string findFallbackInstallationPrefixPath()
196 #if !GMX_NATIVE_WINDOWS
197 if (!isAcceptableLibraryPathPrefix(CMAKE_INSTALL_PREFIX))
199 if (isAcceptableLibraryPathPrefix("/usr/local"))
201 return "/usr/local";
203 if (isAcceptableLibraryPathPrefix("/usr"))
205 return "/usr";
207 if (isAcceptableLibraryPathPrefix("/opt"))
209 return "/opt";
212 #endif
213 return CMAKE_INSTALL_PREFIX;
216 /*! \brief
217 * Generic function to find data files based on path of the binary.
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.
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...
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.
232 std::string findInstallationPrefixPath(const std::string& binaryPath, bool* bSourceLayout)
234 *bSourceLayout = false;
235 // Don't search anything if binary cannot be found.
236 if (Path::exists(binaryPath))
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))
251 std::string testPath = Path::join(CMAKE_SOURCE_DIR, "share/top");
252 if (isAcceptableLibraryPath(testPath))
254 *bSourceLayout = true;
255 return CMAKE_SOURCE_DIR;
258 #endif
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())
264 if (isAcceptableLibraryPathPrefix(searchPath))
266 return searchPath;
268 searchPath = Path::getParentPath(searchPath);
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();
277 //! \}
279 } // namespace
281 /********************************************************************
282 * CommandLineProgramContext::Impl
285 class CommandLineProgramContext::Impl
287 public:
288 Impl();
289 Impl(int argc, const char* const argv[], ExecutableEnvironmentPointer env);
291 /*! \brief
292 * Finds the full binary path if it isn't searched yet.
294 * Sets \a fullBinaryPath_ if it isn't set yet.
296 * The \a binaryPathMutex_ should be locked by the caller before
297 * calling this function.
299 void findBinaryPath() const;
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 Mutex binaryPathMutex_;
312 CommandLineProgramContext::Impl::Impl() : programName_("GROMACS"), bSourceLayout_(false) {}
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)
319 programName_ = Path::getFilename(invokedName_);
320 programName_ = stripSuffixIfPresent(programName_, ".exe");
322 commandLine_ = quoteIfNecessary(programName_.c_str());
323 for (int i = 1; i < argc; ++i)
325 commandLine_.append(" ");
326 commandLine_.append(quoteIfNecessary(argv[i]));
330 void CommandLineProgramContext::Impl::findBinaryPath() const
332 if (fullBinaryPath_.empty())
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.
343 /********************************************************************
344 * CommandLineProgramContext
347 CommandLineProgramContext::CommandLineProgramContext() : impl_(new Impl) {}
349 CommandLineProgramContext::CommandLineProgramContext(const char* binaryName) :
350 impl_(new Impl(1, &binaryName, DefaultExecutableEnvironment::create()))
354 CommandLineProgramContext::CommandLineProgramContext(int argc, const char* const argv[]) :
355 impl_(new Impl(argc, argv, DefaultExecutableEnvironment::create()))
359 CommandLineProgramContext::CommandLineProgramContext(int argc,
360 const char* const argv[],
361 ExecutableEnvironmentPointer env) :
362 impl_(new Impl(argc, argv, move(env)))
366 CommandLineProgramContext::~CommandLineProgramContext() {}
368 void CommandLineProgramContext::setDisplayName(const std::string& name)
370 GMX_RELEASE_ASSERT(impl_->displayName_.empty(), "Can only set display name once");
371 impl_->displayName_ = name;
374 const char* CommandLineProgramContext::programName() const
376 return impl_->programName_.c_str();
379 const char* CommandLineProgramContext::displayName() const
381 return impl_->displayName_.empty() ? impl_->programName_.c_str() : impl_->displayName_.c_str();
384 const char* CommandLineProgramContext::commandLine() const
386 return impl_->commandLine_.c_str();
389 const char* CommandLineProgramContext::fullBinaryPath() const
391 lock_guard<Mutex> lock(impl_->binaryPathMutex_);
392 impl_->findBinaryPath();
393 return impl_->fullBinaryPath_.c_str();
396 InstallationPrefixInfo CommandLineProgramContext::installationPrefix() const
398 lock_guard<Mutex> lock(impl_->binaryPathMutex_);
399 if (impl_->installationPrefix_.empty())
401 impl_->findBinaryPath();
402 impl_->installationPrefix_ = Path::normalize(
403 findInstallationPrefixPath(impl_->fullBinaryPath_, &impl_->bSourceLayout_));
405 return InstallationPrefixInfo(impl_->installationPrefix_.c_str(), impl_->bSourceLayout_);
408 } // namespace gmx