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