Skip to content

Commit

Permalink
[projmgr] List layers: introduce active connect handling
Browse files Browse the repository at this point in the history
  • Loading branch information
grasci-arm authored Jul 9, 2024
1 parent 65ccc21 commit 05e8868
Show file tree
Hide file tree
Showing 8 changed files with 185 additions and 43 deletions.
9 changes: 7 additions & 2 deletions tools/projmgr/include/ProjMgrUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,16 @@

#include "RteConstants.h"

/**
/**
* @brief vector of ConnectItem pointers
*/
*/
typedef std::vector<const ConnectItem*> ConnectPtrVec;

/**
* @brief map of ConnectItem active flags
*/
typedef std::map<const ConnectItem*, bool> ActiveConnectMap;

/**
* @brief connections collection item containing
* filename reference
Expand Down
7 changes: 4 additions & 3 deletions tools/projmgr/include/ProjMgrWorker.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,15 @@
* overflowed connections,
* incompatible connections,
* missed provided combined connections,
* provided connections
* active connect map,
*/
struct ConnectionsValidationResult {
bool valid;
StrVec conflicts;
StrPairVec overflows;
StrPairVec incompatibles;
std::vector<ConnectionsCollection> missedCollections;
StrPairPtrVec provides;
ActiveConnectMap activeConnectMap;
};

/**
Expand Down Expand Up @@ -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,
Expand Down
90 changes: 69 additions & 21 deletions tools/projmgr/src/ProjMgrWorker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand All @@ -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:";
Expand All @@ -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) {
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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;
}
}
}
}
}
Expand All @@ -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;
Expand Down Expand Up @@ -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) {
Expand Down
2 changes: 2 additions & 0 deletions tools/projmgr/test/data/TestLayers/genericlayers.cproject.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ project:

connections:
- connect: Project Connections
consumes:
- MultipleProvided
provides:
# compatible connections
- ExactMatch: 42 # both key and value exact match
Expand Down
13 changes: 13 additions & 0 deletions tools/projmgr/test/data/TestLayers/no-layer-provides.cproject.yml
Original file line number Diff line number Diff line change
@@ -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
13 changes: 13 additions & 0 deletions tools/projmgr/test/data/TestLayers/no-layer-provides.csolution.yml
Original file line number Diff line number Diff line change
@@ -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
27 changes: 12 additions & 15 deletions tools/projmgr/test/src/ProjMgrUnitTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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\
Expand All @@ -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\
Expand All @@ -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\
Expand All @@ -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];
Expand Down
67 changes: 65 additions & 2 deletions tools/projmgr/test/src/ProjMgrWorkerUnitTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand All @@ -1036,14 +1036,77 @@ 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);
EXPECT_EQ(result.overflows, expectedOverflow);
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();
Expand Down

0 comments on commit 05e8868

Please sign in to comment.