From e543c2124d282f61e3e4fb5b0ab19f87496b95f6 Mon Sep 17 00:00:00 2001 From: Evgueni Driouk Date: Thu, 25 Jun 2026 09:27:34 +0200 Subject: [PATCH 1/8] Purge explicit packs on solution load --- libs/rtemodel/src/RteKernel.cpp | 6 +- libs/rtemodel/src/RteModel.cpp | 12 +- libs/rtemodel/src/RtePackage.cpp | 2 +- tools/projmgr/src/ProjMgrRpcServer.cpp | 13 +- tools/projmgr/src/ProjMgrWorker.cpp | 2 +- tools/projmgr/test/src/ProjMgrRpcTests.cpp | 165 ++++++++++++--------- tools/projmgr/test/src/ProjMgrTestEnv.cpp | 11 +- 7 files changed, 124 insertions(+), 87 deletions(-) diff --git a/libs/rtemodel/src/RteKernel.cpp b/libs/rtemodel/src/RteKernel.cpp index db8dcaffb..9140ddc9d 100644 --- a/libs/rtemodel/src/RteKernel.cpp +++ b/libs/rtemodel/src/RteKernel.cpp @@ -477,11 +477,7 @@ bool RteKernel::LoadAndInsertPacks(std::list& packs, std::listGetID(), packs); - if(!loadedPack || (loadedPack->GetPackageState() == PS_INSTALLED && pack->GetPackageState() == PS_EXPLICIT_PATH)) { - newPacks.push_back(pack); - } + newPacks.push_back(pack); } globalModel->InsertPacks(newPacks); diff --git a/libs/rtemodel/src/RteModel.cpp b/libs/rtemodel/src/RteModel.cpp index d982e378e..6411e67e4 100644 --- a/libs/rtemodel/src/RteModel.cpp +++ b/libs/rtemodel/src/RteModel.cpp @@ -392,17 +392,9 @@ void RteModel::InsertPack(RtePackage* package) } auto state = package->GetPackageState(); if(insertedPack->GetPackageState() == state) { - string pdscPath = RteFsUtils::MakePathCanonical(package->GetAbsolutePackagePath()); - if(pdscPath.find(m_rtePath) == 0) { // regular installed pack => error // duplicate, kept it in a temporary collection till validate; - m_packageDuplicates.push_back(package); - return; - } - string insertedPdscPath = RteFsUtils::MakePathCanonical(insertedPack->GetAbsolutePackagePath()); - if(insertedPdscPath.find(m_rtePath) == string::npos) { // inserted pack is also from outside => error - m_packageDuplicates.push_back(package); - return; - } + m_packageDuplicates.push_back(package); + return; } else if(state != PS_EXPLICIT_PATH) { return; // pack with explicit path must override installed pack } diff --git a/libs/rtemodel/src/RtePackage.cpp b/libs/rtemodel/src/RtePackage.cpp index 323ac77e8..bc243bb79 100644 --- a/libs/rtemodel/src/RtePackage.cpp +++ b/libs/rtemodel/src/RtePackage.cpp @@ -1531,7 +1531,7 @@ bool RtePackRegistry::PurgePacks() { set toErase; // collect packs that no longer exist for(auto& [pdscFile, pack] : m_loadedPacks) { - if(!pack || !pack->Exists()) { + if(!pack || pack->GetPackageState() == PackageState::PS_EXPLICIT_PATH || !pack->Exists()) { toErase.insert(pdscFile); } } diff --git a/tools/projmgr/src/ProjMgrRpcServer.cpp b/tools/projmgr/src/ProjMgrRpcServer.cpp index 56941e5b4..6b6f853ef 100644 --- a/tools/projmgr/src/ProjMgrRpcServer.cpp +++ b/tools/projmgr/src/ProjMgrRpcServer.cpp @@ -320,8 +320,17 @@ RpcArgs::SuccessResult RpcHandler::LoadSolution(const string& solution, const st m_bUseAllPacks = false; // loading solution will first use only listed packs m_packReferences.clear(); // will be updated m_solutionLoaded = false; // assume not loaded yet - // clear only projects, global RTE data and packs stay loaded - ProjMgrKernel::Get()->GetGlobalModel()->ClearProjects(); + auto globalModel = ProjMgrKernel::Get()->GetGlobalModel(); + // remove non-existing and explicit packs + bool purged = globalModel->GetPackRegistry()->PurgePacks(); + if(purged) { + // clear project AND global RTE data, packs stay loaded + globalModel->Clear(); + } else { + // clear only projects, global RTE data and packs stay loaded + globalModel->ClearProjects(); + } + RpcArgs::SuccessResult result = {false}; const auto csolutionFile = RteFsUtils::MakePathCanonical(solution); if(!regex_match(csolutionFile, regex(".*\\.csolution\\.(yml|yaml)"))) { diff --git a/tools/projmgr/src/ProjMgrWorker.cpp b/tools/projmgr/src/ProjMgrWorker.cpp index f8379bbdf..c27175293 100644 --- a/tools/projmgr/src/ProjMgrWorker.cpp +++ b/tools/projmgr/src/ProjMgrWorker.cpp @@ -548,7 +548,7 @@ bool ProjMgrWorker::LoadAllRelevantPacks() { return false; } // Get installed packs - if (pdscFiles.empty() || (m_loadPacksPolicy == LoadPacksPolicy::ALL) || (m_loadPacksPolicy == LoadPacksPolicy::LATEST)) { + if (m_rpcMode || pdscFiles.empty() || (m_loadPacksPolicy == LoadPacksPolicy::ALL) || (m_loadPacksPolicy == LoadPacksPolicy::LATEST)) { const bool latest = (m_loadPacksPolicy == LoadPacksPolicy::LATEST) || (m_loadPacksPolicy == LoadPacksPolicy::DEFAULT); if (!m_kernel->GetEffectivePdscFiles(pdscFiles, latest)) { ProjMgrLogger::Get().Error("parsing installed packs failed"); diff --git a/tools/projmgr/test/src/ProjMgrRpcTests.cpp b/tools/projmgr/test/src/ProjMgrRpcTests.cpp index c7114e741..350e83dc8 100644 --- a/tools/projmgr/test/src/ProjMgrRpcTests.cpp +++ b/tools/projmgr/test/src/ProjMgrRpcTests.cpp @@ -33,10 +33,17 @@ class ProjMgrRpcTests : public ProjMgr, public ::testing::Test { string CreateLoadRequests(const string& solution, const string& activeTarget = RteUtils::EMPTY_STRING, - const vector& contextList = RteUtils::EMPTY_STRING_VECTOR + const vector& contextList = RteUtils::EMPTY_STRING_VECTOR, + bool loadPacks = true ); + + string FormatPackInfoRequests(const string& solution, const string& context, bool loadPacks); + + vector RunPackInfoRequests(const string& solution, const string& context, bool loadPacks); + bool CompareRpcResponse(const json& response, const string& ref); + int m_id = 0; }; string ProjMgrRpcTests::FormatRequest(const int id, const string& method, const json& params = json()) { @@ -50,12 +57,18 @@ string ProjMgrRpcTests::FormatRequest(const int id, const string& method, const return request.dump(); } -string ProjMgrRpcTests::CreateLoadRequests(const string& solution, const string& activeTarget, const vector& contextList) +string ProjMgrRpcTests::CreateLoadRequests(const string& solution, const string& activeTarget, const vector& contextList, bool loadPacks) { - string loadSolutionRequest; + + string requests; + if(loadPacks) { + m_id = 0; + requests = FormatRequest(++m_id, "LoadPacks"); + } if(!solution.empty()) { + string loadSolutionRequest; auto csolutionPath = testinput_folder + solution; - loadSolutionRequest = FormatRequest(2, "LoadSolution", json({{ "solution", csolutionPath }, { "activeTarget", activeTarget }})); + loadSolutionRequest = FormatRequest(++m_id, "LoadSolution", json({{ "solution", csolutionPath }, { "activeTarget", activeTarget }})); if(!contextList.empty()) { YAML::Node cbuildset; cbuildset["cbuild-set"]["generated-by"] = "ProjMrgUnitTests"; @@ -70,8 +83,24 @@ string ProjMgrRpcTests::CreateLoadRequests(const string& solution, const string& cbuildsetFile << cbuildset << std::endl; cbuildsetFile.close(); } + requests += loadSolutionRequest; } - return FormatRequest(1, "LoadPacks") + loadSolutionRequest; + return requests; +} + +string ProjMgrRpcTests::FormatPackInfoRequests(const string& solution, const string& context, bool loadPacks) { + vector contextList = { + context + }; + auto requests = CreateLoadRequests(solution, "", contextList, loadPacks); + requests += FormatRequest(++m_id, "GetUsedItems", json({{ "context", context }})); + requests += FormatRequest(++m_id, "GetPacksInfo", json({{ "context", context }, {"all", false}})); + requests += FormatRequest(++m_id, "GetPacksInfo", json({{ "context", context }, {"all", true}})); + return requests; +} + +vector ProjMgrRpcTests::RunPackInfoRequests(const string& solution, const string& context, bool loadPacks = true) { + return RunRpcMethods( FormatPackInfoRequests(solution, context, loadPacks)); } bool ProjMgrRpcTests::CompareRpcResponse(const json& response, const string& ref) { @@ -92,6 +121,7 @@ vector ProjMgrRpcTests::RunRpcMethods(const string& strIn) { while(getline(iss, line)) { responses.push_back(json::parse(line)); } + m_id = 0; return responses; } @@ -1122,7 +1152,7 @@ TEST_F(ProjMgrRpcTests, PackReferenceMissing) { requests += FormatRequest(3, "GetUsedItems", json({{ "context", context }})); const auto& responses = RunRpcMethods(requests); EXPECT_TRUE(responses[2]["result"]["success"]); - auto packs = responses[2]["result"]["packs"]; + auto packs = responses[2]["result"]["packs"]; // Verify missing field for unresolved packs for(const auto& pack : packs) { if(!pack.contains("resolvedPack")) { @@ -1135,16 +1165,8 @@ TEST_F(ProjMgrRpcTests, PackReferenceMissing) { TEST_F(ProjMgrRpcTests, RpcGetPacksInfoSimple) { string context = "selectable+CM0"; - vector contextList = { - context - }; - auto requests = CreateLoadRequests("/Validation/dependencies.csolution.yml", "", contextList); - requests += FormatRequest(3, "GetUsedItems", json({{ "context", context }})); - requests += FormatRequest(4, "GetPacksInfo", json({{ "context", context }, {"all", false}})); - requests += FormatRequest(5, "GetPacksInfo", json({{ "context", context }, {"all", true}})); - - const auto& responses = RunRpcMethods(requests); + const auto& responses = RunPackInfoRequests("/Validation/dependencies.csolution.yml", context); EXPECT_TRUE(responses[2]["result"]["success"]); auto packs = responses[2]["result"]["packs"]; @@ -1174,16 +1196,7 @@ TEST_F(ProjMgrRpcTests, RpcGetPacksInfoSimple) { TEST_F(ProjMgrRpcTests, RpcGetPacksInfo) { string context = "test1.Release+CM0"; - vector contextList = { - context - }; - - auto requests = CreateLoadRequests("/TestSolution/test_pack_requirements.csolution.yml", "", contextList); - requests += FormatRequest(3, "GetUsedItems", json({{ "context", context }})); - requests += FormatRequest(4, "GetPacksInfo", json({{ "context", context }, {"all", false}})); - requests += FormatRequest(5, "GetPacksInfo", json({{ "context", context }, {"all", true}})); - - const auto& responses = RunRpcMethods(requests); + const auto& responses = RunPackInfoRequests("/TestSolution/test_pack_requirements.csolution.yml", context); EXPECT_TRUE(responses[2]["result"]["success"]); auto packs = responses[2]["result"]["packs"]; @@ -1203,17 +1216,65 @@ TEST_F(ProjMgrRpcTests, RpcGetPacksInfo) { EXPECT_FALSE(packInfos[7].contains("used")); } -TEST_F(ProjMgrRpcTests, RpcGetPacksInfoMissing) { - string context = "project+Miss"; - vector contextList = { - context - }; - auto requests = CreateLoadRequests("/TestSolution/PackMissing/missing_pack.csolution.yml", "", contextList); - requests += FormatRequest(3, "GetUsedItems", json({{ "context", context }})); - requests += FormatRequest(4, "GetPacksInfo", json({{ "context", context }, {"all", false}})); +TEST_F(ProjMgrRpcTests, RpcGetPacksInfoPackPath) { + string context = "pack_path+CM0"; - const auto& responses = RunRpcMethods(requests); + // first solution + auto requests = FormatPackInfoRequests("/TestSolution/pack_path.csolution.yml", context, true); + // second solution, same pack in a different directory + requests += FormatPackInfoRequests("/TestSolution/pack_path1.csolution.yml", context, false); + + auto responses = RunRpcMethods(requests); + + // first solution + auto expectedPackPath = testinput_folder + "/SolutionSpecificPack"; + auto expectedDoc = testinput_folder + "/SolutionSpecificPack" + "/Doc/overview.md"; + + EXPECT_TRUE(responses[2]["result"]["success"]); + auto packs = responses[2]["result"]["packs"]; + EXPECT_EQ(packs[0]["pack"], "ARM::RteTest_DFP"); + EXPECT_EQ(packs[0]["resolvedPack"], "ARM::RteTest_DFP"); + EXPECT_EQ(packs[0]["path"], expectedPackPath); + EXPECT_FALSE(packs[0].contains("upgrade")); + + EXPECT_TRUE(responses[3]["result"]["success"]); // get pack infos + auto packInfos = responses[3]["result"]["packs"]; + EXPECT_EQ(packInfos.size(), 1); + EXPECT_EQ(packInfos[0]["id"], "ARM::RteTest_DFP@0.2.0"); + EXPECT_EQ(packInfos[0]["doc"], expectedDoc); + EXPECT_TRUE(packInfos[0]["used"]); + + packInfos = responses[4]["result"]["packs"]; + EXPECT_EQ(packInfos.size(), 8); + +// second solution + + expectedPackPath = testinput_folder + "/SolutionSpecificPack1"; + expectedDoc = testinput_folder + "/SolutionSpecificPack1" + "/Doc/overview.md"; + + EXPECT_TRUE(responses[6]["result"]["success"]); + packs = responses[6]["result"]["packs"]; + EXPECT_EQ(packs[0]["pack"], "ARM::RteTest_DFP"); + EXPECT_EQ(packs[0]["resolvedPack"], "ARM::RteTest_DFP"); + EXPECT_EQ(packs[0]["path"], expectedPackPath); + EXPECT_FALSE(packs[0].contains("upgrade")); + + EXPECT_TRUE(responses[7]["result"]["success"]); // get pack infos + packInfos = responses[7]["result"]["packs"]; + EXPECT_EQ(packInfos.size(), 1); + EXPECT_EQ(packInfos[0]["id"], "ARM::RteTest_DFP@0.2.0"); + EXPECT_EQ(packInfos[0]["doc"], expectedDoc); + EXPECT_TRUE(packInfos[0]["used"]); + + packInfos = responses[8]["result"]["packs"]; + EXPECT_EQ(packInfos.size(), 8); +} + + +TEST_F(ProjMgrRpcTests, RpcGetPacksInfoMissing) { + string context = "project+Miss"; + const auto& responses = RunPackInfoRequests("/TestSolution/PackMissing/missing_pack.csolution.yml", context); EXPECT_TRUE(responses[2]["result"]["success"]); auto packs = responses[2]["result"]["packs"]; @@ -1230,8 +1291,6 @@ TEST_F(ProjMgrRpcTests, RpcGetPacksInfoMissing) { EXPECT_EQ(packs[3]["pack"], "ARM::RteTest_DFP"); EXPECT_EQ(packs[3]["resolvedPack"], "ARM::RteTest_DFP@0.2.0"); - - EXPECT_TRUE(responses[3]["result"]["success"]); // get pack infos auto packInfos = responses[3]["result"]["packs"]; EXPECT_EQ(packInfos.size(), 4); @@ -1242,20 +1301,9 @@ TEST_F(ProjMgrRpcTests, RpcGetPacksInfoMissing) { EXPECT_EQ(packInfos[3]["id"], "ARM::RteTest_DFP@0.2.0"); } - - TEST_F(ProjMgrRpcTests, RpcGetPacksNotLatest) { string context = "test1.DebugOldDfp+CM0"; - vector contextList = { - context - }; - - auto requests = CreateLoadRequests("/TestSolution/test_pack_requirements.csolution.yml", "", contextList); - requests += FormatRequest(3, "GetUsedItems", json({{ "context", context }})); - requests += FormatRequest(4, "GetPacksInfo", json({{ "context", context }, {"all", false}})); - requests += FormatRequest(5, "GetPacksInfo", json({{ "context", context }, {"all", true}})); - - const auto& responses = RunRpcMethods(requests); + const auto& responses = RunPackInfoRequests("/TestSolution/test_pack_requirements.csolution.yml", context); EXPECT_TRUE(responses[2]["result"]["success"]); auto packs = responses[2]["result"]["packs"]; @@ -1293,16 +1341,7 @@ TEST_F(ProjMgrRpcTests, RpcGetPacksNotLatest) { TEST_F(ProjMgrRpcTests, RpcGetPacksInfoLayer) { string context = "packs.CompatibleLayers+RteTest_ARMCM3"; - vector contextList = { - context - }; - - auto requests = CreateLoadRequests("/TestLayers/packs.csolution.yml", "", contextList); - requests += FormatRequest(3, "GetUsedItems", json({{ "context", context }})); - requests += FormatRequest(4, "GetPacksInfo", json({{ "context", context }, {"all", false}})); - requests += FormatRequest(5, "GetPacksInfo", json({{ "context", context }, {"all", true}})); - - const auto& responses = RunRpcMethods(requests); + const auto& responses = RunPackInfoRequests("/TestLayers/packs.csolution.yml", context); EXPECT_TRUE(responses[2]["result"]["success"]); auto packs = responses[2]["result"]["packs"]; @@ -1329,15 +1368,7 @@ TEST_F(ProjMgrRpcTests, RpcGetPacksInfoLayer) { TEST_F(ProjMgrRpcTests, RpcGetPacksInfoLocal) { string context = "incompatible.Debug+CM0"; - vector contextList = { - context - }; - - auto requests = CreateLoadRequests("/TestSolution/PackRequirements/incompatible.csolution.yml", "", contextList); - requests += FormatRequest(3, "GetUsedItems", json({{ "context", context }})); - requests += FormatRequest(4, "GetPacksInfo", json({{ "context", context }, {"all", false}})); - - const auto& responses = RunRpcMethods(requests); + const auto& responses = RunPackInfoRequests("/TestSolution/PackRequirements/incompatible.csolution.yml", context); EXPECT_TRUE(responses[2]["result"]["success"]); auto packs = responses[2]["result"]["packs"]; diff --git a/tools/projmgr/test/src/ProjMgrTestEnv.cpp b/tools/projmgr/test/src/ProjMgrTestEnv.cpp index e32099f58..e06d6b75c 100644 --- a/tools/projmgr/test/src/ProjMgrTestEnv.cpp +++ b/tools/projmgr/test/src/ProjMgrTestEnv.cpp @@ -125,6 +125,15 @@ void ProjMgrTestEnv::SetUp() { } RteFsUtils::CreateDirectories(destPackPath); fs::copy(fs::path(srcPackPath), fs::path(destPackPath), fs::copy_options::recursive, ec); + + destPackPath = testinput_folder + "/SolutionSpecificPack1"; + if (RteFsUtils::Exists(destPackPath)) { + RteFsUtils::RemoveDir(destPackPath); + } + RteFsUtils::CreateDirectories(destPackPath); + fs::copy(fs::path(srcPackPath), fs::path(destPackPath), fs::copy_options::recursive, ec); + + srcPackPath = testcmsispack_folder + "/ARM/RteTest/0.1.0"; destPackPath = testinput_folder + "/SolutionSpecificPack2"; if (RteFsUtils::Exists(destPackPath)) { @@ -243,7 +252,7 @@ std::map ProjMgrTestEnv::GetEffe rteKernel.SetCmsisPackRoot(GetCmsisPackRoot()); std::list pdscFiles; std::list packs; - rteKernel.GetEffectivePdscFiles(pdscFiles, bLatestsOnly); + rteKernel.GetEffectivePdscFiles(pdscFiles, bLatestsOnly); rteKernel.LoadAndInsertPacks(packs, pdscFiles); for (const auto& pack : packs) { pdscMap[pack->GetID()] = pack->GetPackageFileName(); From c942ffad2f1d1954a45ef9a248f3f0aefa3eb4ad Mon Sep 17 00:00:00 2001 From: Evgueni Driouk Date: Thu, 25 Jun 2026 09:45:32 +0200 Subject: [PATCH 2/8] Test data file added --- .../test/data/TestSolution/pack_path1.csolution.yml | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 tools/projmgr/test/data/TestSolution/pack_path1.csolution.yml diff --git a/tools/projmgr/test/data/TestSolution/pack_path1.csolution.yml b/tools/projmgr/test/data/TestSolution/pack_path1.csolution.yml new file mode 100644 index 000000000..ec82e5662 --- /dev/null +++ b/tools/projmgr/test/data/TestSolution/pack_path1.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: CM0 + device: RteTest_ARMCM0 + + packs: + - pack: ARM::RteTest_DFP + path: ../SolutionSpecificPack1 + + projects: + - project: pack_path.cproject.yml From 4c141a653a2ac8cf339b606c131ee63855d16ad1 Mon Sep 17 00:00:00 2001 From: Evgueni Driouk Date: Thu, 25 Jun 2026 14:22:47 +0200 Subject: [PATCH 3/8] PurgeModel --- libs/rtemodel/include/RteModel.h | 7 +++++++ libs/rtemodel/include/RtePackage.h | 5 +++-- libs/rtemodel/src/RteKernel.cpp | 2 -- libs/rtemodel/src/RteModel.cpp | 9 ++++++++- libs/rtemodel/src/RtePackage.cpp | 4 ++-- libs/rtemodel/test/src/RteModelTest.cpp | 11 ++++++++-- tools/projmgr/src/ProjMgrRpcServer.cpp | 27 ++++++++++++++++--------- 7 files changed, 46 insertions(+), 19 deletions(-) diff --git a/libs/rtemodel/include/RteModel.h b/libs/rtemodel/include/RteModel.h index 21f54289c..a0d78e667 100644 --- a/libs/rtemodel/include/RteModel.h +++ b/libs/rtemodel/include/RteModel.h @@ -616,6 +616,13 @@ class RteGlobalModel : public RteModel */ void ClearModel() override; + /** + * @brief purges non-existing and explicit packs, clears model and projects if at least one pack has been deleted + * @param purgeExplicit flag to remove all PackageState::PS_EXPLICIT_PATH packs + * @returns true if at leas one pack has been removed + */ + bool PurgeModel(bool purgeExplicit); + /** * @brief setter for RteCallback object * @param callback given RteCallback object to set diff --git a/libs/rtemodel/include/RtePackage.h b/libs/rtemodel/include/RtePackage.h index bafacacf0..68e3f405d 100644 --- a/libs/rtemodel/include/RtePackage.h +++ b/libs/rtemodel/include/RtePackage.h @@ -1336,10 +1336,11 @@ class RtePackRegistry bool ErasePack(const std::string& pdscFile); /** - * @brief removes all non-existing packs + * @brief removes all non-existing packs and optionally explicitly specified packs + * @param purgeExplicit flag to remove all PackageState::PS_EXPLICIT_PATH packs * @return true if at least one pack was removed */ - bool PurgePacks(); + bool PurgePacks(bool purgeExplicit); /** * @brief get collection of loaded packs diff --git a/libs/rtemodel/src/RteKernel.cpp b/libs/rtemodel/src/RteKernel.cpp index 9140ddc9d..c95f3586b 100644 --- a/libs/rtemodel/src/RteKernel.cpp +++ b/libs/rtemodel/src/RteKernel.cpp @@ -451,8 +451,6 @@ bool RteKernel::GetEffectivePdscFilesAsMap(map& pdscFiles, bool latest) const { - GetPackRegistry()->PurgePacks(); // remove non-existing files from the registry - map pdscMap; if(!GetEffectivePdscFilesAsMap(pdscMap, latest)) { return false; diff --git a/libs/rtemodel/src/RteModel.cpp b/libs/rtemodel/src/RteModel.cpp index 6411e67e4..54e230288 100644 --- a/libs/rtemodel/src/RteModel.cpp +++ b/libs/rtemodel/src/RteModel.cpp @@ -834,7 +834,14 @@ void RteGlobalModel::ClearModel() { ClearProjectTargets(); RteModel::ClearModel(); - m_packRegistry->PurgePacks(); // pack loading is expensive, only remove deleted +} + +bool RteGlobalModel::PurgeModel(bool purgeExplicit) { + if(m_packRegistry->PurgePacks(purgeExplicit)) { + Clear(); + return true; + } + return false; } diff --git a/libs/rtemodel/src/RtePackage.cpp b/libs/rtemodel/src/RtePackage.cpp index bc243bb79..276caf916 100644 --- a/libs/rtemodel/src/RtePackage.cpp +++ b/libs/rtemodel/src/RtePackage.cpp @@ -1525,13 +1525,13 @@ bool RtePackRegistry::ErasePack(const std::string& pdscFile) return false; } -bool RtePackRegistry::PurgePacks() { +bool RtePackRegistry::PurgePacks(bool purgeExplicit) { ClearPdscMap(); // clear because packs can be added or removed after this call set toErase; // collect packs that no longer exist for(auto& [pdscFile, pack] : m_loadedPacks) { - if(!pack || pack->GetPackageState() == PackageState::PS_EXPLICIT_PATH || !pack->Exists()) { + if(!pack || !pack->Exists() || (purgeExplicit && pack->GetPackageState() == PackageState::PS_EXPLICIT_PATH )) { toErase.insert(pdscFile); } } diff --git a/libs/rtemodel/test/src/RteModelTest.cpp b/libs/rtemodel/test/src/RteModelTest.cpp index bfefcc745..67bd397ca 100644 --- a/libs/rtemodel/test/src/RteModelTest.cpp +++ b/libs/rtemodel/test/src/RteModelTest.cpp @@ -40,14 +40,14 @@ TEST_F(RteModelTestConfig, PackRegistry) { EXPECT_TRUE(packRegistry->AddPack(pack)); EXPECT_FALSE(packRegistry->AddPack(pack)); // not second time EXPECT_EQ(packRegistry->GetPack("foo"), pack); - pack = new RtePackage(&testModel); + pack = new RtePackage(&testModel, PackageState::PS_EXPLICIT_PATH); pack->SetAttribute("name", "bar"); pack->SetRootFileName("foo"); EXPECT_TRUE(packRegistry->AddPack(pack, true)); EXPECT_EQ(packRegistry->GetPack("foo"), pack); EXPECT_EQ(packRegistry->GetLoadedPacks().size(), 1); - EXPECT_TRUE(packRegistry->PurgePacks()); + EXPECT_TRUE(packRegistry->PurgePacks(false)); // deleted because not exists EXPECT_EQ(packRegistry->GetPack("foo"), nullptr); EXPECT_FALSE(packRegistry->ErasePack("foo")); // already erased via Purge EXPECT_EQ(packRegistry->GetLoadedPacks().size(), 0); @@ -128,6 +128,13 @@ TEST_F(RteModelTestConfig, PackRegistryLoadPacks) { auto t1 = pack1->GetModificationTime(); EXPECT_NE(t, t1); EXPECT_EQ(pack1->GetFirstChild("dummy_child"), nullptr); // pack got loaded again => no added child + + // test RteGlobalModel::PurgeModel + pack1->SetPackageState(PackageState::PS_EXPLICIT_PATH); // simulate pack is explicit + auto globalModel = rteKernel.GetGlobalModel(); + + EXPECT_FALSE(globalModel->PurgeModel(false)); + EXPECT_TRUE(globalModel->PurgeModel(true)); } TEST(RteModelTest, LoadPacks) { diff --git a/tools/projmgr/src/ProjMgrRpcServer.cpp b/tools/projmgr/src/ProjMgrRpcServer.cpp index 6b6f853ef..94ff3701c 100644 --- a/tools/projmgr/src/ProjMgrRpcServer.cpp +++ b/tools/projmgr/src/ProjMgrRpcServer.cpp @@ -305,7 +305,10 @@ RpcArgs::SuccessResult RpcHandler::LoadPacks(void) { m_manager.Clear(); m_solutionLoaded = false; // clear project and global RTE data, packs stay loaded - ProjMgrKernel::Get()->GetGlobalModel()->Clear(); + auto globalModel = ProjMgrKernel::Get()->GetGlobalModel(); + globalModel->Clear(); + globalModel->PurgeModel(true); // clears also explicit and non-existing packs + m_worker.InitializeModel(); m_worker.SetLoadPacksPolicy(LoadPacksPolicy::ALL); result.success = m_worker.LoadAllRelevantPacks(); @@ -321,13 +324,11 @@ RpcArgs::SuccessResult RpcHandler::LoadSolution(const string& solution, const st m_packReferences.clear(); // will be updated m_solutionLoaded = false; // assume not loaded yet auto globalModel = ProjMgrKernel::Get()->GetGlobalModel(); - // remove non-existing and explicit packs - bool purged = globalModel->GetPackRegistry()->PurgePacks(); - if(purged) { - // clear project AND global RTE data, packs stay loaded - globalModel->Clear(); - } else { - // clear only projects, global RTE data and packs stay loaded + // remove non-existing and explicit packs + // clear model and projects if at least one pack is deleted + bool purged = globalModel->PurgeModel(true); + if(!purged) { + // just only projects, global RTE data and packs stay loaded globalModel->ClearProjects(); } @@ -879,8 +880,14 @@ RpcArgs::ConvertSolutionResult RpcHandler::ConvertSolution(const string& solutio m_bUseAllPacks = false; // loading solution will first use only listed packs m_packReferences.clear(); // will be updated m_solutionLoaded = false; // assume not loaded - // clear only projects, RTE data and packs stay loaded - ProjMgrKernel::Get()->GetGlobalModel()->ClearProjects(); + auto globalModel = ProjMgrKernel::Get()->GetGlobalModel(); + // remove non-existing and explicit packs + // clear model and projects if at least one pack is deleted + bool purged = globalModel->PurgeModel(true); + if(!purged) { + // just only projects, global RTE data and packs stay loaded + globalModel->ClearProjects(); + } if(!m_manager.RunConvert(csolutionFile, activeTarget, updateRte) || !ProjMgrLogger::Get().GetErrors().empty()) { if(m_worker.HasVarDefineError()) { From 1be45d60e30727ffbcb3695ed5df5144e30d1048 Mon Sep 17 00:00:00 2001 From: Evgueni Driouk Date: Thu, 25 Jun 2026 14:31:43 +0200 Subject: [PATCH 4/8] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- libs/rtemodel/include/RteModel.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/libs/rtemodel/include/RteModel.h b/libs/rtemodel/include/RteModel.h index a0d78e667..d445a1c35 100644 --- a/libs/rtemodel/include/RteModel.h +++ b/libs/rtemodel/include/RteModel.h @@ -617,9 +617,9 @@ class RteGlobalModel : public RteModel void ClearModel() override; /** - * @brief purges non-existing and explicit packs, clears model and projects if at least one pack has been deleted - * @param purgeExplicit flag to remove all PackageState::PS_EXPLICIT_PATH packs - * @returns true if at leas one pack has been removed + * @brief purges non-existing packs and optionally explicit packs; clears model and projects if at least one pack was removed + * @param purgeExplicit when true, remove all PackageState::PS_EXPLICIT_PATH packs + * @returns true if at least one pack has been removed */ bool PurgeModel(bool purgeExplicit); From 345c566654b1ccc78132634eb0ab7393e7f02ceb Mon Sep 17 00:00:00 2001 From: Evgueni Driouk Date: Thu, 25 Jun 2026 14:32:33 +0200 Subject: [PATCH 5/8] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- tools/projmgr/test/src/ProjMgrRpcTests.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/projmgr/test/src/ProjMgrRpcTests.cpp b/tools/projmgr/test/src/ProjMgrRpcTests.cpp index 350e83dc8..dc48d64e9 100644 --- a/tools/projmgr/test/src/ProjMgrRpcTests.cpp +++ b/tools/projmgr/test/src/ProjMgrRpcTests.cpp @@ -71,7 +71,7 @@ string ProjMgrRpcTests::CreateLoadRequests(const string& solution, const string& loadSolutionRequest = FormatRequest(++m_id, "LoadSolution", json({{ "solution", csolutionPath }, { "activeTarget", activeTarget }})); if(!contextList.empty()) { YAML::Node cbuildset; - cbuildset["cbuild-set"]["generated-by"] = "ProjMrgUnitTests"; + cbuildset["cbuild-set"]["generated-by"] = "ProjMgrUnitTests"; for(const auto& context : contextList) { cbuildset["cbuild-set"]["contexts"].push_back(map{ { "context", context } }); } From 6ad534e693ffba5db637b7c349f4f24705f29a9b Mon Sep 17 00:00:00 2001 From: Evgueni Driouk Date: Thu, 25 Jun 2026 14:38:39 +0200 Subject: [PATCH 6/8] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- tools/projmgr/src/ProjMgrRpcServer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/projmgr/src/ProjMgrRpcServer.cpp b/tools/projmgr/src/ProjMgrRpcServer.cpp index 94ff3701c..b0124e5fd 100644 --- a/tools/projmgr/src/ProjMgrRpcServer.cpp +++ b/tools/projmgr/src/ProjMgrRpcServer.cpp @@ -885,7 +885,7 @@ RpcArgs::ConvertSolutionResult RpcHandler::ConvertSolution(const string& solutio // clear model and projects if at least one pack is deleted bool purged = globalModel->PurgeModel(true); if(!purged) { - // just only projects, global RTE data and packs stay loaded + // only projects, global RTE data and packs stay loaded globalModel->ClearProjects(); } From 3a5913622fab8670a482ae390020dfed5f2268d6 Mon Sep 17 00:00:00 2001 From: Evgueni Driouk Date: Thu, 25 Jun 2026 14:38:57 +0200 Subject: [PATCH 7/8] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- tools/projmgr/src/ProjMgrRpcServer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/projmgr/src/ProjMgrRpcServer.cpp b/tools/projmgr/src/ProjMgrRpcServer.cpp index b0124e5fd..950be8a94 100644 --- a/tools/projmgr/src/ProjMgrRpcServer.cpp +++ b/tools/projmgr/src/ProjMgrRpcServer.cpp @@ -328,7 +328,7 @@ RpcArgs::SuccessResult RpcHandler::LoadSolution(const string& solution, const st // clear model and projects if at least one pack is deleted bool purged = globalModel->PurgeModel(true); if(!purged) { - // just only projects, global RTE data and packs stay loaded + // only projects, global RTE data and packs stay loaded globalModel->ClearProjects(); } From ca21e7ccf6573f2eb6fbea1c78569c577f0cd08c Mon Sep 17 00:00:00 2001 From: Evgueni Driouk Date: Fri, 26 Jun 2026 16:33:48 +0200 Subject: [PATCH 8/8] Restore packs after purge in rpc mode --- tools/projmgr/src/ProjMgrRpcServer.cpp | 9 +++++++++ tools/projmgr/src/ProjMgrWorker.cpp | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/tools/projmgr/src/ProjMgrRpcServer.cpp b/tools/projmgr/src/ProjMgrRpcServer.cpp index 950be8a94..0170ef277 100644 --- a/tools/projmgr/src/ProjMgrRpcServer.cpp +++ b/tools/projmgr/src/ProjMgrRpcServer.cpp @@ -322,6 +322,7 @@ RpcArgs::SuccessResult RpcHandler::LoadPacks(void) { RpcArgs::SuccessResult RpcHandler::LoadSolution(const string& solution, const string& activeTarget) { m_bUseAllPacks = false; // loading solution will first use only listed packs m_packReferences.clear(); // will be updated + m_manager.Clear(); m_solutionLoaded = false; // assume not loaded yet auto globalModel = ProjMgrKernel::Get()->GetGlobalModel(); // remove non-existing and explicit packs @@ -338,6 +339,13 @@ RpcArgs::SuccessResult RpcHandler::LoadSolution(const string& solution, const st result.message = solution + " is not a *.csolution.yml file"; return result; } + if(purged) { + // we need to add available packs to model again (the packs are already loaded) + m_worker.InitializeModel(); + m_worker.SetLoadPacksPolicy(LoadPacksPolicy::ALL); + result.success = m_worker.LoadAllRelevantPacks(); + m_worker.SetLoadPacksPolicy(LoadPacksPolicy::DEFAULT); + } // we disregard return value of m_manager.LoadSolution() here, because we tolerate some errors m_manager.LoadSolution(csolutionFile, activeTarget); map* contexts = nullptr; @@ -880,6 +888,7 @@ RpcArgs::ConvertSolutionResult RpcHandler::ConvertSolution(const string& solutio m_bUseAllPacks = false; // loading solution will first use only listed packs m_packReferences.clear(); // will be updated m_solutionLoaded = false; // assume not loaded + m_manager.Clear(); auto globalModel = ProjMgrKernel::Get()->GetGlobalModel(); // remove non-existing and explicit packs // clear model and projects if at least one pack is deleted diff --git a/tools/projmgr/src/ProjMgrWorker.cpp b/tools/projmgr/src/ProjMgrWorker.cpp index c27175293..f8379bbdf 100644 --- a/tools/projmgr/src/ProjMgrWorker.cpp +++ b/tools/projmgr/src/ProjMgrWorker.cpp @@ -548,7 +548,7 @@ bool ProjMgrWorker::LoadAllRelevantPacks() { return false; } // Get installed packs - if (m_rpcMode || pdscFiles.empty() || (m_loadPacksPolicy == LoadPacksPolicy::ALL) || (m_loadPacksPolicy == LoadPacksPolicy::LATEST)) { + if (pdscFiles.empty() || (m_loadPacksPolicy == LoadPacksPolicy::ALL) || (m_loadPacksPolicy == LoadPacksPolicy::LATEST)) { const bool latest = (m_loadPacksPolicy == LoadPacksPolicy::LATEST) || (m_loadPacksPolicy == LoadPacksPolicy::DEFAULT); if (!m_kernel->GetEffectivePdscFiles(pdscFiles, latest)) { ProjMgrLogger::Get().Error("parsing installed packs failed");