SdkTool: Fork utils/persistentsettings

And a few helper classes. This allows SdkTool being build
without Creator's Utils, and does not impose restrictions
on the development there.

Change-Id: Id15db9293f343ad2aeee5c09cc819d112ec144e4
Reviewed-by: Eike Ziller <eike.ziller@qt.io>
This commit is contained in:
hjk
2023-03-03 11:25:09 +01:00
parent 755d9769d8
commit bb4d9c92e7
14 changed files with 948 additions and 111 deletions

View File

@@ -66,33 +66,7 @@ add_qtc_library(sdktoolLib
rmqtoperation.cpp rmqtoperation.h rmqtoperation.cpp rmqtoperation.h
rmtoolchainoperation.cpp rmtoolchainoperation.h rmtoolchainoperation.cpp rmtoolchainoperation.h
settings.cpp settings.h settings.cpp settings.h
) sdkpersistentsettings.cpp sdkpersistentsettings.h
extend_qtc_library(sdktoolLib
SOURCES_PREFIX "${UtilsSourcesDir}"
PUBLIC_DEFINES UTILS_STATIC_LIBRARY
SOURCES
commandline.cpp commandline.h
devicefileaccess.cpp devicefileaccess.h
environment.cpp environment.h
filepath.cpp filepath.h
fileutils.cpp fileutils.h
hostosinfo.cpp hostosinfo.h
macroexpander.cpp macroexpander.h
namevaluedictionary.cpp namevaluedictionary.h
namevalueitem.cpp namevalueitem.h
persistentsettings.cpp persistentsettings.h
qtcassert.cpp qtcassert.h
savefile.cpp savefile.h
stringutils.cpp stringutils.h
)
extend_qtc_library(sdktoolLib CONDITION APPLE
SOURCES_PREFIX "${UtilsSourcesDir}"
SOURCES
fileutils_mac.mm fileutils_mac.h
PUBLIC_DEPENDS
${FWFoundation}
) )
if (MSVC) if (MSVC)

View File

@@ -4,13 +4,10 @@
#include "addcmakeoperation.h" #include "addcmakeoperation.h"
#include "addkeysoperation.h" #include "addkeysoperation.h"
#include "findkeyoperation.h"
#include "findvalueoperation.h" #include "findvalueoperation.h"
#include "getoperation.h" #include "getoperation.h"
#include "rmkeysoperation.h" #include "rmkeysoperation.h"
#include "settings.h"
#ifdef WITH_TESTS #ifdef WITH_TESTS
#include <QTest> #include <QTest>
#endif #endif
@@ -205,7 +202,7 @@ QVariantMap AddCMakeData::addCMake(const QVariantMap &map) const
data << KeyValuePair({cm, ID_KEY}, QVariant(m_id)); data << KeyValuePair({cm, ID_KEY}, QVariant(m_id));
data << KeyValuePair({cm, DISPLAYNAME_KEY}, QVariant(m_displayName)); data << KeyValuePair({cm, DISPLAYNAME_KEY}, QVariant(m_displayName));
data << KeyValuePair({cm, AUTODETECTED_KEY}, QVariant(true)); data << KeyValuePair({cm, AUTODETECTED_KEY}, QVariant(true));
data << KeyValuePair({cm, PATH_KEY}, Utils::FilePath::fromUserInput(m_path).toVariant()); data << KeyValuePair({cm, PATH_KEY}, QVariant(m_path));
KeyValuePairList extraList; KeyValuePairList extraList;
for (const KeyValuePair &pair : std::as_const(m_extra)) for (const KeyValuePair &pair : std::as_const(m_extra))
extraList << KeyValuePair(QStringList({cm}) << pair.key, pair.value); extraList << KeyValuePair(QStringList({cm}) << pair.key, pair.value);

View File

@@ -222,8 +222,7 @@ QVariantMap AddDebuggerData::addDebugger(const QVariantMap &map) const
data << KeyValuePair(QStringList() << debugger << QLatin1String(ABIS), QVariant(m_abis)); data << KeyValuePair(QStringList() << debugger << QLatin1String(ABIS), QVariant(m_abis));
data << KeyValuePair(QStringList() << debugger << QLatin1String(ENGINE_TYPE), QVariant(m_engine)); data << KeyValuePair(QStringList() << debugger << QLatin1String(ENGINE_TYPE), QVariant(m_engine));
data << KeyValuePair(QStringList() << debugger << QLatin1String(BINARY), data << KeyValuePair(QStringList() << debugger << QLatin1String(BINARY), QVariant(m_binary));
Utils::FilePath::fromUserInput(m_binary).toSettings());
data << KeyValuePair(QStringList() << QLatin1String(COUNT), QVariant(count + 1)); data << KeyValuePair(QStringList() << QLatin1String(COUNT), QVariant(count + 1));

View File

@@ -685,8 +685,7 @@ QVariantMap AddKitData::addKit(const QVariantMap &map,
if (!m_buildDevice.isNull()) if (!m_buildDevice.isNull())
data << KeyValuePair({kit, DATA, BUILDDEVICE_ID}, QVariant(m_buildDevice)); data << KeyValuePair({kit, DATA, BUILDDEVICE_ID}, QVariant(m_buildDevice));
if (!m_sysRoot.isNull()) if (!m_sysRoot.isNull())
data << KeyValuePair({kit, DATA, SYSROOT}, data << KeyValuePair({kit, DATA, SYSROOT}, QVariant(QDir::cleanPath(m_sysRoot)));
Utils::FilePath::fromUserInput(m_sysRoot).toSettings());
for (auto i = m_tcs.constBegin(); i != m_tcs.constEnd(); ++i) for (auto i = m_tcs.constBegin(); i != m_tcs.constEnd(); ++i)
data << KeyValuePair({kit, DATA, TOOLCHAIN, i.key()}, QVariant(i.value())); data << KeyValuePair({kit, DATA, TOOLCHAIN, i.key()}, QVariant(i.value()));
if (!qtId.isNull()) if (!qtId.isNull())

View File

@@ -11,8 +11,6 @@
#include "settings.h" #include "settings.h"
#include <utils/filepath.h>
#ifdef WITH_TESTS #ifdef WITH_TESTS
#include <QTest> #include <QTest>
#endif #endif
@@ -22,8 +20,6 @@
Q_LOGGING_CATEGORY(log, "qtc.sdktool.operations.addqt", QtWarningMsg) Q_LOGGING_CATEGORY(log, "qtc.sdktool.operations.addqt", QtWarningMsg)
using namespace Utils;
// Qt version file stuff: // Qt version file stuff:
const char PREFIX[] = "QtVersion."; const char PREFIX[] = "QtVersion.";
const char VERSION[] = "Version"; const char VERSION[] = "Version";
@@ -282,7 +278,7 @@ QVariantMap AddQtData::addQt(const QVariantMap &map) const
const QString qt = QString::fromLatin1(PREFIX) + QString::number(versionCount); const QString qt = QString::fromLatin1(PREFIX) + QString::number(versionCount);
// Sanitize qmake path: // Sanitize qmake path:
FilePath saneQmake = FilePath::fromUserInput(m_qmake).cleanPath(); QString saneQmake = QDir::cleanPath(m_qmake);
// insert data: // insert data:
KeyValuePairList data; KeyValuePairList data;
@@ -291,7 +287,7 @@ QVariantMap AddQtData::addQt(const QVariantMap &map) const
data << KeyValuePair(QStringList() << qt << QLatin1String(AUTODETECTED), QVariant(true)); data << KeyValuePair(QStringList() << qt << QLatin1String(AUTODETECTED), QVariant(true));
data << KeyValuePair(QStringList() << qt << QLatin1String(AUTODETECTION_SOURCE), QVariant(sdkId)); data << KeyValuePair(QStringList() << qt << QLatin1String(AUTODETECTION_SOURCE), QVariant(sdkId));
data << KeyValuePair(QStringList() << qt << QLatin1String(QMAKE), saneQmake.toSettings()); data << KeyValuePair(QStringList() << qt << QLatin1String(QMAKE), QVariant(saneQmake));
data << KeyValuePair(QStringList() << qt << QLatin1String(TYPE), QVariant(m_type)); data << KeyValuePair(QStringList() << qt << QLatin1String(TYPE), QVariant(m_type));
data << KeyValuePair(QStringList() << qt << ABIS, QVariant(m_abis)); data << KeyValuePair(QStringList() << qt << ABIS, QVariant(m_abis));

View File

@@ -4,13 +4,10 @@
#include "addtoolchainoperation.h" #include "addtoolchainoperation.h"
#include "addkeysoperation.h" #include "addkeysoperation.h"
#include "findkeyoperation.h"
#include "findvalueoperation.h" #include "findvalueoperation.h"
#include "getoperation.h" #include "getoperation.h"
#include "rmkeysoperation.h" #include "rmkeysoperation.h"
#include "settings.h"
#include <iostream> #include <iostream>
#ifdef WITH_TESTS #ifdef WITH_TESTS
@@ -283,7 +280,7 @@ QVariantMap AddToolChainData::addToolChain(const QVariantMap &map) const
data << KeyValuePair({tc, LANGUAGE_KEY_V2}, QVariant(newLang)); data << KeyValuePair({tc, LANGUAGE_KEY_V2}, QVariant(newLang));
data << KeyValuePair({tc, DISPLAYNAME}, QVariant(m_displayName)); data << KeyValuePair({tc, DISPLAYNAME}, QVariant(m_displayName));
data << KeyValuePair({tc, AUTODETECTED}, QVariant(true)); data << KeyValuePair({tc, AUTODETECTED}, QVariant(true));
data << KeyValuePair({tc, PATH}, Utils::FilePath::fromUserInput(m_path).toSettings()); data << KeyValuePair({tc, PATH}, QVariant(m_path));
data << KeyValuePair({tc, TARGET_ABI}, QVariant(m_targetAbi)); data << KeyValuePair({tc, TARGET_ABI}, QVariant(m_targetAbi));
QVariantList abis; QVariantList abis;
const QStringList abiStrings = m_supportedAbis.split(','); const QStringList abiStrings = m_supportedAbis.split(',');

View File

@@ -30,6 +30,7 @@
#include <iostream> #include <iostream>
#include <QCoreApplication> #include <QCoreApplication>
#include <QDir>
#include <QLibraryInfo> #include <QLibraryInfo>
#include <QStringList> #include <QStringList>
@@ -60,10 +61,7 @@ void printHelp(const std::vector<std::unique_ptr<Operation>> &operations)
std::cout << " --sdkpath=PATH|-s PATH Set the path to the SDK files" << std::endl << std::endl; std::cout << " --sdkpath=PATH|-s PATH Set the path to the SDK files" << std::endl << std::endl;
std::cout << "Default sdkpath is \"" std::cout << "Default sdkpath is \""
<< qPrintable(QDir::cleanPath( << qPrintable(QDir::cleanPath(QCoreApplication::applicationDirPath() + '/' + DATA_PATH))
Utils::FilePath::fromString(QCoreApplication::applicationDirPath())
.pathAppended(DATA_PATH)
.toUserOutput()))
<< "\"" << std::endl << "\"" << std::endl
<< std::endl; << std::endl;
@@ -105,7 +103,7 @@ int parseArguments(const QStringList &args, Settings *s,
// sdkpath // sdkpath
if (current.startsWith(QLatin1String("--sdkpath="))) { if (current.startsWith(QLatin1String("--sdkpath="))) {
s->sdkPath = Utils::FilePath::fromString(current.mid(10)); s->sdkPath = current.mid(10);
continue; continue;
} }
if (current == QLatin1String("-s")) { if (current == QLatin1String("-s")) {
@@ -114,7 +112,7 @@ int parseArguments(const QStringList &args, Settings *s,
printHelp(operations); printHelp(operations);
return 1; return 1;
} }
s->sdkPath = Utils::FilePath::fromString(next); s->sdkPath = next;
++i; // skip next; ++i; // skip next;
continue; continue;
} }

View File

@@ -4,8 +4,7 @@
#include "operation.h" #include "operation.h"
#include "settings.h" #include "settings.h"
#include "sdkpersistentsettings.h"
#include <utils/persistentsettings.h>
#include <QDir> #include <QDir>
#include <QFile> #include <QFile>
@@ -65,9 +64,9 @@ QVariantMap Operation::load(const QString &file)
QVariantMap map; QVariantMap map;
// Read values from original file: // Read values from original file:
Utils::FilePath path = Settings::instance()->getPath(file); QString path = Settings::instance()->getPath(file);
if (path.exists()) { if (QFileInfo::exists(path)) {
Utils::PersistentSettingsReader reader; SdkPersistentSettingsReader reader;
if (!reader.load(path)) if (!reader.load(path))
return QVariantMap(); return QVariantMap();
map = reader.restoreValues(); map = reader.restoreValues();
@@ -78,32 +77,32 @@ QVariantMap Operation::load(const QString &file)
bool Operation::save(const QVariantMap &map, const QString &file) const bool Operation::save(const QVariantMap &map, const QString &file) const
{ {
Utils::FilePath path = Settings::instance()->getPath(file); QString path = Settings::instance()->getPath(file);
if (path.isEmpty()) { if (path.isEmpty()) {
std::cerr << "Error: No path found for " << qPrintable(file) << "." << std::endl; std::cerr << "Error: No path found for " << qPrintable(file) << "." << std::endl;
return false; return false;
} }
Utils::FilePath dirName = path.parentDir(); QString dirName = QDir::cleanPath(path + "/..");
QDir dir(dirName.toString()); QDir dir(dirName);
if (!dir.exists() && !dir.mkpath(QLatin1String("."))) { if (!dir.exists() && !dir.mkpath(QLatin1String("."))) {
std::cerr << "Error: Could not create directory " << qPrintable(dirName.toString()) std::cerr << "Error: Could not create directory " << qPrintable(dirName)
<< "." << std::endl; << "." << std::endl;
return false; return false;
} }
Utils::PersistentSettingsWriter writer(path, QLatin1String("QtCreator") SdkPersistentSettingsWriter writer(path, QLatin1String("QtCreator")
+ file[0].toUpper() + file.mid(1)); + file[0].toUpper() + file.mid(1));
QString errorMessage; QString errorMessage;
if (!writer.save(map, &errorMessage)) { if (!writer.save(map, &errorMessage)) {
std::cerr << "Error: Could not save settings " << qPrintable(path.toString()) std::cerr << "Error: Could not save settings " << qPrintable(path)
<< "." << std::endl; << "." << std::endl;
return false; return false;
} }
if (!path.setPermissions(QFile::ReadOwner | QFile::WriteOwner if (!QFile(path).setPermissions(QFile::ReadOwner | QFile::WriteOwner
| QFile::ReadGroup | QFile::ReadOther)) { | QFile::ReadGroup | QFile::ReadOther)) {
std::cerr << "Error: Could not set permissions for " << qPrintable(path.toString()) std::cerr << "Error: Could not set permissions for " << qPrintable(path)
<< "." << std::endl; << "." << std::endl;
return false; return false;
} }

View File

@@ -3,8 +3,6 @@
#pragma once #pragma once
#include <utils/fileutils.h>
#include <QStringList> #include <QStringList>
#include <QVariant> #include <QVariant>

View File

@@ -0,0 +1,870 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "sdkpersistentsettings.h"
#include <QCoreApplication>
#include <QDateTime>
#include <QDebug>
#include <QDir>
#include <QRect>
#include <QRegularExpression>
#include <QStack>
#include <QTextStream>
#include <QXmlStreamAttributes>
#include <QXmlStreamReader>
#include <QXmlStreamWriter>
#include <QTemporaryFile>
#ifdef Q_OS_WIN
# include <windows.h>
# include <io.h>
#else
# include <unistd.h>
# include <sys/stat.h>
#endif
#define QTC_ASSERT_STRINGIFY_HELPER(x) #x
#define QTC_ASSERT_STRINGIFY(x) QTC_ASSERT_STRINGIFY_HELPER(x)
#define QTC_ASSERT_STRING(cond) writeAssertLocation(\
"\"" cond"\" in " __FILE__ ":" QTC_ASSERT_STRINGIFY(__LINE__))
// The 'do {...} while (0)' idiom is not used for the main block here to be
// able to use 'break' and 'continue' as 'actions'.
#define QTC_ASSERT(cond, action) if (Q_LIKELY(cond)) {} else { QTC_ASSERT_STRING(#cond); action; } do {} while (0)
#define QTC_CHECK(cond) if (Q_LIKELY(cond)) {} else { QTC_ASSERT_STRING(#cond); } do {} while (0)
#define QTC_GUARD(cond) ((Q_LIKELY(cond)) ? true : (QTC_ASSERT_STRING(#cond), false))
void writeAssertLocation(const char *msg)
{
const QByteArray time = QTime::currentTime().toString(Qt::ISODateWithMs).toLatin1();
static bool goBoom = qEnvironmentVariableIsSet("QTC_FATAL_ASSERTS");
if (goBoom)
qFatal("SOFT ASSERT [%s] made fatal: %s", time.data(), msg);
else
qDebug("SOFT ASSERT [%s]: %s", time.data(), msg);
}
static QFile::Permissions m_umask;
class SdkSaveFile : public QFile
{
public:
explicit SdkSaveFile(const QString &filePath) : m_finalFilePath(filePath) {}
~SdkSaveFile() override;
bool open(OpenMode flags = QIODevice::WriteOnly) override;
void rollback();
bool commit();
static void initializeUmask();
private:
const QString m_finalFilePath;
std::unique_ptr<QTemporaryFile> m_tempFile;
bool m_finalized = true;
};
SdkSaveFile::~SdkSaveFile()
{
if (!m_finalized)
rollback();
}
bool SdkSaveFile::open(OpenMode flags)
{
if (m_finalFilePath.isEmpty()) {
qWarning("Save file path empty");
return false;
}
QFile ofi(m_finalFilePath);
// Check whether the existing file is writable
if (ofi.exists() && !ofi.open(QIODevice::ReadWrite)) {
setErrorString(ofi.errorString());
return false;
}
m_tempFile = std::make_unique<QTemporaryFile>(m_finalFilePath);
m_tempFile->setAutoRemove(false);
if (!m_tempFile->open())
return false;
setFileName(m_tempFile->fileName());
if (!QFile::open(flags))
return false;
m_finalized = false; // needs clean up in the end
if (ofi.exists()) {
setPermissions(ofi.permissions()); // Ignore errors
} else {
Permissions permAll = QFile::ReadOwner
| QFile::ReadGroup
| QFile::ReadOther
| QFile::WriteOwner
| QFile::WriteGroup
| QFile::WriteOther;
// set permissions with respect to the current umask
setPermissions(permAll & ~m_umask);
}
return true;
}
void SdkSaveFile::rollback()
{
close();
if (m_tempFile)
m_tempFile->remove();
m_finalized = true;
}
static QString resolveSymlinks(QString current)
{
int links = 16;
while (links--) {
const QFileInfo info(current);
if (!info.isSymLink())
return {};
current = info.symLinkTarget();
}
return current;
}
bool SdkSaveFile::commit()
{
QTC_ASSERT(!m_finalized && m_tempFile, return false;);
m_finalized = true;
if (!flush()) {
close();
m_tempFile->remove();
return false;
}
#ifdef Q_OS_WIN
FlushFileBuffers(reinterpret_cast<HANDLE>(_get_osfhandle(handle())));
#elif _POSIX_SYNCHRONIZED_IO > 0
fdatasync(handle());
#else
fsync(handle());
#endif
close();
m_tempFile->close();
if (error() != NoError) {
m_tempFile->remove();
return false;
}
QString finalFileName = resolveSymlinks(m_finalFilePath);
#ifdef Q_OS_WIN
// Release the file lock
m_tempFile.reset();
bool result = ReplaceFile(finalFileName.toStdWString().data(),
fileName().toStdWString().data(),
nullptr, REPLACEFILE_IGNORE_MERGE_ERRORS, nullptr, nullptr);
if (!result) {
DWORD replaceErrorCode = GetLastError();
QString errorStr;
if (!QFile::exists(finalFileName)) {
// Replace failed because finalFileName does not exist, try rename.
if (!(result = rename(finalFileName)))
errorStr = errorString();
} else {
if (replaceErrorCode == ERROR_UNABLE_TO_REMOVE_REPLACED) {
// If we do not get the rights to remove the original final file we still might try
// to replace the file contents
result = MoveFileEx(fileName().toStdWString().data(),
finalFileName.toStdWString().data(),
MOVEFILE_COPY_ALLOWED
| MOVEFILE_REPLACE_EXISTING
| MOVEFILE_WRITE_THROUGH);
if (!result)
replaceErrorCode = GetLastError();
}
if (!result) {
wchar_t messageBuffer[256];
FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
nullptr, replaceErrorCode,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
messageBuffer, sizeof(messageBuffer), nullptr);
errorStr = QString::fromWCharArray(messageBuffer);
}
}
if (!result) {
remove();
setErrorString(errorStr);
}
}
return result;
#else
const QString backupName = finalFileName + '~';
// Back up current file.
// If it's opened by another application, the lock follows the move.
if (QFile::exists(finalFileName)) {
// Kill old backup. Might be useful if creator crashed before removing backup.
QFile::remove(backupName);
QFile finalFile(finalFileName);
if (!finalFile.rename(backupName)) {
m_tempFile->remove();
setErrorString(finalFile.errorString());
return false;
}
}
bool result = true;
if (!m_tempFile->rename(finalFileName)) {
// The case when someone else was able to create finalFileName after we've renamed it.
// Higher level call may try to save this file again but here we do nothing and
// return false while keeping the error string from last rename call.
const QString &renameError = m_tempFile->errorString();
m_tempFile->remove();
setErrorString(renameError);
QFile::rename(backupName, finalFileName); // rollback to backup if possible ...
return false; // ... or keep the backup copy at least
}
QFile::remove(backupName);
return result;
#endif
}
void SdkSaveFile::initializeUmask()
{
#ifdef Q_OS_WIN
m_umask = QFile::WriteGroup | QFile::WriteOther;
#else
// Get the current process' file creation mask (umask)
// umask() is not thread safe so this has to be done by single threaded
// application initialization
mode_t mask = umask(0); // get current umask
umask(mask); // set it back
m_umask = ((mask & S_IRUSR) ? QFile::ReadOwner : QFlags<QFile::Permission>())
| ((mask & S_IWUSR) ? QFile::WriteOwner : QFlags<QFile::Permission>())
| ((mask & S_IXUSR) ? QFile::ExeOwner : QFlags<QFile::Permission>())
| ((mask & S_IRGRP) ? QFile::ReadGroup : QFlags<QFile::Permission>())
| ((mask & S_IWGRP) ? QFile::WriteGroup : QFlags<QFile::Permission>())
| ((mask & S_IXGRP) ? QFile::ExeGroup : QFlags<QFile::Permission>())
| ((mask & S_IROTH) ? QFile::ReadOther : QFlags<QFile::Permission>())
| ((mask & S_IWOTH) ? QFile::WriteOther : QFlags<QFile::Permission>())
| ((mask & S_IXOTH) ? QFile::ExeOther : QFlags<QFile::Permission>());
#endif
}
class SdkFileSaverBase
{
public:
SdkFileSaverBase() = default;
virtual ~SdkFileSaverBase() = default;
QString filePath() const { return m_filePath; }
bool hasError() const { return m_hasError; }
QString errorString() const { return m_errorString; }
virtual bool finalize();
bool finalize(QString *errStr);
bool write(const char *data, int len);
bool write(const QByteArray &bytes);
bool setResult(QTextStream *stream);
bool setResult(QDataStream *stream);
bool setResult(QXmlStreamWriter *stream);
bool setResult(bool ok);
QFile *file() { return m_file.get(); }
protected:
std::unique_ptr<QFile> m_file;
QString m_filePath;
QString m_errorString;
bool m_hasError = false;
};
bool SdkFileSaverBase::finalize()
{
m_file->close();
setResult(m_file->error() == QFile::NoError);
m_file.reset();
return !m_hasError;
}
bool SdkFileSaverBase::finalize(QString *errStr)
{
if (finalize())
return true;
if (errStr)
*errStr = errorString();
return false;
}
bool SdkFileSaverBase::write(const char *data, int len)
{
if (m_hasError)
return false;
return setResult(m_file->write(data, len) == len);
}
bool SdkFileSaverBase::write(const QByteArray &bytes)
{
if (m_hasError)
return false;
return setResult(m_file->write(bytes) == bytes.count());
}
bool SdkFileSaverBase::setResult(bool ok)
{
if (!ok && !m_hasError) {
if (!m_file->errorString().isEmpty()) {
m_errorString = QString("Cannot write file %1: %2")
.arg(m_filePath, m_file->errorString());
} else {
m_errorString = QString("Cannot write file %1. Disk full?")
.arg(m_filePath);
}
m_hasError = true;
}
return ok;
}
bool SdkFileSaverBase::setResult(QTextStream *stream)
{
stream->flush();
return setResult(stream->status() == QTextStream::Ok);
}
bool SdkFileSaverBase::setResult(QDataStream *stream)
{
return setResult(stream->status() == QDataStream::Ok);
}
bool SdkFileSaverBase::setResult(QXmlStreamWriter *stream)
{
return setResult(!stream->hasError());
}
// SdkFileSaver
class SdkFileSaver : public SdkFileSaverBase
{
public:
// QIODevice::WriteOnly is implicit
explicit SdkFileSaver(const QString &filePath, QIODevice::OpenMode mode = QIODevice::NotOpen);
bool finalize() override;
private:
bool m_isSafe = false;
};
SdkFileSaver::SdkFileSaver(const QString &filePath, QIODevice::OpenMode mode)
{
m_filePath = filePath;
// Workaround an assert in Qt -- and provide a useful error message, too:
#if Q_OS_WIN
// Taken from: https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx
static const QStringList reservedNames
= {"CON", "PRN", "AUX", "NUL",
"COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9",
"LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9"};
const QString fn = QFileInfo(filePath).baseName().toUpper();
if (reservedNames.contains(fn)) {
m_errorString = QString("%1: Is a reserved filename on Windows. Cannot save.").arg(filePath);
m_hasError = true;
return;
}
#endif
if (mode & (QIODevice::ReadOnly | QIODevice::Append)) {
m_file.reset(new QFile{filePath});
m_isSafe = false;
} else {
m_file.reset(new SdkSaveFile(filePath));
m_isSafe = true;
}
if (!m_file->open(QIODevice::WriteOnly | mode)) {
QString err = QFileInfo::exists(filePath) ?
QString("Cannot overwrite file %1: %2") : QString("Cannot create file %1: %2");
m_errorString = err.arg(filePath, m_file->errorString());
m_hasError = true;
}
}
bool SdkFileSaver::finalize()
{
if (!m_isSafe)
return SdkFileSaverBase::finalize();
auto sf = static_cast<SdkSaveFile *>(m_file.get());
if (m_hasError) {
if (sf->isOpen())
sf->rollback();
} else {
setResult(sf->commit());
}
m_file.reset();
return !m_hasError;
}
// Read and write rectangle in X11 resource syntax "12x12+4+3"
static QString rectangleToString(const QRect &r)
{
QString result;
QTextStream str(&result);
str << r.width() << 'x' << r.height();
if (r.x() >= 0)
str << '+';
str << r.x();
if (r.y() >= 0)
str << '+';
str << r.y();
return result;
}
static QRect stringToRectangle(const QString &v)
{
static QRegularExpression pattern("^(\\d+)x(\\d+)([-+]\\d+)([-+]\\d+)$");
Q_ASSERT(pattern.isValid());
const QRegularExpressionMatch match = pattern.match(v);
return match.hasMatch() ?
QRect(QPoint(match.captured(3).toInt(), match.captured(4).toInt()),
QSize(match.captured(1).toInt(), match.captured(2).toInt())) :
QRect();
}
/*!
\class SdkPersistentSettingsReader
\note This is aQString based fork of Utils::PersistentSettigsReader
\brief The SdkPersistentSettingsReader class reads a QVariantMap of arbitrary,
nested data structures from an XML file.
Handles all string-serializable simple types and QVariantList and QVariantMap. Example:
\code
<qtcreator>
<data>
<variable>ProjectExplorer.Project.ActiveTarget</variable>
<value type="int">0</value>
</data>
<data>
<variable>ProjectExplorer.Project.EditorSettings</variable>
<valuemap type="QVariantMap">
<value type="bool" key="EditorConfiguration.AutoIndent">true</value>
</valuemap>
</data>
\endcode
When parsing the structure, a parse stack of ParseValueStackEntry is used for each
<data> element. ParseValueStackEntry is a variant/union of:
\list
\li simple value
\li map
\li list
\endlist
You can register string-serialize functions for custom types by registering them in the Qt Meta
type system. Example:
\code
QMetaType::registerConverter(&MyCustomType::toString);
QMetaType::registerConverter<QString, MyCustomType>(&myCustomTypeFromString);
\endcode
When entering a value element ( \c <value> / \c <valuelist> , \c <valuemap> ), entry is pushed
accordingly. When leaving the element, the QVariant-value of the entry is taken off the stack
and added to the stack entry below (added to list or inserted into map). The first element
of the stack is the value of the <data> element.
\sa SdkPersistentSettingsWriter
*/
struct Context // Basic context containing element name string constants.
{
Context() {}
const QString qtCreatorElement = QString("qtcreator");
const QString dataElement = QString("data");
const QString variableElement = QString("variable");
const QString typeAttribute = QString("type");
const QString valueElement = QString("value");
const QString valueListElement = QString("valuelist");
const QString valueMapElement = QString("valuemap");
const QString keyAttribute = QString("key");
};
struct ParseValueStackEntry
{
explicit ParseValueStackEntry(QVariant::Type t = QVariant::Invalid, const QString &k = QString()) : type(t), key(k) {}
explicit ParseValueStackEntry(const QVariant &aSimpleValue, const QString &k);
QVariant value() const;
void addChild(const QString &key, const QVariant &v);
QVariant::Type type;
QString key;
QVariant simpleValue;
QVariantList listValue;
QVariantMap mapValue;
};
ParseValueStackEntry::ParseValueStackEntry(const QVariant &aSimpleValue, const QString &k) :
type(aSimpleValue.type()), key(k), simpleValue(aSimpleValue)
{
QTC_ASSERT(simpleValue.isValid(), return);
}
QVariant ParseValueStackEntry::value() const
{
switch (type) {
case QVariant::Invalid:
return QVariant();
case QVariant::Map:
return QVariant(mapValue);
case QVariant::List:
return QVariant(listValue);
default:
break;
}
return simpleValue;
}
void ParseValueStackEntry::addChild(const QString &key, const QVariant &v)
{
switch (type) {
case QVariant::Map:
mapValue.insert(key, v);
break;
case QVariant::List:
listValue.push_back(v);
break;
default:
qWarning() << "ParseValueStackEntry::Internal error adding " << key << v << " to "
<< QVariant::typeToName(type) << value();
break;
}
}
class ParseContext : public Context
{
public:
QVariantMap parse(const QString &file);
private:
enum Element { QtCreatorElement, DataElement, VariableElement,
SimpleValueElement, ListValueElement, MapValueElement, UnknownElement };
Element element(const QStringView &r) const;
static inline bool isValueElement(Element e)
{ return e == SimpleValueElement || e == ListValueElement || e == MapValueElement; }
QVariant readSimpleValue(QXmlStreamReader &r, const QXmlStreamAttributes &attributes) const;
bool handleStartElement(QXmlStreamReader &r);
bool handleEndElement(const QStringView &name);
static QString formatWarning(const QXmlStreamReader &r, const QString &message);
QStack<ParseValueStackEntry> m_valueStack;
QVariantMap m_result;
QString m_currentVariableName;
};
static QByteArray fileContents(const QString &path)
{
QFile f(path);
if (!f.exists())
return {};
if (!f.open(QFile::ReadOnly))
return {};
return f.readAll();
}
QVariantMap ParseContext::parse(const QString &file)
{
QXmlStreamReader r(fileContents(file));
m_result.clear();
m_currentVariableName.clear();
while (!r.atEnd()) {
switch (r.readNext()) {
case QXmlStreamReader::StartElement:
if (handleStartElement(r))
return m_result;
break;
case QXmlStreamReader::EndElement:
if (handleEndElement(r.name()))
return m_result;
break;
case QXmlStreamReader::Invalid:
qWarning("Error reading %s:%d: %s", qPrintable(file),
int(r.lineNumber()), qPrintable(r.errorString()));
return QVariantMap();
default:
break;
} // switch token
} // while (!r.atEnd())
return m_result;
}
bool ParseContext::handleStartElement(QXmlStreamReader &r)
{
const QStringView name = r.name();
const Element e = element(name);
if (e == VariableElement) {
m_currentVariableName = r.readElementText();
return false;
}
if (!ParseContext::isValueElement(e))
return false;
const QXmlStreamAttributes attributes = r.attributes();
const QString key = attributes.hasAttribute(keyAttribute) ?
attributes.value(keyAttribute).toString() : QString();
switch (e) {
case SimpleValueElement: {
// This reads away the end element, so, handle end element right here.
const QVariant v = readSimpleValue(r, attributes);
if (!v.isValid()) {
qWarning() << ParseContext::formatWarning(r, QString::fromLatin1("Failed to read element \"%1\".").arg(name.toString()));
return false;
}
m_valueStack.push_back(ParseValueStackEntry(v, key));
return handleEndElement(name);
}
case ListValueElement:
m_valueStack.push_back(ParseValueStackEntry(QVariant::List, key));
break;
case MapValueElement:
m_valueStack.push_back(ParseValueStackEntry(QVariant::Map, key));
break;
default:
break;
}
return false;
}
bool ParseContext::handleEndElement(const QStringView &name)
{
const Element e = element(name);
if (ParseContext::isValueElement(e)) {
QTC_ASSERT(!m_valueStack.isEmpty(), return true);
const ParseValueStackEntry top = m_valueStack.pop();
if (m_valueStack.isEmpty()) { // Last element? -> Done with that variable.
QTC_ASSERT(!m_currentVariableName.isEmpty(), return true);
m_result.insert(m_currentVariableName, top.value());
m_currentVariableName.clear();
return false;
}
m_valueStack.top().addChild(top.key, top.value());
}
return e == QtCreatorElement;
}
QString ParseContext::formatWarning(const QXmlStreamReader &r, const QString &message)
{
QString result = QLatin1String("Warning reading ");
if (const QIODevice *device = r.device())
if (const auto file = qobject_cast<const QFile *>(device))
result += QDir::toNativeSeparators(file->fileName()) + QLatin1Char(':');
result += QString::number(r.lineNumber());
result += QLatin1String(": ");
result += message;
return result;
}
ParseContext::Element ParseContext::element(const QStringView &r) const
{
if (r == valueElement)
return SimpleValueElement;
if (r == valueListElement)
return ListValueElement;
if (r == valueMapElement)
return MapValueElement;
if (r == qtCreatorElement)
return QtCreatorElement;
if (r == dataElement)
return DataElement;
if (r == variableElement)
return VariableElement;
return UnknownElement;
}
QVariant ParseContext::readSimpleValue(QXmlStreamReader &r, const QXmlStreamAttributes &attributes) const
{
// Simple value
const QStringView type = attributes.value(typeAttribute);
const QString text = r.readElementText();
if (type == QLatin1String("QChar")) { // Workaround: QTBUG-12345
QTC_ASSERT(text.size() == 1, return QVariant());
return QVariant(QChar(text.at(0)));
}
if (type == QLatin1String("QRect")) {
const QRect rectangle = stringToRectangle(text);
return rectangle.isValid() ? QVariant(rectangle) : QVariant();
}
QVariant value;
value.setValue(text);
value.convert(QMetaType::type(type.toLatin1().constData()));
return value;
}
// =================================== SdkPersistentSettingsReader
SdkPersistentSettingsReader::SdkPersistentSettingsReader() = default;
QVariant SdkPersistentSettingsReader::restoreValue(const QString &variable, const QVariant &defaultValue) const
{
if (m_valueMap.contains(variable))
return m_valueMap.value(variable);
return defaultValue;
}
QVariantMap SdkPersistentSettingsReader::restoreValues() const
{
return m_valueMap;
}
bool SdkPersistentSettingsReader::load(const QString &fileName)
{
m_valueMap.clear();
if (QFileInfo(fileName).size() == 0) // skip empty files
return false;
ParseContext ctx;
m_valueMap = ctx.parse(fileName);
return true;
}
/*!
\class SdkPersistentSettingsWriter
\note This is a fork of Utils::PersistentSettingsWriter
\brief The SdkPersistentSettingsWriter class serializes a QVariantMap of
arbitrary, nested data structures to an XML file.
\sa SdkPersistentSettingsReader
*/
static void writeVariantValue(QXmlStreamWriter &w, const Context &ctx,
const QVariant &variant, const QString &key = QString())
{
switch (static_cast<int>(variant.type())) {
case static_cast<int>(QVariant::StringList):
case static_cast<int>(QVariant::List): {
w.writeStartElement(ctx.valueListElement);
w.writeAttribute(ctx.typeAttribute, QLatin1String(QVariant::typeToName(QVariant::List)));
if (!key.isEmpty())
w.writeAttribute(ctx.keyAttribute, key);
const QList<QVariant> list = variant.toList();
for (const QVariant &var : list)
writeVariantValue(w, ctx, var);
w.writeEndElement();
break;
}
case static_cast<int>(QVariant::Map): {
w.writeStartElement(ctx.valueMapElement);
w.writeAttribute(ctx.typeAttribute, QLatin1String(QVariant::typeToName(QVariant::Map)));
if (!key.isEmpty())
w.writeAttribute(ctx.keyAttribute, key);
const QVariantMap varMap = variant.toMap();
const QVariantMap::const_iterator cend = varMap.constEnd();
for (QVariantMap::const_iterator i = varMap.constBegin(); i != cend; ++i)
writeVariantValue(w, ctx, i.value(), i.key());
w.writeEndElement();
}
break;
case static_cast<int>(QMetaType::QObjectStar): // ignore QObjects!
case static_cast<int>(QMetaType::VoidStar): // ignore void pointers!
break;
default:
w.writeStartElement(ctx.valueElement);
w.writeAttribute(ctx.typeAttribute, QLatin1String(variant.typeName()));
if (!key.isEmpty())
w.writeAttribute(ctx.keyAttribute, key);
switch (variant.type()) {
case QVariant::Rect:
w.writeCharacters(rectangleToString(variant.toRect()));
break;
default:
w.writeCharacters(variant.toString());
break;
}
w.writeEndElement();
break;
}
}
SdkPersistentSettingsWriter::SdkPersistentSettingsWriter(const QString &fileName, const QString &docType) :
m_fileName(fileName), m_docType(docType)
{ }
bool SdkPersistentSettingsWriter::save(const QVariantMap &data, QString *errorString) const
{
if (data == m_savedData)
return true;
return write(data, errorString);
}
QString SdkPersistentSettingsWriter::fileName() const
{ return m_fileName; }
//** * @brief Set contents of file (e.g. from data read from it). */
void SdkPersistentSettingsWriter::setContents(const QVariantMap &data)
{
m_savedData = data;
}
bool SdkPersistentSettingsWriter::write(const QVariantMap &data, QString *errorString) const
{
const QString parentDir = QDir::cleanPath(m_fileName + "/..");
const QFileInfo fi(parentDir);
if (!(fi.exists() && fi.isDir() && fi.isWritable())) {
bool res = QDir().mkpath(parentDir);
if (!res)
return false;
}
SdkFileSaver saver(m_fileName, QIODevice::Text);
if (!saver.hasError()) {
const Context ctx;
QXmlStreamWriter w(saver.file());
w.setAutoFormatting(true);
w.setAutoFormattingIndent(1); // Historical, used to be QDom.
w.writeStartDocument();
w.writeDTD(QLatin1String("<!DOCTYPE ") + m_docType + QLatin1Char('>'));
w.writeComment(QString::fromLatin1(" Written by %1 %2, %3. ").
arg(QCoreApplication::applicationName(),
QCoreApplication::applicationVersion(),
QDateTime::currentDateTime().toString(Qt::ISODate)));
w.writeStartElement(ctx.qtCreatorElement);
const QVariantMap::const_iterator cend = data.constEnd();
for (QVariantMap::const_iterator it = data.constBegin(); it != cend; ++it) {
w.writeStartElement(ctx.dataElement);
w.writeTextElement(ctx.variableElement, it.key());
writeVariantValue(w, ctx, it.value());
w.writeEndElement();
}
w.writeEndDocument();
saver.setResult(&w);
}
bool ok = saver.finalize();
if (ok) {
m_savedData = data;
} else if (errorString) {
m_savedData.clear();
*errorString = saver.errorString();
}
return ok;
}

View File

@@ -0,0 +1,37 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#pragma once
#include <QVariant>
class SdkPersistentSettingsReader
{
public:
SdkPersistentSettingsReader();
QVariant restoreValue(const QString &variable, const QVariant &defaultValue = QVariant()) const;
QVariantMap restoreValues() const;
bool load(const QString &fileName);
private:
QMap<QString, QVariant> m_valueMap;
};
class SdkPersistentSettingsWriter
{
public:
SdkPersistentSettingsWriter(const QString &fileName, const QString &docType);
bool save(const QVariantMap &data, QString *errorString) const;
QString fileName() const;
void setContents(const QVariantMap &data);
private:
bool write(const QVariantMap &data, QString *errorString) const;
const QString m_fileName;
const QString m_docType;
mutable QMap<QString, QVariant> m_savedData;
};

View File

@@ -85,34 +85,7 @@ QtcLibrary {
"rmtoolchainoperation.h", "rmtoolchainoperation.h",
"settings.cpp", "settings.cpp",
"settings.h", "settings.h",
] "sdkpersistentsettings.cpp",
"sdkpersistentsettings.h",
Group {
name: "Utils"
prefix: libsDir + "/utils/"
files: [
"commandline.cpp", "commandline.h",
"devicefileaccess.cpp", "devicefileaccess.h",
"environment.cpp", "environment.h",
"filepath.cpp", "filepath.h",
"fileutils.cpp", "fileutils.h",
"hostosinfo.cpp", "hostosinfo.h",
"macroexpander.cpp", "macroexpander.h",
"namevaluedictionary.cpp", "namevaluedictionary.h",
"namevalueitem.cpp", "namevalueitem.h",
"persistentsettings.cpp", "persistentsettings.h",
"qtcassert.cpp", "qtcassert.h",
"savefile.cpp", "savefile.h",
"stringutils.cpp"
] ]
} }
Group {
name: "Utils/macOS"
condition: qbs.targetOS.contains("macos")
prefix: libsDir + "/utils/"
files: [
"fileutils_mac.h",
"fileutils_mac.mm",
]
}
}

View File

@@ -2,11 +2,11 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "settings.h" #include "settings.h"
#include "operation.h"
#include <app/app_version.h> #include <app/app_version.h>
#include <QCoreApplication> #include <QCoreApplication>
#include <QDir>
static Settings *m_instance = nullptr; static Settings *m_instance = nullptr;
@@ -21,28 +21,28 @@ Settings::Settings()
m_instance = this; m_instance = this;
// autodetect sdk dir: // autodetect sdk dir:
sdkPath = Utils::FilePath::fromUserInput(QCoreApplication::applicationDirPath()) sdkPath = QDir::cleanPath(QCoreApplication::applicationDirPath()
.pathAppended(DATA_PATH).cleanPath() + '/' + DATA_PATH
.pathAppended(Core::Constants::IDE_SETTINGSVARIANT_STR) + '/' + Core::Constants::IDE_SETTINGSVARIANT_STR
.pathAppended(Core::Constants::IDE_ID); + '/' + Core::Constants::IDE_ID);
} }
Utils::FilePath Settings::getPath(const QString &file) QString Settings::getPath(const QString &file)
{ {
Utils::FilePath result = sdkPath; QString result = sdkPath;
const QString lowerFile = file.toLower(); const QString lowerFile = file.toLower();
const QStringList identical = { const QStringList identical = {
"android", "cmaketools", "debuggers", "devices", "profiles", "qtversions", "toolchains", "abi" "android", "cmaketools", "debuggers", "devices", "profiles", "qtversions", "toolchains", "abi"
}; };
if (lowerFile == "cmake") if (lowerFile == "cmake")
result = result.pathAppended("cmaketools"); result += "/cmaketools";
else if (lowerFile == "kits") else if (lowerFile == "kits")
result = result.pathAppended("profiles"); result += "/profiles";
else if (lowerFile == "qtversions") else if (lowerFile == "qtversions")
result = result.pathAppended("qtversion"); result += "/qtversion";
else if (identical.contains(lowerFile)) else if (identical.contains(lowerFile))
result = result.pathAppended(lowerFile); result += '/' + lowerFile;
else else
result = result.pathAppended(file); // handle arbitrary file names not known yet result += '/' + file; // handle arbitrary file names not known yet
return result.stringAppended(".xml"); return result += ".xml";
} }

View File

@@ -3,7 +3,7 @@
#pragma once #pragma once
#include <utils/fileutils.h> #include <QString>
class Operation; class Operation;
@@ -13,8 +13,8 @@ public:
Settings(); Settings();
static Settings *instance(); static Settings *instance();
Utils::FilePath getPath(const QString &file); QString getPath(const QString &file);
Utils::FilePath sdkPath; QString sdkPath;
Operation *operation = nullptr; Operation *operation = nullptr;
}; };