From 1620d50adf56cad40466023e079a660457ce6e65 Mon Sep 17 00:00:00 2001 From: Dana Jansens Date: Thu, 21 Dec 2023 16:40:30 -0500 Subject: [PATCH] Find and insert the resource dir when the compiler was Clang If the compiler used to compile the source file from compile_commmands.json was Clang, then Subdoc (a Clang tool itself) needs to know where the "resource dir" is that Clang would have used, since Clang goes through the resource dir to find system headers. To do this, we execute the Clang compiler with -print-resource-dir and return the directory given by it. Then we append this to the Subdoc execution of ClangTool with -resource-dir. --- subdoc/CMakeLists.txt | 2 + subdoc/lib/clang_resource_dir.cc | 82 ++++++++++++++++++++++++++++++++ subdoc/lib/clang_resource_dir.h | 43 +++++++++++++++++ subdoc/lib/run.cc | 22 ++++++--- subdoc/llvm.h | 1 + 5 files changed, 143 insertions(+), 7 deletions(-) create mode 100644 subdoc/lib/clang_resource_dir.cc create mode 100644 subdoc/lib/clang_resource_dir.h diff --git a/subdoc/CMakeLists.txt b/subdoc/CMakeLists.txt index 130ec3b0f..17b4d4464 100644 --- a/subdoc/CMakeLists.txt +++ b/subdoc/CMakeLists.txt @@ -45,6 +45,8 @@ target_sources(subdoc_lib PUBLIC "lib/gen/markdown_to_html.cc" "lib/gen/markdown_to_html.h" "lib/gen/options.h" + "lib/clang_resource_dir.cc" + "lib/clang_resource_dir.h" "lib/database.h" "lib/friendly_names.h" "lib/linked_type.cc" diff --git a/subdoc/lib/clang_resource_dir.cc b/subdoc/lib/clang_resource_dir.cc new file mode 100644 index 000000000..a33a47a26 --- /dev/null +++ b/subdoc/lib/clang_resource_dir.cc @@ -0,0 +1,82 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "subdoc/lib/clang_resource_dir.h" + +#include +#include + +#include "subdoc/llvm.h" + +namespace subdoc { + +Option ClangResourceDir::find_resource_dir(std::string_view tool) { + if (auto it = cache_.find(tool); it != cache_.end()) { + return sus::some(it->second); + } + + std::filesystem::path tool_path(tool); + std::string stem = tool_path.stem().string(); + if (!stem.starts_with("clang")) return sus::none(); + + if (!std::filesystem::exists(tool_path)) { + fmt::println(stderr, "WARNING: can't find clang compiler at '{}'", tool); + return sus::none(); + } + + std::array args = {tool, "-print-resource-dir"}; + if (stem.starts_with("clang-cl")) { + args[1] = "/clang:-print-resource-dir"; + } + + llvm::SmallString<100> out; + { + std::error_code code = + llvm::sys::fs::createTemporaryFile("stdout", "txt", out); + if (code) { + fmt::println(stderr, "WARNING: unable to make temp file: {}", + code.message()); + return sus::none(); + } + } + std::array, 3> redirects = {std::nullopt, out, + std::nullopt}; + { + i32 code = + llvm::sys::ExecuteAndWait(tool, args, /*env=*/std::nullopt, redirects); + if (code != 0) { + fmt::println( + stderr, "WARNING: failed to run clang compiler at '{}', exit code {}", + tool, code); + return sus::none(); + } + } + + auto out_file = std::ifstream(out.c_str()); + std::string resource_dir; + if (!std::getline(out_file, resource_dir)) { + fmt::println(stderr, + "WARNING: 'clang -print-resource-dir' did not return anything " + "for clang compiler at '{}'", + tool); + return sus::none(); + } + + auto trimmed_resource_dir = llvm::StringRef(resource_dir).trim(); + + cache_.emplace(tool, std::string(trimmed_resource_dir)); + return sus::some(std::string(trimmed_resource_dir)); +} + +} // namespace subdoc diff --git a/subdoc/lib/clang_resource_dir.h b/subdoc/lib/clang_resource_dir.h new file mode 100644 index 000000000..43641c47c --- /dev/null +++ b/subdoc/lib/clang_resource_dir.h @@ -0,0 +1,43 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include +#include +#include + +#include "sus/prelude.h" + +namespace subdoc { + +/// Find, store, and return the "resource dir" for finding system headers from +/// Clang. +/// +/// Clang tools need to know where the "resource dir" is in order to find +/// system headers there, if Clang was the compiler that's being used for +/// building the target. +/// +/// For other compilers, the headers come from the system header location, but +/// Clang has a resource dir that is known to the compiler, and which Subdoc +/// can't know apriori. So it has to query the Clang compiler to get it. +class ClangResourceDir { + public: + Option find_resource_dir(std::string_view tool); + + std::map> + cache_; +}; + +} // namespace subdoc diff --git a/subdoc/lib/run.cc b/subdoc/lib/run.cc index 78c5ff509..e076da4d2 100644 --- a/subdoc/lib/run.cc +++ b/subdoc/lib/run.cc @@ -14,9 +14,11 @@ #include "subdoc/lib/run.h" +#include #include #include +#include "subdoc/lib/clang_resource_dir.h" #include "subdoc/lib/visit.h" #include "sus/collections/compat_vector.h" #include "sus/iter/iterator.h" @@ -43,8 +45,7 @@ sus::Result run_test( auto vfs = llvm::IntrusiveRefCntPtr(new llvm::vfs::InMemoryFileSystem()); vfs->addFile(pretend_file_name, 0, llvm::MemoryBuffer::getMemBuffer(content)); - return run_files(*comp_db, - Vec(sus::move(pretend_file_name)), + return run_files(*comp_db, Vec(sus::move(pretend_file_name)), std::move(vfs), options); } @@ -64,8 +65,7 @@ struct DiagnosticTracker : public clang::TextDiagnosticPrinter { }; sus::Result run_files( - const clang::tooling::CompilationDatabase& comp_db, - Vec paths, + const clang::tooling::CompilationDatabase& comp_db, Vec paths, llvm::IntrusiveRefCntPtr fs, const RunOptions& options) noexcept { // Clang DiagnoticsConsumer that prints out the full error and context, which @@ -80,13 +80,15 @@ sus::Result run_files( std::make_shared(), std::move(fs)); tool.setDiagnosticConsumer(&*diags); - auto adj = [](clang::tooling::CommandLineArguments args, llvm::StringRef) { + ClangResourceDir resource_dir; + + auto adj = [&resource_dir](clang::tooling::CommandLineArguments args, + llvm::StringRef) { // Clang-cl doesn't understand this argument, but it may appear in the // command-line for MSVC in C++20 codebases (like subspace). std::erase(args, "/Zc:preprocessor"); - const std::string& tool = args[0]; - if (tool.find("cl.exe") != std::string::npos) { + if (std::filesystem::path(args[0]).filename().string() == "cl.exe") { // TODO: https://github.com/llvm/llvm-project/issues/59689 clang-cl // requires this define in order to use offsetof() from constant // expressions, which subspace uses for the never-value optimization. @@ -130,6 +132,12 @@ sus::Result run_files( return a.starts_with("@") && a.ends_with(".modmap"); }); + if (Option dir = resource_dir.find_resource_dir(args[0]); + dir.is_some()) { + args.push_back("-resource-dir"); + args.push_back(dir.as_value()); + } + return std::move(args); }; tool.appendArgumentsAdjuster(adj); diff --git a/subdoc/llvm.h b/subdoc/llvm.h index b9d13c5c9..11bc41c06 100644 --- a/subdoc/llvm.h +++ b/subdoc/llvm.h @@ -58,6 +58,7 @@ #include "llvm/Support/Error.h" #include "llvm/Support/InitLLVM.h" #include "llvm/Support/MemoryBuffer.h" +#include "llvm/Support/Program.h" #include "llvm/Support/SHA1.h" #include "llvm/Support/TargetSelect.h" #include "llvm/Support/VirtualFileSystem.h"