Utils: Cleanup searchInX functions

Also move some often used types into new file "utiltypes.h"

Change-Id: I3f152d1dc2f96ba0259ad6c098d9ac5ee03a59f1
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
Reviewed-by: hjk <hjk@qt.io>
This commit is contained in:
Marcus Tillmanns
2023-02-09 14:41:59 +01:00
parent c6dc54b343
commit a62c5cf89a
8 changed files with 156 additions and 94 deletions

View File

@@ -4,6 +4,7 @@
#include "environment.h" #include "environment.h"
#include "algorithm.h" #include "algorithm.h"
#include "filepath.h"
#include "qtcassert.h" #include "qtcassert.h"
#include <QDir> #include <QDir>
@@ -130,36 +131,44 @@ void Environment::setupEnglishOutput()
m_dict.set("LANGUAGE", "en_US:en"); m_dict.set("LANGUAGE", "en_US:en");
} }
static FilePath searchInDirectory(const QStringList &execs, using SearchResultCallback = std::function<IterationPolicy(const FilePath &)>;
const FilePath &directory,
QSet<FilePath> &alreadyChecked) static IterationPolicy searchInDirectory(const SearchResultCallback &resultCallback,
const FilePaths &execs,
const FilePath &directory,
QSet<FilePath> &alreadyCheckedDirectories,
const FilePathPredicate &filter = {})
{ {
const int checkedCount = alreadyChecked.count(); // Compare the initial size of the set with the size after insertion to check if the directory
alreadyChecked.insert(directory); // was already checked.
const int initialCount = alreadyCheckedDirectories.count();
alreadyCheckedDirectories.insert(directory);
const bool wasAlreadyChecked = alreadyCheckedDirectories.count() == initialCount;
if (directory.isEmpty() || alreadyChecked.count() == checkedCount) if (directory.isEmpty() || wasAlreadyChecked)
return FilePath(); return IterationPolicy::Continue;
for (const QString &exec : execs) { for (const FilePath &exec : execs) {
const FilePath filePath = directory.pathAppended(exec); const FilePath filePath = directory / exec.path();
if (filePath.isExecutableFile()) if (filePath.isExecutableFile() && (!filter || filter(filePath))) {
return filePath; if (resultCallback(filePath) == IterationPolicy::Stop)
return IterationPolicy::Stop;
}
} }
return FilePath(); return IterationPolicy::Continue;
} }
static QStringList appendExeExtensions(const Environment &env, const QString &executable) static FilePaths appendExeExtensions(const Environment &env, const FilePath &executable)
{ {
QStringList execs(executable); FilePaths execs{executable};
if (env.osType() == OsTypeWindows) { if (env.osType() == OsTypeWindows) {
const QFileInfo fi(executable);
// Check all the executable extensions on windows: // Check all the executable extensions on windows:
// PATHEXT is only used if the executable has no extension // PATHEXT is only used if the executable has no extension
if (fi.suffix().isEmpty()) { if (executable.suffix().isEmpty()) {
const QStringList extensions = env.expandedValueForKey("PATHEXT").split(';'); const QStringList extensions = env.expandedValueForKey("PATHEXT").split(';');
for (const QString &ext : extensions) for (const QString &ext : extensions)
execs << executable + ext.toLower(); execs << executable.stringAppended(ext.toLower());
} }
} }
return execs; return execs;
@@ -170,99 +179,101 @@ QString Environment::expandedValueForKey(const QString &key) const
return expandVariables(m_dict.value(key)); return expandVariables(m_dict.value(key));
} }
static FilePath searchInDirectoriesHelper(const Environment &env, static void searchInDirectoriesHelper(const SearchResultCallback &resultCallback,
const QString &executable, const Environment &env,
const FilePaths &dirs, const QString &executable,
const Environment::PathFilter &func, const FilePaths &dirs,
bool usePath) const FilePathPredicate &func,
bool usePath)
{ {
if (executable.isEmpty()) if (executable.isEmpty())
return FilePath(); return;
const QString exec = QDir::cleanPath(env.expandVariables(executable)); const FilePath exec = FilePath::fromUserInput(QDir::cleanPath(env.expandVariables(executable)));
const QFileInfo fi(exec); const FilePaths execs = appendExeExtensions(env, exec);
const QStringList execs = appendExeExtensions(env, exec); if (exec.isAbsolutePath()) {
for (const FilePath &path : execs) {
if (fi.isAbsolute()) { if (path.isExecutableFile() && (!func || func(path)))
for (const QString &path : execs) { if (resultCallback(path) == IterationPolicy::Stop)
QFileInfo pfi = QFileInfo(path); return;
if (pfi.isFile() && pfi.isExecutable())
return FilePath::fromString(path);
} }
return FilePath::fromString(exec); return;
} }
QSet<FilePath> alreadyChecked; QSet<FilePath> alreadyCheckedDirectories;
for (const FilePath &dir : dirs) { for (const FilePath &dir : dirs) {
FilePath tmp = searchInDirectory(execs, dir, alreadyChecked); if (searchInDirectory(resultCallback, execs, dir, alreadyCheckedDirectories, func)
if (!tmp.isEmpty() && (!func || func(tmp))) == IterationPolicy::Stop)
return tmp; return;
} }
if (usePath) { if (usePath) {
if (executable.contains('/')) QTC_ASSERT(!executable.contains('/'), return);
return FilePath();
for (const FilePath &p : env.path()) { for (const FilePath &p : env.path()) {
FilePath tmp = searchInDirectory(execs, p, alreadyChecked); if (searchInDirectory(resultCallback, execs, p, alreadyCheckedDirectories, func)
if (!tmp.isEmpty() && (!func || func(tmp))) == IterationPolicy::Stop)
return tmp; return;
} }
} }
return FilePath(); return;
} }
FilePath Environment::searchInDirectories(const QString &executable, FilePath Environment::searchInDirectories(const QString &executable,
const FilePaths &dirs, const FilePaths &dirs,
const PathFilter &func) const const FilePathPredicate &func) const
{ {
return searchInDirectoriesHelper(*this, executable, dirs, func, false); FilePath result;
searchInDirectoriesHelper(
[&result](const FilePath &path) {
result = path;
return IterationPolicy::Stop;
},
*this,
executable,
dirs,
func,
false);
return result;
} }
FilePath Environment::searchInPath(const QString &executable, FilePath Environment::searchInPath(const QString &executable,
const FilePaths &additionalDirs, const FilePaths &additionalDirs,
const PathFilter &func) const const FilePathPredicate &func) const
{ {
return searchInDirectoriesHelper(*this, executable, additionalDirs, func, true); FilePath result;
searchInDirectoriesHelper(
[&result](const FilePath &path) {
result = path;
return IterationPolicy::Stop;
},
*this,
executable,
additionalDirs,
func,
true);
return result;
} }
FilePaths Environment::findAllInPath(const QString &executable, FilePaths Environment::findAllInPath(const QString &executable,
const FilePaths &additionalDirs, const FilePaths &additionalDirs,
const Environment::PathFilter &func) const const FilePathPredicate &func) const
{ {
if (executable.isEmpty())
return {};
const QString exec = QDir::cleanPath(expandVariables(executable));
const QFileInfo fi(exec);
const QStringList execs = appendExeExtensions(*this, exec);
if (fi.isAbsolute()) {
for (const QString &path : execs) {
QFileInfo pfi = QFileInfo(path);
if (pfi.isFile() && pfi.isExecutable())
return {FilePath::fromString(path)};
}
return {FilePath::fromString(exec)};
}
QSet<FilePath> result; QSet<FilePath> result;
QSet<FilePath> alreadyChecked; searchInDirectoriesHelper(
for (const FilePath &dir : additionalDirs) { [&result](const FilePath &path) {
FilePath tmp = searchInDirectory(execs, dir, alreadyChecked); result.insert(path);
if (!tmp.isEmpty() && (!func || func(tmp))) return IterationPolicy::Continue;
result << tmp; },
} *this,
executable,
additionalDirs,
func,
true);
if (!executable.contains('/')) {
for (const FilePath &p : path()) {
FilePath tmp = searchInDirectory(execs, p, alreadyChecked);
if (!tmp.isEmpty() && (!func || func(tmp)))
result << tmp;
}
}
return result.values(); return result.values();
} }

View File

@@ -8,6 +8,7 @@
#include "environmentfwd.h" #include "environmentfwd.h"
#include "filepath.h" #include "filepath.h"
#include "namevaluedictionary.h" #include "namevaluedictionary.h"
#include "utiltypes.h"
#include <functional> #include <functional>
#include <optional> #include <optional>
@@ -56,16 +57,15 @@ public:
void setupEnglishOutput(); void setupEnglishOutput();
using PathFilter = std::function<bool(const FilePath &)>;
FilePath searchInPath(const QString &executable, FilePath searchInPath(const QString &executable,
const FilePaths &additionalDirs = FilePaths(), const FilePaths &additionalDirs = FilePaths(),
const PathFilter &func = PathFilter()) const; const FilePathPredicate &func = {}) const;
FilePath searchInDirectories(const QString &executable, FilePath searchInDirectories(const QString &executable,
const FilePaths &dirs, const FilePaths &dirs,
const PathFilter &func = {}) const; const FilePathPredicate &func = {}) const;
FilePaths findAllInPath(const QString &executable, FilePaths findAllInPath(const QString &executable,
const FilePaths &additionalDirs = {}, const FilePaths &additionalDirs = {},
const PathFilter &func = {}) const; const FilePathPredicate &func = {}) const;
FilePaths path() const; FilePaths path() const;
FilePaths pathListValue(const QString &varName) const; FilePaths pathListValue(const QString &varName) const;

View File

@@ -1451,7 +1451,7 @@ FilePath FilePath::withNewPath(const QString &newPath) const
assert(fullPath == FilePath::fromUrl("docker://123/usr/bin/make")) assert(fullPath == FilePath::fromUrl("docker://123/usr/bin/make"))
\endcode \endcode
*/ */
FilePath FilePath::searchInDirectories(const FilePaths &dirs, const PathFilter &filter) const FilePath FilePath::searchInDirectories(const FilePaths &dirs, const FilePathPredicate &filter) const
{ {
if (isAbsolutePath()) if (isAbsolutePath())
return *this; return *this;
@@ -1460,7 +1460,7 @@ FilePath FilePath::searchInDirectories(const FilePaths &dirs, const PathFilter &
FilePath FilePath::searchInPath(const FilePaths &additionalDirs, FilePath FilePath::searchInPath(const FilePaths &additionalDirs,
PathAmending amending, PathAmending amending,
const PathFilter &filter) const const FilePathPredicate &filter) const
{ {
if (isAbsolutePath()) if (isAbsolutePath())
return *this; return *this;

View File

@@ -8,6 +8,7 @@
#include "expected.h" #include "expected.h"
#include "filepathinfo.h" #include "filepathinfo.h"
#include "osspecificaspects.h" #include "osspecificaspects.h"
#include "utiltypes.h"
#include <QDir> #include <QDir>
#include <QDirIterator> #include <QDirIterator>
@@ -51,8 +52,6 @@ public:
using FilePaths = QList<class FilePath>; using FilePaths = QList<class FilePath>;
enum class IterationPolicy { Stop, Continue };
class QTCREATOR_UTILS_EXPORT FilePath class QTCREATOR_UTILS_EXPORT FilePath
{ {
public: public:
@@ -159,7 +158,7 @@ public:
[[nodiscard]] FilePath relativeChildPath(const FilePath &parent) const; [[nodiscard]] FilePath relativeChildPath(const FilePath &parent) const;
[[nodiscard]] FilePath relativePathFrom(const FilePath &anchor) const; [[nodiscard]] FilePath relativePathFrom(const FilePath &anchor) const;
[[nodiscard]] FilePath searchInDirectories(const FilePaths &dirs, [[nodiscard]] FilePath searchInDirectories(const FilePaths &dirs,
const PathFilter &filter = {}) const; const FilePathPredicate &filter = {}) const;
[[nodiscard]] Environment deviceEnvironment() const; [[nodiscard]] Environment deviceEnvironment() const;
[[nodiscard]] FilePath onDevice(const FilePath &deviceTemplate) const; [[nodiscard]] FilePath onDevice(const FilePath &deviceTemplate) const;
[[nodiscard]] FilePath withNewPath(const QString &newPath) const; [[nodiscard]] FilePath withNewPath(const QString &newPath) const;
@@ -182,7 +181,7 @@ public:
enum PathAmending { AppendToPath, PrependToPath }; enum PathAmending { AppendToPath, PrependToPath };
[[nodiscard]] FilePath searchInPath(const FilePaths &additionalDirs = {}, [[nodiscard]] FilePath searchInPath(const FilePaths &additionalDirs = {},
PathAmending = AppendToPath, PathAmending = AppendToPath,
const PathFilter &filter = {}) const; const FilePathPredicate &filter = {}) const;
enum MatchScope { ExactMatchOnly, WithExeSuffix, WithBatSuffix, enum MatchScope { ExactMatchOnly, WithExeSuffix, WithBatSuffix,
WithExeOrBatSuffix, WithAnySuffix }; WithExeOrBatSuffix, WithAnySuffix };

View File

@@ -9,6 +9,7 @@
#include "qtcassert.h" #include "qtcassert.h"
#include "stringutils.h" #include "stringutils.h"
#include "utilstr.h" #include "utilstr.h"
#include "utiltypes.h"
#include <QLoggingCategory> #include <QLoggingCategory>
#include <QMutex> #include <QMutex>
@@ -496,8 +497,7 @@ static bool isFileIncluded(const QList<QRegularExpression> &filterRegs,
return isIncluded && (exclusionRegs.isEmpty() || !matches(exclusionRegs, filePath)); return isIncluded && (exclusionRegs.isEmpty() || !matches(exclusionRegs, filePath));
} }
std::function<bool(const FilePath &)> filterFileFunction(const QStringList &filters, FilePathPredicate filterFileFunction(const QStringList &filters, const QStringList &exclusionFilters)
const QStringList &exclusionFilters)
{ {
const QList<QRegularExpression> filterRegs = filtersToRegExps(filters); const QList<QRegularExpression> filterRegs = filtersToRegExps(filters);
const QList<QRegularExpression> exclusionRegs = filtersToRegExps(exclusionFilters); const QList<QRegularExpression> exclusionRegs = filtersToRegExps(exclusionFilters);

View File

@@ -0,0 +1,14 @@
// Copyright (C) 2022 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
#pragma once
#include <functional>
namespace Utils {
class FilePath;
enum class IterationPolicy { Stop, Continue };
using FilePathPredicate = std::function<bool(const FilePath &)>;
} // namespace Utils

View File

@@ -283,7 +283,7 @@ void BuildSettingsWidget::cloneConfiguration()
bc->setDisplayName(name); bc->setDisplayName(name);
const FilePath buildDirectory = bc->buildDirectory(); const FilePath buildDirectory = bc->buildDirectory();
if (buildDirectory != m_target->project()->projectDirectory()) { if (buildDirectory != m_target->project()->projectDirectory()) {
const std::function<bool(const FilePath &)> isBuildDirOk = [this](const FilePath &candidate) { const FilePathPredicate isBuildDirOk = [this](const FilePath &candidate) {
if (candidate.exists()) if (candidate.exists())
return false; return false;
return !anyOf(m_target->buildConfigurations(), [&candidate](const BuildConfiguration *bc) { return !anyOf(m_target->buildConfigurations(), [&candidate](const BuildConfiguration *bc) {

View File

@@ -107,15 +107,21 @@ private slots:
void tmp(); void tmp();
void tmp_data(); void tmp_data();
void searchInWithFilter();
private: private:
QTemporaryDir tempDir; QTemporaryDir tempDir;
QString rootPath; QString rootPath;
QString exeExt;
}; };
static void touch(const QDir &dir, const QString &filename, bool fill) static void touch(const QDir &dir, const QString &filename, bool fill, bool executable = false)
{ {
QFile file(dir.absoluteFilePath(filename)); QFile file(dir.absoluteFilePath(filename));
file.open(QIODevice::WriteOnly); file.open(QIODevice::WriteOnly);
if (executable)
file.setPermissions(file.permissions() | QFileDevice::ExeUser);
if (fill) { if (fill) {
QRandomGenerator *random = QRandomGenerator::global(); QRandomGenerator *random = QRandomGenerator::global();
for (int i = 0; i < 10; ++i) for (int i = 0; i < 10; ++i)
@@ -126,7 +132,7 @@ static void touch(const QDir &dir, const QString &filename, bool fill)
void tst_filepath::initTestCase() void tst_filepath::initTestCase()
{ {
// initialize test for tst_fileutiles::relativePath*() // initialize test for tst_filepath::relativePath*()
QVERIFY(tempDir.isValid()); QVERIFY(tempDir.isValid());
rootPath = tempDir.path(); rootPath = tempDir.path();
QDir dir(rootPath); QDir dir(rootPath);
@@ -141,6 +147,38 @@ void tst_filepath::initTestCase()
// initialize test for tst_filepath::asyncLocalCopy() // initialize test for tst_filepath::asyncLocalCopy()
touch(dir, "x/y/fileToCopy.txt", true); touch(dir, "x/y/fileToCopy.txt", true);
// initialize test for tst_filepath::searchIn()
#ifdef Q_OS_WIN
exeExt = ".exe";
#endif
dir.mkpath("s/1");
dir.mkpath("s/2");
touch(dir, "s/1/testexe" + exeExt, false, true);
touch(dir, "s/2/testexe" + exeExt, false, true);
}
void tst_filepath::searchInWithFilter()
{
const FilePaths dirs = {FilePath::fromUserInput(rootPath) / "s" / "1",
FilePath::fromUserInput(rootPath) / "s" / "2"};
FilePath exe = FilePath::fromUserInput("testexe" + exeExt)
.searchInDirectories(dirs, [](const FilePath &path) {
return path.path().contains("/2/");
});
QVERIFY(!exe.path().endsWith("/1/testexe" + exeExt)
&& exe.path().endsWith("/2/testexe" + exeExt));
FilePath exe2 = FilePath::fromUserInput("testexe" + exeExt)
.searchInDirectories(dirs, [](const FilePath &path) {
return path.path().contains("/1/");
});
QVERIFY(!exe2.path().endsWith("/2/testexe" + exeExt)
&& exe2.path().endsWith("/1/testexe" + exeExt));
} }
void tst_filepath::isEmpty_data() void tst_filepath::isEmpty_data()