diff options
author | Brad King <brad.king@kitware.com> | 2018-10-18 13:39:25 (GMT) |
---|---|---|
committer | Brad King <brad.king@kitware.com> | 2018-12-12 11:40:10 (GMT) |
commit | 3e922ceb5e8cc4c4d72ddcbd8b485803c49d84f1 (patch) | |
tree | e44164dc568a4798ecda54b0d82c115748e9d1a1 | |
parent | 555fa77a35b15cf1ed5163bf9fa1782079534ef1 (diff) | |
download | CMake-3e922ceb5e8cc4c4d72ddcbd8b485803c49d84f1.zip CMake-3e922ceb5e8cc4c4d72ddcbd8b485803c49d84f1.tar.gz CMake-3e922ceb5e8cc4c4d72ddcbd8b485803c49d84f1.tar.bz2 |
fileapi: add codemodel v2
Start with v2 to distinguish it from server-mode v1.
Issue: #18398
-rw-r--r-- | Help/manual/cmake-file-api.7.rst | 470 | ||||
-rw-r--r-- | Source/CMakeLists.txt | 2 | ||||
-rw-r--r-- | Source/cmFileAPI.cxx | 61 | ||||
-rw-r--r-- | Source/cmFileAPI.h | 5 | ||||
-rw-r--r-- | Source/cmFileAPICodemodel.cxx | 1111 | ||||
-rw-r--r-- | Source/cmFileAPICodemodel.h | 15 | ||||
-rw-r--r-- | Tests/RunCMake/FileAPI/RunCMakeTest.cmake | 11 | ||||
-rw-r--r-- | Tests/RunCMake/FileAPI/codemodel-v2-ClientStateful-check.cmake | 11 | ||||
-rw-r--r-- | Tests/RunCMake/FileAPI/codemodel-v2-ClientStateful-prep.cmake | 4 | ||||
-rw-r--r-- | Tests/RunCMake/FileAPI/codemodel-v2-ClientStateless-check.cmake | 11 | ||||
-rw-r--r-- | Tests/RunCMake/FileAPI/codemodel-v2-ClientStateless-prep.cmake | 2 | ||||
-rw-r--r-- | Tests/RunCMake/FileAPI/codemodel-v2-SharedStateless-check.cmake | 10 | ||||
-rw-r--r-- | Tests/RunCMake/FileAPI/codemodel-v2-SharedStateless-prep.cmake | 2 | ||||
-rw-r--r-- | Tests/RunCMake/FileAPI/codemodel-v2-check.py | 15 | ||||
-rw-r--r-- | Tests/RunCMake/FileAPI/codemodel-v2.cmake | 1 | ||||
-rw-r--r-- | Utilities/IWYU/mapping.imp | 2 |
16 files changed, 1731 insertions, 2 deletions
diff --git a/Help/manual/cmake-file-api.7.rst b/Help/manual/cmake-file-api.7.rst index a1b4830..f87d7f0 100644 --- a/Help/manual/cmake-file-api.7.rst +++ b/Help/manual/cmake-file-api.7.rst @@ -399,3 +399,473 @@ The ``kind`` member is a string specifying the object kind name. The ``version`` member is a JSON object with ``major`` and ``minor`` members specifying integer components of the object kind's version. Additional top-level members are specific to each object kind. + +Object Kind "codemodel" +----------------------- + +The ``codemodel`` object kind describes the build system structure as +modeled by CMake. + +There is only one ``codemodel`` object major version, version 2. +Version 1 does not exist to avoid confusion with that from +:manual:`cmake-server(7)` mode. + +"codemodel" version 2 +^^^^^^^^^^^^^^^^^^^^^ + +``codemodel`` object version 2 is a JSON object: + +.. code-block:: json + + { + "kind": "codemodel", + "version": { "major": 2, "minor": 0 }, + "paths": { + "source": "/path/to/top-level-source-dir", + "build": "/path/to/top-level-build-dir" + }, + "configurations": [ + { + "name": "Debug", + "directories": [ + { + "source": ".", + "build": ".", + "childIndexes": [ 1 ], + "targetIndexes": [ 0 ] + }, + { + "source": "sub", + "build": "sub", + "parentIndex": 0, + "targetIndexes": [ 1 ] + } + ], + "targets": [ + { + "name": "MyExecutable", + "directoryIndex": 0, + "jsonFile": "<file>" + }, + { + "name": "MyLibrary", + "directoryIndex": 1, + "jsonFile": "<file>" + } + ] + } + ] + } + +The members specific to ``codemodel`` objects are: + +``paths`` + A JSON object containing members: + + ``source`` + A string specifying the absolute path to the top-level source directory, + represented with forward slashes. + + ``build`` + A string specifying the absolute path to the top-level build directory, + represented with forward slashes. + +``configurations`` + A JSON array of entries corresponding to available build configurations. + On single-configuration generators there is one entry for the value + of the :variable:`CMAKE_BUILD_TYPE` variable. For multi-configuration + generators there is an entry for each configuration listed in the + :variable:`CMAKE_CONFIGURATION_TYPES` variable. + Each entry is a JSON object containing members: + + ``name`` + A string specifying the name of the configuration, e.g. ``Debug``. + + ``directories`` + A JSON array of entries each corresponding to a build system directory + whose source directory contains a ``CMakeLists.txt`` file. The first + entry corresponds to the top-level directory. Each entry is a + JSON object containing members: + + ``source`` + A string specifying the path to the source directory, represented + with forward slashes. If the directory is inside the top-level + source directory then the path is specified relative to that + directory (with ``.`` for the top-level source directory itself). + Otherwise the path is absolute. + + ``build`` + A string specifying the path to the build directory, represented + with forward slashes. If the directory is inside the top-level + build directory then the path is specified relative to that + directory (with ``.`` for the top-level build directory itself). + Otherwise the path is absolute. + + ``parentIndex`` + Optional member that is present when the directory is not top-level. + The value is an unsigned integer 0-based index of another entry in + the main ``directories`` array that corresponds to the parent + directory that added this directory as a subdirectory. + + ``childIndexes`` + Optional member that is present when the directory has subdirectories. + The value is a JSON array of entries corresponding to child directories + created by the :command:`add_subdirectory` or :command:`subdirs` + command. Each entry is an unsigned integer 0-based index of another + entry in the main ``directories`` array. + + ``targetIndexes`` + Optional member that is present when the directory itself has targets, + excluding those belonging to subdirectories. The value is a JSON + array of entries corresponding to the targets. Each entry is an + unsigned integer 0-based index into the main ``targets`` array. + + ``targets`` + A JSON array of entries corresponding to the build system targets. + Such targets are created by calls to :command:`add_executable`, + :command:`add_library`, and :command:`add_custom_target`, excluding + imported targets and interface libraries (which do not generate any + build rules). Each entry is a JSON object containing members: + + ``name`` + A string specifying the target name. + + ``id`` + A string uniquely identifying the target. This matches the ``id`` + field in the file referenced by ``jsonFile``. + + ``directoryIndex`` + An unsigned integer 0-based index into the main ``directories`` array + indicating the build system directory in which the target is defined. + + ``jsonFile`` + A JSON string specifying a path relative to the codemodel file + to another JSON file containing a + `"codemodel" version 2 "target" object`_. + +"codemodel" version 2 "target" object +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +A codemodel "target" object is referenced by a `"codemodel" version 2`_ +object's ``targets`` array. Each "target" object is a JSON object +with members: + +``name`` + A string specifying the logical name of the target. + +``id`` + A string uniquely identifying the target. The format is unspecified + and should not be interpreted by clients. + +``type`` + A string specifying the type of the target. The value is one of + ``EXECUTABLE``, ``STATIC_LIBRARY``, ``SHARED_LIBRARY``, + ``MODULE_LIBRARY``, ``OBJECT_LIBRARY``, or ``UTILITY``. + +``backtrace`` + Optional member that is present when a CMake language backtrace to + the command in the source code that created the target is available. + The value is an unsigned integer 0-based index into the + ``backtraceGraph`` member's ``nodes`` array. + +``folder`` + Optional member that is present when the :prop_tgt:`FOLDER` target + property is set. The value is a JSON object with one member: + + ``name`` + A string specifying the name of the target folder. + +``paths`` + A JSON object containing members: + + ``source`` + A string specifying the path to the target's source directory, + represented with forward slashes. If the directory is inside the + top-level source directory then the path is specified relative to + that directory (with ``.`` for the top-level source directory itself). + Otherwise the path is absolute. + + ``build`` + A string specifying the path to the target's build directory, + represented with forward slashes. If the directory is inside the + top-level build directory then the path is specified relative to + that directory (with ``.`` for the top-level build directory itself). + Otherwise the path is absolute. + +``nameOnDisk`` + Optional member that is present for executable and library targets + that are linked or archived into a single primary artifact. + The value is a string specifying the file name of that artifact on disk. + +``artifacts`` + Optional member that is present for executable and library targets + that produce artifacts on disk meant for consumption by dependents. + The value is a JSON array of entries corresponding to the artifacts. + Each entry is a JSON object containing one member: + + ``path`` + A string specifying the path to the file on disk, represented with + forward slashes. If the file is inside the top-level build directory + then the path is specified relative to that directory. + Otherwise the path is absolute. + +``isGeneratorProvided`` + Optional member that is present with boolean value ``true`` if the + target is provided by CMake's build system generator rather than by + a command in the source code. + +``install`` + Optional member that is present when the target has an :command:`install` + rule. The value is a JSON object with members: + + ``prefix`` + A JSON object specifying the installation prefix. It has one member: + + ``path`` + A string specifying the value of :variable:`CMAKE_INSTALL_PREFIX`. + + ``destinations`` + A JSON array of entries specifying an install destination path. + Each entry is a JSON object with members: + + ``path`` + A string specifying the install destination path. The path may + be absolute or relative to the install prefix. + + ``backtrace`` + Optional member that is present when a CMake language backtrace to + the :command:`install` command invocation that specified this + destination is available. The value is an unsigned integer 0-based + index into the ``backtraceGraph`` member's ``nodes`` array. + +``link`` + Optional member that is present for executables and shared library + targets that link into a runtime binary. The value is a JSON object + with members describing the link step: + + ``language`` + A string specifying the language (e.g. ``C``, ``CXX``, ``Fortran``) + of the toolchain is used to invoke the linker. + + ``commandFragments`` + Optional member that is present when fragments of the link command + line invocation are available. The value is a JSON array of entries + specifying ordered fragments. Each entry is a JSON object with members: + + ``fragment`` + A string specifying a fragment of the link command line invocation. + The value is encoded in the build system's native shell format. + + ``role`` + A string specifying the role of the fragment's content: + + * ``flags``: link flags. + * ``libraries``: link library file paths or flags. + * ``libraryPath``: library search path flags. + * ``frameworkPath``: macOS framework search path flags. + + ``lto`` + Optional member that is present with boolean value ``true`` + when link-time optimization (a.k.a. interprocedural optimization + or link-time code generation) is enabled. + + ``sysroot`` + Optional member that is present when the :variable:`CMAKE_SYSROOT_LINK` + or :variable:`CMAKE_SYSROOT` variable is defined. The value is a + JSON object with one member: + + ``path`` + A string specifying the absolute path to the sysroot, represented + with forward slashes. + +``archive`` + Optional member that is present for static library targets. The value + is a JSON object with members describing the archive step: + + ``commandFragments`` + Optional member that is present when fragments of the archiver command + line invocation are available. The value is a JSON array of entries + specifying the fragments. Each entry is a JSON object with members: + + ``fragment`` + A string specifying a fragment of the archiver command line invocation. + The value is encoded in the build system's native shell format. + + ``role`` + A string specifying the role of the fragment's content: + + * ``flags``: archiver flags. + + ``lto`` + Optional member that is present with boolean value ``true`` + when link-time optimization (a.k.a. interprocedural optimization + or link-time code generation) is enabled. + +``dependencies`` + Optional member that is present when the target depends on other targets. + The value is a JSON array of entries corresponding to the dependencies. + Each entry is a JSON object with members: + + ``id`` + A string uniquely identifying the target on which this target depends. + This matches the main ``id`` member of the other target. + + ``backtrace`` + Optional member that is present when a CMake language backtrace to + the :command:`add_dependencies`, :command:`target_link_libraries`, + or other command invocation that created this dependency is + available. The value is an unsigned integer 0-based index into + the ``backtraceGraph`` member's ``nodes`` array. + +``sources`` + A JSON array of entries corresponding to the target's source files. + Each entry is a JSON object with members: + + ``path`` + A string specifying the path to the source file on disk, represented + with forward slashes. If the file is inside the top-level source + directory then the path is specified relative to that directory. + Otherwise the path is absolute. + + ``compileGroupIndex`` + Optional member that is present when the source is compiled. + The value is an unsigned integer 0-based index into the + ``compileGroups`` array. + + ``sourceGroupIndex`` + Optional member that is present when the source is part of a source + group either via the :command:`source_group` command or by default. + The value is an unsigned integer 0-based index into the + ``sourceGroups`` array. + + ``isGenerated`` + Optional member that is present with boolean value ``true`` if + the source is :prop_sf:`GENERATED`. + + ``backtrace`` + Optional member that is present when a CMake language backtrace to + the :command:`target_sources`, :command:`add_executable`, + :command:`add_library`, :command:`add_custom_target`, or other + command invocation that added this source to the target is + available. The value is an unsigned integer 0-based index into + the ``backtraceGraph`` member's ``nodes`` array. + +``sourceGroups`` + Optional member that is present when sources are grouped together by + the :command:`source_group` command or by default. The value is a + JSON array of entries corresponding to the groups. Each entry is + a JSON object with members: + + ``name`` + A string specifying the name of the source group. + + ``sourceIndexes`` + A JSON array listing the sources belonging to the group. + Each entry is an unsigned integer 0-based index into the + main ``sources`` array for the target. + +``compileGroups`` + Optional member that is present when the target has sources that compile. + The value is a JSON array of entries corresponding to groups of sources + that all compile with the same settings. Each entry is a JSON object + with members: + + ``sourceIndexes`` + A JSON array listing the sources belonging to the group. + Each entry is an unsigned integer 0-based index into the + main ``sources`` array for the target. + + ``language`` + A string specifying the language (e.g. ``C``, ``CXX``, ``Fortran``) + of the toolchain is used to compile the source file. + + ``compileCommandFragments`` + Optional member that is present when fragments of the compiler command + line invocation are available. The value is a JSON array of entries + specifying ordered fragments. Each entry is a JSON object with + one member: + + ``fragment`` + A string specifying a fragment of the compile command line invocation. + The value is encoded in the build system's native shell format. + + ``includes`` + Optional member that is present when there are include directories. + The value is a JSON array with an entry for each directory. Each + entry is a JSON object with members: + + ``path`` + A string specifying the path to the include directory, + represented with forward slashes. + + ``isSystem`` + Optional member that is present with boolean value ``true`` if + the include directory is marked as a system include directory. + + ``backtrace`` + Optional member that is present when a CMake language backtrace to + the :command:`target_include_directories` or other command invocation + that added this include directory is available. The value is + an unsigned integer 0-based index into the ``backtraceGraph`` + member's ``nodes`` array. + + ``defines`` + Optional member that is present when there are preprocessor definitions. + The value is a JSON array with an entry for each definition. Each + entry is a JSON object with members: + + ``define`` + A string specifying the preprocessor definition in the format + ``<name>[=<value>]``, e.g. ``DEF`` or ``DEF=1``. + + ``backtrace`` + Optional member that is present when a CMake language backtrace to + the :command:`target_compile_definitions` or other command invocation + that added this preprocessor definition is available. The value is + an unsigned integer 0-based index into the ``backtraceGraph`` + member's ``nodes`` array. + + ``sysroot`` + Optional member that is present when the + :variable:`CMAKE_SYSROOT_COMPILE` or :variable:`CMAKE_SYSROOT` + variable is defined. The value is a JSON object with one member: + + ``path`` + A string specifying the absolute path to the sysroot, represented + with forward slashes. + +``backtraceGraph`` + A JSON object describing the graph of backtraces whose nodes are + referenced from ``backtrace`` members elsewhere. The members are: + + ``nodes`` + A JSON array listing nodes in the backtrace graph. Each entry + is a JSON object with members: + + ``file`` + An unsigned integer 0-based index into the backtrace ``files`` array. + + ``line`` + An optional member present when the node represents a line within + the file. The value is an unsigned integer 1-based line number. + + ``command`` + An optional member present when the node represents a command + invocation within the file. The value is an unsigned integer + 0-based index into the backtrace ``commands`` array. + + ``parent`` + An optional member present when the node is not the bottom of + the call stack. The value is an unsigned integer 0-based index + of another entry in the backtrace ``nodes`` array. + + ``commands`` + A JSON array listing command names referenced by backtrace nodes. + Each entry is a string specifying a command name. + + ``files`` + A JSON array listing CMake language files referenced by backtrace nodes. + Each entry is a string specifying the path to a file, represented + with forward slashes. If the file is inside the top-level source + directory then the path is specified relative to that directory. + Otherwise the path is absolute. diff --git a/Source/CMakeLists.txt b/Source/CMakeLists.txt index ec71fe0..e672eab 100644 --- a/Source/CMakeLists.txt +++ b/Source/CMakeLists.txt @@ -209,6 +209,8 @@ set(SRCS cmExtraSublimeTextGenerator.h cmFileAPI.cxx cmFileAPI.h + cmFileAPICodemodel.cxx + cmFileAPICodemodel.h cmFileLock.cxx cmFileLock.h cmFileLockPool.cxx diff --git a/Source/cmFileAPI.cxx b/Source/cmFileAPI.cxx index 1a41370..b63349a 100644 --- a/Source/cmFileAPI.cxx +++ b/Source/cmFileAPI.cxx @@ -4,6 +4,7 @@ #include "cmAlgorithms.h" #include "cmCryptoHash.h" +#include "cmFileAPICodemodel.h" #include "cmGlobalGenerator.h" #include "cmSystemTools.h" #include "cmTimestamp.h" @@ -224,6 +225,17 @@ bool cmFileAPI::ReadQuery(std::string const& query, } 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::InternalTest)) { Object o; o.Kind = ObjectKind::InternalTest; @@ -361,7 +373,8 @@ const char* cmFileAPI::ObjectKindName(ObjectKind kind) { // Keep in sync with ObjectKind enum. static const char* objectKindNames[] = { - "__test" // + "codemodel", // + "__test" // }; return objectKindNames[size_t(kind)]; } @@ -379,6 +392,9 @@ Json::Value cmFileAPI::BuildObject(Object const& object) Json::Value value; switch (object.Kind) { + case ObjectKind::CodeModel: + value = this->BuildCodeModel(object); + break; case ObjectKind::InternalTest: value = this->BuildInternalTest(object); break; @@ -429,7 +445,9 @@ cmFileAPI::ClientRequest cmFileAPI::BuildClientRequest( } std::string const& kindName = kind.asString(); - if (kindName == this->ObjectKindName(ObjectKind::InternalTest)) { + if (kindName == this->ObjectKindName(ObjectKind::CodeModel)) { + r.Kind = ObjectKind::CodeModel; + } else if (kindName == this->ObjectKindName(ObjectKind::InternalTest)) { r.Kind = ObjectKind::InternalTest; } else { r.Error = "unknown request kind '" + kindName + "'"; @@ -447,6 +465,9 @@ cmFileAPI::ClientRequest cmFileAPI::BuildClientRequest( } switch (r.Kind) { + case ObjectKind::CodeModel: + this->BuildClientRequestCodeModel(r, versions); + break; case ObjectKind::InternalTest: this->BuildClientRequestInternalTest(r, versions); break; @@ -592,6 +613,42 @@ std::string cmFileAPI::NoSupportedVersion( return msg.str(); } +// The "codemodel" object kind. + +static unsigned int const CodeModelV2Minor = 0; + +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) +{ + using namespace std::placeholders; + Json::Value codemodel = cmFileAPICodemodelDump(*this, object.Version); + codemodel["kind"] = this->ObjectKindName(object.Kind); + + Json::Value& version = codemodel["version"] = Json::objectValue; + if (object.Version == 2) { + version["major"] = 2; + version["minor"] = CodeModelV2Minor; + } else { + return codemodel; // should be unreachable + } + + return codemodel; +} + // The "__test" object kind is for internal testing of CMake. static unsigned int const InternalTestV1Minor = 3; diff --git a/Source/cmFileAPI.h b/Source/cmFileAPI.h index 8bfae31..5339ba7 100644 --- a/Source/cmFileAPI.h +++ b/Source/cmFileAPI.h @@ -51,6 +51,7 @@ private: // Keep in sync with ObjectKindName. enum class ObjectKind { + CodeModel, InternalTest }; @@ -181,6 +182,10 @@ private: static std::string NoSupportedVersion( std::vector<RequestVersion> const& versions); + void BuildClientRequestCodeModel( + ClientRequest& r, std::vector<RequestVersion> const& versions); + Json::Value BuildCodeModel(Object const& object); + void BuildClientRequestInternalTest( ClientRequest& r, std::vector<RequestVersion> const& versions); Json::Value BuildInternalTest(Object const& object); diff --git a/Source/cmFileAPICodemodel.cxx b/Source/cmFileAPICodemodel.cxx new file mode 100644 index 0000000..3d4a7cf --- /dev/null +++ b/Source/cmFileAPICodemodel.cxx @@ -0,0 +1,1111 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ +#include "cmFileAPICodemodel.h" + +#include "cmCryptoHash.h" +#include "cmFileAPI.h" +#include "cmGeneratorExpression.h" +#include "cmGeneratorTarget.h" +#include "cmGlobalGenerator.h" +#include "cmInstallGenerator.h" +#include "cmInstallTargetGenerator.h" +#include "cmLinkLineComputer.h" +#include "cmListFileCache.h" +#include "cmLocalGenerator.h" +#include "cmMakefile.h" +#include "cmSourceFile.h" +#include "cmSourceGroup.h" +#include "cmState.h" +#include "cmStateDirectory.h" +#include "cmStateSnapshot.h" +#include "cmStateTypes.h" +#include "cmSystemTools.h" +#include "cmTarget.h" +#include "cmTargetDepend.h" +#include "cmake.h" + +#include "cm_jsoncpp_value.h" + +#include <algorithm> +#include <cassert> +#include <map> +#include <set> +#include <string> +#include <unordered_map> +#include <utility> +#include <vector> + +namespace { + +class Codemodel +{ + cmFileAPI& FileAPI; + unsigned long Version; + + Json::Value DumpPaths(); + Json::Value DumpConfigurations(); + Json::Value DumpConfiguration(std::string const& config); + +public: + Codemodel(cmFileAPI& fileAPI, unsigned long version); + Json::Value Dump(); +}; + +class CodemodelConfig +{ + cmFileAPI& FileAPI; + unsigned long Version; + std::string const& Config; + std::string TopSource; + std::string TopBuild; + + struct Directory + { + cmStateSnapshot Snapshot; + Json::Value TargetIndexes = Json::arrayValue; + }; + std::map<cmStateSnapshot, Json::ArrayIndex, cmStateSnapshot::StrictWeakOrder> + DirectoryMap; + std::vector<Directory> Directories; + + void ProcessDirectories(); + + Json::ArrayIndex GetDirectoryIndex(cmLocalGenerator const* lg); + Json::ArrayIndex GetDirectoryIndex(cmStateSnapshot s); + + Json::Value DumpTargets(); + Json::Value DumpTarget(cmGeneratorTarget* gt, Json::ArrayIndex ti); + + Json::Value DumpDirectories(); + Json::Value DumpDirectory(Directory& d); + +public: + CodemodelConfig(cmFileAPI& fileAPI, unsigned long version, + std::string const& config); + Json::Value Dump(); +}; + +std::string RelativeIfUnder(std::string const& top, std::string const& in) +{ + std::string out; + if (in == top) { + out = "."; + } else if (cmSystemTools::IsSubDirectory(in, top)) { + out = in.substr(top.size() + 1); + } else { + out = in; + } + return out; +} + +std::string TargetId(cmGeneratorTarget const* gt, std::string const& topBuild) +{ + cmCryptoHash hasher(cmCryptoHash::AlgoSHA3_256); + std::string path = RelativeIfUnder( + topBuild, gt->GetLocalGenerator()->GetCurrentBinaryDirectory()); + std::string hash = hasher.HashString(path); + hash.resize(20, '0'); + return gt->GetName() + CMAKE_DIRECTORY_ID_SEP + hash; +} + +class BacktraceData +{ + std::string TopSource; + std::unordered_map<std::string, Json::ArrayIndex> CommandMap; + std::unordered_map<std::string, Json::ArrayIndex> FileMap; + std::unordered_map<cmListFileContext const*, Json::ArrayIndex> NodeMap; + Json::Value Commands = Json::arrayValue; + Json::Value Files = Json::arrayValue; + Json::Value Nodes = Json::arrayValue; + + Json::ArrayIndex AddCommand(std::string const& command) + { + auto i = this->CommandMap.find(command); + if (i == this->CommandMap.end()) { + auto cmdIndex = static_cast<Json::ArrayIndex>(this->Commands.size()); + i = this->CommandMap.emplace(command, cmdIndex).first; + this->Commands.append(command); + } + return i->second; + } + + Json::ArrayIndex AddFile(std::string const& file) + { + auto i = this->FileMap.find(file); + if (i == this->FileMap.end()) { + auto fileIndex = static_cast<Json::ArrayIndex>(this->Files.size()); + i = this->FileMap.emplace(file, fileIndex).first; + this->Files.append(RelativeIfUnder(this->TopSource, file)); + } + return i->second; + } + +public: + BacktraceData(std::string const& topSource); + bool Add(cmListFileBacktrace const& bt, Json::ArrayIndex& index); + Json::Value Dump(); +}; + +BacktraceData::BacktraceData(std::string const& topSource) + : TopSource(topSource) +{ +} + +bool BacktraceData::Add(cmListFileBacktrace const& bt, Json::ArrayIndex& index) +{ + if (bt.Empty()) { + return false; + } + cmListFileContext const* top = &bt.Top(); + auto found = this->NodeMap.find(top); + if (found != this->NodeMap.end()) { + index = found->second; + return true; + } + Json::Value entry = Json::objectValue; + entry["file"] = this->AddFile(top->FilePath); + if (top->Line) { + entry["line"] = static_cast<int>(top->Line); + } + if (!top->Name.empty()) { + entry["command"] = this->AddCommand(top->Name); + } + Json::ArrayIndex parent; + if (this->Add(bt.Pop(), parent)) { + entry["parent"] = parent; + } + index = this->NodeMap[top] = this->Nodes.size(); + this->Nodes.append(std::move(entry)); // NOLINT(*) + return true; +} + +Json::Value BacktraceData::Dump() +{ + Json::Value backtraceGraph; + this->CommandMap.clear(); + this->FileMap.clear(); + this->NodeMap.clear(); + backtraceGraph["commands"] = std::move(this->Commands); + backtraceGraph["files"] = std::move(this->Files); + backtraceGraph["nodes"] = std::move(this->Nodes); + return backtraceGraph; +} + +struct CompileData +{ + struct IncludeEntry + { + BT<std::string> Path; + bool IsSystem = false; + IncludeEntry(BT<std::string> path, bool isSystem) + : Path(std::move(path)) + , IsSystem(isSystem) + { + } + }; + + void SetDefines(std::set<BT<std::string>> const& defines); + + std::string Language; + std::string Sysroot; + std::vector<BT<std::string>> Flags; + std::vector<BT<std::string>> Defines; + std::vector<IncludeEntry> Includes; +}; + +void CompileData::SetDefines(std::set<BT<std::string>> const& defines) +{ + this->Defines.reserve(defines.size()); + for (BT<std::string> const& d : defines) { + this->Defines.push_back(d); + } +} + +class Target +{ + cmGeneratorTarget* GT; + std::string const& Config; + std::string TopSource; + std::string TopBuild; + std::vector<cmSourceGroup> SourceGroupsLocal; + BacktraceData Backtraces; + + std::map<std::string, CompileData> CompileDataMap; + + std::unordered_map<cmSourceFile const*, Json::ArrayIndex> SourceMap; + Json::Value Sources = Json::arrayValue; + + struct SourceGroup + { + std::string Name; + Json::Value SourceIndexes = Json::arrayValue; + }; + std::unordered_map<cmSourceGroup const*, Json::ArrayIndex> SourceGroupsMap; + std::vector<SourceGroup> SourceGroups; + + struct CompileGroup + { + std::map<Json::Value, Json::ArrayIndex>::iterator Entry; + Json::Value SourceIndexes = Json::arrayValue; + }; + std::map<Json::Value, Json::ArrayIndex> CompileGroupMap; + std::vector<CompileGroup> CompileGroups; + + void ProcessLanguages(); + void ProcessLanguage(std::string const& lang); + + Json::ArrayIndex AddSourceGroup(cmSourceGroup* sg, Json::ArrayIndex si); + CompileData BuildCompileData(cmSourceFile* sf); + Json::ArrayIndex AddSourceCompileGroup(cmSourceFile* sf, + Json::ArrayIndex si); + void AddBacktrace(Json::Value& object, cmListFileBacktrace const& bt); + Json::Value DumpPaths(); + Json::Value DumpCompileData(CompileData cd); + Json::Value DumpInclude(CompileData::IncludeEntry const& inc); + Json::Value DumpDefine(BT<std::string> const& def); + Json::Value DumpSources(); + Json::Value DumpSource(cmGeneratorTarget::SourceAndKind const& sk, + Json::ArrayIndex si); + Json::Value DumpSourceGroups(); + Json::Value DumpSourceGroup(SourceGroup& sg); + Json::Value DumpCompileGroups(); + Json::Value DumpCompileGroup(CompileGroup& cg); + Json::Value DumpSysroot(std::string const& path); + Json::Value DumpInstall(); + Json::Value DumpInstallPrefix(); + Json::Value DumpInstallDestinations(); + Json::Value DumpInstallDestination(cmInstallTargetGenerator* itGen); + Json::Value DumpArtifacts(); + Json::Value DumpLink(); + Json::Value DumpArchive(); + Json::Value DumpLinkCommandFragments(); + Json::Value DumpCommandFragments(std::vector<BT<std::string>> const& frags); + Json::Value DumpCommandFragment(BT<std::string> const& frag, + std::string const& role = std::string()); + Json::Value DumpDependencies(); + Json::Value DumpDependency(cmTargetDepend const& td); + Json::Value DumpFolder(); + +public: + Target(cmGeneratorTarget* gt, std::string const& config); + Json::Value Dump(); +}; + +Codemodel::Codemodel(cmFileAPI& fileAPI, unsigned long version) + : FileAPI(fileAPI) + , Version(version) +{ +} + +Json::Value Codemodel::Dump() +{ + Json::Value codemodel = Json::objectValue; + + codemodel["paths"] = this->DumpPaths(); + codemodel["configurations"] = this->DumpConfigurations(); + + return codemodel; +} + +Json::Value Codemodel::DumpPaths() +{ + Json::Value paths = Json::objectValue; + paths["source"] = this->FileAPI.GetCMakeInstance()->GetHomeDirectory(); + paths["build"] = this->FileAPI.GetCMakeInstance()->GetHomeOutputDirectory(); + return paths; +} + +Json::Value Codemodel::DumpConfigurations() +{ + std::vector<std::string> configs; + cmGlobalGenerator* gg = + this->FileAPI.GetCMakeInstance()->GetGlobalGenerator(); + auto makefiles = gg->GetMakefiles(); + if (!makefiles.empty()) { + makefiles[0]->GetConfigurations(configs); + if (configs.empty()) { + configs.emplace_back(); + } + } + Json::Value configurations = Json::arrayValue; + for (std::string const& config : configs) { + configurations.append(this->DumpConfiguration(config)); + } + return configurations; +} + +Json::Value Codemodel::DumpConfiguration(std::string const& config) +{ + CodemodelConfig configuration(this->FileAPI, this->Version, config); + return configuration.Dump(); +} + +CodemodelConfig::CodemodelConfig(cmFileAPI& fileAPI, unsigned long version, + std::string const& config) + : FileAPI(fileAPI) + , Version(version) + , Config(config) + , TopSource(this->FileAPI.GetCMakeInstance()->GetHomeDirectory()) + , TopBuild(this->FileAPI.GetCMakeInstance()->GetHomeOutputDirectory()) +{ + static_cast<void>(this->Version); +} + +Json::Value CodemodelConfig::Dump() +{ + Json::Value configuration = Json::objectValue; + configuration["name"] = this->Config; + this->ProcessDirectories(); + configuration["targets"] = this->DumpTargets(); + configuration["directories"] = this->DumpDirectories(); + return configuration; +} + +void CodemodelConfig::ProcessDirectories() +{ + cmGlobalGenerator* gg = + this->FileAPI.GetCMakeInstance()->GetGlobalGenerator(); + std::vector<cmLocalGenerator*> const& localGens = gg->GetLocalGenerators(); + + // Add directories in forward order to process parents before children. + this->Directories.reserve(localGens.size()); + for (cmLocalGenerator* lg : localGens) { + auto directoryIndex = + static_cast<Json::ArrayIndex>(this->Directories.size()); + this->Directories.emplace_back(); + Directory& d = this->Directories[directoryIndex]; + d.Snapshot = lg->GetStateSnapshot().GetBuildsystemDirectory(); + this->DirectoryMap[d.Snapshot] = directoryIndex; + } +} + +Json::ArrayIndex CodemodelConfig::GetDirectoryIndex(cmLocalGenerator const* lg) +{ + return this->GetDirectoryIndex( + lg->GetStateSnapshot().GetBuildsystemDirectory()); +} + +Json::ArrayIndex CodemodelConfig::GetDirectoryIndex(cmStateSnapshot s) +{ + auto i = this->DirectoryMap.find(s); + assert(i != this->DirectoryMap.end()); + return i->second; +} + +Json::Value CodemodelConfig::DumpTargets() +{ + Json::Value targets = Json::arrayValue; + + std::vector<cmGeneratorTarget*> targetList; + cmGlobalGenerator* gg = + this->FileAPI.GetCMakeInstance()->GetGlobalGenerator(); + for (cmLocalGenerator const* lg : gg->GetLocalGenerators()) { + std::vector<cmGeneratorTarget*> const& list = lg->GetGeneratorTargets(); + targetList.insert(targetList.end(), list.begin(), list.end()); + } + std::sort(targetList.begin(), targetList.end(), + [](cmGeneratorTarget* l, cmGeneratorTarget* r) { + return l->GetName() < r->GetName(); + }); + + for (cmGeneratorTarget* gt : targetList) { + if (gt->GetType() == cmStateEnums::GLOBAL_TARGET || + gt->GetType() == cmStateEnums::INTERFACE_LIBRARY) { + continue; + } + + targets.append(this->DumpTarget(gt, targets.size())); + } + + return targets; +} + +Json::Value CodemodelConfig::DumpTarget(cmGeneratorTarget* gt, + Json::ArrayIndex ti) +{ + Target t(gt, this->Config); + std::string prefix = "target-" + gt->GetName(); + if (!this->Config.empty()) { + prefix += "-" + this->Config; + } + Json::Value target = this->FileAPI.MaybeJsonFile(t.Dump(), prefix); + target["name"] = gt->GetName(); + target["id"] = TargetId(gt, this->TopBuild); + + // Cross-reference directory containing target. + Json::ArrayIndex di = this->GetDirectoryIndex(gt->GetLocalGenerator()); + target["directoryIndex"] = di; + this->Directories[di].TargetIndexes.append(ti); + + return target; +} + +Json::Value CodemodelConfig::DumpDirectories() +{ + Json::Value directories = Json::arrayValue; + for (Directory& d : this->Directories) { + directories.append(this->DumpDirectory(d)); + } + return directories; +} + +Json::Value CodemodelConfig::DumpDirectory(Directory& d) +{ + Json::Value directory = Json::objectValue; + + std::string sourceDir = d.Snapshot.GetDirectory().GetCurrentSource(); + directory["source"] = RelativeIfUnder(this->TopSource, sourceDir); + + std::string buildDir = d.Snapshot.GetDirectory().GetCurrentBinary(); + directory["build"] = RelativeIfUnder(this->TopBuild, buildDir); + + cmStateSnapshot parentDir = d.Snapshot.GetBuildsystemDirectoryParent(); + if (parentDir.IsValid()) { + directory["parentIndex"] = this->GetDirectoryIndex(parentDir); + } + + Json::Value childIndexes = Json::arrayValue; + for (cmStateSnapshot const& child : d.Snapshot.GetChildren()) { + childIndexes.append( + this->GetDirectoryIndex(child.GetBuildsystemDirectory())); + } + if (!childIndexes.empty()) { + directory["childIndexes"] = std::move(childIndexes); + } + + if (!d.TargetIndexes.empty()) { + directory["targetIndexes"] = std::move(d.TargetIndexes); + } + + return directory; +} + +Target::Target(cmGeneratorTarget* gt, std::string const& config) + : GT(gt) + , Config(config) + , TopSource(gt->GetGlobalGenerator()->GetCMakeInstance()->GetHomeDirectory()) + , TopBuild( + gt->GetGlobalGenerator()->GetCMakeInstance()->GetHomeOutputDirectory()) + , SourceGroupsLocal(this->GT->Makefile->GetSourceGroups()) + , Backtraces(this->TopSource) +{ +} + +Json::Value Target::Dump() +{ + Json::Value target = Json::objectValue; + + cmStateEnums::TargetType const type = this->GT->GetType(); + + target["name"] = this->GT->GetName(); + target["type"] = cmState::GetTargetTypeName(type); + target["id"] = TargetId(this->GT, this->TopBuild); + target["paths"] = this->DumpPaths(); + if (this->GT->Target->GetIsGeneratorProvided()) { + target["isGeneratorProvided"] = true; + } + + this->AddBacktrace(target, this->GT->GetBacktrace()); + + if (this->GT->Target->GetHaveInstallRule()) { + target["install"] = this->DumpInstall(); + } + + if (this->GT->HaveWellDefinedOutputFiles()) { + Json::Value artifacts = this->DumpArtifacts(); + if (!artifacts.empty()) { + target["artifacts"] = std::move(artifacts); + } + } + + if (type == cmStateEnums::EXECUTABLE || + type == cmStateEnums::SHARED_LIBRARY || + type == cmStateEnums::MODULE_LIBRARY) { + target["nameOnDisk"] = this->GT->GetFullName(this->Config); + target["link"] = this->DumpLink(); + } else if (type == cmStateEnums::STATIC_LIBRARY) { + target["nameOnDisk"] = this->GT->GetFullName(this->Config); + target["archive"] = this->DumpArchive(); + } + + Json::Value dependencies = this->DumpDependencies(); + if (!dependencies.empty()) { + target["dependencies"] = dependencies; + } + + { + this->ProcessLanguages(); + + target["sources"] = this->DumpSources(); + + Json::Value folder = this->DumpFolder(); + if (!folder.isNull()) { + target["folder"] = std::move(folder); + } + + Json::Value sourceGroups = this->DumpSourceGroups(); + if (!sourceGroups.empty()) { + target["sourceGroups"] = std::move(sourceGroups); + } + + Json::Value compileGroups = this->DumpCompileGroups(); + if (!compileGroups.empty()) { + target["compileGroups"] = std::move(compileGroups); + } + } + + target["backtraceGraph"] = this->Backtraces.Dump(); + + return target; +} + +void Target::ProcessLanguages() +{ + std::set<std::string> languages; + this->GT->GetLanguages(languages, this->Config); + for (std::string const& lang : languages) { + this->ProcessLanguage(lang); + } +} + +void Target::ProcessLanguage(std::string const& lang) +{ + CompileData& cd = this->CompileDataMap[lang]; + cd.Language = lang; + if (const char* sysrootCompile = + this->GT->Makefile->GetDefinition("CMAKE_SYSROOT_COMPILE")) { + cd.Sysroot = sysrootCompile; + } else if (const char* sysroot = + this->GT->Makefile->GetDefinition("CMAKE_SYSROOT")) { + cd.Sysroot = sysroot; + } + cmLocalGenerator* lg = this->GT->GetLocalGenerator(); + { + // FIXME: Add flags from end section of ExpandRuleVariable, + // which may need to be factored out. + std::string flags; + lg->GetTargetCompileFlags(this->GT, this->Config, lang, flags); + cd.Flags.emplace_back(std::move(flags), cmListFileBacktrace()); + } + std::set<BT<std::string>> defines = + lg->GetTargetDefines(this->GT, this->Config, lang); + cd.SetDefines(defines); + std::vector<BT<std::string>> includePathList = + lg->GetIncludeDirectories(this->GT, lang, this->Config, true); + for (BT<std::string> const& i : includePathList) { + cd.Includes.emplace_back( + i, this->GT->IsSystemIncludeDirectory(i.Value, this->Config, lang)); + } +} + +Json::ArrayIndex Target::AddSourceGroup(cmSourceGroup* sg, Json::ArrayIndex si) +{ + std::unordered_map<cmSourceGroup const*, Json::ArrayIndex>::iterator i = + this->SourceGroupsMap.find(sg); + if (i == this->SourceGroupsMap.end()) { + auto sgIndex = static_cast<Json::ArrayIndex>(this->SourceGroups.size()); + i = this->SourceGroupsMap.emplace(sg, sgIndex).first; + SourceGroup g; + g.Name = sg->GetFullName(); + this->SourceGroups.push_back(std::move(g)); + } + this->SourceGroups[i->second].SourceIndexes.append(si); + return i->second; +} + +CompileData Target::BuildCompileData(cmSourceFile* sf) +{ + CompileData fd; + + fd.Language = sf->GetLanguage(); + if (fd.Language.empty()) { + return fd; + } + CompileData const& cd = this->CompileDataMap.at(fd.Language); + + fd.Sysroot = cd.Sysroot; + + cmLocalGenerator* lg = this->GT->GetLocalGenerator(); + cmGeneratorExpressionInterpreter genexInterpreter(lg, this->Config, this->GT, + fd.Language); + + fd.Flags = cd.Flags; + const std::string COMPILE_FLAGS("COMPILE_FLAGS"); + if (const char* cflags = sf->GetProperty(COMPILE_FLAGS)) { + std::string flags = genexInterpreter.Evaluate(cflags, COMPILE_FLAGS); + fd.Flags.emplace_back(std::move(flags), cmListFileBacktrace()); + } + const std::string COMPILE_OPTIONS("COMPILE_OPTIONS"); + if (const char* coptions = sf->GetProperty(COMPILE_OPTIONS)) { + std::string flags; + lg->AppendCompileOptions( + flags, genexInterpreter.Evaluate(coptions, COMPILE_OPTIONS)); + fd.Flags.emplace_back(std::move(flags), cmListFileBacktrace()); + } + + // Add include directories from source file properties. + { + std::vector<std::string> includes; + const std::string INCLUDE_DIRECTORIES("INCLUDE_DIRECTORIES"); + if (const char* cincludes = sf->GetProperty(INCLUDE_DIRECTORIES)) { + const std::string& evaluatedIncludes = + genexInterpreter.Evaluate(cincludes, INCLUDE_DIRECTORIES); + lg->AppendIncludeDirectories(includes, evaluatedIncludes, *sf); + + for (std::string const& include : includes) { + bool const isSystemInclude = this->GT->IsSystemIncludeDirectory( + include, this->Config, fd.Language); + fd.Includes.emplace_back(include, isSystemInclude); + } + } + } + fd.Includes.insert(fd.Includes.end(), cd.Includes.begin(), + cd.Includes.end()); + + const std::string COMPILE_DEFINITIONS("COMPILE_DEFINITIONS"); + std::set<std::string> fileDefines; + if (const char* defs = sf->GetProperty(COMPILE_DEFINITIONS)) { + lg->AppendDefines(fileDefines, + genexInterpreter.Evaluate(defs, COMPILE_DEFINITIONS)); + } + + const std::string defPropName = + "COMPILE_DEFINITIONS_" + cmSystemTools::UpperCase(this->Config); + if (const char* config_defs = sf->GetProperty(defPropName)) { + lg->AppendDefines( + fileDefines, + genexInterpreter.Evaluate(config_defs, COMPILE_DEFINITIONS)); + } + + std::set<BT<std::string>> defines; + defines.insert(fileDefines.begin(), fileDefines.end()); + defines.insert(cd.Defines.begin(), cd.Defines.end()); + + fd.SetDefines(defines); + + return fd; +} + +Json::ArrayIndex Target::AddSourceCompileGroup(cmSourceFile* sf, + Json::ArrayIndex si) +{ + Json::Value compileDataJson = + this->DumpCompileData(this->BuildCompileData(sf)); + std::map<Json::Value, Json::ArrayIndex>::iterator i = + this->CompileGroupMap.find(compileDataJson); + if (i == this->CompileGroupMap.end()) { + Json::ArrayIndex cgIndex = + static_cast<Json::ArrayIndex>(this->CompileGroups.size()); + i = + this->CompileGroupMap.emplace(std::move(compileDataJson), cgIndex).first; + CompileGroup g; + g.Entry = i; + this->CompileGroups.push_back(std::move(g)); + } + this->CompileGroups[i->second].SourceIndexes.append(si); + return i->second; +} + +void Target::AddBacktrace(Json::Value& object, cmListFileBacktrace const& bt) +{ + Json::ArrayIndex backtrace; + if (this->Backtraces.Add(bt, backtrace)) { + object["backtrace"] = backtrace; + } +} + +Json::Value Target::DumpPaths() +{ + Json::Value paths = Json::objectValue; + cmLocalGenerator* lg = this->GT->GetLocalGenerator(); + + std::string const& sourceDir = lg->GetCurrentSourceDirectory(); + paths["source"] = RelativeIfUnder(this->TopSource, sourceDir); + + std::string const& buildDir = lg->GetCurrentBinaryDirectory(); + paths["build"] = RelativeIfUnder(this->TopBuild, buildDir); + + return paths; +} + +Json::Value Target::DumpSources() +{ + Json::Value sources = Json::arrayValue; + cmGeneratorTarget::KindedSources const& kinded = + this->GT->GetKindedSources(this->Config); + for (cmGeneratorTarget::SourceAndKind const& sk : kinded.Sources) { + sources.append(this->DumpSource(sk, sources.size())); + } + return sources; +} + +Json::Value Target::DumpSource(cmGeneratorTarget::SourceAndKind const& sk, + Json::ArrayIndex si) +{ + Json::Value source = Json::objectValue; + + std::string const path = sk.Source.Value->GetFullPath(); + source["path"] = RelativeIfUnder(this->TopSource, path); + if (sk.Source.Value->GetPropertyAsBool("GENERATED")) { + source["isGenerated"] = true; + } + this->AddBacktrace(source, sk.Source.Backtrace); + + if (cmSourceGroup* sg = + this->GT->Makefile->FindSourceGroup(path, this->SourceGroupsLocal)) { + source["sourceGroupIndex"] = this->AddSourceGroup(sg, si); + } + + switch (sk.Kind) { + case cmGeneratorTarget::SourceKindObjectSource: { + source["compileGroupIndex"] = + this->AddSourceCompileGroup(sk.Source.Value, si); + } break; + case cmGeneratorTarget::SourceKindAppManifest: + case cmGeneratorTarget::SourceKindCertificate: + case cmGeneratorTarget::SourceKindCustomCommand: + case cmGeneratorTarget::SourceKindExternalObject: + case cmGeneratorTarget::SourceKindExtra: + case cmGeneratorTarget::SourceKindHeader: + case cmGeneratorTarget::SourceKindIDL: + case cmGeneratorTarget::SourceKindManifest: + case cmGeneratorTarget::SourceKindModuleDefinition: + case cmGeneratorTarget::SourceKindResx: + case cmGeneratorTarget::SourceKindXaml: + break; + } + + return source; +} + +Json::Value Target::DumpCompileData(CompileData cd) +{ + Json::Value result = Json::objectValue; + + if (!cd.Language.empty()) { + result["language"] = cd.Language; + } + if (!cd.Sysroot.empty()) { + result["sysroot"] = this->DumpSysroot(cd.Sysroot); + } + if (!cd.Flags.empty()) { + result["compileCommandFragments"] = this->DumpCommandFragments(cd.Flags); + } + if (!cd.Includes.empty()) { + Json::Value includes = Json::arrayValue; + for (auto const& i : cd.Includes) { + includes.append(this->DumpInclude(i)); + } + result["includes"] = includes; + } + if (!cd.Defines.empty()) { + Json::Value defines = Json::arrayValue; + for (BT<std::string> const& d : cd.Defines) { + defines.append(this->DumpDefine(d)); + } + result["defines"] = std::move(defines); + } + + return result; +} + +Json::Value Target::DumpInclude(CompileData::IncludeEntry const& inc) +{ + Json::Value include = Json::objectValue; + include["path"] = inc.Path.Value; + if (inc.IsSystem) { + include["isSystem"] = true; + } + this->AddBacktrace(include, inc.Path.Backtrace); + return include; +} + +Json::Value Target::DumpDefine(BT<std::string> const& def) +{ + Json::Value define = Json::objectValue; + define["define"] = def.Value; + this->AddBacktrace(define, def.Backtrace); + return define; +} + +Json::Value Target::DumpSourceGroups() +{ + Json::Value sourceGroups = Json::arrayValue; + for (auto& sg : this->SourceGroups) { + sourceGroups.append(this->DumpSourceGroup(sg)); + } + return sourceGroups; +} + +Json::Value Target::DumpSourceGroup(SourceGroup& sg) +{ + Json::Value group = Json::objectValue; + group["name"] = sg.Name; + group["sourceIndexes"] = std::move(sg.SourceIndexes); + return group; +} + +Json::Value Target::DumpCompileGroups() +{ + Json::Value compileGroups = Json::arrayValue; + for (auto& cg : this->CompileGroups) { + compileGroups.append(this->DumpCompileGroup(cg)); + } + return compileGroups; +} + +Json::Value Target::DumpCompileGroup(CompileGroup& cg) +{ + Json::Value group = cg.Entry->first; + group["sourceIndexes"] = std::move(cg.SourceIndexes); + return group; +} + +Json::Value Target::DumpSysroot(std::string const& path) +{ + Json::Value sysroot = Json::objectValue; + sysroot["path"] = path; + return sysroot; +} + +Json::Value Target::DumpInstall() +{ + Json::Value install = Json::objectValue; + install["prefix"] = this->DumpInstallPrefix(); + install["destinations"] = this->DumpInstallDestinations(); + return install; +} + +Json::Value Target::DumpInstallPrefix() +{ + Json::Value prefix = Json::objectValue; + std::string p = + this->GT->Makefile->GetSafeDefinition("CMAKE_INSTALL_PREFIX"); + cmSystemTools::ConvertToUnixSlashes(p); + prefix["path"] = p; + return prefix; +} + +Json::Value Target::DumpInstallDestinations() +{ + Json::Value destinations = Json::arrayValue; + auto installGens = this->GT->Makefile->GetInstallGenerators(); + for (auto iGen : installGens) { + auto itGen = dynamic_cast<cmInstallTargetGenerator*>(iGen); + if (itGen != nullptr && itGen->GetTarget() == this->GT) { + destinations.append(this->DumpInstallDestination(itGen)); + } + } + return destinations; +} + +Json::Value Target::DumpInstallDestination(cmInstallTargetGenerator* itGen) +{ + Json::Value destination = Json::objectValue; + destination["path"] = itGen->GetDestination(this->Config); + this->AddBacktrace(destination, itGen->GetBacktrace()); + return destination; +} + +Json::Value Target::DumpArtifacts() +{ + Json::Value artifacts = Json::arrayValue; + + // Object libraries have only object files as artifacts. + if (this->GT->GetType() == cmStateEnums::OBJECT_LIBRARY) { + if (!this->GT->GetGlobalGenerator()->HasKnownObjectFileLocation(nullptr)) { + return artifacts; + } + std::vector<cmSourceFile const*> objectSources; + this->GT->GetObjectSources(objectSources, this->Config); + std::string const obj_dir = this->GT->GetObjectDirectory(this->Config); + for (cmSourceFile const* sf : objectSources) { + const std::string& obj = this->GT->GetObjectName(sf); + Json::Value artifact = Json::objectValue; + artifact["path"] = RelativeIfUnder(this->TopBuild, obj_dir + obj); + artifacts.append(std::move(artifact)); // NOLINT(*) + } + return artifacts; + } + + // Other target types always have a "main" artifact. + { + Json::Value artifact = Json::objectValue; + artifact["path"] = + RelativeIfUnder(this->TopBuild, + this->GT->GetFullPath( + this->Config, cmStateEnums::RuntimeBinaryArtifact)); + artifacts.append(std::move(artifact)); // NOLINT(*) + } + + // Add Windows-specific artifacts produced by the linker. + if (this->GT->IsDLLPlatform() && + this->GT->GetType() != cmStateEnums::STATIC_LIBRARY) { + if (this->GT->GetType() == cmStateEnums::SHARED_LIBRARY || + this->GT->IsExecutableWithExports()) { + Json::Value artifact = Json::objectValue; + artifact["path"] = + RelativeIfUnder(this->TopBuild, + this->GT->GetFullPath( + this->Config, cmStateEnums::ImportLibraryArtifact)); + artifacts.append(std::move(artifact)); // NOLINT(*) + } + cmGeneratorTarget::OutputInfo const* output = + this->GT->GetOutputInfo(this->Config); + if (output && !output->PdbDir.empty()) { + Json::Value artifact = Json::objectValue; + artifact["path"] = RelativeIfUnder(this->TopBuild, + output->PdbDir + '/' + + this->GT->GetPDBName(this->Config)); + artifacts.append(std::move(artifact)); // NOLINT(*) + } + } + return artifacts; +} + +Json::Value Target::DumpLink() +{ + Json::Value link = Json::objectValue; + std::string lang = this->GT->GetLinkerLanguage(this->Config); + link["language"] = lang; + { + Json::Value commandFragments = this->DumpLinkCommandFragments(); + if (!commandFragments.empty()) { + link["commandFragments"] = std::move(commandFragments); + } + } + if (const char* sysrootLink = + this->GT->Makefile->GetDefinition("CMAKE_SYSROOT_LINK")) { + link["sysroot"] = this->DumpSysroot(sysrootLink); + } else if (const char* sysroot = + this->GT->Makefile->GetDefinition("CMAKE_SYSROOT")) { + link["sysroot"] = this->DumpSysroot(sysroot); + } + if (this->GT->IsIPOEnabled(lang, this->Config)) { + link["lto"] = true; + } + return link; +} + +Json::Value Target::DumpArchive() +{ + Json::Value archive = Json::objectValue; + { + // The "link" fragments not relevant to static libraries are empty. + Json::Value commandFragments = this->DumpLinkCommandFragments(); + if (!commandFragments.empty()) { + archive["commandFragments"] = std::move(commandFragments); + } + } + std::string lang = this->GT->GetLinkerLanguage(this->Config); + if (this->GT->IsIPOEnabled(lang, this->Config)) { + archive["lto"] = true; + } + return archive; +} + +Json::Value Target::DumpLinkCommandFragments() +{ + Json::Value linkFragments = Json::arrayValue; + + std::string linkLanguageFlags; + std::string linkFlags; + std::string frameworkPath; + std::string linkPath; + std::string linkLibs; + cmLocalGenerator* lg = this->GT->GetLocalGenerator(); + cmLinkLineComputer linkLineComputer(lg, + lg->GetStateSnapshot().GetDirectory()); + lg->GetTargetFlags(&linkLineComputer, this->Config, linkLibs, + linkLanguageFlags, linkFlags, frameworkPath, linkPath, + this->GT); + linkLanguageFlags = cmSystemTools::TrimWhitespace(linkLanguageFlags); + linkFlags = cmSystemTools::TrimWhitespace(linkFlags); + frameworkPath = cmSystemTools::TrimWhitespace(frameworkPath); + linkPath = cmSystemTools::TrimWhitespace(linkPath); + linkLibs = cmSystemTools::TrimWhitespace(linkLibs); + + if (!linkLanguageFlags.empty()) { + linkFragments.append( + this->DumpCommandFragment(std::move(linkLanguageFlags), "flags")); + } + + if (!linkFlags.empty()) { + linkFragments.append( + this->DumpCommandFragment(std::move(linkFlags), "flags")); + } + + if (!frameworkPath.empty()) { + linkFragments.append( + this->DumpCommandFragment(std::move(frameworkPath), "frameworkPath")); + } + + if (!linkPath.empty()) { + linkFragments.append( + this->DumpCommandFragment(std::move(linkPath), "libraryPath")); + } + + if (!linkLibs.empty()) { + linkFragments.append( + this->DumpCommandFragment(std::move(linkLibs), "libraries")); + } + + return linkFragments; +} + +Json::Value Target::DumpCommandFragments( + std::vector<BT<std::string>> const& frags) +{ + Json::Value commandFragments = Json::arrayValue; + for (BT<std::string> const& f : frags) { + commandFragments.append(this->DumpCommandFragment(f)); + } + return commandFragments; +} + +Json::Value Target::DumpCommandFragment(BT<std::string> const& frag, + std::string const& role) +{ + Json::Value fragment = Json::objectValue; + fragment["fragment"] = frag.Value; + if (!role.empty()) { + fragment["role"] = role; + } + this->AddBacktrace(fragment, frag.Backtrace); + return fragment; +} + +Json::Value Target::DumpDependencies() +{ + Json::Value dependencies = Json::arrayValue; + cmGlobalGenerator* gg = this->GT->GetGlobalGenerator(); + for (cmTargetDepend const& td : gg->GetTargetDirectDepends(this->GT)) { + dependencies.append(this->DumpDependency(td)); + } + return dependencies; +} + +Json::Value Target::DumpDependency(cmTargetDepend const& td) +{ + Json::Value dependency = Json::objectValue; + dependency["id"] = TargetId(td, this->TopBuild); + this->AddBacktrace(dependency, td.GetBacktrace()); + return dependency; +} + +Json::Value Target::DumpFolder() +{ + Json::Value folder; + if (const char* f = this->GT->GetProperty("FOLDER")) { + folder = Json::objectValue; + folder["name"] = f; + } + return folder; +} +} + +Json::Value cmFileAPICodemodelDump(cmFileAPI& fileAPI, unsigned long version) +{ + Codemodel codemodel(fileAPI, version); + return codemodel.Dump(); +} diff --git a/Source/cmFileAPICodemodel.h b/Source/cmFileAPICodemodel.h new file mode 100644 index 0000000..ffbd928 --- /dev/null +++ b/Source/cmFileAPICodemodel.h @@ -0,0 +1,15 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ +#ifndef cmFileAPICodemodel_h +#define cmFileAPICodemodel_h + +#include "cmConfigure.h" // IWYU pragma: keep + +#include "cm_jsoncpp_value.h" + +class cmFileAPI; + +extern Json::Value cmFileAPICodemodelDump(cmFileAPI& fileAPI, + unsigned long version); + +#endif diff --git a/Tests/RunCMake/FileAPI/RunCMakeTest.cmake b/Tests/RunCMake/FileAPI/RunCMakeTest.cmake index 1cc10ee..515a4bd 100644 --- a/Tests/RunCMake/FileAPI/RunCMakeTest.cmake +++ b/Tests/RunCMake/FileAPI/RunCMakeTest.cmake @@ -43,3 +43,14 @@ run_cmake(ClientStateless) run_cmake(MixedStateless) run_cmake(DuplicateStateless) run_cmake(ClientStateful) + +function(run_object object) + set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/${object}-build) + run_cmake(${object}) + set(RunCMake_TEST_NO_CLEAN 1) + run_cmake_command(${object}-SharedStateless ${CMAKE_COMMAND} .) + run_cmake_command(${object}-ClientStateless ${CMAKE_COMMAND} .) + run_cmake_command(${object}-ClientStateful ${CMAKE_COMMAND} .) +endfunction() + +run_object(codemodel-v2) diff --git a/Tests/RunCMake/FileAPI/codemodel-v2-ClientStateful-check.cmake b/Tests/RunCMake/FileAPI/codemodel-v2-ClientStateful-check.cmake new file mode 100644 index 0000000..cdbfe7c --- /dev/null +++ b/Tests/RunCMake/FileAPI/codemodel-v2-ClientStateful-check.cmake @@ -0,0 +1,11 @@ +set(expect + query + query/client-foo + query/client-foo/query.json + reply + reply/codemodel-v2-[0-9a-f]+.json + reply/index-[0-9.T-]+.json + ) +check_api("^${expect}$") + +check_python(codemodel-v2) diff --git a/Tests/RunCMake/FileAPI/codemodel-v2-ClientStateful-prep.cmake b/Tests/RunCMake/FileAPI/codemodel-v2-ClientStateful-prep.cmake new file mode 100644 index 0000000..e2b38ff --- /dev/null +++ b/Tests/RunCMake/FileAPI/codemodel-v2-ClientStateful-prep.cmake @@ -0,0 +1,4 @@ +file(REMOVE_RECURSE ${RunCMake_TEST_BINARY_DIR}/.cmake/api/v1/query) +file(WRITE "${RunCMake_TEST_BINARY_DIR}/.cmake/api/v1/query/client-foo/query.json" [[ +{ "requests": [ { "kind": "codemodel", "version" : 2 } ] } +]]) diff --git a/Tests/RunCMake/FileAPI/codemodel-v2-ClientStateless-check.cmake b/Tests/RunCMake/FileAPI/codemodel-v2-ClientStateless-check.cmake new file mode 100644 index 0000000..612120b --- /dev/null +++ b/Tests/RunCMake/FileAPI/codemodel-v2-ClientStateless-check.cmake @@ -0,0 +1,11 @@ +set(expect + query + query/client-foo + query/client-foo/codemodel-v2 + reply + reply/codemodel-v2-[0-9a-f]+.json + reply/index-[0-9.T-]+.json + ) +check_api("^${expect}$") + +check_python(codemodel-v2) diff --git a/Tests/RunCMake/FileAPI/codemodel-v2-ClientStateless-prep.cmake b/Tests/RunCMake/FileAPI/codemodel-v2-ClientStateless-prep.cmake new file mode 100644 index 0000000..d1ce292 --- /dev/null +++ b/Tests/RunCMake/FileAPI/codemodel-v2-ClientStateless-prep.cmake @@ -0,0 +1,2 @@ +file(REMOVE_RECURSE ${RunCMake_TEST_BINARY_DIR}/.cmake/api/v1/query) +file(WRITE "${RunCMake_TEST_BINARY_DIR}/.cmake/api/v1/query/client-foo/codemodel-v2" "") diff --git a/Tests/RunCMake/FileAPI/codemodel-v2-SharedStateless-check.cmake b/Tests/RunCMake/FileAPI/codemodel-v2-SharedStateless-check.cmake new file mode 100644 index 0000000..2afaeeb --- /dev/null +++ b/Tests/RunCMake/FileAPI/codemodel-v2-SharedStateless-check.cmake @@ -0,0 +1,10 @@ +set(expect + query + query/codemodel-v2 + reply + reply/codemodel-v2-[0-9a-f]+.json + reply/index-[0-9.T-]+.json + ) +check_api("^${expect}$") + +check_python(codemodel-v2) diff --git a/Tests/RunCMake/FileAPI/codemodel-v2-SharedStateless-prep.cmake b/Tests/RunCMake/FileAPI/codemodel-v2-SharedStateless-prep.cmake new file mode 100644 index 0000000..8a519d5 --- /dev/null +++ b/Tests/RunCMake/FileAPI/codemodel-v2-SharedStateless-prep.cmake @@ -0,0 +1,2 @@ +file(REMOVE_RECURSE ${RunCMake_TEST_BINARY_DIR}/.cmake/api/v1/query) +file(WRITE "${RunCMake_TEST_BINARY_DIR}/.cmake/api/v1/query/codemodel-v2" "") diff --git a/Tests/RunCMake/FileAPI/codemodel-v2-check.py b/Tests/RunCMake/FileAPI/codemodel-v2-check.py new file mode 100644 index 0000000..8e65acb --- /dev/null +++ b/Tests/RunCMake/FileAPI/codemodel-v2-check.py @@ -0,0 +1,15 @@ +from check_index import * + +def check_objects(o): + assert is_list(o) + assert len(o) == 1 + check_index_object(o[0], "codemodel", 2, 0, check_object_codemodel) + +def check_object_codemodel(o): + assert sorted(o.keys()) == ["configurations", "kind", "paths", "version"] + # The "kind" and "version" members are handled by check_index_object. + # FIXME: Check "configurations" and "paths" members + +assert is_dict(index) +assert sorted(index.keys()) == ["cmake", "objects", "reply"] +check_objects(index["objects"]) diff --git a/Tests/RunCMake/FileAPI/codemodel-v2.cmake b/Tests/RunCMake/FileAPI/codemodel-v2.cmake new file mode 100644 index 0000000..8c782b9 --- /dev/null +++ b/Tests/RunCMake/FileAPI/codemodel-v2.cmake @@ -0,0 +1 @@ +# FIXME: enable_language(C) and add targets to dump diff --git a/Utilities/IWYU/mapping.imp b/Utilities/IWYU/mapping.imp index 5af8b85..4f26ce1 100644 --- a/Utilities/IWYU/mapping.imp +++ b/Utilities/IWYU/mapping.imp @@ -126,6 +126,8 @@ { symbol: [ "SIGINT", private, "\"cm_uv.h\"", public ] }, { symbol: [ "ssize_t", private, "\"cm_uv.h\"", public ] }, + { symbol: [ "Json::ArrayIndex", private, "\"cm_jsoncpp_value.h\"", public ] }, + { symbol: [ "std::ifstream", private, "\"cmsys/FStream.hxx\"", public ] }, { symbol: [ "std::ofstream", private, "\"cmsys/FStream.hxx\"", public ] }, { symbol: [ "cmsys::ifstream", private, "\"cmsys/FStream.hxx\"", public ] }, |