diff options
-rw-r--r-- | Help/manual/cmake-server.7.rst | 261 | ||||
-rw-r--r-- | Source/cmServerDictionary.h | 33 | ||||
-rw-r--r-- | Source/cmServerProtocol.cxx | 549 | ||||
-rw-r--r-- | Source/cmServerProtocol.h | 3 | ||||
-rw-r--r-- | Tests/Server/CMakeLists.txt | 2 | ||||
-rw-r--r-- | Tests/Server/buildsystem1/CMakeLists.txt | 22 | ||||
-rw-r--r-- | Tests/Server/buildsystem1/foo.cpp | 5 | ||||
-rw-r--r-- | Tests/Server/buildsystem1/main.cpp | 5 | ||||
-rw-r--r-- | Tests/Server/buildsystem1/subdir/CMakeLists.txt | 5 | ||||
-rw-r--r-- | Tests/Server/buildsystem1/subdir/empty.cpp | 5 | ||||
-rw-r--r-- | Tests/Server/cmakelib.py | 20 | ||||
-rw-r--r-- | Tests/Server/server-test.py | 37 | ||||
-rw-r--r-- | Tests/Server/tc_buildsystem1.json | 27 |
13 files changed, 952 insertions, 22 deletions
diff --git a/Help/manual/cmake-server.7.rst b/Help/manual/cmake-server.7.rst index b8a425c..f662125 100644 --- a/Help/manual/cmake-server.7.rst +++ b/Help/manual/cmake-server.7.rst @@ -374,3 +374,264 @@ CMake will reply (after reporting progress information):: [== CMake Server ==[ {"cookie":"","inReplyTo":"compute","type":"reply"} ]== CMake Server ==] + + +Type "codemodel" +^^^^^^^^^^^^^^^^ + +The "codemodel" request can be used after a project was "compute"d successfully. + +It will list the complete project structure as it is known to cmake. + +The reply will contain a key "projects", which will contain a list of +project objects, one for each (sub-)project defined in the cmake build system. + +Each project object can have the following keys: + +"name" + contains the (sub-)projects name. +"sourceDirectory" + contains the current source directory +"buildDirectory" + contains the current build directory. +"configurations" + contains a list of configuration objects. + +Configuration objects are used to destinquish between different +configurations the build directory might have enabled. While most generators +only support one configuration, others support several. + +Each configuration object can have the following keys: + +"name" + contains the name of the configuration. The name may be empty. +"targets" + contains a list of target objects, one for each build target. + +Target objects define individual build targets for a certain configuration. + +Each target object can have the following keys: + +"name" + contains the name of the target. +"type" + defines the type of build of the target. Possible values are + "STATIC_LIBRARY", "MODULE_LIBRARY", "SHARED_LIBRARY", "OBJECT_LIBRARY", + "EXECUTABLE", "UTILITY" and "INTERFACE_LIBRARY". +"fullName" + contains the full name of the build result (incl. extensions, etc.). +"sourceDirectory" + contains the current source directory. +"buildDirectory" + contains the current build directory. +"artifacts" + with a list of build artifacts. The list is sorted with the most + important artifacts first (e.g. a .DLL file is listed before a + .PDB file on windows). +"linkerLanguage" + contains the language of the linker used to produce the artifact. +"linkLibraries" + with a list of libraries to link to. This value is encoded in the + system's native shell format. +"linkFlags" + with a list of flags to pass to the linker. This value is encoded in + the system's native shell format. +"linkLanguageFlags" + with the flags for a compiler using the linkerLanguage. This value is + encoded in the system's native shell format. +"frameworkPath" + with the framework path (on Apple computers). This value is encoded + in the system's native shell format. +"linkPath" + with the link path. This value is encoded in the system's native shell + format. +"sysroot" + with the sysroot path. +"fileGroups" + contains the source files making up the target. + +FileGroups are used to group sources using similar settings together. + +Each fileGroup object may contain the following keys: + +"language" + contains the programming language used by all files in the group. +"compileFlags" + with a string containing all the flags passed to the compiler + when building any of the files in this group. This value is encoded in + the system's native shell format. +"includePath" + with a list of include paths. Each include path is an object + containing a "path" with the actual include path and "isSystem" with a bool + value informing whether this is a normal include or a system include. This + value is encoded in the system's native shell format. +"defines" + with a list of defines in the form "SOMEVALUE" or "SOMEVALUE=42". This + value is encoded in the system's native shell format. +"sources" + with a list of source files. + +All file paths in the fileGroup are either absolute or relative to the +sourceDirectory of the target. + +Example:: + + [== CMake Server ==[ + {"type":"project"} + ]== CMake Server ==] + +CMake will reply:: + + [== CMake Server ==[ + { + "cookie":"", + "type":"reply", + "inReplyTo":"project", + + "projects": + [ + { + "name":"CMAKE_FORM", + "sourceDirectory":"/home/code/src/cmake/Source/CursesDialog/form" + "buildDirectory":"/tmp/cmake-build-test/Source/CursesDialog/form", + "configurations": + [ + { + "name":"", + "targets": + [ + { + "artifactDirectory":"/tmp/cmake/Source/CursesDialog/form", + "fileGroups": + [ + { + "compileFlags":" -std=gnu11", + "defines": + [ + "SOMETHING=1", + "LIBARCHIVE_STATIC" + ], + "includePath": + [ + { "path":"/tmp/cmake-build-test/Utilities" }, + { "isSystem": true, "path":"/usr/include/something" }, + ... + ] + "language":"C", + "sources": + [ + "fld_arg.c", + ... + "fty_regex.c" + ] + } + ], + "fullName":"libcmForm.a", + "linkerLanguage":"C", + "name":"cmForm", + "type":"STATIC_LIBRARY" + } + ] + } + ], + }, + ... + ] + } + ]== CMake Server ==] + +The output can be tailored to the specific needs via parameter passed when +requesting "project" information. + +You can have a "depth" key, which accepts "project", "configuration" and +"target" as string values. These cause the output to be trimmed at the +appropriate depth of the output tree. + +You can also set "configurations" to an array of strings with configuration +names to list. This will cause any configuration that is not listed to be +trimmed from the output. + +Generated files can be included in the listing by setting "includeGeneratedFiles" +to "true". This setting defaults to "false", so generated files are not +listed by default. + +Finally you can limit the target types that are going to be listed. This is +done by providing a list of target types as an array of strings to the +"targetTypes" key. + + +Type "cmakeInputs" +^^^^^^^^^^^^^^^^^^ + +The "cmakeInputs" requests will report files used by CMake as part +of the build system itself. + +This request is only available after a project was successfully +"configure"d. + +Example:: + + [== CMake Server ==[ + {"type":"cmakeInputs"} + ]== CMake Server ==] + +CMake will reply with the following information:: + + [== CMake Server ==[ + {"buildFiles": + [ + {"isCMake":true,"isTemporary":false,"sources":["/usr/lib/cmake/...", ... ]}, + {"isCMake":false,"isTemporary":false,"sources":["CMakeLists.txt", ...]}, + {"isCMake":false,"isTemporary":true,"sources":["/tmp/build/CMakeFiles/...", ...]} + ], + "cmakeRootDirectory":"/usr/lib/cmake", + "sourceDirectory":"/home/code/src/cmake", + "cookie":"", + "inReplyTo":"cmakeInputs", + "type":"reply" + } + ]== CMake Server ==] + +All file names are either relative to the top level source directory or +absolute. + +The list of files which "isCMake" set to true are part of the cmake installation. + +The list of files witch "isTemporary" set to true are part of the build directory +and will not survive the build directory getting cleaned out. + + +Type "cache" +^^^^^^^^^^^^ + +The "cache" request can be used once a project is configured and will +list the cached configuration values. + +Example:: + + [== CMake Server ==[ + {"type":"cache"} + ]== CMake Server ==] + +CMake will respond with the following output:: + + [== CMake Server ==[ + { + "cookie":"","inReplyTo":"cache","type":"reply", + "cache": + [ + { + "key":"SOMEVALUE", + "properties": + { + "ADVANCED":"1", + "HELPSTRING":"This is not helpful" + } + "type":"STRING", + "value":"TEST"} + ] + } + ]== CMake Server ==] + +The output can be limited to a list of keys by passing an array of key names +to the "keys" optional field of the "cache" request. diff --git a/Source/cmServerDictionary.h b/Source/cmServerDictionary.h index e429571..c811b83 100644 --- a/Source/cmServerDictionary.h +++ b/Source/cmServerDictionary.h @@ -6,6 +6,9 @@ // Vocabulary: +static const std::string kCACHE_TYPE = "cache"; +static const std::string kCMAKE_INPUTS_TYPE = "cmakeInputs"; +static const std::string kCODE_MODEL_TYPE = "codemodel"; static const std::string kCOMPUTE_TYPE = "compute"; static const std::string kCONFIGURE_TYPE = "configure"; static const std::string kERROR_TYPE = "error"; @@ -17,33 +20,63 @@ static const std::string kREPLY_TYPE = "reply"; static const std::string kSET_GLOBAL_SETTINGS_TYPE = "setGlobalSettings"; static const std::string kSIGNAL_TYPE = "signal"; +static const std::string kARTIFACTS_KEY = "artifacts"; static const std::string kBUILD_DIRECTORY_KEY = "buildDirectory"; +static const std::string kBUILD_FILES_KEY = "buildFiles"; static const std::string kCACHE_ARGUMENTS_KEY = "cacheArguments"; +static const std::string kCACHE_KEY = "cache"; static const std::string kCAPABILITIES_KEY = "capabilities"; static const std::string kCHECK_SYSTEM_VARS_KEY = "checkSystemVars"; +static const std::string kCMAKE_ROOT_DIRECTORY_KEY = "cmakeRootDirectory"; +static const std::string kCOMPILE_FLAGS_KEY = "compileFlags"; +static const std::string kCONFIGURATIONS_KEY = "configurations"; static const std::string kCOOKIE_KEY = "cookie"; static const std::string kDEBUG_OUTPUT_KEY = "debugOutput"; +static const std::string kDEFINES_KEY = "defines"; static const std::string kERROR_MESSAGE_KEY = "errorMessage"; static const std::string kEXTRA_GENERATOR_KEY = "extraGenerator"; +static const std::string kFILE_GROUPS_KEY = "fileGroups"; +static const std::string kFRAMEWORK_PATH_KEY = "frameworkPath"; +static const std::string kFULL_NAME_KEY = "fullName"; static const std::string kGENERATOR_KEY = "generator"; +static const std::string kINCLUDE_PATH_KEY = "includePath"; +static const std::string kIS_CMAKE_KEY = "isCMake"; static const std::string kIS_EXPERIMENTAL_KEY = "isExperimental"; +static const std::string kIS_GENERATED_KEY = "isGenerated"; +static const std::string kIS_SYSTEM_KEY = "isSystem"; +static const std::string kIS_TEMPORARY_KEY = "isTemporary"; +static const std::string kKEY_KEY = "key"; +static const std::string kKEYS_KEY = "keys"; +static const std::string kLANGUAGE_KEY = "language"; +static const std::string kLINKER_LANGUAGE_KEY = "linkerLanguage"; +static const std::string kLINK_FLAGS_KEY = "linkFlags"; +static const std::string kLINK_LANGUAGE_FLAGS_KEY = "linkLanguageFlags"; +static const std::string kLINK_LIBRARIES_KEY = "linkLibraries"; +static const std::string kLINK_PATH_KEY = "linkPath"; static const std::string kMAJOR_KEY = "major"; static const std::string kMESSAGE_KEY = "message"; static const std::string kMINOR_KEY = "minor"; static const std::string kNAME_KEY = "name"; +static const std::string kPATH_KEY = "path"; static const std::string kPROGRESS_CURRENT_KEY = "progressCurrent"; static const std::string kPROGRESS_MAXIMUM_KEY = "progressMaximum"; static const std::string kPROGRESS_MESSAGE_KEY = "progressMessage"; static const std::string kPROGRESS_MINIMUM_KEY = "progressMinimum"; +static const std::string kPROJECTS_KEY = "projects"; +static const std::string kPROPERTIES_KEY = "properties"; static const std::string kPROTOCOL_VERSION_KEY = "protocolVersion"; static const std::string kREPLY_TO_KEY = "inReplyTo"; static const std::string kSOURCE_DIRECTORY_KEY = "sourceDirectory"; +static const std::string kSOURCES_KEY = "sources"; static const std::string kSUPPORTED_PROTOCOL_VERSIONS = "supportedProtocolVersions"; +static const std::string kSYSROOT_KEY = "sysroot"; +static const std::string kTARGETS_KEY = "targets"; static const std::string kTITLE_KEY = "title"; static const std::string kTRACE_EXPAND_KEY = "traceExpand"; static const std::string kTRACE_KEY = "trace"; static const std::string kTYPE_KEY = "type"; +static const std::string kVALUE_KEY = "value"; static const std::string kWARN_UNINITIALIZED_KEY = "warnUninitialized"; static const std::string kWARN_UNUSED_CLI_KEY = "warnUnusedCli"; static const std::string kWARN_UNUSED_KEY = "warnUnused"; diff --git a/Source/cmServerProtocol.cxx b/Source/cmServerProtocol.cxx index c505568..f083e49 100644 --- a/Source/cmServerProtocol.cxx +++ b/Source/cmServerProtocol.cxx @@ -2,10 +2,16 @@ file Copyright.txt or https://cmake.org/licensing for details. */ #include "cmServerProtocol.h" +#include "cmCacheManager.h" #include "cmExternalMakefileProjectGenerator.h" +#include "cmGeneratorTarget.h" #include "cmGlobalGenerator.h" +#include "cmListFileCache.h" +#include "cmLocalGenerator.h" +#include "cmMakefile.h" #include "cmServer.h" #include "cmServerDictionary.h" +#include "cmSourceFile.h" #include "cmSystemTools.h" #include "cmake.h" @@ -16,6 +22,100 @@ #include "cm_jsoncpp_value.h" #endif +#include <algorithm> +#include <string> +#include <vector> + +// Get rid of some windows macros: +#undef max + +namespace { + +static 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; +} + +static bool hasString(const Json::Value& v, const std::string& s) +{ + return !v.isNull() && + std::find_if(v.begin(), v.end(), [s](const Json::Value& i) { + return i.asString() == s; + }) != v.end(); +} + +template <class T> +static Json::Value fromStringList(const T& in) +{ + Json::Value result = Json::arrayValue; + for (const std::string& i : in) { + result.append(i); + } + return result; +} + +static std::vector<std::string> toStringList(const Json::Value& in) +{ + std::vector<std::string> result; + for (const auto& it : in) { + result.push_back(it.asString()); + } + return result; +} + +static 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() + '/'; + const std::vector<cmMakefile*> makefiles = gg->GetMakefiles(); + for (auto it = makefiles.begin(); it != makefiles.end(); ++it) { + const std::vector<std::string> listFiles = (*it)->GetListFiles(); + + for (auto jt = listFiles.begin(); jt != listFiles.end(); ++jt) { + + const std::string startOfFile = jt->substr(0, cmakeRootDir.size()); + const bool isInternal = (startOfFile == cmakeRootDir); + const bool isTemporary = !isInternal && (jt->find(buildDir + '/') == 0); + + std::string toAdd = *jt; + if (!sourceDir.empty()) { + const std::string& relative = + cmSystemTools::RelativePath(sourceDir.c_str(), jt->c_str()); + if (toAdd.size() > relative.size()) + toAdd = relative; + } + + if (isInternal) { + if (internalFiles) + internalFiles->push_back(toAdd); + } else { + if (isTemporary) { + if (tmpFiles) + tmpFiles->push_back(toAdd); + } else { + if (explicitFiles) + explicitFiles->push_back(toAdd); + } + } + } + } +} + +} // namespace + cmServerRequest::cmServerRequest(cmServer* server, const std::string& t, const std::string& c, const Json::Value& d) : Type(t) @@ -270,6 +370,15 @@ const cmServerResponse cmServerProtocol1_0::Process( { 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); } @@ -291,6 +400,441 @@ bool cmServerProtocol1_0::IsExperimental() const return true; } +cmServerResponse cmServerProtocol1_0::ProcessCache( + const cmServerRequest& request) +{ + if (this->m_State < STATE_CONFIGURED) { + return request.ReportError("This project was not configured yet."); + } + + 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 i : keys) { + if (std::find_if(allKeys.begin(), allKeys.end(), + [i](const std::string& j) { return i == j; }) == + allKeys.end()) { + return request.ReportError("Key \"" + i + "\" not found in cache."); + } + } + } + std::sort(keys.begin(), keys.end()); + for (auto 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 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); +} + +cmServerResponse cmServerProtocol1_0::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 cmGlobalGenerator* gg = cm->GetGlobalGenerator(); + const std::string cmakeRootDir = cmSystemTools::GetCMakeRoot(); + const std::string buildDir = cm->GetHomeOutputDirectory(); + const std::string sourceDir = cm->GetHomeDirectory(); + + Json::Value result = Json::objectValue; + result[kSOURCE_DIRECTORY_KEY] = sourceDir; + result[kCMAKE_ROOT_DIRECTORY_KEY] = cmakeRootDir; + + 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); + + result[kBUILD_FILES_KEY] = array; + + return request.Reply(result); +} + +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; + for (auto i : defines) { + result.push_back(i); + } + std::sort(result.begin(), result.end()); + Defines = result; +} + +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 i : in.IncludePathList) { + result = result ^ (hash<std::string>()(i.first) ^ + (i.second ? std::numeric_limits<size_t>::max() : 0)); + } + for (auto 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, + const std::vector<std::string>& files, + const std::string& baseDir) +{ + Json::Value result = Json::objectValue; + + 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 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 i : files) { + const std::string relPath = + cmSystemTools::RelativePath(baseDir.c_str(), i.c_str()); + sourcesValue.append(relPath.size() < i.size() ? relPath : i); + } + + result[kSOURCES_KEY] = sourcesValue; + return result; +} + +static Json::Value DumpSourceFilesList( + cmGeneratorTarget* target, const std::string& config, + const std::map<std::string, LanguageData>& languageDataMap) +{ + // Collect sourcefile groups: + + std::vector<cmSourceFile*> files; + target->GetSourceFiles(files, config); + + std::unordered_map<LanguageData, std::vector<std::string> > fileGroups; + for (cmSourceFile* file : files) { + LanguageData fileData; + fileData.Language = file->GetLanguage(); + if (!fileData.Language.empty()) { + const LanguageData& ld = languageDataMap.at(fileData.Language); + cmLocalGenerator* lg = target->GetLocalGenerator(); + + std::string compileFlags = ld.Flags; + lg->AppendFlags(compileFlags, file->GetProperty("COMPILE_FLAGS")); + fileData.Flags = compileFlags; + + fileData.IncludePathList = ld.IncludePathList; + + std::set<std::string> defines; + lg->AppendDefines(defines, file->GetProperty("COMPILE_DEFINITIONS")); + const std::string defPropName = + "COMPILE_DEFINITIONS_" + cmSystemTools::UpperCase(config); + lg->AppendDefines(defines, file->GetProperty(defPropName)); + defines.insert(ld.Defines.begin(), ld.Defines.end()); + + fileData.SetDefines(defines); + } + + fileData.IsGenerated = file->GetPropertyAsBool("GENERATED"); + std::vector<std::string>& groupFileList = fileGroups[fileData]; + groupFileList.push_back(file->GetFullPath()); + } + + const std::string baseDir = target->Makefile->GetCurrentSourceDirectory(); + Json::Value result = Json::arrayValue; + for (auto it = fileGroups.begin(); it != fileGroups.end(); ++it) { + Json::Value group = DumpSourceFileGroup(it->first, it->second, baseDir); + if (!group.isNull()) + result.append(group); + } + + return result; +} + +static Json::Value DumpTarget(cmGeneratorTarget* target, + const std::string& config) +{ + cmLocalGenerator* lg = target->GetLocalGenerator(); + const cmState* state = lg->GetState(); + + const cmState::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[kTYPE_KEY] = typeName; + result[kFULL_NAME_KEY] = target->GetFullName(config); + result[kSOURCE_DIRECTORY_KEY] = lg->GetCurrentSourceDirectory(); + result[kBUILD_DIRECTORY_KEY] = lg->GetCurrentBinaryDirectory(); + + if (target->HaveWellDefinedOutputFiles()) { + Json::Value artifacts = Json::arrayValue; + artifacts.append(target->GetFullPath(config, false)); + if (target->IsDLLPlatform()) { + artifacts.append(target->GetFullPath(config, true)); + 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; + lg->GetTargetFlags(config, linkLibs, linkLanguageFlags, linkFlags, + frameworkPath, linkPath, target, false); + + 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; + target->GetLanguages(languages, config); + std::map<std::string, LanguageData> languageDataMap; + for (auto 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 (auto i : includePathList) { + ld.IncludePathList.push_back( + std::make_pair(i, target->IsSystemIncludeDirectory(i, config))); + } + } + + 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 (const auto& lgIt : generators) { + 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, const std::string 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; + + assert(projectIt.second.size() > + 0); // All Projects must have at least one local generator + const cmLocalGenerator* lg = projectIt.second.at(0); + + // Project structure information: + const cmMakefile* mf = lg->GetMakefile(); + pObj[kSOURCE_DIRECTORY_KEY] = mf->GetCurrentSourceDirectory(); + pObj[kBUILD_DIRECTORY_KEY] = mf->GetCurrentBinaryDirectory(); + pObj[kTARGETS_KEY] = DumpTargetsList(projectIt.second, config); + + 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 (const std::string& c : getConfigurations(cm)) { + result.append(DumpConfiguration(cm, c)); + } + + return result; +} + +cmServerResponse cmServerProtocol1_0::ProcessCodeModel( + const cmServerRequest& request) +{ + if (this->m_State != STATE_COMPUTED) { + return request.ReportError("No build system was generated yet."); + } + + Json::Value result = Json::objectValue; + result[kCONFIGURATIONS_KEY] = DumpConfigurationsList(this->CMakeInstance()); + return request.Reply(result); +} + cmServerResponse cmServerProtocol1_0::ProcessCompute( const cmServerRequest& request) { @@ -346,6 +890,8 @@ cmServerResponse cmServerProtocol1_0::ProcessConfigure( 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 setGlobalSettings."); @@ -366,8 +912,7 @@ cmServerResponse cmServerProtocol1_0::ProcessConfigure( const char* cachedGenerator = cm->GetState()->GetInitializedCacheValue("CMAKE_GENERATOR"); if (cachedGenerator) { - cmGlobalGenerator* gen = cm->GetGlobalGenerator(); - if (gen && gen->GetName() != cachedGenerator) { + if (gg && gg->GetName() != cachedGenerator) { return request.ReportError("Configured generator does not match with " "CMAKE_GENERATOR found in cache."); } diff --git a/Source/cmServerProtocol.h b/Source/cmServerProtocol.h index 63ef0be..d672a60 100644 --- a/Source/cmServerProtocol.h +++ b/Source/cmServerProtocol.h @@ -108,6 +108,9 @@ private: std::string* errorMessage) override; // Handle requests: + cmServerResponse ProcessCache(const cmServerRequest& request); + cmServerResponse ProcessCMakeInputs(const cmServerRequest& request); + cmServerResponse ProcessCodeModel(const cmServerRequest& request); cmServerResponse ProcessCompute(const cmServerRequest& request); cmServerResponse ProcessConfigure(const cmServerRequest& request); cmServerResponse ProcessGlobalSettings(const cmServerRequest& request); diff --git a/Tests/Server/CMakeLists.txt b/Tests/Server/CMakeLists.txt index 03f5042..8913406 100644 --- a/Tests/Server/CMakeLists.txt +++ b/Tests/Server/CMakeLists.txt @@ -10,6 +10,7 @@ macro(do_test bsname file) "${CMAKE_SOURCE_DIR}/${file}" "${CMAKE_SOURCE_DIR}" "${CMAKE_BINARY_DIR}" + "${CMAKE_GENERATOR}" RESULT_VARIABLE test_result ) @@ -20,5 +21,6 @@ endmacro() do_test("test_handshake" "tc_handshake.json") do_test("test_globalSettings" "tc_globalSettings.json") +do_test("test_buildsystem1" "tc_buildsystem1.json") add_executable(Server empty.cpp) diff --git a/Tests/Server/buildsystem1/CMakeLists.txt b/Tests/Server/buildsystem1/CMakeLists.txt new file mode 100644 index 0000000..d690472 --- /dev/null +++ b/Tests/Server/buildsystem1/CMakeLists.txt @@ -0,0 +1,22 @@ +cmake_minimum_required(VERSION 3.4) + +project(buildsystem2) + +set(var1 123) + +set(var2 345) + +add_executable(main main.cpp) + +add_executable(m_other main.cpp) + +add_library(foo foo.cpp) + +function(f1) +endfunction() + +set(var3 345) + +add_library(someImportedLib UNKNOWN IMPORTED) + +add_subdirectory(subdir) diff --git a/Tests/Server/buildsystem1/foo.cpp b/Tests/Server/buildsystem1/foo.cpp new file mode 100644 index 0000000..7f39d71 --- /dev/null +++ b/Tests/Server/buildsystem1/foo.cpp @@ -0,0 +1,5 @@ + +int foo() +{ + return 0; +} diff --git a/Tests/Server/buildsystem1/main.cpp b/Tests/Server/buildsystem1/main.cpp new file mode 100644 index 0000000..766b775 --- /dev/null +++ b/Tests/Server/buildsystem1/main.cpp @@ -0,0 +1,5 @@ + +int main() +{ + return 0; +} diff --git a/Tests/Server/buildsystem1/subdir/CMakeLists.txt b/Tests/Server/buildsystem1/subdir/CMakeLists.txt new file mode 100644 index 0000000..9157312 --- /dev/null +++ b/Tests/Server/buildsystem1/subdir/CMakeLists.txt @@ -0,0 +1,5 @@ +set(bar4 something) + +set(bar5 more) + +add_executable(ooo empty.cpp) diff --git a/Tests/Server/buildsystem1/subdir/empty.cpp b/Tests/Server/buildsystem1/subdir/empty.cpp new file mode 100644 index 0000000..7f39d71 --- /dev/null +++ b/Tests/Server/buildsystem1/subdir/empty.cpp @@ -0,0 +1,5 @@ + +int foo() +{ + return 0; +} diff --git a/Tests/Server/cmakelib.py b/Tests/Server/cmakelib.py index 8beaeef..94384eb 100644 --- a/Tests/Server/cmakelib.py +++ b/Tests/Server/cmakelib.py @@ -102,10 +102,20 @@ def waitForMessage(cmakeCommand, expected): sys.exit(-1) return packet -def waitForReply(cmakeCommand, originalType, cookie): - packet = waitForRawMessage(cmakeCommand) - if packet['cookie'] != cookie or packet['type'] != 'reply' or packet['inReplyTo'] != originalType: +def waitForReply(cmakeCommand, originalType, cookie, skipProgress): + gotResult = False + while True: + packet = waitForRawMessage(cmakeCommand) + t = packet['type'] + if packet['cookie'] != cookie or packet['inReplyTo'] != originalType: + sys.exit(1) + if t == 'message' or t == 'progress': + if skipProgress: + continue + if t == 'reply': + break sys.exit(1) + return packet def waitForError(cmakeCommand, originalType, cookie, message): @@ -126,10 +136,10 @@ def handshake(cmakeCommand, major, minor, source, build, generator, extraGenerat writePayload(cmakeCommand, { 'type': 'handshake', 'protocolVersion': version, 'cookie': 'TEST_HANDSHAKE', 'sourceDirectory': source, 'buildDirectory': build, 'generator': generator, 'extraGenerator': extraGenerator }) - waitForReply(cmakeCommand, 'handshake', 'TEST_HANDSHAKE') + waitForReply(cmakeCommand, 'handshake', 'TEST_HANDSHAKE', False) def validateGlobalSettings(cmakeCommand, cmakeCommandPath, data): - packet = waitForReply(cmakeCommand, 'globalSettings', '') + packet = waitForReply(cmakeCommand, 'globalSettings', '', False) capabilities = packet['capabilities'] diff --git a/Tests/Server/server-test.py b/Tests/Server/server-test.py index d2bf92e..72f82ba 100644 --- a/Tests/Server/server-test.py +++ b/Tests/Server/server-test.py @@ -1,24 +1,25 @@ -import sys, cmakelib, json +import sys, cmakelib, json, os, shutil debug = True cmakeCommand = sys.argv[1] testFile = sys.argv[2] sourceDir = sys.argv[3] -buildDir = sys.argv[4] +buildDir = sys.argv[4] + "/" + os.path.splitext(os.path.basename(testFile))[0] +cmakeGenerator = sys.argv[5] -print("SourceDir: ", sourceDir, " -- BuildDir: ", buildDir) +print("Test:", testFile, + "\n-- SourceDir:", sourceDir, + "\n-- BuildDir:", buildDir, + "\n-- Generator:", cmakeGenerator) + +if os.path.exists(buildDir): + shutil.rmtree(buildDir) proc = cmakelib.initProc(cmakeCommand) with open(testFile) as f: - testText = f.read() - testText = testText.replace('%BUILDDIR%', buildDir) - testText = testText.replace('%SOURCEDIR%', sourceDir) - testData = json.loads(testText) - -buildDir = sys.argv[3] -sourceDir = sys.argv[4] + testData = json.loads(f.read()) for obj in testData: if 'sendRaw' in obj: @@ -38,9 +39,11 @@ for obj in testData: if debug: print("Waiting for reply:", json.dumps(data)) originalType = "" cookie = "" + skipProgress = False; if 'cookie' in data: cookie = data['cookie'] if 'type' in data: originalType = data['type'] - cmakelib.waitForReply(proc, originalType, cookie) + if 'skipProgress' in data: skipProgress = data['skipProgress'] + cmakelib.waitForReply(proc, originalType, cookie, skipProgress) elif 'error' in obj: data = obj['error'] if debug: print("Waiting for error:", json.dumps(data)) @@ -68,8 +71,8 @@ for obj in testData: if debug: print("Doing handshake:", json.dumps(data)) major = -1 minor = -1 - generator = 'Ninja' - extraGenerator = 'CodeBlocks' + generator = cmakeGenerator + extraGenerator = '' sourceDirectory = sourceDir buildDirectory = buildDir if 'major' in data: major = data['major'] @@ -78,14 +81,18 @@ for obj in testData: if 'sourceDirectory' in data: sourceDirectory = data['sourceDirectory'] if 'generator' in data: generator = data['generator'] if 'extraGenerator' in data: extraGenerator = data['extraGenerator'] + if not os.path.isabs(buildDirectory): + buildDirectory = buildDir + "/" + buildDirectory + if not os.path.isabs(sourceDirectory): + sourceDirectory = sourceDir + "/" + sourceDirectory cmakelib.handshake(proc, major, minor, sourceDirectory, buildDirectory, generator, extraGenerator) elif 'validateGlobalSettings' in obj: data = obj['validateGlobalSettings'] if not 'buildDirectory' in data: data['buildDirectory'] = buildDir if not 'sourceDirectory' in data: data['sourceDirectory'] = sourceDir - if not 'generator' in data: data['generator'] = 'Ninja' - if not 'extraGenerator' in data: data['extraGenerator'] = 'CodeBlocks' + if not 'generator' in data: data['generator'] = cmakeGenerator + if not 'extraGenerator' in data: data['extraGenerator'] = '' cmakelib.validateGlobalSettings(proc, cmakeCommand, data) elif 'message' in obj: print("MESSAGE:", obj["message"]) diff --git a/Tests/Server/tc_buildsystem1.json b/Tests/Server/tc_buildsystem1.json new file mode 100644 index 0000000..08831b7 --- /dev/null +++ b/Tests/Server/tc_buildsystem1.json @@ -0,0 +1,27 @@ +[ +{ "message": "Testing globalSettings" }, + +{ "handshake": {"major": 1, "sourceDirectory":"buildsystem1","buildDirectory":"buildsystem1"} }, + +{ "message": "Configure:" }, +{ "send": { "type": "configure", "cookie":"CONFIG" } }, +{ "reply": { "type": "configure", "cookie":"CONFIG", "skipProgress":true } }, + +{ "message": "Compute:" }, +{ "send": { "type": "compute", "cookie":"COMPUTE" } }, +{ "reply": { "type": "compute", "cookie":"COMPUTE", "skipProgress":true } }, + +{ "message": "Codemodel:" }, +{ "send": { "type": "codemodel", "cookie":"CODEMODEL" } }, +{ "reply": { "type": "codemodel", "cookie":"CODEMODEL" } }, + +{ "message": "CMake Inputs:"}, +{ "send": { "type": "cmakeInputs", "cookie":"INPUTS" } }, +{ "reply": { "type": "cmakeInputs", "cookie":"INPUTS" } }, + +{ "message": "Cache:"}, +{ "send": { "type": "cache", "cookie":"CACHE" } }, +{ "reply": { "type": "cache", "cookie":"CACHE" } }, + +{ "message": "Everything ok." } +] |