xref: /aosp_15_r20/build/make/tools/ide_query/cc_analyzer/analyzer.cc (revision 9e94795a3d4ef5c1d47486f9a02bb378756cea8a)
1 /*
2  * Copyright (C) 2024 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 #include "analyzer.h"
17 
18 #include <memory>
19 #include <string>
20 #include <utility>
21 #include <vector>
22 
23 #include "cc_analyzer.pb.h"
24 #include "clang/Tooling/CompilationDatabase.h"
25 #include "clang/Tooling/JSONCompilationDatabase.h"
26 #include "include_scanner.h"
27 #include "llvm/ADT/SmallString.h"
28 #include "llvm/ADT/StringRef.h"
29 #include "llvm/ADT/Twine.h"
30 #include "llvm/Support/Error.h"
31 #include "llvm/Support/Path.h"
32 #include "llvm/Support/VirtualFileSystem.h"
33 
34 namespace tools::ide_query::cc_analyzer {
35 namespace {
LoadCompDB(llvm::StringRef comp_db_path)36 llvm::Expected<std::unique_ptr<clang::tooling::CompilationDatabase>> LoadCompDB(
37     llvm::StringRef comp_db_path) {
38   std::string err;
39   std::unique_ptr<clang::tooling::CompilationDatabase> db =
40       clang::tooling::JSONCompilationDatabase::loadFromFile(
41           comp_db_path, err, clang::tooling::JSONCommandLineSyntax::AutoDetect);
42   if (!db) {
43     return llvm::createStringError(llvm::inconvertibleErrorCode(),
44                                    "Failed to load CDB: " + err);
45   }
46   // Provide some heuristic support for missing files.
47   return inferMissingCompileCommands(std::move(db));
48 }
49 }  // namespace
50 
GetDeps(::cc_analyzer::RepoState state)51 ::cc_analyzer::DepsResponse GetDeps(::cc_analyzer::RepoState state) {
52   ::cc_analyzer::DepsResponse results;
53   auto db = LoadCompDB(state.comp_db_path());
54   if (!db) {
55     results.mutable_status()->set_code(::cc_analyzer::Status::FAILURE);
56     results.mutable_status()->set_message(llvm::toString(db.takeError()));
57     return results;
58   }
59   for (llvm::StringRef active_file : state.active_file_path()) {
60     auto& result = *results.add_deps();
61 
62     llvm::SmallString<256> abs_file(state.repo_dir());
63     llvm::sys::path::append(abs_file, active_file);
64     auto cmds = db->get()->getCompileCommands(active_file);
65     if (cmds.empty()) {
66       result.mutable_status()->set_code(::cc_analyzer::Status::FAILURE);
67       result.mutable_status()->set_message(
68           llvm::Twine("Can't find compile flags for file: ", abs_file).str());
69       continue;
70     }
71     result.set_source_file(active_file.str());
72     llvm::StringRef file = cmds[0].Filename;
73     if (llvm::StringRef actual_file(cmds[0].Heuristic);
74         actual_file.consume_front("inferred from ")) {
75       file = actual_file;
76     }
77     // TODO: Query ninja graph to figure out a minimal set of targets to build.
78     result.add_build_target(file.str() + "^");
79   }
80   return results;
81 }
82 
GetBuildInputs(::cc_analyzer::RepoState state)83 ::cc_analyzer::IdeAnalysis GetBuildInputs(::cc_analyzer::RepoState state) {
84   auto db = LoadCompDB(state.comp_db_path());
85   ::cc_analyzer::IdeAnalysis results;
86   if (!db) {
87     results.mutable_status()->set_code(::cc_analyzer::Status::FAILURE);
88     results.mutable_status()->set_message(llvm::toString(db.takeError()));
89     return results;
90   }
91   std::string repo_dir = state.repo_dir();
92   if (!repo_dir.empty() && repo_dir.back() == '/') repo_dir.pop_back();
93 
94   llvm::SmallString<256> genfile_root_abs(repo_dir);
95   llvm::sys::path::append(genfile_root_abs, state.out_dir());
96   if (genfile_root_abs.empty() || genfile_root_abs.back() != '/') {
97     genfile_root_abs.push_back('/');
98   }
99 
100   for (llvm::StringRef active_file : state.active_file_path()) {
101     auto& result = *results.add_sources();
102     result.set_path(active_file.str());
103 
104     llvm::SmallString<256> abs_file(repo_dir);
105     llvm::sys::path::append(abs_file, active_file);
106     auto cmds = db->get()->getCompileCommands(abs_file);
107     if (cmds.empty()) {
108       result.mutable_status()->set_code(::cc_analyzer::Status::FAILURE);
109       result.mutable_status()->set_message(
110           llvm::Twine("Can't find compile flags for file: ", abs_file).str());
111       continue;
112     }
113     const auto& cmd = cmds.front();
114     llvm::StringRef working_dir = cmd.Directory;
115     if (!working_dir.consume_front(repo_dir)) {
116       result.mutable_status()->set_code(::cc_analyzer::Status::FAILURE);
117       result.mutable_status()->set_message("Command working dir " +
118                                            working_dir.str() +
119                                            " outside repository " + repo_dir);
120       continue;
121     }
122     working_dir = working_dir.ltrim('/');
123     result.set_working_dir(working_dir.str());
124     for (auto& arg : cmd.CommandLine) result.add_compiler_arguments(arg);
125 
126     auto includes =
127         ScanIncludes(cmds.front(), llvm::vfs::createPhysicalFileSystem());
128     if (!includes) {
129       result.mutable_status()->set_code(::cc_analyzer::Status::FAILURE);
130       result.mutable_status()->set_message(
131           llvm::toString(includes.takeError()));
132       continue;
133     }
134 
135     for (auto& [req_input, contents] : *includes) {
136       llvm::StringRef req_input_ref(req_input);
137       // We're only interested in generated files.
138       if (!req_input_ref.consume_front(genfile_root_abs)) continue;
139       auto& genfile = *result.add_generated();
140       genfile.set_path(req_input_ref.str());
141       genfile.set_contents(std::move(contents));
142     }
143   }
144   return results;
145 }
146 }  // namespace tools::ide_query::cc_analyzer
147