diff --git a/tools/projmgr/include/ProjMgr.h b/tools/projmgr/include/ProjMgr.h index 9cc306bc6..057e0e7f5 100644 --- a/tools/projmgr/include/ProjMgr.h +++ b/tools/projmgr/include/ProjMgr.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020-2025 Arm Limited. All rights reserved. + * Copyright (c) 2020-2026 Arm Limited. All rights reserved. * * SPDX-License-Identifier: Apache-2.0 */ @@ -198,6 +198,7 @@ class ProjMgr { bool m_cbuildgen; bool m_updateIdx; bool m_rpcMode = false; + bool m_locked; GroupNode m_files; std::vector m_processedContexts; std::vector m_allContexts; diff --git a/tools/projmgr/include/ProjMgrWorker.h b/tools/projmgr/include/ProjMgrWorker.h index 3b9ff9a57..100bdbc50 100644 --- a/tools/projmgr/include/ProjMgrWorker.h +++ b/tools/projmgr/include/ProjMgrWorker.h @@ -480,6 +480,7 @@ struct VariablesConfiguration { * west on flag * layer variables configurations * unresolved components + * map of available packs for locked packs */ struct ContextItem { CdefaultItem* cdefault = nullptr; @@ -553,6 +554,7 @@ struct ContextItem { bool westOn = false; std::vector variablesConfigurations; std::set unresolvedComponents; + StrMap availablePackVersions; }; /** @@ -627,10 +629,11 @@ class ProjMgrWorker { * @brief list available packs * @param reference to list of packs * @param bListMissingPacksOnly only missing packs + * @param bLocked print available update version for locked packs * @param filter words to filter results * @return true if executed successfully */ - bool ListPacks(std::vector& packs, bool bListMissingPacksOnly, const std::string& filter = RteUtils::EMPTY_STRING); + bool ListPacks(std::vector& packs, bool bListMissingPacksOnly, bool bLocked = false, const std::string& filter = RteUtils::EMPTY_STRING); /** * @brief list available boards @@ -1298,7 +1301,7 @@ class ProjMgrWorker { bool IsEnvironmentCompatible(const std::string& environment, const StrVec& filter); bool HasCompatibleEnvironment(const Collection& environments, const StrVec& filter); template bool CheckFilter(const std::string& filter, const T& item); - void ResolvePackRequirement(ContextItem& context, const PackItem& packEntry); + void ResolvePackRequirement(ContextItem& context, const PackItem& packEntry, bool ignoreCBuildPack); void FormatResolvedPackIds(); }; diff --git a/tools/projmgr/src/ProjMgr.cpp b/tools/projmgr/src/ProjMgr.cpp index 45c21eeb9..48ff43266 100644 --- a/tools/projmgr/src/ProjMgr.cpp +++ b/tools/projmgr/src/ProjMgr.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020-2025 Arm Limited. All rights reserved. + * Copyright (c) 2020-2026 Arm Limited. All rights reserved. * * SPDX-License-Identifier: Apache-2.0 */ @@ -170,6 +170,7 @@ int ProjMgr::ParseCommandLine(int argc, char** argv) { cxxopts::Option cbuildgen("cbuildgen", "Generate legacy *.cprj files", cxxopts::value()->default_value("false")); cxxopts::Option contentLength("content-length", "Prepend 'Content-Length' header to JSON RPC requests and responses", cxxopts::value()->default_value("false")); cxxopts::Option activeTargetSet("a,active", "Select active target-set: [@]", cxxopts::value()); + cxxopts::Option locked("locked", "Print available update version for locked packs", cxxopts::value()->default_value("false")); // command options dictionary map>> optionsDict = { @@ -177,7 +178,7 @@ int ProjMgr::ParseCommandLine(int argc, char** argv) { {"update-rte", { false, {context, contextSet, activeTargetSet, debug, load, quiet, schemaCheck, toolchain, verbose, frozenPacks}}}, {"convert", { false, {context, contextSet, activeTargetSet, debug, exportSuffix, load, quiet, schemaCheck, noUpdateRte, output, outputAlt, toolchain, verbose, frozenPacks, cbuildgen}}}, {"run", { false, {context, contextSet, activeTargetSet, debug, generator, load, quiet, schemaCheck, verbose, dryRun}}}, - {"list packs", { true, {context, contextSet, activeTargetSet, debug, filter, load, missing, quiet, schemaCheck, toolchain, verbose}}}, + {"list packs", { true, {context, contextSet, activeTargetSet, debug, filter, load, missing, locked, quiet, schemaCheck, toolchain, verbose}}}, {"list boards", { true, {context, contextSet, activeTargetSet, debug, filter, load, quiet, schemaCheck, toolchain, verbose}}}, {"list devices", { true, {context, contextSet, activeTargetSet, debug, filter, load, quiet, schemaCheck, toolchain, verbose}}}, {"list configs", { false, {context, contextSet, activeTargetSet, debug, filter, load, quiet, schemaCheck, toolchain, verbose}}}, @@ -201,7 +202,8 @@ int ProjMgr::ParseCommandLine(int argc, char** argv) { solution, context, contextSet, filter, generator, load, clayerSearchPath, missing, schemaCheck, noUpdateRte, output, outputAlt, help, version, verbose, debug, dryRun, exportSuffix, toolchain, ymlOrder, - relativePaths, frozenPacks, updateIdx, quiet, cbuildgen, contentLength, activeTargetSet + relativePaths, frozenPacks, updateIdx, quiet, cbuildgen, contentLength, + activeTargetSet, locked }); options.parse_positional({ "positional" }); @@ -230,6 +232,7 @@ int ProjMgr::ParseCommandLine(int argc, char** argv) { ProjMgrLogger::m_quiet = parseResult.count("quiet"); m_rpcServer.SetContentLengthHeader(parseResult.count("content-length")); m_rpcServer.SetDebug(m_debug); + m_locked = parseResult.count("locked"); vector positionalArguments; if (parseResult.count("positional")) { @@ -804,7 +807,7 @@ bool ProjMgr::RunListPacks(void) { } vector packs; - bool ret = m_worker.ListPacks(packs, m_missingPacks, m_filter); + bool ret = m_worker.ListPacks(packs, m_missingPacks, m_locked, m_filter); for (const auto& pack : packs) { ProjMgrLogger::out() << pack << endl; } diff --git a/tools/projmgr/src/ProjMgrWorker.cpp b/tools/projmgr/src/ProjMgrWorker.cpp index 22e9868be..b54500ab7 100644 --- a/tools/projmgr/src/ProjMgrWorker.cpp +++ b/tools/projmgr/src/ProjMgrWorker.cpp @@ -1713,27 +1713,13 @@ bool ProjMgrWorker::AddPackRequirements(ContextItem& context, const vector matchedPackIds = FindMatchingPackIdsInCbuildPack(packageEntry, resolvedPacks); - if (matchedPackIds.size()) { - // Cbuild pack content matches, so use it - for (const auto& resolvedPackId : matchedPackIds) { - PackageItem package; - package.origin = packageEntry.origin; - package.selectedBy = packageEntry.pack; - ProjMgrUtils::ConvertToPackInfo(resolvedPackId, package.pack); - context.userInputToResolvedPackIdMap[packageEntry.pack].insert(make_pair(resolvedPackId, package)); - context.packRequirements.push_back(package); - } - } else { - // Not matching cbuild pack, add it unless a wildcard entry - PackageItem package; - ProjMgrUtils::ConvertToPackInfo(packageEntry.pack, package.pack); - package.selectedBy = packageEntry.pack; - // Store pack entries in separated vectors for later resolution in the right order - if (!package.pack.name.empty() && !WildCards::IsWildcardPattern(package.pack.name)) { - packEntries[ProjMgrUtils::GetVersionType(package.pack.version)].push_back(packageEntry); - } + // Store pack entries in separated vectors for later resolution in the right order, unless a wildcard entry + // TODO: store also the wildcard entries, expanding them but preserving origin and selectedBy for each resolved pack + PackageItem package; + ProjMgrUtils::ConvertToPackInfo(packageEntry.pack, package.pack); + package.selectedBy = packageEntry.pack; + if (!package.pack.name.empty() && !WildCards::IsWildcardPattern(package.pack.name)) { + packEntries[ProjMgrUtils::GetVersionType(package.pack.version)].push_back(packageEntry); } } else { // Project local pack - add as-is @@ -1762,7 +1748,7 @@ bool ProjMgrWorker::AddPackRequirements(ContextItem& context, const vectorGetEffectivePdscFile(attributes); + auto [packId, _] = m_kernel->GetEffectivePdscFile(attributes); // Only remember the version of the pack if we had it installed or local // Will be used when serializing the cbuild-pack.yml file later - if (!pdsc.first.empty()) { - string installedVersion = RtePackage::VersionFromId(pdsc.first); + if (!packId.empty()) { + string installedVersion = RtePackage::VersionFromId(packId); package.pack.version = VersionCmp::RemoveVersionMeta(installedVersion); - context.userInputToResolvedPackIdMap[packageEntry.pack].insert(make_pair(pdsc.first, package)); + + // Check whether the packageEntry is already locked in cbuild-pack + if (!ignoreCBuildPack) { + vector locked = FindMatchingPackIdsInCbuildPack(packageEntry, context.csolution->cbuildPack.packs); + if (!locked.empty()) { + // TODO: When wildcards will be fully stored in cbuild-pack there may be multiple matches + const auto& lockedId = locked.front(); + if (lockedId != packId) { + // Save available version if different from locked + context.availablePackVersions[lockedId] = package.pack.version; + // Keep the locked pack + packId = lockedId; + package.pack.version = RtePackage::VersionFromId(lockedId); + } + } + } + context.userInputToResolvedPackIdMap[packageEntry.pack].insert(make_pair(packId, package)); } else { // Remember that we had the user input, but it does not match any installed pack context.userInputToResolvedPackIdMap[packageEntry.pack] = {}; @@ -3989,8 +3991,9 @@ bool ProjMgrWorker::ProcessContext(ContextItem& context, bool loadGenFiles, bool return ret; } -bool ProjMgrWorker::ListPacks(vector&packs, bool bListMissingPacksOnly, const string& filter) { +bool ProjMgrWorker::ListPacks(vector&packs, bool bListMissingPacksOnly, bool bLocked, const string& filter) { map packsMap; + StrMap availablePackVersions; list pdscFiles; if (!InitializeModel()) { return false; @@ -4052,6 +4055,13 @@ bool ProjMgrWorker::ListPacks(vector&packs, bool bListMissingPacksOnly, // check if additional dependencies must be added CheckMissingPackRequirements(RteUtils::EMPTY_STRING); } + // get available updates for locked packs + if (bLocked) { + for (const auto& selectedContext : m_selectedContexts) { + ContextItem& context = m_contexts[selectedContext]; + availablePackVersions.merge(context.availablePackVersions); + } + } } if (!m_contextErrMap.empty()) { @@ -4066,7 +4076,11 @@ bool ProjMgrWorker::ListPacks(vector&packs, bool bListMissingPacksOnly, packsVec.reserve(packsMap.size()); for (auto [id, fileName] : packsMap) { string s = id; - if (!fileName.empty()) { + if (bLocked) { + if (!availablePackVersions[id].empty()) { + s += " (locked) available update " + availablePackVersions[id]; + } + } else if (!fileName.empty()) { string str = fileName; if(m_relativePaths) { if(str.find(m_packRoot) == 0) { diff --git a/tools/projmgr/test/src/ProjMgrUnitTests.cpp b/tools/projmgr/test/src/ProjMgrUnitTests.cpp index 87cb9b691..9c232ecbd 100644 --- a/tools/projmgr/test/src/ProjMgrUnitTests.cpp +++ b/tools/projmgr/test/src/ProjMgrUnitTests.cpp @@ -2621,7 +2621,7 @@ TEST_F(ProjMgrUnitTests, ListPacks) { vector packs; EXPECT_TRUE(m_worker.ParseContextSelection({})); m_worker.SetLoadPacksPolicy(LoadPacksPolicy::ALL); - EXPECT_TRUE(m_worker.ListPacks(packs, false, "RTETest")); + EXPECT_TRUE(m_worker.ListPacks(packs, false, false, "RTETest")); string allPacks; for (auto& pack : packs) { allPacks += pack + "\n"; @@ -2636,7 +2636,7 @@ TEST_F(ProjMgrUnitTests, ListPacksLatest) { vector packs; EXPECT_TRUE(m_worker.ParseContextSelection({})); m_worker.SetLoadPacksPolicy(LoadPacksPolicy::LATEST); - EXPECT_TRUE(m_worker.ListPacks(packs, false, "RTETest")); + EXPECT_TRUE(m_worker.ListPacks(packs, false, false, "RTETest")); string latestPacks; for (auto& pack : packs) { latestPacks += pack + "\n"; @@ -6476,6 +6476,22 @@ TEST_F(ProjMgrUnitTests, RunProjMgr_ListPacks_ContextSet) { EXPECT_NE(outStr.find("ARM::RteTest_DFP@0.2.0"), string::npos); } +TEST_F(ProjMgrUnitTests, RunProjMgr_ListPacks_LockedOption) { + char* argv[5]; + StdStreamRedirect streamRedirect; + const string& csolution = testinput_folder + "/TestSolution/PackLocking/lock_pack_version.csolution.yml"; + + // list packs --locked + argv[1] = (char*)"list"; + argv[2] = (char*)"packs"; + argv[3] = (char*)csolution.c_str(); + argv[4] = (char*)"--locked"; + EXPECT_EQ(0, RunProjMgr(5, argv, 0)); + + auto outStr = streamRedirect.GetOutString(); + EXPECT_NE(outStr.find("ARM::RteTest_DFP@0.1.1 (locked) available update 0.2.0"), string::npos); +} + TEST_F(ProjMgrUnitTests, RunProjMgr_ListBoards_ContextSet) { char* argv[6]; StdStreamRedirect streamRedirect;