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"