diff --git a/libs/rtemodel/include/RteComponent.h b/libs/rtemodel/include/RteComponent.h index 6d9eae914..172d39e13 100644 --- a/libs/rtemodel/include/RteComponent.h +++ b/libs/rtemodel/include/RteComponent.h @@ -431,7 +431,7 @@ class RteApiContainer : public RteItem RteItem* CreateItem(const std::string& tag) override; }; - +typedef std::map RteApiMap; typedef std::map RteComponentMap; typedef std::map RteComponentVersionMap; typedef std::list RteComponentList; @@ -637,9 +637,10 @@ class RteComponentAggregate : public RteComponentContainer /** * @brief checks if the aggregate contains at least one component whose attributes match those in the supplied map * @param attributes map of key-value pairs to match against component attributes + * @param bRespectVersion flag to consider Cversion and Capiversion attributes, default is true * @return true if at least one component has all attributes found in the supplied map */ - bool MatchComponentAttributes(const std::map& attributes) const override; + bool MatchComponentAttributes(const std::map& attributes, bool bRespectVersion = true) const override; /** * @brief get short component aggregate display name to use in a tree view diff --git a/libs/rtemodel/include/RteItem.h b/libs/rtemodel/include/RteItem.h index c516f8f1a..b375b7ec1 100644 --- a/libs/rtemodel/include/RteItem.h +++ b/libs/rtemodel/include/RteItem.h @@ -553,16 +553,18 @@ class RteItem : public XmlTreeItem /** * @brief checks if the item contains all attributes matching those in the supplied map * @param attributes map of key-value pairs to match against component attributes + * @param bRespectVersion flag to consider Cversion and Capiversion attributes, default is true * @return true if the item has all attributes found in the supplied map */ - virtual bool MatchComponentAttributes(const std::map& attributes) const; + virtual bool MatchComponentAttributes(const std::map& attributes, bool bRespectVersion = true) const; /** * @brief check if the item matches supplied API attributes * @param attributes collection of 'C' (API) attributes + * @param bRespectVersion flag to consider Capiversion attribute, default is true * @return true if the item matches supplied API attributes */ - virtual bool MatchApiAttributes(const std::map& attributes) const; + virtual bool MatchApiAttributes(const std::map& attributes, bool bRespectVersion = true) const; /** * @brief check if given collection of attributes contains the same values for "Dname", "Pname" and "Dvendor" diff --git a/libs/rtemodel/include/RteModel.h b/libs/rtemodel/include/RteModel.h index 28a8512c0..4941c04de 100644 --- a/libs/rtemodel/include/RteModel.h +++ b/libs/rtemodel/include/RteModel.h @@ -198,6 +198,13 @@ class RteModel : public RteItem */ RteComponent* FindComponents(const RteItem& item, std::list& components) const override; + /** + * @brief find first component matching supplied attributes + * @param item reference to RteItem containing component attributes to match + * @return pointer to first component found if any, null otherwise +*/ + RteComponent* FindFirstComponent(const RteItem& item) const; + /** * @brief check if this object has no children * @return true if this object has no children @@ -205,12 +212,19 @@ class RteModel : public RteItem bool IsEmpty() const override { return m_children.size() == 0; } /** - * @brief getter for component by given component ID + * @brief getter for component by given unique component ID * @param uniqueID component ID * @return RteComponent pointer */ RteComponent* GetComponent(const std::string& uniqueID) const; + /** + * @brief getter for component by given component ID + * @param id component ID with oe without version + * @return RteComponent pointer + */ + RteComponent* FindComponent(const std::string& id) const; + /** * @brief getter for component by given RteComponentInstance and version to be matched * @param ci given RteComponentInstance object @@ -239,11 +253,19 @@ class RteModel : public RteItem */ RteApi* GetApi(const std::string& id) const; + /** + * @brief get latest available API version + * @param id API ID (can be with or without version) + * @return RteApi pointer + */ + RteApi* GetLatestApi(const std::string& id) const; + + /** * @brief getter for collection of APIs * @return collection of api ID mapped to RteApi object pointer */ - const std::map& GetApiList() const { return m_apiList; } + const RteApiMap& GetApiList() const { return m_apiList; } /** * @brief getter for bundles @@ -544,7 +566,7 @@ class RteModel : public RteItem RteCallback* m_callback; // pointer to callback // components, APIs, taxonomy - std::map m_apiList; // collection of available APIs + RteApiMap m_apiList; // collection of available APIs RteComponentMap m_componentList; // full collection of unique components std::map m_taxonomy; // collection of standard Class descriptions RteBundleMap m_bundles; // collection of available bundles diff --git a/libs/rtemodel/src/RteComponent.cpp b/libs/rtemodel/src/RteComponent.cpp index 250884c5a..868dd8e11 100644 --- a/libs/rtemodel/src/RteComponent.cpp +++ b/libs/rtemodel/src/RteComponent.cpp @@ -119,24 +119,28 @@ size_t RteComponent::GetFileCount() const return m_files ? m_files->GetChildCount() : 0; } - RteItem::ConditionResult RteComponent::GetConditionResult(RteConditionContext* context) const { ConditionResult result = RteItem::GetConditionResult(context); if (result < INSTALLED) return result; RteTarget* target = context->GetTarget(); - RteApi* api = GetApi(target, false); - if (api && api->IsExclusive()) { + RteItem::ConditionResult apiRes = IsApiAvailable(target); + if (apiRes < FULFILLED) { + return apiRes; + } else if(apiRes == FULFILLED) { // api exists + RteApi* thisApi = GetApi(target, true); // no need to check for NULL set components; - target->GetComponentsForApi(api, components, true); - if (components.size() > 1) - return CONFLICT; + apiRes = target->GetComponentsForApi(thisApi, components, true); + if (apiRes == CONFLICT) { + return apiRes; + } } return result; } + const string& RteComponent::GetVendorString() const { if (IsApi()) @@ -277,18 +281,18 @@ RteItem::ConditionResult RteComponent::IsApiAvailable(RteTarget* target) const if (!HasApi(target)) { return IGNORED; } - RteApi* api = target->GetApi(GetApiID(false)); + RteApi* api = GetApi(target, true); + if (api) { + return FULFILLED; + } + api = GetApi(target, false); if (!api) { return MISSING_API; } - string availableApiVersion = api->GetApiVersionString(); - if (availableApiVersion.empty()) + if (api->GetApiVersionString().empty()) { return FULFILLED; // API without version - deprecated case that we still need to support - string requiredApiVersion = GetApiVersionString(); - if (!requiredApiVersion.empty() && VersionCmp::RangeCompare(availableApiVersion, requiredApiVersion) < 0) { - return MISSING_API_VERSION; } - return FULFILLED; + return MISSING_API_VERSION; } @@ -392,7 +396,7 @@ string RteApi::GetFullDisplayName() const string RteApi::GetComponentUniqueID() const { - return GetApiID(false); // api ID is always without version (the latest is used) + return GetApiID(true); } string RteApi::GetComponentID(bool withVersion) const @@ -459,20 +463,20 @@ bool RteComponentAggregate::SetSelected(int nSel) return false; } -bool RteComponentAggregate::MatchComponentAttributes(const map& attributes) const +bool RteComponentAggregate::MatchComponentAttributes(const map& attributes, bool bRespectVersion) const { if (!m_components.empty()) { for (auto itvar = m_components.begin(); itvar != m_components.end(); itvar++) { const RteComponentVersionMap& versionMap = itvar->second; for (auto it = versionMap.begin(); it != versionMap.end(); it++) { RteComponent* c = it->second; - if (c && c->MatchComponentAttributes(attributes)) + if (c && c->MatchComponentAttributes(attributes, bRespectVersion)) return true; } } return false; } else if (m_instance) - return m_instance->MatchComponentAttributes(attributes); + return m_instance->MatchComponentAttributes(attributes, bRespectVersion); return false; } diff --git a/libs/rtemodel/src/RteCondition.cpp b/libs/rtemodel/src/RteCondition.cpp index ef98e4a2d..958d73ab8 100644 --- a/libs/rtemodel/src/RteCondition.cpp +++ b/libs/rtemodel/src/RteCondition.cpp @@ -706,6 +706,9 @@ string RteDependencyResult::GetMessageText() const case RteItem::MISSING_API: message = "API is missing"; break; + case RteItem::MISSING_API_VERSION: + message = "API with required or compatible version is missing"; + break; case RteItem::CONFLICT: message = "Conflict, select exactly one component from list"; break; diff --git a/libs/rtemodel/src/RteItem.cpp b/libs/rtemodel/src/RteItem.cpp index 890493b45..095eb0926 100644 --- a/libs/rtemodel/src/RteItem.cpp +++ b/libs/rtemodel/src/RteItem.cpp @@ -43,7 +43,7 @@ const std::string& RteItem::ConditionResultToString(RteItem::ConditionResult res "INCOMPATIBLE", // incompatible component is selected "INCOMPATIBLE_VERSION", // incompatible version of component is selected "INCOMPATIBLE_VARIANT", // incompatible variant of component is selected - "CONFLICT", // more than one exclusive component selected + "CONFLICT", // several exclusive or incompatible components are selected "INSTALLED", // matching component is installed, but not selectable because not in active bundle "SELECTABLE", // matching component is installed, but not selected "FULFILLED", // required component selected or no dependency exists @@ -159,8 +159,7 @@ string RteItem::GetDependencyExpressionID() const string RteItem::GetComponentID(bool withVersion) const // to use in filtered list { if (IsApi()) { - withVersion = false; // api ID is always without version (the latest is used) - return GetApiID(false); + return GetApiID(withVersion); } return ConstructComponentID(withVersion); } @@ -168,7 +167,7 @@ string RteItem::GetComponentID(bool withVersion) const // to use in filtered lis string RteItem::GetComponentUniqueID() const { if (IsApi()) { - return GetApiID(false); // api ID is always without version (the latest is used) + return GetApiID(true); } string id = ConstructComponentID(true); id += RteConstants::OBRACE_STR; @@ -602,7 +601,7 @@ bool RteItem::MatchComponent(const RteItem& item) const } -bool RteItem::MatchComponentAttributes(const map& attributes) const +bool RteItem::MatchComponentAttributes(const map& attributes, bool bRespectVersion) const { if (attributes.empty()) // no limiting attributes return true; @@ -621,7 +620,7 @@ bool RteItem::MatchComponentAttributes(const map& attributes) co return false; } if (a == "Cversion" || a == "Capiversion") { // version of this component should match supplied version range - int verCompareResult = VersionCmp::RangeCompare(it->second, v); + int verCompareResult = bRespectVersion? VersionCmp::CompatibleRangeCompare(it->second, v) : 0; if (verCompareResult != 0) return false; } else { @@ -633,7 +632,7 @@ bool RteItem::MatchComponentAttributes(const map& attributes) co } -bool RteItem::MatchApiAttributes(const map& attributes) const +bool RteItem::MatchApiAttributes(const map& attributes, bool bRespectVersion) const { if (attributes.empty()) return false; @@ -644,10 +643,10 @@ bool RteItem::MatchApiAttributes(const map& attributes) const if (!a.empty() && a[0] == 'C' && a != "Cvendor") { const string& v = itm->second; ita = attributes.find(a); - if (a == "Capiversion") { // version of this api should be >= supplied version + if (a == "Capiversion") { // version of this api should be >= supplied version, but less than next major if (ita == attributes.end()) continue; // version is not set => accept any - if (VersionCmp::RangeCompare(v, ita->second) != 0) // this version is within supplied range + if (bRespectVersion && VersionCmp::CompatibleRangeCompare(v, ita->second) != 0) // this version is within supplied range return false; } else { if (ita == attributes.end()) // no api attribute in supplied map diff --git a/libs/rtemodel/src/RteModel.cpp b/libs/rtemodel/src/RteModel.cpp index 7381e3f01..b038694a1 100644 --- a/libs/rtemodel/src/RteModel.cpp +++ b/libs/rtemodel/src/RteModel.cpp @@ -19,6 +19,8 @@ #include "CprjFile.h" #include "RteFsUtils.h" +#include "RteConstants.h" + #include "XMLTree.h" using namespace std; @@ -28,6 +30,7 @@ RteModel::RteModel(RteItem* parent, PackageState packageState) : RteItem(parent), m_packageState(packageState), m_callback(NULL), + m_apiList(VersionCmp::Greater(RteConstants::PREFIX_CVERSION_CHAR)), m_bUseDeviceTree(true), m_filterContext(NULL) { @@ -215,6 +218,12 @@ RteComponent* RteModel::FindComponents(const RteItem& item, std::list components; + return FindComponents(item, components); +} + RteComponent* RteModel::GetComponent(const string& uniqueID) const { // look in the APIs @@ -231,19 +240,23 @@ RteComponent* RteModel::GetComponent(const string& uniqueID) const return NULL; } +RteComponent* RteModel::FindComponent(const std::string& id) const +{ + bool withVersion = id.find(RteConstants::PREFIX_CVERSION_CHAR) != string::npos; + for (auto [_, c] : m_componentList) { + if (c->GetComponentID(withVersion) == id) { + return c; + } + } + return nullptr; +} + RteComponent* RteModel::GetComponent(RteComponentInstance* ci, bool matchVersion) const { - if (ci->IsApi() || matchVersion) + if (ci->IsApi() || matchVersion) { return GetComponent(ci->GetID()); - - string id = ci->GetComponentID(false); - - for (auto it = m_componentList.begin(); it != m_componentList.end(); it++) { - RteComponent* c = it->second; - if (c->GetComponentID(false) == id) - return c; } - return NULL; + return FindComponent(ci->GetComponentID(false)); } @@ -255,25 +268,41 @@ RteApi* RteModel::GetApi(const map& componentAttributes) const if (a && a->MatchApiAttributes(componentAttributes)) return a; } - return NULL; + return nullptr; } RteApi* RteModel::GetApi(const string& id) const { - map::const_iterator it = m_apiList.find(id); - if (it != m_apiList.end()) { - RteApi* a = it->second; - return a; + if (id.find(RteConstants::PREFIX_CVERSION_CHAR) != string::npos) { + auto it = m_apiList.find(id); + if (it != m_apiList.end()) { + RteApi* api = it->second; + return api; + } + return nullptr; } - return NULL; + return GetLatestApi(id); } +RteApi* RteModel::GetLatestApi(const string& id) const +{ + string commonId = RteUtils::GetPrefix(id, RteConstants::PREFIX_CVERSION_CHAR); + // get highest API version + for (auto [key, api] : m_apiList) { + if (RteUtils::GetPrefix(key, RteConstants::PREFIX_CVERSION_CHAR) == commonId) { + return api; + } + } + return nullptr; +} + + RteBundle* RteModel::GetBundle(const string& id) const { auto it = m_bundles.find(id); if (it != m_bundles.end()) return it->second; - return NULL; + return nullptr; } RteBundle* RteModel::GetLatestBundle(const string& id) const diff --git a/libs/rtemodel/src/RteProject.cpp b/libs/rtemodel/src/RteProject.cpp index 4f046dfde..8c08c24b5 100644 --- a/libs/rtemodel/src/RteProject.cpp +++ b/libs/rtemodel/src/RteProject.cpp @@ -2215,30 +2215,51 @@ bool RteProject::Validate() target->AddMissingPackId(packId, url); } } else if (!c->IsApi()) { - RteItem::ConditionResult apiResult = c->IsApiAvailable(target); + RteItem::ConditionResult apiResult = c->GetConditionResult(target->GetDependencySolver()); + if (apiResult >= RteItem::FULFILLED) { + continue; + } + string apiVer = c->GetApiVersionString(); if (apiResult == RteItem::MISSING_API) { bValid = false; string msg = "Error #542: Component '"; msg += c->GetFullDisplayName(); msg += "': API version '"; - msg += c->GetApiVersionString(); - msg += "' or higher is required. "; + msg += apiVer; + msg += "' or compatible is required."; msg += "API definition is missing (no pack ID is available)"; m_errors.push_back(msg); } else if (apiResult == RteItem::MISSING_API_VERSION) { bValid = false; - RteApi* api = c->GetApi(target, false); string msg = "Error #552: Component '"; msg += c->GetFullDisplayName(); msg += "': API version '"; - msg += c->GetApiVersionString(); - msg += "' or higher is required."; - if (api) { + msg += apiVer; + msg += "' or compatible is required."; + RteApi* availableApi = c->GetApi(target, false); + if (availableApi) { + string availableApiVer = availableApi->GetApiVersionString(); msg += " (Version '"; - msg += api->GetApiVersionString(); + msg += availableApiVer; msg += "' is found in pack '"; - msg += api->GetPackageID(true); - msg += "')."; + msg += availableApi->GetPackageID(true); + msg += "', install "; + msg += VersionCmp::Compare(apiVer, availableApiVer) < 0 ? "previous" : "next"; + msg += " pack version)."; + } + m_errors.push_back(msg); + } else if (apiResult == RteItem::CONFLICT) { + bValid = false; + RteApi* api = c->GetApi(target, true); + apiVer = api->GetVersionString(); + string msg = "Error #553: Component '"; + msg += c->GetFullDisplayName(); + if (api->IsExclusive()) { + msg += "': conflicts with other components of the same API"; + msg += ": select only one component"; + } else { + msg += "': uses API version '" + apiVer + "' that conflicts with other components of the same API"; + msg += ": select only components with compatible API versions"; } m_errors.push_back(msg); } diff --git a/libs/rtemodel/src/RteTarget.cpp b/libs/rtemodel/src/RteTarget.cpp index f79b8a908..230a5b283 100644 --- a/libs/rtemodel/src/RteTarget.cpp +++ b/libs/rtemodel/src/RteTarget.cpp @@ -331,8 +331,7 @@ RteItem::ConditionResult RteTarget::GetDepsResult(map::iterator itc = components.begin(); itc != components.end(); itc++) { - RteComponent* c = *itc; + for (RteComponent* c : components) { if (c && IsComponentFiltered(c)) { RteComponentAggregate* a = GetComponentAggregate(c); if (a) @@ -1561,8 +1560,9 @@ string RteTarget::GetCMSISCoreIncludePath() const RteComponent* RteTarget::ResolveComponent(RteComponentInstance* ci) const { - if (ci->IsApi()) - return m_filteredModel->GetApi(ci->GetComponentUniqueID()); + if (ci->IsApi()) { + return m_filteredModel->GetApi(ci->GetAttributes()); + } VersionCmp::MatchMode mode = ci->GetVersionMatchMode(GetName()); RteComponent* c = NULL; @@ -1611,36 +1611,27 @@ RteComponent* RteTarget::GetPotentialComponent(RteComponentInstance* ci) const RteItem::ConditionResult RteTarget::GetComponentsForApi(RteApi* api, set& components, bool selectedOnly) const { - ConditionResult result = MISSING; - if (api) { - // make copy of the attributes to remove api version - map apiAttributes = api->GetAttributes(); - if (selectedOnly) { - auto it = apiAttributes.find("Capiversion"); - if (it != apiAttributes.end()) - apiAttributes.erase(it); - } - return GetComponentsForApi(api, apiAttributes, components, selectedOnly); + if (!api) { + return MISSING_API; } - return result; -} - - -RteItem::ConditionResult RteTarget::GetComponentsForApi(RteApi* api, const map& componentAttributes, set& components, bool selectedOnly) const -{ - bool exclusive = api != NULL && api->IsExclusive(); + set apiVersions; + bool exclusive = api->IsExclusive(); ConditionResult result = MISSING; + auto& apiAttributes = api->GetAttributes(); int nSelected = 0; - for (auto it = m_filteredComponents.begin(); it != m_filteredComponents.end(); it++) { - RteComponent* c = it->second; - if (c->MatchComponentAttributes(componentAttributes)) { + for (auto [id, c] : m_filteredComponents) { + if (c->MatchComponentAttributes(apiAttributes, false)) { if (IsComponentSelected(c)) { components.insert(c); nSelected++; - if (exclusive && nSelected > 1) + apiVersions.insert(GetApi(c->GetAttributes())); + if (exclusive && nSelected > 1) { + result = CONFLICT; + } else if (apiVersions.size() > 1) { //effectively means different API version result = CONFLICT; - else + } else if (result == MISSING) { result = FULFILLED; + } } else if (result == MISSING) { result = INSTALLED; if (!selectedOnly) diff --git a/libs/rtemodel/test/src/RteChkTest.cpp b/libs/rtemodel/test/src/RteChkTest.cpp index fdb590c01..0ecd2713a 100644 --- a/libs/rtemodel/test/src/RteChkTest.cpp +++ b/libs/rtemodel/test/src/RteChkTest.cpp @@ -30,8 +30,8 @@ Generic: 3\n\ DFP: 3\n\ BSP: 1\n\ \n\ -Components: 53\n\ -From generic packs: 31\n\ +Components: 55\n\ +From generic packs: 33\n\ From DFP: 22\n\ From BSP: 0\n\ \n\ @@ -51,7 +51,7 @@ completed\n"; int res = rteChk.RunCheckRte(); EXPECT_EQ(res, 0); EXPECT_EQ(rteChk.GetPackCount(), 7); - EXPECT_EQ(rteChk.GetComponentCount(), 53); + EXPECT_EQ(rteChk.GetComponentCount(), 55); EXPECT_EQ(rteChk.GetDeviceCount(), 10); EXPECT_EQ(rteChk.GetBoardCount(), 13); diff --git a/libs/rtemodel/test/src/RteConditionTest.cpp b/libs/rtemodel/test/src/RteConditionTest.cpp index 2f06acdc1..2829bc43f 100644 --- a/libs/rtemodel/test/src/RteConditionTest.cpp +++ b/libs/rtemodel/test/src/RteConditionTest.cpp @@ -53,6 +53,7 @@ TEST_F(RteConditionTest, MissingIgnoredFulfilledSelectable) { rteKernel.SetCmsisPackRoot(RteModelTestConfig::CMSIS_PACK_ROOT); RteCprjProject* loadedCprjProject = rteKernel.LoadCprj(RteTestM3_cprj); ASSERT_NE(loadedCprjProject, nullptr); + EXPECT_TRUE(loadedCprjProject->Validate()); RteTarget* activeTarget = loadedCprjProject->GetActiveTarget(); ASSERT_NE(activeTarget, nullptr); RteConditionContext* filterContext = activeTarget->GetFilterContext(); @@ -79,7 +80,6 @@ TEST_F(RteConditionTest, MissingIgnoredFulfilledSelectable) { ASSERT_NE(denyIncompatibleVariant, nullptr); // select component to check dependencies - list components; RteComponentInstance item(nullptr); item.SetTag("component"); item.SetAttributes({ {"Cclass","RteTest" }, @@ -88,7 +88,7 @@ TEST_F(RteConditionTest, MissingIgnoredFulfilledSelectable) { {"condition","AcceptDependency"} }); item.SetPackageAttributes(packInfo); - RteComponent* c = rteModel->FindComponents(item, components); + RteComponent* c = rteModel->FindFirstComponent(item); ASSERT_NE(c, nullptr); activeTarget->SelectComponent(c, 1, true); EXPECT_EQ(depSolver->GetConditionResult(), RteItem::FULFILLED); // required dependency already selected @@ -103,8 +103,7 @@ TEST_F(RteConditionTest, MissingIgnoredFulfilledSelectable) { item.SetAttributes({ {"Cclass","RteTest" }, {"Cgroup", "LocalFile" }, {"Cversion","0.0.3"} }); - components.clear(); - c = rteModel->FindComponents(item, components); + c = rteModel->FindFirstComponent(item); ASSERT_NE(c, nullptr); activeTarget->SelectComponent(c, 0, true); EXPECT_EQ(depSolver->GetConditionResult(), RteItem::FULFILLED); // still fulfilled @@ -116,8 +115,7 @@ TEST_F(RteConditionTest, MissingIgnoredFulfilledSelectable) { EXPECT_EQ(denyDenyDependency->Evaluate(depSolver), RteItem::FULFILLED); item.SetAttribute("Cgroup", "GlobalFile"), - components.clear(); - c = rteModel->FindComponents(item, components); + c = rteModel->FindFirstComponent(item); ASSERT_NE(c, nullptr); activeTarget->SelectComponent(c, 0, true); EXPECT_EQ(depSolver->GetConditionResult(), RteItem::SELECTABLE); @@ -136,8 +134,7 @@ TEST_F(RteConditionTest, MissingIgnoredFulfilledSelectable) { {"Cgroup", "RequireDependency" }, {"Cversion","0.9.9"}, {"condition", "GlobalFile"}}); - components.clear(); - c = rteModel->FindComponents(item, components); + c = rteModel->FindFirstComponent(item); ASSERT_NE(c, nullptr); activeTarget->SelectComponent(c, 1, true); EXPECT_EQ(depSolver->GetConditionResult(), RteItem::SELECTABLE); @@ -156,21 +153,108 @@ TEST_F(RteConditionTest, MissingIgnoredFulfilledSelectable) { item.SetAttributes({ {"Cclass","RteTest" }, {"Cgroup", "Dependency" }, {"Csub", "Variant" }, - {"Cvariant","Compatible"}, - {"Cversion","0.9.9"}, + {"Cvariant","Compatible"} }); - components.clear(); - c = rteModel->FindComponents(item, components); + c = rteModel->FindFirstComponent(item); ASSERT_NE(c, nullptr); activeTarget->SelectComponent(c, 1, true); EXPECT_EQ(denyIncompatibleVariant->Evaluate(depSolver), RteItem::INCOMPATIBLE); item.SetAttribute("Cvariant", "Incompatible"); - components.clear(); - c = rteModel->FindComponents(item, components); + c = rteModel->FindFirstComponent(item); activeTarget->SelectComponent(c, 1, true); ASSERT_NE(c, nullptr); EXPECT_EQ(denyIncompatibleVariant->Evaluate(depSolver), RteItem::FULFILLED); + // API + item.SetAttributes({ {"Cclass","RteTest" }, + {"Cgroup", "MissingApi" } }); + c = rteModel->FindFirstComponent(item); + ASSERT_NE(c, nullptr); + EXPECT_EQ(c->GetConditionResult(depSolver), RteItem::MISSING_API); + activeTarget->SelectComponent(c, 1, true); + loadedCprjProject->Apply(); + EXPECT_FALSE(loadedCprjProject->Validate()); + activeTarget->SelectComponent(c, 0, true); + loadedCprjProject->Apply(); + EXPECT_TRUE(loadedCprjProject->Validate()); + + item.SetAttributes({ {"Cclass","RteTest" }, + {"Cgroup", "ApiNonExclusive" }, + {"Csub", "MissingApiVersion"} }); + c = rteModel->FindFirstComponent(item); + ASSERT_NE(c, nullptr); + EXPECT_EQ(c->GetConditionResult(depSolver), RteItem::MISSING_API_VERSION); + activeTarget->SelectComponent(c, 1, true); + loadedCprjProject->Apply(); + EXPECT_FALSE(loadedCprjProject->Validate()); + activeTarget->SelectComponent(c, 0, true); + loadedCprjProject->Apply(); + EXPECT_TRUE(loadedCprjProject->Validate()); + + item.SetAttribute("Csub", "MissingApiVersionMin"); + c = rteModel->FindFirstComponent(item); + ASSERT_NE(c, nullptr); + EXPECT_EQ(c->GetConditionResult(depSolver), RteItem::MISSING_API_VERSION); + +// Exclusive API conflict + item.SetAttributes({ {"Cclass","RteTest" }, + {"Cgroup", "ApiExclusive" }, + {"Csub", "S1" } }); + c = rteModel->FindFirstComponent(item); + ASSERT_NE(c, nullptr); + activeTarget->SelectComponent(c, 1, true); + EXPECT_EQ(c->GetConditionResult(depSolver), RteItem::IGNORED); + item.SetAttribute("Csub", "S2"); + RteComponent* c2 = rteModel->FindFirstComponent(item); + ASSERT_NE(c2, nullptr); + activeTarget->SelectComponent(c2, 1, true); + EXPECT_EQ(c->GetConditionResult(depSolver), RteItem::CONFLICT); + EXPECT_EQ(c2->GetConditionResult(depSolver), RteItem::CONFLICT); + loadedCprjProject->Apply(); + EXPECT_FALSE(loadedCprjProject->Validate()); + activeTarget->SelectComponent(c2, 0, true); + EXPECT_EQ(c->GetConditionResult(depSolver), RteItem::IGNORED); + EXPECT_EQ(c2->GetConditionResult(depSolver), RteItem::IGNORED); + loadedCprjProject->Apply(); + EXPECT_TRUE(loadedCprjProject->Validate()); + + // API version conflict + item.SetAttributes({ {"Cclass","RteTest" }, + {"Cgroup", "ApiNonExclusive" }, + {"Csub", "SN1" }}); + c = rteModel->FindFirstComponent(item); + ASSERT_NE(c, nullptr); + RteApi* api = c->GetApi(activeTarget, true); + ASSERT_NE(api, nullptr); + EXPECT_EQ(api->GetVersionString(), "1.1.0"); + activeTarget->SelectComponent(c, 1, true); + EXPECT_EQ(c->GetConditionResult(depSolver), RteItem::IGNORED); + item.SetAttribute("Csub", "SN2"); + + c2 = rteModel->FindFirstComponent(item); + ASSERT_NE(c2, nullptr); + api = c2->GetApi(activeTarget, true); + EXPECT_EQ(api->GetVersionString(), "1.1.0"); + activeTarget->SelectComponent(c2, 1, true); + EXPECT_EQ(c2->GetConditionResult(depSolver), RteItem::IGNORED); + + item.SetAttribute("Csub", "SN3"); + RteComponent* c3 = rteModel->FindFirstComponent(item); + ASSERT_NE(c3, nullptr); + api = c3->GetApi(activeTarget, true); + EXPECT_EQ(api->GetVersionString(), "2.0.0"); + activeTarget->SelectComponent(c3, 1, true); + loadedCprjProject->Apply(); + EXPECT_EQ(c3->GetConditionResult(depSolver), RteItem::CONFLICT); + EXPECT_EQ(c2->GetConditionResult(depSolver), RteItem::CONFLICT); + EXPECT_EQ(c->GetConditionResult(depSolver), RteItem::CONFLICT); + EXPECT_FALSE(loadedCprjProject->Validate()); + activeTarget->SelectComponent(c3, 0, true); + loadedCprjProject->Apply(); + EXPECT_EQ(c2->GetConditionResult(depSolver), RteItem::IGNORED); + EXPECT_EQ(c->GetConditionResult(depSolver), RteItem::IGNORED); + EXPECT_TRUE(loadedCprjProject->Validate()); + // evaluate other condition possibilities RteRequireExpression deviceExpression(nullptr); deviceExpression.AddAttribute("Dname", activeTarget->GetDeviceName()); diff --git a/libs/rteutils/include/VersionCmp.h b/libs/rteutils/include/VersionCmp.h index dee8eff93..f77418247 100644 --- a/libs/rteutils/include/VersionCmp.h +++ b/libs/rteutils/include/VersionCmp.h @@ -50,15 +50,44 @@ class VersionCmp * @brief compare version with range versions in the form major.minor.release:major.minor.release * @param version version to be compared * @param versionRange range version to be compared + * @param bCompatible if upper rang is not given, limit upper range by next major version * @return 0 if version is between range version, > 0 if greater, < 0 if smaller */ - static int RangeCompare(const std::string& version, const std::string& versionRange); + static int RangeCompare(const std::string& version, const std::string& versionRange, bool bCompatible = false); + + /** + * @brief equivalent to RangeCompare(version, versionRange, true) + * @param version version to be compared + * @param versionRange range version to be compared + * @return 0 if version is between range version, > 0 if greater, < 0 if smaller + */ + static int CompatibleRangeCompare(const std::string& version, const std::string& versionRange) { + return RangeCompare(version, versionRange, true); + } + /** * @brief remove string after plus sign * @param v version string * @return string ahead of plus sign */ static std::string RemoveVersionMeta(const std::string& v); + + /** + * @brief calculates next major version + * @param v version string + * @param bMinus append minus sign as it can be used in range comparison + * @return string rounded to upper major version + */ + static std::string Ceil(const std::string& v, bool bMinus = true); + + /** + * @brief calculates previous major version + * @param v version string + * @return string rounded to upper major version + */ + static std::string Floor(const std::string& v); + + /** * @brief compare string to return mode constant * @param mode mode specification: "fixed" | "latest" | "excluded" @@ -81,48 +110,62 @@ class VersionCmp * @brief get matched version from available versions * @param filter version filter or version range e.g: @>=1.2.3, 1.2.3:1.4.0 * @param availableVersions list of available versions + * @param bCompatible if upper rang is not given, limit upper range by next major version in * @return matching version string, based on filter * also, in case of multiple matches, latest matching version * has precedence over other versions */ - static const std::string GetMatchingVersion( - const std::string& filter, - const std::set availableVersions); + static const std::string GetMatchingVersion(const std::string& filter, + const std::set& availableVersions, bool bCompatible = false); + + class ComparatorBase + { + public: + ComparatorBase(char delimiter = 0) : m_delimiter(delimiter) {} + int Compare(const std::string& v1, const std::string& v2, bool cs = true) const; + protected: + char m_delimiter; + }; // helper compare class for map containers (version compare operators) - class Less + class Less : public ComparatorBase { public: + Less(char delimiter = 0) : ComparatorBase(delimiter) {} bool operator()(const std::string& a, const std::string& b) const { - return VersionCmp::Compare(a, b) < 0; + return Compare(a, b) < 0; } }; - class LessNoCase + class LessNoCase : public ComparatorBase { public: + LessNoCase(char delimiter = 0) : ComparatorBase(delimiter) {} bool operator()(const std::string& a, const std::string& b) const { - return VersionCmp::Compare(a, b, false) < 0; + return Compare(a, b, false) < 0; } }; - class Greater + class Greater : public ComparatorBase { public: + Greater(char delimiter = 0) : ComparatorBase(delimiter) {} bool operator()(const std::string& a, const std::string& b) const { - return VersionCmp::Compare(a, b) > 0; + return Compare(a, b) > 0; } }; - class GreaterNoCase + class GreaterNoCase : public ComparatorBase { + public: + GreaterNoCase(char delimiter = 0) : ComparatorBase(delimiter) {} public: bool operator()(const std::string& a, const std::string& b) const { - return VersionCmp::Compare(a, b, false) > 0; + return Compare(a, b, false) > 0; } }; }; diff --git a/libs/rteutils/src/VersionCmp.cpp b/libs/rteutils/src/VersionCmp.cpp index 1ad4020a1..b42ca954f 100644 --- a/libs/rteutils/src/VersionCmp.cpp +++ b/libs/rteutils/src/VersionCmp.cpp @@ -161,7 +161,7 @@ int VersionCmp::Compare(const string& v1, const string& v2, bool cs) { return res; } -int VersionCmp::RangeCompare(const string& version, const string& versionRange) +int VersionCmp::RangeCompare(const string& version, const string& versionRange, bool bCompatible) { if (version == versionRange) { return 0; @@ -169,8 +169,9 @@ int VersionCmp::RangeCompare(const string& version, const string& versionRange) string verMin = RteUtils::GetPrefix(versionRange); string verMax = RteUtils::GetSuffix(versionRange); + int resMin = 0; if (!verMin.empty()) { - int resMin = Compare(version, verMin); + resMin = Compare(version, verMin); if (resMin < 0 || verMin == verMax) // lower than min or exact match is required? return resMin; } @@ -178,6 +179,8 @@ int VersionCmp::RangeCompare(const string& version, const string& versionRange) int resMax = Compare(version, verMax); if (resMax > 0) return resMax; + }else if(bCompatible && resMin > 2) { + return resMin; // semantic version: major version change -> incompatible } return 0; } @@ -189,6 +192,26 @@ string VersionCmp::RemoveVersionMeta(const string& v) { return v; } +std::string VersionCmp::Ceil(const std::string& v, bool bMinus) +{ + int major = RteUtils::StringToInt(RteUtils::GetPrefix(v, '.')); + if (major < 0) { + major = 0; + } + std::string majorVer = RteUtils::LongToString(major + 1, 10); + majorVer += ".0.0"; + if (bMinus) { + majorVer += "-"; + } + return majorVer; +} + +std::string VersionCmp::Floor(const std::string& v) +{ + return RteUtils::GetPrefix(v, '.') + ".0.0"; +} + + VersionCmp::MatchMode VersionCmp::MatchModeFromString(const std::string& mode) { if (mode == "fixed") @@ -245,13 +268,15 @@ std::string VersionCmp::MatchModeToString(VersionCmp::MatchMode mode) return s; } -const std::string VersionCmp::GetMatchingVersion(const std::string& filter, const std::set availableVersions) { +const std::string VersionCmp::GetMatchingVersion(const std::string& filter, + const std::set& availableVersions, bool bCompatible) +{ string matchedVersion; if (std::string::npos == filter.find('@')) { // version range vector matchedVersions; for (auto& version : availableVersions) { - if (0 == RangeCompare(version, filter)) { + if (0 == RangeCompare(version, filter, bCompatible)) { matchedVersions.push_back(version); } } @@ -291,4 +316,16 @@ const std::string VersionCmp::GetMatchingVersion(const std::string& filter, cons return matchedVersion; } +int VersionCmp::ComparatorBase::Compare(const std::string& v1, const std::string& v2, bool cs) const +{ + if (m_delimiter) { + int res = AlnumCmp::CompareLen(RteUtils::GetPrefix(v1, m_delimiter), RteUtils::GetPrefix(v2, m_delimiter), cs); + if (res == 0) { + res = VersionCmp::Compare(RteUtils::GetSuffix(v1, m_delimiter), RteUtils::GetSuffix(v2, m_delimiter), cs); + } + return res; + } + return VersionCmp::Compare(v1, v2, cs); +} + // End of VersionCmp.cpp diff --git a/libs/rteutils/test/src/VersionCmpTests.cpp b/libs/rteutils/test/src/VersionCmpTests.cpp index 667d16200..63f60f606 100644 --- a/libs/rteutils/test/src/VersionCmpTests.cpp +++ b/libs/rteutils/test/src/VersionCmpTests.cpp @@ -22,6 +22,18 @@ TEST(VersionCmpTest, VersionMatchMode) { EXPECT_EQ(VersionCmp::MatchModeToString(VersionCmp::MatchMode::EXCLUDED_VERSION), "excluded"); } +TEST(VersionCmpTest, CeilFloor) { + + EXPECT_EQ(VersionCmp::Ceil("2.3.0"), "3.0.0-"); + EXPECT_EQ(VersionCmp::Ceil("2.3.0", false), "3.0.0"); + EXPECT_EQ(VersionCmp::Ceil(""), "1.0.0-"); + EXPECT_EQ(VersionCmp::Ceil("a"), "1.0.0-"); + + EXPECT_EQ(VersionCmp::Floor("2.3.0"), "2.0.0"); + EXPECT_EQ(VersionCmp::Floor(""), ".0.0"); +} + + TEST(VersionCmpTest, VersionCompare) { EXPECT_EQ( -1, VersionCmp::Compare("6.5.0-a", "6.5.0", true)); EXPECT_EQ( 0, VersionCmp::Compare("6.5.0-a", "6.5.0-A", true)); @@ -54,6 +66,9 @@ TEST(VersionCmpTest, VersionRangeCompare) { EXPECT_EQ(0, VersionCmp::RangeCompare("3.2.0", ":3.8.0")); EXPECT_EQ(0, VersionCmp::RangeCompare("3.2.0", "3.2.0")); EXPECT_EQ(1, VersionCmp::RangeCompare("3.2.0", ":3.2.0-")); + EXPECT_EQ(0, VersionCmp::RangeCompare("3.0.0", "2.9.0")); + /* Major version is greater that allowed by semantic version */ + EXPECT_EQ(3, VersionCmp::CompatibleRangeCompare("3.0.0", "2.9.0")); EXPECT_EQ(0, VersionCmp::RangeCompare("1.0.0", "1.0.0:2.0.0")); EXPECT_EQ(0, VersionCmp::RangeCompare("2.0.0", "1.0.0:2.0.0")); @@ -65,7 +80,7 @@ TEST(VersionCmpTest, VersionRangeCompare) { EXPECT_EQ(0, VersionCmp::RangeCompare("2.0.0-a", "2.0.0-:2.0.0")); EXPECT_EQ(0, VersionCmp::RangeCompare("1.99.99", "1.0.0:2.0.0-")); - EXPECT_EQ( 3, VersionCmp::RangeCompare("9.0.0", "1.0.0:2.0.0")); + EXPECT_EQ( 3, VersionCmp::RangeCompare("9.0.0", "1.0.0:2.0.0")); EXPECT_EQ(-3, VersionCmp::RangeCompare("0.9.0", "1.0.0:2.0.0")); /* Greater than max version : Patch version out of range */ @@ -126,4 +141,15 @@ TEST(VersionCmpTest, GetMatchingVersion) { EXPECT_EQ(expectedOutput, VersionCmp::GetMatchingVersion(input.first, input.second)) << "error: for input " << input.first << " expected output is \"" << expectedOutput << "\"" << endl; } + EXPECT_EQ("4.0.1", VersionCmp::GetMatchingVersion("3.6.0", { "4.0.0", "4.0.1" })); + EXPECT_EQ("", VersionCmp::GetMatchingVersion("3.6.0", { "4.0.0", "4.0.1" }, true)); + +} +TEST(VersionCmpTest, ComparatorBase) { + VersionCmp::ComparatorBase comparator; + EXPECT_EQ(-2, comparator.Compare("1.1.0", "1.2.0")); + VersionCmp::ComparatorBase comparator1('@'); + EXPECT_EQ(-2, comparator1.Compare("Test@1.1.0", "Test@1.2.0")); + EXPECT_EQ(1, comparator1.Compare("Foo@1.1.0", "Bar@1.2.0")); } +// end of VersionCmpTest.cpp diff --git a/test/packs/ARM/RteTest/0.1.0/API1/NonExclusive.h b/test/packs/ARM/RteTest/0.1.0/API1/NonExclusive.h new file mode 100644 index 000000000..32ceee34a --- /dev/null +++ b/test/packs/ARM/RteTest/0.1.0/API1/NonExclusive.h @@ -0,0 +1 @@ +// NonExclusive.h version 1.1.0 \ No newline at end of file diff --git a/test/packs/ARM/RteTest/0.1.0/API2/NonExclusive.h b/test/packs/ARM/RteTest/0.1.0/API2/NonExclusive.h new file mode 100644 index 000000000..ee0c57b31 --- /dev/null +++ b/test/packs/ARM/RteTest/0.1.0/API2/NonExclusive.h @@ -0,0 +1 @@ +// NonExclusive.h version 2.0.0 \ No newline at end of file diff --git a/test/packs/ARM/RteTest/0.1.0/ARM.RteTest.pdsc b/test/packs/ARM/RteTest/0.1.0/ARM.RteTest.pdsc index 1269f9eba..8f673a0de 100644 --- a/test/packs/ARM/RteTest/0.1.0/ARM.RteTest.pdsc +++ b/test/packs/ARM/RteTest/0.1.0/ARM.RteTest.pdsc @@ -98,11 +98,23 @@ - RteTes Non-Exclusive API + RteTest Non-Exclusive API + + RteTest Non-Exclusive API 1.1.0 + + + + + + RteTest Non-Exclusive API 2.0.0 + + + + @@ -237,14 +249,21 @@ - + Tests Missing API version - + + Tests Missing API version min range + + + + + + Tests Non-Exclusive API component SN1 @@ -258,6 +277,14 @@ + + Tests Non-Exclusive API component SN3 + + + + + + Component that has accept dependencies @@ -308,7 +335,7 @@ - + Component that does not satisfy variant dependency diff --git a/tools/packchk/src/ValidateSemantic.cpp b/tools/packchk/src/ValidateSemantic.cpp index 135c9cbff..d79625f8d 100644 --- a/tools/packchk/src/ValidateSemantic.cpp +++ b/tools/packchk/src/ValidateSemantic.cpp @@ -131,13 +131,14 @@ const map conditionResultTxt = { { RteItem::R_ERROR , "error evaluating condition ( recursion detected, condition is missing)" }, { RteItem::FAILED , "HW or compiler not match" }, { RteItem::MISSING , "no component is installed" }, - { RteItem::MISSING_API , "no API of required version is installed" }, + { RteItem::MISSING_API , "no required API is installed" }, + { RteItem::MISSING_API_VERSION , "no API with the required or compatible version is installed" }, { RteItem::UNAVAILABLE , "component is installed, but filtered out" }, { RteItem::UNAVAILABLE_PACK , "component is installed, pack is not selected" }, { RteItem::INCOMPATIBLE , "incompatible component is selected" }, { RteItem::INCOMPATIBLE_VERSION , "incompatible version of component is selected" }, { RteItem::INCOMPATIBLE_VARIANT , "incompatible variant of component is selected" }, - { RteItem::CONFLICT , "more than one exclusive component selected" }, + { RteItem::CONFLICT , "several exclusive or incompatible components selected" }, { RteItem::INSTALLED , "matching components are installed, but not selectable because not in active bundle" }, { RteItem::SELECTABLE , "matching components are installed, but not selected" }, { RteItem::FULFILLED , "required component selected or no dependency exist" },