cmBuildDatabase: add initial structures

This class represents a build database as introduced by P2977R0. It
includes support for reading, writing, and merging.

See: http://wg21.link/p2977r0
This commit is contained in:
Ben Boeckel
2023-09-24 15:35:43 -04:00
parent 7b8b751637
commit 4df5a6fbb4
4 changed files with 426 additions and 0 deletions

View File

@@ -121,6 +121,8 @@ add_library(
cmBinUtilsWindowsPELinker.h
cmBinUtilsWindowsPEObjdumpGetRuntimeDependenciesTool.cxx
cmBinUtilsWindowsPEObjdumpGetRuntimeDependenciesTool.h
cmBuildDatabase.cxx
cmBuildDatabase.h
cmBuildOptions.h
cmCacheManager.cxx
cmCacheManager.h

374
Source/cmBuildDatabase.cxx Normal file
View File

@@ -0,0 +1,374 @@
/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
file Copyright.txt or https://cmake.org/licensing for details. */
#include "cmBuildDatabase.h"
#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 "cmGeneratedFileStream.h"
#include "cmStringAlgorithms.h"
#include "cmSystemTools.h"
cmBuildDatabase::cmBuildDatabase() = default;
cmBuildDatabase::cmBuildDatabase(cmBuildDatabase const&) = default;
cmBuildDatabase::~cmBuildDatabase() = default;
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;
}

49
Source/cmBuildDatabase.h Normal file
View File

@@ -0,0 +1,49 @@
/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
file Copyright.txt or https://cmake.org/licensing for details. */
#pragma once
#include "cmConfigure.h" // IWYU pragma: keep
#include <map>
#include <memory>
#include <string>
#include <vector>
#include <cm/optional>
class cmBuildDatabase
{
public:
struct TranslationUnit
{
std::string WorkDirectory;
std::string Source;
cm::optional<std::string> Object;
std::vector<std::string> Requires;
std::map<std::string, std::string> Provides;
std::vector<std::string> BaselineArguments;
std::vector<std::string> LocalArguments;
std::vector<std::string> Arguments;
bool Private = false;
};
struct Set
{
std::string Name;
std::string FamilyName;
std::vector<std::string> VisibleSets;
std::vector<TranslationUnit> TranslationUnits;
};
cmBuildDatabase();
cmBuildDatabase(cmBuildDatabase const&);
~cmBuildDatabase();
void Write(std::string const& path) const;
static std::unique_ptr<cmBuildDatabase> Load(std::string const& path);
static cmBuildDatabase Merge(std::vector<cmBuildDatabase> const& components);
private:
std::vector<Set> Sets;
};

View File

@@ -306,6 +306,7 @@ CMAKE_CXX_SOURCES="\
cmBlockCommand \
cmBreakCommand \
cmBuildCommand \
cmBuildDatabase \
cmCMakeLanguageCommand \
cmCMakeMinimumRequired \
cmList \