diff --git a/src/tools/sdktool/CMakeLists.txt b/src/tools/sdktool/CMakeLists.txt index 39c7a1b5446..937aa04087a 100644 --- a/src/tools/sdktool/CMakeLists.txt +++ b/src/tools/sdktool/CMakeLists.txt @@ -66,33 +66,7 @@ add_qtc_library(sdktoolLib rmqtoperation.cpp rmqtoperation.h rmtoolchainoperation.cpp rmtoolchainoperation.h settings.cpp settings.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} + sdkpersistentsettings.cpp sdkpersistentsettings.h ) if (MSVC) diff --git a/src/tools/sdktool/addcmakeoperation.cpp b/src/tools/sdktool/addcmakeoperation.cpp index 751db965e82..f97fcef2a68 100644 --- a/src/tools/sdktool/addcmakeoperation.cpp +++ b/src/tools/sdktool/addcmakeoperation.cpp @@ -4,13 +4,10 @@ #include "addcmakeoperation.h" #include "addkeysoperation.h" -#include "findkeyoperation.h" #include "findvalueoperation.h" #include "getoperation.h" #include "rmkeysoperation.h" -#include "settings.h" - #ifdef WITH_TESTS #include #endif @@ -205,7 +202,7 @@ QVariantMap AddCMakeData::addCMake(const QVariantMap &map) const data << KeyValuePair({cm, ID_KEY}, QVariant(m_id)); data << KeyValuePair({cm, DISPLAYNAME_KEY}, QVariant(m_displayName)); 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; for (const KeyValuePair &pair : std::as_const(m_extra)) extraList << KeyValuePair(QStringList({cm}) << pair.key, pair.value); diff --git a/src/tools/sdktool/adddebuggeroperation.cpp b/src/tools/sdktool/adddebuggeroperation.cpp index c11b20ec81d..69de58be05e 100644 --- a/src/tools/sdktool/adddebuggeroperation.cpp +++ b/src/tools/sdktool/adddebuggeroperation.cpp @@ -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(ENGINE_TYPE), QVariant(m_engine)); - data << KeyValuePair(QStringList() << debugger << QLatin1String(BINARY), - Utils::FilePath::fromUserInput(m_binary).toSettings()); + data << KeyValuePair(QStringList() << debugger << QLatin1String(BINARY), QVariant(m_binary)); data << KeyValuePair(QStringList() << QLatin1String(COUNT), QVariant(count + 1)); diff --git a/src/tools/sdktool/addkitoperation.cpp b/src/tools/sdktool/addkitoperation.cpp index 14569ba8398..6ed2feea365 100644 --- a/src/tools/sdktool/addkitoperation.cpp +++ b/src/tools/sdktool/addkitoperation.cpp @@ -685,8 +685,7 @@ QVariantMap AddKitData::addKit(const QVariantMap &map, if (!m_buildDevice.isNull()) data << KeyValuePair({kit, DATA, BUILDDEVICE_ID}, QVariant(m_buildDevice)); if (!m_sysRoot.isNull()) - data << KeyValuePair({kit, DATA, SYSROOT}, - Utils::FilePath::fromUserInput(m_sysRoot).toSettings()); + data << KeyValuePair({kit, DATA, SYSROOT}, QVariant(QDir::cleanPath(m_sysRoot))); for (auto i = m_tcs.constBegin(); i != m_tcs.constEnd(); ++i) data << KeyValuePair({kit, DATA, TOOLCHAIN, i.key()}, QVariant(i.value())); if (!qtId.isNull()) diff --git a/src/tools/sdktool/addqtoperation.cpp b/src/tools/sdktool/addqtoperation.cpp index 875e59d9143..69117eb53a0 100644 --- a/src/tools/sdktool/addqtoperation.cpp +++ b/src/tools/sdktool/addqtoperation.cpp @@ -11,8 +11,6 @@ #include "settings.h" -#include - #ifdef WITH_TESTS #include #endif @@ -22,8 +20,6 @@ Q_LOGGING_CATEGORY(log, "qtc.sdktool.operations.addqt", QtWarningMsg) -using namespace Utils; - // Qt version file stuff: const char PREFIX[] = "QtVersion."; const char VERSION[] = "Version"; @@ -282,7 +278,7 @@ QVariantMap AddQtData::addQt(const QVariantMap &map) const const QString qt = QString::fromLatin1(PREFIX) + QString::number(versionCount); // Sanitize qmake path: - FilePath saneQmake = FilePath::fromUserInput(m_qmake).cleanPath(); + QString saneQmake = QDir::cleanPath(m_qmake); // insert 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(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 << ABIS, QVariant(m_abis)); diff --git a/src/tools/sdktool/addtoolchainoperation.cpp b/src/tools/sdktool/addtoolchainoperation.cpp index 1290711e566..74f4965cf87 100644 --- a/src/tools/sdktool/addtoolchainoperation.cpp +++ b/src/tools/sdktool/addtoolchainoperation.cpp @@ -4,13 +4,10 @@ #include "addtoolchainoperation.h" #include "addkeysoperation.h" -#include "findkeyoperation.h" #include "findvalueoperation.h" #include "getoperation.h" #include "rmkeysoperation.h" -#include "settings.h" - #include #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, DISPLAYNAME}, QVariant(m_displayName)); 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)); QVariantList abis; const QStringList abiStrings = m_supportedAbis.split(','); diff --git a/src/tools/sdktool/main.cpp b/src/tools/sdktool/main.cpp index 4f28b64b9e5..f53cd306a1c 100644 --- a/src/tools/sdktool/main.cpp +++ b/src/tools/sdktool/main.cpp @@ -30,6 +30,7 @@ #include #include +#include #include #include @@ -60,10 +61,7 @@ void printHelp(const std::vector> &operations) std::cout << " --sdkpath=PATH|-s PATH Set the path to the SDK files" << std::endl << std::endl; std::cout << "Default sdkpath is \"" - << qPrintable(QDir::cleanPath( - Utils::FilePath::fromString(QCoreApplication::applicationDirPath()) - .pathAppended(DATA_PATH) - .toUserOutput())) + << qPrintable(QDir::cleanPath(QCoreApplication::applicationDirPath() + '/' + DATA_PATH)) << "\"" << std::endl << std::endl; @@ -105,7 +103,7 @@ int parseArguments(const QStringList &args, Settings *s, // sdkpath if (current.startsWith(QLatin1String("--sdkpath="))) { - s->sdkPath = Utils::FilePath::fromString(current.mid(10)); + s->sdkPath = current.mid(10); continue; } if (current == QLatin1String("-s")) { @@ -114,7 +112,7 @@ int parseArguments(const QStringList &args, Settings *s, printHelp(operations); return 1; } - s->sdkPath = Utils::FilePath::fromString(next); + s->sdkPath = next; ++i; // skip next; continue; } diff --git a/src/tools/sdktool/operation.cpp b/src/tools/sdktool/operation.cpp index ed673b9b780..7a81c3c7adf 100644 --- a/src/tools/sdktool/operation.cpp +++ b/src/tools/sdktool/operation.cpp @@ -4,8 +4,7 @@ #include "operation.h" #include "settings.h" - -#include +#include "sdkpersistentsettings.h" #include #include @@ -65,9 +64,9 @@ QVariantMap Operation::load(const QString &file) QVariantMap map; // Read values from original file: - Utils::FilePath path = Settings::instance()->getPath(file); - if (path.exists()) { - Utils::PersistentSettingsReader reader; + QString path = Settings::instance()->getPath(file); + if (QFileInfo::exists(path)) { + SdkPersistentSettingsReader reader; if (!reader.load(path)) return QVariantMap(); map = reader.restoreValues(); @@ -78,32 +77,32 @@ QVariantMap Operation::load(const QString &file) 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()) { std::cerr << "Error: No path found for " << qPrintable(file) << "." << std::endl; return false; } - Utils::FilePath dirName = path.parentDir(); - QDir dir(dirName.toString()); + QString dirName = QDir::cleanPath(path + "/.."); + QDir dir(dirName); 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; return false; } - Utils::PersistentSettingsWriter writer(path, QLatin1String("QtCreator") + SdkPersistentSettingsWriter writer(path, QLatin1String("QtCreator") + file[0].toUpper() + file.mid(1)); QString 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; return false; } - if (!path.setPermissions(QFile::ReadOwner | QFile::WriteOwner + if (!QFile(path).setPermissions(QFile::ReadOwner | QFile::WriteOwner | 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; return false; } diff --git a/src/tools/sdktool/operation.h b/src/tools/sdktool/operation.h index 1b43eb7b441..886ed386675 100644 --- a/src/tools/sdktool/operation.h +++ b/src/tools/sdktool/operation.h @@ -3,8 +3,6 @@ #pragma once -#include - #include #include diff --git a/src/tools/sdktool/sdkpersistentsettings.cpp b/src/tools/sdktool/sdkpersistentsettings.cpp new file mode 100644 index 00000000000..db5ba21a34d --- /dev/null +++ b/src/tools/sdktool/sdkpersistentsettings.cpp @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef Q_OS_WIN +# include +# include +#else +# include +# include +#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 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(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(_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()) + | ((mask & S_IWUSR) ? QFile::WriteOwner : QFlags()) + | ((mask & S_IXUSR) ? QFile::ExeOwner : QFlags()) + | ((mask & S_IRGRP) ? QFile::ReadGroup : QFlags()) + | ((mask & S_IWGRP) ? QFile::WriteGroup : QFlags()) + | ((mask & S_IXGRP) ? QFile::ExeGroup : QFlags()) + | ((mask & S_IROTH) ? QFile::ReadOther : QFlags()) + | ((mask & S_IWOTH) ? QFile::WriteOther : QFlags()) + | ((mask & S_IXOTH) ? QFile::ExeOther : QFlags()); +#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 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(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 + + + ProjectExplorer.Project.ActiveTarget + 0 + + + ProjectExplorer.Project.EditorSettings + + true + + + \endcode + + When parsing the structure, a parse stack of ParseValueStackEntry is used for each + 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(&myCustomTypeFromString); + \endcode + + When entering a value element ( \c / \c , \c ), 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 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 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(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(variant.type())) { + case static_cast(QVariant::StringList): + case static_cast(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 list = variant.toList(); + for (const QVariant &var : list) + writeVariantValue(w, ctx, var); + w.writeEndElement(); + break; + } + case static_cast(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(QMetaType::QObjectStar): // ignore QObjects! + case static_cast(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("')); + 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; +} diff --git a/src/tools/sdktool/sdkpersistentsettings.h b/src/tools/sdktool/sdkpersistentsettings.h new file mode 100644 index 00000000000..691cf0e08a7 --- /dev/null +++ b/src/tools/sdktool/sdkpersistentsettings.h @@ -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 + +class SdkPersistentSettingsReader +{ +public: + SdkPersistentSettingsReader(); + QVariant restoreValue(const QString &variable, const QVariant &defaultValue = QVariant()) const; + QVariantMap restoreValues() const; + bool load(const QString &fileName); + +private: + QMap 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 m_savedData; +}; diff --git a/src/tools/sdktool/sdktoollib.qbs b/src/tools/sdktool/sdktoollib.qbs index 60823390832..ca40590d8d1 100644 --- a/src/tools/sdktool/sdktoollib.qbs +++ b/src/tools/sdktool/sdktoollib.qbs @@ -85,34 +85,7 @@ QtcLibrary { "rmtoolchainoperation.h", "settings.cpp", "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", - ] - } } diff --git a/src/tools/sdktool/settings.cpp b/src/tools/sdktool/settings.cpp index d87df504f6e..53a1af0565f 100644 --- a/src/tools/sdktool/settings.cpp +++ b/src/tools/sdktool/settings.cpp @@ -2,11 +2,11 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "settings.h" -#include "operation.h" #include #include +#include static Settings *m_instance = nullptr; @@ -21,28 +21,28 @@ Settings::Settings() m_instance = this; // autodetect sdk dir: - sdkPath = Utils::FilePath::fromUserInput(QCoreApplication::applicationDirPath()) - .pathAppended(DATA_PATH).cleanPath() - .pathAppended(Core::Constants::IDE_SETTINGSVARIANT_STR) - .pathAppended(Core::Constants::IDE_ID); + sdkPath = QDir::cleanPath(QCoreApplication::applicationDirPath() + + '/' + DATA_PATH + + '/' + Core::Constants::IDE_SETTINGSVARIANT_STR + + '/' + 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 QStringList identical = { "android", "cmaketools", "debuggers", "devices", "profiles", "qtversions", "toolchains", "abi" }; if (lowerFile == "cmake") - result = result.pathAppended("cmaketools"); + result += "/cmaketools"; else if (lowerFile == "kits") - result = result.pathAppended("profiles"); + result += "/profiles"; else if (lowerFile == "qtversions") - result = result.pathAppended("qtversion"); + result += "/qtversion"; else if (identical.contains(lowerFile)) - result = result.pathAppended(lowerFile); + result += '/' + lowerFile; else - result = result.pathAppended(file); // handle arbitrary file names not known yet - return result.stringAppended(".xml"); + result += '/' + file; // handle arbitrary file names not known yet + return result += ".xml"; } diff --git a/src/tools/sdktool/settings.h b/src/tools/sdktool/settings.h index b94c2c9ad0c..3b182e1c96f 100644 --- a/src/tools/sdktool/settings.h +++ b/src/tools/sdktool/settings.h @@ -3,7 +3,7 @@ #pragma once -#include +#include class Operation; @@ -13,8 +13,8 @@ public: Settings(); static Settings *instance(); - Utils::FilePath getPath(const QString &file); + QString getPath(const QString &file); - Utils::FilePath sdkPath; + QString sdkPath; Operation *operation = nullptr; };