|  | 
|  | 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