From 685b9697f6f30538558a0892af6ff4701f7795f3 Mon Sep 17 00:00:00 2001 From: Daniel Brondani Date: Thu, 20 Aug 2026 13:26:33 +0200 Subject: [PATCH 1/2] [projmgr] Add native CMake project support Parse and validate CMake project entries, generate corresponding cbuild metadata, and handle device and output settings without CMSIS project processing. --- tools/projmgr/include/ProjMgrParser.h | 20 +++++++ tools/projmgr/include/ProjMgrWorker.h | 2 + tools/projmgr/include/ProjMgrYamlParser.h | 3 + tools/projmgr/schemas/common.schema.json | 48 +++++++++++++++- tools/projmgr/src/ProjMgr.cpp | 2 +- tools/projmgr/src/ProjMgrCbuild.cpp | 24 +++++++- tools/projmgr/src/ProjMgrCbuildIdx.cpp | 5 +- tools/projmgr/src/ProjMgrWorker.cpp | 41 ++++++++++++-- tools/projmgr/src/ProjMgrYamlParser.cpp | 32 ++++++++++- .../data/CmakeSupport/solution.csolution.yml | 39 +++++++++++++ tools/projmgr/test/src/ProjMgrUnitTests.cpp | 55 +++++++++++++++++++ 11 files changed, 258 insertions(+), 13 deletions(-) create mode 100644 tools/projmgr/test/data/CmakeSupport/solution.csolution.yml diff --git a/tools/projmgr/include/ProjMgrParser.h b/tools/projmgr/include/ProjMgrParser.h index e8a2acaa5..799fe1242 100644 --- a/tools/projmgr/include/ProjMgrParser.h +++ b/tools/projmgr/include/ProjMgrParser.h @@ -508,6 +508,24 @@ struct WestDesc { std::vector westOpt; }; +/** + * @brief CMake native project descriptor +*/ +struct CmakeImage { + std::string image; + std::string type; +}; + +struct CmakeDesc { + std::string projectId; + std::string source; + std::string generator; + std::vector configure; + std::string target; + std::vector images; + std::string device; +}; + /** * @brief context descriptor containing * cproject filename, @@ -517,6 +535,7 @@ struct ContextDesc { std::string cproject; TypeFilter type; WestDesc west; + CmakeDesc cmake; }; /** @@ -649,6 +668,7 @@ struct CsolutionItem { TargetType target; std::vector cprojects; std::vector westApps; + std::vector cmakeApps; std::vector contexts; std::vector packs; bool enableCdefault; diff --git a/tools/projmgr/include/ProjMgrWorker.h b/tools/projmgr/include/ProjMgrWorker.h index cfd739728..2cd75dafa 100644 --- a/tools/projmgr/include/ProjMgrWorker.h +++ b/tools/projmgr/include/ProjMgrWorker.h @@ -585,6 +585,8 @@ struct ContextItem { bool imageOnly = false; WestDesc west; bool westOn = false; + CmakeDesc cmake; + bool cmakeOn = false; std::vector variablesConfigurations; std::set unresolvedComponents; StrMap availablePackVersions; diff --git a/tools/projmgr/include/ProjMgrYamlParser.h b/tools/projmgr/include/ProjMgrYamlParser.h index 1682357c9..35ec7a23a 100644 --- a/tools/projmgr/include/ProjMgrYamlParser.h +++ b/tools/projmgr/include/ProjMgrYamlParser.h @@ -43,6 +43,7 @@ static constexpr const char* YAML_BLOCKS = "blocks"; static constexpr const char* YAML_BOARD = "board"; static constexpr const char* YAML_BOARD_BOOKS = "board-books"; static constexpr const char* YAML_BOARD_PACK = "board-pack"; +static constexpr const char* YAML_CONFIGURE = "configure"; static constexpr const char* YAML_BOARD_PACK_PATH = "board-pack-path"; static constexpr const char* YAML_BRANCH_PROTECTION = "branch-protection"; static constexpr const char* YAML_BUILD = "build"; @@ -54,6 +55,7 @@ static constexpr const char* YAML_BUILDTYPES = "build-types"; static constexpr const char* YAML_CATEGORY = "category"; static constexpr const char* YAML_CBUILDS = "cbuilds"; static constexpr const char* YAML_CBUILD = "cbuild"; +static constexpr const char* YAML_CMAKE = "cmake"; static constexpr const char* YAML_CBUILD_GENS = "cbuild-gens"; static constexpr const char* YAML_CBUILD_GEN = "cbuild-gen"; static constexpr const char* YAML_CBUILD_MLOPS = "cbuild-mlops"; @@ -255,6 +257,7 @@ static constexpr const char* YAML_SIZE = "size"; static constexpr const char* YAML_START = "start"; static constexpr const char* YAML_START_PNAME = "start-pname"; static constexpr const char* YAML_STATUS = "status"; +static constexpr const char* YAML_SOURCE = "source"; static constexpr const char* YAML_SOLUTION = "solution"; static constexpr const char* YAML_SPROT = "SPROT"; static constexpr const char* YAML_SYSTEM = "system"; diff --git a/tools/projmgr/schemas/common.schema.json b/tools/projmgr/schemas/common.schema.json index eac3b9572..d19cc903c 100644 --- a/tools/projmgr/schemas/common.schema.json +++ b/tools/projmgr/schemas/common.schema.json @@ -103,6 +103,11 @@ "ConditionIDType": { "type": "string" }, + "ProjectIdType": { + "type": "string", + "pattern": "^[a-zA-Z0-9_-]{1,32}$", + "description": "Project identifier used as a context name and output path component." + }, "ForType": { "oneOf": [ {"$ref": "#/definitions/ArrayOfBuildContext" }, @@ -738,13 +743,18 @@ "description": "Path to the valid project file." }, "west": { "$ref": "#/definitions/WestProjectType" }, + "cmake": { "$ref": "#/definitions/CmakeProjectType" }, "for-context": { "$ref": "#/definitions/ForContext" }, "not-for-context": { "$ref": "#/definitions/NotForContext" } }, "additionalProperties": false, "allOf": [ { "$ref": "#/definitions/TypeListMutualExclusion" }, - { "oneOf": [ { "required": ["project"], "not": {"required": ["west"]} }, { "required": ["west"], "not": {"required": ["project"]} } ] } + { "oneOf": [ + { "required": ["project"], "not": {"anyOf": [{"required": ["west"]}, {"required": ["cmake"]}]} }, + { "required": ["west"], "not": {"anyOf": [{"required": ["project"]}, {"required": ["cmake"]}]} }, + { "required": ["cmake"], "not": {"anyOf": [{"required": ["project"]}, {"required": ["west"]}]} } + ] } ] }, "ProcessorTrustzone": { @@ -1355,6 +1365,7 @@ "properties": { "cbuild": { "type": "string", "description": "Path to .cbuild.yml file." }, "west": { "type": "boolean" }, + "cmake": { "type": "boolean" }, "project": { "type": "string", "description": "Project name." }, "configuration": { "$ref": "#/definitions/BuildContext" }, "depends-on": { @@ -1510,7 +1521,8 @@ "type": "array", "items": { "$ref": "#/definitions/LicenseInfoType" } }, - "west": { "$ref": "#/definitions/WestProjectType" } + "west": { "$ref": "#/definitions/WestProjectType" }, + "cmake": { "$ref": "#/definitions/CmakeProjectType" } }, "additionalProperties": false, "required": ["generated-by"] @@ -2650,6 +2662,38 @@ "additionalProperties": false, "required": ["app-path"] }, + "CmakeProjectType": { + "title": "cmake:\nDocumentation: https://open-cmsis-pack.github.io/cmsis-toolbox/YML-Input-Format/#cmake", + "description": "Enable native CMake project build orchestration.", + "type": "object", + "properties": { + "source": { "type": "string", "minLength": 1, "description": "Path to the CMake source directory." }, + "generator": { "type": "string", "description": "CMake generator to use for the build (default: Ninja)." }, + "configure": { "type": "array", "description": "Options for the CMake configure command.", "items": { "type": "string" } }, + "target": { "type": "string", "description": "CMake build target." }, + "images": { "$ref": "#/definitions/CmakeImagesType" }, + "device": { "$ref": "#/definitions/ProcessorNameType" }, + "project-id": { "$ref": "#/definitions/ProjectIdType", "description": "Project identifier (default: last sub-dir name of source)." } + }, + "additionalProperties": false, + "required": ["source"] + }, + "CmakeImagesType": { + "type": "array", + "description": "List of output images produced by the CMake project.", + "uniqueItems": true, + "minItems": 1, + "items": { "$ref": "#/definitions/CmakeImageType" } + }, + "CmakeImageType": { + "type": "object", + "properties": { + "image": { "type": "string", "minLength": 1, "description": "Path to the CMake build output image." }, + "type": { "enum": [ "elf", "hex", "bin", "lib" ], "description": "Output image type." } + }, + "additionalProperties": false, + "required": ["image", "type"] + }, "ProcessorsCapabilitiesType": { "title": "processors:\nDocumentation: https://open-cmsis-pack.github.io/cmsis-toolbox/YML-CBuild-Format/#processors", "description": "Capabilities of the device processors.", diff --git a/tools/projmgr/src/ProjMgr.cpp b/tools/projmgr/src/ProjMgr.cpp index bb1211c05..a5213752b 100644 --- a/tools/projmgr/src/ProjMgr.cpp +++ b/tools/projmgr/src/ProjMgr.cpp @@ -499,7 +499,7 @@ bool ProjMgr::PopulateContexts(void) { } // Check cproject separate folders and unique names const StrVec& cprojects = m_parser.GetCsolution().cprojects; - if (!IsSolutionImageOnly() && cprojects.empty() && m_parser.GetCsolution().westApps.empty()) { + if (!IsSolutionImageOnly() && cprojects.empty() && m_parser.GetCsolution().westApps.empty() && m_parser.GetCsolution().cmakeApps.empty()) { ProjMgrLogger::Get().Error("projects not found", "", m_csolutionFile); return false; } diff --git a/tools/projmgr/src/ProjMgrCbuild.cpp b/tools/projmgr/src/ProjMgrCbuild.cpp index 5ade49c6b..ac7d34059 100644 --- a/tools/projmgr/src/ProjMgrCbuild.cpp +++ b/tools/projmgr/src/ProjMgrCbuild.cpp @@ -44,6 +44,7 @@ class ProjMgrCbuild : public ProjMgrCbuildBase { void SetDebugConfigNode(YAML::Node node, const ContextItem* context); void SetPLMStatus(YAML::Node node, const ContextItem* context, const string& file); void SetWestNode(YAML::Node node, const ContextItem* context); + void SetCmakeNode(YAML::Node node, const ContextItem* context); bool m_ignoreRteFileMissing; }; @@ -89,12 +90,12 @@ void ProjMgrCbuild::SetContextNode(YAML::Node contextNode, const ContextItem* co } SetBooksNode(contextNode[YAML_DEVICE_BOOKS], context->deviceBooks, context->directories.cbuild); SetDebugConfigNode(contextNode[YAML_DBGCONF], context); - if (!context->imageOnly && !context->westOn) { + if (!context->imageOnly && !context->westOn && !context->cmakeOn) { SetProcessorNode(contextNode[YAML_PROCESSOR], context->targetAttributes); SetNpuInfoNode(contextNode[YAML_NPU_INFO], context); } SetPacksNode(contextNode[YAML_PACKS], context); - if (!context->imageOnly && !context->westOn) { + if (!context->imageOnly && !context->westOn && !context->cmakeOn) { SetControlsNode(contextNode, context, context->controls.processed); vector defines; if (context->rteActiveTarget != nullptr) { @@ -120,7 +121,7 @@ void ProjMgrCbuild::SetContextNode(YAML::Node contextNode, const ContextItem* co SetNodeValue(contextNode[YAML_OUTPUTDIRS][YAML_OUTPUT_OUTDIR], FormatPath(outDir, context->directories.cbuild)); } SetOutputNode(contextNode[YAML_OUTPUT], context); - if (!context->imageOnly && !context->westOn) { + if (!context->imageOnly && !context->westOn && !context->cmakeOn) { SetComponentsNode(contextNode[YAML_COMPONENTS], context); SetApisNode(contextNode[YAML_APIS], context); SetGeneratorsNode(contextNode[YAML_GENERATORS], context); @@ -132,6 +133,9 @@ void ProjMgrCbuild::SetContextNode(YAML::Node contextNode, const ContextItem* co if (context->westOn) { SetWestNode(contextNode[YAML_WEST], context); } + if (context->cmakeOn) { + SetCmakeNode(contextNode[YAML_CMAKE], context); + } } void ProjMgrCbuild::SetComponentsNode(YAML::Node node, const ContextItem* context) { @@ -631,6 +635,20 @@ void ProjMgrCbuild::SetWestNode(YAML::Node node, const ContextItem* context) { SetNodeValue(node[YAML_WEST_OPT], context->west.westOpt); } +void ProjMgrCbuild::SetCmakeNode(YAML::Node node, const ContextItem* context) { + SetNodeValue(node[YAML_PROJECT_ID], context->cmake.projectId); + SetNodeValue(node[YAML_SOURCE], FormatPath(context->cmake.source, context->directories.cbuild)); + SetNodeValue(node[YAML_GENERATOR], context->cmake.generator); + SetNodeValue(node[YAML_CONFIGURE], context->cmake.configure); + SetNodeValue(node[YAML_TARGET], context->cmake.target); + for (const auto& image : context->cmake.images) { + YAML::Node imageNode; + SetNodeValue(imageNode[YAML_IMAGE], image.image); + SetNodeValue(imageNode[YAML_TYPE], image.type); + node[YAML_IMAGES].push_back(imageNode); + } +} + //-- ProjMgrYamlEmitter::GenerateCbuild ----------------------------------------------------------- bool ProjMgrYamlEmitter::GenerateCbuild(ContextItem* context, const string& generatorId, const string& generatorPack, bool ignoreRteFileMissing) diff --git a/tools/projmgr/src/ProjMgrCbuildIdx.cpp b/tools/projmgr/src/ProjMgrCbuildIdx.cpp index 43b0df6c1..539d206ce 100644 --- a/tools/projmgr/src/ProjMgrCbuildIdx.cpp +++ b/tools/projmgr/src/ProjMgrCbuildIdx.cpp @@ -33,7 +33,7 @@ ProjMgrCbuildIdx::ProjMgrCbuildIdx(YAML::Node node, if (!processedContexts.empty()) { const auto& context = processedContexts.front(); SetNodeValue(node[YAML_DESCRIPTION], context->csolution->description); - if (!parser->GetCdefault().path.empty() && !context->imageOnly && !context->westOn) { + if (!parser->GetCdefault().path.empty() && !context->imageOnly && !context->westOn && !context->cmakeOn) { SetNodeValue(node[YAML_CDEFAULT], FormatPath(parser->GetCdefault().path, directory)); } } @@ -129,6 +129,9 @@ ProjMgrCbuildIdx::ProjMgrCbuildIdx(YAML::Node node, if (context->westOn) { cbuildNode[YAML_WEST] = true; } + if (context->cmakeOn) { + cbuildNode[YAML_CMAKE] = true; + } if (context->cproject) { if (!context->imageOnly) { SetNodeValue(cbuildNode[YAML_PROJECT], context->cproject->name); diff --git a/tools/projmgr/src/ProjMgrWorker.cpp b/tools/projmgr/src/ProjMgrWorker.cpp index d9c3361a8..ad6528d06 100644 --- a/tools/projmgr/src/ProjMgrWorker.cpp +++ b/tools/projmgr/src/ProjMgrWorker.cpp @@ -113,11 +113,12 @@ bool ProjMgrWorker::AddContexts(ProjMgrParser& parser, ContextDesc& descriptor, if (cprojects.find(cprojectFile) != cprojects.end()) { context.cproject = &cprojects.at(cprojectFile); } else { - if (descriptor.west.app.empty()) { + if (descriptor.west.app.empty() && descriptor.cmake.source.empty()) { ProjMgrLogger::Get().Error("cproject not parsed, adding context failed", "", cprojectFile); return false; } else { - context.cproject = &cprojects[descriptor.west.projectId]; + const string& projectId = descriptor.west.app.empty() ? descriptor.cmake.projectId : descriptor.west.projectId; + context.cproject = &cprojects[projectId]; } } @@ -132,6 +133,14 @@ bool ProjMgrWorker::AddContexts(ProjMgrParser& parser, ContextDesc& descriptor, CollectionUtils::PushBackUniquely(context.west.westDefs, "CONFIG_BUILD_OUTPUT_HEX=y"); } + // CMake native project + if (!descriptor.cmake.source.empty()) { + context.cmakeOn = true; + context.cmake = descriptor.cmake; + RteFsUtils::NormalizePath(context.cmake.source, context.csolution->directory); + context.cproject->name = context.cmake.projectId; + } + // No build/target-types if (context.csolution->buildTypes.empty() && context.csolution->targetTypes.empty()) { AddContext(descriptor, { "" }, context); @@ -2342,7 +2351,7 @@ bool ProjMgrWorker::ProcessConfigFiles(ContextItem& context) { } } // Linker script - if (context.outputTypes.elf.on && !context.imageOnly && !context.westOn) { + if (context.outputTypes.elf.on && !context.imageOnly && !context.westOn && !context.cmakeOn) { if (context.linker.autoGen) { if (!context.linker.script.empty()) { ProjMgrLogger::Get().Warn("conflict: automatic linker script generation overrules specified script '" + context.linker.script + "'", context.name); @@ -3044,6 +3053,12 @@ bool ProjMgrWorker::ProcessPrecedences(ContextItem& context, BoardOrDevice proce if (!context.west.device.empty()) { context.cproject->target.device = context.west.device; } + if (!context.cmake.device.empty()) { + DeviceItem device; + GetDeviceItem(context.cproject->target.device, device); + device.pname = RteUtils::ExtractSuffix(context.cmake.device); + context.cproject->target.device = GetDeviceInfoString(device.vendor, device.name, device.pname); + } StringCollection device = { &context.device, @@ -5566,6 +5581,10 @@ bool ProjMgrWorker::ProcessOutputFilenames(ContextItem& context) { baseName = context.cproject->name; } + for (const auto& image : context.cmake.images) { + ProjMgrUtils::SetOutputType(image.type, context.outputTypes); + } + // secure project requires cmse output type if (context.controls.processed.processor.trustzone == "secure") { context.outputTypes.cmse.on = true; @@ -5579,14 +5598,15 @@ bool ProjMgrWorker::ProcessOutputFilenames(ContextItem& context) { } // default: elf - if (!context.outputTypes.lib.on && !context.outputTypes.elf.on && !context.imageOnly) { + if (!context.outputTypes.lib.on && !context.outputTypes.elf.on && !context.imageOnly && + !(context.cmakeOn && !context.cmake.images.empty())) { context.outputTypes.elf.on = true; } // set output filename for each required output type const string toolchain = affixesMap.find(context.toolchain.name) != affixesMap.end() ? context.toolchain.name : ""; if (context.outputTypes.elf.on) { - context.outputTypes.elf.filename = baseName + (context.westOn ? ".elf" : get<0>(affixesMap.at(toolchain))); + context.outputTypes.elf.filename = baseName + ((context.westOn || context.cmakeOn) ? ".elf" : get<0>(affixesMap.at(toolchain))); } if (context.outputTypes.lib.on) { context.outputTypes.lib.filename = get<1>(affixesMap.at(toolchain)) + baseName + get<2>(affixesMap.at(toolchain)); @@ -5597,6 +5617,17 @@ bool ProjMgrWorker::ProcessOutputFilenames(ContextItem& context) { if (context.outputTypes.bin.on) { context.outputTypes.bin.filename = baseName + ".bin"; } + for (const auto& image : context.cmake.images) { + if (image.type == RteConstants::OUTPUT_TYPE_ELF) { + context.outputTypes.elf.filename = image.image; + } else if (image.type == RteConstants::OUTPUT_TYPE_HEX) { + context.outputTypes.hex.filename = image.image; + } else if (image.type == RteConstants::OUTPUT_TYPE_BIN) { + context.outputTypes.bin.filename = image.image; + } else if (image.type == RteConstants::OUTPUT_TYPE_LIB) { + context.outputTypes.lib.filename = image.image; + } + } if (context.outputTypes.map.on) { context.outputTypes.map.filename = baseName + get<0>(affixesMap.at(toolchain)) + ".map"; } diff --git a/tools/projmgr/src/ProjMgrYamlParser.cpp b/tools/projmgr/src/ProjMgrYamlParser.cpp index 0488cc1f2..8e357407a 100644 --- a/tools/projmgr/src/ProjMgrYamlParser.cpp +++ b/tools/projmgr/src/ProjMgrYamlParser.cpp @@ -901,6 +901,35 @@ bool ProjMgrYamlParser::ParseContexts(const YAML::Node& parent, CsolutionItem& c descriptor.west = west; CollectionUtils::PushBackUniquely(csolution.westApps, west.projectId); } + if (projectsEntry[YAML_CMAKE].IsDefined()) { + const auto& cmakeEntry = projectsEntry[YAML_CMAKE]; + CmakeDesc cmake; + ParsePortablePath(cmakeEntry, csolution.path, YAML_SOURCE, cmake.source); + ParseString(cmakeEntry, YAML_GENERATOR, cmake.generator); + ParseVector(cmakeEntry, YAML_CONFIGURE, cmake.configure); + ParseString(cmakeEntry, YAML_TARGET, cmake.target); + if (cmakeEntry[YAML_IMAGES].IsDefined()) { + set imageTypes; + for (const auto& imageEntry : cmakeEntry[YAML_IMAGES]) { + CmakeImage image; + ParseString(imageEntry, YAML_IMAGE, image.image); + ParseString(imageEntry, YAML_TYPE, image.type); + if (!imageTypes.insert(image.type).second) { + ProjMgrLogger::Get().Error("duplicate CMake image type '" + image.type + "'", "", csolution.path, + imageEntry[YAML_TYPE].Mark().line + 1, imageEntry[YAML_TYPE].Mark().column + 1); + return false; + } + cmake.images.push_back(image); + } + } + ParseString(cmakeEntry, YAML_DEVICE, cmake.device); + ParseString(cmakeEntry, YAML_PROJECT_ID, cmake.projectId); + if (cmake.projectId.empty()) { + cmake.projectId = fs::path(cmake.source).filename().generic_string(); + } + descriptor.cmake = cmake; + CollectionUtils::PushBackUniquely(csolution.cmakeApps, cmake.projectId); + } csolution.contexts.push_back(descriptor); } } @@ -1256,7 +1285,8 @@ const set projectsKeys = { YAML_PROJECT, YAML_FORCONTEXT, YAML_NOTFORCONTEXT, - YAML_WEST + YAML_WEST, + YAML_CMAKE }; const set projectKeys = { diff --git a/tools/projmgr/test/data/CmakeSupport/solution.csolution.yml b/tools/projmgr/test/data/CmakeSupport/solution.csolution.yml new file mode 100644 index 000000000..616fba2fa --- /dev/null +++ b/tools/projmgr/test/data/CmakeSupport/solution.csolution.yml @@ -0,0 +1,39 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/Open-CMSIS-Pack/devtools/main/tools/projmgr/schemas/csolution.schema.json +solution: + + packs: + - pack: ARM::RteTest_DFP + + compiler: AC6 + + target-types: + - type: CM0 + board: RteTest Dummy board + target-set: + - set: + images: + - project-context: core0.Debug + - project-context: explicit-core1.Debug + + build-types: + - type: Debug + + projects: + - cmake: + source: ./cmake/core0 + device: :cm0_core0 + generator: Ninja + configure: + - -DCMAKE_BUILD_TYPE=Debug + target: core0-target + images: + - image: build/core0.elf + type: elf + - image: build/core0.hex + type: hex + - image: build/core0.bin + type: bin + - cmake: + source: ./cmake/core1 + device: :cm0_core1 + project-id: explicit-core1 diff --git a/tools/projmgr/test/src/ProjMgrUnitTests.cpp b/tools/projmgr/test/src/ProjMgrUnitTests.cpp index 33edce2c0..229a5a0d4 100644 --- a/tools/projmgr/test/src/ProjMgrUnitTests.cpp +++ b/tools/projmgr/test/src/ProjMgrUnitTests.cpp @@ -7754,6 +7754,61 @@ TEST_F(ProjMgrUnitTests, WestSupport) { testinput_folder + "/WestSupport/ref/core1.Debug+CM0.cbuild.yml"); } +TEST_F(ProjMgrUnitTests, CmakeSupport) { + char* argv[5]; + const string& csolution = testinput_folder + "/CmakeSupport/solution.csolution.yml"; + argv[1] = (char*)"convert"; + argv[2] = (char*)csolution.c_str(); + argv[3] = (char*)"--active"; + argv[4] = (char*)"CM0"; + EXPECT_EQ(0, RunProjMgr(5, argv, m_envp)); + + const string base = testinput_folder + "/CmakeSupport/out/"; + const YAML::Node& cbuild0 = YAML::LoadFile(base + "core0/CM0/Debug/core0.Debug+CM0.cbuild.yml"); + const YAML::Node& cbuild1 = YAML::LoadFile(base + "explicit-core1/CM0/Debug/explicit-core1.Debug+CM0.cbuild.yml"); + EXPECT_EQ("core0", cbuild0["build"]["cmake"]["project-id"].as()); + EXPECT_EQ("../../../../cmake/core0", cbuild0["build"]["cmake"]["source"].as()); + EXPECT_EQ("Ninja", cbuild0["build"]["cmake"]["generator"].as()); + EXPECT_EQ("-DCMAKE_BUILD_TYPE=Debug", cbuild0["build"]["cmake"]["configure"][0].as()); + EXPECT_EQ("core0-target", cbuild0["build"]["cmake"]["target"].as()); + EXPECT_EQ("build/core0.elf", cbuild0["build"]["cmake"]["images"][0]["image"].as()); + EXPECT_EQ("elf", cbuild0["build"]["cmake"]["images"][0]["type"].as()); + EXPECT_EQ("build/core0.hex", cbuild0["build"]["cmake"]["images"][1]["image"].as()); + EXPECT_EQ("hex", cbuild0["build"]["cmake"]["images"][1]["type"].as()); + EXPECT_EQ("build/core0.bin", cbuild0["build"]["cmake"]["images"][2]["image"].as()); + EXPECT_EQ("bin", cbuild0["build"]["cmake"]["images"][2]["type"].as()); + EXPECT_EQ("bin", cbuild0["build"]["output"][0]["type"].as()); + EXPECT_EQ("build/core0.bin", cbuild0["build"]["output"][0]["file"].as()); + EXPECT_EQ("elf", cbuild0["build"]["output"][1]["type"].as()); + EXPECT_EQ("build/core0.elf", cbuild0["build"]["output"][1]["file"].as()); + EXPECT_EQ("hex", cbuild0["build"]["output"][2]["type"].as()); + EXPECT_EQ("build/core0.hex", cbuild0["build"]["output"][2]["file"].as()); + EXPECT_EQ("ARM::RteTest_ARMCM0_Dual:cm0_core0", cbuild0["build"]["device"].as()); + EXPECT_EQ("explicit-core1", cbuild1["build"]["cmake"]["project-id"].as()); + EXPECT_EQ("../../../../cmake/core1", cbuild1["build"]["cmake"]["source"].as()); + EXPECT_EQ("ARM::RteTest_ARMCM0_Dual:cm0_core1", cbuild1["build"]["device"].as()); + EXPECT_EQ("elf", cbuild1["build"]["output"][0]["type"].as()); + EXPECT_EQ("explicit-core1.elf", cbuild1["build"]["output"][0]["file"].as()); + EXPECT_FALSE(cbuild1["build"]["cmake"]["generator"]); + EXPECT_FALSE(cbuild1["build"]["cmake"]["configure"]); + EXPECT_FALSE(cbuild1["build"]["cmake"]["target"]); + EXPECT_FALSE(cbuild1["build"]["cmake"]["images"]); + + const YAML::Node& cbuildRun = YAML::LoadFile(base + "solution+CM0.cbuild-run.yml"); + EXPECT_EQ("elf", cbuildRun["cbuild-run"]["output"][0]["type"].as()); + EXPECT_EQ("core0/CM0/Debug/build/core0.elf", cbuildRun["cbuild-run"]["output"][0]["file"].as()); + EXPECT_EQ("hex", cbuildRun["cbuild-run"]["output"][1]["type"].as()); + EXPECT_EQ("core0/CM0/Debug/build/core0.hex", cbuildRun["cbuild-run"]["output"][1]["file"].as()); + EXPECT_EQ("bin", cbuildRun["cbuild-run"]["output"][2]["type"].as()); + EXPECT_EQ("core0/CM0/Debug/build/core0.bin", cbuildRun["cbuild-run"]["output"][2]["file"].as()); + EXPECT_EQ("elf", cbuildRun["cbuild-run"]["output"][3]["type"].as()); + EXPECT_EQ("explicit-core1/CM0/Debug/explicit-core1.elf", cbuildRun["cbuild-run"]["output"][3]["file"].as()); + + const YAML::Node& cbuildIdx = YAML::LoadFile(testinput_folder + "/CmakeSupport/solution.cbuild-idx.yml"); + EXPECT_TRUE(cbuildIdx["build-idx"]["cbuilds"][0]["cmake"].as()); + EXPECT_TRUE(cbuildIdx["build-idx"]["cbuilds"][1]["cmake"].as()); +} + TEST_F(ProjMgrUnitTests, TargetSetDependencies) { char* argv[5]; const string& csolution = testinput_folder + "/TestTargetSet/cross-dependency.csolution.yml"; From a25b6c5562e060285e78a31260e4ed67d2396242 Mon Sep 17 00:00:00 2001 From: Daniel Brondani Date: Thu, 20 Aug 2026 15:50:03 +0200 Subject: [PATCH 2/2] Apply code review findings --- tools/projmgr/src/ProjMgrYamlParser.cpp | 11 +++++++- .../test/src/ProjMgrYamlParserUnitTest.cpp | 26 +++++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/tools/projmgr/src/ProjMgrYamlParser.cpp b/tools/projmgr/src/ProjMgrYamlParser.cpp index 8e357407a..294ea6e99 100644 --- a/tools/projmgr/src/ProjMgrYamlParser.cpp +++ b/tools/projmgr/src/ProjMgrYamlParser.cpp @@ -877,6 +877,15 @@ bool ProjMgrYamlParser::ParseContexts(const YAML::Node& parent, CsolutionItem& c if (parent[YAML_PROJECTS].IsDefined()) { const YAML::Node& projectsNode = parent[YAML_PROJECTS]; for (const auto& projectsEntry : projectsNode) { + const size_t descriptorCount = + static_cast(projectsEntry[YAML_PROJECT].IsDefined()) + + static_cast(projectsEntry[YAML_WEST].IsDefined()) + + static_cast(projectsEntry[YAML_CMAKE].IsDefined()); + if (descriptorCount > 1) { + ProjMgrLogger::Get().Error("project descriptors 'project', 'west', and 'cmake' are mutually exclusive", "", + csolution.path, projectsEntry.Mark().line + 1, projectsEntry.Mark().column + 1); + return false; + } ContextDesc descriptor; if (!ParseTypeFilter(projectsEntry, descriptor.type)) { return false; @@ -912,7 +921,7 @@ bool ProjMgrYamlParser::ParseContexts(const YAML::Node& parent, CsolutionItem& c set imageTypes; for (const auto& imageEntry : cmakeEntry[YAML_IMAGES]) { CmakeImage image; - ParseString(imageEntry, YAML_IMAGE, image.image); + ParsePortablePath(imageEntry, csolution.path, YAML_IMAGE, image.image); ParseString(imageEntry, YAML_TYPE, image.type); if (!imageTypes.insert(image.type).second) { ProjMgrLogger::Get().Error("duplicate CMake image type '" + image.type + "'", "", csolution.path, diff --git a/tools/projmgr/test/src/ProjMgrYamlParserUnitTest.cpp b/tools/projmgr/test/src/ProjMgrYamlParserUnitTest.cpp index 41c703105..0a3f94d66 100644 --- a/tools/projmgr/test/src/ProjMgrYamlParserUnitTest.cpp +++ b/tools/projmgr/test/src/ProjMgrYamlParserUnitTest.cpp @@ -51,6 +51,32 @@ TEST_F(ProjMgrYamlParserUnitTests, ParseCompilerAlias) { EXPECT_EQ(compilerAliases[1], "GCC"); } +TEST_F(ProjMgrYamlParserUnitTests, RejectMultipleProjectDescriptors) { + const vector> descriptorCombinations = { + { YAML_PROJECT, YAML_WEST }, + { YAML_PROJECT, YAML_CMAKE }, + { YAML_WEST, YAML_CMAKE }, + { YAML_PROJECT, YAML_WEST, YAML_CMAKE }, + }; + + for (const auto& descriptors : descriptorCombinations) { + YAML::Node root; + YAML::Node project; + for (const auto& descriptor : descriptors) { + project[descriptor] = YAML::Node(YAML::NodeType::Map); + } + root[YAML_PROJECTS].push_back(project); + + CsolutionItem csolution; + csolution.path = "multiple-descriptors.csolution.yml"; + EXPECT_FALSE(ParseContexts(root, csolution)); + EXPECT_TRUE(csolution.contexts.empty()); + EXPECT_TRUE(csolution.cprojects.empty()); + EXPECT_TRUE(csolution.westApps.empty()); + EXPECT_TRUE(csolution.cmakeApps.empty()); + } +} + TEST_F(ProjMgrYamlParserUnitTests, ValidateCbuildSet) { string cbuildSetFile = testinput_folder + "/TestSolution/invalid_keys_test.cbuild-set.yml"; YAML::Node root = YAML::LoadFile(cbuildSetFile);