diff --git a/tools/projmgr/include/ProjMgrUtils.h b/tools/projmgr/include/ProjMgrUtils.h index 9075a5e09..66b098f2b 100644 --- a/tools/projmgr/include/ProjMgrUtils.h +++ b/tools/projmgr/include/ProjMgrUtils.h @@ -12,11 +12,16 @@ #include "RteConstants.h" - /** +/** * @brief vector of ConnectItem pointers - */ +*/ typedef std::vector ConnectPtrVec; +/** + * @brief map of ConnectItem active flags +*/ +typedef std::map ActiveConnectMap; + /** * @brief connections collection item containing * filename reference diff --git a/tools/projmgr/include/ProjMgrWorker.h b/tools/projmgr/include/ProjMgrWorker.h index 43d5e2dfb..9015b9e63 100644 --- a/tools/projmgr/include/ProjMgrWorker.h +++ b/tools/projmgr/include/ProjMgrWorker.h @@ -19,7 +19,7 @@ * overflowed connections, * incompatible connections, * missed provided combined connections, - * provided connections + * active connect map, */ struct ConnectionsValidationResult { bool valid; @@ -27,7 +27,7 @@ struct ConnectionsValidationResult { StrPairVec overflows; StrPairVec incompatibles; std::vector missedCollections; - StrPairPtrVec provides; + ActiveConnectMap activeConnectMap; }; /** @@ -781,7 +781,8 @@ class ProjMgrWorker { bool ProcessLayerCombinations(ContextItem& context, LayersDiscovering& discover); bool DiscoverMatchingLayers(ContextItem& context, std::string clayerSearchPath); void CollectConnections(ContextItem& context, ConnectionsCollectionVec& connections); - void GetConsumesProvides(const ConnectionsCollectionVec& collection, ConnectionsList& connections); + void GetActiveConnectMap(const ConnectionsCollectionVec& collection, ActiveConnectMap& activeConnectMap); + void SetActiveConnect(const ConnectItem* activeConnect, const ConnectionsCollectionVec& collection, ActiveConnectMap& activeConnectMap); ConnectionsCollectionMap ClassifyConnections(const ConnectionsCollectionVec& connections, BoolMap optionalTypeFlags); ConnectionsValidationResult ValidateConnections(ConnectionsCollectionVec combination); void GetAllCombinations(const ConnectionsCollectionMap& src, const ConnectionsCollectionMap::iterator& it, diff --git a/tools/projmgr/src/ProjMgrWorker.cpp b/tools/projmgr/src/ProjMgrWorker.cpp index ce106b076..f708aade5 100644 --- a/tools/projmgr/src/ProjMgrWorker.cpp +++ b/tools/projmgr/src/ProjMgrWorker.cpp @@ -828,7 +828,7 @@ bool ProjMgrWorker::ProcessLayerCombinations(ContextItem& context, LayersDiscove if (!CheckBoardDeviceInLayer(context, clayerItem)) { continue; } - ConnectionsCollection collection = {clayerItem.path, type}; + ConnectionsCollection collection = { clayerItem.path, type }; for (const auto& connect : clayerItem.connections) { collection.connections.push_back(&connect); } @@ -850,7 +850,11 @@ bool ProjMgrWorker::ProcessLayerCombinations(ContextItem& context, LayersDiscove } // validate connections combinations - for (const auto& combination : combinations) { + for (auto& combination : combinations) { + + // validate connections + ConnectionsValidationResult result = ValidateConnections(combination); + // debug message if (m_debug) { debugMsg += "\ncheck combined connections:"; @@ -859,16 +863,25 @@ bool ProjMgrWorker::ProcessLayerCombinations(ContextItem& context, LayersDiscove debugMsg += "\n " + item.filename + (type.empty() ? "" : " (layer type: " + type + ")"); for (const auto& connect : item.connections) { debugMsg += "\n " + (connect->set.empty() ? "" : "set: " + connect->set + " ") + "(" + - connect->connect + (connect->info.empty() ? "" : " - " + connect->info) + ")"; + connect->connect + (connect->info.empty() ? "" : " - " + connect->info) + ")" + (result.activeConnectMap[connect] ? "" : " ignored"); } } debugMsg += "\n"; } - // validate connections - ConnectionsValidationResult result = ValidateConnections(combination); - // update list of compatible layers if (result.valid) { + // remove inactive connects + for (auto& item : combination) { + for (auto it = item.connections.begin(); it != item.connections.end();) { + if (result.activeConnectMap[*it]) { + it++; + } else { + it = item.connections.erase(it); + } + } + } + + // update list of compatible layers context.validConnections.push_back(combination); for (const auto& [type, _] : discover.candidateClayers) { for (const auto& collection : combination) { @@ -985,7 +998,7 @@ void ProjMgrWorker::PrintConnectionsValidation(ConnectionsValidationResult resul } if (!result.missedCollections.empty()) { - msg += "provided combined connections not consumed:"; + msg += "no provided connections from this layer are consumed:"; for (const auto& missedCollection : result.missedCollections) { msg += "\n " + missedCollection.filename + (missedCollection.type.empty() ? "" : " (layer type: " + missedCollection.type + ")"); for (const auto& connect : missedCollection.connections) { @@ -1077,20 +1090,42 @@ ConnectionsCollectionMap ProjMgrWorker::ClassifyConnections(const ConnectionsCol return classifiedConnections; } -void ProjMgrWorker::GetConsumesProvides(const ConnectionsCollectionVec& collection, ConnectionsList& connections) { - // collect consumed and provided connections - ConnectPtrVec visitedConnect; +void ProjMgrWorker::GetActiveConnectMap(const ConnectionsCollectionVec& collection, ActiveConnectMap& activeConnectMap) { + // collect default active connects for (const auto& item : collection) { - for (const auto& connect : item.connections) { - if (find(visitedConnect.begin(), visitedConnect.end(), connect) != visitedConnect.end()) { - continue; + if (regex_match(item.filename, regex(".*\\.cproject\\.(yml|yaml)"))) { + // the 'connect' is always active in cproject.yml + for (const auto& connect : item.connections) { + activeConnectMap[connect] = true; } - visitedConnect.push_back(connect); - for (const auto& consumed : connect->consumes) { - connections.consumes.push_back(&consumed); + } else { + // the 'connect' is always active if it has no 'provides' + for (const auto& connect : item.connections) { + activeConnectMap[connect] = connect->provides.empty(); } - for (const auto& provided : connect->provides) { - connections.provides.push_back(&provided); + } + } + // the 'connect' is only active if one or more key listed under 'provides' is listed under 'consumes' in other active 'connect' + for (auto& [activeConnect, activeFlag] : activeConnectMap) { + if (activeFlag) { + // set active connect recursively + SetActiveConnect(activeConnect, collection, activeConnectMap); + } + } +} + +void ProjMgrWorker::SetActiveConnect(const ConnectItem* activeConnect, const ConnectionsCollectionVec& collection, ActiveConnectMap& activeConnectMap) { + // the 'connect' is only active if one or more key listed under 'provides' is listed under 'consumes' in other active 'connect' + for (const auto& consumed : activeConnect->consumes) { + for (const auto& item : collection) { + for (const auto& connect : item.connections) { + for (const auto& provided : connect->provides) { + if (provided.first == consumed.first && !activeConnectMap[connect]) { + activeConnectMap[connect] = true; + SetActiveConnect(connect, collection, activeConnectMap); + break; + } + } } } } @@ -1117,9 +1152,22 @@ bool ProjMgrWorker::ProvidedConnectionsMatch(ConnectionsCollection collection, C } ConnectionsValidationResult ProjMgrWorker::ValidateConnections(ConnectionsCollectionVec combination) { - // get connections + // get active connects + ActiveConnectMap activeConnectMap; + GetActiveConnectMap(combination, activeConnectMap); + + // collect active consumed and provided connections ConnectionsList connections; - GetConsumesProvides(combination, connections); + for (const auto& [connect, active] : activeConnectMap) { + if (active) { + for (const auto& consumed : connect->consumes) { + connections.consumes.push_back(&consumed); + } + for (const auto& provided : connect->provides) { + connections.provides.push_back(&provided); + } + } + } // elaborate provided list StrMap providedValues; @@ -1180,7 +1228,7 @@ ConnectionsValidationResult ProjMgrWorker::ValidateConnections(ConnectionsCollec // set results bool result = !conflicts.empty() || !overflows.empty() || !incompatibles.empty() || !missedCollections.empty() ? false : true; - return { result, conflicts, overflows, incompatibles, missedCollections, connections.provides }; + return { result, conflicts, overflows, incompatibles, missedCollections, activeConnectMap }; } bool ProjMgrWorker::ProcessDevice(ContextItem& context) { diff --git a/tools/projmgr/test/data/TestLayers/genericlayers.cproject.yml b/tools/projmgr/test/data/TestLayers/genericlayers.cproject.yml index 3048689f2..4c5387478 100644 --- a/tools/projmgr/test/data/TestLayers/genericlayers.cproject.yml +++ b/tools/projmgr/test/data/TestLayers/genericlayers.cproject.yml @@ -28,6 +28,8 @@ project: connections: - connect: Project Connections + consumes: + - MultipleProvided provides: # compatible connections - ExactMatch: 42 # both key and value exact match diff --git a/tools/projmgr/test/data/TestLayers/no-layer-provides.cproject.yml b/tools/projmgr/test/data/TestLayers/no-layer-provides.cproject.yml new file mode 100644 index 000000000..5258d53c1 --- /dev/null +++ b/tools/projmgr/test/data/TestLayers/no-layer-provides.cproject.yml @@ -0,0 +1,13 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/Open-CMSIS-Pack/devtools/main/tools/projmgr/schemas/cproject.schema.json + +project: + compiler: AC6 + + layers: + - type: Incompatible + optional: false + + connections: + - connect: Project Connections + provides: + - MultipleProvided diff --git a/tools/projmgr/test/data/TestLayers/no-layer-provides.csolution.yml b/tools/projmgr/test/data/TestLayers/no-layer-provides.csolution.yml new file mode 100644 index 000000000..0095b50f6 --- /dev/null +++ b/tools/projmgr/test/data/TestLayers/no-layer-provides.csolution.yml @@ -0,0 +1,13 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/Open-CMSIS-Pack/devtools/main/tools/projmgr/schemas/csolution.schema.json + +solution: + + target-types: + - type: RteTest_ARMCM3 + device: RteTest_ARMCM3 + + projects: + - project: no-layer-provides.cproject.yml + + packs: + - pack: ARM::RteTest_DFP diff --git a/tools/projmgr/test/src/ProjMgrUnitTests.cpp b/tools/projmgr/test/src/ProjMgrUnitTests.cpp index a7353968e..dc422aba4 100644 --- a/tools/projmgr/test/src/ProjMgrUnitTests.cpp +++ b/tools/projmgr/test/src/ProjMgrUnitTests.cpp @@ -1964,11 +1964,6 @@ required connections not provided:\n\ ProvidedEmpty: 123\n\ sum of required values exceed provided:\n\ AddedValueHigherThanProvided: 100 > 99\n\ -provided combined connections not consumed:\n\ - .*/ARM/RteTest_DFP/0.2.0/Layers/incompatible.clayer.yml \\(layer type: Incompatible\\)\n\ - MultipleProvided\n\ - MultipleProvidedNonIdentical0\n\ - MultipleProvidedNonIdentical1\n\ connections are invalid\n\ \n\ check combined connections:\n\ @@ -1988,11 +1983,6 @@ required connections not provided:\n\ ProvidedEmpty: 123\n\ sum of required values exceed provided:\n\ AddedValueHigherThanProvided: 100 > 99\n\ -provided combined connections not consumed:\n\ - .*/ARM/RteTest_DFP/0.2.0/Layers/incompatible.clayer.yml \\(layer type: Incompatible\\)\n\ - MultipleProvided\n\ - MultipleProvidedNonIdentical0\n\ - MultipleProvidedNonIdentical1\n\ connections are invalid\n\ \n\ check combined connections:\n\ @@ -2012,11 +2002,6 @@ required connections not provided:\n\ ProvidedEmpty: 123\n\ sum of required values exceed provided:\n\ AddedValueHigherThanProvided: 100 > 99\n\ -provided combined connections not consumed:\n\ - .*/ARM/RteTest_DFP/0.2.0/Layers/incompatible.clayer.yml \\(layer type: Incompatible\\)\n\ - MultipleProvided\n\ - MultipleProvidedNonIdentical0\n\ - MultipleProvidedNonIdentical1\n\ connections are invalid\n\ \n\ no valid combination of clayers was found\n\ @@ -2032,6 +2017,18 @@ no valid combination of clayers was found\n\ EXPECT_TRUE(regex_match(outStr, regex(expectedOutStr))); } +TEST_F(ProjMgrUnitTests, ListLayersIncompatibleNoLayerProvides) { + StdStreamRedirect streamRedirect; + char* argv[5]; + const string& csolution = testinput_folder + "/TestLayers/no-layer-provides.csolution.yml"; + argv[1] = (char*)"list"; + argv[2] = (char*)"layers"; + argv[3] = (char*)csolution.c_str(); + argv[4] = (char*)"-d"; + EXPECT_EQ(1, RunProjMgr(5, argv, m_envp)); + EXPECT_NE(string::npos, streamRedirect.GetErrorString().find("no provided connections from this layer are consumed")); +} + TEST_F(ProjMgrUnitTests, ListLayersOptionalLayerType) { StdStreamRedirect streamRedirect; char* argv[8]; diff --git a/tools/projmgr/test/src/ProjMgrWorkerUnitTests.cpp b/tools/projmgr/test/src/ProjMgrWorkerUnitTests.cpp index f6d8784b0..48331df59 100644 --- a/tools/projmgr/test/src/ProjMgrWorkerUnitTests.cpp +++ b/tools/projmgr/test/src/ProjMgrWorkerUnitTests.cpp @@ -1012,7 +1012,7 @@ TEST_F(ProjMgrWorkerUnitTests, ValidateConnections) { }; ConnectItem validConnectItem = { RteUtils::EMPTY_STRING, RteUtils::EMPTY_STRING, RteUtils::EMPTY_STRING, providedList, consumedList }; - ConnectionsCollection validCollection = { RteUtils::EMPTY_STRING, RteUtils::EMPTY_STRING, {&validConnectItem} }; + ConnectionsCollection validCollection = { ".cproject.yml", RteUtils::EMPTY_STRING, {&validConnectItem} }; result = ValidateConnections({ validCollection }); EXPECT_TRUE(result.valid); @@ -1036,7 +1036,7 @@ TEST_F(ProjMgrWorkerUnitTests, ValidateConnections) { StrPairVec expectedOverflow = {{"Lemon", "170 > 160"}}; StrPairVec expectedIncompatibles = {{"Ananas", "98"}, {"Grape Fruit", "1"}}; ConnectItem invalidConnectItem = { RteUtils::EMPTY_STRING, RteUtils::EMPTY_STRING, RteUtils::EMPTY_STRING, providedList, consumedList }; - ConnectionsCollection invalidCollection = { RteUtils::EMPTY_STRING, RteUtils::EMPTY_STRING, {&invalidConnectItem} }; + ConnectionsCollection invalidCollection = { ".cproject.yml", RteUtils::EMPTY_STRING, {&invalidConnectItem} }; result = ValidateConnections({ invalidCollection }); EXPECT_FALSE(result.valid); EXPECT_EQ(result.conflicts, expectedConflicts); @@ -1044,6 +1044,69 @@ TEST_F(ProjMgrWorkerUnitTests, ValidateConnections) { EXPECT_EQ(result.incompatibles, expectedIncompatibles); } +TEST_F(ProjMgrWorkerUnitTests, ValidateActiveConnects) { + StrPairVec consumedList1 = {{ "Ananas", "" }}; + StrPairVec consumedList2 = {{ "Banana", "" }}; + StrPairVec consumedList3 = {{ "Cherry", "" }}; + StrPairVec providedList1 = consumedList1; + StrPairVec providedList2 = consumedList2; + StrPairVec providedList3 = consumedList3; + ConnectItem empty = { RteUtils::EMPTY_STRING, RteUtils::EMPTY_STRING, RteUtils::EMPTY_STRING, StrPairVec() , StrPairVec() }; + ConnectItem consumed1 = { RteUtils::EMPTY_STRING, RteUtils::EMPTY_STRING, RteUtils::EMPTY_STRING, StrPairVec() , consumedList1 }; + ConnectItem provided1 = { RteUtils::EMPTY_STRING, RteUtils::EMPTY_STRING, RteUtils::EMPTY_STRING, providedList1, StrPairVec() }; + ConnectItem consumed2 = { RteUtils::EMPTY_STRING, RteUtils::EMPTY_STRING, RteUtils::EMPTY_STRING, StrPairVec() , consumedList2 }; + ConnectItem provided2 = { RteUtils::EMPTY_STRING, RteUtils::EMPTY_STRING, RteUtils::EMPTY_STRING, providedList2, StrPairVec() }; + ConnectItem consumed2_provided1 = { RteUtils::EMPTY_STRING, RteUtils::EMPTY_STRING, RteUtils::EMPTY_STRING, providedList1, consumedList2 }; + ConnectItem consumed2_provided3 = { RteUtils::EMPTY_STRING, RteUtils::EMPTY_STRING, RteUtils::EMPTY_STRING, providedList3, consumedList2 }; + + // test with active cproject connect + ConnectionsCollectionVec collectionVec1 = { + { "0.cproject.yml", RteUtils::EMPTY_STRING, { &consumed1 }}, + { "1.clayer.yml" , RteUtils::EMPTY_STRING, { &consumed2_provided1 }}, + { "2.clayer.yml" , RteUtils::EMPTY_STRING, { &provided2 }}, + }; + ConnectionsValidationResult result1 = ValidateConnections(collectionVec1); + EXPECT_TRUE(result1.valid); + EXPECT_TRUE(result1.activeConnectMap[&consumed1]); + EXPECT_TRUE(result1.activeConnectMap[&consumed2_provided1]); + EXPECT_TRUE(result1.activeConnectMap[&provided2]); + + // test with active cproject connect and inactive layer connect + ConnectionsCollectionVec collectionVec2 = { + { "0.cproject.yml", RteUtils::EMPTY_STRING, { &consumed1 }}, + { "1.clayer.yml" , RteUtils::EMPTY_STRING, { &consumed2_provided3, &provided1 }}, + }; + ConnectionsValidationResult result2 = ValidateConnections(collectionVec2); + EXPECT_TRUE(result2.valid); + EXPECT_TRUE(result2.activeConnectMap[&consumed1]); + EXPECT_TRUE(result2.activeConnectMap[&provided1]); + + // test with empty cproject connect and active layer and matching connects + ConnectionsCollectionVec collectionVec3 = { + { "0.cproject.yml", RteUtils::EMPTY_STRING, { &empty }}, + { "1.clayer.yml" , RteUtils::EMPTY_STRING, { &consumed1 }}, + { "2.clayer.yml" , RteUtils::EMPTY_STRING, { &consumed2_provided1 }}, + { "3.clayer.yml" , RteUtils::EMPTY_STRING, { &provided2 }}, + }; + ConnectionsValidationResult result3 = ValidateConnections(collectionVec3); + EXPECT_TRUE(result3.valid); + EXPECT_TRUE(result3.activeConnectMap[&empty]); + EXPECT_TRUE(result3.activeConnectMap[&consumed1]); + EXPECT_TRUE(result3.activeConnectMap[&consumed2_provided1]); + EXPECT_TRUE(result3.activeConnectMap[&provided2]); + + // test with empty cproject connect and mismatching provided vs consumed + ConnectionsCollectionVec collectionVec4 = { + { "0.cproject.yml", RteUtils::EMPTY_STRING, { &empty }}, + { "1.clayer.yml" , RteUtils::EMPTY_STRING, { &consumed1 }}, + { "2.clayer.yml" , RteUtils::EMPTY_STRING, { &provided2 }}, + }; + ConnectionsValidationResult result4 = ValidateConnections(collectionVec4); + EXPECT_FALSE(result4.valid); + EXPECT_EQ(consumedList1.front(), result4.incompatibles.front()); + EXPECT_EQ(providedList2.front(), result4.missedCollections.front().connections.front()->provides.front()); +} + TEST_F(ProjMgrWorkerUnitTests, CollectLayersFromPacks) { // test CollectLayersFromPacks with an non-existent clayer file InitializeModel();