Explorar el Código

fileapi: extend codemodel v2 with a project model

Offer clients a `project()`-centric view of the build system.  This is
similar to the directory-centric view but consolidates subdirectories
that do not call `project()` with a new project name.

Issue: #18398
Co-Author: Kyle Edwards <[email protected]>
Brad King hace 7 años
padre
commit
eb8c7676a4

+ 56 - 0
Help/manual/cmake-file-api.7.rst

@@ -432,24 +432,35 @@ Version 1 does not exist to avoid confusion with that from
             "source": ".",
             "build": ".",
             "childIndexes": [ 1 ],
+            "projectIndex": 0,
             "targetIndexes": [ 0 ]
           },
           {
             "source": "sub",
             "build": "sub",
             "parentIndex": 0,
+            "projectIndex": 0,
             "targetIndexes": [ 1 ]
           }
         ],
+        "projects": [
+          {
+            "name": "MyProject",
+            "directoryIndexes": [ 0, 1 ],
+            "targetIndexes": [ 0, 1 ]
+          }
+        ],
         "targets": [
           {
             "name": "MyExecutable",
             "directoryIndex": 0,
+            "projectIndex": 0,
             "jsonFile": "<file>"
           },
           {
             "name": "MyLibrary",
             "directoryIndex": 1,
+            "projectIndex": 0,
             "jsonFile": "<file>"
           }
         ]
@@ -514,12 +525,53 @@ The members specific to ``codemodel`` objects are:
       command.  Each entry is an unsigned integer 0-based index of another
       entry in the main ``directories`` array.
 
+    ``projectIndex``
+      An unsigned integer 0-based index into the main ``projects`` array
+      indicating the build system project to which the this directory belongs.
+
     ``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.
 
+  ``projects``
+    A JSON array of entries corresponding to the top-level project
+    and sub-projects defined in the build system.  Each (sub-)project
+    corresponds to a source directory whose ``CMakeLists.txt`` file
+    calls the :command:`project` command with a project name different
+    from its parent directory.  The first entry corresponds to the
+    top-level project.
+
+    Each entry is a JSON object containing members:
+
+    ``name``
+      A string specifying the name given to the :command:`project` command.
+
+    ``parentIndex``
+      Optional member that is present when the project is not top-level.
+      The value is an unsigned integer 0-based index of another entry in
+      the main ``projects`` array that corresponds to the parent project
+      that added this project as a sub-project.
+
+    ``childIndexes``
+      Optional member that is present when the project has sub-projects.
+      The value is a JSON array of entries corresponding to the sub-projects.
+      Each entry is an unsigned integer 0-based index of another
+      entry in the main ``projects`` array.
+
+    ``directoryIndexes``
+      A JSON array of entries corresponding to build system directories
+      that are part of the project.  The first entry corresponds to the
+      top-level directory of the project.  Each entry is an unsigned
+      integer 0-based index into the main ``directories`` array.
+
+    ``targetIndexes``
+      Optional member that is present when the project itself has targets,
+      excluding those belonging to sub-projects.  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`,
@@ -538,6 +590,10 @@ The members specific to ``codemodel`` objects are:
       An unsigned integer 0-based index into the main ``directories`` array
       indicating the build system directory in which the target is defined.
 
+    ``projectIndex``
+      An unsigned integer 0-based index into the main ``projects`` array
+      indicating the build system project in which the target is defined.
+
     ``jsonFile``
       A JSON string specifying a path relative to the codemodel file
       to another JSON file containing a

+ 86 - 0
Source/cmFileAPICodemodel.cxx

@@ -63,22 +63,42 @@ class CodemodelConfig
   {
     cmStateSnapshot Snapshot;
     Json::Value TargetIndexes = Json::arrayValue;
+    Json::ArrayIndex ProjectIndex;
   };
   std::map<cmStateSnapshot, Json::ArrayIndex, cmStateSnapshot::StrictWeakOrder>
     DirectoryMap;
   std::vector<Directory> Directories;
 
+  struct Project
+  {
+    cmStateSnapshot Snapshot;
+    static const Json::ArrayIndex NoParentIndex =
+      static_cast<Json::ArrayIndex>(-1);
+    Json::ArrayIndex ParentIndex = NoParentIndex;
+    Json::Value ChildIndexes = Json::arrayValue;
+    Json::Value DirectoryIndexes = Json::arrayValue;
+    Json::Value TargetIndexes = Json::arrayValue;
+  };
+  std::map<cmStateSnapshot, Json::ArrayIndex, cmStateSnapshot::StrictWeakOrder>
+    ProjectMap;
+  std::vector<Project> Projects;
+
   void ProcessDirectories();
 
   Json::ArrayIndex GetDirectoryIndex(cmLocalGenerator const* lg);
   Json::ArrayIndex GetDirectoryIndex(cmStateSnapshot s);
 
+  Json::ArrayIndex AddProject(cmStateSnapshot s);
+
   Json::Value DumpTargets();
   Json::Value DumpTarget(cmGeneratorTarget* gt, Json::ArrayIndex ti);
 
   Json::Value DumpDirectories();
   Json::Value DumpDirectory(Directory& d);
 
+  Json::Value DumpProjects();
+  Json::Value DumpProject(Project& p);
+
 public:
   CodemodelConfig(cmFileAPI& fileAPI, unsigned long version,
                   std::string const& config);
@@ -358,6 +378,7 @@ Json::Value CodemodelConfig::Dump()
   this->ProcessDirectories();
   configuration["targets"] = this->DumpTargets();
   configuration["directories"] = this->DumpDirectories();
+  configuration["projects"] = this->DumpProjects();
   return configuration;
 }
 
@@ -376,6 +397,9 @@ void CodemodelConfig::ProcessDirectories()
     Directory& d = this->Directories[directoryIndex];
     d.Snapshot = lg->GetStateSnapshot().GetBuildsystemDirectory();
     this->DirectoryMap[d.Snapshot] = directoryIndex;
+
+    d.ProjectIndex = this->AddProject(d.Snapshot);
+    this->Projects[d.ProjectIndex].DirectoryIndexes.append(directoryIndex);
   }
 }
 
@@ -392,6 +416,29 @@ Json::ArrayIndex CodemodelConfig::GetDirectoryIndex(cmStateSnapshot s)
   return i->second;
 }
 
+Json::ArrayIndex CodemodelConfig::AddProject(cmStateSnapshot s)
+{
+  cmStateSnapshot ps = s.GetBuildsystemDirectoryParent();
+  if (ps.IsValid() && ps.GetProjectName() == s.GetProjectName()) {
+    // This directory is part of its parent directory project.
+    Json::ArrayIndex const parentDirIndex = this->GetDirectoryIndex(ps);
+    return this->Directories[parentDirIndex].ProjectIndex;
+  }
+
+  // This directory starts a new project.
+  auto projectIndex = static_cast<Json::ArrayIndex>(this->Projects.size());
+  this->Projects.emplace_back();
+  Project& p = this->Projects[projectIndex];
+  p.Snapshot = s;
+  this->ProjectMap[s] = projectIndex;
+  if (ps.IsValid()) {
+    Json::ArrayIndex const parentDirIndex = this->GetDirectoryIndex(ps);
+    p.ParentIndex = this->Directories[parentDirIndex].ProjectIndex;
+    this->Projects[p.ParentIndex].ChildIndexes.append(projectIndex);
+  }
+  return projectIndex;
+}
+
 Json::Value CodemodelConfig::DumpTargets()
 {
   Json::Value targets = Json::arrayValue;
@@ -437,6 +484,11 @@ Json::Value CodemodelConfig::DumpTarget(cmGeneratorTarget* gt,
   target["directoryIndex"] = di;
   this->Directories[di].TargetIndexes.append(ti);
 
+  // Cross-reference project containing target.
+  Json::ArrayIndex pi = this->Directories[di].ProjectIndex;
+  target["projectIndex"] = pi;
+  this->Projects[pi].TargetIndexes.append(ti);
+
   return target;
 }
 
@@ -473,6 +525,8 @@ Json::Value CodemodelConfig::DumpDirectory(Directory& d)
     directory["childIndexes"] = std::move(childIndexes);
   }
 
+  directory["projectIndex"] = d.ProjectIndex;
+
   if (!d.TargetIndexes.empty()) {
     directory["targetIndexes"] = std::move(d.TargetIndexes);
   }
@@ -480,6 +534,38 @@ Json::Value CodemodelConfig::DumpDirectory(Directory& d)
   return directory;
 }
 
+Json::Value CodemodelConfig::DumpProjects()
+{
+  Json::Value projects = Json::arrayValue;
+  for (Project& p : this->Projects) {
+    projects.append(this->DumpProject(p));
+  }
+  return projects;
+}
+
+Json::Value CodemodelConfig::DumpProject(Project& p)
+{
+  Json::Value project = Json::objectValue;
+
+  project["name"] = p.Snapshot.GetProjectName();
+
+  if (p.ParentIndex != Project::NoParentIndex) {
+    project["parentIndex"] = p.ParentIndex;
+  }
+
+  if (!p.ChildIndexes.empty()) {
+    project["childIndexes"] = std::move(p.ChildIndexes);
+  }
+
+  project["directoryIndexes"] = std::move(p.DirectoryIndexes);
+
+  if (!p.TargetIndexes.empty()) {
+    project["targetIndexes"] = std::move(p.TargetIndexes);
+  }
+
+  return project;
+}
+
 Target::Target(cmGeneratorTarget* gt, std::string const& config)
   : GT(gt)
   , Config(config)

+ 258 - 3
Tests/RunCMake/FileAPI/codemodel-v2-check.py

@@ -39,9 +39,12 @@ def check_backtrace(t, b, backtrace):
 def check_directory(c):
     def _check(actual, expected):
         assert is_dict(actual)
-        expected_keys = ["build", "source"]
+        expected_keys = ["build", "source", "projectIndex"]
         assert matches(actual["build"], expected["build"])
 
+        assert is_int(actual["projectIndex"])
+        assert is_string(c["projects"][actual["projectIndex"]]["name"], expected["projectName"])
+
         if expected["parentSource"] is not None:
             expected_keys.append("parentIndex")
             assert is_int(actual["parentIndex"])
@@ -102,11 +105,13 @@ def check_target_backtrace_graph(t):
 def check_target(c):
     def _check(actual, expected):
         assert is_dict(actual)
-        assert sorted(actual.keys()) == ["directoryIndex", "id", "jsonFile", "name"]
+        assert sorted(actual.keys()) == ["directoryIndex", "id", "jsonFile", "name", "projectIndex"]
         assert is_int(actual["directoryIndex"])
         assert matches(c["directories"][actual["directoryIndex"]]["source"], expected["directorySource"])
         assert is_string(actual["name"], expected["name"])
         assert is_string(actual["jsonFile"])
+        assert is_int(actual["projectIndex"])
+        assert is_string(c["projects"][actual["projectIndex"]]["name"], expected["projectName"])
 
         filepath = os.path.join(reply_dir, actual["jsonFile"])
         with open(filepath) as f:
@@ -383,6 +388,39 @@ def check_target(c):
 
     return _check
 
+def check_project(c):
+    def _check(actual, expected):
+        assert is_dict(actual)
+        expected_keys = ["name", "directoryIndexes"]
+
+        check_list_match(lambda a, e: matches(c["directories"][a]["source"], e),
+                         actual["directoryIndexes"], expected["directorySources"],
+                         missing_exception=lambda e: "Directory source: %s" % e,
+                         extra_exception=lambda a: "Directory source: %s" % c["directories"][a]["source"])
+
+        if expected["parentName"] is not None:
+            expected_keys.append("parentIndex")
+            assert is_int(actual["parentIndex"])
+            assert is_string(c["projects"][actual["parentIndex"]]["name"], expected["parentName"])
+
+        if expected["childNames"] is not None:
+            expected_keys.append("childIndexes")
+            check_list_match(lambda a, e: is_string(c["projects"][a]["name"], e),
+                             actual["childIndexes"], expected["childNames"],
+                             missing_exception=lambda e: "Child name: %s" % e,
+                             extra_exception=lambda a: "Child name: %s" % c["projects"][a]["name"])
+
+        if expected["targetIds"] is not None:
+            expected_keys.append("targetIndexes")
+            check_list_match(lambda a, e: matches(c["targets"][a]["id"], e),
+                             actual["targetIndexes"], expected["targetIds"],
+                             missing_exception=lambda e: "Target ID: %s" % e,
+                             extra_exception=lambda a: "Target ID: %s" % c["targets"][a]["id"])
+
+        assert sorted(actual.keys()) == sorted(expected_keys)
+
+    return _check
+
 def gen_check_directories(c, g):
     expected = [
         {
@@ -396,6 +434,7 @@ def gen_check_directories(c, g):
                 "^imported$",
                 "^object$",
                 "^.*/Tests/RunCMake/FileAPIExternalSource$",
+                "^dir$",
             ],
             "targetIds": [
                 "^ALL_BUILD::@6890427a1f51a3e7e1df$",
@@ -408,6 +447,7 @@ def gen_check_directories(c, g):
                 "^c_static_lib::@6890427a1f51a3e7e1df$",
                 "^interface_exe::@6890427a1f51a3e7e1df$",
             ],
+            "projectName": "codemodel-v2",
         },
         {
             "source": "^alias$",
@@ -420,6 +460,7 @@ def gen_check_directories(c, g):
                 "^c_alias_exe::@53632cba2752272bb008$",
                 "^cxx_alias_exe::@53632cba2752272bb008$",
             ],
+            "projectName": "Alias",
         },
         {
             "source": "^custom$",
@@ -432,6 +473,7 @@ def gen_check_directories(c, g):
                 "^custom_exe::@c11385ffed57b860da63$",
                 "^custom_tgt::@c11385ffed57b860da63$",
             ],
+            "projectName": "Custom",
         },
         {
             "source": "^cxx$",
@@ -448,6 +490,7 @@ def gen_check_directories(c, g):
                 "^cxx_static_exe::@a56b12a3f5c0529fb296$",
                 "^cxx_static_lib::@a56b12a3f5c0529fb296$",
             ],
+            "projectName": "Cxx",
         },
         {
             "source": "^imported$",
@@ -463,6 +506,7 @@ def gen_check_directories(c, g):
                 "^link_imported_shared_exe::@ba7eb709d0b48779c6c8$",
                 "^link_imported_static_exe::@ba7eb709d0b48779c6c8$",
             ],
+            "projectName": "Imported",
         },
         {
             "source": "^object$",
@@ -477,6 +521,27 @@ def gen_check_directories(c, g):
                 "^cxx_object_exe::@5ed5358f70faf8d8af7a$",
                 "^cxx_object_lib::@5ed5358f70faf8d8af7a$",
             ],
+            "projectName": "Object",
+        },
+        {
+            "source": "^dir$",
+            "build": "^dir$",
+            "parentSource": "^\\.$",
+            "childSources": [
+                "^dir/dir$",
+            ],
+            "targetIds": None,
+            "projectName": "codemodel-v2",
+            "minimumCMakeVersion": "3.12",
+            "hasInstallRule": None,
+        },
+        {
+            "source": "^dir/dir$",
+            "build": "^dir/dir$",
+            "parentSource": "^dir$",
+            "childSources": None,
+            "targetIds": None,
+            "projectName": "codemodel-v2",
         },
         {
             "source": "^.*/Tests/RunCMake/FileAPIExternalSource$",
@@ -488,6 +553,7 @@ def gen_check_directories(c, g):
                 "^ZERO_CHECK::@[0-9a-f]+$",
                 "^generated_exe::@[0-9a-f]+$",
             ],
+            "projectName": "External",
         },
     ]
 
@@ -520,6 +586,7 @@ def gen_check_targets(c, g, inSource):
             "name": "ALL_BUILD",
             "id": "^ALL_BUILD::@6890427a1f51a3e7e1df$",
             "directorySource": "^\\.$",
+            "projectName": "codemodel-v2",
             "type": "UTILITY",
             "isGeneratorProvided": True,
             "sources": [
@@ -698,6 +765,7 @@ def gen_check_targets(c, g, inSource):
             "name": "ZERO_CHECK",
             "id": "^ZERO_CHECK::@6890427a1f51a3e7e1df$",
             "directorySource": "^\\.$",
+            "projectName": "codemodel-v2",
             "type": "UTILITY",
             "isGeneratorProvided": True,
             "sources": [
@@ -767,6 +835,7 @@ def gen_check_targets(c, g, inSource):
             "name": "interface_exe",
             "id": "^interface_exe::@6890427a1f51a3e7e1df$",
             "directorySource": "^\\.$",
+            "projectName": "codemodel-v2",
             "type": "EXECUTABLE",
             "isGeneratorProvided": None,
             "sources": [
@@ -911,6 +980,7 @@ def gen_check_targets(c, g, inSource):
             "name": "c_lib",
             "id": "^c_lib::@6890427a1f51a3e7e1df$",
             "directorySource": "^\\.$",
+            "projectName": "codemodel-v2",
             "type": "STATIC_LIBRARY",
             "isGeneratorProvided": None,
             "sources": [
@@ -1017,6 +1087,7 @@ def gen_check_targets(c, g, inSource):
             "name": "c_exe",
             "id": "^c_exe::@6890427a1f51a3e7e1df$",
             "directorySource": "^\\.$",
+            "projectName": "codemodel-v2",
             "type": "EXECUTABLE",
             "isGeneratorProvided": None,
             "sources": [
@@ -1157,6 +1228,7 @@ def gen_check_targets(c, g, inSource):
             "name": "c_shared_lib",
             "id": "^c_shared_lib::@6890427a1f51a3e7e1df$",
             "directorySource": "^\\.$",
+            "projectName": "codemodel-v2",
             "type": "SHARED_LIBRARY",
             "isGeneratorProvided": None,
             "sources": [
@@ -1277,6 +1349,7 @@ def gen_check_targets(c, g, inSource):
             "name": "c_shared_exe",
             "id": "^c_shared_exe::@6890427a1f51a3e7e1df$",
             "directorySource": "^\\.$",
+            "projectName": "codemodel-v2",
             "type": "EXECUTABLE",
             "isGeneratorProvided": None,
             "sources": [
@@ -1417,6 +1490,7 @@ def gen_check_targets(c, g, inSource):
             "name": "c_static_lib",
             "id": "^c_static_lib::@6890427a1f51a3e7e1df$",
             "directorySource": "^\\.$",
+            "projectName": "codemodel-v2",
             "type": "STATIC_LIBRARY",
             "isGeneratorProvided": None,
             "sources": [
@@ -1523,6 +1597,7 @@ def gen_check_targets(c, g, inSource):
             "name": "c_static_exe",
             "id": "^c_static_exe::@6890427a1f51a3e7e1df$",
             "directorySource": "^\\.$",
+            "projectName": "codemodel-v2",
             "type": "EXECUTABLE",
             "isGeneratorProvided": None,
             "sources": [
@@ -1663,6 +1738,7 @@ def gen_check_targets(c, g, inSource):
             "name": "ALL_BUILD",
             "id": "^ALL_BUILD::@a56b12a3f5c0529fb296$",
             "directorySource": "^cxx$",
+            "projectName": "Cxx",
             "type": "UTILITY",
             "isGeneratorProvided": True,
             "sources": [
@@ -1761,6 +1837,7 @@ def gen_check_targets(c, g, inSource):
             "name": "ZERO_CHECK",
             "id": "^ZERO_CHECK::@a56b12a3f5c0529fb296$",
             "directorySource": "^cxx$",
+            "projectName": "Cxx",
             "type": "UTILITY",
             "isGeneratorProvided": True,
             "sources": [
@@ -1830,6 +1907,7 @@ def gen_check_targets(c, g, inSource):
             "name": "cxx_lib",
             "id": "^cxx_lib::@a56b12a3f5c0529fb296$",
             "directorySource": "^cxx$",
+            "projectName": "Cxx",
             "type": "STATIC_LIBRARY",
             "isGeneratorProvided": None,
             "sources": [
@@ -1912,6 +1990,7 @@ def gen_check_targets(c, g, inSource):
             "name": "cxx_exe",
             "id": "^cxx_exe::@a56b12a3f5c0529fb296$",
             "directorySource": "^cxx$",
+            "projectName": "Cxx",
             "type": "EXECUTABLE",
             "isGeneratorProvided": None,
             "sources": [
@@ -2016,6 +2095,7 @@ def gen_check_targets(c, g, inSource):
             "name": "cxx_shared_lib",
             "id": "^cxx_shared_lib::@a56b12a3f5c0529fb296$",
             "directorySource": "^cxx$",
+            "projectName": "Cxx",
             "type": "SHARED_LIBRARY",
             "isGeneratorProvided": None,
             "sources": [
@@ -2112,6 +2192,7 @@ def gen_check_targets(c, g, inSource):
             "name": "cxx_shared_exe",
             "id": "^cxx_shared_exe::@a56b12a3f5c0529fb296$",
             "directorySource": "^cxx$",
+            "projectName": "Cxx",
             "type": "EXECUTABLE",
             "isGeneratorProvided": None,
             "sources": [
@@ -2216,6 +2297,7 @@ def gen_check_targets(c, g, inSource):
             "name": "cxx_static_lib",
             "id": "^cxx_static_lib::@a56b12a3f5c0529fb296$",
             "directorySource": "^cxx$",
+            "projectName": "Cxx",
             "type": "STATIC_LIBRARY",
             "isGeneratorProvided": None,
             "sources": [
@@ -2298,6 +2380,7 @@ def gen_check_targets(c, g, inSource):
             "name": "cxx_static_exe",
             "id": "^cxx_static_exe::@a56b12a3f5c0529fb296$",
             "directorySource": "^cxx$",
+            "projectName": "Cxx",
             "type": "EXECUTABLE",
             "isGeneratorProvided": None,
             "sources": [
@@ -2402,6 +2485,7 @@ def gen_check_targets(c, g, inSource):
             "name": "ALL_BUILD",
             "id": "^ALL_BUILD::@53632cba2752272bb008$",
             "directorySource": "^alias$",
+            "projectName": "Alias",
             "type": "UTILITY",
             "isGeneratorProvided": True,
             "sources": [
@@ -2484,6 +2568,7 @@ def gen_check_targets(c, g, inSource):
             "name": "ZERO_CHECK",
             "id": "^ZERO_CHECK::@53632cba2752272bb008$",
             "directorySource": "^alias$",
+            "projectName": "Alias",
             "type": "UTILITY",
             "isGeneratorProvided": True,
             "sources": [
@@ -2553,6 +2638,7 @@ def gen_check_targets(c, g, inSource):
             "name": "c_alias_exe",
             "id": "^c_alias_exe::@53632cba2752272bb008$",
             "directorySource": "^alias$",
+            "projectName": "Alias",
             "type": "EXECUTABLE",
             "isGeneratorProvided": None,
             "sources": [
@@ -2657,6 +2743,7 @@ def gen_check_targets(c, g, inSource):
             "name": "cxx_alias_exe",
             "id": "^cxx_alias_exe::@53632cba2752272bb008$",
             "directorySource": "^alias$",
+            "projectName": "Alias",
             "type": "EXECUTABLE",
             "isGeneratorProvided": None,
             "sources": [
@@ -2761,6 +2848,7 @@ def gen_check_targets(c, g, inSource):
             "name": "ALL_BUILD",
             "id": "^ALL_BUILD::@5ed5358f70faf8d8af7a$",
             "directorySource": "^object$",
+            "projectName": "Object",
             "type": "UTILITY",
             "isGeneratorProvided": True,
             "sources": [
@@ -2851,6 +2939,7 @@ def gen_check_targets(c, g, inSource):
             "name": "ZERO_CHECK",
             "id": "^ZERO_CHECK::@5ed5358f70faf8d8af7a$",
             "directorySource": "^object$",
+            "projectName": "Object",
             "type": "UTILITY",
             "isGeneratorProvided": True,
             "sources": [
@@ -2920,6 +3009,7 @@ def gen_check_targets(c, g, inSource):
             "name": "c_object_lib",
             "id": "^c_object_lib::@5ed5358f70faf8d8af7a$",
             "directorySource": "^object$",
+            "projectName": "Object",
             "type": "OBJECT_LIBRARY",
             "isGeneratorProvided": None,
             "sources": [
@@ -3000,6 +3090,7 @@ def gen_check_targets(c, g, inSource):
             "name": "c_object_exe",
             "id": "^c_object_exe::@5ed5358f70faf8d8af7a$",
             "directorySource": "^object$",
+            "projectName": "Object",
             "type": "EXECUTABLE",
             "isGeneratorProvided": None,
             "sources": [
@@ -3141,6 +3232,7 @@ def gen_check_targets(c, g, inSource):
             "name": "cxx_object_lib",
             "id": "^cxx_object_lib::@5ed5358f70faf8d8af7a$",
             "directorySource": "^object$",
+            "projectName": "Object",
             "type": "OBJECT_LIBRARY",
             "isGeneratorProvided": None,
             "sources": [
@@ -3221,6 +3313,7 @@ def gen_check_targets(c, g, inSource):
             "name": "cxx_object_exe",
             "id": "^cxx_object_exe::@5ed5358f70faf8d8af7a$",
             "directorySource": "^object$",
+            "projectName": "Object",
             "type": "EXECUTABLE",
             "isGeneratorProvided": None,
             "sources": [
@@ -3362,6 +3455,7 @@ def gen_check_targets(c, g, inSource):
             "name": "ALL_BUILD",
             "id": "^ALL_BUILD::@ba7eb709d0b48779c6c8$",
             "directorySource": "^imported$",
+            "projectName": "Imported",
             "type": "UTILITY",
             "isGeneratorProvided": True,
             "sources": [
@@ -3456,6 +3550,7 @@ def gen_check_targets(c, g, inSource):
             "name": "ZERO_CHECK",
             "id": "^ZERO_CHECK::@ba7eb709d0b48779c6c8$",
             "directorySource": "^imported$",
+            "projectName": "Imported",
             "type": "UTILITY",
             "isGeneratorProvided": True,
             "sources": [
@@ -3525,6 +3620,7 @@ def gen_check_targets(c, g, inSource):
             "name": "link_imported_exe",
             "id": "^link_imported_exe::@ba7eb709d0b48779c6c8$",
             "directorySource": "^imported$",
+            "projectName": "Imported",
             "type": "EXECUTABLE",
             "isGeneratorProvided": None,
             "sources": [
@@ -3612,6 +3708,7 @@ def gen_check_targets(c, g, inSource):
             "name": "link_imported_shared_exe",
             "id": "^link_imported_shared_exe::@ba7eb709d0b48779c6c8$",
             "directorySource": "^imported$",
+            "projectName": "Imported",
             "type": "EXECUTABLE",
             "isGeneratorProvided": None,
             "sources": [
@@ -3699,6 +3796,7 @@ def gen_check_targets(c, g, inSource):
             "name": "link_imported_static_exe",
             "id": "^link_imported_static_exe::@ba7eb709d0b48779c6c8$",
             "directorySource": "^imported$",
+            "projectName": "Imported",
             "type": "EXECUTABLE",
             "isGeneratorProvided": None,
             "sources": [
@@ -3786,6 +3884,7 @@ def gen_check_targets(c, g, inSource):
             "name": "link_imported_object_exe",
             "id": "^link_imported_object_exe::@ba7eb709d0b48779c6c8$",
             "directorySource": "^imported$",
+            "projectName": "Imported",
             "type": "EXECUTABLE",
             "isGeneratorProvided": None,
             "sources": [
@@ -3873,6 +3972,7 @@ def gen_check_targets(c, g, inSource):
             "name": "link_imported_interface_exe",
             "id": "^link_imported_interface_exe::@ba7eb709d0b48779c6c8$",
             "directorySource": "^imported$",
+            "projectName": "Imported",
             "type": "EXECUTABLE",
             "isGeneratorProvided": None,
             "sources": [
@@ -3960,6 +4060,7 @@ def gen_check_targets(c, g, inSource):
             "name": "ALL_BUILD",
             "id": "^ALL_BUILD::@c11385ffed57b860da63$",
             "directorySource": "^custom$",
+            "projectName": "Custom",
             "type": "UTILITY",
             "isGeneratorProvided": True,
             "sources": [
@@ -4038,6 +4139,7 @@ def gen_check_targets(c, g, inSource):
             "name": "ZERO_CHECK",
             "id": "^ZERO_CHECK::@c11385ffed57b860da63$",
             "directorySource": "^custom$",
+            "projectName": "Custom",
             "type": "UTILITY",
             "isGeneratorProvided": True,
             "sources": [
@@ -4107,6 +4209,7 @@ def gen_check_targets(c, g, inSource):
             "name": "custom_tgt",
             "id": "^custom_tgt::@c11385ffed57b860da63$",
             "directorySource": "^custom$",
+            "projectName": "Custom",
             "type": "UTILITY",
             "isGeneratorProvided": None,
             "sources": [
@@ -4193,6 +4296,7 @@ def gen_check_targets(c, g, inSource):
             "name": "custom_exe",
             "id": "^custom_exe::@c11385ffed57b860da63$",
             "directorySource": "^custom$",
+            "projectName": "Custom",
             "type": "EXECUTABLE",
             "isGeneratorProvided": None,
             "sources": [
@@ -4297,6 +4401,7 @@ def gen_check_targets(c, g, inSource):
             "name": "ALL_BUILD",
             "id": "^ALL_BUILD::@[0-9a-f]+$",
             "directorySource": "^.*/Tests/RunCMake/FileAPIExternalSource$",
+            "projectName": "External",
             "type": "UTILITY",
             "isGeneratorProvided": True,
             "sources": [
@@ -4375,6 +4480,7 @@ def gen_check_targets(c, g, inSource):
             "name": "ZERO_CHECK",
             "id": "^ZERO_CHECK::@[0-9a-f]+$",
             "directorySource": "^.*/Tests/RunCMake/FileAPIExternalSource$",
+            "projectName": "External",
             "type": "UTILITY",
             "isGeneratorProvided": True,
             "sources": [
@@ -4444,6 +4550,7 @@ def gen_check_targets(c, g, inSource):
             "name": "generated_exe",
             "id": "^generated_exe::@[0-9a-f]+$",
             "directorySource": "^.*/Tests/RunCMake/FileAPIExternalSource$",
+            "projectName": "External",
             "type": "EXECUTABLE",
             "isGeneratorProvided": None,
             "sources": [
@@ -4772,11 +4879,159 @@ def check_targets(c, g, inSource):
                      missing_exception=lambda e: "Target ID: %s" % e["id"],
                      extra_exception=lambda a: "Target ID: %s" % a["id"])
 
+def gen_check_projects(c, g):
+    expected = [
+        {
+            "name": "codemodel-v2",
+            "parentName": None,
+            "childNames": [
+                "Alias",
+                "Custom",
+                "Cxx",
+                "Imported",
+                "Object",
+                "External",
+            ],
+            "directorySources": [
+                "^\\.$",
+                "^dir$",
+                "^dir/dir$",
+            ],
+            "targetIds": [
+                "^ALL_BUILD::@6890427a1f51a3e7e1df$",
+                "^ZERO_CHECK::@6890427a1f51a3e7e1df$",
+                "^interface_exe::@6890427a1f51a3e7e1df$",
+                "^c_lib::@6890427a1f51a3e7e1df$",
+                "^c_exe::@6890427a1f51a3e7e1df$",
+                "^c_shared_lib::@6890427a1f51a3e7e1df$",
+                "^c_shared_exe::@6890427a1f51a3e7e1df$",
+                "^c_static_lib::@6890427a1f51a3e7e1df$",
+                "^c_static_exe::@6890427a1f51a3e7e1df$",
+            ],
+        },
+        {
+            "name": "Cxx",
+            "parentName": "codemodel-v2",
+            "childNames": None,
+            "directorySources": [
+                "^cxx$",
+            ],
+            "targetIds": [
+                "^ALL_BUILD::@a56b12a3f5c0529fb296$",
+                "^ZERO_CHECK::@a56b12a3f5c0529fb296$",
+                "^cxx_lib::@a56b12a3f5c0529fb296$",
+                "^cxx_exe::@a56b12a3f5c0529fb296$",
+                "^cxx_shared_lib::@a56b12a3f5c0529fb296$",
+                "^cxx_shared_exe::@a56b12a3f5c0529fb296$",
+                "^cxx_static_lib::@a56b12a3f5c0529fb296$",
+                "^cxx_static_exe::@a56b12a3f5c0529fb296$",
+            ],
+        },
+        {
+            "name": "Alias",
+            "parentName": "codemodel-v2",
+            "childNames": None,
+            "directorySources": [
+                "^alias$",
+            ],
+            "targetIds": [
+                "^ALL_BUILD::@53632cba2752272bb008$",
+                "^ZERO_CHECK::@53632cba2752272bb008$",
+                "^c_alias_exe::@53632cba2752272bb008$",
+                "^cxx_alias_exe::@53632cba2752272bb008$",
+            ],
+        },
+        {
+            "name": "Object",
+            "parentName": "codemodel-v2",
+            "childNames": None,
+            "directorySources": [
+                "^object$",
+            ],
+            "targetIds": [
+                "^ALL_BUILD::@5ed5358f70faf8d8af7a$",
+                "^ZERO_CHECK::@5ed5358f70faf8d8af7a$",
+                "^c_object_lib::@5ed5358f70faf8d8af7a$",
+                "^c_object_exe::@5ed5358f70faf8d8af7a$",
+                "^cxx_object_lib::@5ed5358f70faf8d8af7a$",
+                "^cxx_object_exe::@5ed5358f70faf8d8af7a$",
+            ],
+        },
+        {
+            "name": "Imported",
+            "parentName": "codemodel-v2",
+            "childNames": None,
+            "directorySources": [
+                "^imported$",
+            ],
+            "targetIds": [
+                "^ALL_BUILD::@ba7eb709d0b48779c6c8$",
+                "^ZERO_CHECK::@ba7eb709d0b48779c6c8$",
+                "^link_imported_exe::@ba7eb709d0b48779c6c8$",
+                "^link_imported_shared_exe::@ba7eb709d0b48779c6c8$",
+                "^link_imported_static_exe::@ba7eb709d0b48779c6c8$",
+                "^link_imported_object_exe::@ba7eb709d0b48779c6c8$",
+                "^link_imported_interface_exe::@ba7eb709d0b48779c6c8$",
+            ],
+        },
+        {
+            "name": "Custom",
+            "parentName": "codemodel-v2",
+            "childNames": None,
+            "directorySources": [
+                "^custom$",
+            ],
+            "targetIds": [
+                "^ALL_BUILD::@c11385ffed57b860da63$",
+                "^ZERO_CHECK::@c11385ffed57b860da63$",
+                "^custom_tgt::@c11385ffed57b860da63$",
+                "^custom_exe::@c11385ffed57b860da63$",
+            ],
+        },
+        {
+            "name": "External",
+            "parentName": "codemodel-v2",
+            "childNames": None,
+            "directorySources": [
+                "^.*/Tests/RunCMake/FileAPIExternalSource$",
+            ],
+            "targetIds": [
+                "^ALL_BUILD::@[0-9a-f]+$",
+                "^ZERO_CHECK::@[0-9a-f]+$",
+                "^generated_exe::@[0-9a-f]+$",
+            ],
+        },
+    ]
+
+    if matches(g, "^Visual Studio "):
+        for e in expected:
+            if e["parentName"] is not None:
+                e["targetIds"] = filter_list(lambda t: not matches(t, "^\\^ZERO_CHECK"), e["targetIds"])
+
+    elif g == "Xcode":
+        if ';' in os.environ.get("CMAKE_OSX_ARCHITECTURES", ""):
+            for e in expected:
+                e["targetIds"] = filter_list(lambda t: not matches(t, "^\\^(link_imported_object_exe)"), e["targetIds"])
+
+    else:
+        for e in expected:
+            e["targetIds"] = filter_list(lambda t: not matches(t, "^\\^(ALL_BUILD|ZERO_CHECK)"), e["targetIds"])
+
+    return expected
+
+def check_projects(c, g):
+    check_list_match(lambda a, e: is_string(a["name"], e["name"]), c["projects"], gen_check_projects(c, g),
+                     check=check_project(c),
+                     check_exception=lambda a, e: "Project name: %s" % a["name"],
+                     missing_exception=lambda e: "Project name: %s" % e["name"],
+                     extra_exception=lambda a: "Project name: %s" % a["name"])
+
 def check_object_codemodel_configuration(c, g, inSource):
-    assert sorted(c.keys()) == ["directories", "name", "targets"]
+    assert sorted(c.keys()) == ["directories", "name", "projects", "targets"]
     assert is_string(c["name"])
     check_directories(c, g)
     check_targets(c, g, inSource)
+    check_projects(c, g)
 
 def check_object_codemodel(g):
     def _check(o):

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

@@ -20,6 +20,7 @@ add_subdirectory(object)
 add_subdirectory(imported)
 add_subdirectory(custom)
 add_subdirectory("${CMAKE_CURRENT_SOURCE_DIR}/../FileAPIExternalSource" "${CMAKE_CURRENT_BINARY_DIR}/../FileAPIExternalBuild")
+add_subdirectory(dir)
 
 set_property(TARGET c_shared_lib PROPERTY LIBRARY_OUTPUT_DIRECTORY lib)
 set_property(TARGET c_shared_lib PROPERTY RUNTIME_OUTPUT_DIRECTORY lib)