mirror of
https://github.com/Kitware/CMake.git
synced 2025-06-16 17:03:54 +08:00

Make entry points for these that do not reference the server code. For now we leave the "cache" object generation alone because its implementation interleaves error handling and the format may not suitable outside a server response.
1548 lines
47 KiB
C++
1548 lines
47 KiB
C++
/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
|
|
file Copyright.txt or https://cmake.org/licensing for details. */
|
|
#include "cmServerProtocol.h"
|
|
|
|
#include "cmAlgorithms.h"
|
|
#include "cmExternalMakefileProjectGenerator.h"
|
|
#include "cmFileMonitor.h"
|
|
#include "cmGeneratorExpression.h"
|
|
#include "cmGeneratorTarget.h"
|
|
#include "cmGlobalGenerator.h"
|
|
#include "cmInstallGenerator.h"
|
|
#include "cmInstallTargetGenerator.h"
|
|
#include "cmLinkLineComputer.h"
|
|
#include "cmLocalGenerator.h"
|
|
#include "cmMakefile.h"
|
|
#include "cmProperty.h"
|
|
#include "cmServer.h"
|
|
#include "cmServerDictionary.h"
|
|
#include "cmSourceFile.h"
|
|
#include "cmState.h"
|
|
#include "cmStateDirectory.h"
|
|
#include "cmStateSnapshot.h"
|
|
#include "cmStateTypes.h"
|
|
#include "cmSystemTools.h"
|
|
#include "cmTarget.h"
|
|
#include "cmTest.h"
|
|
#include "cm_uv.h"
|
|
#include "cmake.h"
|
|
|
|
#include <algorithm>
|
|
#include <cassert>
|
|
#include <cstddef>
|
|
#include <functional>
|
|
#include <limits>
|
|
#include <map>
|
|
#include <memory>
|
|
#include <set>
|
|
#include <string>
|
|
#include <unordered_map>
|
|
#include <vector>
|
|
|
|
// Get rid of some windows macros:
|
|
#undef max
|
|
|
|
namespace {
|
|
|
|
std::vector<std::string> getConfigurations(const cmake* cm)
|
|
{
|
|
std::vector<std::string> configurations;
|
|
auto makefiles = cm->GetGlobalGenerator()->GetMakefiles();
|
|
if (makefiles.empty()) {
|
|
return configurations;
|
|
}
|
|
|
|
makefiles[0]->GetConfigurations(configurations);
|
|
if (configurations.empty()) {
|
|
configurations.push_back("");
|
|
}
|
|
return configurations;
|
|
}
|
|
|
|
bool hasString(const Json::Value& v, const std::string& s)
|
|
{
|
|
return !v.isNull() &&
|
|
std::any_of(v.begin(), v.end(),
|
|
[s](const Json::Value& i) { return i.asString() == s; });
|
|
}
|
|
|
|
template <class T>
|
|
Json::Value fromStringList(const T& in)
|
|
{
|
|
Json::Value result = Json::arrayValue;
|
|
for (std::string const& i : in) {
|
|
result.append(i);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
std::vector<std::string> toStringList(const Json::Value& in)
|
|
{
|
|
std::vector<std::string> result;
|
|
for (auto const& it : in) {
|
|
result.push_back(it.asString());
|
|
}
|
|
return result;
|
|
}
|
|
|
|
void getCMakeInputs(const cmGlobalGenerator* gg, const std::string& sourceDir,
|
|
const std::string& buildDir,
|
|
std::vector<std::string>* internalFiles,
|
|
std::vector<std::string>* explicitFiles,
|
|
std::vector<std::string>* tmpFiles)
|
|
{
|
|
const std::string cmakeRootDir = cmSystemTools::GetCMakeRoot() + '/';
|
|
std::vector<cmMakefile*> const& makefiles = gg->GetMakefiles();
|
|
for (cmMakefile const* mf : makefiles) {
|
|
for (std::string const& lf : mf->GetListFiles()) {
|
|
|
|
const std::string startOfFile = lf.substr(0, cmakeRootDir.size());
|
|
const bool isInternal = (startOfFile == cmakeRootDir);
|
|
const bool isTemporary = !isInternal && (lf.find(buildDir + '/') == 0);
|
|
|
|
std::string toAdd = lf;
|
|
if (!sourceDir.empty()) {
|
|
const std::string& relative =
|
|
cmSystemTools::RelativePath(sourceDir, lf);
|
|
if (toAdd.size() > relative.size()) {
|
|
toAdd = relative;
|
|
}
|
|
}
|
|
|
|
if (isInternal) {
|
|
if (internalFiles) {
|
|
internalFiles->push_back(std::move(toAdd));
|
|
}
|
|
} else {
|
|
if (isTemporary) {
|
|
if (tmpFiles) {
|
|
tmpFiles->push_back(std::move(toAdd));
|
|
}
|
|
} else {
|
|
if (explicitFiles) {
|
|
explicitFiles->push_back(std::move(toAdd));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
} // namespace
|
|
|
|
cmServerRequest::cmServerRequest(cmServer* server, cmConnection* connection,
|
|
const std::string& t, const std::string& c,
|
|
const Json::Value& d)
|
|
: Type(t)
|
|
, Cookie(c)
|
|
, Data(d)
|
|
, Connection(connection)
|
|
, m_Server(server)
|
|
{
|
|
}
|
|
|
|
void cmServerRequest::ReportProgress(int min, int current, int max,
|
|
const std::string& message) const
|
|
{
|
|
this->m_Server->WriteProgress(*this, min, current, max, message);
|
|
}
|
|
|
|
void cmServerRequest::ReportMessage(const std::string& message,
|
|
const std::string& title) const
|
|
{
|
|
m_Server->WriteMessage(*this, message, title);
|
|
}
|
|
|
|
cmServerResponse cmServerRequest::Reply(const Json::Value& data) const
|
|
{
|
|
cmServerResponse response(*this);
|
|
response.SetData(data);
|
|
return response;
|
|
}
|
|
|
|
cmServerResponse cmServerRequest::ReportError(const std::string& message) const
|
|
{
|
|
cmServerResponse response(*this);
|
|
response.SetError(message);
|
|
return response;
|
|
}
|
|
|
|
cmServerResponse::cmServerResponse(const cmServerRequest& request)
|
|
: Type(request.Type)
|
|
, Cookie(request.Cookie)
|
|
{
|
|
}
|
|
|
|
void cmServerResponse::SetData(const Json::Value& data)
|
|
{
|
|
assert(this->m_Payload == PAYLOAD_UNKNOWN);
|
|
if (!data[kCOOKIE_KEY].isNull() || !data[kTYPE_KEY].isNull()) {
|
|
this->SetError("Response contains cookie or type field.");
|
|
return;
|
|
}
|
|
this->m_Payload = PAYLOAD_DATA;
|
|
this->m_Data = data;
|
|
}
|
|
|
|
void cmServerResponse::SetError(const std::string& message)
|
|
{
|
|
assert(this->m_Payload == PAYLOAD_UNKNOWN);
|
|
this->m_Payload = PAYLOAD_ERROR;
|
|
this->m_ErrorMessage = message;
|
|
}
|
|
|
|
bool cmServerResponse::IsComplete() const
|
|
{
|
|
return this->m_Payload != PAYLOAD_UNKNOWN;
|
|
}
|
|
|
|
bool cmServerResponse::IsError() const
|
|
{
|
|
assert(this->m_Payload != PAYLOAD_UNKNOWN);
|
|
return this->m_Payload == PAYLOAD_ERROR;
|
|
}
|
|
|
|
std::string cmServerResponse::ErrorMessage() const
|
|
{
|
|
if (this->m_Payload == PAYLOAD_ERROR) {
|
|
return this->m_ErrorMessage;
|
|
}
|
|
return std::string();
|
|
}
|
|
|
|
Json::Value cmServerResponse::Data() const
|
|
{
|
|
assert(this->m_Payload != PAYLOAD_UNKNOWN);
|
|
return this->m_Data;
|
|
}
|
|
|
|
bool cmServerProtocol::Activate(cmServer* server,
|
|
const cmServerRequest& request,
|
|
std::string* errorMessage)
|
|
{
|
|
assert(server);
|
|
this->m_Server = server;
|
|
this->m_CMakeInstance = cm::make_unique<cmake>(cmake::RoleProject);
|
|
const bool result = this->DoActivate(request, errorMessage);
|
|
if (!result) {
|
|
this->m_CMakeInstance = nullptr;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
cmFileMonitor* cmServerProtocol::FileMonitor() const
|
|
{
|
|
return this->m_Server ? this->m_Server->FileMonitor() : nullptr;
|
|
}
|
|
|
|
void cmServerProtocol::SendSignal(const std::string& name,
|
|
const Json::Value& data) const
|
|
{
|
|
if (this->m_Server) {
|
|
this->m_Server->WriteSignal(name, data);
|
|
}
|
|
}
|
|
|
|
cmake* cmServerProtocol::CMakeInstance() const
|
|
{
|
|
return this->m_CMakeInstance.get();
|
|
}
|
|
|
|
bool cmServerProtocol::DoActivate(const cmServerRequest& /*request*/,
|
|
std::string* /*errorMessage*/)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
std::pair<int, int> cmServerProtocol1::ProtocolVersion() const
|
|
{
|
|
// Revision history
|
|
// 1, 1 - Report backtraces in codemodel response
|
|
// 1, 2 - Add target install destinations to codemodel
|
|
// 1, 3 - Add a flag to target filegroups indicating whether or not the
|
|
// filegroup is for INTERFACE_SOURCES
|
|
return std::make_pair(1, 3);
|
|
}
|
|
|
|
static void setErrorMessage(std::string* errorMessage, const std::string& text)
|
|
{
|
|
if (errorMessage) {
|
|
*errorMessage = text;
|
|
}
|
|
}
|
|
|
|
static bool getOrTestHomeDirectory(cmState* state, std::string& value,
|
|
std::string* errorMessage)
|
|
{
|
|
const std::string cachedValue =
|
|
std::string(state->GetCacheEntryValue("CMAKE_HOME_DIRECTORY"));
|
|
if (value.empty()) {
|
|
value = cachedValue;
|
|
return true;
|
|
}
|
|
const std::string suffix = "/CMakeLists.txt";
|
|
const std::string cachedValueCML = cachedValue + suffix;
|
|
const std::string valueCML = value + suffix;
|
|
if (!cmSystemTools::SameFile(valueCML, cachedValueCML)) {
|
|
setErrorMessage(errorMessage,
|
|
std::string("\"CMAKE_HOME_DIRECTORY\" is set but "
|
|
"incompatible with configured "
|
|
"source directory value."));
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static bool getOrTestValue(cmState* state, const std::string& key,
|
|
std::string& value,
|
|
const std::string& keyDescription,
|
|
std::string* errorMessage)
|
|
{
|
|
const char* entry = state->GetCacheEntryValue(key);
|
|
const std::string cachedValue =
|
|
entry == nullptr ? std::string() : std::string(entry);
|
|
if (value.empty()) {
|
|
value = cachedValue;
|
|
}
|
|
if (!cachedValue.empty() && cachedValue != value) {
|
|
setErrorMessage(errorMessage,
|
|
std::string("\"") + key +
|
|
"\" is set but incompatible with configured " +
|
|
keyDescription + " value.");
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool cmServerProtocol1::DoActivate(const cmServerRequest& request,
|
|
std::string* errorMessage)
|
|
{
|
|
std::string sourceDirectory = request.Data[kSOURCE_DIRECTORY_KEY].asString();
|
|
const std::string buildDirectory =
|
|
request.Data[kBUILD_DIRECTORY_KEY].asString();
|
|
std::string generator = request.Data[kGENERATOR_KEY].asString();
|
|
std::string extraGenerator = request.Data[kEXTRA_GENERATOR_KEY].asString();
|
|
std::string toolset = request.Data[kTOOLSET_KEY].asString();
|
|
std::string platform = request.Data[kPLATFORM_KEY].asString();
|
|
|
|
if (buildDirectory.empty()) {
|
|
setErrorMessage(errorMessage,
|
|
std::string("\"") + kBUILD_DIRECTORY_KEY +
|
|
"\" is missing.");
|
|
return false;
|
|
}
|
|
|
|
cmake* cm = CMakeInstance();
|
|
if (cmSystemTools::PathExists(buildDirectory)) {
|
|
if (!cmSystemTools::FileIsDirectory(buildDirectory)) {
|
|
setErrorMessage(errorMessage,
|
|
std::string("\"") + kBUILD_DIRECTORY_KEY +
|
|
"\" exists but is not a directory.");
|
|
return false;
|
|
}
|
|
|
|
const std::string cachePath = cm->FindCacheFile(buildDirectory);
|
|
if (cm->LoadCache(cachePath)) {
|
|
cmState* state = cm->GetState();
|
|
|
|
// Check generator:
|
|
if (!getOrTestValue(state, "CMAKE_GENERATOR", generator, "generator",
|
|
errorMessage)) {
|
|
return false;
|
|
}
|
|
|
|
// check extra generator:
|
|
if (!getOrTestValue(state, "CMAKE_EXTRA_GENERATOR", extraGenerator,
|
|
"extra generator", errorMessage)) {
|
|
return false;
|
|
}
|
|
|
|
// check sourcedir:
|
|
if (!getOrTestHomeDirectory(state, sourceDirectory, errorMessage)) {
|
|
return false;
|
|
}
|
|
|
|
// check toolset:
|
|
if (!getOrTestValue(state, "CMAKE_GENERATOR_TOOLSET", toolset, "toolset",
|
|
errorMessage)) {
|
|
return false;
|
|
}
|
|
|
|
// check platform:
|
|
if (!getOrTestValue(state, "CMAKE_GENERATOR_PLATFORM", platform,
|
|
"platform", errorMessage)) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (sourceDirectory.empty()) {
|
|
setErrorMessage(errorMessage,
|
|
std::string("\"") + kSOURCE_DIRECTORY_KEY +
|
|
"\" is unset but required.");
|
|
return false;
|
|
}
|
|
if (!cmSystemTools::FileIsDirectory(sourceDirectory)) {
|
|
setErrorMessage(errorMessage,
|
|
std::string("\"") + kSOURCE_DIRECTORY_KEY +
|
|
"\" is not a directory.");
|
|
return false;
|
|
}
|
|
if (generator.empty()) {
|
|
setErrorMessage(errorMessage,
|
|
std::string("\"") + kGENERATOR_KEY +
|
|
"\" is unset but required.");
|
|
return false;
|
|
}
|
|
|
|
std::vector<cmake::GeneratorInfo> generators;
|
|
cm->GetRegisteredGenerators(generators);
|
|
auto baseIt = std::find_if(generators.begin(), generators.end(),
|
|
[&generator](const cmake::GeneratorInfo& info) {
|
|
return info.name == generator;
|
|
});
|
|
if (baseIt == generators.end()) {
|
|
setErrorMessage(errorMessage,
|
|
std::string("Generator \"") + generator +
|
|
"\" not supported.");
|
|
return false;
|
|
}
|
|
auto extraIt = std::find_if(
|
|
generators.begin(), generators.end(),
|
|
[&generator, &extraGenerator](const cmake::GeneratorInfo& info) {
|
|
return info.baseName == generator && info.extraName == extraGenerator;
|
|
});
|
|
if (extraIt == generators.end()) {
|
|
setErrorMessage(errorMessage,
|
|
std::string("The combination of generator \"" + generator +
|
|
"\" and extra generator \"" + extraGenerator +
|
|
"\" is not supported."));
|
|
return false;
|
|
}
|
|
if (!extraIt->supportsToolset && !toolset.empty()) {
|
|
setErrorMessage(errorMessage,
|
|
std::string("Toolset was provided but is not supported by "
|
|
"the requested generator."));
|
|
return false;
|
|
}
|
|
if (!extraIt->supportsPlatform && !platform.empty()) {
|
|
setErrorMessage(errorMessage,
|
|
std::string("Platform was provided but is not supported "
|
|
"by the requested generator."));
|
|
return false;
|
|
}
|
|
|
|
this->GeneratorInfo =
|
|
GeneratorInformation(generator, extraGenerator, toolset, platform,
|
|
sourceDirectory, buildDirectory);
|
|
|
|
this->m_State = STATE_ACTIVE;
|
|
return true;
|
|
}
|
|
|
|
void cmServerProtocol1::HandleCMakeFileChanges(const std::string& path,
|
|
int event, int status)
|
|
{
|
|
assert(status == 0);
|
|
static_cast<void>(status);
|
|
|
|
if (!m_isDirty) {
|
|
m_isDirty = true;
|
|
SendSignal(kDIRTY_SIGNAL, Json::objectValue);
|
|
}
|
|
Json::Value obj = Json::objectValue;
|
|
obj[kPATH_KEY] = path;
|
|
Json::Value properties = Json::arrayValue;
|
|
if (event & UV_RENAME) {
|
|
properties.append(kRENAME_PROPERTY_VALUE);
|
|
}
|
|
if (event & UV_CHANGE) {
|
|
properties.append(kCHANGE_PROPERTY_VALUE);
|
|
}
|
|
|
|
obj[kPROPERTIES_KEY] = properties;
|
|
SendSignal(kFILE_CHANGE_SIGNAL, obj);
|
|
}
|
|
|
|
const cmServerResponse cmServerProtocol1::Process(
|
|
const cmServerRequest& request)
|
|
{
|
|
assert(this->m_State >= STATE_ACTIVE);
|
|
|
|
if (request.Type == kCACHE_TYPE) {
|
|
return this->ProcessCache(request);
|
|
}
|
|
if (request.Type == kCMAKE_INPUTS_TYPE) {
|
|
return this->ProcessCMakeInputs(request);
|
|
}
|
|
if (request.Type == kCODE_MODEL_TYPE) {
|
|
return this->ProcessCodeModel(request);
|
|
}
|
|
if (request.Type == kCOMPUTE_TYPE) {
|
|
return this->ProcessCompute(request);
|
|
}
|
|
if (request.Type == kCONFIGURE_TYPE) {
|
|
return this->ProcessConfigure(request);
|
|
}
|
|
if (request.Type == kFILESYSTEM_WATCHERS_TYPE) {
|
|
return this->ProcessFileSystemWatchers(request);
|
|
}
|
|
if (request.Type == kGLOBAL_SETTINGS_TYPE) {
|
|
return this->ProcessGlobalSettings(request);
|
|
}
|
|
if (request.Type == kSET_GLOBAL_SETTINGS_TYPE) {
|
|
return this->ProcessSetGlobalSettings(request);
|
|
}
|
|
if (request.Type == kCTEST_INFO_TYPE) {
|
|
return this->ProcessCTests(request);
|
|
}
|
|
|
|
return request.ReportError("Unknown command!");
|
|
}
|
|
|
|
bool cmServerProtocol1::IsExperimental() const
|
|
{
|
|
return true;
|
|
}
|
|
|
|
cmServerResponse cmServerProtocol1::ProcessCache(
|
|
const cmServerRequest& request)
|
|
{
|
|
cmState* state = this->CMakeInstance()->GetState();
|
|
|
|
Json::Value result = Json::objectValue;
|
|
|
|
std::vector<std::string> allKeys = state->GetCacheEntryKeys();
|
|
|
|
Json::Value list = Json::arrayValue;
|
|
std::vector<std::string> keys = toStringList(request.Data[kKEYS_KEY]);
|
|
if (keys.empty()) {
|
|
keys = allKeys;
|
|
} else {
|
|
for (auto const& i : keys) {
|
|
if (std::find(allKeys.begin(), allKeys.end(), i) == allKeys.end()) {
|
|
return request.ReportError("Key \"" + i + "\" not found in cache.");
|
|
}
|
|
}
|
|
}
|
|
std::sort(keys.begin(), keys.end());
|
|
for (auto const& key : keys) {
|
|
Json::Value entry = Json::objectValue;
|
|
entry[kKEY_KEY] = key;
|
|
entry[kTYPE_KEY] =
|
|
cmState::CacheEntryTypeToString(state->GetCacheEntryType(key));
|
|
entry[kVALUE_KEY] = state->GetCacheEntryValue(key);
|
|
|
|
Json::Value props = Json::objectValue;
|
|
bool haveProperties = false;
|
|
for (auto const& prop : state->GetCacheEntryPropertyList(key)) {
|
|
haveProperties = true;
|
|
props[prop] = state->GetCacheEntryProperty(key, prop);
|
|
}
|
|
if (haveProperties) {
|
|
entry[kPROPERTIES_KEY] = props;
|
|
}
|
|
|
|
list.append(entry);
|
|
}
|
|
|
|
result[kCACHE_KEY] = list;
|
|
return request.Reply(result);
|
|
}
|
|
|
|
static Json::Value DumpCMakeInputs(const cmake* cm)
|
|
{
|
|
const cmGlobalGenerator* gg = cm->GetGlobalGenerator();
|
|
const std::string& buildDir = cm->GetHomeOutputDirectory();
|
|
const std::string& sourceDir = cm->GetHomeDirectory();
|
|
|
|
std::vector<std::string> internalFiles;
|
|
std::vector<std::string> explicitFiles;
|
|
std::vector<std::string> tmpFiles;
|
|
getCMakeInputs(gg, sourceDir, buildDir, &internalFiles, &explicitFiles,
|
|
&tmpFiles);
|
|
|
|
Json::Value array = Json::arrayValue;
|
|
|
|
Json::Value tmp = Json::objectValue;
|
|
tmp[kIS_CMAKE_KEY] = true;
|
|
tmp[kIS_TEMPORARY_KEY] = false;
|
|
tmp[kSOURCES_KEY] = fromStringList(internalFiles);
|
|
array.append(tmp);
|
|
|
|
tmp = Json::objectValue;
|
|
tmp[kIS_CMAKE_KEY] = false;
|
|
tmp[kIS_TEMPORARY_KEY] = false;
|
|
tmp[kSOURCES_KEY] = fromStringList(explicitFiles);
|
|
array.append(tmp);
|
|
|
|
tmp = Json::objectValue;
|
|
tmp[kIS_CMAKE_KEY] = false;
|
|
tmp[kIS_TEMPORARY_KEY] = true;
|
|
tmp[kSOURCES_KEY] = fromStringList(tmpFiles);
|
|
array.append(tmp);
|
|
|
|
return array;
|
|
}
|
|
|
|
cmServerResponse cmServerProtocol1::ProcessCMakeInputs(
|
|
const cmServerRequest& request)
|
|
{
|
|
if (this->m_State < STATE_CONFIGURED) {
|
|
return request.ReportError("This instance was not yet configured.");
|
|
}
|
|
|
|
const cmake* cm = this->CMakeInstance();
|
|
const std::string cmakeRootDir = cmSystemTools::GetCMakeRoot();
|
|
const std::string& sourceDir = cm->GetHomeDirectory();
|
|
|
|
Json::Value result = Json::objectValue;
|
|
result[kSOURCE_DIRECTORY_KEY] = sourceDir;
|
|
result[kCMAKE_ROOT_DIRECTORY_KEY] = cmakeRootDir;
|
|
result[kBUILD_FILES_KEY] = DumpCMakeInputs(cm);
|
|
return request.Reply(result);
|
|
}
|
|
|
|
const std::string kInterfaceSourcesLanguageDataKey =
|
|
"INTERFACE_SOURCES_LD_KEY";
|
|
class LanguageData
|
|
{
|
|
public:
|
|
bool operator==(const LanguageData& other) const;
|
|
|
|
void SetDefines(const std::set<std::string>& defines);
|
|
|
|
bool IsGenerated = false;
|
|
std::string Language;
|
|
std::string Flags;
|
|
std::vector<std::string> Defines;
|
|
std::vector<std::pair<std::string, bool>> IncludePathList;
|
|
};
|
|
|
|
bool LanguageData::operator==(const LanguageData& other) const
|
|
{
|
|
return Language == other.Language && Defines == other.Defines &&
|
|
Flags == other.Flags && IncludePathList == other.IncludePathList &&
|
|
IsGenerated == other.IsGenerated;
|
|
}
|
|
|
|
void LanguageData::SetDefines(const std::set<std::string>& defines)
|
|
{
|
|
std::vector<std::string> result;
|
|
result.reserve(defines.size());
|
|
for (std::string const& i : defines) {
|
|
result.push_back(i);
|
|
}
|
|
std::sort(result.begin(), result.end());
|
|
Defines = std::move(result);
|
|
}
|
|
|
|
struct FileGroupSources
|
|
{
|
|
bool IsInterfaceSources;
|
|
std::vector<std::string> Files;
|
|
};
|
|
|
|
namespace std {
|
|
|
|
template <>
|
|
struct hash<LanguageData>
|
|
{
|
|
std::size_t operator()(const LanguageData& in) const
|
|
{
|
|
using std::hash;
|
|
size_t result =
|
|
hash<std::string>()(in.Language) ^ hash<std::string>()(in.Flags);
|
|
for (auto const& i : in.IncludePathList) {
|
|
result = result ^
|
|
(hash<std::string>()(i.first) ^
|
|
(i.second ? std::numeric_limits<size_t>::max() : 0));
|
|
}
|
|
for (auto const& i : in.Defines) {
|
|
result = result ^ hash<std::string>()(i);
|
|
}
|
|
result =
|
|
result ^ (in.IsGenerated ? std::numeric_limits<size_t>::max() : 0);
|
|
return result;
|
|
}
|
|
};
|
|
|
|
} // namespace std
|
|
|
|
static Json::Value DumpSourceFileGroup(const LanguageData& data,
|
|
bool isInterfaceSource,
|
|
const std::vector<std::string>& files,
|
|
const std::string& baseDir)
|
|
{
|
|
Json::Value result = Json::objectValue;
|
|
|
|
if (isInterfaceSource) {
|
|
result[kIS_INTERFACE_SOURCES_KEY] = true;
|
|
}
|
|
if (!data.Language.empty()) {
|
|
result[kLANGUAGE_KEY] = data.Language;
|
|
}
|
|
if (!data.Flags.empty()) {
|
|
result[kCOMPILE_FLAGS_KEY] = data.Flags;
|
|
}
|
|
if (!data.IncludePathList.empty()) {
|
|
Json::Value includes = Json::arrayValue;
|
|
for (auto const& i : data.IncludePathList) {
|
|
Json::Value tmp = Json::objectValue;
|
|
tmp[kPATH_KEY] = i.first;
|
|
if (i.second) {
|
|
tmp[kIS_SYSTEM_KEY] = i.second;
|
|
}
|
|
includes.append(tmp);
|
|
}
|
|
result[kINCLUDE_PATH_KEY] = includes;
|
|
}
|
|
if (!data.Defines.empty()) {
|
|
result[kDEFINES_KEY] = fromStringList(data.Defines);
|
|
}
|
|
|
|
result[kIS_GENERATED_KEY] = data.IsGenerated;
|
|
|
|
Json::Value sourcesValue = Json::arrayValue;
|
|
for (auto const& i : files) {
|
|
const std::string relPath = cmSystemTools::RelativePath(baseDir, i);
|
|
sourcesValue.append(relPath.size() < i.size() ? relPath : i);
|
|
}
|
|
|
|
result[kSOURCES_KEY] = sourcesValue;
|
|
return result;
|
|
}
|
|
|
|
static void PopulateFileGroupData(
|
|
cmGeneratorTarget* target, bool isInterfaceSources,
|
|
const std::vector<cmSourceFile*>& files, const std::string& config,
|
|
const std::map<std::string, LanguageData>& languageDataMap,
|
|
std::unordered_map<LanguageData, FileGroupSources>& fileGroups)
|
|
{
|
|
for (cmSourceFile* file : files) {
|
|
LanguageData fileData;
|
|
fileData.Language = file->GetLanguage();
|
|
if (!fileData.Language.empty() || isInterfaceSources) {
|
|
const LanguageData& ld = isInterfaceSources
|
|
? languageDataMap.at(kInterfaceSourcesLanguageDataKey)
|
|
: languageDataMap.at(fileData.Language);
|
|
cmLocalGenerator* lg = target->GetLocalGenerator();
|
|
cmGeneratorExpressionInterpreter genexInterpreter(lg, config, target,
|
|
fileData.Language);
|
|
|
|
std::string compileFlags = ld.Flags;
|
|
const std::string COMPILE_FLAGS("COMPILE_FLAGS");
|
|
if (const char* cflags = file->GetProperty(COMPILE_FLAGS)) {
|
|
lg->AppendFlags(compileFlags,
|
|
genexInterpreter.Evaluate(cflags, COMPILE_FLAGS));
|
|
}
|
|
const std::string COMPILE_OPTIONS("COMPILE_OPTIONS");
|
|
if (const char* coptions = file->GetProperty(COMPILE_OPTIONS)) {
|
|
lg->AppendCompileOptions(
|
|
compileFlags, genexInterpreter.Evaluate(coptions, COMPILE_OPTIONS));
|
|
}
|
|
fileData.Flags = compileFlags;
|
|
|
|
// Add include directories from source file properties.
|
|
std::vector<std::string> includes;
|
|
|
|
const std::string INCLUDE_DIRECTORIES("INCLUDE_DIRECTORIES");
|
|
if (const char* cincludes = file->GetProperty(INCLUDE_DIRECTORIES)) {
|
|
const std::string& evaluatedIncludes =
|
|
genexInterpreter.Evaluate(cincludes, INCLUDE_DIRECTORIES);
|
|
lg->AppendIncludeDirectories(includes, evaluatedIncludes, *file);
|
|
|
|
for (const auto& include : includes) {
|
|
// INTERFACE_LIBRARY targets do not support the
|
|
// IsSystemIncludeDirectory call so just set it to false.
|
|
const bool isSystemInclude = isInterfaceSources
|
|
? false
|
|
: target->IsSystemIncludeDirectory(include, config,
|
|
fileData.Language);
|
|
fileData.IncludePathList.push_back(
|
|
std::make_pair(include, isSystemInclude));
|
|
}
|
|
}
|
|
|
|
fileData.IncludePathList.insert(fileData.IncludePathList.end(),
|
|
ld.IncludePathList.begin(),
|
|
ld.IncludePathList.end());
|
|
|
|
const std::string COMPILE_DEFINITIONS("COMPILE_DEFINITIONS");
|
|
std::set<std::string> defines;
|
|
if (const char* defs = file->GetProperty(COMPILE_DEFINITIONS)) {
|
|
lg->AppendDefines(
|
|
defines, genexInterpreter.Evaluate(defs, COMPILE_DEFINITIONS));
|
|
}
|
|
|
|
const std::string defPropName =
|
|
"COMPILE_DEFINITIONS_" + cmSystemTools::UpperCase(config);
|
|
if (const char* config_defs = file->GetProperty(defPropName)) {
|
|
lg->AppendDefines(
|
|
defines,
|
|
genexInterpreter.Evaluate(config_defs, COMPILE_DEFINITIONS));
|
|
}
|
|
|
|
defines.insert(ld.Defines.begin(), ld.Defines.end());
|
|
|
|
fileData.SetDefines(defines);
|
|
}
|
|
|
|
fileData.IsGenerated = file->GetPropertyAsBool("GENERATED");
|
|
FileGroupSources& groupFileList = fileGroups[fileData];
|
|
groupFileList.IsInterfaceSources = isInterfaceSources;
|
|
groupFileList.Files.push_back(file->GetFullPath());
|
|
}
|
|
}
|
|
|
|
static Json::Value DumpSourceFilesList(
|
|
cmGeneratorTarget* target, const std::string& config,
|
|
const std::map<std::string, LanguageData>& languageDataMap)
|
|
{
|
|
const cmStateEnums::TargetType type = target->GetType();
|
|
std::unordered_map<LanguageData, FileGroupSources> fileGroups;
|
|
|
|
// Collect sourcefile groups:
|
|
|
|
std::vector<cmSourceFile*> files;
|
|
if (type == cmStateEnums::INTERFACE_LIBRARY) {
|
|
// INTERFACE_LIBRARY targets do not create all the data structures
|
|
// associated with regular targets. If properties are explicitly specified
|
|
// for files in INTERFACE_SOURCES then we can get them through the Makefile
|
|
// rather than the target.
|
|
files = target->Makefile->GetSourceFiles();
|
|
} else {
|
|
target->GetSourceFiles(files, config);
|
|
PopulateFileGroupData(target, false /* isInterfaceSources */, files,
|
|
config, languageDataMap, fileGroups);
|
|
}
|
|
|
|
// Collect interface sourcefile groups:
|
|
|
|
auto targetProp = target->Target->GetProperty("INTERFACE_SOURCES");
|
|
if (targetProp != nullptr) {
|
|
cmGeneratorExpressionInterpreter genexInterpreter(
|
|
target->GetLocalGenerator(), config, target);
|
|
|
|
auto evaluatedSources = cmsys::SystemTools::SplitString(
|
|
genexInterpreter.Evaluate(targetProp, "INTERFACE_SOURCES"), ';');
|
|
|
|
std::map<std::string, cmSourceFile*> filesMap;
|
|
for (auto file : files) {
|
|
filesMap[file->GetFullPath()] = file;
|
|
}
|
|
|
|
std::vector<cmSourceFile*> interfaceSourceFiles;
|
|
for (const std::string& interfaceSourceFilePath : evaluatedSources) {
|
|
auto entry = filesMap.find(interfaceSourceFilePath);
|
|
if (entry != filesMap.end()) {
|
|
// use what we have since it has all the associated properties
|
|
interfaceSourceFiles.push_back(entry->second);
|
|
} else {
|
|
interfaceSourceFiles.push_back(
|
|
new cmSourceFile(target->Makefile, interfaceSourceFilePath));
|
|
}
|
|
}
|
|
|
|
PopulateFileGroupData(target, true /* isInterfaceSources */,
|
|
interfaceSourceFiles, config, languageDataMap,
|
|
fileGroups);
|
|
}
|
|
|
|
const std::string& baseDir = target->Makefile->GetCurrentSourceDirectory();
|
|
Json::Value result = Json::arrayValue;
|
|
for (auto const& it : fileGroups) {
|
|
Json::Value group = DumpSourceFileGroup(
|
|
it.first, it.second.IsInterfaceSources, it.second.Files, baseDir);
|
|
if (!group.isNull()) {
|
|
result.append(group);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
static Json::Value DumpCTestInfo(cmLocalGenerator* lg, cmTest* testInfo,
|
|
const std::string& config)
|
|
{
|
|
Json::Value result = Json::objectValue;
|
|
result[kCTEST_NAME] = testInfo->GetName();
|
|
|
|
// Concat command entries together. After the first should be the arguments
|
|
// for the command
|
|
std::string command;
|
|
for (auto const& cmd : testInfo->GetCommand()) {
|
|
command.append(cmd);
|
|
command.append(" ");
|
|
}
|
|
|
|
// Remove any config specific variables from the output.
|
|
cmGeneratorExpression ge;
|
|
auto cge = ge.Parse(command);
|
|
const std::string& processed = cge->Evaluate(lg, config);
|
|
result[kCTEST_COMMAND] = processed;
|
|
|
|
// Build up the list of properties that may have been specified
|
|
Json::Value properties = Json::arrayValue;
|
|
for (auto& prop : testInfo->GetProperties()) {
|
|
Json::Value entry = Json::objectValue;
|
|
entry[kKEY_KEY] = prop.first;
|
|
|
|
// Remove config variables from the value too.
|
|
auto cge_value = ge.Parse(prop.second.GetValue());
|
|
const std::string& processed_value = cge_value->Evaluate(lg, config);
|
|
entry[kVALUE_KEY] = processed_value;
|
|
properties.append(entry);
|
|
}
|
|
result[kPROPERTIES_KEY] = properties;
|
|
|
|
return result;
|
|
}
|
|
|
|
static void DumpMakefileTests(cmLocalGenerator* lg, const std::string& config,
|
|
Json::Value* result)
|
|
{
|
|
auto mf = lg->GetMakefile();
|
|
std::vector<cmTest*> tests;
|
|
mf->GetTests(config, tests);
|
|
for (auto test : tests) {
|
|
Json::Value tmp = DumpCTestInfo(lg, test, config);
|
|
if (!tmp.isNull()) {
|
|
result->append(tmp);
|
|
}
|
|
}
|
|
}
|
|
|
|
static Json::Value DumpCTestProjectList(const cmake* cm,
|
|
std::string const& config)
|
|
{
|
|
Json::Value result = Json::arrayValue;
|
|
|
|
auto globalGen = cm->GetGlobalGenerator();
|
|
|
|
for (const auto& projectIt : globalGen->GetProjectMap()) {
|
|
Json::Value pObj = Json::objectValue;
|
|
pObj[kNAME_KEY] = projectIt.first;
|
|
|
|
Json::Value tests = Json::arrayValue;
|
|
|
|
// Gather tests for every generator
|
|
for (const auto& lg : projectIt.second) {
|
|
// Make sure they're generated.
|
|
lg->GenerateTestFiles();
|
|
DumpMakefileTests(lg, config, &tests);
|
|
}
|
|
|
|
pObj[kCTEST_INFO] = tests;
|
|
|
|
result.append(pObj);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
static Json::Value DumpCTestConfiguration(const cmake* cm,
|
|
const std::string& config)
|
|
{
|
|
Json::Value result = Json::objectValue;
|
|
result[kNAME_KEY] = config;
|
|
|
|
result[kPROJECTS_KEY] = DumpCTestProjectList(cm, config);
|
|
|
|
return result;
|
|
}
|
|
|
|
static Json::Value DumpCTestConfigurationsList(const cmake* cm)
|
|
{
|
|
Json::Value result = Json::arrayValue;
|
|
|
|
for (const std::string& c : getConfigurations(cm)) {
|
|
result.append(DumpCTestConfiguration(cm, c));
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
static Json::Value DumpCTestInfo(const cmake* cm)
|
|
{
|
|
Json::Value result = Json::objectValue;
|
|
result[kCONFIGURATIONS_KEY] = DumpCTestConfigurationsList(cm);
|
|
return result;
|
|
}
|
|
|
|
static void GetTargetProperty(
|
|
cmGeneratorExpressionInterpreter& genexInterpreter,
|
|
cmGeneratorTarget* target, const char* propertyName,
|
|
std::vector<std::string>& propertyValue)
|
|
{
|
|
auto targetProp = target->Target->GetProperty(propertyName);
|
|
if (targetProp != nullptr) {
|
|
propertyValue = cmsys::SystemTools::SplitString(
|
|
genexInterpreter.Evaluate(targetProp, propertyName), ';');
|
|
}
|
|
}
|
|
|
|
static void CreateInterfaceSourcesEntry(
|
|
cmLocalGenerator* lg, cmGeneratorTarget* target, const std::string& config,
|
|
std::map<std::string, LanguageData>& languageDataMap)
|
|
{
|
|
LanguageData& ld = languageDataMap[kInterfaceSourcesLanguageDataKey];
|
|
ld.Language = "";
|
|
|
|
cmGeneratorExpressionInterpreter genexInterpreter(lg, config, target);
|
|
std::vector<std::string> propertyValue;
|
|
GetTargetProperty(genexInterpreter, target, "INTERFACE_INCLUDE_DIRECTORIES",
|
|
propertyValue);
|
|
for (std::string const& i : propertyValue) {
|
|
ld.IncludePathList.push_back(
|
|
std::make_pair(i, false /* isSystemInclude */));
|
|
}
|
|
|
|
propertyValue.clear();
|
|
GetTargetProperty(genexInterpreter, target,
|
|
"INTERFACE_SYSTEM_INCLUDE_DIRECTORIES", propertyValue);
|
|
for (std::string const& i : propertyValue) {
|
|
ld.IncludePathList.push_back(
|
|
std::make_pair(i, true /* isSystemInclude */));
|
|
}
|
|
|
|
propertyValue.clear();
|
|
GetTargetProperty(genexInterpreter, target, "INTERFACE_COMPILE_OPTIONS",
|
|
propertyValue);
|
|
for (const auto& s : propertyValue) {
|
|
ld.Flags += " " + s;
|
|
}
|
|
|
|
propertyValue.clear();
|
|
GetTargetProperty(genexInterpreter, target, "INTERFACE_COMPILE_DEFINITIONS",
|
|
propertyValue);
|
|
if (!propertyValue.empty()) {
|
|
std::set<std::string> defines(propertyValue.begin(), propertyValue.end());
|
|
ld.SetDefines(defines);
|
|
}
|
|
}
|
|
|
|
static Json::Value DumpTarget(cmGeneratorTarget* target,
|
|
const std::string& config)
|
|
{
|
|
cmLocalGenerator* lg = target->GetLocalGenerator();
|
|
const cmState* state = lg->GetState();
|
|
|
|
const cmStateEnums::TargetType type = target->GetType();
|
|
const std::string typeName = state->GetTargetTypeName(type);
|
|
|
|
Json::Value ttl = Json::arrayValue;
|
|
ttl.append("EXECUTABLE");
|
|
ttl.append("STATIC_LIBRARY");
|
|
ttl.append("SHARED_LIBRARY");
|
|
ttl.append("MODULE_LIBRARY");
|
|
ttl.append("OBJECT_LIBRARY");
|
|
ttl.append("UTILITY");
|
|
ttl.append("INTERFACE_LIBRARY");
|
|
|
|
if (!hasString(ttl, typeName) || target->IsImported()) {
|
|
return Json::Value();
|
|
}
|
|
|
|
Json::Value result = Json::objectValue;
|
|
result[kNAME_KEY] = target->GetName();
|
|
result[kIS_GENERATOR_PROVIDED_KEY] =
|
|
target->Target->GetIsGeneratorProvided();
|
|
result[kTYPE_KEY] = typeName;
|
|
result[kSOURCE_DIRECTORY_KEY] = lg->GetCurrentSourceDirectory();
|
|
result[kBUILD_DIRECTORY_KEY] = lg->GetCurrentBinaryDirectory();
|
|
result[kFULL_NAME_KEY] = target->GetFullName(config);
|
|
|
|
if (target->Target->GetHaveInstallRule()) {
|
|
result[kHAS_INSTALL_RULE] = true;
|
|
|
|
Json::Value installPaths = Json::arrayValue;
|
|
auto targetGenerators = target->Makefile->GetInstallGenerators();
|
|
for (auto installGenerator : targetGenerators) {
|
|
auto installTargetGenerator =
|
|
dynamic_cast<cmInstallTargetGenerator*>(installGenerator);
|
|
if (installTargetGenerator != nullptr &&
|
|
installTargetGenerator->GetTarget()->Target == target->Target) {
|
|
auto dest = installTargetGenerator->GetDestination(config);
|
|
|
|
std::string installPath;
|
|
if (!dest.empty() && cmSystemTools::FileIsFullPath(dest)) {
|
|
installPath = dest;
|
|
} else {
|
|
std::string installPrefix =
|
|
target->Makefile->GetSafeDefinition("CMAKE_INSTALL_PREFIX");
|
|
installPath = installPrefix + '/' + dest;
|
|
}
|
|
|
|
installPaths.append(installPath);
|
|
}
|
|
}
|
|
|
|
result[kINSTALL_PATHS] = installPaths;
|
|
}
|
|
|
|
if (target->HaveWellDefinedOutputFiles()) {
|
|
Json::Value artifacts = Json::arrayValue;
|
|
artifacts.append(
|
|
target->GetFullPath(config, cmStateEnums::RuntimeBinaryArtifact));
|
|
if (target->IsDLLPlatform()) {
|
|
artifacts.append(
|
|
target->GetFullPath(config, cmStateEnums::ImportLibraryArtifact));
|
|
const cmGeneratorTarget::OutputInfo* output =
|
|
target->GetOutputInfo(config);
|
|
if (output && !output->PdbDir.empty()) {
|
|
artifacts.append(output->PdbDir + '/' + target->GetPDBName(config));
|
|
}
|
|
}
|
|
result[kARTIFACTS_KEY] = artifacts;
|
|
|
|
result[kLINKER_LANGUAGE_KEY] = target->GetLinkerLanguage(config);
|
|
|
|
std::string linkLibs;
|
|
std::string linkFlags;
|
|
std::string linkLanguageFlags;
|
|
std::string frameworkPath;
|
|
std::string linkPath;
|
|
cmLinkLineComputer linkLineComputer(lg,
|
|
lg->GetStateSnapshot().GetDirectory());
|
|
lg->GetTargetFlags(&linkLineComputer, config, linkLibs, linkLanguageFlags,
|
|
linkFlags, frameworkPath, linkPath, target);
|
|
|
|
linkLibs = cmSystemTools::TrimWhitespace(linkLibs);
|
|
linkFlags = cmSystemTools::TrimWhitespace(linkFlags);
|
|
linkLanguageFlags = cmSystemTools::TrimWhitespace(linkLanguageFlags);
|
|
frameworkPath = cmSystemTools::TrimWhitespace(frameworkPath);
|
|
linkPath = cmSystemTools::TrimWhitespace(linkPath);
|
|
|
|
if (!cmSystemTools::TrimWhitespace(linkLibs).empty()) {
|
|
result[kLINK_LIBRARIES_KEY] = linkLibs;
|
|
}
|
|
if (!cmSystemTools::TrimWhitespace(linkFlags).empty()) {
|
|
result[kLINK_FLAGS_KEY] = linkFlags;
|
|
}
|
|
if (!cmSystemTools::TrimWhitespace(linkLanguageFlags).empty()) {
|
|
result[kLINK_LANGUAGE_FLAGS_KEY] = linkLanguageFlags;
|
|
}
|
|
if (!frameworkPath.empty()) {
|
|
result[kFRAMEWORK_PATH_KEY] = frameworkPath;
|
|
}
|
|
if (!linkPath.empty()) {
|
|
result[kLINK_PATH_KEY] = linkPath;
|
|
}
|
|
const std::string sysroot =
|
|
lg->GetMakefile()->GetSafeDefinition("CMAKE_SYSROOT");
|
|
if (!sysroot.empty()) {
|
|
result[kSYSROOT_KEY] = sysroot;
|
|
}
|
|
}
|
|
|
|
std::set<std::string> languages;
|
|
std::map<std::string, LanguageData> languageDataMap;
|
|
if (type == cmStateEnums::INTERFACE_LIBRARY) {
|
|
// INTERFACE_LIBRARY targets do not create all the data structures
|
|
// associated with regular targets. If properties are explicitly specified
|
|
// for files in INTERFACE_SOURCES then we can get them through the Makefile
|
|
// rather than the target.
|
|
for (auto file : target->Makefile->GetSourceFiles()) {
|
|
const std::string& language = file->GetLanguage();
|
|
if (!language.empty()) {
|
|
languages.insert(language);
|
|
}
|
|
}
|
|
} else {
|
|
target->GetLanguages(languages, config);
|
|
}
|
|
|
|
for (std::string const& lang : languages) {
|
|
LanguageData& ld = languageDataMap[lang];
|
|
ld.Language = lang;
|
|
lg->GetTargetCompileFlags(target, config, lang, ld.Flags);
|
|
std::set<std::string> defines;
|
|
lg->GetTargetDefines(target, config, lang, defines);
|
|
ld.SetDefines(defines);
|
|
std::vector<std::string> includePathList;
|
|
lg->GetIncludeDirectories(includePathList, target, lang, config, true);
|
|
for (std::string const& i : includePathList) {
|
|
ld.IncludePathList.push_back(
|
|
std::make_pair(i, target->IsSystemIncludeDirectory(i, config, lang)));
|
|
}
|
|
}
|
|
|
|
if (target->Target->GetProperty("INTERFACE_SOURCES") != nullptr) {
|
|
// Create an entry in the languageDataMap for interface sources.
|
|
CreateInterfaceSourcesEntry(lg, target, config, languageDataMap);
|
|
}
|
|
|
|
Json::Value sourceGroupsValue =
|
|
DumpSourceFilesList(target, config, languageDataMap);
|
|
if (!sourceGroupsValue.empty()) {
|
|
result[kFILE_GROUPS_KEY] = sourceGroupsValue;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
static Json::Value DumpTargetsList(
|
|
const std::vector<cmLocalGenerator*>& generators, const std::string& config)
|
|
{
|
|
Json::Value result = Json::arrayValue;
|
|
|
|
std::vector<cmGeneratorTarget*> targetList;
|
|
for (auto const& lgIt : generators) {
|
|
const auto& list = lgIt->GetGeneratorTargets();
|
|
targetList.insert(targetList.end(), list.begin(), list.end());
|
|
}
|
|
std::sort(targetList.begin(), targetList.end());
|
|
|
|
for (cmGeneratorTarget* target : targetList) {
|
|
Json::Value tmp = DumpTarget(target, config);
|
|
if (!tmp.isNull()) {
|
|
result.append(tmp);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
static Json::Value DumpProjectList(const cmake* cm, std::string const& config)
|
|
{
|
|
Json::Value result = Json::arrayValue;
|
|
|
|
auto globalGen = cm->GetGlobalGenerator();
|
|
|
|
for (auto const& projectIt : globalGen->GetProjectMap()) {
|
|
Json::Value pObj = Json::objectValue;
|
|
pObj[kNAME_KEY] = projectIt.first;
|
|
|
|
// All Projects must have at least one local generator
|
|
assert(!projectIt.second.empty());
|
|
const cmLocalGenerator* lg = projectIt.second.at(0);
|
|
|
|
// Project structure information:
|
|
const cmMakefile* mf = lg->GetMakefile();
|
|
auto minVersion = mf->GetDefinition("CMAKE_MINIMUM_REQUIRED_VERSION");
|
|
pObj[kMINIMUM_CMAKE_VERSION] = minVersion ? minVersion : "";
|
|
pObj[kSOURCE_DIRECTORY_KEY] = mf->GetCurrentSourceDirectory();
|
|
pObj[kBUILD_DIRECTORY_KEY] = mf->GetCurrentBinaryDirectory();
|
|
pObj[kTARGETS_KEY] = DumpTargetsList(projectIt.second, config);
|
|
|
|
// For a project-level install rule it might be defined in any of its
|
|
// associated generators.
|
|
bool hasInstallRule = false;
|
|
for (const auto generator : projectIt.second) {
|
|
hasInstallRule =
|
|
generator->GetMakefile()->GetInstallGenerators().empty() == false;
|
|
|
|
if (hasInstallRule) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
pObj[kHAS_INSTALL_RULE] = hasInstallRule;
|
|
|
|
result.append(pObj);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
static Json::Value DumpConfiguration(const cmake* cm,
|
|
const std::string& config)
|
|
{
|
|
Json::Value result = Json::objectValue;
|
|
result[kNAME_KEY] = config;
|
|
|
|
result[kPROJECTS_KEY] = DumpProjectList(cm, config);
|
|
|
|
return result;
|
|
}
|
|
|
|
static Json::Value DumpConfigurationsList(const cmake* cm)
|
|
{
|
|
Json::Value result = Json::arrayValue;
|
|
|
|
for (std::string const& c : getConfigurations(cm)) {
|
|
result.append(DumpConfiguration(cm, c));
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
static Json::Value DumpCodeModel(const cmake* cm)
|
|
{
|
|
Json::Value result = Json::objectValue;
|
|
result[kCONFIGURATIONS_KEY] = DumpConfigurationsList(cm);
|
|
return result;
|
|
}
|
|
|
|
cmServerResponse cmServerProtocol1::ProcessCodeModel(
|
|
const cmServerRequest& request)
|
|
{
|
|
if (this->m_State != STATE_COMPUTED) {
|
|
return request.ReportError("No build system was generated yet.");
|
|
}
|
|
|
|
return request.Reply(DumpCodeModel(this->CMakeInstance()));
|
|
}
|
|
|
|
cmServerResponse cmServerProtocol1::ProcessCompute(
|
|
const cmServerRequest& request)
|
|
{
|
|
if (this->m_State > STATE_CONFIGURED) {
|
|
return request.ReportError("This build system was already generated.");
|
|
}
|
|
if (this->m_State < STATE_CONFIGURED) {
|
|
return request.ReportError("This project was not configured yet.");
|
|
}
|
|
|
|
cmake* cm = this->CMakeInstance();
|
|
int ret = cm->Generate();
|
|
|
|
if (ret < 0) {
|
|
return request.ReportError("Failed to compute build system.");
|
|
}
|
|
m_State = STATE_COMPUTED;
|
|
return request.Reply(Json::Value());
|
|
}
|
|
|
|
cmServerResponse cmServerProtocol1::ProcessConfigure(
|
|
const cmServerRequest& request)
|
|
{
|
|
if (this->m_State == STATE_INACTIVE) {
|
|
return request.ReportError("This instance is inactive.");
|
|
}
|
|
|
|
FileMonitor()->StopMonitoring();
|
|
|
|
std::string errorMessage;
|
|
cmake* cm = this->CMakeInstance();
|
|
this->GeneratorInfo.SetupGenerator(cm, &errorMessage);
|
|
if (!errorMessage.empty()) {
|
|
return request.ReportError(errorMessage);
|
|
}
|
|
|
|
// Make sure the types of cacheArguments matches (if given):
|
|
std::vector<std::string> cacheArgs = { "unused" };
|
|
bool cacheArgumentsError = false;
|
|
const Json::Value passedArgs = request.Data[kCACHE_ARGUMENTS_KEY];
|
|
if (!passedArgs.isNull()) {
|
|
if (passedArgs.isString()) {
|
|
cacheArgs.push_back(passedArgs.asString());
|
|
} else if (passedArgs.isArray()) {
|
|
for (auto const& arg : passedArgs) {
|
|
if (!arg.isString()) {
|
|
cacheArgumentsError = true;
|
|
break;
|
|
}
|
|
cacheArgs.push_back(arg.asString());
|
|
}
|
|
} else {
|
|
cacheArgumentsError = true;
|
|
}
|
|
}
|
|
if (cacheArgumentsError) {
|
|
request.ReportError(
|
|
"cacheArguments must be unset, a string or an array of strings.");
|
|
}
|
|
|
|
std::string sourceDir = cm->GetHomeDirectory();
|
|
const std::string buildDir = cm->GetHomeOutputDirectory();
|
|
|
|
cmGlobalGenerator* gg = cm->GetGlobalGenerator();
|
|
|
|
if (buildDir.empty()) {
|
|
return request.ReportError("No build directory set via Handshake.");
|
|
}
|
|
|
|
if (cm->LoadCache(buildDir)) {
|
|
// build directory has been set up before
|
|
const std::string* cachedSourceDir =
|
|
cm->GetState()->GetInitializedCacheValue("CMAKE_HOME_DIRECTORY");
|
|
if (!cachedSourceDir) {
|
|
return request.ReportError("No CMAKE_HOME_DIRECTORY found in cache.");
|
|
}
|
|
if (sourceDir.empty()) {
|
|
sourceDir = *cachedSourceDir;
|
|
cm->SetHomeDirectory(sourceDir);
|
|
}
|
|
|
|
const std::string* cachedGenerator =
|
|
cm->GetState()->GetInitializedCacheValue("CMAKE_GENERATOR");
|
|
if (cachedGenerator) {
|
|
if (gg && gg->GetName() != *cachedGenerator) {
|
|
return request.ReportError("Configured generator does not match with "
|
|
"CMAKE_GENERATOR found in cache.");
|
|
}
|
|
}
|
|
} else {
|
|
// build directory has not been set up before
|
|
if (sourceDir.empty()) {
|
|
return request.ReportError("No sourceDirectory set via "
|
|
"setGlobalSettings and no cache found in "
|
|
"buildDirectory.");
|
|
}
|
|
}
|
|
|
|
cmSystemTools::ResetErrorOccuredFlag(); // Reset error state
|
|
|
|
if (cm->AddCMakePaths() != 1) {
|
|
return request.ReportError("Failed to set CMake paths.");
|
|
}
|
|
|
|
if (!cm->SetCacheArgs(cacheArgs)) {
|
|
return request.ReportError("cacheArguments could not be set.");
|
|
}
|
|
|
|
int ret = cm->Configure();
|
|
if (ret < 0) {
|
|
return request.ReportError("Configuration failed.");
|
|
}
|
|
|
|
std::vector<std::string> toWatchList;
|
|
getCMakeInputs(gg, std::string(), buildDir, nullptr, &toWatchList, nullptr);
|
|
|
|
FileMonitor()->MonitorPaths(toWatchList,
|
|
[this](const std::string& p, int e, int s) {
|
|
this->HandleCMakeFileChanges(p, e, s);
|
|
});
|
|
|
|
m_State = STATE_CONFIGURED;
|
|
m_isDirty = false;
|
|
return request.Reply(Json::Value());
|
|
}
|
|
|
|
cmServerResponse cmServerProtocol1::ProcessGlobalSettings(
|
|
const cmServerRequest& request)
|
|
{
|
|
cmake* cm = this->CMakeInstance();
|
|
Json::Value obj = Json::objectValue;
|
|
|
|
// Capabilities information:
|
|
obj[kCAPABILITIES_KEY] = cm->ReportCapabilitiesJson(true);
|
|
|
|
obj[kDEBUG_OUTPUT_KEY] = cm->GetDebugOutput();
|
|
obj[kTRACE_KEY] = cm->GetTrace();
|
|
obj[kTRACE_EXPAND_KEY] = cm->GetTraceExpand();
|
|
obj[kWARN_UNINITIALIZED_KEY] = cm->GetWarnUninitialized();
|
|
obj[kWARN_UNUSED_KEY] = cm->GetWarnUnused();
|
|
obj[kWARN_UNUSED_CLI_KEY] = cm->GetWarnUnusedCli();
|
|
obj[kCHECK_SYSTEM_VARS_KEY] = cm->GetCheckSystemVars();
|
|
|
|
obj[kSOURCE_DIRECTORY_KEY] = this->GeneratorInfo.SourceDirectory;
|
|
obj[kBUILD_DIRECTORY_KEY] = this->GeneratorInfo.BuildDirectory;
|
|
|
|
// Currently used generator:
|
|
obj[kGENERATOR_KEY] = this->GeneratorInfo.GeneratorName;
|
|
obj[kEXTRA_GENERATOR_KEY] = this->GeneratorInfo.ExtraGeneratorName;
|
|
|
|
return request.Reply(obj);
|
|
}
|
|
|
|
static void setBool(const cmServerRequest& request, const std::string& key,
|
|
std::function<void(bool)> const& setter)
|
|
{
|
|
if (request.Data[key].isNull()) {
|
|
return;
|
|
}
|
|
setter(request.Data[key].asBool());
|
|
}
|
|
|
|
cmServerResponse cmServerProtocol1::ProcessSetGlobalSettings(
|
|
const cmServerRequest& request)
|
|
{
|
|
const std::vector<std::string> boolValues = {
|
|
kDEBUG_OUTPUT_KEY, kTRACE_KEY, kTRACE_EXPAND_KEY,
|
|
kWARN_UNINITIALIZED_KEY, kWARN_UNUSED_KEY, kWARN_UNUSED_CLI_KEY,
|
|
kCHECK_SYSTEM_VARS_KEY
|
|
};
|
|
for (std::string const& i : boolValues) {
|
|
if (!request.Data[i].isNull() && !request.Data[i].isBool()) {
|
|
return request.ReportError("\"" + i +
|
|
"\" must be unset or a bool value.");
|
|
}
|
|
}
|
|
|
|
cmake* cm = this->CMakeInstance();
|
|
|
|
setBool(request, kDEBUG_OUTPUT_KEY,
|
|
[cm](bool e) { cm->SetDebugOutputOn(e); });
|
|
setBool(request, kTRACE_KEY, [cm](bool e) { cm->SetTrace(e); });
|
|
setBool(request, kTRACE_EXPAND_KEY, [cm](bool e) { cm->SetTraceExpand(e); });
|
|
setBool(request, kWARN_UNINITIALIZED_KEY,
|
|
[cm](bool e) { cm->SetWarnUninitialized(e); });
|
|
setBool(request, kWARN_UNUSED_KEY, [cm](bool e) { cm->SetWarnUnused(e); });
|
|
setBool(request, kWARN_UNUSED_CLI_KEY,
|
|
[cm](bool e) { cm->SetWarnUnusedCli(e); });
|
|
setBool(request, kCHECK_SYSTEM_VARS_KEY,
|
|
[cm](bool e) { cm->SetCheckSystemVars(e); });
|
|
|
|
return request.Reply(Json::Value());
|
|
}
|
|
|
|
cmServerResponse cmServerProtocol1::ProcessFileSystemWatchers(
|
|
const cmServerRequest& request)
|
|
{
|
|
const cmFileMonitor* const fm = FileMonitor();
|
|
Json::Value result = Json::objectValue;
|
|
Json::Value files = Json::arrayValue;
|
|
for (auto const& f : fm->WatchedFiles()) {
|
|
files.append(f);
|
|
}
|
|
Json::Value directories = Json::arrayValue;
|
|
for (auto const& d : fm->WatchedDirectories()) {
|
|
directories.append(d);
|
|
}
|
|
result[kWATCHED_FILES_KEY] = files;
|
|
result[kWATCHED_DIRECTORIES_KEY] = directories;
|
|
|
|
return request.Reply(result);
|
|
}
|
|
|
|
cmServerResponse cmServerProtocol1::ProcessCTests(
|
|
const cmServerRequest& request)
|
|
{
|
|
if (this->m_State < STATE_COMPUTED) {
|
|
return request.ReportError("This instance was not yet computed.");
|
|
}
|
|
|
|
return request.Reply(DumpCTestInfo(this->CMakeInstance()));
|
|
}
|
|
|
|
cmServerProtocol1::GeneratorInformation::GeneratorInformation(
|
|
const std::string& generatorName, const std::string& extraGeneratorName,
|
|
const std::string& toolset, const std::string& platform,
|
|
const std::string& sourceDirectory, const std::string& buildDirectory)
|
|
: GeneratorName(generatorName)
|
|
, ExtraGeneratorName(extraGeneratorName)
|
|
, Toolset(toolset)
|
|
, Platform(platform)
|
|
, SourceDirectory(sourceDirectory)
|
|
, BuildDirectory(buildDirectory)
|
|
{
|
|
}
|
|
|
|
void cmServerProtocol1::GeneratorInformation::SetupGenerator(
|
|
cmake* cm, std::string* errorMessage)
|
|
{
|
|
const std::string fullGeneratorName =
|
|
cmExternalMakefileProjectGenerator::CreateFullGeneratorName(
|
|
GeneratorName, ExtraGeneratorName);
|
|
|
|
cm->SetHomeDirectory(SourceDirectory);
|
|
cm->SetHomeOutputDirectory(BuildDirectory);
|
|
|
|
cmGlobalGenerator* gg = cm->CreateGlobalGenerator(fullGeneratorName);
|
|
if (!gg) {
|
|
setErrorMessage(
|
|
errorMessage,
|
|
std::string("Could not set up the requested combination of \"") +
|
|
kGENERATOR_KEY + "\" and \"" + kEXTRA_GENERATOR_KEY + "\"");
|
|
return;
|
|
}
|
|
|
|
cm->SetGlobalGenerator(gg);
|
|
|
|
cm->SetGeneratorToolset(Toolset);
|
|
cm->SetGeneratorPlatform(Platform);
|
|
}
|