File indexing completed on 2024-05-19 05:41:59
0001 // ct_lvtclp_compilerutil.cpp -*-C++-*- 0002 0003 /* 0004 // Copyright 2023 Codethink Ltd <codethink@codethink.co.uk> 0005 // SPDX-License-Identifier: Apache-2.0 0006 // 0007 // Licensed under the Apache License, Version 2.0 (the "License"); 0008 // you may not use this file except in compliance with the License. 0009 // You may obtain a copy of the License at 0010 // 0011 // http://www.apache.org/licenses/LICENSE-2.0 0012 // 0013 // Unless required by applicable law or agreed to in writing, software 0014 // distributed under the License is distributed on an "AS IS" BASIS, 0015 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 0016 // See the License for the specific language governing permissions and 0017 // limitations under the License. 0018 */ 0019 0020 #include <ct_lvtclp_compilerutil.h> 0021 #include <ct_lvtshr_stringhelpers.h> 0022 0023 #include <filesystem> 0024 #include <initializer_list> 0025 #include <iostream> 0026 #include <istream> 0027 #include <optional> 0028 0029 #include <QDebug> 0030 0031 #include <clang/Frontend/FrontendActions.h> 0032 #include <clang/Tooling/Tooling.h> 0033 0034 namespace { 0035 0036 #ifdef __linux__ 0037 std::string trimLeadingSpaces(const std::string& str) 0038 // remove leading spaces from str 0039 { 0040 std::size_t loc = str.find_first_not_of(' '); 0041 return str.substr(loc); 0042 } 0043 0044 std::optional<std::vector<std::string>> tryCompiler(const std::string& compiler) 0045 { 0046 std::optional<std::string> compilerOut = Codethink::lvtclp::CompilerUtil::runCompiler(compiler); 0047 if (!compilerOut) { 0048 return {}; 0049 } 0050 0051 bool inIncludeSearchBlock = false; 0052 const char *blockStart = "#include <...> search starts here:"; 0053 const char *blockEnd = "End of search list."; 0054 0055 std::vector<std::string> includes; 0056 0057 // now collect each line between blockStart and blockEnd 0058 std::stringstream ss(*compilerOut); 0059 std::string line; 0060 while (std::getline(ss, line)) { 0061 if (line == blockEnd) { 0062 break; 0063 } 0064 0065 if (inIncludeSearchBlock) { 0066 includes.push_back(trimLeadingSpaces(line)); 0067 } 0068 0069 if (line == blockStart) { 0070 inIncludeSearchBlock = true; 0071 } 0072 } 0073 0074 return includes; 0075 } 0076 0077 std::vector<std::string> findLinuxIncludes() 0078 // run tryCompiler on clang++ or g++ with various version suffixes, 0079 // returning the first successful result 0080 { 0081 // lower versions first because hopefully things are backwards compatible 0082 std::initializer_list<std::string> suffixes = { 0083 "-9", 0084 "-10", 0085 "-11", 0086 "-12", 0087 "-13", 0088 "-14", 0089 "-15", 0090 "", 0091 }; 0092 0093 for (const char *compiler : {"clang++", "g++"}) { 0094 for (const std::string& suffix : suffixes) { 0095 const std::string fullName = compiler + suffix; 0096 std::optional<std::vector<std::string>> res = tryCompiler(fullName); 0097 if (res) { 0098 return std::move(*res); 0099 } 0100 } 0101 } 0102 0103 return {}; 0104 } 0105 #endif // __linux__ 0106 0107 #ifdef __APPLE__ 0108 static std::vector<std::string> findMacIncludes() 0109 { 0110 // Apple's clang has some include paths built in, which we don't get 0111 // from our open source libclang. We need to add this include path. 0112 // The path should look something like this, but we need to get the 0113 // right version number 0114 // /Library/Developer/CommandLineTools/usr/lib/clang/12.0.5/include 0115 // Asking apple's clang (e.g. using findLinuxIncludes) yields unbuildable 0116 // headers 0117 0118 std::filesystem::path base("/Library/Developer/CommandLineTools/usr/lib/clang"); 0119 if (!std::filesystem::is_directory(base)) { 0120 std::cerr << "Cannot find XCode CommandLineTools include directory: " << base << std::endl; 0121 return {}; 0122 } 0123 0124 // find version number by trying each subdirectory of base 0125 for (const auto& subdir : std::filesystem::directory_iterator(base)) { 0126 const std::filesystem::path& versionDir = subdir; 0127 std::filesystem::path includeDir = versionDir / "include"; 0128 0129 if (std::filesystem::is_directory(includeDir)) { 0130 return {includeDir.string()}; 0131 } 0132 } 0133 0134 std::cerr << "Cannot find XCode CommandLineTools include directory" << std::endl; 0135 return {}; 0136 } 0137 #endif // __APPLE__ 0138 0139 } // unnamed namespace 0140 0141 namespace Codethink::lvtclp { 0142 0143 std::vector<std::string> CompilerUtil::findSystemIncludes() 0144 { 0145 #ifdef __APPLE__ 0146 return findMacIncludes(); 0147 #endif 0148 #ifdef __linux__ 0149 return findLinuxIncludes(); 0150 #endif 0151 return {}; 0152 } 0153 0154 #ifdef __linux__ 0155 std::optional<std::string> CompilerUtil::runCompiler(const std::string& compiler) 0156 // attempt to run something like 0157 // compiler -E -v - </dev/null 1>/dev/null 2>out 0158 // if compiler isn't in path, return {} 0159 // if compiler doesn't return EXIT_SUCCESS, return {} 0160 // otherwise return the contents of stderr 0161 { 0162 // The popen() function shall execute the command specified by the string command. It shall create a pipe between 0163 // the calling program and the executed command, and shall return a pointer to a stream that can be used to either 0164 // read from or write to the pipe. 0165 // https://pubs.opengroup.org/onlinepubs/009696699/functions/popen.html 0166 auto fp = popen("g++ -E -v -x c++ - </dev/null 2>&1", "r"); 0167 if (fp == nullptr) { 0168 return {}; 0169 } 0170 0171 auto constexpr READ_SIZE = 4096; 0172 char output[READ_SIZE]; 0173 std::string result; 0174 while (fgets(output, READ_SIZE, fp) != nullptr) { 0175 result += output; 0176 } 0177 0178 // The pclose() function shall close a stream that was opened by popen(), wait for the command to terminate, and 0179 // return the termination status of the process that was running the command language interpreter. However, if a 0180 // call caused the termination status to be unavailable to pclose(), then pclose() shall return -1 with errno set 0181 // to [ECHILD] to report this situation. 0182 // https://pubs.opengroup.org/onlinepubs/009696699/functions/pclose.html 0183 auto status = pclose(fp); 0184 if (status != 0) { 0185 return {}; 0186 } 0187 0188 return result; 0189 } 0190 #endif 0191 0192 std::optional<std::string> CompilerUtil::findBundledHeaders(bool silent) 0193 { 0194 #ifdef __APPLE__ 0195 // the bundled headers are broken on MacOS. Fortunately we have working 0196 // system includes 0197 return {}; 0198 #endif 0199 0200 #ifdef CT_CLANG_HEADERS_RELATIVE_DIR 0201 // CT_LVT_BINDIR should contain the directory in which argv[0] is located. 0202 // CT_CLANG_HEADERS_RELATIVE_DIR should contain a relative path from that 0203 // directory to the installed copy of the clang tooling headers 0204 const char *env_p = std::getenv("CT_LVT_BINDIR"); 0205 if (env_p) { 0206 auto env = std::string{env_p}; 0207 const std::filesystem::path binDir(env); 0208 std::filesystem::path headersDir = binDir / CT_CLANG_HEADERS_RELATIVE_DIR; 0209 if (std::filesystem::is_regular_file(headersDir / "stddef.h")) { 0210 if (!silent) { 0211 qDebug() << "Found clang header dir: " << headersDir.string(); 0212 } 0213 return std::filesystem::canonical(headersDir).string(); 0214 } 0215 0216 // try appimage path 0217 headersDir = binDir / "lib" / "clang" / "include"; 0218 if (std::filesystem::is_regular_file(headersDir / "stddef.h")) { 0219 if (!silent) { 0220 qDebug() << "Found clang header dir: " << headersDir.string(); 0221 } 0222 return std::filesystem::canonical(headersDir).string(); 0223 } 0224 if (!silent) { 0225 qDebug() << "WARNING: broken installation: cannot find clang headers at " << headersDir.string(); 0226 } 0227 } 0228 if (!silent) { 0229 qDebug() << "Finding headers with heuristics - expect compilation failures"; 0230 } 0231 #endif // CT_CLANG_HEADERS_RELATIVE_DIR 0232 0233 return {}; 0234 } 0235 0236 bool CompilerUtil::weNeedSystemHeaders() 0237 // Figure out if we can already find system headers without modifying anything 0238 { 0239 std::initializer_list<const char *> sources = { 0240 // stddef.h is the header mentioned in clang::tooling documentation 0241 "#include <stddef.h>", 0242 // weirdly MacOS finds stddef.h but then fails on stdarg.h so check 0243 // that too 0244 "#include <stdarg.h>", 0245 }; 0246 0247 for (const char *source : sources) { 0248 auto action = std::make_unique<clang::PreprocessOnlyAction>(); 0249 0250 // the tool will fail if it can't find the include 0251 bool ret = clang::tooling::runToolOnCode(std::move(action), source, "dummy.cpp"); 0252 if (!ret) { 0253 return true; 0254 } 0255 } 0256 0257 return false; 0258 } 0259 0260 } // namespace Codethink::lvtclp