| /* 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/reader.h> |
| #include <cm3p/json/value.h> |
| #include <cm3p/json/writer.h> |
| |
| #include "cmsys/FStream.hxx" |
| |
| #include "cmComputeLinkInformation.h" |
| #include "cmFileSet.h" |
| #include "cmGeneratedFileStream.h" |
| #include "cmGeneratorTarget.h" |
| #include "cmGlobalGenerator.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; |
| { |
| cmsys::ifstream mcdbf(path.c_str(), std::ios::in | std::ios::binary); |
| Json::Reader reader; |
| if (!reader.parse(mcdbf, mcdb, false)) { |
| cmSystemTools::Error( |
| cmStrCat("-E cmake_module_compile_db failed to parse ", path, |
| reader.getFormattedErrorMessages())); |
| 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 (sf->GetLanguage() == "CXX"_s) { |
| auto const* fs = gt->GetFileSetForSource(config, sf); |
| if (fs && fs->GetType() == "CXX_MODULES"_s) { |
| isCXXModule = true; |
| isPrivate = !cmFileSetVisibilityIsForInterface(fs->GetVisibility()); |
| } |
| } |
| |
| 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; |
| } |