From e8d488949f33f559fc97a18a85d7c529b1c48073 Mon Sep 17 00:00:00 2001 From: Evgueni Driouk Date: Tue, 9 Dec 2025 13:23:47 +0100 Subject: [PATCH] FsUtils::Equivalent() method to compare files (#1370) --- libs/rtefsutils/include/RteFsUtils.h | 9 ++++ libs/rtefsutils/src/RteFsUtils.cpp | 19 +++++++ libs/rtefsutils/test/src/RteFsUtilsTest.cpp | 60 ++++++++++++++++++++- libs/rtemodel/src/RteProject.cpp | 3 +- tools/projmgr/src/ProjMgrRpcServer.cpp | 6 +-- tools/projmgr/src/ProjMgrWorker.cpp | 6 +-- 6 files changed, 92 insertions(+), 11 deletions(-) diff --git a/libs/rtefsutils/include/RteFsUtils.h b/libs/rtefsutils/include/RteFsUtils.h index 3d31a01d7..ff27c643d 100644 --- a/libs/rtefsutils/include/RteFsUtils.h +++ b/libs/rtefsutils/include/RteFsUtils.h @@ -223,6 +223,15 @@ class RteFsUtils */ static std::string ParentPath(const std::string& path); + /** + * @brief checks if two paths are equivalent + * @param p1 first path to be compared + * @param p2 second path to be compared + * @return true if p1 == p2 or both refer to the same file or both are empty + */ + static bool Equivalent(const std::string& p1, const std::string& p2); + + /** * @brief get lexically normalized path * @param path path to be processed diff --git a/libs/rtefsutils/src/RteFsUtils.cpp b/libs/rtefsutils/src/RteFsUtils.cpp index a072de15c..1ac72c140 100644 --- a/libs/rtefsutils/src/RteFsUtils.cpp +++ b/libs/rtefsutils/src/RteFsUtils.cpp @@ -558,6 +558,25 @@ string RteFsUtils::ParentPath(const string& path) { return fs::path(path).parent_path().generic_string(); } +bool RteFsUtils::Equivalent(const std::string& p1, const std::string& p2) { + if(p1 == p2) { + return true; + } + + if(p1.empty() || p2.empty()) { + return false; + } + error_code _ec; + if(fs::equivalent(p1, p2, _ec)) { + return true; + } + if(MakePathCanonical(p1) == MakePathCanonical(p2)) { + return true; + } + return false; +} + + string RteFsUtils::LexicallyNormal(const string& path) { string lexicallyNormal = fs::path(path).lexically_normal().generic_string(); if ((lexicallyNormal.length() > 1) && (lexicallyNormal.back() == '/')) { diff --git a/libs/rtefsutils/test/src/RteFsUtilsTest.cpp b/libs/rtefsutils/test/src/RteFsUtilsTest.cpp index dba4d2a34..1087c8246 100644 --- a/libs/rtefsutils/test/src/RteFsUtilsTest.cpp +++ b/libs/rtefsutils/test/src/RteFsUtilsTest.cpp @@ -47,11 +47,12 @@ const string filenameBackup0 = RteUtils::SlashesToOsSlashes(filenameRegular + ". const string filenameBackup1 = RteUtils::SlashesToOsSlashes(filenameRegular + ".0001"); const string pathInvalid = dirnameSubdir + "/Invalid"; -// For Canonical Tests +// For Canonical and Equivalent tests const string filenameBackslashForCanonical = dirnameSubdirBackslash + "\\file.txt"; const string dirnameSubdirBackslashForCanonical = dirnameBase + "\\dir\\subdir"; const string dirnameMixedWithTrailingForCanonical = dirnameBase + "/dir\\subdir/"; const string dirnameBackslashWithTrailingForCanonical = dirnameBase + "\\dir\\subdir\\"; +const string filenameMixedForCanonical = dirnameMixedWithTrailingForCanonical + "\\file.txt"; static set sortedFileSet = { "foo.h", @@ -859,7 +860,7 @@ TEST_F(RteFsUtilsTest, MakePathCanonical) { EXPECT_EQ(ret, filenameCanonical); // Test filename with mixed separators - ret = RteFsUtils::MakePathCanonical(filenameBackslashForCanonical); + ret = RteFsUtils::MakePathCanonical(filenameMixedForCanonical); EXPECT_EQ(ret, filenameCanonical); // Test dirname with regular separators and trailing @@ -885,6 +886,61 @@ TEST_F(RteFsUtilsTest, MakePathCanonical) { RteFsUtils::RemoveDir(dirnameSubdir); } +TEST_F(RteFsUtilsTest, Equivalent) { + + + EXPECT_TRUE(RteFsUtils::Equivalent("","")); + EXPECT_TRUE(RteFsUtils::Equivalent("foo","foo")); + + EXPECT_FALSE(RteFsUtils::Equivalent("","foo")); + EXPECT_FALSE(RteFsUtils::Equivalent("foo","")); + + + // now test different combinations: files must exist! + string ret; + error_code ec; + const string filenameCanonical = fs::current_path(ec).append(filenameRegular).generic_string(); + const string dirnameCanonical = fs::current_path(ec).append(dirnameSubdir).generic_string(); + + // create file and with parent directories for reliability of the tests + RteFsUtils::CreateTextFile(filenameRegular, "foo"); + + // check filename with regular separators + EXPECT_TRUE(RteFsUtils::Equivalent(filenameRegular, filenameCanonical)); + + // Even longer path are equal + string nonExistingFileRel = dirnameSubdir + "/non/existing/path/../file.txt"; + string nonExistingFileAbs = dirnameCanonical + "/non/existing/file.txt"; + EXPECT_TRUE(RteFsUtils::Equivalent(nonExistingFileRel, nonExistingFileAbs)); + + // Test filenames with backslashes separators + EXPECT_TRUE(RteFsUtils::Equivalent(filenameBackslashForCanonical, filenameCanonical)); + + // Test filename with mixed separators + EXPECT_TRUE(RteFsUtils::Equivalent(filenameMixedForCanonical, filenameCanonical)); + + //Test filename with mixed separators against backslashes + EXPECT_TRUE(RteFsUtils::Equivalent(filenameMixedForCanonical, filenameBackslashForCanonical)); + + // Test dirname with regular separators and trailing + EXPECT_TRUE(RteFsUtils::Equivalent(dirnameSubdirWithTrailing, dirnameCanonical)); + + // Test dirname with backslashes separators and trailing + EXPECT_TRUE(RteFsUtils::Equivalent(dirnameBackslashWithTrailingForCanonical, dirnameCanonical)); + + // Test dirname with mixed separators and trailing + EXPECT_TRUE(RteFsUtils::Equivalent(dirnameMixedWithTrailingForCanonical, dirnameCanonical)); + + // Test path with dot inside + EXPECT_TRUE(RteFsUtils::Equivalent(dirnameDotSubdir, dirnameCanonical)); + + // Test path with two dots inside + EXPECT_TRUE(RteFsUtils::Equivalent(dirnameDotDotSubdir, dirnameCanonical)); + + RteFsUtils::RemoveDir(dirnameSubdir); +} + + TEST_F(RteFsUtilsTest, GetCurrentFolder) { error_code ec; string currDir; diff --git a/libs/rtemodel/src/RteProject.cpp b/libs/rtemodel/src/RteProject.cpp index 03d51346c..a53570298 100644 --- a/libs/rtemodel/src/RteProject.cpp +++ b/libs/rtemodel/src/RteProject.cpp @@ -710,8 +710,7 @@ void RteProject::UpdateConfigFileBackups(RteFileInstance* fi, RteItem* f) RteFsUtils::GrepFileNames(backupFileNames, dir, baseName + "@*"); RteFsUtils::GrepFileNames(backupFileNames, dir, updateName + "@*"); for (string fileName : backupFileNames) { - error_code ec; - if (!fs::equivalent(fileName, baseFile, ec) && !fs::equivalent(fileName, updateFile, ec)) { + if (!RteFsUtils::Equivalent(fileName, baseFile) && !RteFsUtils::Equivalent(fileName, updateFile)) { RteFsUtils::DeleteFileAutoRetry(fileName); } } diff --git a/tools/projmgr/src/ProjMgrRpcServer.cpp b/tools/projmgr/src/ProjMgrRpcServer.cpp index dd5d60b9f..4dd2dcdf8 100644 --- a/tools/projmgr/src/ProjMgrRpcServer.cpp +++ b/tools/projmgr/src/ProjMgrRpcServer.cpp @@ -407,7 +407,7 @@ RpcArgs::PackReference& RpcHandler::EnsurePackReferenceForPack(const string& con auto& packRefs = GetPackReferences(context); for(auto& ref : packRefs) { if(ref.resolvedPack.has_value() && ref.resolvedPack == packId && - (origin.empty() || ref.origin == origin)) { + (origin.empty() || RteFsUtils::Equivalent(ref.origin, origin))) { ref.selected = true; // ensure selected return ref; } @@ -428,8 +428,8 @@ RpcArgs::PackReference& RpcHandler::EnsurePackReference(const string& context, c auto& packRefs = GetPackReferences(context); for(auto& ref : packRefs) { if(ref.pack == packRef.pack && - (!packRef.path.has_value() || ref.path == packRef.path) && - (packRef.origin.empty() || ref.origin == packRef.origin)) { + (packRef.origin.empty() || RteFsUtils::Equivalent(ref.origin, packRef.origin)) && + (RteFsUtils::Equivalent(ref.path.value_or(""), packRef.path.value_or("")))) { return ref; } } diff --git a/tools/projmgr/src/ProjMgrWorker.cpp b/tools/projmgr/src/ProjMgrWorker.cpp index 4c9882eaa..d196da91e 100644 --- a/tools/projmgr/src/ProjMgrWorker.cpp +++ b/tools/projmgr/src/ProjMgrWorker.cpp @@ -2690,8 +2690,7 @@ bool ProjMgrWorker::IsPreIncludeByTarget(const RteTarget* activeTarget, const st const auto& preIncludeFiles = activeTarget->GetPreIncludeFiles(); for (const auto& [_, fileSet] : preIncludeFiles) { for (auto file : fileSet) { - error_code ec; - if (fs::equivalent(file, preInclude, ec)) { + if (RteFsUtils::Equivalent(file, preInclude)) { return true; } } @@ -3621,9 +3620,8 @@ bool ProjMgrWorker::ProcessSequenceRelative(ContextItem& context, string& item, } } if (!pathReplace && !ref.empty()) { - error_code ec; // adjust relative path according to the given reference - if (!fs::equivalent(outDir, ref, ec)) { + if (!RteFsUtils::Equivalent(outDir, ref)) { const string absPath = RteFsUtils::MakePathCanonical(fs::path(item).is_relative() ? ref + "/" + item : item); const string relPath = RteFsUtils::RelativePath(absPath, outDir, withHeadingDot); if (!relPath.empty()) {