|
| 1 | +#include "ProjectBuildDatabase.h" |
| 2 | + |
| 3 | +ProjectBuildDatabase::ProjectBuildDatabase(fs::path _buildCommandsJsonPath, |
| 4 | + fs::path _serverBuildDir, |
| 5 | + utbot::ProjectContext _projectContext) : |
| 6 | + serverBuildDir(std::move(_serverBuildDir)), |
| 7 | + projectContext(std::move(_projectContext)), |
| 8 | + buildCommandsJsonPath(std::move(_buildCommandsJsonPath)), |
| 9 | + linkCommandsJsonPath(fs::canonical(buildCommandsJsonPath / "link_commands.json")), |
| 10 | + compileCommandsJsonPath(fs::canonical(buildCommandsJsonPath / "compile_commands.json")) { |
| 11 | + if (!fs::exists(linkCommandsJsonPath) || !fs::exists(compileCommandsJsonPath)) { |
| 12 | + throw CompilationDatabaseException("Couldn't open link_commands.json or compile_commands.json files"); |
| 13 | + } |
| 14 | + |
| 15 | + auto linkCommandsJson = JsonUtils::getJsonFromFile(linkCommandsJsonPath); |
| 16 | + auto compileCommandsJson = JsonUtils::getJsonFromFile(compileCommandsJsonPath); |
| 17 | + |
| 18 | + initObjects(compileCommandsJson); |
| 19 | + initInfo(linkCommandsJson); |
| 20 | + filterInstalledFiles(); |
| 21 | + addLocalSharedLibraries(); |
| 22 | + fillTargetInfoParents(); |
| 23 | + createClangCompileCommandsJson(); |
| 24 | + target = GrpcUtils::UTBOT_AUTO_TARGET_PATH; |
| 25 | +} |
| 26 | + |
| 27 | +std::shared_ptr<BuildDatabase> BuildDatabase::create(const utbot::ProjectContext &projectContext) { |
| 28 | + fs::path compileCommandsJsonPath = |
| 29 | + CompilationUtils::substituteRemotePathToCompileCommandsJsonPath( |
| 30 | + projectContext.projectPath, projectContext.buildDirRelativePath); |
| 31 | + fs::path serverBuildDir = Paths::getUtbotBuildDir(projectContext); |
| 32 | + std::shared_ptr<BuildDatabase> buildDatabase = std::make_shared<BuildDatabase>(compileCommandsJsonPath, |
| 33 | + serverBuildDir, projectContext); |
| 34 | + return buildDatabase; |
| 35 | +} |
| 36 | + |
| 37 | + |
| 38 | +void BuildDatabase::initObjects(const nlohmann::json &compileCommandsJson) { |
| 39 | + for (const nlohmann::json &compileCommand: compileCommandsJson) { |
| 40 | + auto objectInfo = std::make_shared<ObjectFileInfo>(); |
| 41 | + |
| 42 | + fs::path directory = compileCommand.at("directory").get<std::string>(); |
| 43 | + fs::path jsonFile = compileCommand.at("file").get<std::string>(); |
| 44 | + fs::path sourceFile = Paths::getCCJsonFileFullPath(jsonFile, directory); |
| 45 | + |
| 46 | + std::vector<std::string> jsonArguments; |
| 47 | + if (compileCommand.contains("command")) { |
| 48 | + std::string command = compileCommand.at("command"); |
| 49 | + jsonArguments = StringUtils::splitByWhitespaces(command); |
| 50 | + } else { |
| 51 | + jsonArguments = std::vector<std::string>(compileCommand.at("arguments")); |
| 52 | + } |
| 53 | + std::transform(jsonArguments.begin(), jsonArguments.end(), jsonArguments.begin(), |
| 54 | + [&directory](const std::string &argument) { |
| 55 | + return tryConvertOptionToPath(argument, directory); |
| 56 | + }); |
| 57 | + objectInfo->command = utbot::CompileCommand(jsonArguments, directory, sourceFile); |
| 58 | + objectInfo->command.removeWerror(); |
| 59 | + fs::path outputFile = objectInfo->getOutputFile(); |
| 60 | + fs::path kleeFilePathTemplate = |
| 61 | + Paths::createNewDirForFile(sourceFile, projectContext.buildDir(), serverBuildDir); |
| 62 | + fs::path kleeFile = Paths::addSuffix(kleeFilePathTemplate, "_klee"); |
| 63 | + objectInfo->kleeFilesInfo = std::make_shared<KleeFilesInfo>(kleeFile); |
| 64 | + |
| 65 | + if (CollectionUtils::containsKey(objectFileInfos, outputFile) || |
| 66 | + CollectionUtils::containsKey(targetInfos, outputFile)) { |
| 67 | + /* |
| 68 | + * If the condition above is true, that means that the output file |
| 69 | + * is built from multiple sources. Hence, it is not an object file, |
| 70 | + * but an executable, and it should be treated as a target. |
| 71 | + * This is a hack. This inconsistency is produced by Bear |
| 72 | + * when it treats a Makefile command like |
| 73 | + * gcc -o output a.c b.c c.c |
| 74 | + * This code is creating artificial compile and link commands, similar |
| 75 | + * to commands Bear generates from CMake command like |
| 76 | + * add_executable(output a.c b.c c.c) |
| 77 | + */ |
| 78 | + auto targetInfo = targetInfos[outputFile]; |
| 79 | + if (targetInfo == nullptr) { |
| 80 | + LOG_S(DEBUG) << outputFile << " is treated as a target instead of an object file"; |
| 81 | + auto targetObjectInfo = objectFileInfos[outputFile]; |
| 82 | + auto tmpObjectFileName = createExplicitObjectFileCompilationCommand(targetObjectInfo); |
| 83 | + objectFileInfos.erase(outputFile); |
| 84 | + |
| 85 | + //create targetInfo |
| 86 | + targetInfo = targetInfos[outputFile] = std::make_shared<TargetInfo>(); |
| 87 | + targetInfo->commands.emplace_back( |
| 88 | + std::initializer_list<std::string>{targetObjectInfo->command.getBuildTool(), |
| 89 | + "-o", outputFile, tmpObjectFileName}, |
| 90 | + directory); |
| 91 | + targetInfo->addFile(tmpObjectFileName); |
| 92 | + } |
| 93 | + //redirect new compilation command to temporary file |
| 94 | + auto tmpObjectFileName = createExplicitObjectFileCompilationCommand(objectInfo); |
| 95 | + |
| 96 | + //add new dependency to an implicit target |
| 97 | + targetInfo->commands[0].addFlagToEnd(tmpObjectFileName); |
| 98 | + targetInfo->addFile(tmpObjectFileName); |
| 99 | + } else { |
| 100 | + objectFileInfos[outputFile] = objectInfo; |
| 101 | + } |
| 102 | + compileCommands_temp.emplace_back(compileCommand, objectInfo); |
| 103 | + const fs::path &sourcePath = objectInfo->getSourcePath(); |
| 104 | + sourceFileInfos[sourcePath].emplace_back(objectInfo); |
| 105 | + } |
| 106 | + for (auto &[sourceFile, objectInfos]: sourceFileInfos) { |
| 107 | + std::sort(objectInfos.begin(), objectInfos.end(), BuildDatabase::ObjectFileInfo::conflictPriorityMore); |
| 108 | + } |
| 109 | +} |
| 110 | + |
| 111 | +void BuildDatabase::initInfo(const nlohmann::json &linkCommandsJson) { |
| 112 | + for (nlohmann::json const &linkCommand : linkCommandsJson) { |
| 113 | + fs::path directory = linkCommand.at("directory").get<std::string>(); |
| 114 | + std::vector<std::string> jsonArguments; |
| 115 | + if (linkCommand.contains("command")) { |
| 116 | + std::string command = linkCommand.at("command"); |
| 117 | + jsonArguments = StringUtils::splitByWhitespaces(command); |
| 118 | + } else { |
| 119 | + jsonArguments = std::vector<std::string>(linkCommand.at("arguments")); |
| 120 | + } |
| 121 | + if (StringUtils::endsWith(jsonArguments[0], "ranlib") || |
| 122 | + StringUtils::endsWith(jsonArguments[0], "cmake")) { |
| 123 | + continue; |
| 124 | + } |
| 125 | + std::transform(jsonArguments.begin(), jsonArguments.end(), jsonArguments.begin(), |
| 126 | + [&directory](const std::string &argument) { |
| 127 | + return tryConvertOptionToPath(argument, directory); |
| 128 | + }); |
| 129 | + |
| 130 | + mergeLibraryOptions(jsonArguments); |
| 131 | + |
| 132 | + utbot::LinkCommand command(jsonArguments, directory); |
| 133 | + fs::path const &output = command.getOutput(); |
| 134 | + auto targetInfo = targetInfos[output]; |
| 135 | + if (targetInfo == nullptr) { |
| 136 | + targetInfo = targetInfos[output] = std::make_shared<TargetInfo>(); |
| 137 | + } else { |
| 138 | + LOG_S(WARNING) << "Multiple commands for one file: " << output.string(); |
| 139 | + } |
| 140 | + for (nlohmann::json const &jsonFile: linkCommand.at("files")) { |
| 141 | + auto filename = jsonFile.get<std::string>(); |
| 142 | + fs::path currentFile = Paths::getCCJsonFileFullPath(filename, command.getDirectory()); |
| 143 | + targetInfo->addFile(currentFile); |
| 144 | + if (Paths::isObjectFile(currentFile)) { |
| 145 | + if (!CollectionUtils::containsKey(objectFileInfos, currentFile)) { |
| 146 | + throw CompilationDatabaseException("compile_commands.json doesn't contain a command for object file " |
| 147 | + + currentFile.string()); |
| 148 | + } |
| 149 | + objectFileInfos[currentFile]->linkUnit = output; |
| 150 | + } |
| 151 | + } |
| 152 | + targetInfo->commands.emplace_back(command); |
| 153 | + } |
| 154 | +} |
| 155 | + |
| 156 | + |
| 157 | +void BuildDatabase::filterInstalledFiles() { |
| 158 | + for (auto &it : targetInfos) { |
| 159 | + auto &linkFile = it.first; |
| 160 | + auto &targetInfo = it.second; |
| 161 | + CollectionUtils::OrderedFileSet fileset; |
| 162 | + targetInfo->installedFiles = |
| 163 | + CollectionUtils::filterOut(targetInfo->files, [this](fs::path const &file) { |
| 164 | + return CollectionUtils::containsKey(targetInfos, file) || |
| 165 | + CollectionUtils::containsKey(objectFileInfos, file); |
| 166 | + }); |
| 167 | + if (!targetInfo->installedFiles.empty()) { |
| 168 | + LOG_S(DEBUG) << "Target " << linkFile << " depends on " << targetInfo->installedFiles.size() << " installed files"; |
| 169 | + } |
| 170 | + CollectionUtils::erase_if(targetInfo->files, [&targetInfo](fs::path const &file) { |
| 171 | + return CollectionUtils::contains(targetInfo->installedFiles, file); |
| 172 | + }); |
| 173 | + } |
| 174 | +} |
| 175 | + |
| 176 | +void BuildDatabase::addLocalSharedLibraries() { |
| 177 | + sharedLibrariesMap sharedLibraryFiles; |
| 178 | + for (const auto &[linkFile, linkUnit] : targetInfos) { |
| 179 | + if (Paths::isSharedLibraryFile(linkFile)) { |
| 180 | + auto withoutVersion = CompilationUtils::removeSharedLibraryVersion(linkFile); |
| 181 | + sharedLibraryFiles[withoutVersion.filename()][linkFile.parent_path()] = linkFile; |
| 182 | + } |
| 183 | + } |
| 184 | + for (auto &[linkFile, targetInfo] : targetInfos) { |
| 185 | + for (auto &command : targetInfo->commands) { |
| 186 | + addLibrariesForCommand(command, *targetInfo, sharedLibraryFiles); |
| 187 | + } |
| 188 | + } |
| 189 | + for (auto &[objectFile, objectInfo] : objectFileInfos) { |
| 190 | + addLibrariesForCommand(objectInfo->command, *objectInfo, sharedLibraryFiles, true); |
| 191 | + } |
| 192 | +} |
| 193 | + |
| 194 | +void BuildDatabase::fillTargetInfoParents() { |
| 195 | + CollectionUtils::MapFileTo<std::vector<fs::path>> parentTargets; |
| 196 | + for (const auto &[linkFile, linkUnit] : targetInfos) { |
| 197 | + for (const fs::path &dependencyFile : linkUnit->files) { |
| 198 | + if (Paths::isLibraryFile(dependencyFile)) { |
| 199 | + parentTargets[dependencyFile].emplace_back(linkFile); |
| 200 | + } |
| 201 | + if (Paths::isObjectFile(dependencyFile)) { |
| 202 | + objectFileTargets[dependencyFile].emplace_back(linkFile); |
| 203 | + } |
| 204 | + } |
| 205 | + } |
| 206 | + for (auto &[library, parents] : parentTargets) { |
| 207 | + if (!CollectionUtils::containsKey(targetInfos, library)) { |
| 208 | + throw CompilationDatabaseException( |
| 209 | + "link_commands.json doesn't contain a command for building library: " + |
| 210 | + library.string() + "\nReferenced from command for: " + (parents.empty() ? "none" : parents[0].string())); |
| 211 | + } |
| 212 | + targetInfos[library]->parentLinkUnits = std::move(parents); |
| 213 | + } |
| 214 | +} |
| 215 | + |
0 commit comments