From 4315076f2e7b2611b9ea708dd3915b37884804cb Mon Sep 17 00:00:00 2001 From: Craig Scott Date: Fri, 4 Jul 2025 14:39:46 +1000 Subject: [PATCH] fileapi: Add codemodelVersion fields to target and directory objects This will allow JSON schemas for these two types of files to describe the version-specific content without requiring any outside information. Fixes: #27031 --- Help/manual/cmake-file-api.7.rst | 26 ++++++++ ...model-version-directory-target-objects.rst | 8 +++ Source/cmFileAPI.cxx | 10 ++- Source/cmFileAPI.h | 5 +- Source/cmFileAPICodemodel.cxx | 63 +++++++++++++------ Source/cmFileAPICodemodel.h | 3 +- .../CommandLine/E_capabilities-stdout.txt | 2 +- Tests/RunCMake/FileAPI/codemodel-v2-check.py | 42 ++++++++----- 8 files changed, 120 insertions(+), 39 deletions(-) create mode 100644 Help/release/dev/codemodel-version-directory-target-objects.rst diff --git a/Help/manual/cmake-file-api.7.rst b/Help/manual/cmake-file-api.7.rst index 058e4aaabf..d64169697e 100644 --- a/Help/manual/cmake-file-api.7.rst +++ b/Help/manual/cmake-file-api.7.rst @@ -704,6 +704,19 @@ A codemodel "directory" object is referenced by a `"codemodel" version 2`_ object's ``directories`` array. Each "directory" object is a JSON object with members: +``codemodelVersion`` + This specifies the codemodel version this file is part of. It will match + the ``version`` field of the codemodel object kind that references this file. + It is a JSON object with the following members: + + ``major`` + The codemodel major version. + + ``minor`` + The codemodel minor version. + + This field was added in codemodel version 2.9. + ``paths`` A JSON object containing members: @@ -980,6 +993,19 @@ A codemodel "target" object is referenced by a `"codemodel" version 2`_ object's ``targets`` array. Each "target" object is a JSON object with members: +``codemodelVersion`` + This specifies the codemodel version this file is part of. It will match + the ``version`` field of the codemodel object kind that references this file. + It is a JSON object with the following members: + + ``major`` + The codemodel major version. + + ``minor`` + The codemodel minor version. + + This field was added in codemodel version 2.9. + ``name`` A string specifying the logical name of the target. diff --git a/Help/release/dev/codemodel-version-directory-target-objects.rst b/Help/release/dev/codemodel-version-directory-target-objects.rst new file mode 100644 index 0000000000..8b31ef2403 --- /dev/null +++ b/Help/release/dev/codemodel-version-directory-target-objects.rst @@ -0,0 +1,8 @@ +codemodel-version-directory-target-objects +------------------------------------------ + +* The :manual:`cmake-file-api(7)` "codemodel" version 2 ``version`` field has + been updated to 2.9. + +* The :manual:`cmake-file-api(7)` "codemodel" version 2 "target" and + "directory" objects gained a new ``codemodelVersion`` field. diff --git a/Source/cmFileAPI.cxx b/Source/cmFileAPI.cxx index d9f484f603..0e1300387a 100644 --- a/Source/cmFileAPI.cxx +++ b/Source/cmFileAPI.cxx @@ -789,8 +789,10 @@ std::string cmFileAPI::NoSupportedVersion( // The "codemodel" object kind. -// Update Help/manual/cmake-file-api.7.rst when updating this constant. -static unsigned int const CodeModelV2Minor = 8; +// Update the following files as well when updating this constant: +// Help/manual/cmake-file-api.7.rst +// Tests/RunCMake/FileAPI/codemodel-v2-check.py (check_objects()) +static unsigned int const CodeModelV2Minor = 9; void cmFileAPI::BuildClientRequestCodeModel( ClientRequest& r, std::vector const& versions) @@ -809,7 +811,9 @@ void cmFileAPI::BuildClientRequestCodeModel( Json::Value cmFileAPI::BuildCodeModel(Object const& object) { - Json::Value codemodel = cmFileAPICodemodelDump(*this, object.Version); + assert(object.Version == 2); + Json::Value codemodel = + cmFileAPICodemodelDump(*this, object.Version, CodeModelV2Minor); codemodel["kind"] = this->ObjectKindName(object.Kind); Json::Value& version = codemodel["version"]; diff --git a/Source/cmFileAPI.h b/Source/cmFileAPI.h index d847e8b2a2..5d255d4104 100644 --- a/Source/cmFileAPI.h +++ b/Source/cmFileAPI.h @@ -64,6 +64,9 @@ public: bool AddProjectQuery(ObjectKind kind, unsigned majorVersion, unsigned minorVersion); + /** Build a JSON object with major and minor fields. */ + static Json::Value BuildVersion(unsigned int major, unsigned int minor); + private: cmake* CMakeInstance; @@ -196,8 +199,6 @@ private: static char const* ObjectKindName(ObjectKind kind); static std::string ObjectName(Object const& o); - static Json::Value BuildVersion(unsigned int major, unsigned int minor); - Json::Value BuildObject(Object const& object); ClientRequests BuildClientRequests(Json::Value const& requests); diff --git a/Source/cmFileAPICodemodel.cxx b/Source/cmFileAPICodemodel.cxx index 5b61105254..c1650d8e6a 100644 --- a/Source/cmFileAPICodemodel.cxx +++ b/Source/cmFileAPICodemodel.cxx @@ -223,21 +223,24 @@ Json::Value BacktraceData::Dump() class Codemodel { cmFileAPI& FileAPI; - unsigned int Version; + unsigned int VersionMajor; + unsigned int VersionMinor; Json::Value DumpPaths(); Json::Value DumpConfigurations(); Json::Value DumpConfiguration(std::string const& config); public: - Codemodel(cmFileAPI& fileAPI, unsigned int version); + Codemodel(cmFileAPI& fileAPI, unsigned int versionMajor, + unsigned int versionMinor); Json::Value Dump(); }; class CodemodelConfig { cmFileAPI& FileAPI; - unsigned int Version; + unsigned int VersionMajor; + unsigned int VersionMinor; std::string const& Config; std::string TopSource; std::string TopBuild; @@ -290,8 +293,8 @@ class CodemodelConfig Json::Value DumpMinimumCMakeVersion(cmStateSnapshot s); public: - CodemodelConfig(cmFileAPI& fileAPI, unsigned int version, - std::string const& config); + CodemodelConfig(cmFileAPI& fileAPI, unsigned int versionMajor, + unsigned int versionMinor, std::string const& config); Json::Value Dump(); }; @@ -392,6 +395,8 @@ namespace { class DirectoryObject { cmLocalGenerator const* LG = nullptr; + unsigned int VersionMajor; + unsigned int VersionMinor; std::string const& Config; TargetIndexMapType& TargetIndexMap; std::string TopSource; @@ -409,7 +414,8 @@ class DirectoryObject std::string const& toPath); public: - DirectoryObject(cmLocalGenerator const* lg, std::string const& config, + DirectoryObject(cmLocalGenerator const* lg, unsigned int versionMajor, + unsigned int versionMinor, std::string const& config, TargetIndexMapType& targetIndexMap); Json::Value Dump(); }; @@ -417,6 +423,8 @@ public: class Target { cmGeneratorTarget* GT; + unsigned int VersionMajor; + unsigned int VersionMinor; std::string const& Config; std::string TopSource; std::string TopBuild; @@ -511,13 +519,16 @@ class Target Json::Value DumpDebugger(); public: - Target(cmGeneratorTarget* gt, std::string const& config); + Target(cmGeneratorTarget* gt, unsigned int versionMajor, + unsigned int versionMinor, std::string const& config); Json::Value Dump(); }; -Codemodel::Codemodel(cmFileAPI& fileAPI, unsigned int version) +Codemodel::Codemodel(cmFileAPI& fileAPI, unsigned int versionMajor, + unsigned int versionMinor) : FileAPI(fileAPI) - , Version(version) + , VersionMajor(versionMajor) + , VersionMinor(versionMinor) { } @@ -557,19 +568,21 @@ Json::Value Codemodel::DumpConfigurations() Json::Value Codemodel::DumpConfiguration(std::string const& config) { - CodemodelConfig configuration(this->FileAPI, this->Version, config); + CodemodelConfig configuration(this->FileAPI, this->VersionMajor, + this->VersionMinor, config); return configuration.Dump(); } -CodemodelConfig::CodemodelConfig(cmFileAPI& fileAPI, unsigned int version, +CodemodelConfig::CodemodelConfig(cmFileAPI& fileAPI, unsigned int versionMajor, + unsigned int versionMinor, std::string const& config) : FileAPI(fileAPI) - , Version(version) + , VersionMajor(versionMajor) + , VersionMinor(versionMinor) , Config(config) , TopSource(this->FileAPI.GetCMakeInstance()->GetHomeDirectory()) , TopBuild(this->FileAPI.GetCMakeInstance()->GetHomeOutputDirectory()) { - static_cast(this->Version); } Json::Value CodemodelConfig::Dump() @@ -701,7 +714,7 @@ Json::Value CodemodelConfig::DumpTargets() Json::Value CodemodelConfig::DumpTarget(cmGeneratorTarget* gt, Json::ArrayIndex ti) { - Target t(gt, this->Config); + Target t(gt, this->VersionMajor, this->VersionMinor, this->Config); std::string prefix = "target-" + gt->GetName(); if (!this->Config.empty()) { prefix += "-" + this->Config; @@ -797,7 +810,8 @@ Json::Value CodemodelConfig::DumpDirectoryObject(Directory& d) prefix += "-" + this->Config; } - DirectoryObject dir(d.LocalGenerator, this->Config, this->TargetIndexMap); + DirectoryObject dir(d.LocalGenerator, this->VersionMajor, this->VersionMinor, + this->Config, this->TargetIndexMap); return this->FileAPI.MaybeJsonFile(dir.Dump(), prefix); } @@ -844,9 +858,13 @@ Json::Value CodemodelConfig::DumpMinimumCMakeVersion(cmStateSnapshot s) } DirectoryObject::DirectoryObject(cmLocalGenerator const* lg, + unsigned int versionMajor, + unsigned int versionMinor, std::string const& config, TargetIndexMapType& targetIndexMap) : LG(lg) + , VersionMajor(versionMajor) + , VersionMinor(versionMinor) , Config(config) , TargetIndexMap(targetIndexMap) , TopSource(lg->GetGlobalGenerator()->GetCMakeInstance()->GetHomeDirectory()) @@ -859,6 +877,8 @@ DirectoryObject::DirectoryObject(cmLocalGenerator const* lg, Json::Value DirectoryObject::Dump() { Json::Value directoryObject = Json::objectValue; + directoryObject["codemodelVersion"] = + cmFileAPI::BuildVersion(this->VersionMajor, this->VersionMinor); directoryObject["paths"] = this->DumpPaths(); directoryObject["installers"] = this->DumpInstallers(); directoryObject["backtraceGraph"] = this->Backtraces.Dump(); @@ -1186,8 +1206,11 @@ Json::Value DirectoryObject::DumpInstallerPath(std::string const& top, return installPath; } -Target::Target(cmGeneratorTarget* gt, std::string const& config) +Target::Target(cmGeneratorTarget* gt, unsigned int versionMajor, + unsigned int versionMinor, std::string const& config) : GT(gt) + , VersionMajor(versionMajor) + , VersionMinor(versionMinor) , Config(config) , TopSource(gt->GetGlobalGenerator()->GetCMakeInstance()->GetHomeDirectory()) , TopBuild( @@ -1203,6 +1226,8 @@ Json::Value Target::Dump() cmStateEnums::TargetType const type = this->GT->GetType(); + target["codemodelVersion"] = + cmFileAPI::BuildVersion(this->VersionMajor, this->VersionMinor); target["name"] = this->GT->GetName(); target["type"] = cmState::GetTargetTypeName(type); target["id"] = TargetId(this->GT, this->TopBuild); @@ -2154,8 +2179,10 @@ Json::Value Target::DumpDebugger() return debuggerInformation; } -Json::Value cmFileAPICodemodelDump(cmFileAPI& fileAPI, unsigned int version) +Json::Value cmFileAPICodemodelDump(cmFileAPI& fileAPI, + unsigned int versionMajor, + unsigned int versionMinor) { - Codemodel codemodel(fileAPI, version); + Codemodel codemodel(fileAPI, versionMajor, versionMinor); return codemodel.Dump(); } diff --git a/Source/cmFileAPICodemodel.h b/Source/cmFileAPICodemodel.h index de74d7f80e..55632cab97 100644 --- a/Source/cmFileAPICodemodel.h +++ b/Source/cmFileAPICodemodel.h @@ -9,4 +9,5 @@ class cmFileAPI; extern Json::Value cmFileAPICodemodelDump(cmFileAPI& fileAPI, - unsigned int version); + unsigned int majorVersion, + unsigned int minorVersion); diff --git a/Tests/RunCMake/CommandLine/E_capabilities-stdout.txt b/Tests/RunCMake/CommandLine/E_capabilities-stdout.txt index 51b7e259b4..f22ef61296 100644 --- a/Tests/RunCMake/CommandLine/E_capabilities-stdout.txt +++ b/Tests/RunCMake/CommandLine/E_capabilities-stdout.txt @@ -1 +1 @@ -^{"debugger":(true|false),"fileApi":{"requests":\[{"kind":"codemodel","version":\[{"major":2,"minor":8}]},{"kind":"configureLog","version":\[{"major":1,"minor":0}]},{"kind":"cache","version":\[{"major":2,"minor":0}]},{"kind":"cmakeFiles","version":\[{"major":1,"minor":1}]},{"kind":"toolchains","version":\[{"major":1,"minor":0}]}]},"generators":\[.*\],"serverMode":false,"tls":(true|false),"version":{.*}}$ +^{"debugger":(true|false),"fileApi":{"requests":\[{"kind":"codemodel","version":\[{"major":2,"minor":9}]},{"kind":"configureLog","version":\[{"major":1,"minor":0}]},{"kind":"cache","version":\[{"major":2,"minor":0}]},{"kind":"cmakeFiles","version":\[{"major":1,"minor":1}]},{"kind":"toolchains","version":\[{"major":1,"minor":0}]}]},"generators":\[.*\],"serverMode":false,"tls":(true|false),"version":{.*}}$ diff --git a/Tests/RunCMake/FileAPI/codemodel-v2-check.py b/Tests/RunCMake/FileAPI/codemodel-v2-check.py index b7b0090397..84b452a60f 100644 --- a/Tests/RunCMake/FileAPI/codemodel-v2-check.py +++ b/Tests/RunCMake/FileAPI/codemodel-v2-check.py @@ -12,7 +12,9 @@ def read_codemodel_json_data(filename): def check_objects(o, g): assert is_list(o) assert len(o) == 1 - check_index_object(o[0], "codemodel", 2, 8, check_object_codemodel(g)) + major = 2 + minor = 9 + check_index_object(o[0], "codemodel", major, minor, check_object_codemodel(g, major, minor)) def check_backtrace(t, b, backtrace): btg = t["backtraceGraph"] @@ -52,7 +54,7 @@ def check_backtraces(t, actual, expected): check_backtrace(t, actual[i], expected[i]) i += 1 -def check_directory(c): +def check_directory(c, major, minor): def _check(actual, expected): assert is_dict(actual) expected_keys = ["build", "jsonFile", "source", "projectIndex"] @@ -100,7 +102,12 @@ def check_directory(c): d = json.load(f) assert is_dict(d) - assert sorted(d.keys()) == ["backtraceGraph", "installers", "paths"] + assert sorted(d.keys()) == ["backtraceGraph", "codemodelVersion", "installers", "paths"] + + # We get the values for major and minor directly rather than from the "expected" data. + # This avoids having to update every data file any time the major or minor version changes. + assert is_int(d["codemodelVersion"]["major"], major) + assert is_int(d["codemodelVersion"]["minor"], minor) assert is_string(d["paths"]["source"], actual["source"]) assert is_string(d["paths"]["build"], actual["build"]) @@ -266,7 +273,7 @@ def check_backtrace_graph(btg): assert sorted(n.keys()) == sorted(expected_keys) -def check_target(c): +def check_target(c, major, minor): def _check(actual, expected): assert is_dict(actual) assert sorted(actual.keys()) == ["directoryIndex", "id", "jsonFile", "name", "projectIndex"] @@ -281,7 +288,7 @@ def check_target(c): with open(filepath) as f: obj = json.load(f) - expected_keys = ["name", "id", "type", "backtraceGraph", "paths", "sources"] + expected_keys = ["codemodelVersion", "name", "id", "type", "backtraceGraph", "paths", "sources"] assert is_dict(obj) assert is_string(obj["name"], expected["name"]) assert matches(obj["id"], expected["id"]) @@ -293,6 +300,13 @@ def check_target(c): assert matches(obj["paths"]["build"], expected["build"]) assert matches(obj["paths"]["source"], expected["source"]) + # We get the values for major and minor directly rather than from the "expected" data. + # This avoids having to update every data file any time the major or minor version changes. + assert is_dict(obj["codemodelVersion"]) + assert sorted(obj["codemodelVersion"].keys()) == ["major", "minor"] + assert is_int(obj["codemodelVersion"]["major"], major) + assert is_int(obj["codemodelVersion"]["minor"], minor) + def check_file_set(actual, expected): assert is_dict(actual) expected_keys = ["name", "type", "visibility", "baseDirectories"] @@ -794,9 +808,9 @@ def gen_check_directories(c, g): return expected -def check_directories(c, g): +def check_directories(c, g, major, minor): check_list_match(lambda a, e: matches(a["source"], e["source"]), c["directories"], gen_check_directories(c, g), - check=check_directory(c), + check=check_directory(c, major, minor), check_exception=lambda a, e: "Directory source: %s" % a["source"], missing_exception=lambda e: "Directory source: %s" % e["source"], extra_exception=lambda a: "Directory source: %s" % a["source"]) @@ -1001,10 +1015,10 @@ def gen_check_targets(c, g, inSource): return expected -def check_targets(c, g, inSource): +def check_targets(c, g, major, minor, inSource): check_list_match(lambda a, e: matches(a["id"], e["id"]), c["targets"], gen_check_targets(c, g, inSource), - check=check_target(c), + check=check_target(c, major, minor), check_exception=lambda a, e: "Target ID: %s" % a["id"], missing_exception=lambda e: "Target ID: %s" % e["id"], extra_exception=lambda a: "Target ID: %s" % a["id"]) @@ -1045,14 +1059,14 @@ def check_projects(c, g): 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): +def check_object_codemodel_configuration(c, g, major, minor, inSource): assert sorted(c.keys()) == ["directories", "name", "projects", "targets"] assert is_string(c["name"]) - check_directories(c, g) - check_targets(c, g, inSource) + check_directories(c, g, major, minor) + check_targets(c, g, major, minor, inSource) check_projects(c, g) -def check_object_codemodel(g): +def check_object_codemodel(g, major, minor): def _check(o): assert sorted(o.keys()) == ["configurations", "kind", "paths", "version"] # The "kind" and "version" members are handled by check_index_object. @@ -1070,7 +1084,7 @@ def check_object_codemodel(g): assert o["configurations"][0]["name"] in ("", "Debug", "Release", "RelWithDebInfo", "MinSizeRel") for c in o["configurations"]: - check_object_codemodel_configuration(c, g, inSource) + check_object_codemodel_configuration(c, g, major, minor, inSource) return _check cxx_compiler_id = sys.argv[2]