1061da546Spatrick //===-- CppModuleConfiguration.cpp ----------------------------------------===//
2061da546Spatrick //
3061da546Spatrick // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4061da546Spatrick // See https://llvm.org/LICENSE.txt for license information.
5061da546Spatrick // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6061da546Spatrick //
7061da546Spatrick //===----------------------------------------------------------------------===//
8061da546Spatrick
9061da546Spatrick #include "CppModuleConfiguration.h"
10061da546Spatrick
11061da546Spatrick #include "ClangHost.h"
12061da546Spatrick #include "lldb/Host/FileSystem.h"
13*f6aab3d8Srobert #include "llvm/ADT/Triple.h"
14*f6aab3d8Srobert #include <optional>
15061da546Spatrick
16061da546Spatrick using namespace lldb_private;
17061da546Spatrick
TrySet(llvm::StringRef path)18061da546Spatrick bool CppModuleConfiguration::SetOncePath::TrySet(llvm::StringRef path) {
19061da546Spatrick // Setting for the first time always works.
20061da546Spatrick if (m_first) {
21061da546Spatrick m_path = path.str();
22061da546Spatrick m_valid = true;
23061da546Spatrick m_first = false;
24061da546Spatrick return true;
25061da546Spatrick }
26061da546Spatrick // Changing the path to the same value is fine.
27061da546Spatrick if (m_path == path)
28061da546Spatrick return true;
29061da546Spatrick
30061da546Spatrick // Changing the path after it was already set is not allowed.
31061da546Spatrick m_valid = false;
32061da546Spatrick return false;
33061da546Spatrick }
34061da546Spatrick
35*f6aab3d8Srobert static llvm::SmallVector<std::string, 2>
getTargetIncludePaths(const llvm::Triple & triple)36*f6aab3d8Srobert getTargetIncludePaths(const llvm::Triple &triple) {
37*f6aab3d8Srobert llvm::SmallVector<std::string, 2> paths;
38*f6aab3d8Srobert if (!triple.str().empty()) {
39*f6aab3d8Srobert paths.push_back("/usr/include/" + triple.str());
40*f6aab3d8Srobert if (!triple.getArchName().empty() ||
41*f6aab3d8Srobert triple.getOSAndEnvironmentName().empty())
42*f6aab3d8Srobert paths.push_back(("/usr/include/" + triple.getArchName() + "-" +
43*f6aab3d8Srobert triple.getOSAndEnvironmentName())
44*f6aab3d8Srobert .str());
45*f6aab3d8Srobert }
46*f6aab3d8Srobert return paths;
47*f6aab3d8Srobert }
48*f6aab3d8Srobert
49*f6aab3d8Srobert /// Returns the include path matching the given pattern for the given file
50*f6aab3d8Srobert /// path (or std::nullopt if the path doesn't match the pattern).
51*f6aab3d8Srobert static std::optional<llvm::StringRef>
guessIncludePath(llvm::StringRef path_to_file,llvm::StringRef pattern)52*f6aab3d8Srobert guessIncludePath(llvm::StringRef path_to_file, llvm::StringRef pattern) {
53*f6aab3d8Srobert if (pattern.empty())
54*f6aab3d8Srobert return std::nullopt;
55*f6aab3d8Srobert size_t pos = path_to_file.find(pattern);
56*f6aab3d8Srobert if (pos == llvm::StringRef::npos)
57*f6aab3d8Srobert return std::nullopt;
58*f6aab3d8Srobert
59*f6aab3d8Srobert return path_to_file.substr(0, pos + pattern.size());
60*f6aab3d8Srobert }
61*f6aab3d8Srobert
analyzeFile(const FileSpec & f,const llvm::Triple & triple)62*f6aab3d8Srobert bool CppModuleConfiguration::analyzeFile(const FileSpec &f,
63*f6aab3d8Srobert const llvm::Triple &triple) {
64061da546Spatrick using namespace llvm::sys::path;
65061da546Spatrick // Convert to slashes to make following operations simpler.
66061da546Spatrick std::string dir_buffer = convert_to_slash(f.GetDirectory().GetStringRef());
67061da546Spatrick llvm::StringRef posix_dir(dir_buffer);
68061da546Spatrick
69061da546Spatrick // Check for /c++/vX/ that is used by libc++.
70061da546Spatrick static llvm::Regex libcpp_regex(R"regex(/c[+][+]/v[0-9]/)regex");
71be691f3bSpatrick // If the path is in the libc++ include directory use it as the found libc++
72be691f3bSpatrick // path. Ignore subdirectories such as /c++/v1/experimental as those don't
73be691f3bSpatrick // need to be specified in the header search.
74be691f3bSpatrick if (libcpp_regex.match(f.GetPath()) &&
75be691f3bSpatrick parent_path(posix_dir, Style::posix).endswith("c++")) {
76*f6aab3d8Srobert if (!m_std_inc.TrySet(posix_dir))
77*f6aab3d8Srobert return false;
78*f6aab3d8Srobert if (triple.str().empty())
79*f6aab3d8Srobert return true;
80*f6aab3d8Srobert
81*f6aab3d8Srobert posix_dir.consume_back("c++/v1");
82*f6aab3d8Srobert // Check if this is a target-specific libc++ include directory.
83*f6aab3d8Srobert return m_std_target_inc.TrySet(
84*f6aab3d8Srobert (posix_dir + triple.str() + "/c++/v1").str());
85061da546Spatrick }
86061da546Spatrick
87*f6aab3d8Srobert std::optional<llvm::StringRef> inc_path;
88*f6aab3d8Srobert // Target specific paths contains /usr/include, so we check them first
89*f6aab3d8Srobert for (auto &path : getTargetIncludePaths(triple)) {
90*f6aab3d8Srobert if ((inc_path = guessIncludePath(posix_dir, path)))
91*f6aab3d8Srobert return m_c_target_inc.TrySet(*inc_path);
92*f6aab3d8Srobert }
93*f6aab3d8Srobert if ((inc_path = guessIncludePath(posix_dir, "/usr/include")))
94*f6aab3d8Srobert return m_c_inc.TrySet(*inc_path);
95061da546Spatrick
96061da546Spatrick // File wasn't interesting, continue analyzing.
97061da546Spatrick return true;
98061da546Spatrick }
99061da546Spatrick
100be691f3bSpatrick /// Utility function for just appending two paths.
MakePath(llvm::StringRef lhs,llvm::StringRef rhs)101be691f3bSpatrick static std::string MakePath(llvm::StringRef lhs, llvm::StringRef rhs) {
102be691f3bSpatrick llvm::SmallString<256> result(lhs);
103be691f3bSpatrick llvm::sys::path::append(result, rhs);
104be691f3bSpatrick return std::string(result);
105be691f3bSpatrick }
106be691f3bSpatrick
hasValidConfig()107061da546Spatrick bool CppModuleConfiguration::hasValidConfig() {
108be691f3bSpatrick // We need to have a C and C++ include dir for a valid configuration.
109be691f3bSpatrick if (!m_c_inc.Valid() || !m_std_inc.Valid())
110be691f3bSpatrick return false;
111be691f3bSpatrick
112be691f3bSpatrick // Do some basic sanity checks on the directories that we don't activate
113be691f3bSpatrick // the module when it's clear that it's not usable.
114be691f3bSpatrick const std::vector<std::string> files_to_check = {
115be691f3bSpatrick // * Check that the C library contains at least one random C standard
116be691f3bSpatrick // library header.
117be691f3bSpatrick MakePath(m_c_inc.Get(), "stdio.h"),
118be691f3bSpatrick // * Without a libc++ modulemap file we can't have a 'std' module that
119be691f3bSpatrick // could be imported.
120be691f3bSpatrick MakePath(m_std_inc.Get(), "module.modulemap"),
121be691f3bSpatrick // * Check for a random libc++ header (vector in this case) that has to
122be691f3bSpatrick // exist in a working libc++ setup.
123be691f3bSpatrick MakePath(m_std_inc.Get(), "vector"),
124be691f3bSpatrick };
125be691f3bSpatrick
126be691f3bSpatrick for (llvm::StringRef file_to_check : files_to_check) {
127be691f3bSpatrick if (!FileSystem::Instance().Exists(file_to_check))
128be691f3bSpatrick return false;
129be691f3bSpatrick }
130be691f3bSpatrick
131be691f3bSpatrick return true;
132061da546Spatrick }
133061da546Spatrick
CppModuleConfiguration(const FileSpecList & support_files,const llvm::Triple & triple)134061da546Spatrick CppModuleConfiguration::CppModuleConfiguration(
135*f6aab3d8Srobert const FileSpecList &support_files, const llvm::Triple &triple) {
136061da546Spatrick // Analyze all files we were given to build the configuration.
137061da546Spatrick bool error = !llvm::all_of(support_files,
138061da546Spatrick std::bind(&CppModuleConfiguration::analyzeFile,
139*f6aab3d8Srobert this, std::placeholders::_1, triple));
140061da546Spatrick // If we have a valid configuration at this point, set the
141061da546Spatrick // include directories and module list that should be used.
142061da546Spatrick if (!error && hasValidConfig()) {
143061da546Spatrick // Calculate the resource directory for LLDB.
144061da546Spatrick llvm::SmallString<256> resource_dir;
145061da546Spatrick llvm::sys::path::append(resource_dir, GetClangResourceDir().GetPath(),
146061da546Spatrick "include");
147dda28197Spatrick m_resource_inc = std::string(resource_dir.str());
148061da546Spatrick
149061da546Spatrick // This order matches the way Clang orders these directories.
150be691f3bSpatrick m_include_dirs = {m_std_inc.Get().str(), m_resource_inc,
151be691f3bSpatrick m_c_inc.Get().str()};
152*f6aab3d8Srobert if (m_c_target_inc.Valid())
153*f6aab3d8Srobert m_include_dirs.push_back(m_c_target_inc.Get().str());
154*f6aab3d8Srobert if (m_std_target_inc.Valid())
155*f6aab3d8Srobert m_include_dirs.push_back(m_std_target_inc.Get().str());
156061da546Spatrick m_imported_modules = {"std"};
157061da546Spatrick }
158061da546Spatrick }
159