Преглед на файлове

fileapi: add codemodel v2

Start with v2 to distinguish it from server-mode v1.

Issue: #18398
Brad King преди 7 години
родител
ревизия
3e922ceb5e

+ 470 - 0
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.

+ 2 - 0
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

+ 59 - 2
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;

+ 5 - 0
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);

+ 1111 - 0
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();
+}

+ 15 - 0
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

+ 11 - 0
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)

+ 11 - 0
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)

+ 4 - 0
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 } ] }
+]])

+ 11 - 0
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)

+ 2 - 0
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" "")

+ 10 - 0
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)

+ 2 - 0
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" "")

+ 15 - 0
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"])

+ 1 - 0
Tests/RunCMake/FileAPI/codemodel-v2.cmake

@@ -0,0 +1 @@
+# FIXME: enable_language(C) and add targets to dump

+ 2 - 0
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 ] },