mirror of
https://github.com/Kitware/CMake.git
synced 2025-10-14 02:08:27 +08:00

The file API code used unsigned long to hold the major version in most places, but not all. Some places used unsigned int, and an important one of those is the cmFileApi::BuildVersion() function. As a result, it has never been safe for a large value not representable by an unsigned int to be used in these variables. Convert all of the file API version number variables and function arguments to use unsigned int consistently. This avoids any size mismatch warnings when passing values around. They also don't need to be unsigned long, as we never expect version numbers to be anything even close to what an unsigned int cannot represent.
1085 lines
29 KiB
C++
1085 lines
29 KiB
C++
/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
|
|
file LICENSE.rst or https://cmake.org/licensing for details. */
|
|
#include "cmFileAPI.h"
|
|
|
|
#include <algorithm>
|
|
#include <cassert>
|
|
#include <chrono>
|
|
#include <ctime>
|
|
#include <iomanip>
|
|
#include <iterator>
|
|
#include <sstream>
|
|
#include <utility>
|
|
|
|
#include <cm/optional>
|
|
#include <cmext/string_view>
|
|
|
|
#include "cmsys/Directory.hxx"
|
|
#include "cmsys/FStream.hxx"
|
|
|
|
#include "cmCryptoHash.h"
|
|
#include "cmFileAPICMakeFiles.h"
|
|
#include "cmFileAPICache.h"
|
|
#include "cmFileAPICodemodel.h"
|
|
#include "cmFileAPIConfigureLog.h"
|
|
#include "cmFileAPIToolchains.h"
|
|
#include "cmGlobalGenerator.h"
|
|
#include "cmStringAlgorithms.h"
|
|
#include "cmSystemTools.h"
|
|
#include "cmTimestamp.h"
|
|
#include "cmake.h"
|
|
|
|
cmFileAPI::cmFileAPI(cmake* cm)
|
|
: CMakeInstance(cm)
|
|
{
|
|
this->APIv1 =
|
|
cmStrCat(this->CMakeInstance->GetHomeOutputDirectory(), "/.cmake/api/v1");
|
|
|
|
if (cm::optional<std::string> cmakeConfigDir =
|
|
cmSystemTools::GetCMakeConfigDirectory()) {
|
|
this->UserAPIv1 = cmStrCat(std::move(*cmakeConfigDir), "/api/v1"_s);
|
|
}
|
|
|
|
Json::CharReaderBuilder rbuilder;
|
|
rbuilder["collectComments"] = false;
|
|
rbuilder["failIfExtra"] = true;
|
|
rbuilder["rejectDupKeys"] = false;
|
|
rbuilder["strictRoot"] = true;
|
|
this->JsonReader =
|
|
std::unique_ptr<Json::CharReader>(rbuilder.newCharReader());
|
|
|
|
Json::StreamWriterBuilder wbuilder;
|
|
wbuilder["indentation"] = "\t";
|
|
this->JsonWriter =
|
|
std::unique_ptr<Json::StreamWriter>(wbuilder.newStreamWriter());
|
|
}
|
|
|
|
void cmFileAPI::ReadQueries()
|
|
{
|
|
std::string const query_dir = cmStrCat(this->APIv1, "/query");
|
|
std::string const user_query_dir = cmStrCat(this->UserAPIv1, "/query");
|
|
this->QueryExists = cmSystemTools::FileIsDirectory(query_dir);
|
|
if (!this->UserAPIv1.empty()) {
|
|
this->QueryExists =
|
|
this->QueryExists || cmSystemTools::FileIsDirectory(user_query_dir);
|
|
}
|
|
if (!this->QueryExists) {
|
|
return;
|
|
}
|
|
|
|
// Load queries at the top level.
|
|
std::vector<std::string> queries = cmFileAPI::LoadDir(query_dir);
|
|
if (!this->UserAPIv1.empty()) {
|
|
std::vector<std::string> user_queries = cmFileAPI::LoadDir(user_query_dir);
|
|
std::move(user_queries.begin(), user_queries.end(),
|
|
std::back_inserter(queries));
|
|
}
|
|
|
|
// Read the queries and save for later.
|
|
for (std::string& query : queries) {
|
|
if (cmHasLiteralPrefix(query, "client-")) {
|
|
this->ReadClient(query);
|
|
} else if (!cmFileAPI::ReadQuery(query, this->TopQuery.Known)) {
|
|
this->TopQuery.Unknown.push_back(std::move(query));
|
|
}
|
|
}
|
|
}
|
|
|
|
std::vector<unsigned int> cmFileAPI::GetConfigureLogVersions()
|
|
{
|
|
std::vector<unsigned int> versions;
|
|
auto getConfigureLogVersions = [&versions](Query const& q) {
|
|
for (Object const& o : q.Known) {
|
|
if (o.Kind == ObjectKind::ConfigureLog) {
|
|
versions.emplace_back(o.Version);
|
|
}
|
|
}
|
|
};
|
|
getConfigureLogVersions(this->TopQuery);
|
|
for (auto const& client : this->ClientQueries) {
|
|
getConfigureLogVersions(client.second.DirQuery);
|
|
}
|
|
std::sort(versions.begin(), versions.end());
|
|
versions.erase(std::unique(versions.begin(), versions.end()),
|
|
versions.end());
|
|
return versions;
|
|
}
|
|
|
|
void cmFileAPI::WriteReplies(IndexFor indexFor)
|
|
{
|
|
bool const success = indexFor == IndexFor::Success;
|
|
this->ReplyIndexFor = indexFor;
|
|
|
|
if (this->QueryExists) {
|
|
cmSystemTools::MakeDirectory(this->APIv1 + "/reply");
|
|
this->WriteJsonFile(this->BuildReplyIndex(), success ? "index" : "error",
|
|
ComputeSuffixTime);
|
|
}
|
|
|
|
if (success) {
|
|
this->RemoveOldReplyFiles();
|
|
}
|
|
}
|
|
|
|
std::vector<std::string> cmFileAPI::LoadDir(std::string const& dir)
|
|
{
|
|
std::vector<std::string> files;
|
|
cmsys::Directory d;
|
|
d.Load(dir);
|
|
for (unsigned int i = 0; i < d.GetNumberOfFiles(); ++i) {
|
|
std::string f = d.GetFile(i);
|
|
if (f != "." && f != "..") {
|
|
files.push_back(std::move(f));
|
|
}
|
|
}
|
|
std::sort(files.begin(), files.end());
|
|
return files;
|
|
}
|
|
|
|
void cmFileAPI::RemoveOldReplyFiles()
|
|
{
|
|
std::string const reply_dir = this->APIv1 + "/reply";
|
|
std::vector<std::string> files = this->LoadDir(reply_dir);
|
|
for (std::string const& f : files) {
|
|
if (this->ReplyFiles.find(f) == this->ReplyFiles.end()) {
|
|
std::string file = cmStrCat(reply_dir, '/', f);
|
|
cmSystemTools::RemoveFile(file);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool cmFileAPI::ReadJsonFile(std::string const& file, Json::Value& value,
|
|
std::string& error)
|
|
{
|
|
std::vector<char> content;
|
|
|
|
cmsys::ifstream fin;
|
|
if (!cmSystemTools::FileIsDirectory(file)) {
|
|
fin.open(file.c_str(), std::ios::binary);
|
|
}
|
|
auto finEnd = fin.rdbuf()->pubseekoff(0, std::ios::end);
|
|
if (finEnd > 0) {
|
|
size_t finSize = finEnd;
|
|
try {
|
|
// Allocate a buffer to read the whole file.
|
|
content.resize(finSize);
|
|
|
|
// Now read the file from the beginning.
|
|
fin.seekg(0, std::ios::beg);
|
|
fin.read(content.data(), finSize);
|
|
} catch (...) {
|
|
fin.setstate(std::ios::failbit);
|
|
}
|
|
}
|
|
fin.close();
|
|
if (!fin) {
|
|
value = Json::Value();
|
|
error = "failed to read from file";
|
|
return false;
|
|
}
|
|
|
|
// Parse our buffer as json.
|
|
if (!this->JsonReader->parse(content.data(), content.data() + content.size(),
|
|
&value, &error)) {
|
|
value = Json::Value();
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
std::string cmFileAPI::WriteJsonFile(
|
|
Json::Value const& value, std::string const& prefix,
|
|
std::string (*computeSuffix)(std::string const&))
|
|
{
|
|
std::string fileName;
|
|
|
|
// Write the json file with a temporary name.
|
|
std::string const& tmpFile = this->APIv1 + "/tmp.json";
|
|
cmsys::ofstream ftmp(tmpFile.c_str());
|
|
this->JsonWriter->write(value, &ftmp);
|
|
ftmp << "\n";
|
|
ftmp.close();
|
|
if (!ftmp) {
|
|
cmSystemTools::RemoveFile(tmpFile);
|
|
return fileName;
|
|
}
|
|
|
|
// Compute the final name for the file.
|
|
std::string suffix = computeSuffix(tmpFile);
|
|
std::string suffixWithExtension = cmStrCat('-', suffix, ".json");
|
|
fileName = cmStrCat(prefix, suffixWithExtension);
|
|
|
|
// Truncate the file name length
|
|
// eCryptFS has a maximal file name length recommendation of 140
|
|
size_t const maxFileNameLength = 140;
|
|
size_t const fileNameLength = fileName.size();
|
|
if (fileNameLength > maxFileNameLength) {
|
|
size_t const newHashLength = 20;
|
|
size_t const newSuffixLength =
|
|
suffixWithExtension.size() - suffix.size() + newHashLength;
|
|
size_t const overLength =
|
|
fileNameLength - maxFileNameLength + newSuffixLength;
|
|
size_t const startPos = fileNameLength - overLength;
|
|
std::string const toBeRemoved = fileName.substr(startPos, overLength);
|
|
suffix = cmCryptoHash(cmCryptoHash::AlgoSHA256)
|
|
.HashString(toBeRemoved)
|
|
.substr(0, newHashLength);
|
|
suffixWithExtension = cmStrCat('-', suffix, ".json");
|
|
fileName.replace(startPos, overLength, suffixWithExtension);
|
|
}
|
|
|
|
// Create the destination.
|
|
std::string file = this->APIv1 + "/reply";
|
|
cmSystemTools::MakeDirectory(file);
|
|
file += "/";
|
|
file += fileName;
|
|
|
|
// If the final name already exists then assume it has proper content.
|
|
// Otherwise, atomically place the reply file at its final name
|
|
if (cmSystemTools::FileExists(file, true) ||
|
|
!cmSystemTools::RenameFile(tmpFile, file)) {
|
|
cmSystemTools::RemoveFile(tmpFile);
|
|
}
|
|
|
|
// Record this among files we have just written.
|
|
this->ReplyFiles.insert(fileName);
|
|
|
|
return fileName;
|
|
}
|
|
|
|
Json::Value cmFileAPI::MaybeJsonFile(Json::Value in, std::string const& prefix)
|
|
{
|
|
Json::Value out;
|
|
if (in.isObject() || in.isArray()) {
|
|
out = Json::objectValue;
|
|
out["jsonFile"] = this->WriteJsonFile(in, prefix);
|
|
} else {
|
|
out = std::move(in);
|
|
}
|
|
return out;
|
|
}
|
|
|
|
std::string cmFileAPI::ComputeSuffixHash(std::string const& file)
|
|
{
|
|
cmCryptoHash hasher(cmCryptoHash::AlgoSHA3_256);
|
|
std::string hash = hasher.HashFile(file);
|
|
hash.resize(20, '0');
|
|
return hash;
|
|
}
|
|
|
|
std::string cmFileAPI::ComputeSuffixTime(std::string const&)
|
|
{
|
|
std::chrono::milliseconds ms =
|
|
std::chrono::duration_cast<std::chrono::milliseconds>(
|
|
std::chrono::system_clock::now().time_since_epoch());
|
|
std::chrono::seconds s =
|
|
std::chrono::duration_cast<std::chrono::seconds>(ms);
|
|
|
|
std::time_t ts = s.count();
|
|
std::size_t tms = ms.count() % 1000;
|
|
|
|
cmTimestamp cmts;
|
|
std::ostringstream ss;
|
|
ss << cmts.CreateTimestampFromTimeT(ts, "%Y-%m-%dT%H-%M-%S", true) << '-'
|
|
<< std::setfill('0') << std::setw(4) << tms;
|
|
return ss.str();
|
|
}
|
|
|
|
bool cmFileAPI::ReadQuery(std::string const& query,
|
|
std::vector<Object>& objects)
|
|
{
|
|
// Parse the "<kind>-" syntax.
|
|
std::string::size_type sep_pos = query.find('-');
|
|
if (sep_pos == std::string::npos) {
|
|
return false;
|
|
}
|
|
std::string kindName = query.substr(0, sep_pos);
|
|
std::string verStr = query.substr(sep_pos + 1);
|
|
if (kindName == ObjectKindName(ObjectKind::CodeModel)) {
|
|
Object o;
|
|
o.Kind = ObjectKind::CodeModel;
|
|
if (verStr == "v2") {
|
|
o.Version = 2;
|
|
} else {
|
|
return false;
|
|
}
|
|
objects.push_back(o);
|
|
return true;
|
|
}
|
|
if (kindName == ObjectKindName(ObjectKind::ConfigureLog)) {
|
|
Object o;
|
|
o.Kind = ObjectKind::ConfigureLog;
|
|
if (verStr == "v1") {
|
|
o.Version = 1;
|
|
} else {
|
|
return false;
|
|
}
|
|
objects.push_back(o);
|
|
return true;
|
|
}
|
|
if (kindName == ObjectKindName(ObjectKind::Cache)) {
|
|
Object o;
|
|
o.Kind = ObjectKind::Cache;
|
|
if (verStr == "v2") {
|
|
o.Version = 2;
|
|
} else {
|
|
return false;
|
|
}
|
|
objects.push_back(o);
|
|
return true;
|
|
}
|
|
if (kindName == ObjectKindName(ObjectKind::CMakeFiles)) {
|
|
Object o;
|
|
o.Kind = ObjectKind::CMakeFiles;
|
|
if (verStr == "v1") {
|
|
o.Version = 1;
|
|
} else {
|
|
return false;
|
|
}
|
|
objects.push_back(o);
|
|
return true;
|
|
}
|
|
if (kindName == ObjectKindName(ObjectKind::Toolchains)) {
|
|
Object o;
|
|
o.Kind = ObjectKind::Toolchains;
|
|
if (verStr == "v1") {
|
|
o.Version = 1;
|
|
} else {
|
|
return false;
|
|
}
|
|
objects.push_back(o);
|
|
return true;
|
|
}
|
|
if (kindName == ObjectKindName(ObjectKind::InternalTest)) {
|
|
Object o;
|
|
o.Kind = ObjectKind::InternalTest;
|
|
if (verStr == "v1") {
|
|
o.Version = 1;
|
|
} else if (verStr == "v2") {
|
|
o.Version = 2;
|
|
} else {
|
|
return false;
|
|
}
|
|
objects.push_back(o);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void cmFileAPI::ReadClient(std::string const& client)
|
|
{
|
|
// Load queries for the client.
|
|
std::string clientDir = this->APIv1 + "/query/" + client;
|
|
std::vector<std::string> queries = this->LoadDir(clientDir);
|
|
|
|
// Read the queries and save for later.
|
|
ClientQuery& clientQuery = this->ClientQueries[client];
|
|
for (std::string& query : queries) {
|
|
if (query == "query.json") {
|
|
clientQuery.HaveQueryJson = true;
|
|
this->ReadClientQuery(client, clientQuery.QueryJson);
|
|
} else if (!this->ReadQuery(query, clientQuery.DirQuery.Known)) {
|
|
clientQuery.DirQuery.Unknown.push_back(std::move(query));
|
|
}
|
|
}
|
|
}
|
|
|
|
void cmFileAPI::ReadClientQuery(std::string const& client, ClientQueryJson& q)
|
|
{
|
|
// Read the query.json file.
|
|
std::string queryFile = this->APIv1 + "/query/" + client + "/query.json";
|
|
Json::Value query;
|
|
if (!this->ReadJsonFile(queryFile, query, q.Error)) {
|
|
return;
|
|
}
|
|
if (!query.isObject()) {
|
|
q.Error = "query root is not an object";
|
|
return;
|
|
}
|
|
|
|
Json::Value const& clientValue = query["client"];
|
|
if (!clientValue.isNull()) {
|
|
q.ClientValue = clientValue;
|
|
}
|
|
q.RequestsValue = std::move(query["requests"]);
|
|
q.Requests = this->BuildClientRequests(q.RequestsValue);
|
|
}
|
|
|
|
Json::Value cmFileAPI::BuildReplyIndex()
|
|
{
|
|
Json::Value index(Json::objectValue);
|
|
|
|
// Report information about this version of CMake.
|
|
index["cmake"] = this->BuildCMake();
|
|
|
|
// Reply to all queries that we loaded.
|
|
Json::Value& reply = index["reply"] = this->BuildReply(this->TopQuery);
|
|
for (auto const& client : this->ClientQueries) {
|
|
std::string const& clientName = client.first;
|
|
ClientQuery const& clientQuery = client.second;
|
|
reply[clientName] = this->BuildClientReply(clientQuery);
|
|
}
|
|
|
|
// Move our index of generated objects into its field.
|
|
Json::Value& objects = index["objects"] = Json::arrayValue;
|
|
for (auto& entry : this->ReplyIndexObjects) {
|
|
objects.append(std::move(entry.second)); // NOLINT(*)
|
|
}
|
|
|
|
return index;
|
|
}
|
|
|
|
Json::Value cmFileAPI::BuildCMake()
|
|
{
|
|
Json::Value cmake = Json::objectValue;
|
|
cmake["version"] = this->CMakeInstance->ReportVersionJson();
|
|
Json::Value& cmake_paths = cmake["paths"] = Json::objectValue;
|
|
cmake_paths["cmake"] = cmSystemTools::GetCMakeCommand();
|
|
cmake_paths["ctest"] = cmSystemTools::GetCTestCommand();
|
|
cmake_paths["cpack"] = cmSystemTools::GetCPackCommand();
|
|
cmake_paths["root"] = cmSystemTools::GetCMakeRoot();
|
|
cmake["generator"] = this->CMakeInstance->GetGlobalGenerator()->GetJson();
|
|
return cmake;
|
|
}
|
|
|
|
Json::Value cmFileAPI::BuildReply(Query const& q)
|
|
{
|
|
Json::Value reply = Json::objectValue;
|
|
for (Object const& o : q.Known) {
|
|
std::string const& name = ObjectName(o);
|
|
reply[name] = this->BuildReplyEntry(o);
|
|
}
|
|
|
|
for (std::string const& name : q.Unknown) {
|
|
reply[name] = cmFileAPI::BuildReplyError("unknown query file");
|
|
}
|
|
return reply;
|
|
}
|
|
|
|
Json::Value cmFileAPI::BuildReplyEntry(Object const& object)
|
|
{
|
|
if (this->ReplyIndexFor != IndexFor::Success) {
|
|
switch (object.Kind) {
|
|
case ObjectKind::ConfigureLog:
|
|
break;
|
|
case ObjectKind::CodeModel:
|
|
case ObjectKind::Cache:
|
|
case ObjectKind::CMakeFiles:
|
|
case ObjectKind::Toolchains:
|
|
case ObjectKind::InternalTest:
|
|
return this->BuildReplyError("no buildsystem generated");
|
|
}
|
|
}
|
|
return this->AddReplyIndexObject(object);
|
|
}
|
|
|
|
Json::Value cmFileAPI::BuildReplyError(std::string const& error)
|
|
{
|
|
Json::Value e = Json::objectValue;
|
|
e["error"] = error;
|
|
return e;
|
|
}
|
|
|
|
Json::Value const& cmFileAPI::AddReplyIndexObject(Object const& o)
|
|
{
|
|
Json::Value& indexEntry = this->ReplyIndexObjects[o];
|
|
if (!indexEntry.isNull()) {
|
|
// The reply object has already been generated.
|
|
return indexEntry;
|
|
}
|
|
|
|
// Generate this reply object.
|
|
Json::Value const& object = this->BuildObject(o);
|
|
assert(object.isObject());
|
|
|
|
// Populate this index entry.
|
|
indexEntry = Json::objectValue;
|
|
indexEntry["kind"] = object["kind"];
|
|
indexEntry["version"] = object["version"];
|
|
indexEntry["jsonFile"] = this->WriteJsonFile(object, ObjectName(o));
|
|
return indexEntry;
|
|
}
|
|
|
|
char const* cmFileAPI::ObjectKindName(ObjectKind kind)
|
|
{
|
|
// Keep in sync with ObjectKind enum.
|
|
static char const* objectKindNames[] = {
|
|
"codemodel", //
|
|
"configureLog", //
|
|
"cache", //
|
|
"cmakeFiles", //
|
|
"toolchains", //
|
|
"__test" //
|
|
};
|
|
return objectKindNames[static_cast<size_t>(kind)];
|
|
}
|
|
|
|
std::string cmFileAPI::ObjectName(Object const& o)
|
|
{
|
|
std::string name = cmStrCat(ObjectKindName(o.Kind), "-v", o.Version);
|
|
return name;
|
|
}
|
|
|
|
Json::Value cmFileAPI::BuildVersion(unsigned int major, unsigned int minor)
|
|
{
|
|
Json::Value version;
|
|
version["major"] = major;
|
|
version["minor"] = minor;
|
|
return version;
|
|
}
|
|
|
|
Json::Value cmFileAPI::BuildObject(Object const& object)
|
|
{
|
|
Json::Value value;
|
|
|
|
switch (object.Kind) {
|
|
case ObjectKind::CodeModel:
|
|
value = this->BuildCodeModel(object);
|
|
break;
|
|
case ObjectKind::ConfigureLog:
|
|
value = this->BuildConfigureLog(object);
|
|
break;
|
|
case ObjectKind::Cache:
|
|
value = this->BuildCache(object);
|
|
break;
|
|
case ObjectKind::CMakeFiles:
|
|
value = this->BuildCMakeFiles(object);
|
|
break;
|
|
case ObjectKind::Toolchains:
|
|
value = this->BuildToolchains(object);
|
|
break;
|
|
case ObjectKind::InternalTest:
|
|
value = this->BuildInternalTest(object);
|
|
break;
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
cmFileAPI::ClientRequests cmFileAPI::BuildClientRequests(
|
|
Json::Value const& requests)
|
|
{
|
|
ClientRequests result;
|
|
if (requests.isNull()) {
|
|
result.Error = "'requests' member missing";
|
|
return result;
|
|
}
|
|
if (!requests.isArray()) {
|
|
result.Error = "'requests' member is not an array";
|
|
return result;
|
|
}
|
|
|
|
result.reserve(requests.size());
|
|
for (Json::Value const& request : requests) {
|
|
result.emplace_back(this->BuildClientRequest(request));
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
cmFileAPI::ClientRequest cmFileAPI::BuildClientRequest(
|
|
Json::Value const& request)
|
|
{
|
|
ClientRequest r;
|
|
|
|
if (!request.isObject()) {
|
|
r.Error = "request is not an object";
|
|
return r;
|
|
}
|
|
|
|
Json::Value const& kind = request["kind"];
|
|
if (kind.isNull()) {
|
|
r.Error = "'kind' member missing";
|
|
return r;
|
|
}
|
|
if (!kind.isString()) {
|
|
r.Error = "'kind' member is not a string";
|
|
return r;
|
|
}
|
|
std::string const& kindName = kind.asString();
|
|
|
|
if (kindName == this->ObjectKindName(ObjectKind::CodeModel)) {
|
|
r.Kind = ObjectKind::CodeModel;
|
|
} else if (kindName == this->ObjectKindName(ObjectKind::ConfigureLog)) {
|
|
r.Kind = ObjectKind::ConfigureLog;
|
|
} else if (kindName == this->ObjectKindName(ObjectKind::Cache)) {
|
|
r.Kind = ObjectKind::Cache;
|
|
} else if (kindName == this->ObjectKindName(ObjectKind::CMakeFiles)) {
|
|
r.Kind = ObjectKind::CMakeFiles;
|
|
} else if (kindName == this->ObjectKindName(ObjectKind::Toolchains)) {
|
|
r.Kind = ObjectKind::Toolchains;
|
|
} else if (kindName == this->ObjectKindName(ObjectKind::InternalTest)) {
|
|
r.Kind = ObjectKind::InternalTest;
|
|
} else {
|
|
r.Error = "unknown request kind '" + kindName + "'";
|
|
return r;
|
|
}
|
|
|
|
Json::Value const& version = request["version"];
|
|
if (version.isNull()) {
|
|
r.Error = "'version' member missing";
|
|
return r;
|
|
}
|
|
std::vector<RequestVersion> versions;
|
|
if (!cmFileAPI::ReadRequestVersions(version, versions, r.Error)) {
|
|
return r;
|
|
}
|
|
|
|
switch (r.Kind) {
|
|
case ObjectKind::CodeModel:
|
|
this->BuildClientRequestCodeModel(r, versions);
|
|
break;
|
|
case ObjectKind::ConfigureLog:
|
|
this->BuildClientRequestConfigureLog(r, versions);
|
|
break;
|
|
case ObjectKind::Cache:
|
|
this->BuildClientRequestCache(r, versions);
|
|
break;
|
|
case ObjectKind::CMakeFiles:
|
|
this->BuildClientRequestCMakeFiles(r, versions);
|
|
break;
|
|
case ObjectKind::Toolchains:
|
|
this->BuildClientRequestToolchains(r, versions);
|
|
break;
|
|
case ObjectKind::InternalTest:
|
|
this->BuildClientRequestInternalTest(r, versions);
|
|
break;
|
|
}
|
|
|
|
return r;
|
|
}
|
|
|
|
Json::Value cmFileAPI::BuildClientReply(ClientQuery const& q)
|
|
{
|
|
Json::Value reply = this->BuildReply(q.DirQuery);
|
|
|
|
if (!q.HaveQueryJson) {
|
|
return reply;
|
|
}
|
|
|
|
Json::Value& reply_query_json = reply["query.json"];
|
|
ClientQueryJson const& qj = q.QueryJson;
|
|
|
|
if (!qj.Error.empty()) {
|
|
reply_query_json = this->BuildReplyError(qj.Error);
|
|
return reply;
|
|
}
|
|
|
|
if (!qj.ClientValue.isNull()) {
|
|
reply_query_json["client"] = qj.ClientValue;
|
|
}
|
|
|
|
if (!qj.RequestsValue.isNull()) {
|
|
reply_query_json["requests"] = qj.RequestsValue;
|
|
}
|
|
|
|
reply_query_json["responses"] = this->BuildClientReplyResponses(qj.Requests);
|
|
|
|
return reply;
|
|
}
|
|
|
|
Json::Value cmFileAPI::BuildClientReplyResponses(
|
|
ClientRequests const& requests)
|
|
{
|
|
Json::Value responses;
|
|
|
|
if (!requests.Error.empty()) {
|
|
responses = this->BuildReplyError(requests.Error);
|
|
return responses;
|
|
}
|
|
|
|
responses = Json::arrayValue;
|
|
for (ClientRequest const& request : requests) {
|
|
responses.append(this->BuildClientReplyResponse(request));
|
|
}
|
|
|
|
return responses;
|
|
}
|
|
|
|
Json::Value cmFileAPI::BuildClientReplyResponse(ClientRequest const& request)
|
|
{
|
|
Json::Value response;
|
|
if (!request.Error.empty()) {
|
|
response = this->BuildReplyError(request.Error);
|
|
return response;
|
|
}
|
|
response = this->BuildReplyEntry(request);
|
|
return response;
|
|
}
|
|
|
|
bool cmFileAPI::ReadRequestVersions(Json::Value const& version,
|
|
std::vector<RequestVersion>& versions,
|
|
std::string& error)
|
|
{
|
|
if (version.isArray()) {
|
|
for (Json::Value const& v : version) {
|
|
if (!ReadRequestVersion(v, /*inArray=*/true, versions, error)) {
|
|
return false;
|
|
}
|
|
}
|
|
} else {
|
|
if (!ReadRequestVersion(version, /*inArray=*/false, versions, error)) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool cmFileAPI::ReadRequestVersion(Json::Value const& version, bool inArray,
|
|
std::vector<RequestVersion>& result,
|
|
std::string& error)
|
|
{
|
|
if (version.isUInt()) {
|
|
RequestVersion v;
|
|
v.Major = version.asUInt();
|
|
result.push_back(v);
|
|
return true;
|
|
}
|
|
|
|
if (!version.isObject()) {
|
|
if (inArray) {
|
|
error = "'version' array entry is not a non-negative integer or object";
|
|
} else {
|
|
error =
|
|
"'version' member is not a non-negative integer, object, or array";
|
|
}
|
|
return false;
|
|
}
|
|
|
|
Json::Value const& major = version["major"];
|
|
if (major.isNull()) {
|
|
error = "'version' object 'major' member missing";
|
|
return false;
|
|
}
|
|
if (!major.isUInt()) {
|
|
error = "'version' object 'major' member is not a non-negative integer";
|
|
return false;
|
|
}
|
|
|
|
RequestVersion v;
|
|
v.Major = major.asUInt();
|
|
|
|
Json::Value const& minor = version["minor"];
|
|
if (minor.isUInt()) {
|
|
v.Minor = minor.asUInt();
|
|
} else if (!minor.isNull()) {
|
|
error = "'version' object 'minor' member is not a non-negative integer";
|
|
return false;
|
|
}
|
|
|
|
result.push_back(v);
|
|
|
|
return true;
|
|
}
|
|
|
|
std::string cmFileAPI::NoSupportedVersion(
|
|
std::vector<RequestVersion> const& versions)
|
|
{
|
|
std::ostringstream msg;
|
|
msg << "no supported version specified";
|
|
if (!versions.empty()) {
|
|
msg << " among:";
|
|
for (RequestVersion const& v : versions) {
|
|
msg << " " << v.Major << "." << v.Minor;
|
|
}
|
|
}
|
|
return msg.str();
|
|
}
|
|
|
|
// The "codemodel" object kind.
|
|
|
|
// Update Help/manual/cmake-file-api.7.rst when updating this constant.
|
|
static unsigned int const CodeModelV2Minor = 8;
|
|
|
|
void cmFileAPI::BuildClientRequestCodeModel(
|
|
ClientRequest& r, std::vector<RequestVersion> const& versions)
|
|
{
|
|
// Select a known version from those requested.
|
|
for (RequestVersion const& v : versions) {
|
|
if ((v.Major == 2 && v.Minor <= CodeModelV2Minor)) {
|
|
r.Version = v.Major;
|
|
break;
|
|
}
|
|
}
|
|
if (!r.Version) {
|
|
r.Error = NoSupportedVersion(versions);
|
|
}
|
|
}
|
|
|
|
Json::Value cmFileAPI::BuildCodeModel(Object const& object)
|
|
{
|
|
Json::Value codemodel = cmFileAPICodemodelDump(*this, object.Version);
|
|
codemodel["kind"] = this->ObjectKindName(object.Kind);
|
|
|
|
Json::Value& version = codemodel["version"];
|
|
if (object.Version == 2) {
|
|
version = BuildVersion(2, CodeModelV2Minor);
|
|
} else {
|
|
return codemodel; // should be unreachable
|
|
}
|
|
|
|
return codemodel;
|
|
}
|
|
|
|
// The "configureLog" object kind.
|
|
|
|
// Update Help/manual/cmake-file-api.7.rst when updating this constant.
|
|
static unsigned int const ConfigureLogV1Minor = 0;
|
|
|
|
void cmFileAPI::BuildClientRequestConfigureLog(
|
|
ClientRequest& r, std::vector<RequestVersion> const& versions)
|
|
{
|
|
// Select a known version from those requested.
|
|
for (RequestVersion const& v : versions) {
|
|
if ((v.Major == 1 && v.Minor <= ConfigureLogV1Minor)) {
|
|
r.Version = v.Major;
|
|
break;
|
|
}
|
|
}
|
|
if (!r.Version) {
|
|
r.Error = NoSupportedVersion(versions);
|
|
}
|
|
}
|
|
|
|
Json::Value cmFileAPI::BuildConfigureLog(Object const& object)
|
|
{
|
|
Json::Value configureLog = cmFileAPIConfigureLogDump(*this, object.Version);
|
|
configureLog["kind"] = this->ObjectKindName(object.Kind);
|
|
|
|
Json::Value& version = configureLog["version"];
|
|
if (object.Version == 1) {
|
|
version = BuildVersion(1, ConfigureLogV1Minor);
|
|
} else {
|
|
return configureLog; // should be unreachable
|
|
}
|
|
|
|
return configureLog;
|
|
}
|
|
|
|
// The "cache" object kind.
|
|
|
|
static unsigned int const CacheV2Minor = 0;
|
|
|
|
void cmFileAPI::BuildClientRequestCache(
|
|
ClientRequest& r, std::vector<RequestVersion> const& versions)
|
|
{
|
|
// Select a known version from those requested.
|
|
for (RequestVersion const& v : versions) {
|
|
if ((v.Major == 2 && v.Minor <= CacheV2Minor)) {
|
|
r.Version = v.Major;
|
|
break;
|
|
}
|
|
}
|
|
if (!r.Version) {
|
|
r.Error = NoSupportedVersion(versions);
|
|
}
|
|
}
|
|
|
|
Json::Value cmFileAPI::BuildCache(Object const& object)
|
|
{
|
|
Json::Value cache = cmFileAPICacheDump(*this, object.Version);
|
|
cache["kind"] = this->ObjectKindName(object.Kind);
|
|
|
|
Json::Value& version = cache["version"];
|
|
if (object.Version == 2) {
|
|
version = BuildVersion(2, CacheV2Minor);
|
|
} else {
|
|
return cache; // should be unreachable
|
|
}
|
|
|
|
return cache;
|
|
}
|
|
|
|
// The "cmakeFiles" object kind.
|
|
|
|
static unsigned int const CMakeFilesV1Minor = 1;
|
|
|
|
void cmFileAPI::BuildClientRequestCMakeFiles(
|
|
ClientRequest& r, std::vector<RequestVersion> const& versions)
|
|
{
|
|
// Select a known version from those requested.
|
|
for (RequestVersion const& v : versions) {
|
|
if ((v.Major == 1 && v.Minor <= CMakeFilesV1Minor)) {
|
|
r.Version = v.Major;
|
|
break;
|
|
}
|
|
}
|
|
if (!r.Version) {
|
|
r.Error = NoSupportedVersion(versions);
|
|
}
|
|
}
|
|
|
|
Json::Value cmFileAPI::BuildCMakeFiles(Object const& object)
|
|
{
|
|
Json::Value cmakeFiles = cmFileAPICMakeFilesDump(*this, object.Version);
|
|
cmakeFiles["kind"] = this->ObjectKindName(object.Kind);
|
|
|
|
Json::Value& version = cmakeFiles["version"];
|
|
if (object.Version == 1) {
|
|
version = BuildVersion(1, CMakeFilesV1Minor);
|
|
} else {
|
|
return cmakeFiles; // should be unreachable
|
|
}
|
|
|
|
return cmakeFiles;
|
|
}
|
|
|
|
// The "toolchains" object kind.
|
|
|
|
static unsigned int const ToolchainsV1Minor = 0;
|
|
|
|
void cmFileAPI::BuildClientRequestToolchains(
|
|
ClientRequest& r, std::vector<RequestVersion> const& versions)
|
|
{
|
|
// Select a known version from those requested.
|
|
for (RequestVersion const& v : versions) {
|
|
if ((v.Major == 1 && v.Minor <= ToolchainsV1Minor)) {
|
|
r.Version = v.Major;
|
|
break;
|
|
}
|
|
}
|
|
if (!r.Version) {
|
|
r.Error = NoSupportedVersion(versions);
|
|
}
|
|
}
|
|
|
|
Json::Value cmFileAPI::BuildToolchains(Object const& object)
|
|
{
|
|
Json::Value toolchains = cmFileAPIToolchainsDump(*this, object.Version);
|
|
toolchains["kind"] = this->ObjectKindName(object.Kind);
|
|
|
|
Json::Value& version = toolchains["version"];
|
|
if (object.Version == 1) {
|
|
version = BuildVersion(1, ToolchainsV1Minor);
|
|
} else {
|
|
return toolchains; // should be unreachable
|
|
}
|
|
|
|
return toolchains;
|
|
}
|
|
|
|
// The "__test" object kind is for internal testing of CMake.
|
|
|
|
static unsigned int const InternalTestV1Minor = 3;
|
|
static unsigned int const InternalTestV2Minor = 0;
|
|
|
|
void cmFileAPI::BuildClientRequestInternalTest(
|
|
ClientRequest& r, std::vector<RequestVersion> const& versions)
|
|
{
|
|
// Select a known version from those requested.
|
|
for (RequestVersion const& v : versions) {
|
|
if ((v.Major == 1 && v.Minor <= InternalTestV1Minor) || //
|
|
(v.Major == 2 && v.Minor <= InternalTestV2Minor)) {
|
|
r.Version = v.Major;
|
|
break;
|
|
}
|
|
}
|
|
if (!r.Version) {
|
|
r.Error = NoSupportedVersion(versions);
|
|
}
|
|
}
|
|
|
|
Json::Value cmFileAPI::BuildInternalTest(Object const& object)
|
|
{
|
|
Json::Value test = Json::objectValue;
|
|
test["kind"] = this->ObjectKindName(object.Kind);
|
|
Json::Value& version = test["version"];
|
|
if (object.Version == 2) {
|
|
version = BuildVersion(2, InternalTestV2Minor);
|
|
} else {
|
|
version = BuildVersion(1, InternalTestV1Minor);
|
|
}
|
|
return test;
|
|
}
|
|
|
|
Json::Value cmFileAPI::ReportCapabilities()
|
|
{
|
|
Json::Value capabilities = Json::objectValue;
|
|
Json::Value& requests = capabilities["requests"] = Json::arrayValue;
|
|
|
|
{
|
|
Json::Value request = Json::objectValue;
|
|
request["kind"] = ObjectKindName(ObjectKind::CodeModel);
|
|
Json::Value& versions = request["version"] = Json::arrayValue;
|
|
versions.append(BuildVersion(2, CodeModelV2Minor));
|
|
requests.append(std::move(request)); // NOLINT(*)
|
|
}
|
|
|
|
{
|
|
Json::Value request = Json::objectValue;
|
|
request["kind"] = ObjectKindName(ObjectKind::ConfigureLog);
|
|
Json::Value& versions = request["version"] = Json::arrayValue;
|
|
versions.append(BuildVersion(1, ConfigureLogV1Minor));
|
|
requests.append(std::move(request)); // NOLINT(*)
|
|
}
|
|
|
|
{
|
|
Json::Value request = Json::objectValue;
|
|
request["kind"] = ObjectKindName(ObjectKind::Cache);
|
|
Json::Value& versions = request["version"] = Json::arrayValue;
|
|
versions.append(BuildVersion(2, CacheV2Minor));
|
|
requests.append(std::move(request)); // NOLINT(*)
|
|
}
|
|
|
|
{
|
|
Json::Value request = Json::objectValue;
|
|
request["kind"] = ObjectKindName(ObjectKind::CMakeFiles);
|
|
Json::Value& versions = request["version"] = Json::arrayValue;
|
|
versions.append(BuildVersion(1, CMakeFilesV1Minor));
|
|
requests.append(std::move(request)); // NOLINT(*)
|
|
}
|
|
|
|
{
|
|
Json::Value request = Json::objectValue;
|
|
request["kind"] = ObjectKindName(ObjectKind::Toolchains);
|
|
Json::Value& versions = request["version"] = Json::arrayValue;
|
|
versions.append(BuildVersion(1, ToolchainsV1Minor));
|
|
requests.append(std::move(request)); // NOLINT(*)
|
|
}
|
|
|
|
return capabilities;
|
|
}
|
|
|
|
bool cmFileAPI::AddProjectQuery(cmFileAPI::ObjectKind kind,
|
|
unsigned majorVersion, unsigned minorVersion)
|
|
{
|
|
switch (kind) {
|
|
case ObjectKind::CodeModel:
|
|
if (majorVersion != 2 || minorVersion > CodeModelV2Minor) {
|
|
return false;
|
|
}
|
|
break;
|
|
case ObjectKind::Cache:
|
|
if (majorVersion != 2 || minorVersion > CacheV2Minor) {
|
|
return false;
|
|
}
|
|
break;
|
|
case ObjectKind::CMakeFiles:
|
|
if (majorVersion != 1 || minorVersion > CMakeFilesV1Minor) {
|
|
return false;
|
|
}
|
|
break;
|
|
case ObjectKind::Toolchains:
|
|
if (majorVersion != 1 || minorVersion > ToolchainsV1Minor) {
|
|
return false;
|
|
}
|
|
break;
|
|
// These cannot be requested by the project
|
|
case ObjectKind::ConfigureLog:
|
|
case ObjectKind::InternalTest:
|
|
return false;
|
|
}
|
|
|
|
Object query;
|
|
query.Kind = kind;
|
|
query.Version = majorVersion;
|
|
if (std::find(this->TopQuery.Known.begin(), this->TopQuery.Known.end(),
|
|
query) == this->TopQuery.Known.end()) {
|
|
this->TopQuery.Known.emplace_back(query);
|
|
this->QueryExists = true;
|
|
}
|
|
|
|
return true;
|
|
}
|