Skip to content

Commit

Permalink
Find and insert the resource dir when the compiler was Clang
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
danakj committed Dec 21, 2023
1 parent 058b6be commit 1620d50
Show file tree
Hide file tree
Showing 5 changed files with 143 additions and 7 deletions.
2 changes: 2 additions & 0 deletions subdoc/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
82 changes: 82 additions & 0 deletions subdoc/lib/clang_resource_dir.cc
Original file line number Diff line number Diff line change
@@ -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 <filesystem>
#include <fstream>

#include "subdoc/llvm.h"

namespace subdoc {

Option<std::string> 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<llvm::StringRef, 2> 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<std::optional<llvm::StringRef>, 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
43 changes: 43 additions & 0 deletions subdoc/lib/clang_resource_dir.h
Original file line number Diff line number Diff line change
@@ -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 <map>
#include <string>
#include <string_view>

#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<std::string> find_resource_dir(std::string_view tool);

std::map<std::string /* tool */, std::string /* resource_dir */, std::less<>>
cache_;
};

} // namespace subdoc
22 changes: 15 additions & 7 deletions subdoc/lib/run.cc
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,11 @@

#include "subdoc/lib/run.h"

#include <filesystem>
#include <memory>
#include <utility>

#include "subdoc/lib/clang_resource_dir.h"
#include "subdoc/lib/visit.h"
#include "sus/collections/compat_vector.h"
#include "sus/iter/iterator.h"
Expand All @@ -43,8 +45,7 @@ sus::Result<Database, DiagnosticResults> 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<std::string>(sus::move(pretend_file_name)),
return run_files(*comp_db, Vec<std::string>(sus::move(pretend_file_name)),
std::move(vfs), options);
}

Expand All @@ -64,8 +65,7 @@ struct DiagnosticTracker : public clang::TextDiagnosticPrinter {
};

sus::Result<Database, DiagnosticResults> run_files(
const clang::tooling::CompilationDatabase& comp_db,
Vec<std::string> paths,
const clang::tooling::CompilationDatabase& comp_db, Vec<std::string> paths,
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fs,
const RunOptions& options) noexcept {
// Clang DiagnoticsConsumer that prints out the full error and context, which
Expand All @@ -80,13 +80,15 @@ sus::Result<Database, DiagnosticResults> run_files(
std::make_shared<clang::PCHContainerOperations>(), 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.
Expand Down Expand Up @@ -130,6 +132,12 @@ sus::Result<Database, DiagnosticResults> run_files(
return a.starts_with("@") && a.ends_with(".modmap");
});

if (Option<std::string> 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);
Expand Down
1 change: 1 addition & 0 deletions subdoc/llvm.h
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down

0 comments on commit 1620d50

Please sign in to comment.