Kadir Çetinkaya | 0769200 | 2024-02-28 07:10:05 +0000 | [diff] [blame] | 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 "clang/Tooling/CompilationDatabase.h" |
| 24 | #include "clang/Tooling/JSONCompilationDatabase.h" |
| 25 | #include "ide_query.pb.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 { |
| 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 | |
| 51 | ::ide_query::DepsResponse GetDeps(::ide_query::RepoState state) { |
| 52 | ::ide_query::DepsResponse results; |
| 53 | auto db = LoadCompDB(state.comp_db_path()); |
| 54 | if (!db) { |
| 55 | results.mutable_status()->set_code(::ide_query::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(::ide_query::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 | |
| 83 | ::ide_query::IdeAnalysis GetBuildInputs(::ide_query::RepoState state) { |
| 84 | auto db = LoadCompDB(state.comp_db_path()); |
| 85 | ::ide_query::IdeAnalysis results; |
| 86 | if (!db) { |
| 87 | results.mutable_status()->set_code(::ide_query::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 | results.set_build_artifact_root(state.out_dir()); |
| 101 | for (llvm::StringRef active_file : state.active_file_path()) { |
| 102 | auto& result = *results.add_sources(); |
| 103 | result.set_path(active_file.str()); |
| 104 | |
| 105 | llvm::SmallString<256> abs_file(repo_dir); |
| 106 | llvm::sys::path::append(abs_file, active_file); |
| 107 | auto cmds = db->get()->getCompileCommands(abs_file); |
| 108 | if (cmds.empty()) { |
| 109 | result.mutable_status()->set_code(::ide_query::Status::FAILURE); |
| 110 | result.mutable_status()->set_message( |
| 111 | llvm::Twine("Can't find compile flags for file: ", abs_file).str()); |
| 112 | continue; |
| 113 | } |
| 114 | const auto& cmd = cmds.front(); |
| 115 | llvm::StringRef working_dir = cmd.Directory; |
| 116 | if (!working_dir.consume_front(repo_dir)) { |
| 117 | result.mutable_status()->set_code(::ide_query::Status::FAILURE); |
| 118 | result.mutable_status()->set_message("Command working dir " + |
| 119 | working_dir.str() + |
Michael Merg | d8880ab | 2024-03-20 11:05:23 +0000 | [diff] [blame^] | 120 | " outside repository " + repo_dir); |
Kadir Çetinkaya | 0769200 | 2024-02-28 07:10:05 +0000 | [diff] [blame] | 121 | continue; |
| 122 | } |
| 123 | working_dir = working_dir.ltrim('/'); |
| 124 | result.set_working_dir(working_dir.str()); |
| 125 | for (auto& arg : cmd.CommandLine) result.add_compiler_arguments(arg); |
| 126 | |
| 127 | auto includes = |
| 128 | ScanIncludes(cmds.front(), llvm::vfs::createPhysicalFileSystem()); |
| 129 | if (!includes) { |
| 130 | result.mutable_status()->set_code(::ide_query::Status::FAILURE); |
| 131 | result.mutable_status()->set_message( |
| 132 | llvm::toString(includes.takeError())); |
| 133 | continue; |
| 134 | } |
| 135 | |
| 136 | for (auto& [req_input, contents] : *includes) { |
| 137 | llvm::StringRef req_input_ref(req_input); |
| 138 | // We're only interested in generated files. |
| 139 | if (!req_input_ref.consume_front(genfile_root_abs)) continue; |
| 140 | auto& genfile = *result.add_generated(); |
| 141 | genfile.set_path(req_input_ref.str()); |
| 142 | genfile.set_contents(std::move(contents)); |
| 143 | } |
| 144 | } |
| 145 | return results; |
| 146 | } |
| 147 | } // namespace tools::ide_query::cc_analyzer |