Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qqmltoolingsettings.cpp
Go to the documentation of this file.
1// Copyright (C) 2021 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
3// Qt-Security score:significant
4
6
7#include <QtCore/qdebug.h>
8#include <QtCore/qdir.h>
9#include <QtCore/qfileinfo.h>
10#include <QtCore/qset.h>
11#include <QtCore/qtextstream.h>
12#if QT_CONFIG(settings)
13#include <QtCore/qsettings.h>
14#endif
15#include <QtCore/qstandardpaths.h>
16
17using namespace Qt::StringLiterals;
18
19QQmlToolingSettings::SearchOptions::SearchOptions() = default;
20QQmlToolingSettings::SearchOptions::SearchOptions(
21 const QString &settingFileName, bool reportFoundSettingsFiles, bool isQmllintSilent)
22 : settingsFileName(settingFileName), reportFoundSettingsFiles(reportFoundSettingsFiles),
23 isQmllintSilent(isQmllintSilent)
24{
25
26}
27
28void QQmlToolingSettings::addOption(const QString &name, const QVariant &defaultValue)
29{
30 if (defaultValue.isValid()) {
31 m_values[name] = defaultValue;
32 }
33}
34
35QQmlToolingSettings::QQmlToolingSettings(const QString &toolName,
36 const QStringList &recognizedIniSections,
37 const QString &localSettingsFile,
38 const QString &globalSettingsFile)
39 : m_searcher(localSettingsFile.isEmpty() ? u".%1.ini"_s.arg(toolName) : localSettingsFile,
40 globalSettingsFile.isEmpty() ? u"%1.ini"_s.arg(toolName) : globalSettingsFile),
41 m_recognizedIniSections(recognizedIniSections)
42{
43}
44
45QQmlToolingSettings::SearchResult QQmlToolingSettings::read(const QString &settingsFilePath,
46 SearchOptions options)
47{
48#if QT_CONFIG(settings)
49 Q_ASSERT(QFileInfo::exists(settingsFilePath));
50
51 if (m_currentSettingsPath == settingsFilePath)
52 return { SearchResult::ResultType::Found, settingsFilePath };
53
54 QSettings settings(settingsFilePath, QSettings::IniFormat);
55
56 if (!options.isQmllintSilent && !m_recognizedIniSections.isEmpty()) {
57 const QStringList sections = settings.childGroups() << QLatin1String("General");
58 for (const QString &section : sections) {
59 if (!m_recognizedIniSections.contains(section)) {
60 qWarning().noquote()
61 << "Unrecognized section \"%1\" in %2\n"_L1.arg(section).arg(settingsFilePath)
62 + "Recognized sections are: ["_L1
63 + m_recognizedIniSections.join(", "_L1) + u']';
64 }
65 }
66 }
67
68 for (const QString &key : settings.allKeys())
69 m_values[key] = settings.value(key).toString();
70
71 m_currentSettingsPath = settingsFilePath;
72 return { SearchResult::ResultType::Found, settingsFilePath };
73#else
74 Q_UNUSED(settingsFilePath);
75 return SearchResult();
76#endif
77}
78
79bool QQmlToolingSettings::writeDefaults() const
80{
81#if QT_CONFIG(settings)
82 const QString path = QFileInfo(m_searcher.localSettingsFile()).absoluteFilePath();
83
84 QSettings settings(path, QSettings::IniFormat);
85 for (auto it = m_values.constBegin(); it != m_values.constEnd(); ++it) {
86 settings.setValue(it.key(), it.value().isNull() ? QString() : it.value());
87 }
88
89 settings.sync();
90
91 if (settings.status() != QSettings::NoError) {
92 qWarning() << "Failed to write default settings to" << path
93 << "Error:" << settings.status();
94 return false;
95 }
96
97 qInfo() << "Wrote default settings to" << path;
98 return true;
99#else
100 return false;
101#endif
102}
103
104QQmlToolingSettings::SearchResult
105QQmlToolingSettings::Searcher::searchCurrentDirInCache(const QString &dirPath)
106{
107 const auto it = m_seenDirectories.constFind(dirPath);
108 return it != m_seenDirectories.constEnd()
109 ? SearchResult{ SearchResult::ResultType::Found, *it }
110 : SearchResult{ SearchResult::ResultType::NotFound, {} };
111}
112
113static QString findIniFile(const QString &local, const QString &global)
114{
115 // If we reach here, we didn't find the settings file in the current directory or any parent
116 // directories. Now we will try to locate the settings file in the standard locations. First try
117 // to locate settings file with the standard name.
118 const QString iniFile = QStandardPaths::locate(QStandardPaths::GenericConfigLocation, local);
119 if (!iniFile.isEmpty())
120 return iniFile;
121
122 // If not found, try alternate name format
123 return QStandardPaths::locate(QStandardPaths::GenericConfigLocation, global);
124}
125
126QQmlToolingSettings::SearchResult
127QQmlToolingSettings::Searcher::searchDefaultLocation(const QSet<QString> *visitedDirs)
128{
129 QString iniFile = findIniFile(m_localSettingsFile, m_globalSettingsFile);
130
131 // Update the seen directories cache unconditionally with the current result
132 for (const QString &dir : *visitedDirs)
133 m_seenDirectories[dir] = iniFile;
134
135 const SearchResult::ResultType found = iniFile.isEmpty()
136 ? SearchResult::ResultType::NotFound
137 : SearchResult::ResultType::Found;
138 return SearchResult { found, std::move(iniFile) };
139}
140
141QQmlToolingSettings::SearchResult
142QQmlToolingSettings::Searcher::searchDirectoryHierarchy(
143 QSet<QString> *visitedDirs, const QString &path)
144{
145 const QFileInfo fileInfo(path);
146 QDir dir(fileInfo.isDir() ? path : fileInfo.dir());
147
148 while (dir.exists() && dir.isReadable()) {
149 const QString dirPath = dir.absolutePath();
150
151 // Check if we have already seen this directory
152 // If we have, we can use the cached INI file path
153 // to avoid unnecessary file system operations
154 // This is useful for large directory trees where the settings file might be in a parent
155 // directory
156 if (const SearchResult result = searchCurrentDirInCache(dirPath); result.isValid())
157 return result;
158
159 visitedDirs->insert(dirPath);
160
161 // Check if the settings file exists in the current directory
162 // If it does, read it and update the seen directories cache
163 if (QString ini = dir.absoluteFilePath(m_localSettingsFile); QFileInfo::exists(ini)) {
164 for (const QString &visitedDir : std::as_const(*visitedDirs))
165 m_seenDirectories[visitedDir] = ini;
166
167 return { SearchResult::ResultType::Found, std::move(ini) };
168 }
169
170 if (!dir.cdUp())
171 break;
172 }
173
174 return SearchResult();
175}
176
177QQmlToolingSettings::SearchResult QQmlToolingSettings::Searcher::search(const QString &path)
178{
179 QSet<QString> visitedDirs;
180
181 // Try to find settings in directory hierarchy
182 if (const SearchResult result = searchDirectoryHierarchy(&visitedDirs, path); result.isValid())
183 return result;
184
185 // If we didn't find the settings file in the current directory or any parent directories,
186 // try to locate the settings file in the standard locations
187 if (const SearchResult result = searchDefaultLocation(&visitedDirs); result.isValid())
188 return result;
189
190 return SearchResult();
191}
192
193QQmlToolingSettings::SearchResult QQmlToolingSettings::search(
194 const QString &path, const SearchOptions &options)
195{
196 const auto maybeReport = qScopeGuard([&]() {
197 if (options.reportFoundSettingsFiles)
198 reportConfigForFiles({ path });
199 });
200
201 // If a specific settings file is provided, read it directly
202 if (!options.settingsFileName.isEmpty()) {
203 QFileInfo fileInfo(options.settingsFileName);
204 return fileInfo.exists() ? read(fileInfo.absoluteFilePath(), options) : SearchResult();
205 }
206
207 if (const SearchResult result = m_searcher.search(path); result.isValid())
208 return read(result.iniFilePath, options);
209
210 return SearchResult();
211}
212
213void QQmlToolingSettings::setValue(const QString &name, QVariant value)
214{
215 m_values[name] = value;
216}
217
218QVariant QQmlToolingSettings::value(const QString &name) const
219{
220 return m_values.value(name);
221}
222
223QStringList QQmlToolingSettings::valueAsStringList(const QString &name) const
224{
225 return value(name).toString().split(QDir::listSeparator());
226}
227
228void QQmlToolingSettings::resolveRelativeImportPaths(const QString &filePath, QStringList *paths)
229{
230 // transform relative paths in absolute paths starting from filePath's directory
231 const QDir fileDir = QFileInfo(filePath).absoluteDir();
232 for (auto it = paths->begin(), end = paths->end(); it != end; ++it) {
233 if (QFileInfo(*it).isAbsolute())
234 continue;
235 *it = QDir::cleanPath(fileDir.filePath(*it));
236 }
237}
238
239QStringList QQmlToolingSettings::valueAsAbsolutePathList(const QString &name,
240 const QString &baseForRelativePaths) const
241{
242 QStringList paths = valueAsStringList(name);
243 resolveRelativeImportPaths(baseForRelativePaths, &paths);
244 return paths;
245}
246
247bool QQmlToolingSettings::isSet(const QString &name) const
248{
249 if (!m_values.contains(name))
250 return false;
251
252 QVariant variant = m_values[name];
253
254 // Unset is encoded as an empty string
255 return !(variant.canConvert(QMetaType(QMetaType::QString)) && variant.toString().isEmpty());
256}
257
258bool QQmlToolingSettings::reportConfigForFiles(const QStringList &files)
259{
260 constexpr int maxAllowedFileLength = 255;
261 constexpr int minAllowedFileLength = 40;
262 bool headerPrinted = false;
263 auto lengthForFile = [maxAllowedFileLength](const QString &file) {
264 return std::min(int(file.length()), maxAllowedFileLength);
265 };
266
267 int maxFileLength =
268 std::accumulate(files.begin(), files.end(), 0, [&](int acc, const QString &file) {
269 return std::max(acc, lengthForFile(file));
270 });
271
272 if (maxFileLength < minAllowedFileLength)
273 maxFileLength = minAllowedFileLength;
274
275 for (const auto &file : files) {
276 if (file.isEmpty()) {
277 qWarning().noquote() << "Error: Could not find file" << file;
278 return false;
279 }
280
281 QString displayFile = file;
282 if (displayFile.length() > maxAllowedFileLength) {
283 displayFile = u"..." + displayFile.right(maxAllowedFileLength - 3);
284 }
285
286 const auto result = m_searcher.search(file);
287
288 if (!headerPrinted) {
289 QString header =
290 QStringLiteral("%1 | %2").arg("File", -maxFileLength).arg("Settings File");
291 qWarning().noquote() << header;
292 qWarning().noquote() << QString(header.length(), u'-');
293 headerPrinted = true;
294 }
295 QString line =
296 QStringLiteral("%1 | %2").arg(displayFile, -maxFileLength).arg(result.iniFilePath);
297 qWarning().noquote() << line;
298 }
299
300 return true;
301}
static QString findIniFile(const QString &local, const QString &global)