diff --git a/.github/.cppcheck_suppressions b/.github/.cppcheck_suppressions index 87586715c..cfb00bf2a 100644 --- a/.github/.cppcheck_suppressions +++ b/.github/.cppcheck_suppressions @@ -3,3 +3,4 @@ unusedFunction:libs/xmltree/src/XMLTree.cpp:47 unusedFunction:libs/xmlschemachecker/src/XmlErrorHandler.cpp:27 unusedFunction:libs/xmlschemachecker/src/XmlErrorHandler.cpp:32 +unusedFunction:tools/projmgr/src/ProjMgrRpcServer.cpp diff --git a/.gitmodules b/.gitmodules index 31f5b34b5..c36eb1c30 100644 --- a/.gitmodules +++ b/.gitmodules @@ -29,3 +29,6 @@ path = external/xerces-c url = https://github.com/apache/xerces-c.git ignore = dirty +[submodule "external/json-rpc-cxx"] + path = external/json-rpc-cxx + url = https://github.com/jsonrpcx/json-rpc-cxx.git diff --git a/external/json-rpc-cxx b/external/json-rpc-cxx new file mode 160000 index 000000000..a0e195b57 --- /dev/null +++ b/external/json-rpc-cxx @@ -0,0 +1 @@ +Subproject commit a0e195b575d62cb07016321ac9cd7e1b9e048fe5 diff --git a/libs/rtemodel/src/RteComponent.cpp b/libs/rtemodel/src/RteComponent.cpp index 370dc2539..a2a0d253f 100644 --- a/libs/rtemodel/src/RteComponent.cpp +++ b/libs/rtemodel/src/RteComponent.cpp @@ -20,6 +20,7 @@ #include "RteInstance.h" #include "RteProject.h" #include "RteGenerator.h" +#include "RteConstants.h" #include "XMLTree.h" @@ -608,6 +609,7 @@ void RteComponentAggregate::SetComponentInstance(RteComponentInstance* ci, int c RteItem* ei = ci->GetEffectiveItem(targetName); m_selectedVariant = ei->GetCvariantName(); m_selectedVersion = ei->GetVersionString(); + AssignAttribute("layer", *ci); if (m_components.empty()) { if (c) { @@ -1625,8 +1627,9 @@ string RteComponentGroup::GetTaxonomyDescriptionID() const string id; if (m_parent) id = m_parent->GetTaxonomyDescriptionID(); - if (!id.empty()) - id += "."; + if(!id.empty()) { + id += RteConstants::PREFIX_CGROUP; + } id += GetName(); return id; } diff --git a/libs/rtemodel/src/RteProject.cpp b/libs/rtemodel/src/RteProject.cpp index 7b432b2bb..3926dbc80 100644 --- a/libs/rtemodel/src/RteProject.cpp +++ b/libs/rtemodel/src/RteProject.cpp @@ -832,6 +832,8 @@ bool RteProject::Apply() } } ci = AddComponent(c, count, target, ci); + ci->AssignAttribute("layer", *a); + // add API if any RteApi* api = c->GetApi(target, true); if (api) { diff --git a/libs/rtemodel/test/src/RteModelTest.cpp b/libs/rtemodel/test/src/RteModelTest.cpp index 686785c43..77c832d6b 100644 --- a/libs/rtemodel/test/src/RteModelTest.cpp +++ b/libs/rtemodel/test/src/RteModelTest.cpp @@ -1127,6 +1127,12 @@ TEST_F(RteModelPrjTest, LoadCprjM4) { EXPECT_EQ(allLayerDescriptors.size(), 10); auto& filteredLayerDescriptors = activeTarget->GetFilteredModel()->GetLayerDescriptors(); EXPECT_EQ(filteredLayerDescriptors.size(), 10); + ca = activeTarget->GetComponentAggregate("ARM::Device:Startup"); + ASSERT_NE(ca, nullptr); + EXPECT_EQ(ca->GetAttribute("layer"), "LayerOne"); + ci = ca->GetComponentInstance(); + EXPECT_EQ(ci->GetAttribute("layer"), "LayerOne"); + ASSERT_NE(ci, nullptr); const string projDir = RteUtils::ExtractFilePath(RteTestM4_cprj, true); const string rteDir = projDir + "RTE/"; diff --git a/libs/xmltree/include/XmlItem.h b/libs/xmltree/include/XmlItem.h index 7a4c719e0..252cc730c 100644 --- a/libs/xmltree/include/XmlItem.h +++ b/libs/xmltree/include/XmlItem.h @@ -160,11 +160,19 @@ class XmlItem /** * @brief replace instance attributes with the given ones - * @param attributes given instance of XmlItem + * @param attributes instance of XmlItem containing attributes to assign * @return true if attributes are set */ bool SetAttributes(const XmlItem &attributes); + /** + * @brief assign specified attribute value from supplied object to this one + * @param name attribute name + * @param from instance of XmlItem to get value from + * @return true if attribute is changed + */ + bool AssignAttribute(const std::string& name, const XmlItem &from); + /** * @brief remove attribute * @param name attribute name diff --git a/libs/xmltree/src/XmlItem.cpp b/libs/xmltree/src/XmlItem.cpp index ff9e5c7a4..b195b3a57 100644 --- a/libs/xmltree/src/XmlItem.cpp +++ b/libs/xmltree/src/XmlItem.cpp @@ -62,9 +62,12 @@ bool XmlItem::AddAttribute(const string& name, const string& value, bool insertE return true; } } - if (insertEmpty || !value.empty()) + + if(insertEmpty || !value.empty()) { m_attributes[name] = value; - return true; + return true; + } + return false; } bool XmlItem::SetAttribute(const char* name, const char* value) @@ -104,6 +107,10 @@ bool XmlItem::SetAttributes(const XmlItem& attributes) return SetAttributes(attributes.GetAttributes()); } +bool XmlItem::AssignAttribute(const std::string& name, const XmlItem& from) { + return AddAttribute(name, from.GetAttribute(name), false); +} + bool XmlItem::RemoveAttribute(const std::string& name) { map::iterator it = m_attributes.find(name); diff --git a/libs/xmltree/test/src/XmlTreeTest.cpp b/libs/xmltree/test/src/XmlTreeTest.cpp index 8308cc638..62c892c92 100644 --- a/libs/xmltree/test/src/XmlTreeTest.cpp +++ b/libs/xmltree/test/src/XmlTreeTest.cpp @@ -26,6 +26,19 @@ TEST(XmlTreeTest, GetAttribute) { e.AddAttribute("zeroTen", "010"); e.AddAttribute("minusOne", "-1"); + XMLTreeElement another; + EXPECT_FALSE(another.AssignAttribute("unknown",e)); + EXPECT_FALSE(another.HasAttribute("unknown")); + EXPECT_TRUE(another.SetAttribute("unknown", "unknown")); + EXPECT_TRUE(another.AssignAttribute("unknown",e)); + EXPECT_FALSE(another.HasAttribute("unknown")); + + EXPECT_TRUE(another.AssignAttribute("ten",e)); + EXPECT_TRUE(another.HasAttribute("ten")); + EXPECT_FALSE(another.AssignAttribute("ten",e)); // already assigned + EXPECT_TRUE(another.SetAttribute("ten","010")); + EXPECT_TRUE(another.AssignAttribute("ten",e)); + EXPECT_EQ(e.GetAttribute("unknown").empty(), true); EXPECT_EQ(e.GetAttribute("string"), "strVal"); EXPECT_EQ(e.GetAttribute("empty").empty(), true); diff --git a/tools/projmgr/CMakeLists.txt b/tools/projmgr/CMakeLists.txt index 1d1a61022..bad6f8fda 100644 --- a/tools/projmgr/CMakeLists.txt +++ b/tools/projmgr/CMakeLists.txt @@ -22,12 +22,15 @@ SET(PROJMGR_SOURCE_FILES ProjMgr.cpp ProjMgrKernel.cpp ProjMgrCallback.cpp ProjMgrCbuildBase.cpp ProjMgrCbuild.cpp ProjMgrCbuildIdx.cpp ProjMgrCbuildGenIdx.cpp ProjMgrCbuildPack.cpp ProjMgrCbuildSet.cpp ProjMgrCbuildRun.cpp ProjMgrRunDebug.cpp + ProjMgrRpcServer.cpp ProjMgrRpcServerData.cpp ) SET(PROJMGR_HEADER_FILES ProjMgr.h ProjMgrKernel.h ProjMgrCallback.h ProjMgrParser.h ProjMgrWorker.h ProjMgrGenerator.h ProjMgrXmlParser.h ProjMgrYamlParser.h ProjMgrLogger.h ProjMgrYamlSchemaChecker.h ProjMgrYamlEmitter.h ProjMgrUtils.h ProjMgrExtGenerator.h ProjMgrCbuildBase.h ProjMgrRunDebug.h + ProjMgrRpcServer.h ProjMgrRpcServerData.h + RpcInterface.h ) list(TRANSFORM PROJMGR_SOURCE_FILES PREPEND src/) @@ -37,8 +40,12 @@ add_library(projmgrlib OBJECT ${PROJMGR_SOURCE_FILES} ${PROJMGR_HEADER_FILES}) target_link_libraries(projmgrlib PUBLIC CrossPlatform RteFsUtils RteUtils XmlTree XmlTreeSlim XmlReader - RteModel cxxopts yaml-cpp YmlSchemaChecker) -target_include_directories(projmgrlib PRIVATE include ${PROJECT_BINARY_DIR}) + RteModel cxxopts yaml-cpp YmlSchemaChecker +) +target_include_directories(projmgrlib PUBLIC include ${PROJECT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/external/json + ${CMAKE_SOURCE_DIR}/external/json-rpc-cxx/include +) if(SWIG_LIBS) # projmgr swig diff --git a/tools/projmgr/include/ProjMgr.h b/tools/projmgr/include/ProjMgr.h index 6dfa68935..c75a849c6 100644 --- a/tools/projmgr/include/ProjMgr.h +++ b/tools/projmgr/include/ProjMgr.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020-2024 Arm Limited. All rights reserved. + * Copyright (c) 2020-2025 Arm Limited. All rights reserved. * * SPDX-License-Identifier: Apache-2.0 */ @@ -12,6 +12,7 @@ #include "ProjMgrGenerator.h" #include "ProjMgrYamlEmitter.h" #include "ProjMgrRunDebug.h" +#include "ProjMgrRpcServer.h" #include @@ -50,6 +51,18 @@ class ProjMgr { */ static int RunProjMgr(int argc, char **argv, char** envp); + /** + * @brief get worker object + * @return reference to m_worker + */ + ProjMgrWorker& GetWorker() { return m_worker; }; + + /** + * @brief load solution + * @param path to .csolution.yml file + * @return processing status + */ + bool LoadSolution(const std::string& csolution); protected: /** @@ -88,12 +101,6 @@ class ProjMgr { */ ProjMgrParser& GetParser() { return m_parser; }; - /** - * @brief get worker object - * @return reference to m_worker - */ - ProjMgrWorker& GetWorker() { return m_worker; }; - /** * @brief get generator object * @return reference to m_generator @@ -137,6 +144,7 @@ class ProjMgr { ProjMgrGenerator m_generator; ProjMgrYamlEmitter m_emitter; ProjMgrRunDebug m_runDebug; + ProjMgrRpcServer m_rpcServer; std::string m_csolutionFile; std::string m_cdefaultFile; @@ -193,6 +201,7 @@ class ProjMgr { bool GenerateYMLConfigurationFiles(bool previousResult); bool UpdateRte(); bool ParseAndValidateContexts(); + bool ProcessContexts(); }; #endif // PROJMGR_H diff --git a/tools/projmgr/include/ProjMgrExtGenerator.h b/tools/projmgr/include/ProjMgrExtGenerator.h index 3948e189a..f60055d2a 100644 --- a/tools/projmgr/include/ProjMgrExtGenerator.h +++ b/tools/projmgr/include/ProjMgrExtGenerator.h @@ -101,6 +101,10 @@ class ProjMgrExtGenerator { */ ClayerItem* GetGeneratorImport(const std::string& contextId, bool& success); + void Clear() { + m_usedGenerators.clear(); + } + protected: ProjMgrParser* m_parser = nullptr; GeneratorContextVecMap m_usedGenerators; diff --git a/tools/projmgr/include/ProjMgrParser.h b/tools/projmgr/include/ProjMgrParser.h index 8f710c60f..9b0f617a8 100644 --- a/tools/projmgr/include/ProjMgrParser.h +++ b/tools/projmgr/include/ProjMgrParser.h @@ -75,11 +75,13 @@ struct MiscItem { * pack name * pack path * type filter + * origin file */ struct PackItem { std::string pack; std::string path; TypeFilter type; + std::string origin; }; /** @@ -733,6 +735,14 @@ class ProjMgrParser { */ CbuildSetItem& GetCbuildSetItem(void); + void Clear() { + m_cdefault = {}; + m_csolution = {}; + m_cbuildSet = {}; + m_cprojects.clear(); + m_clayers.clear(); + m_genericClayers.clear(); + } /** * @brief get debug adapters * @return debug adapters list diff --git a/tools/projmgr/include/ProjMgrRpcServer.h b/tools/projmgr/include/ProjMgrRpcServer.h new file mode 100644 index 000000000..699cc0996 --- /dev/null +++ b/tools/projmgr/include/ProjMgrRpcServer.h @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2025 Arm Limited. All rights reserved. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef PROJMGRRPCSERVER_H +#define PROJMGRRPCSERVER_H + +#include +#include + +/** + * Forward declarations +*/ +class ProjMgr; + +/** + * @brief projmgr rpc server +*/ +class ProjMgrRpcServer { +public: + /** + * @brief class constructor + */ + ProjMgrRpcServer(ProjMgr& manager); + + /** + * @brief class destructor + */ + ~ProjMgrRpcServer(void); + + /** + * @brief run rpc server + * @return true if terminated successfully, otherwise false + */ + bool Run(void); + + /** + * @brief get parser object + * @return reference to m_manager + */ + ProjMgr& GetManager() { return m_manager; }; + + void SetDebug(bool debug) { m_debug = debug; } + + /** + * @brief set m_shutdown flag + * @param boolean value + */ + void SetShutdown(bool value) { m_shutdown = value; } + + /** + * @brief set m_contextLength flag + * @param boolean value + */ + void SetContentLengthHeader(bool value) { m_contextLength = value; } + +protected: + ProjMgr& m_manager; + bool m_debug = false; + bool m_shutdown = false; + bool m_contextLength = false; + const std::string GetRequestFromStdinWithLength(void); + const std::string GetRequestFromStdin(void); +}; + +#endif // PROJMGRRPCSERVER_H diff --git a/tools/projmgr/include/ProjMgrRpcServerData.h b/tools/projmgr/include/ProjMgrRpcServerData.h new file mode 100644 index 000000000..3f1075201 --- /dev/null +++ b/tools/projmgr/include/ProjMgrRpcServerData.h @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2025 Arm Limited. All rights reserved. + * + * SPDX-License-Identifier: Apache-2.0 + */ +#ifndef PROJMGRRPCSERVERDATA_H +#define PROJMGRRPCSERVERDATA_H + +#include + +using namespace std; + +class RteTarget; +class RteBundle; +class RteComponent; +class RteComponentInstance; +class RteComponentAggregate; +class RteComponentGroup; +class RteItem; +class RpcDataCollector { +public: + RpcDataCollector(RteTarget* t); + + RteTarget* GetTarget() const { return m_target; } + + void CollectCtClasses(Args::CtRoot& ctRoot) const; + void CollectUsedItems(Args::UsedItems& usedItems) const; + + Args::Component FromRteComponent(const RteComponent* rteComponent) const; + Args::ComponentInstance FromComponentInstance(const RteComponentInstance* rteCi) const; + RteItem* GetTaxonomyItem(const RteComponentGroup* rteGroup) const; + +protected: + + void CollectCtBundles(Args::CtClass& ctClass, RteComponentGroup* rteClass) const; + void CollectCtChildren(Args::CtTreeItem& parent, RteComponentGroup* rteGroup, const string& bundleName) const; + void CollectCtAggregates(Args::CtTreeItem& parent, RteComponentGroup* rteGroup, const string& bundleName) const; + void CollectCtVariants(Args::CtAggregate& ctAggregate, RteComponentAggregate* rteAggregate) const; + +private: + RteTarget* m_target; +}; + +#endif // PROJMGRRPCSERVERDATA_ diff --git a/tools/projmgr/include/ProjMgrRunDebug.h b/tools/projmgr/include/ProjMgrRunDebug.h index e930427aa..6d3fa0666 100644 --- a/tools/projmgr/include/ProjMgrRunDebug.h +++ b/tools/projmgr/include/ProjMgrRunDebug.h @@ -205,6 +205,10 @@ class ProjMgrRunDebug { */ bool CollectSettings(const std::vector& contexts, const DebugAdaptersItem& adapters); + void Clear() { + m_runDebug = {}; + } + protected: RunDebugType m_runDebug; void GetDebugSequenceBlock(const RteItem* item, DebugSequencesBlockType& block); diff --git a/tools/projmgr/include/ProjMgrWorker.h b/tools/projmgr/include/ProjMgrWorker.h index 760d902c4..64b247f13 100644 --- a/tools/projmgr/include/ProjMgrWorker.h +++ b/tools/projmgr/include/ProjMgrWorker.h @@ -94,11 +94,13 @@ struct ToolchainItem { /** * @brief package item containing * pack information pack, - * path to pack path + * path to pack path, + * origin file, */ struct PackageItem { PackInfo pack; std::string path; + std::string origin; }; /** @@ -243,6 +245,31 @@ struct ContextTypesItem { }; /** + * @brief validation condition item containing + * expression (accept/deny/require ...) + * related aggregates +*/ +struct ValidationCondition { + std::string expression; + StrSet aggregates; +}; + +/** + * @brief validation result containing + * result according to enum ConditionResult + * component/api identifier + * direct related aggregates + * conditions (expressions and related identifiers) +*/ + +struct ValidationResult { + RteItem::ConditionResult result; + std::string id; + StrSet aggregates; + std::vector conditions; +}; + +/** * @brief gdb server item containing * port number of processor * processor name @@ -368,7 +395,7 @@ struct ContextItem { std::map>> apis; std::map bootstrapComponents; StrMap bootstrapMap; - std::vector, std::set>> validationResults; + std::vector validationResults; std::map> configFiles; std::map plmStatus; std::map> componentFiles; @@ -844,6 +871,62 @@ class ProjMgrWorker { */ bool CheckRteErrors(void); + /** + * @brief load packs + * @param context item + * @return true if there is no error + */ + bool LoadPacks(ContextItem& context); + + /** + * @brief set target attributes + * @param context item + * @param map of attributes + * @return true if there is no error + */ + bool SetTargetAttributes(ContextItem& context, std::map& attributes); + + /** + * @brief add required components + * @param context item + * @return true if there is no error + */ + bool AddRequiredComponents(ContextItem& context); + + /** + * @brief validate context + * @param context item + * @return true if there is no error + */ + bool ValidateContext(ContextItem& context); + + /** + * @brief clear worker members for reloading a solution + * @return true if there is no error + */ + void Clear() { + for (auto context : m_contexts) { + for (auto componentItem : context.second.components) { + delete componentItem.second.instance; + } + } + m_contexts.clear(); + m_ymlOrderedContexts.clear(); + m_contextsPtr->clear(); + m_contextErrMap.clear(); + m_selectedContexts.clear(); + m_outputDir.clear(); + m_selectedToolchain.clear(); + m_rootDir.clear(); + m_undefLayerVars.clear(); + m_packMetadata.clear(); + m_executes.clear(); + m_toolchainErrors.clear(); + m_selectableCompilers.clear(); + m_missingFiles.clear(); + m_types = {}; + }; + protected: ProjMgrParser* m_parser = nullptr; ProjMgrKernel* m_kernel = nullptr; @@ -886,7 +969,6 @@ class ProjMgrWorker { std::string m_activeTargetType; TargetSetItem m_activeTargetSet; - bool LoadPacks(ContextItem& context); bool CheckMissingPackRequirements(const std::string& contextName); void CheckMissingLinkerScript(ContextItem& context); bool CollectRequiredPdscFiles(ContextItem& context, const std::string& packRoot); @@ -897,7 +979,6 @@ class ProjMgrWorker { bool GetTypeContent(ContextItem& context); bool GetProjectSetup(ContextItem& context); bool InitializeTarget(ContextItem& context); - bool SetTargetAttributes(ContextItem& context, std::map& attributes); bool ProcessPrecedences(ContextItem& context, BoardOrDevice process = BoardOrDevice::None, bool rerun = false); bool ProcessPrecedence(StringCollection& item); bool ProcessCompilerPrecedence(StringCollection& item, bool acceptRedefinition = false); @@ -924,7 +1005,6 @@ class ProjMgrWorker { bool ProcessLinkerOptions(ContextItem& context, const LinkerItem& linker, const std::string& ref); bool ProcessProcessorOptions(ContextItem& context); void AddContext(ContextDesc& descriptor, const TypePair& type, ContextItem& parentContext); - bool ValidateContext(ContextItem& context); bool FormatValidationResults(std::set& results, const ContextItem& context); void UpdateMisc(std::vector& vec, const std::string& compiler); void AddMiscUniquely(MiscItem& dst, std::vector*>& srcVec); @@ -932,7 +1012,6 @@ class ProjMgrWorker { bool AddGroup(const GroupNode& src, std::vector& dst, ContextItem& context, const std::string root); bool AddFile(const FileNode& src, std::vector& dst, ContextItem& context, const std::string root); bool AddComponent(const ComponentItem& src, const std::string& layer, std::vector>& dst, TypePair type, ContextItem& context); - bool AddRequiredComponents(ContextItem& context); void GetDeviceItem(const std::string& element, DeviceItem& device) const; void GetBoardItem (const std::string& element, BoardItem& board) const; bool GetPrecedentValue(std::string& outValue, const std::string& element) const; @@ -994,6 +1073,7 @@ class ProjMgrWorker { StrVec CollectSelectableCompilers(); void ProcessTmpDir(std::string& tmpdir, const std::string& base); bool IsCreatedByExecute(const std::string file, const std::string dir); + bool CollectAllRequiredPdscFiles(); bool ParseTargetSetContextSelection(const std::string& activeTargetSet); bool GetActiveTargetSet(const std::string& activeTargetSet); }; diff --git a/tools/projmgr/include/RpcInterface.h b/tools/projmgr/include/RpcInterface.h new file mode 100644 index 000000000..62f2c3cfd --- /dev/null +++ b/tools/projmgr/include/RpcInterface.h @@ -0,0 +1,266 @@ +/* + * Copyright (c) 2025 Arm Limited. All rights reserved. + * + * SPDX-License-Identifier: Apache-2.0 + */ +#ifndef RPCINTERFACE_H +#define RPCINTERFACE_H + +#include +#include +#include +#include + +using namespace std; +using namespace jsonrpccxx; + +namespace Args { + + struct PackElement { + string id; + optional description; + optional doc; + }; + + // GetPacksInfo + struct Pack : public PackElement { + optional overview; + optional used; + optional> references; + }; + struct PacksInfo { + vector packs; + }; + + // GetCoponentTree + struct Component : public PackElement { + string fromPack; + optional implements; + optional maxInstances; + }; + + using Api = PackElement; + using Taxonomy = PackElement; + using Bundle = PackElement; + + // component Tree + struct CtItem { + string name; + }; + + struct ComponentInstance { + string id; + int selectedCount; // number of selected instances + optional resolvedComponent; + optional layer; + }; + + struct CtVariant : public CtItem { + vector components; // version-sorted components + }; + + struct CtAggregate : public CtItem { + string id; + optional activeVariant; + optional activeVersion; + optional selectedCount; // selection flag, represents selected number of instances + vector variants; + optional layer; + }; + + struct CtGroup; + struct CtAggregate; + struct CtTreeItem : public CtItem { + optional> groups; + optional> aggregates; + }; + + struct CtGroup : public CtTreeItem { + optional api; + optional taxonomy; + }; + + struct CtBundle : public CtTreeItem { + optional bundle; + }; + + struct CtClass : public CtItem{ + optional taxonomy; + optional activeBundle; + vector bundles; + }; + + struct CtRoot { + vector classes; + }; + + // ValidateComponents + struct Condition { + string expression; + optional> aggregates; + }; + struct Result { + string result; + string id; + optional> aggregates; + optional> conditions; + }; + struct Results { + optional> validation; + }; + + struct UsedItems { + vector components; + vector packs; + }; + + // GetLogMessages + struct LogMessages { + optional> info; + optional> errors; + optional> warnings; + }; + + // to_json converters + template void to_json(nlohmann::json& j, const string& key, const T& value) { + j[key] = value; + } + template void to_json(nlohmann::json& j, const string& key, const optional& opt) { + if (opt.has_value()) { + j[key] = opt.value(); + } + } + inline void to_json(nlohmann::json& j, const Pack& p) { + to_json(j, "id", p.id); + to_json(j, "description", p.description); + to_json(j, "overview", p.overview); + to_json(j, "used", p.used); + to_json(j, "references", p.references); + } + inline void to_json(nlohmann::json& j, const PacksInfo& info) { + to_json(j, "packs", info.packs); + } + + inline void to_json(nlohmann::json& j, const Component& c) { + to_json(j, "id", c.id); + to_json(j, "description", c.description); + to_json(j, "doc", c.doc); + to_json(j, "from-pack", c.fromPack); + to_json(j, "implements", c.implements); + to_json(j, "maxInstances", c.maxInstances); + } + inline void to_json(nlohmann::json& j, const PackElement& e) { + to_json(j, "id", e.id); + to_json(j, "description", e.description); + to_json(j, "doc", e.doc); + } + + inline void to_json(nlohmann::json& j, const ComponentInstance& ci) { + to_json(j, "id", ci.id); + to_json(j, "selectedCount", ci.selectedCount); + to_json(j, "layer", ci.layer); + to_json(j, "resolvedComponent", ci.resolvedComponent); + } + + inline void to_json(nlohmann::json& j, const Condition& c) { + to_json(j, "expression", c.expression); + to_json(j, "aggregates", c.aggregates); + } + inline void to_json(nlohmann::json& j, const Result& r) { + to_json(j, "result", r.result); + to_json(j, "id", r.id); + to_json(j, "aggregates", r.aggregates); + to_json(j, "conditions", r.conditions); + } + inline void to_json(nlohmann::json& j, const Results& r) { + to_json(j, "validation", r.validation); + } + + inline void to_json(nlohmann::json& j, const LogMessages& m) { + to_json(j, "info", m.info); + to_json(j, "errors", m.errors); + to_json(j, "warnings", m.warnings); + } + + inline void to_json(nlohmann::json& j, const CtVariant& v) { + to_json(j, "name", v.name); + to_json(j, "components", v.components); + } + + inline void to_json(nlohmann::json& j, const CtAggregate& a) { + to_json(j, "name", a.name); + to_json(j, "id", a.id); + to_json(j, "selectedCount", a.selectedCount); + to_json(j, "activeVariant", a.activeVariant); + to_json(j, "activeVersion", a.activeVersion); + to_json(j, "layer", a.layer); + to_json(j, "variants", a.variants); + } + + inline void to_json(nlohmann::json& j, const CtGroup& g) { + to_json(j, "name", g.name); + to_json(j, "api", g.api); + to_json(j, "taxonomy", g.taxonomy); + to_json(j, "groups", g.groups); + to_json(j, "aggregates", g.aggregates); + } + + inline void to_json(nlohmann::json& j, const CtBundle& b) { + to_json(j, "name", b.name); + to_json(j, "bundle", b.bundle); + to_json(j, "groups", b.groups); + to_json(j, "aggregates", b.aggregates); + } + + inline void to_json(nlohmann::json& j, const CtClass& c) { + to_json(j, "name", c.name); + to_json(j, "activeBundle", c.activeBundle); + to_json(j, "bundles", c.bundles); + to_json(j, "taxonomy", c.taxonomy); + } + + inline void to_json(nlohmann::json& j, const UsedItems& u) { + to_json(j, "components", u.components); + to_json(j, "packs", u.packs); + } + + inline void to_json(nlohmann::json& j, const CtRoot& r) { + to_json(j, "classes", r.classes); + } +} + +class RpcMethods { +public: + RpcMethods(JsonRpc2Server& jsonServer) { + jsonServer.Add("GetVersion", GetHandle(&RpcMethods::GetVersion, *this)); + jsonServer.Add("Shutdown", GetHandle(&RpcMethods::Shutdown, *this)); + jsonServer.Add("Apply", GetHandle(&RpcMethods::Apply, *this), { "context" }); + jsonServer.Add("LoadPacks", GetHandle(&RpcMethods::LoadPacks, *this)); + jsonServer.Add("LoadSolution", GetHandle(&RpcMethods::LoadSolution, *this), { "solution" }); + jsonServer.Add("GetPacksInfo", GetHandle(&RpcMethods::GetPacksInfo, *this), { "context" }); + jsonServer.Add("GetUsedItems", GetHandle(&RpcMethods::GetUsedItems, *this), { "context" }); + jsonServer.Add("GetComponentsTree", GetHandle(&RpcMethods::GetComponentsTree, *this), { "context", "all" }); + jsonServer.Add("SelectComponent", GetHandle(&RpcMethods::SelectComponent, *this), { "context", "id", "count" }); + jsonServer.Add("SelectVariant", GetHandle(&RpcMethods::SelectVariant, *this), { "context", "id", "variant" }); + jsonServer.Add("SelectVersion", GetHandle(&RpcMethods::SelectVersion, *this), { "context", "id", "version" }); + jsonServer.Add("SelectBundle", GetHandle(&RpcMethods::SelectBundle, *this), { "context", "class", "bundle" }); + jsonServer.Add("ValidateComponents", GetHandle(&RpcMethods::ValidateComponents, *this), { "context" }); + jsonServer.Add("GetLogMessages", GetHandle(&RpcMethods::GetLogMessages, *this)); + } + virtual const string GetVersion(void) { return string(); } + virtual const bool Shutdown(void) { return bool(); } + virtual const bool Apply(const string& context) { return bool(); } + virtual const bool LoadPacks(void) { return bool(); } + virtual const bool LoadSolution(const string& solution) { return bool(); } + virtual const Args::UsedItems GetUsedItems(const string& context) { return Args::UsedItems(); } + virtual const Args::PacksInfo GetPacksInfo(const string& context) { return Args::PacksInfo(); } + virtual const Args::CtRoot GetComponentsTree(const string& context, bool all) { return Args::CtRoot(); } + virtual const bool SelectComponent(const string& context, const string& aggregateId, int count) { return bool(); } + virtual const bool SelectVariant(const string& context, const string& aggregateId, const string& variantName) { return bool(); } + virtual const bool SelectVersion(const string& context, const string& aggregateId, const string& version) { return bool(); } + virtual const bool SelectBundle(const string& context, const string& className, const string& bundleName) { return bool(); } + virtual const Args::Results ValidateComponents(const string& context) { return Args::Results(); } + virtual const Args::LogMessages GetLogMessages(void) { return Args::LogMessages(); } +}; + +#endif // RPCINTERFACE_H diff --git a/tools/projmgr/src/ProjMgr.cpp b/tools/projmgr/src/ProjMgr.cpp index fae0e3fb5..d170b19c4 100644 --- a/tools/projmgr/src/ProjMgr.cpp +++ b/tools/projmgr/src/ProjMgr.cpp @@ -36,6 +36,7 @@ Commands:\n\ list target-sets Print list of target-sets in a .csolution.yml\n\ list toolchains Print list of supported toolchains\n\ run Run code generator\n\ + rpc Run remote procedure call server\n\ update-rte Create/update configuration files and validate solution\n\n\ Options:\n\ -a, --active arg Select active target-set: [@]\n\ @@ -65,6 +66,7 @@ ProjMgr::ProjMgr() : m_extGenerator(&m_parser), m_worker(&m_parser, &m_extGenerator), m_emitter(&m_parser, &m_worker), + m_rpcServer(*this), m_checkSchema(false), m_missingPacks(false), m_updateRteFiles(true), @@ -164,6 +166,7 @@ int ProjMgr::ParseCommandLine(int argc, char** argv) { cxxopts::Option updateIdx("update-idx", "Update cbuild-idx file with layer info", cxxopts::value()->default_value("false")); cxxopts::Option quiet("q,quiet", "Run silently, printing only error messages", cxxopts::value()->default_value("false")); 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()); // command options dictionary @@ -184,6 +187,7 @@ int ProjMgr::ParseCommandLine(int argc, char** argv) { {"list layers", { false, {context, contextSet, activeTargetSet, debug, load, clayerSearchPath, quiet, schemaCheck, toolchain, verbose, updateIdx}}}, {"list toolchains", { false, {context, contextSet, activeTargetSet, debug, quiet, toolchain, verbose}}}, {"list environment", { true, {}}}, + {"rpc", { true, {contentLength}}}, }; try { @@ -192,7 +196,7 @@ 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, activeTargetSet + relativePaths, frozenPacks, updateIdx, quiet, cbuildgen, contentLength, activeTargetSet }); options.parse_positional({ "positional" }); @@ -219,6 +223,8 @@ int ProjMgr::ParseCommandLine(int argc, char** argv) { m_cbuildgen = parseResult.count("cbuildgen"); m_worker.SetCbuild2Cmake(!m_cbuildgen); ProjMgrLogger::m_quiet = parseResult.count("quiet"); + m_rpcServer.SetContentLengthHeader(parseResult.count("content-length")); + m_rpcServer.SetDebug(m_debug); vector positionalArguments; if (parseResult.count("positional")) { @@ -415,6 +421,12 @@ int ProjMgr::ProcessCommands() { if (!RunCodeGenerator()) { return ErrorCode::ERROR; } + } else if (m_command == "rpc") { + // Launch 'rpc' server over stdin/stdout + ProjMgrLogger::m_silent = true; + if (!m_rpcServer.Run()) { + return ErrorCode::ERROR; + } } else { ProjMgrLogger::Get().Error(" was not found"); return ErrorCode::ERROR; @@ -607,31 +619,8 @@ bool ProjMgr::Configure() { ProjMgrLogger::Get().Error(errMsg); } - // Get context pointers - map* contexts = nullptr; - m_worker.GetContexts(contexts); - - vector orderedContexts; - m_worker.GetYmlOrderedContexts(orderedContexts); - // Process contexts - bool error = false; - m_allContexts.clear(); - m_processedContexts.clear(); - m_failedContext.clear(); - for (auto& contextName : orderedContexts) { - auto& contextItem = (*contexts)[contextName]; - m_allContexts.push_back(&contextItem); - if (!m_worker.IsContextSelected(contextName)) { - continue; - } - if (!m_worker.ProcessContext(contextItem, true, true, false)) { - ProjMgrLogger::Get().Error("processing context '" + contextName + "' failed", contextName); - m_failedContext.insert(contextItem.name); - error = true; - } - m_processedContexts.push_back(&contextItem); - } + bool error = !ProcessContexts(); if (m_worker.HasToolchainErrors()) { error = true; @@ -672,6 +661,35 @@ bool ProjMgr::Configure() { return !error; } +bool ProjMgr::ProcessContexts() { + // Get context pointers + map* contexts = nullptr; + m_worker.GetContexts(contexts); + + vector orderedContexts; + m_worker.GetYmlOrderedContexts(orderedContexts); + + // Process contexts + bool success = true; + m_allContexts.clear(); + m_processedContexts.clear(); + m_failedContext.clear(); + for (auto& contextName : orderedContexts) { + auto& contextItem = (*contexts)[contextName]; + m_allContexts.push_back(&contextItem); + if (!m_worker.IsContextSelected(contextName)) { + continue; + } + if (!m_worker.ProcessContext(contextItem, true, true, false)) { + ProjMgrLogger::Get().Error("processing context '" + contextName + "' failed", contextName); + m_failedContext.insert(contextItem.name); + success = false; + } + m_processedContexts.push_back(&contextItem); + } + return success; +} + bool ProjMgr::UpdateRte() { // Update the RTE files for (auto& contextItem : m_processedContexts) { @@ -1151,6 +1169,33 @@ const string ProjMgr::GetToolboxVersion(const string& toolboxDir) { return matchResult[1].str(); } +bool ProjMgr::LoadSolution(const std::string& csolution) { + if (!m_csolutionFile.empty()) { + m_parser.Clear(); + m_extGenerator.Clear(); + m_worker.Clear(); + m_runDebug.Clear(); + ProjMgrLogger::Get().Clear(); + } + + m_csolutionFile = csolution; + m_rootDir = RteUtils::ExtractFilePath(m_csolutionFile, false); + + m_contextSet = true; + m_updateRteFiles = false; + + if (!PopulateContexts()) { + return false; + } + if (!ParseAndValidateContexts()) { + return false; + } + if (!ProcessContexts()) { + return false; + } + return true; +} + const string ProjMgr::GetDebugAdaptersFile(void) { error_code ec; const string exePath = RteUtils::ExtractFilePath(CrossPlatformUtils::GetExecutablePath(ec), true); @@ -1159,4 +1204,4 @@ const string ProjMgr::GetDebugAdaptersFile(void) { return debugAdapterFile; } return RteUtils::EMPTY_STRING; -} +} \ No newline at end of file diff --git a/tools/projmgr/src/ProjMgrRpcServer.cpp b/tools/projmgr/src/ProjMgrRpcServer.cpp new file mode 100644 index 000000000..2016196a1 --- /dev/null +++ b/tools/projmgr/src/ProjMgrRpcServer.cpp @@ -0,0 +1,433 @@ +/* + * Copyright (c) 2025 Arm Limited. All rights reserved. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "ProjMgrRpcServer.h" +#include "ProjMgrRpcServerData.h" +#include "ProjMgrLogger.h" +#include "ProjMgr.h" +#include "ProductInfo.h" +#include + +#include +#include +#include + +using namespace std; +using namespace jsonrpccxx; + +static constexpr const char* CONTENT_LENGTH_HEADER = "Content-Length:"; + +ProjMgrRpcServer::ProjMgrRpcServer(ProjMgr& manager) : + m_manager(manager) { +} + +ProjMgrRpcServer::~ProjMgrRpcServer(void) { + // Reserved +} + +const string ProjMgrRpcServer::GetRequestFromStdinWithLength(void) { + string line; + int contentLength = 0; + const string& header = CONTENT_LENGTH_HEADER; + while (getline(cin, line) && !line.empty() && !cin.fail()) { + if (line.find(header) == 0) { + contentLength = RteUtils::StringToInt(line.substr(header.length()), 0); + } + } + string request(contentLength, '\0'); + cin.read(&request[0], contentLength); + return request; +} + +const string ProjMgrRpcServer::GetRequestFromStdin(void) { + string jsonData; + int braces = 0; + bool inJson = false; + char c; + while (cin.get(c) && !cin.fail()) { + if (c == '{') { + braces++; + inJson = true; + } + if (c == '}') { + braces--; + } + if (inJson) { + jsonData += c; + } + if (inJson && braces == 0) { + break; + } + } + return jsonData; +} + +class RpcHandler : public RpcMethods { +public: + RpcHandler(ProjMgrRpcServer& server, JsonRpc2Server& jsonServer) : RpcMethods(jsonServer), + m_server(server), + m_manager(server.GetManager()), + m_worker(server.GetManager().GetWorker()) {} + + const string GetVersion(void); + const bool Shutdown(void); + const bool Apply(const string& context); + const bool LoadPacks(void); + const bool LoadSolution(const string& solution); + const Args::UsedItems GetUsedItems(const string& context); + const Args::PacksInfo GetPacksInfo(const string& context); + const Args::CtRoot GetComponentsTree(const string& context, bool all); + const bool SelectComponent(const string& context, const string& aggregateId, int count); + const bool SelectVariant(const string& context, const string& aggregateId, const string& variantName); + const bool SelectVersion(const string& context, const string& aggregateId, const string& version); + const bool SelectBundle(const string& context, const string& className, const string& bundleName); + const Args::Results ValidateComponents(const string& context); + const Args::LogMessages GetLogMessages(void); + +protected: + enum Exception + { + SOLUTION_NOT_FOUND = -1, + SOLUTION_NOT_VALID = -2, + SOLUTION_NOT_LOADED = -3, + CONTEXT_NOT_FOUND = -4, + CONTEXT_NOT_VALID = -5, + COMPONENT_NOT_FOUND = -6, + COMPONENT_NOT_RESOLVED = -7, + PACKS_NOT_LOADED = -8, + PACKS_LOADING_FAIL = -9, + RTE_MODEL_ERROR = -10, + }; + + ProjMgrRpcServer& m_server; + ProjMgr& m_manager; + ProjMgrWorker& m_worker; + ContextItem m_globalContext; + bool m_packsLoaded = false; + bool m_solutionLoaded = false; + const ContextItem& GetContext(const string& context) const; + RteTarget* GetActiveTarget(const string& context) const; + RteComponentAggregate* GetComponentAggregate(const string& context, const string& id) const; + const bool SelectVariantOrVersion(const string& context, const string& id, const string& value, bool bVariant); +}; + +bool ProjMgrRpcServer::Run(void) { + JsonRpc2Server jsonServer; + RpcHandler handler(*this, jsonServer); + + while (!m_shutdown && !cin.fail()) { + // Get request + const auto request = m_contextLength ? + GetRequestFromStdinWithLength() : + GetRequestFromStdin(); + + if (request.empty()) { + continue; + } + + ofstream log; + if (m_debug) { + log.open(RteFsUtils::GetCurrentFolder(true) + "csolution-rpc-log.txt", fstream::app); + log << request << std::endl; + } + + // Handle request + const auto response = jsonServer.HandleRequest(request); + + // Send response + if (m_contextLength) { + cout << CONTENT_LENGTH_HEADER << response.size() << std::endl << std::endl; + } + cout << response << std::flush; + + if (m_debug) { + log << response << std::endl; + log.close(); + } + + } + return true; +} + +const ContextItem& RpcHandler::GetContext(const string& context) const { + if (!m_solutionLoaded) { + throw JsonRpcException(SOLUTION_NOT_LOADED, "a valid solution must be loaded before proceeding"); + } + if (context.empty()) { + throw JsonRpcException(CONTEXT_NOT_VALID, "'context' argument cannot be empty"); + } + const auto selected = m_worker.GetSelectedContexts(); + if (find(selected.begin(), selected.end(), context) == selected.end()) { + throw JsonRpcException(CONTEXT_NOT_FOUND, context + " was not found among selected contexts"); + } + map* contexts = nullptr; + m_worker.GetContexts(contexts); + return (*contexts)[context]; +} + +RteTarget* RpcHandler::GetActiveTarget(const string& context) const { + const auto& contextItem = GetContext(context); + return contextItem.rteActiveTarget; +} + + +RteComponentAggregate* RpcHandler::GetComponentAggregate(const string& context, const string& id) const { + auto rteAggregate = GetContext(context).rteActiveTarget->GetComponentAggregate(id); + if(!rteAggregate) { + throw JsonRpcException(COMPONENT_NOT_FOUND, id + ": component not found"); + } + return rteAggregate; +} + +const string RpcHandler::GetVersion(void) { + return VERSION_STRING; +} + +const bool RpcHandler::Shutdown(void) { + m_server.SetShutdown(true); + return true; +} + +const bool RpcHandler::Apply(const string& context) { + + auto rteProject = GetActiveTarget(context)->GetProject(); + if(rteProject) { + return rteProject->Apply(); + } + return false; +} + +const Args::UsedItems RpcHandler::GetUsedItems(const string& context) { + + Args::UsedItems usedItems; + RpcDataCollector dc(GetActiveTarget(context)); + dc.CollectUsedItems(usedItems); + return usedItems; +} + +const bool RpcHandler::LoadPacks(void) { + m_packsLoaded = m_worker.LoadPacks(m_globalContext); + if (!m_packsLoaded) { + throw JsonRpcException(PACKS_LOADING_FAIL, "packs failed to load"); + } + return true; +} + +const bool RpcHandler::LoadSolution(const string& solution) { + if (!m_packsLoaded) { + throw JsonRpcException(PACKS_NOT_LOADED, "packs must be loaded before proceeding"); + } + const auto csolutionFile = RteFsUtils::MakePathCanonical(solution); + if (!regex_match(csolutionFile, regex(".*\\.csolution\\.(yml|yaml)"))) { + throw JsonRpcException(SOLUTION_NOT_FOUND, solution + " is not a *.csolution.yml file"); + } + m_solutionLoaded = m_manager.LoadSolution(csolutionFile); + if (!m_solutionLoaded) { + throw JsonRpcException(SOLUTION_NOT_VALID, "failed to load and process solution " + csolutionFile); + } + return true; +} + +const Args::PacksInfo RpcHandler::GetPacksInfo(const string& context) { + const auto contextItem = GetContext(context); + + map> packRefs; + for (const auto& packItem : contextItem.packRequirements) { + if (!packItem.origin.empty()) { + const auto packId = RtePackage::ComposePackageID(packItem.pack.vendor, packItem.pack.name, packItem.pack.version); + CollectionUtils::PushBackUniquely(packRefs[packId], packItem.origin); + } + } + + Args::PacksInfo packsInfo; + for (auto& [pack, packItem] : m_globalContext.rteActiveTarget->GetFilteredModel()->GetPackages()) { + Args::Pack p; + p.id = packItem->GetPackageID(true); + const auto& description = packItem->GetDescription(); + if (!description.empty()) { + p.description = description; + } + string overview = packItem->GetChildAttribute("description", "overview"); + if (!overview.empty()) { + RteFsUtils::NormalizePath(overview, packItem->GetAbsolutePackagePath()); + p.overview = overview; + } + if (contextItem.packages.find(p.id) != contextItem.packages.end()) { + p.used = true; + if (packRefs.find(p.id) != packRefs.end()) { + p.references = packRefs.at(p.id); + } + } + packsInfo.packs.push_back(p); + } + + return packsInfo; +} + +const Args::CtRoot RpcHandler::GetComponentsTree(const string& context, bool all) { + Args::CtRoot ctRoot; + RteTarget* rteTarget = GetActiveTarget(context); + // store selected components, not aggregates: they will be destroyed + map selectedComponents; + auto& selectedAggregates = rteTarget->CollectSelectedComponentAggregates(); + for(auto [aggregate, count] : selectedAggregates) { + RteComponent* c = aggregate->GetComponent(); + if(c) { + // consider only components, instances are added from project anyway + selectedComponents[c] = count; + } + } + RtePackageFilter packFilter; + if(!all) { + // construct and apply filter + auto& contextItem = GetContext(context); + // use pack ID's from context + set packIds; + for(auto [id, pack] : contextItem.packages) { + packIds.insert(id); + } + packFilter.SetSelectedPackages(packIds); + } + // only update filter if differs from current state + if(!packFilter.IsEqual(rteTarget->GetPackageFilter())) { + rteTarget->SetPackageFilter(packFilter); + rteTarget->UpdateFilterModel(); // updates available components + rteTarget->GetProject()->UpdateModel(); // inserts already instantiated components + // restore selection + for(auto [c, count] : selectedComponents) { + rteTarget->SelectComponent(c, count, false, false); + } + rteTarget->EvaluateComponentDependencies(); + } + + RpcDataCollector dc(rteTarget); + dc.CollectCtClasses(ctRoot); + return ctRoot; +} + +const bool RpcHandler::SelectComponent(const string& context, const string& id, int count) { +// first try full component ID + RteComponent* rteComponent = GetContext(context).rteActiveTarget->GetComponent(id); + if(rteComponent) { + return GetActiveTarget(context)->SelectComponent(rteComponent, count, true); + } + return GetActiveTarget(context)->SelectComponent(GetComponentAggregate(context, id), count, true); +} + +const bool RpcHandler::SelectVariant(const string& context, const string& id, const string& variant) { + return SelectVariantOrVersion(context, id, variant, true); +} + +const bool RpcHandler::SelectVersion(const string& context, const string& id, const string& version) { + return SelectVariantOrVersion(context, id, version, false); +} + +const bool RpcHandler::SelectVariantOrVersion(const string& context, const string& id, const string& value, bool bVariant) { + RteComponentAggregate* rteAggregate = GetComponentAggregate(context, id); + + auto& selectedValue = bVariant ? rteAggregate->GetSelectedVariant() : rteAggregate->GetSelectedVersion(); + if(selectedValue == value) { + return false; + } + + if(bVariant || !value.empty()) { + auto availableValues = bVariant ? rteAggregate->GetVariants() : rteAggregate->GetVersions(rteAggregate->GetSelectedVariant()); + if(std::find(availableValues.begin(), availableValues.end(), value) == availableValues.end()) { + return false; + } + } + if(bVariant) { + rteAggregate->SetSelectedVariant(value); + } else { + rteAggregate->SetSelectedVersion(value); + } + if(rteAggregate->IsSelected()) { + GetActiveTarget(context)->EvaluateComponentDependencies(); + } + return true; +} + + + +const bool RpcHandler::SelectBundle(const string& context, const string& className, const string& bundleName) { + RteTarget* rteTarget = GetActiveTarget(context); + RteComponentClass* rteClass = rteTarget->GetComponentClass(className); + if(!rteClass) { + throw JsonRpcException(COMPONENT_NOT_FOUND, className + ": component class not found"); + } + rteClass->SetSelectedBundleName(bundleName, true); + GetActiveTarget(context)->EvaluateComponentDependencies(); + return true; +} + + +const Args::Results RpcHandler::ValidateComponents(const string& context) { + auto contextItem = GetContext(context); + + if (!m_worker.CheckRteErrors()) { + throw JsonRpcException(RTE_MODEL_ERROR, "rte model reported error"); + } + + Args::Results results; + if (!m_worker.ValidateContext(contextItem)) { + results.validation = vector{}; + for (const auto& validation : contextItem.validationResults) { + Args::Result r; + r.result = RteItem::ConditionResultToString(validation.result); + r.id = validation.id; + if (!validation.aggregates.empty()) { + r.aggregates = vector(validation.aggregates.begin(), validation.aggregates.end()); + } + if (!validation.conditions.empty()) { + Args::Condition c; + r.conditions = vector{}; + for (const auto& condition : validation.conditions) { + c.expression = condition.expression; + if (!condition.aggregates.empty()) { + c.aggregates = vector(condition.aggregates.begin(), condition.aggregates.end()); + } + r.conditions->push_back(c); + } + } + results.validation->push_back(r); + } + } + return results; +} + +const Args::LogMessages RpcHandler::GetLogMessages(void) { + StrVec infoVec; + for (const auto& [_, info] : ProjMgrLogger::Get().GetInfos()) { + for (const auto& msg : info) { + CollectionUtils::PushBackUniquely(infoVec, msg); + } + } + StrVec errorsVec; + for (const auto& [_, errors] : ProjMgrLogger::Get().GetErrors()) { + for (const auto& msg : errors) { + CollectionUtils::PushBackUniquely(errorsVec, msg); + } + } + StrVec warningsVec; + for (const auto& [_, warnings] : ProjMgrLogger::Get().GetWarns()) { + for (const auto& msg : warnings) { + CollectionUtils::PushBackUniquely(warningsVec, msg); + } + } + Args::LogMessages messages; + if (!infoVec.empty()) { + messages.info = infoVec; + } + if (!errorsVec.empty()) { + messages.errors = errorsVec; + } + if (!warningsVec.empty()) { + messages.warnings = warningsVec; + } + return messages; +} + +// end of ProkMgrRpcServer.cpp diff --git a/tools/projmgr/src/ProjMgrRpcServerData.cpp b/tools/projmgr/src/ProjMgrRpcServerData.cpp new file mode 100644 index 000000000..a1238234b --- /dev/null +++ b/tools/projmgr/src/ProjMgrRpcServerData.cpp @@ -0,0 +1,226 @@ +/* + * Copyright (c) 2025 Arm Limited. All rights reserved. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "ProjMgrRpcServerData.h" + +#include "RteComponent.h" +#include "RteInstance.h" +#include "RtePackage.h" +#include "RteProject.h" +#include "RteTarget.h" +#include "RteModel.h" + +#include "CollectionUtils.h" + +using namespace Args; + +template +T FromRteItem(const string& id, RteItem* rteItem) { + T e; + e.id = id; + const auto& description = rteItem->GetDescription(); + if(!description.empty()) { + e.description = description; + } + const auto& doc = rteItem->GetDocFile(); + if(!doc.empty()) { + e.doc = doc; + } + return e; +} + +RpcDataCollector::RpcDataCollector(RteTarget* t) : + m_target(t) +{ +} + +Args::Component RpcDataCollector::FromRteComponent(const RteComponent* rteComponent) const{ + Args::Component c; + c.id = rteComponent->GetComponentID(true); + c.fromPack = rteComponent->GetPackageID(true); + const auto& description = rteComponent->GetDescription(); + if (!description.empty()) { + c.description = description; + } + const auto& doc = rteComponent->GetDocFile(); + if (!doc.empty()) { + c.doc = doc; + } + const auto& api = rteComponent->GetApi(m_target, true); + if (api) { + c.implements = api->ConstructComponentID(true); + } + if (rteComponent->HasMaxInstances()) { + c.maxInstances = rteComponent->GetMaxInstances(); + } + return c; +} + +Args::ComponentInstance RpcDataCollector::FromComponentInstance(const RteComponentInstance* rteCi) const { + ComponentInstance ci; + if(rteCi) { + ci.id = rteCi->GetDisplayName(); + ci.selectedCount = rteCi->GetInstanceCount(m_target->GetName()); + auto& layer = rteCi->GetAttribute("layer"); + if(!layer.empty()) { + ci.layer = layer; + } + auto rteComponent = rteCi->GetResolvedComponent(m_target->GetName()); + if(rteComponent) { + ci.resolvedComponent = FromRteComponent(rteComponent); + } + } + return ci; +} + + +RteItem* RpcDataCollector::GetTaxonomyItem(const RteComponentGroup* rteGroup) const { + if(m_target && rteGroup) { + auto taxonimyId = rteGroup->GetTaxonomyDescriptionID(); + return m_target->GetFilteredModel()->GetTaxonomyItem(taxonimyId); + } + return nullptr; +} + +void RpcDataCollector::CollectUsedItems(Args::UsedItems& usedItems) const { + + auto rteProject = m_target ? m_target->GetProject() : nullptr; + if(!rteProject) { + return; + } + + for(auto [_id, rteCi] : rteProject->GetComponentInstances()) { + usedItems.components.push_back(FromComponentInstance(rteCi)); + } + RtePackageMap packs; + rteProject->GetUsedPacks(packs, m_target->GetName()); + for(auto [id, rtePack] : packs) { + Pack p; + p.id = id; + usedItems.packs.push_back(p); + } +} + + +void RpcDataCollector::CollectCtClasses(Args::CtRoot& root) const { + + auto classContainer = m_target ? m_target->GetClasses() : nullptr; + if(!classContainer) { + return; // can happen if no solution is loaded + } + + for(auto [name, rteClass] : classContainer->GetGroups()) { + Args::CtClass ctClass; + ctClass.name = name; + auto activeBundle = rteClass->GetSelectedBundleName(); + ctClass.activeBundle = activeBundle; + auto taxonomyItem = GetTaxonomyItem(rteClass); + if(taxonomyItem) { + ctClass.taxonomy = FromRteItem(taxonomyItem->GetTaxonomyDescriptionID(), taxonomyItem); + } + CollectCtBundles(ctClass, rteClass); + root.classes.push_back(ctClass); + } +} + +void RpcDataCollector::CollectCtBundles(Args::CtClass& ctClass, RteComponentGroup* rteClass) const { + for(auto [bundleName, bundleId] : rteClass->GetBundleNames()) { + m_target->GetFilteredBundles(); + CtBundle ctBundle; + ctBundle.name = bundleName; + RteBundle* rteBundle = get_or_null(GetTarget()->GetFilteredBundles(), bundleId); + if(rteBundle) { + ctBundle.bundle = FromRteItem(bundleName, rteBundle); + } + CollectCtChildren(ctBundle, rteClass, bundleName); + // add to parent collection + ctClass.bundles.push_back(ctBundle); + } +} + +void RpcDataCollector::CollectCtChildren(Args::CtTreeItem& parent, RteComponentGroup* parentRteGroup, const string& bundleName) const +{ + // collect aggregates to this level + CollectCtAggregates(parent, parentRteGroup, bundleName); + auto& rteGroups = parentRteGroup->GetGroups(); + if(rteGroups.empty()) { + return; + } + vector groups; + for(auto [name, rteGroup] : rteGroups) { + if(!rteGroup->HasBundleName(bundleName)) { + continue; + } + CtGroup g; + g.name = name; + auto rteApi = rteGroup->GetApi(); + if(rteApi) { + g.api = FromRteItem(rteApi->GetID(), rteApi); + } + auto taxonomyItem = GetTaxonomyItem(rteGroup); + if(taxonomyItem) { + g.taxonomy = FromRteItem(taxonomyItem->GetTaxonomyDescriptionID(), taxonomyItem); + } + // subgroups + CollectCtChildren(g, rteGroup, bundleName); + CollectCtAggregates(g, rteGroup,bundleName); + // add to parent collection + groups.push_back(g); + } + parent.groups = groups; +} + +void RpcDataCollector::CollectCtAggregates(Args::CtTreeItem& parent, RteComponentGroup* parentRteGroup, const string& bundleName) const +{ + // aggregates + vector aggregates; + for(auto child : parentRteGroup->GetChildren()) { + RteComponentAggregate* rteAggregate = dynamic_cast(child); + if(!rteAggregate || rteAggregate->GetCbundleName() != bundleName ) { + continue; + } + CtAggregate a; + a.name = rteAggregate->GetDisplayName(); + a.id = rteAggregate->GetID(); + a.activeVersion = rteAggregate->GetEffectiveVersion(); + auto& activeVariant = rteAggregate->GetSelectedVariant(); + if(!activeVariant.empty()) { + a.activeVariant = rteAggregate->GetSelectedVariant(); + } + auto selectedCount = rteAggregate->IsSelected(); + if(selectedCount) { + a.selectedCount = selectedCount; + } + + auto layer = rteAggregate->GetAttribute("layer"); + if(!layer.empty()) { + a.layer = layer; + } + + CollectCtVariants(a, rteAggregate); + aggregates.push_back(a); + } + if(!aggregates.empty()) { + parent.aggregates = aggregates; + } +} + +void RpcDataCollector::CollectCtVariants(Args::CtAggregate& ctAggregate, RteComponentAggregate* rteAggregate) const +{ + for(auto& variantName : rteAggregate->GetVariants()) { + CtVariant v; + v.name = variantName; + auto components = rteAggregate->GetComponentVersions(variantName); + if(components) { + for(auto [version, rteComponent] : *components) { + v.components.push_back(FromRteComponent(rteComponent)); + } + } + ctAggregate.variants.push_back(v); + } +} + +// end of ProjMgrRpcServerData.cpp diff --git a/tools/projmgr/src/ProjMgrWorker.cpp b/tools/projmgr/src/ProjMgrWorker.cpp index 4698f12c8..dd34c60e6 100644 --- a/tools/projmgr/src/ProjMgrWorker.cpp +++ b/tools/projmgr/src/ProjMgrWorker.cpp @@ -414,9 +414,8 @@ bool ProjMgrWorker::InitializeModel() { return m_kernel->Init(); } -bool ProjMgrWorker::LoadAllRelevantPacks() { - // Get required pdsc files - std::list pdscFiles; +bool ProjMgrWorker::CollectAllRequiredPdscFiles() { + // Check and collect requirements if (m_selectedContexts.empty()) { for (const auto& [context,_] : m_contexts) { m_selectedContexts.push_back(context); @@ -430,6 +429,15 @@ bool ProjMgrWorker::LoadAllRelevantPacks() { success &= false; continue; } + } + return success; +} + +bool ProjMgrWorker::LoadAllRelevantPacks() { + // Get required pdsc files + std::list pdscFiles; + for (const auto& context : m_selectedContexts) { + auto& contextItem = m_contexts.at(context); for (const auto& [pdscFile, pathVer] : contextItem.pdscFiles) { const string& path = pathVer.first; if (!path.empty()) { @@ -444,9 +452,6 @@ bool ProjMgrWorker::LoadAllRelevantPacks() { } } } - if (!success) { - return false; - } // Check load packs policy if (pdscFiles.empty() && (m_loadPacksPolicy == LoadPacksPolicy::REQUIRED)) { ProjMgrLogger::Get().Error("required packs must be specified"); @@ -488,10 +493,13 @@ bool ProjMgrWorker::LoadPacks(ContextItem& context) { if (!InitializeTarget(context)) { return false; } - if (m_loadedPacks.empty() && !LoadAllRelevantPacks()) { + if (!CollectAllRequiredPdscFiles()) { PrintContextErrors(context.name); return false; } + if (m_loadedPacks.empty() && !LoadAllRelevantPacks()) { + return false; + } // Filter context specific packs set selectedPacks; const bool allOrLatest = (m_loadPacksPolicy == LoadPacksPolicy::ALL) || (m_loadPacksPolicy == LoadPacksPolicy::LATEST); @@ -1628,6 +1636,7 @@ bool ProjMgrWorker::AddPackRequirements(ContextItem& context, const vectordirectory + "/"); if (!RteFsUtils::Exists(package.path)) { @@ -1682,6 +1693,7 @@ bool ProjMgrWorker::AddPackRequirements(ContextItem& context, const vectordirectory, context.cproject->directory, ec).append("RTE").generic_string(); matchedComponentInstance->AddAttribute("rtedir", rteDir); + matchedComponentInstance->AddAttribute("layer", layer); } // Get generator @@ -2553,21 +2566,26 @@ bool ProjMgrWorker::ValidateContext(ContextItem& context) { map results; context.rteActiveTarget->GetDepsResult(results, context.rteActiveTarget); - for (const auto& [component, result] : results) { - RteItem::ConditionResult validationResult = result.GetResult(); - const auto& componentID = component->GetComponentID(true); - const auto& depResults = result.GetResults(); - const auto& aggregates = result.GetComponentAggregates(); + for (const auto& [item, result] : results) { + ValidationResult validation; + validation.result = result.GetResult(); + validation.id = item->ConstructComponentID(true); - set aggregatesSet; - for (const auto& aggregate : aggregates) { - aggregatesSet.insert(aggregate->GetComponentAggregateID()); + for (const auto& aggregate : result.GetComponentAggregates()) { + validation.aggregates.insert(aggregate->ConstructComponentID(true)); } - set expressionsSet; - for (const auto& [item, _] : depResults) { - expressionsSet.insert(item->GetDependencyExpressionID()); + + const auto& depResults = result.GetResults(); + for (const auto& [item, result] : depResults) { + ValidationCondition condition; + condition.expression = item->GetDependencyExpressionID(); + for (const auto& aggregate : result.GetComponentAggregates()) { + condition.aggregates.insert(aggregate->ConstructComponentID(true)); + } + validation.conditions.push_back(condition); } - context.validationResults.push_back({ validationResult, componentID, expressionsSet, aggregatesSet }); + + context.validationResults.push_back(validation); } if (context.validationResults.empty()) { @@ -4055,10 +4073,10 @@ bool ProjMgrWorker::ListDependencies(vector& dependencies, const string& return false; } if (!ValidateContext(context)) { - for (const auto& [result, component, expressions, _] : context.validationResults) { - if ((result == RteItem::MISSING) || (result == RteItem::SELECTABLE)) { - for (const auto& expression : expressions) { - dependenciesSet.insert(component + " " + expression); + for (const auto& validation : context.validationResults) { + if ((validation.result == RteItem::MISSING) || (validation.result == RteItem::SELECTABLE)) { + for (const auto& condition : validation.conditions) { + dependenciesSet.insert(validation.id + " " + condition.expression); } } } @@ -4079,13 +4097,16 @@ bool ProjMgrWorker::ListDependencies(vector& dependencies, const string& } bool ProjMgrWorker::FormatValidationResults(set& results, const ContextItem& context) { - for (const auto& [result, component, expressions, aggregates] : context.validationResults) { - string resultStr = RteItem::ConditionResultToString(result) + " " + component; - for (const auto& expression : expressions) { - resultStr += "\n " + expression; + for (const auto& validation : context.validationResults) { + string resultStr = RteItem::ConditionResultToString(validation.result) + " " + validation.id; + for (const auto& condition : validation.conditions) { + resultStr += "\n " + condition.expression; + for (const auto& id : condition.aggregates) { + resultStr += "\n " + id; + } } - for (const auto& aggregate : aggregates) { - resultStr += "\n " + aggregate; + for (const auto& id : validation.aggregates) { + resultStr += "\n " + id; } results.insert(resultStr); } @@ -5142,6 +5163,10 @@ bool ProjMgrWorker::ProcessGeneratedLayers(ContextItem& context) { vector packRequirements; InsertPackRequirements(cgen->packs, packRequirements, cgen->directory); AddPackRequirements(context, packRequirements); + if (!CollectAllRequiredPdscFiles()) { + PrintContextErrors(context.name); + return false; + } if (!LoadAllRelevantPacks() || !LoadPacks(context)) { PrintContextErrors(context.name); return false; diff --git a/tools/projmgr/src/ProjMgrYamlParser.cpp b/tools/projmgr/src/ProjMgrYamlParser.cpp index 349bf5b89..c933785ab 100644 --- a/tools/projmgr/src/ProjMgrYamlParser.cpp +++ b/tools/projmgr/src/ProjMgrYamlParser.cpp @@ -728,6 +728,7 @@ void ProjMgrYamlParser::ParsePacks(const YAML::Node& parent, const string& file, const YAML::Node& packNode = parent[YAML_PACKS]; for (const auto& packEntry : packNode) { PackItem packItem; + packItem.origin = file; ParseString(packEntry, YAML_PACK, packItem.pack); ParsePortablePath(packEntry, file, YAML_PATH, packItem.path); ParseTypeFilter(packEntry, packItem.type); diff --git a/tools/projmgr/test/CMakeLists.txt b/tools/projmgr/test/CMakeLists.txt index a2c641a28..a12eb8655 100644 --- a/tools/projmgr/test/CMakeLists.txt +++ b/tools/projmgr/test/CMakeLists.txt @@ -1,5 +1,5 @@ add_executable(ProjMgrUnitTests src/ProjMgrUnitTests.cpp src/ProjMgrTestEnv.cpp - src/ProjMgrWorkerUnitTests.cpp src/ProjMgrGeneratorUnitTests.cpp + src/ProjMgrWorkerUnitTests.cpp src/ProjMgrGeneratorUnitTests.cpp src/ProjMgrRpcTests.cpp src/ProjMgrSchemaCheckerUnitTests.cpp src/ProjMgrUtilsUnitTests.cpp src/ProjMgrYamlParserUnitTest.cpp src/ProjMgrTestEnv.h) diff --git a/tools/projmgr/test/data/TestSolution/simple/RTE/Device/RteTest_ARMCM0/ARMCM.dbgconf b/tools/projmgr/test/data/TestSolution/simple/RTE/Device/RteTest_ARMCM0/ARMCM.dbgconf new file mode 100644 index 000000000..f9c9009de --- /dev/null +++ b/tools/projmgr/test/data/TestSolution/simple/RTE/Device/RteTest_ARMCM0/ARMCM.dbgconf @@ -0,0 +1,45 @@ +// <<< Use Configuration Wizard in Context Menu >>> + +// version from from DFP 0.2.0 + +// Debug Setup + +// Release M0 On Connect +// <0=> No +// <1=> Yes +// Debugger releases the M0 Application processor from reset when connecting to it. +ReleaseM0OnConnect = 1; + +// Release M0 Sub-System On Connect +// <0=> No +// <1=> Yes +// Debugger releases the M0 Sub-System from reset when connecting to it (LPC437x only). +ReleaseM0SubOnConnect = 1; + +// Vector Reset +// <0=> Processor Only +// <1=> Processor and Peripherals +// Select if to additionally reset peripherals (LCD, USB0, USB1, DMA, SDIO, ETHERNET) after a Vector Reset +VecResetWithPeriph = 1; + +// + +// TPIU Pin Routing (TRACECLK fixed on PF_4) +// Configure the TPIU pin routing as used on your target platform. +// TRACEDATA0 +// <0=> Pin PF_5 +// <1=> Pin P7_4 +// TRACEDATA1 +// <0=> Pin PF_6 +// <1=> Pin P7_5 +// TRACEDATA2 +// <0=> Pin PF_7 +// <1=> Pin P7_6 +// TRACEDATA3 +// <0=> Pin PF_8 +// <1=> Pin P7_7 +RoutingTPIU = 0x00000000; + +// + +// <<< end of configuration section >>> diff --git a/tools/projmgr/test/data/TestSolution/simple/RTE/Device/RteTest_ARMCM0/ARMCM.dbgconf.base@0.0.2 b/tools/projmgr/test/data/TestSolution/simple/RTE/Device/RteTest_ARMCM0/ARMCM.dbgconf.base@0.0.2 new file mode 100644 index 000000000..f9c9009de --- /dev/null +++ b/tools/projmgr/test/data/TestSolution/simple/RTE/Device/RteTest_ARMCM0/ARMCM.dbgconf.base@0.0.2 @@ -0,0 +1,45 @@ +// <<< Use Configuration Wizard in Context Menu >>> + +// version from from DFP 0.2.0 + +// Debug Setup + +// Release M0 On Connect +// <0=> No +// <1=> Yes +// Debugger releases the M0 Application processor from reset when connecting to it. +ReleaseM0OnConnect = 1; + +// Release M0 Sub-System On Connect +// <0=> No +// <1=> Yes +// Debugger releases the M0 Sub-System from reset when connecting to it (LPC437x only). +ReleaseM0SubOnConnect = 1; + +// Vector Reset +// <0=> Processor Only +// <1=> Processor and Peripherals +// Select if to additionally reset peripherals (LCD, USB0, USB1, DMA, SDIO, ETHERNET) after a Vector Reset +VecResetWithPeriph = 1; + +// + +// TPIU Pin Routing (TRACECLK fixed on PF_4) +// Configure the TPIU pin routing as used on your target platform. +// TRACEDATA0 +// <0=> Pin PF_5 +// <1=> Pin P7_4 +// TRACEDATA1 +// <0=> Pin PF_6 +// <1=> Pin P7_5 +// TRACEDATA2 +// <0=> Pin PF_7 +// <1=> Pin P7_6 +// TRACEDATA3 +// <0=> Pin PF_8 +// <1=> Pin P7_7 +RoutingTPIU = 0x00000000; + +// + +// <<< end of configuration section >>> diff --git a/tools/projmgr/test/data/TestSolution/simple/RTE/Device/RteTest_ARMCM0/ac6_linker_script.sct.src b/tools/projmgr/test/data/TestSolution/simple/RTE/Device/RteTest_ARMCM0/ac6_linker_script.sct.src new file mode 100644 index 000000000..7820e1f12 --- /dev/null +++ b/tools/projmgr/test/data/TestSolution/simple/RTE/Device/RteTest_ARMCM0/ac6_linker_script.sct.src @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2023 Arm Limited. All rights reserved. + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the License); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an AS IS BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* ---------------------------------------------------------------------------- + Stack seal size definition + *----------------------------------------------------------------------------*/ +#if defined (__ARM_FEATURE_CMSE) && (__ARM_FEATURE_CMSE == 3U) +#define __STACKSEAL_SIZE 8 +#else +#define __STACKSEAL_SIZE 0 +#endif + +/*---------------------------------------------------------------------------- + Scatter File Definitions definition + *----------------------------------------------------------------------------*/ + +LR_ROM0 __ROM0_BASE __ROM0_SIZE { + + ER_ROM0 __ROM0_BASE __ROM0_SIZE { + *.o (RESET, +First) + *(InRoot$$Sections) + *(+RO +XO) + } + +#if defined (__ARM_FEATURE_CMSE) && (__ARM_FEATURE_CMSE == 3U) + ER_CMSE_VENEER AlignExpr(+0, 32) (__ROM0_SIZE - AlignExpr(ImageLength(ER_ROM0), 32)) { + *(Veneer$$CMSE) + } +#endif + + RW_NOINIT __RAM0_BASE UNINIT (__RAM0_SIZE - __HEAP_SIZE - __STACK_SIZE - __STACKSEAL_SIZE) { + *.o(.bss.noinit) + *.o(.bss.noinit.*) + } + + RW_RAM0 AlignExpr(+0, 8) (__RAM0_SIZE - __HEAP_SIZE - __STACK_SIZE - __STACKSEAL_SIZE - AlignExpr(ImageLength(RW_NOINIT), 8)) { + *(+RW +ZI) + } + +#if __HEAP_SIZE > 0 + ARM_LIB_HEAP (AlignExpr(+0, 8)) EMPTY __HEAP_SIZE { ; Reserve empty region for heap + } +#endif + + ARM_LIB_STACK (__RAM0_BASE + __RAM0_SIZE - __STACKSEAL_SIZE) EMPTY -__STACK_SIZE { ; Reserve empty region for stack + } + +#if __STACKSEAL_SIZE > 0 + STACKSEAL +0 EMPTY __STACKSEAL_SIZE { ; Reserve empty region for stack seal immediately after stack + } +#endif + +#if __RAM1_SIZE > 0 + RW_RAM1 __RAM1_BASE __RAM1_SIZE { + .ANY (+RW +ZI) + } +#endif + +#if __RAM2_SIZE > 0 + RW_RAM2 __RAM2_BASE __RAM2_SIZE { + .ANY (+RW +ZI) + } +#endif + +#if __RAM3_SIZE > 0 + RW_RAM3 __RAM3_BASE __RAM3_SIZE { + .ANY (+RW +ZI) + } +#endif +} + +#if __ROM1_SIZE > 0 +LR_ROM1 __ROM1_BASE __ROM1_SIZE { + ER_ROM1 +0 __ROM1_SIZE { + .ANY (+RO +XO) + } +} +#endif + +#if __ROM2_SIZE > 0 +LR_ROM2 __ROM2_BASE __ROM2_SIZE { + ER_ROM2 +0 __ROM2_SIZE { + .ANY (+RO +XO) + } +} +#endif + +#if __ROM3_SIZE > 0 +LR_ROM3 __ROM3_BASE __ROM3_SIZE { + ER_ROM3 +0 __ROM3_SIZE { + .ANY (+RO +XO) + } +} +#endif diff --git a/tools/projmgr/test/data/TestSolution/simple/RTE/Device/RteTest_ARMCM0/regions_RteTest_ARMCM0.h b/tools/projmgr/test/data/TestSolution/simple/RTE/Device/RteTest_ARMCM0/regions_RteTest_ARMCM0.h new file mode 100644 index 000000000..3c4dc42f8 --- /dev/null +++ b/tools/projmgr/test/data/TestSolution/simple/RTE/Device/RteTest_ARMCM0/regions_RteTest_ARMCM0.h @@ -0,0 +1,101 @@ +#ifndef REGIONS_RTETEST_ARMCM0_H +#define REGIONS_RTETEST_ARMCM0_H + + +//-------- <<< Use Configuration Wizard in Context Menu >>> -------------------- +//------ With VS Code: Open Preview for Configuration Wizard ------------------- + +// Auto-generated using information from packs +// Device Family Pack (DFP): ARM::RteTest_DFP@0.2.0 + +// ROM Configuration +// ======================= +// __ROM0 (is rx memory: IROM1 from DFP) +// Base address <0x0-0xFFFFFFFF:8> +// Defines base address of memory region. Default: 0x00000000 +// Contains Startup and Vector Table +#define __ROM0_BASE 0x00000000 +// Region size [bytes] <0x0-0xFFFFFFFF:8> +// Defines size of memory region. Default: 0x00040000 +#define __ROM0_SIZE 0x00040000 +// + +// __ROM1 (unused) +// Base address <0x0-0xFFFFFFFF:8> +// Defines base address of memory region. +#define __ROM1_BASE 0 +// Region size [bytes] <0x0-0xFFFFFFFF:8> +// Defines size of memory region. +#define __ROM1_SIZE 0 +// + +// __ROM2 (unused) +// Base address <0x0-0xFFFFFFFF:8> +// Defines base address of memory region. +#define __ROM2_BASE 0 +// Region size [bytes] <0x0-0xFFFFFFFF:8> +// Defines size of memory region. +#define __ROM2_SIZE 0 +// + +// __ROM3 (unused) +// Base address <0x0-0xFFFFFFFF:8> +// Defines base address of memory region. +#define __ROM3_BASE 0 +// Region size [bytes] <0x0-0xFFFFFFFF:8> +// Defines size of memory region. +#define __ROM3_SIZE 0 +// + +// + +// RAM Configuration +// ======================= +// __RAM0 (is rw memory: IRAM1 from DFP) +// Base address <0x0-0xFFFFFFFF:8> +// Defines base address of memory region. Default: 0x20000000 +// Contains uninitialized RAM, Stack, and Heap +#define __RAM0_BASE 0x20000000 +// Region size [bytes] <0x0-0xFFFFFFFF:8> +// Defines size of memory region. Default: 0x00020000 +#define __RAM0_SIZE 0x00020000 +// + +// __RAM1 (unused) +// Base address <0x0-0xFFFFFFFF:8> +// Defines base address of memory region. +#define __RAM1_BASE 0 +// Region size [bytes] <0x0-0xFFFFFFFF:8> +// Defines size of memory region. +#define __RAM1_SIZE 0 +// + +// __RAM2 (unused) +// Base address <0x0-0xFFFFFFFF:8> +// Defines base address of memory region. +#define __RAM2_BASE 0 +// Region size [bytes] <0x0-0xFFFFFFFF:8> +// Defines size of memory region. +#define __RAM2_SIZE 0 +// + +// __RAM3 (unused) +// Base address <0x0-0xFFFFFFFF:8> +// Defines base address of memory region. +#define __RAM3_BASE 0 +// Region size [bytes] <0x0-0xFFFFFFFF:8> +// Defines size of memory region. +#define __RAM3_SIZE 0 +// + +// + +// Stack / Heap Configuration +// Stack Size (in Bytes) <0x0-0xFFFFFFFF:8> +// Heap Size (in Bytes) <0x0-0xFFFFFFFF:8> +#define __STACK_SIZE 0x00000200 +#define __HEAP_SIZE 0x00000C00 +// + + +#endif /* REGIONS_RTETEST_ARMCM0_H */ diff --git a/tools/projmgr/test/data/TestSolution/simple/RTE/_Debug_TEST_TARGET/RTE_Components.h b/tools/projmgr/test/data/TestSolution/simple/RTE/_Debug_TEST_TARGET/RTE_Components.h new file mode 100644 index 000000000..43c756bad --- /dev/null +++ b/tools/projmgr/test/data/TestSolution/simple/RTE/_Debug_TEST_TARGET/RTE_Components.h @@ -0,0 +1,20 @@ +/* + * CSOLUTION generated file: DO NOT EDIT! + * Generated by: csolution version 2.8.0 + * + * Project: 'project.Debug+TEST_TARGET' + * Target: 'Debug+TEST_TARGET' + */ + +#ifndef RTE_COMPONENTS_H +#define RTE_COMPONENTS_H + + +/* + * Define the Device Header File: + */ +#define CMSIS_device_header "ARMCM0.h" + + + +#endif /* RTE_COMPONENTS_H */ diff --git a/tools/projmgr/test/data/TestSolution/simple/main.c b/tools/projmgr/test/data/TestSolution/simple/main.c new file mode 100644 index 000000000..7e06c94d0 --- /dev/null +++ b/tools/projmgr/test/data/TestSolution/simple/main.c @@ -0,0 +1 @@ +/* Dummy */ diff --git a/tools/projmgr/test/data/TestSolution/simple/project.Debug+TEST_TARGET.cbuild.yml b/tools/projmgr/test/data/TestSolution/simple/project.Debug+TEST_TARGET.cbuild.yml new file mode 100644 index 000000000..96ce9a67c --- /dev/null +++ b/tools/projmgr/test/data/TestSolution/simple/project.Debug+TEST_TARGET.cbuild.yml @@ -0,0 +1,76 @@ +build: + generated-by: csolution version 2.8.0 + solution: test.csolution.yml + project: project.cproject.yml + context: project.Debug+TEST_TARGET + compiler: AC6 + device: ARM::RteTest_ARMCM0 + device-pack: ARM::RteTest_DFP@0.2.0 + device-books: + - name: http://infocenter.arm.com/help/topic/com.arm.doc.dui0497a/index.html + title: Cortex-M0 Device Generic Users Guide + dbgconf: + - file: RTE/Device/RteTest_ARMCM0/ARMCM.dbgconf + version: 0.0.2 + processor: + fpu: off + core: Cortex-M0 + packs: + - pack: ARM::RteTest_DFP@0.2.0 + path: ${CMSIS_PACK_ROOT}/ARM/RteTest_DFP/0.2.0 + define: + - ARMCM0 + - _RTE_ + define-asm: + - ARMCM0 + - _RTE_ + add-path: + - RTE/_Debug_TEST_TARGET + - ${CMSIS_PACK_ROOT}/ARM/RteTest_DFP/0.2.0/Device/ARM/ARMCM0/Include + add-path-asm: + - RTE/_Debug_TEST_TARGET + - ${CMSIS_PACK_ROOT}/ARM/RteTest_DFP/0.2.0/Device/ARM/ARMCM0/Include + output-dirs: + intdir: tmp + outdir: out/project/TEST_TARGET/Debug + rtedir: RTE + output: + - type: elf + file: project.axf + components: + - component: ARM::RteTest:CORE@0.1.1 + condition: Cortex-M Device + from-pack: ARM::RteTest_DFP@0.2.0 + selected-by: CORE + implements: RteTest:CORE@1.1.2 + files: + - file: ${CMSIS_PACK_ROOT}/ARM/RteTest_DFP/0.2.0/Doc/html/index.html + category: doc + version: 0.1.1 + apis: + - api: RteTest:CORE@1.1.2 + from-pack: ARM::RteTest_DFP@0.2.0 + implemented-by: ARM::RteTest:CORE@0.1.1 + files: + - file: https://arm-software.github.io/CMSIS_5/Pack/html/pdsc_apis_pg.html + category: doc + version: 1.1.2 + linker: + script: RTE/Device/RteTest_ARMCM0/ac6_linker_script.sct.src + regions: RTE/Device/RteTest_ARMCM0/regions_RteTest_ARMCM0.h + groups: + - group: Sources + files: + - file: main.c + category: sourceC + constructed-files: + - file: RTE/_Debug_TEST_TARGET/RTE_Components.h + category: header + licenses: + - license: + license-agreement: ${CMSIS_PACK_ROOT}/ARM/RteTest_DFP/0.2.0/Doc/license.txt + packs: + - pack: ARM::RteTest_DFP@0.2.0 + components: + - component: ARM::RteTest:CORE@0.1.1 + - component: RteTest:CORE(API) diff --git a/tools/projmgr/test/data/TestSolution/simple/project.cproject.yml b/tools/projmgr/test/data/TestSolution/simple/project.cproject.yml new file mode 100644 index 000000000..3e7a69114 --- /dev/null +++ b/tools/projmgr/test/data/TestSolution/simple/project.cproject.yml @@ -0,0 +1,11 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/Open-CMSIS-Pack/devtools/main/tools/projmgr/schemas/cproject.schema.json + +project: + device: RteTest_ARMCM0 + components: + - component: CORE + - component: ARM::Device:Startup + groups: + - group: Sources + files: + - file: main.c diff --git a/tools/projmgr/test/data/TestSolution/simple/test+TEST_TARGET.cbuild-run.yml b/tools/projmgr/test/data/TestSolution/simple/test+TEST_TARGET.cbuild-run.yml new file mode 100644 index 000000000..50548e546 --- /dev/null +++ b/tools/projmgr/test/data/TestSolution/simple/test+TEST_TARGET.cbuild-run.yml @@ -0,0 +1,102 @@ +cbuild-run: + generated-by: csolution version 2.8.0 + solution: test.csolution.yml + target-type: TEST_TARGET + compiler: AC6 + device: ARM::RteTest_ARMCM0 + device-pack: ARM::RteTest_DFP@0.2.0 + programming: + - algorithm: ${CMSIS_PACK_ROOT}/ARM/RteTest_DFP/0.2.0/Device/ARM/Flash/FAMILY.FLM + start: 0x00000000 + size: 0x00040000 + default: true + system-descriptions: + - file: ${CMSIS_PACK_ROOT}/ARM/RteTest_DFP/0.2.0/Device/ARM/SVD/ARMCM0.svd + type: svd + output: + - file: out/project/TEST_TARGET/Debug/project.axf + info: generate by project.Debug+TEST_TARGET + type: elf + system-resources: + memory: + - name: IROM1 + start: 0x00000000 + size: 0x00040000 + default: true + startup: true + from-pack: ARM::RteTest_DFP@0.2.0 + - name: IRAM1 + start: 0x20000000 + size: 0x00020000 + default: true + uninit: true + from-pack: ARM::RteTest_DFP@0.2.0 + debugger: + - name: + port: swd + clock: 10000000 + dbgconf: RTE/Device/RteTest_ARMCM0/ARMCM.dbgconf + debug-vars: + vars: | + __var DbgMCU_CR = 0x00000007; // DBGMCU_CR: DBG_SLEEP, DBG_STOP, DBG_STANDBY + __var TraceClk_Pin = 0x00040002; // PE2 + __var TraceD0_Pin = 0x00040003; // PE3 + __var TraceD1_Pin = 0x00040004; // PE4 + debug-sequences: + - name: DebugDeviceUnlock + blocks: + - execute: | + Sequence("CheckID"); + - name: DebugCoreStart + blocks: + - execute: | + // Replication of Standard Functionality + Write32(0xE000EDF0, 0xA05F0001); // Enable Core Debug via DHCSR + - info: DbgMCU registers + execute: | + // Device Specific Debug Setup + Write32(0x40021018, Read32(0x40021018) | 0x00400000); // Set RCC_APB2ENR.DBGMCUEN + - name: CheckID + blocks: + - execute: | + __var pidr1 = 0; + __var pidr2 = 0; + __var jep106id = 0; + __var ROMTableBase = 0; + + __ap = 0; // AHB-AP + + ROMTableBase = ReadAP(0xF8) & ~0x3; + + pidr1 = Read32(ROMTableBase + 0x0FE4); + pidr2 = Read32(ROMTableBase + 0x0FE8); + jep106id = ((pidr2 & 0x7) << 4 ) | ((pidr1 >> 4) & 0xF); + - if: jep106id != 0x20 + execute: | + Query(0, "Incorrect ID! Abort connection", 1); + Message(2, "Incorrect ID! Abort connection."); + - name: DebugPortStop + blocks: + - execute: | + __var connectionFlash = ( __connection & 0xF ) == 2 ; + __var FLASH_BASE = 0x40022000 ; + __var FLASH_CR = FLASH_BASE + 0x10 ; + __var OBL_LAUNCH_BIT = ( 1 << 13 ) ; + __var FLASH_CR_Value = 0 ; + __var DoDebugPortStop = 1 ; + __var DP_CTRL_STAT = 0x4 ; + __var DP_SELECT = 0x8 ; + - if: connectionFlash && DoDebugPortStop + execute: | + DoDebugPortStop = 0 ; + FLASH_CR_Value = Read32( FLASH_CR ) ; + __errorcontrol = 1 ; + // write OBL_LAUNCH bit (causes a reset) + Write32( FLASH_CR, FLASH_CR_Value | ( OBL_LAUNCH_BIT ) ) ; + __errorcontrol = 0 ; + - if: DoDebugPortStop + execute: | + // Switch to DP Register Bank 0 + WriteDP(DP_SELECT, 0x00000000); + // Power Down Debug port + WriteDP(DP_CTRL_STAT, 0x00000000); diff --git a/tools/projmgr/test/data/TestSolution/simple/test.cbuild-idx.yml b/tools/projmgr/test/data/TestSolution/simple/test.cbuild-idx.yml new file mode 100644 index 000000000..6aae918b5 --- /dev/null +++ b/tools/projmgr/test/data/TestSolution/simple/test.cbuild-idx.yml @@ -0,0 +1,20 @@ +build-idx: + generated-by: csolution version 2.8.0 + csolution: test.csolution.yml + tmpdir: tmp + cprojects: + - cproject: project.cproject.yml + cbuilds: + - cbuild: project.Debug+TEST_TARGET.cbuild.yml + project: project + configuration: .Debug+TEST_TARGET + errors: true + messages: + errors: + - "no component was found with identifier 'ARM::Device:Startup'\n did you mean 'Device:Startup&RteTest Startup'?" + - processing context 'project.Debug+TEST_TARGET' failed + warnings: + - "project.cproject.yml - 'device: Dname' is deprecated at this level and accepted in *.csolution.yml only" + info: + - test.cbuild-pack.yml - file is already up-to-date + - project.Debug+TEST_TARGET.cbuild.yml - file is already up-to-date diff --git a/tools/projmgr/test/data/TestSolution/simple/test.cbuild-pack.yml b/tools/projmgr/test/data/TestSolution/simple/test.cbuild-pack.yml new file mode 100644 index 000000000..20081b017 --- /dev/null +++ b/tools/projmgr/test/data/TestSolution/simple/test.cbuild-pack.yml @@ -0,0 +1,5 @@ +cbuild-pack: + resolved-packs: + - resolved-pack: ARM::RteTest_DFP@0.2.0 + selected-by-pack: + - ARM::RteTest_DFP diff --git a/tools/projmgr/test/data/TestSolution/simple/test.cbuild-set.yml b/tools/projmgr/test/data/TestSolution/simple/test.cbuild-set.yml new file mode 100644 index 000000000..9505a9941 --- /dev/null +++ b/tools/projmgr/test/data/TestSolution/simple/test.cbuild-set.yml @@ -0,0 +1,4 @@ +cbuild-set: + generated-by: vscode-cmsis-csolution version 1.50.1-24-gaf492fc + contexts: + - context: project.Debug+TEST_TARGET diff --git a/tools/projmgr/test/data/TestSolution/simple/test.csolution.yml b/tools/projmgr/test/data/TestSolution/simple/test.csolution.yml new file mode 100644 index 000000000..12d068192 --- /dev/null +++ b/tools/projmgr/test/data/TestSolution/simple/test.csolution.yml @@ -0,0 +1,16 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/Open-CMSIS-Pack/devtools/main/tools/projmgr/schemas/csolution.schema.json + +solution: + target-types: + - type: TEST_TARGET + + build-types: + - type: Debug + compiler: AC6 + + packs: + - pack: ARM::RteTest_DFP + + projects: + - project: ./project.cproject.yml + diff --git a/tools/projmgr/test/src/ProjMgrRpcTests.cpp b/tools/projmgr/test/src/ProjMgrRpcTests.cpp new file mode 100644 index 000000000..7e45c02c1 --- /dev/null +++ b/tools/projmgr/test/src/ProjMgrRpcTests.cpp @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2020-2025 Arm Limited. All rights reserved. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "ProjMgr.h" +#include "ProjMgrTestEnv.h" +#include "ProjMgrRpcServer.h" +#include "ProjMgrRpcServerData.h" + + +#include "ProjMgrLogger.h" + + +using namespace std; + +class ProjMgrRpcTests : public ProjMgr, public ::testing::Test { +protected: + ProjMgrRpcTests() {} + virtual ~ProjMgrRpcTests() {} +}; + +TEST_F(ProjMgrRpcTests, Load_Solution) { + + + +} + +// end of ProjMgrRpcTests.cpp diff --git a/tools/projmgr/test/src/ProjMgrUnitTests.cpp b/tools/projmgr/test/src/ProjMgrUnitTests.cpp index 7e73b6c12..eb94b47a0 100644 --- a/tools/projmgr/test/src/ProjMgrUnitTests.cpp +++ b/tools/projmgr/test/src/ProjMgrUnitTests.cpp @@ -4011,11 +4011,25 @@ TEST_F(ProjMgrUnitTests, Convert_ValidationResults_Dependencies) { argv[5] = (char*)"-c"; map testData = { - {"conflict+CM0", "warning csolution: dependency validation for context 'conflict+CM0' failed:\nCONFLICT RteTest:ApiExclusive(API)\n ARM::RteTest:ApiExclusive:S1\n ARM::RteTest:ApiExclusive:S2" }, - {"incompatible+CM0", "warning csolution: dependency validation for context 'incompatible+CM0' failed:\nINCOMPATIBLE ARM::RteTest:Check:Incompatible@0.9.9\n deny RteTest:Dependency:Incompatible_component" }, - {"incompatible-variant+CM0", "warning csolution: dependency validation for context 'incompatible-variant+CM0' failed:\nINCOMPATIBLE_VARIANT ARM::RteTest:Check:IncompatibleVariant@0.9.9\n require RteTest:Dependency:Variant&Compatible" }, - {"missing+CM0", "warning csolution: dependency validation for context 'missing+CM0' failed:\nMISSING ARM::RteTest:Check:Missing@0.9.9\n require RteTest:Dependency:Missing" }, - {"selectable+CM0", "warning csolution: dependency validation for context 'selectable+CM0' failed:\nSELECTABLE ARM::Device:Startup&RteTest Startup@2.0.3\n require RteTest:CORE" } + {"conflict+CM0", "warning csolution: dependency validation for context 'conflict+CM0' failed:\n\ +CONFLICT RteTest:ApiExclusive@1.0.0\n\ + ARM::RteTest:ApiExclusive:S1\n\ + ARM::RteTest:ApiExclusive:S2" }, + {"incompatible+CM0", "warning csolution: dependency validation for context 'incompatible+CM0' failed:\n\ +INCOMPATIBLE ARM::RteTest:Check:Incompatible@0.9.9\n\ + deny RteTest:Dependency:Incompatible_component\n\ + ARM::RteTest:Dependency:Incompatible_component" }, + {"incompatible-variant+CM0", "warning csolution: dependency validation for context 'incompatible-variant+CM0' failed:\n\ +INCOMPATIBLE_VARIANT ARM::RteTest:Check:IncompatibleVariant@0.9.9\n\ + require RteTest:Dependency:Variant&Compatible\n\ + ARM::RteTest:Dependency:Variant" }, + {"missing+CM0", "warning csolution: dependency validation for context 'missing+CM0' failed:\n\ +MISSING ARM::RteTest:Check:Missing@0.9.9\n\ + require RteTest:Dependency:Missing" }, + {"selectable+CM0", "warning csolution: dependency validation for context 'selectable+CM0' failed:\n\ +SELECTABLE ARM::Device:Startup&RteTest Startup@2.0.3\n\ + require RteTest:CORE\n\ + ARM::RteTest:CORE" } }; for (const auto& [context, expected] : testData) { diff --git a/tools/projmgr/test/src/ProjMgrWorkerUnitTests.cpp b/tools/projmgr/test/src/ProjMgrWorkerUnitTests.cpp index f448308c3..7a5eb04b4 100644 --- a/tools/projmgr/test/src/ProjMgrWorkerUnitTests.cpp +++ b/tools/projmgr/test/src/ProjMgrWorkerUnitTests.cpp @@ -528,8 +528,10 @@ TEST_F(ProjMgrWorkerUnitTests, ProcessDependencies) { EXPECT_FALSE(ValidateContext(context)); ASSERT_EQ(expected.size(), context.validationResults.size()); map> dependenciesMap; - for (const auto& [result, component, dependencies, _] : context.validationResults) { - dependenciesMap[component] = dependencies; + for (const auto& validation : context.validationResults) { + for (const auto& condition : validation.conditions) { + dependenciesMap[validation.id].insert(condition.expression); + } } for (const auto& [expectedComponent, expectedDependencies] : expected) { EXPECT_TRUE(dependenciesMap.find(expectedComponent) != dependenciesMap.end());