From 88eaa23c2e53c379b4ca750072da2bb6a2633110 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Thu, 20 Aug 2026 20:47:13 +0200 Subject: [PATCH 1/2] Fix #14983 (GUI: exclude file with relative path) --- gui/projectfile.cpp | 13 +++++++++++- gui/projectfile.h | 8 +++++++ gui/projectfiledialog.cpp | 9 +++----- gui/test/projectfile/testprojectfile.cpp | 27 ++++++++++++++++++++++++ gui/test/projectfile/testprojectfile.h | 4 ++++ 5 files changed, 54 insertions(+), 7 deletions(-) diff --git a/gui/projectfile.cpp b/gui/projectfile.cpp index 9144f5a6117..3b997343189 100644 --- a/gui/projectfile.cpp +++ b/gui/projectfile.cpp @@ -751,7 +751,9 @@ void ProjectFile::setCheckPaths(const QStringList &paths) void ProjectFile::setExcludedPaths(const QStringList &paths) { - mExcludedPaths = paths; + mExcludedPaths.clear(); + for (const QString &path : paths) + mExcludedPaths << (QFileInfo(path).isAbsolute() ? getRelativePath(path) : path); } void ProjectFile::setLibraries(const QStringList &libraries) @@ -1193,6 +1195,15 @@ QStringList ProjectFile::getSearchPaths(const QString& projectPath, const QStrin return ret; } +QString ProjectFile::getRelativePath(const QString &absolutePath) const +{ + const QDir dir(QFileInfo(mFilename).absolutePath()); + const QString relativePath(dir.relativeFilePath(absolutePath)); + if (relativePath.startsWith("../..")) + return absolutePath; + return relativePath; +} + QStringList ProjectFile::getSearchPaths(const QString& dir) const { const QFileInfo inf(mFilename); const QString applicationFilePath = QCoreApplication::applicationFilePath(); diff --git a/gui/projectfile.h b/gui/projectfile.h index 5a5e0ccb89b..e0b85582c99 100644 --- a/gui/projectfile.h +++ b/gui/projectfile.h @@ -447,6 +447,14 @@ class ProjectFile : public QObject { static QStringList getSearchPaths(const QString& projectPath, const QString& appPath, const QString& datadir, const QString& dir); + /** + * @brief Convert an absolute path to a path relative to this project's directory. + * If the relative path would need to walk up more than 2 parent folders + * (i.e. "../../...") the absolute path is returned unchanged instead. + * @param absolutePath Absolute path to convert. + */ + QString getRelativePath(const QString &absolutePath) const; + /** Set user includes in settings if non-empty */ void setSettingsUserIncludes(Settings &settings) const; diff --git a/gui/projectfiledialog.cpp b/gui/projectfiledialog.cpp index 72e3d651471..a75ba528c0b 100644 --- a/gui/projectfiledialog.cpp +++ b/gui/projectfiledialog.cpp @@ -571,10 +571,7 @@ QString ProjectFileDialog::getExistingDirectory(const QString &caption, bool tra // Check if the path is relative to project file's path and if so // make it a relative path instead of absolute path. - const QDir dir(projectPath); - const QString relpath(dir.relativeFilePath(selectedDir)); - if (!relpath.startsWith("../..")) - selectedDir = relpath; + selectedDir = mProjectFile->getRelativePath(selectedDir); // Trailing slash.. if (trailingSlash && !selectedDir.endsWith('/')) @@ -631,7 +628,7 @@ void ProjectFileDialog::browseImportProject() dir.canonicalPath(), toFilterString(filters)); if (!fileName.isEmpty()) { - mUI->mEditImportProject->setText(dir.relativeFilePath(fileName)); + mUI->mEditImportProject->setText(mProjectFile->getRelativePath(fileName)); updatePathsAndDefines(); setProjectConfigurations(getProjectConfigs(fileName)); for (int row = 0; row < mUI->mListVsConfigs->count(); ++row) { @@ -652,7 +649,7 @@ void ProjectFileDialog::browseUserInclude() dir.canonicalPath(), toFilterString(filters)); if (!fileName.isEmpty()) { - mUI->mEditUserInclude->setText(dir.relativeFilePath(fileName)); + mUI->mEditUserInclude->setText(mProjectFile->getRelativePath(fileName)); } } diff --git a/gui/test/projectfile/testprojectfile.cpp b/gui/test/projectfile/testprojectfile.cpp index efc1dce6c4f..ab7b754a8f9 100644 --- a/gui/test/projectfile/testprojectfile.cpp +++ b/gui/test/projectfile/testprojectfile.cpp @@ -214,5 +214,32 @@ void TestProjectFile::emptyUserInclude() const QCOMPARE(settings.userIncludes.size(), 0); } +// Absolute path is made relative when it does not require walking up more than 2 parent folders +void TestProjectFile::setExcludedPathsRelative() const +{ + ProjectFile projectFile; + projectFile.setFilename("/some/path/123.cppcheck"); + projectFile.setExcludedPaths(QStringList() << "/some/externals/foo.cpp"); + QCOMPARE(projectFile.getExcludedPaths()[0], QString("../externals/foo.cpp")); +} + +// Absolute path is kept as-is when making it relative would require walking up more than 2 parent folders +void TestProjectFile::setExcludedPathsTooFarUp() const +{ + ProjectFile projectFile; + projectFile.setFilename("/some/path/123.cppcheck"); + projectFile.setExcludedPaths(QStringList() << "/other/deep/foo.cpp"); + QCOMPARE(projectFile.getExcludedPaths()[0], QString("/other/deep/foo.cpp")); +} + +// Paths that are already relative are kept unchanged +void TestProjectFile::setExcludedPathsAlreadyRelative() const +{ + ProjectFile projectFile; + projectFile.setFilename("/some/path/123.cppcheck"); + projectFile.setExcludedPaths(QStringList() << "gui/temp/"); + QCOMPARE(projectFile.getExcludedPaths()[0], QString("gui/temp/")); +} + QTEST_MAIN(TestProjectFile) diff --git a/gui/test/projectfile/testprojectfile.h b/gui/test/projectfile/testprojectfile.h index ceda2ff6b96..d3caa54edc5 100644 --- a/gui/test/projectfile/testprojectfile.h +++ b/gui/test/projectfile/testprojectfile.h @@ -38,4 +38,8 @@ private slots: void getCheckingSuppressionsStar() const; void emptyUserInclude() const; + + void setExcludedPathsRelative() const; + void setExcludedPathsTooFarUp() const; + void setExcludedPathsAlreadyRelative() const; }; From cada3a2e248785612e0d386993b77989cbb4c9f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Fri, 21 Aug 2026 14:20:32 +0200 Subject: [PATCH 2/2] fix --- gui/manualtest/projectfiledialog.md | 14 ++++++++++++++ gui/projectfile.cpp | 4 +--- gui/projectfiledialog.cpp | 5 ++++- gui/test/projectfile/testprojectfile.cpp | 17 +++++++---------- gui/test/projectfile/testprojectfile.h | 6 +++--- 5 files changed, 29 insertions(+), 17 deletions(-) diff --git a/gui/manualtest/projectfiledialog.md b/gui/manualtest/projectfiledialog.md index f11b030f246..ce7d39b1df0 100644 --- a/gui/manualtest/projectfiledialog.md +++ b/gui/manualtest/projectfiledialog.md @@ -4,6 +4,20 @@ Some manual testing in the project file dialog interface +## Test: Relative paths + +Ticket: #14983 + +1. Configure files/paths in project folder: + * import a projectfile + * add include paths in project folder + * exclude file/folder + +2. Save project + +EXPECTED: Relative paths should be used in the XML + + ## Test: Platform file pic8.xml Ticket: #14489 diff --git a/gui/projectfile.cpp b/gui/projectfile.cpp index 3b997343189..6be9efb0c4d 100644 --- a/gui/projectfile.cpp +++ b/gui/projectfile.cpp @@ -751,9 +751,7 @@ void ProjectFile::setCheckPaths(const QStringList &paths) void ProjectFile::setExcludedPaths(const QStringList &paths) { - mExcludedPaths.clear(); - for (const QString &path : paths) - mExcludedPaths << (QFileInfo(path).isAbsolute() ? getRelativePath(path) : path); + mExcludedPaths = paths; } void ProjectFile::setLibraries(const QStringList &libraries) diff --git a/gui/projectfiledialog.cpp b/gui/projectfiledialog.cpp index a75ba528c0b..dac39b162d1 100644 --- a/gui/projectfiledialog.cpp +++ b/gui/projectfiledialog.cpp @@ -888,7 +888,10 @@ void ProjectFileDialog::addExcludeFile() QMap filters; filters[tr("Source files")] = "*.c *.cpp"; filters[tr("All files")] = "*.*"; - addExcludePath(QFileDialog::getOpenFileName(this, tr("Exclude file"), dir.canonicalPath(), toFilterString(filters))); + QString fileName = QFileDialog::getOpenFileName(this, tr("Exclude file"), dir.canonicalPath(), toFilterString(filters)); + if (!fileName.isEmpty()) + fileName = mProjectFile->getRelativePath(fileName); + addExcludePath(fileName); } void ProjectFileDialog::editExcludePath() diff --git a/gui/test/projectfile/testprojectfile.cpp b/gui/test/projectfile/testprojectfile.cpp index ab7b754a8f9..8226ebb49f7 100644 --- a/gui/test/projectfile/testprojectfile.cpp +++ b/gui/test/projectfile/testprojectfile.cpp @@ -215,30 +215,27 @@ void TestProjectFile::emptyUserInclude() const } // Absolute path is made relative when it does not require walking up more than 2 parent folders -void TestProjectFile::setExcludedPathsRelative() const +void TestProjectFile::getRelativePathRelative() const { ProjectFile projectFile; projectFile.setFilename("/some/path/123.cppcheck"); - projectFile.setExcludedPaths(QStringList() << "/some/externals/foo.cpp"); - QCOMPARE(projectFile.getExcludedPaths()[0], QString("../externals/foo.cpp")); + QCOMPARE(projectFile.getRelativePath("/some/externals/foo.cpp"), QString("../externals/foo.cpp")); } // Absolute path is kept as-is when making it relative would require walking up more than 2 parent folders -void TestProjectFile::setExcludedPathsTooFarUp() const +void TestProjectFile::getRelativePathTooFarUp() const { ProjectFile projectFile; projectFile.setFilename("/some/path/123.cppcheck"); - projectFile.setExcludedPaths(QStringList() << "/other/deep/foo.cpp"); - QCOMPARE(projectFile.getExcludedPaths()[0], QString("/other/deep/foo.cpp")); + QCOMPARE(projectFile.getRelativePath("/other/deep/foo.cpp"), QString("/other/deep/foo.cpp")); } -// Paths that are already relative are kept unchanged -void TestProjectFile::setExcludedPathsAlreadyRelative() const +// Absolute path in a subfolder of the project path is made relative without walking up at all +void TestProjectFile::getRelativePathSubfolder() const { ProjectFile projectFile; projectFile.setFilename("/some/path/123.cppcheck"); - projectFile.setExcludedPaths(QStringList() << "gui/temp/"); - QCOMPARE(projectFile.getExcludedPaths()[0], QString("gui/temp/")); + QCOMPARE(projectFile.getRelativePath("/some/path/src/file1.c"), QString("src/file1.c")); } QTEST_MAIN(TestProjectFile) diff --git a/gui/test/projectfile/testprojectfile.h b/gui/test/projectfile/testprojectfile.h index d3caa54edc5..8be16ceba44 100644 --- a/gui/test/projectfile/testprojectfile.h +++ b/gui/test/projectfile/testprojectfile.h @@ -39,7 +39,7 @@ private slots: void emptyUserInclude() const; - void setExcludedPathsRelative() const; - void setExcludedPathsTooFarUp() const; - void setExcludedPathsAlreadyRelative() const; + void getRelativePathRelative() const; + void getRelativePathTooFarUp() const; + void getRelativePathSubfolder() const; };