| /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying | |
| file LICENSE.rst or https://cmake.org/licensing for details. */ | |
| #include "cmBuildDatabase.h" | |
| #include <cstdlib> | |
| #include <set> | |
| #include <utility> | |
| #include <cm/memory> | |
| #include <cm/string_view> | |
| #include <cmext/string_view> | |
| #include <cm3p/json/value.h> | |
| #include <cm3p/json/writer.h> | |
| #include "cmsys/FStream.hxx" | |
| #include "cmComputeLinkInformation.h" | |
| #include "cmFileSetMetadata.h" | |
| #include "cmGeneratedFileStream.h" | |
| #include "cmGeneratorFileSet.h" | |
| #include "cmGeneratorTarget.h" | |
| #include "cmGlobalGenerator.h" | |
| #include "cmJSONState.h" | |
| #include "cmListFileCache.h" | |
| #include "cmLocalGenerator.h" | |
| #include "cmSourceFile.h" | |
| #include "cmStringAlgorithms.h" | |
| #include "cmSystemTools.h" | |
| namespace { | |
| std::string PlaceholderName = "<__CMAKE_UNKNOWN>"; | |
| } | |
| cmBuildDatabase::cmBuildDatabase() = default; | |
| cmBuildDatabase::cmBuildDatabase(cmBuildDatabase const&) = default; | |
| cmBuildDatabase::~cmBuildDatabase() = default; | |
| cmBuildDatabase::LookupTable cmBuildDatabase::GenerateLookupTable() | |
| { | |
| LookupTable lut; | |
| for (auto& Set_ : this->Sets) { | |
| for (auto& TranslationUnit_ : Set_.TranslationUnits) { | |
| // This table is from source path to TU instance. This is fine because a | |
| // single target (where this is used) cannot contain the same source file | |
| // multiple times. | |
| lut[TranslationUnit_.Source] = &TranslationUnit_; | |
| } | |
| } | |
| return lut; | |
| } | |
| bool cmBuildDatabase::HasPlaceholderNames() const | |
| { | |
| for (auto const& Set_ : this->Sets) { | |
| for (auto const& TranslationUnit_ : Set_.TranslationUnits) { | |
| for (auto const& provide : TranslationUnit_.Provides) { | |
| if (provide.first == PlaceholderName) { | |
| return true; | |
| } | |
| if (provide.second == PlaceholderName) { | |
| return true; | |
| } | |
| } | |
| } | |
| } | |
| return false; | |
| } | |
| void cmBuildDatabase::Write(std::string const& path) const | |
| { | |
| Json::Value mcdb = Json::objectValue; | |
| mcdb["version"] = 1; | |
| mcdb["revision"] = 0; | |
| Json::Value& sets = mcdb["sets"] = Json::arrayValue; | |
| for (auto const& Set_ : this->Sets) { | |
| Json::Value set = Json::objectValue; | |
| set["name"] = Set_.Name; | |
| set["family-name"] = Set_.FamilyName; | |
| Json::Value& visible_sets = set["visible-sets"] = Json::arrayValue; | |
| for (auto const& VisibleSet : Set_.VisibleSets) { | |
| visible_sets.append(VisibleSet); | |
| } | |
| Json::Value& tus = set["translation-units"] = Json::arrayValue; | |
| for (auto const& TranslationUnit_ : Set_.TranslationUnits) { | |
| Json::Value tu = Json::objectValue; | |
| if (!TranslationUnit_.WorkDirectory.empty()) { | |
| tu["work-directory"] = TranslationUnit_.WorkDirectory; | |
| } | |
| tu["source"] = TranslationUnit_.Source; | |
| if (TranslationUnit_.Object) { | |
| tu["object"] = *TranslationUnit_.Object; | |
| } | |
| tu["private"] = TranslationUnit_.Private; | |
| Json::Value& reqs = tu["requires"] = Json::arrayValue; | |
| for (auto const& Require : TranslationUnit_.Requires) { | |
| reqs.append(Require); | |
| } | |
| Json::Value& provides = tu["provides"] = Json::objectValue; | |
| for (auto const& Provide : TranslationUnit_.Provides) { | |
| provides[Provide.first] = Provide.second; | |
| } | |
| Json::Value& baseline_arguments = tu["baseline-arguments"] = | |
| Json::arrayValue; | |
| for (auto const& BaselineArgument : TranslationUnit_.BaselineArguments) { | |
| baseline_arguments.append(BaselineArgument); | |
| } | |
| Json::Value& local_arguments = tu["local-arguments"] = Json::arrayValue; | |
| for (auto const& LocalArgument : TranslationUnit_.LocalArguments) { | |
| local_arguments.append(LocalArgument); | |
| } | |
| Json::Value& arguments = tu["arguments"] = Json::arrayValue; | |
| for (auto const& Argument : TranslationUnit_.Arguments) { | |
| arguments.append(Argument); | |
| } | |
| tus.append(tu); | |
| } | |
| sets.append(set); | |
| } | |
| cmGeneratedFileStream mcdbf(path); | |
| mcdbf << mcdb; | |
| } | |
| static bool ParseFilename(Json::Value const& val, std::string& result) | |
| { | |
| if (val.isString()) { | |
| result = val.asString(); | |
| } else { | |
| return false; | |
| } | |
| return true; | |
| } | |
| #define PARSE_BLOB(val, res) \ | |
| do { \ | |
| if (!ParseFilename(val, res)) { \ | |
| cmSystemTools::Error(cmStrCat("-E cmake_module_compile_db failed to ", \ | |
| "parse ", path, ": invalid blob")); \ | |
| return {}; \ | |
| } \ | |
| } while (0) | |
| #define PARSE_FILENAME(val, res, make_full) \ | |
| do { \ | |
| if (!ParseFilename(val, res)) { \ | |
| cmSystemTools::Error(cmStrCat("-E cmake_module_compile_db failed to ", \ | |
| "parse ", path, ": invalid filename")); \ | |
| return {}; \ | |
| } \ | |
| \ | |
| if (make_full && work_directory && !work_directory->empty() && \ | |
| !cmSystemTools::FileIsFullPath(res)) { \ | |
| res = cmStrCat(*work_directory, '/', res); \ | |
| } \ | |
| } while (0) | |
| std::unique_ptr<cmBuildDatabase> cmBuildDatabase::Load(std::string const& path) | |
| { | |
| Json::Value mcdb; | |
| { | |
| cmJSONState parseState(path, &mcdb); | |
| if (!parseState.errors.empty()) { | |
| cmSystemTools::Error( | |
| cmStrCat("-E cmake_module_compile_db failed to parse ", path, | |
| parseState.GetErrorMessage())); | |
| return {}; | |
| } | |
| } | |
| Json::Value const& version = mcdb["version"]; | |
| if (version.asUInt() > 1) { | |
| cmSystemTools::Error( | |
| cmStrCat("-E cmake_module_compile_db failed to parse ", path, | |
| ": version ", version.asString())); | |
| return {}; | |
| } | |
| auto db = cm::make_unique<cmBuildDatabase>(); | |
| Json::Value const& sets = mcdb["sets"]; | |
| if (sets.isArray()) { | |
| for (auto const& set : sets) { | |
| Set Set_; | |
| Json::Value const& name = set["name"]; | |
| if (!name.isString()) { | |
| cmSystemTools::Error( | |
| cmStrCat("-E cmake_module_compile_db failed to parse ", path, | |
| ": name is not a string")); | |
| return {}; | |
| } | |
| Set_.Name = name.asString(); | |
| Json::Value const& family_name = set["family-name"]; | |
| if (!family_name.isString()) { | |
| cmSystemTools::Error( | |
| cmStrCat("-E cmake_module_compile_db failed to parse ", path, | |
| ": family-name is not a string")); | |
| return {}; | |
| } | |
| Set_.FamilyName = family_name.asString(); | |
| Json::Value const& visible_sets = set["visible-sets"]; | |
| if (!visible_sets.isArray()) { | |
| cmSystemTools::Error( | |
| cmStrCat("-E cmake_module_compile_db failed to parse ", path, | |
| ": visible-sets is not an array")); | |
| return {}; | |
| } | |
| for (auto const& visible_set : visible_sets) { | |
| if (!visible_set.isString()) { | |
| cmSystemTools::Error( | |
| cmStrCat("-E cmake_module_compile_db failed to parse ", path, | |
| ": a visible-sets item is not a string")); | |
| return {}; | |
| } | |
| Set_.VisibleSets.emplace_back(visible_set.asString()); | |
| } | |
| Json::Value const& translation_units = set["translation-units"]; | |
| if (!translation_units.isArray()) { | |
| cmSystemTools::Error( | |
| cmStrCat("-E cmake_module_compile_db failed to parse ", path, | |
| ": translation-units is not an array")); | |
| return {}; | |
| } | |
| for (auto const& translation_unit : translation_units) { | |
| if (!translation_unit.isObject()) { | |
| cmSystemTools::Error( | |
| cmStrCat("-E cmake_module_compile_db failed to parse ", path, | |
| ": a translation-units item is not an object")); | |
| return {}; | |
| } | |
| TranslationUnit TranslationUnit_; | |
| cm::optional<std::string> work_directory; | |
| Json::Value const& workdir = translation_unit["work-directory"]; | |
| if (workdir.isString()) { | |
| PARSE_BLOB(workdir, TranslationUnit_.WorkDirectory); | |
| work_directory = TranslationUnit_.WorkDirectory; | |
| } else if (!workdir.isNull()) { | |
| cmSystemTools::Error( | |
| cmStrCat("-E cmake_module_compile_db failed to parse ", path, | |
| ": work-directory is not a string")); | |
| return {}; | |
| } | |
| Json::Value const& source = translation_unit["source"]; | |
| PARSE_FILENAME(source, TranslationUnit_.Source, true); | |
| if (translation_unit.isMember("object")) { | |
| Json::Value const& object = translation_unit["object"]; | |
| if (!object.isNull()) { | |
| TranslationUnit_.Object = ""; | |
| PARSE_FILENAME(object, *TranslationUnit_.Object, false); | |
| } | |
| } | |
| if (translation_unit.isMember("private")) { | |
| Json::Value const& priv = translation_unit["private"]; | |
| if (!priv.isBool()) { | |
| cmSystemTools::Error( | |
| cmStrCat("-E cmake_module_compile_db failed to parse ", path, | |
| ": private is not a boolean")); | |
| return {}; | |
| } | |
| TranslationUnit_.Private = priv.asBool(); | |
| } | |
| if (translation_unit.isMember("requires")) { | |
| Json::Value const& reqs = translation_unit["requires"]; | |
| if (!reqs.isArray()) { | |
| cmSystemTools::Error( | |
| cmStrCat("-E cmake_module_compile_db failed to parse ", path, | |
| ": requires is not an array")); | |
| return {}; | |
| } | |
| for (auto const& require : reqs) { | |
| if (!require.isString()) { | |
| cmSystemTools::Error( | |
| cmStrCat("-E cmake_module_compile_db failed to parse ", path, | |
| ": a requires item is not a string")); | |
| return {}; | |
| } | |
| TranslationUnit_.Requires.emplace_back(require.asString()); | |
| } | |
| } | |
| if (translation_unit.isMember("provides")) { | |
| Json::Value const& provides = translation_unit["provides"]; | |
| if (!provides.isObject()) { | |
| cmSystemTools::Error( | |
| cmStrCat("-E cmake_module_compile_db failed to parse ", path, | |
| ": provides is not an object")); | |
| return {}; | |
| } | |
| for (auto i = provides.begin(); i != provides.end(); ++i) { | |
| if (!i->isString()) { | |
| cmSystemTools::Error( | |
| cmStrCat("-E cmake_module_compile_db failed to parse ", path, | |
| ": a provides value is not a string")); | |
| return {}; | |
| } | |
| TranslationUnit_.Provides[i.key().asString()] = i->asString(); | |
| } | |
| } | |
| if (translation_unit.isMember("baseline-arguments")) { | |
| Json::Value const& baseline_arguments = | |
| translation_unit["baseline-arguments"]; | |
| if (!baseline_arguments.isArray()) { | |
| cmSystemTools::Error( | |
| cmStrCat("-E cmake_module_compile_db failed to parse ", path, | |
| ": baseline_arguments is not an array")); | |
| return {}; | |
| } | |
| for (auto const& baseline_argument : baseline_arguments) { | |
| if (baseline_argument.isString()) { | |
| TranslationUnit_.BaselineArguments.emplace_back( | |
| baseline_argument.asString()); | |
| } else { | |
| cmSystemTools::Error( | |
| cmStrCat("-E cmake_module_compile_db failed to parse ", path, | |
| ": a baseline argument is not a string")); | |
| return {}; | |
| } | |
| } | |
| } | |
| if (translation_unit.isMember("local-arguments")) { | |
| Json::Value const& local_arguments = | |
| translation_unit["local-arguments"]; | |
| if (!local_arguments.isArray()) { | |
| cmSystemTools::Error( | |
| cmStrCat("-E cmake_module_compile_db failed to parse ", path, | |
| ": local_arguments is not an array")); | |
| return {}; | |
| } | |
| for (auto const& local_argument : local_arguments) { | |
| if (local_argument.isString()) { | |
| TranslationUnit_.LocalArguments.emplace_back( | |
| local_argument.asString()); | |
| } else { | |
| cmSystemTools::Error( | |
| cmStrCat("-E cmake_module_compile_db failed to parse ", path, | |
| ": a local argument is not a string")); | |
| return {}; | |
| } | |
| } | |
| } | |
| if (translation_unit.isMember("arguments")) { | |
| Json::Value const& arguments = translation_unit["arguments"]; | |
| if (!arguments.isArray()) { | |
| cmSystemTools::Error( | |
| cmStrCat("-E cmake_module_compile_db failed to parse ", path, | |
| ": arguments is not an array")); | |
| return {}; | |
| } | |
| for (auto const& argument : arguments) { | |
| if (argument.isString()) { | |
| TranslationUnit_.Arguments.emplace_back(argument.asString()); | |
| } else { | |
| cmSystemTools::Error( | |
| cmStrCat("-E cmake_module_compile_db failed to parse ", path, | |
| ": an argument is not a string")); | |
| return {}; | |
| } | |
| } | |
| } | |
| Set_.TranslationUnits.emplace_back(std::move(TranslationUnit_)); | |
| } | |
| db->Sets.emplace_back(std::move(Set_)); | |
| } | |
| } | |
| return db; | |
| } | |
| cmBuildDatabase cmBuildDatabase::Merge( | |
| std::vector<cmBuildDatabase> const& components) | |
| { | |
| cmBuildDatabase db; | |
| for (auto const& component : components) { | |
| db.Sets.insert(db.Sets.end(), component.Sets.begin(), | |
| component.Sets.end()); | |
| } | |
| return db; | |
| } | |
| cmBuildDatabase cmBuildDatabase::ForTarget(cmGeneratorTarget* gt, | |
| std::string const& config) | |
| { | |
| cmBuildDatabase db; | |
| Set set; | |
| set.Name = cmStrCat(gt->GetName(), '@', config); | |
| set.FamilyName = gt->GetFamilyName(); | |
| if (auto* cli = gt->GetLinkInformation(config)) { | |
| std::set<cmGeneratorTarget const*> emitted; | |
| std::vector<cmGeneratorTarget const*> targets; | |
| for (auto const& item : cli->GetItems()) { | |
| auto const* linkee = item.Target; | |
| if (linkee && linkee->HaveCxx20ModuleSources() && | |
| !linkee->IsImported() && emitted.insert(linkee).second) { | |
| set.VisibleSets.push_back(cmStrCat(linkee->GetName(), '@', config)); | |
| } | |
| } | |
| } | |
| for (auto const& sfbt : gt->GetSourceFiles(config)) { | |
| auto const* sf = sfbt.Value; | |
| bool isCXXModule = false; | |
| bool isPrivate = true; | |
| if (gt->GetSourceFileLanguage(sf, config) == "CXX"_s) { | |
| auto const* fs = gt->GetFileSetForSource(config, sf); | |
| if (fs && fs->GetType() == cm::FileSetMetadata::CXX_MODULES) { | |
| isCXXModule = true; | |
| isPrivate = !fs->IsForInterface(); | |
| } | |
| } | |
| TranslationUnit tu; | |
| // FIXME: Makefiles will want this to be the current working directory. | |
| tu.WorkDirectory = gt->GetLocalGenerator()->GetBinaryDirectory(); | |
| tu.Source = sf->GetFullPath(); | |
| if (!gt->IsSynthetic()) { | |
| auto* gg = gt->GetGlobalGenerator(); | |
| std::string const objectDir = gg->ConvertToOutputPath( | |
| cmStrCat(gt->GetSupportDirectory(), gg->GetConfigDirectory(config))); | |
| std::string const objectFileName = gt->GetObjectName(sf); | |
| tu.Object = cmStrCat(objectDir, '/', objectFileName); | |
| } | |
| if (isCXXModule) { | |
| tu.Provides[PlaceholderName] = PlaceholderName; | |
| } | |
| cmGeneratorTarget::ClassifiedFlags classifiedFlags = | |
| gt->GetClassifiedFlagsForSource(sf, config); | |
| for (auto const& classifiedFlag : classifiedFlags) { | |
| if (classifiedFlag.Classification == | |
| cmGeneratorTarget::FlagClassification::BaselineFlag) { | |
| tu.BaselineArguments.push_back(classifiedFlag.Flag); | |
| tu.LocalArguments.push_back(classifiedFlag.Flag); | |
| } else if (classifiedFlag.Classification == | |
| cmGeneratorTarget::FlagClassification::PrivateFlag) { | |
| tu.LocalArguments.push_back(classifiedFlag.Flag); | |
| } | |
| tu.Arguments.push_back(classifiedFlag.Flag); | |
| } | |
| tu.Private = isPrivate; | |
| set.TranslationUnits.emplace_back(std::move(tu)); | |
| } | |
| db.Sets.emplace_back(std::move(set)); | |
| return db; | |
| } | |
| int cmcmd_cmake_module_compile_db( | |
| std::vector<std::string>::const_iterator argBeg, | |
| std::vector<std::string>::const_iterator argEnd) | |
| { | |
| std::string const* command = nullptr; | |
| std::string const* output = nullptr; | |
| std::vector<std::string const*> inputs; | |
| bool next_is_output = false; | |
| for (auto i = argBeg; i != argEnd; ++i) { | |
| // The first argument is always the command. | |
| if (!command) { | |
| command = &(*i); | |
| continue; | |
| } | |
| if (*i == "-o"_s) { | |
| next_is_output = true; | |
| continue; | |
| } | |
| if (next_is_output) { | |
| if (output) { | |
| cmSystemTools::Error( | |
| "-E cmake_module_compile_db only supports one output file"); | |
| return EXIT_FAILURE; | |
| } | |
| output = &(*i); | |
| next_is_output = false; | |
| continue; | |
| } | |
| inputs.emplace_back(&(*i)); | |
| } | |
| if (!command) { | |
| cmSystemTools::Error("-E cmake_module_compile_db requires a subcommand"); | |
| return EXIT_FAILURE; | |
| } | |
| int ret = EXIT_SUCCESS; | |
| if (*command == "verify"_s) { | |
| if (output) { | |
| cmSystemTools::Error( | |
| "-E cmake_module_compile_db verify does not support an output"); | |
| return EXIT_FAILURE; | |
| } | |
| for (auto const* i : inputs) { | |
| auto db = cmBuildDatabase::Load(*i); | |
| if (!db) { | |
| cmSystemTools::Error(cmStrCat("failed to verify ", *i)); | |
| ret = EXIT_FAILURE; | |
| } | |
| } | |
| } else if (*command == "merge"_s) { | |
| if (!output) { | |
| cmSystemTools::Error( | |
| "-E cmake_module_compile_db verify requires an output"); | |
| return EXIT_FAILURE; | |
| } | |
| std::vector<cmBuildDatabase> dbs; | |
| for (auto const* i : inputs) { | |
| auto db = cmBuildDatabase::Load(*i); | |
| if (!db) { | |
| cmSystemTools::Error(cmStrCat("failed to read ", *i)); | |
| return EXIT_FAILURE; | |
| } | |
| dbs.emplace_back(std::move(*db)); | |
| } | |
| auto db = cmBuildDatabase::Merge(dbs); | |
| db.Write(*output); | |
| } else { | |
| cmSystemTools::Error( | |
| cmStrCat("-E cmake_module_compile_db unknown subcommand ", *command)); | |
| return EXIT_FAILURE; | |
| } | |
| return ret; | |
| } |