From 7ef50ec70c672aeb1269b1860952f74742e707f0 Mon Sep 17 00:00:00 2001 From: Christian Stenger Date: Tue, 20 Jul 2021 14:27:11 +0200 Subject: [PATCH 01/20] Docker: Use shell to exit container ..and only fallback to calling docker command if this fails. Change-Id: I854499f7df8605ffe444c43bc6d1dc0155183627 Reviewed-by: hjk --- src/plugins/docker/dockerdevice.cpp | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/plugins/docker/dockerdevice.cpp b/src/plugins/docker/dockerdevice.cpp index 6d4c08b5da8..6abe4f16257 100644 --- a/src/plugins/docker/dockerdevice.cpp +++ b/src/plugins/docker/dockerdevice.cpp @@ -304,7 +304,7 @@ public: }); } - ~DockerDevicePrivate() { delete m_shell; } + ~DockerDevicePrivate() { stopCurrentContainer(); } bool runInContainer(const CommandLine &cmd) const; @@ -745,6 +745,19 @@ void DockerDevicePrivate::stopCurrentContainer() if (m_container.isEmpty() || m_accessible == NoDaemon) return; + if (m_shell) { + m_shell->write("exit\n"); + m_shell->waitForFinished(2000); + if (m_shell->state() == QProcess::NotRunning) { + LOG("Clean exit via shell"); + m_container.clear(); + m_mergedDir.clear(); + delete m_shell; + m_shell = nullptr; + return; + } + } + QtcProcess proc; proc.setCommand({"docker", {"container", "stop", m_container}}); From 366b4c0199fd73bd586c6ecf92ca11315f597e76 Mon Sep 17 00:00:00 2001 From: Cristian Adam Date: Tue, 20 Jul 2021 19:58:05 +0200 Subject: [PATCH 02/20] CMakePM: Consider all the missing expandable CMake variables CMAKE_PREFIX_PATH and CMAKE_FIND_ROOT_PATH can have multiple values, and now all the values are taken into account. Ammends e1a68f259832ef6fb4e1446476a2ceb0b5644f90 which had only one value with all the items separated by semicolon. Change-Id: I88d98fbbf165e7e61d70ab4a4e84eb4de4cc9f82 Reviewed-by: Qt CI Bot Reviewed-by: hjk --- .../cmakeprojectmanager/cmakebuildsystem.cpp | 27 ++++++++++--------- 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/src/plugins/cmakeprojectmanager/cmakebuildsystem.cpp b/src/plugins/cmakeprojectmanager/cmakebuildsystem.cpp index aae63f2f41f..37743fceb59 100644 --- a/src/plugins/cmakeprojectmanager/cmakebuildsystem.cpp +++ b/src/plugins/cmakeprojectmanager/cmakebuildsystem.cpp @@ -1299,20 +1299,23 @@ void CMakeBuildSystem::updateInitialCMakeExpandableVars() }); if (it != cm.cend()) { - const QByteArray initialValue = CMakeConfigItem::expandedValueOf(kit(), var, initialConfig).toUtf8(); - const FilePath initialPath = FilePath::fromString(QString::fromUtf8(initialValue)); + const QByteArrayList initialValueList = CMakeConfigItem::expandedValueOf(kit(), var, initialConfig).toUtf8().split(';'); - const bool pathIsContained - = Utils::contains(it->value.split(';'), [samePath, initialPath](const QByteArray &p) { - return samePath(FilePath::fromString(QString::fromUtf8(p)), initialPath); - }); - if (!initialValue.isEmpty() && !pathIsContained) { - CMakeConfigItem item(*it); - item.value = initialValue; - item.value.append(";"); - item.value.append(it->value); + for (const auto &initialValue: initialValueList) { + const FilePath initialPath = FilePath::fromString(QString::fromUtf8(initialValue)); - config << item; + const bool pathIsContained + = Utils::contains(it->value.split(';'), [samePath, initialPath](const QByteArray &p) { + return samePath(FilePath::fromString(QString::fromUtf8(p)), initialPath); + }); + if (!initialValue.isEmpty() && !pathIsContained) { + CMakeConfigItem item(*it); + item.value = initialValue; + item.value.append(";"); + item.value.append(it->value); + + config << item; + } } } } From 9e015dbaf7d47d64abdd852dfde49fb9391b7c0e Mon Sep 17 00:00:00 2001 From: Christian Stenger Date: Tue, 20 Jul 2021 15:23:26 +0200 Subject: [PATCH 03/20] Docker: Let some commands run inside shell If there is no local access for the docker container we create lots of explicit docker calls which take time to be created and executed as this always fires up another connection to the container. Currently we expect the container to have a shell and we already have it in place, so re-use the shell to execute at least a couple of commands. This heavily increases the performance of these commands. Change-Id: Ic778a250a2b3b8c5ce2a8dd6b7fa8c532bc6d4bf Reviewed-by: hjk --- src/plugins/docker/dockerdevice.cpp | 73 +++++++++++++++++++++++------ 1 file changed, 58 insertions(+), 15 deletions(-) diff --git a/src/plugins/docker/dockerdevice.cpp b/src/plugins/docker/dockerdevice.cpp index 6abe4f16257..dfcf7687468 100644 --- a/src/plugins/docker/dockerdevice.cpp +++ b/src/plugins/docker/dockerdevice.cpp @@ -69,6 +69,7 @@ #include #include #include +#include #include #include #include @@ -307,6 +308,8 @@ public: ~DockerDevicePrivate() { stopCurrentContainer(); } bool runInContainer(const CommandLine &cmd) const; + bool runInShell(const CommandLine &cmd) const; + QString outputForRunInShell(const CommandLine &cmd) const; void tryCreateLocalFileAccess(); @@ -318,6 +321,7 @@ public: // For local file access QPointer m_shell; + mutable QMutex m_shellMutex; QString m_container; QString m_mergedDir; QFileSystemWatcher m_mergedDirWatcher; @@ -746,6 +750,7 @@ void DockerDevicePrivate::stopCurrentContainer() return; if (m_shell) { + QMutexLocker l(&m_shellMutex); m_shell->write("exit\n"); m_shell->waitForFinished(2000); if (m_shell->state() == QProcess::NotRunning) { @@ -1032,7 +1037,7 @@ bool DockerDevice::isExecutableFile(const FilePath &filePath) const return res; } const QString path = filePath.path(); - return d->runInContainer({"test", {"-x", path}}); + return d->runInShell({"test", {"-x", path}}); } bool DockerDevice::isReadableFile(const FilePath &filePath) const @@ -1046,7 +1051,7 @@ bool DockerDevice::isReadableFile(const FilePath &filePath) const return res; } const QString path = filePath.path(); - return d->runInContainer({"test", {"-r", path, "-a", "-f", path}}); + return d->runInShell({"test", {"-r", path, "-a", "-f", path}}); } bool DockerDevice::isWritableFile(const Utils::FilePath &filePath) const @@ -1060,7 +1065,7 @@ bool DockerDevice::isWritableFile(const Utils::FilePath &filePath) const return res; } const QString path = filePath.path(); - return d->runInContainer({"test", {"-w", path, "-a", "-f", path}}); + return d->runInShell({"test", {"-w", path, "-a", "-f", path}}); } bool DockerDevice::isReadableDirectory(const FilePath &filePath) const @@ -1074,7 +1079,7 @@ bool DockerDevice::isReadableDirectory(const FilePath &filePath) const return res; } const QString path = filePath.path(); - return d->runInContainer({"test", {"-r", path, "-a", "-d", path}}); + return d->runInShell({"test", {"-r", path, "-a", "-d", path}}); } bool DockerDevice::isWritableDirectory(const FilePath &filePath) const @@ -1088,7 +1093,7 @@ bool DockerDevice::isWritableDirectory(const FilePath &filePath) const return res; } const QString path = filePath.path(); - return d->runInContainer({"test", {"-w", path, "-a", "-d", path}}); + return d->runInShell({"test", {"-w", path, "-a", "-d", path}}); } bool DockerDevice::isFile(const FilePath &filePath) const @@ -1102,7 +1107,7 @@ bool DockerDevice::isFile(const FilePath &filePath) const return res; } const QString path = filePath.path(); - return d->runInContainer({"test", {"-f", path}}); + return d->runInShell({"test", {"-f", path}}); } bool DockerDevice::isDirectory(const FilePath &filePath) const @@ -1116,7 +1121,7 @@ bool DockerDevice::isDirectory(const FilePath &filePath) const return res; } const QString path = filePath.path(); - return d->runInContainer({"test", {"-d", path}}); + return d->runInShell({"test", {"-d", path}}); } bool DockerDevice::createDirectory(const FilePath &filePath) const @@ -1144,7 +1149,7 @@ bool DockerDevice::exists(const FilePath &filePath) const return res; } const QString path = filePath.path(); - return d->runInContainer({"test", {"-e", path}}); + return d->runInShell({"test", {"-e", path}}); } bool DockerDevice::ensureExistingFile(const FilePath &filePath) const @@ -1158,7 +1163,7 @@ bool DockerDevice::ensureExistingFile(const FilePath &filePath) const return res; } const QString path = filePath.path(); - return d->runInContainer({"touch", {path}}); + return d->runInShell({"touch", {path}}); } bool DockerDevice::removeFile(const FilePath &filePath) const @@ -1271,12 +1276,8 @@ FilePaths DockerDevice::directoryEntries(const FilePath &filePath, }); } - QtcProcess proc; - proc.setCommand({"ls", {"-1", "-b", "--", filePath.path()}}); - runProcess(proc); - proc.waitForFinished(); - - QStringList entries = proc.stdOut().split('\n', Qt::SkipEmptyParts); + const QString output = d->outputForRunInShell({"ls", {"-1", "-b", "--", filePath.path()}}); + QStringList entries = output.split('\n', Qt::SkipEmptyParts); return FilePath::filterEntriesHelper(filePath, entries, nameFilters, filters, sort); } @@ -1388,6 +1389,48 @@ bool DockerDevicePrivate::runInContainer(const CommandLine &cmd) const return exitCode == 0; } +bool DockerDevicePrivate::runInShell(const CommandLine &cmd) const +{ + if (m_accessible == NoDaemon) + return false; + QTC_ASSERT(m_shell, return false); + QMutexLocker l(&m_shellMutex); + m_shell->readAllStandardOutput(); // clean possible left-overs + m_shell->write(cmd.toUserOutput().toUtf8() + "\necho $?\n"); + m_shell->waitForReadyRead(); + QByteArray output = m_shell->readAllStandardOutput(); + int result = output.toInt(); + LOG("Run command in shell:" << cmd.toUserOutput() << "result: " << output << " ==>" << result); + return result == 0; +} + +// generate hex value +static QByteArray randomHex() +{ + quint32 val = QRandomGenerator::global()->generate(); + return QString::number(val, 16).toUtf8(); +} + +QString DockerDevicePrivate::outputForRunInShell(const CommandLine &cmd) const +{ + if (m_accessible == NoDaemon) + return {}; + QTC_ASSERT(m_shell, return {}); + QMutexLocker l(&m_shellMutex); + m_shell->readAllStandardOutput(); // clean possible left-overs + const QByteArray markerWithNewLine("___QC_DOCKER_" + randomHex() + "_OUTPUT_MARKER___\n"); + m_shell->write(cmd.toUserOutput().toUtf8() + "\necho -n \"" + markerWithNewLine + "\"\n"); + QByteArray output; + while (!output.endsWith(markerWithNewLine)) { + m_shell->waitForReadyRead(); + output.append(m_shell->readAllStandardOutput()); + } + LOG("Run command in shell:" << cmd.toUserOutput() << "output size:" << output.size()); + if (QTC_GUARD(output.endsWith(markerWithNewLine))) + output.chop(markerWithNewLine.size()); + return QString::fromUtf8(output); +} + // Factory DockerDeviceFactory::DockerDeviceFactory() From 4895eb346affb520c11ac094e635aa1fe868a882 Mon Sep 17 00:00:00 2001 From: Christian Stenger Date: Mon, 19 Jul 2021 13:45:31 +0200 Subject: [PATCH 04/20] Docker: Implement symLinkTarget() for non-local access Change-Id: I895d7e190a556ee582e7c767e7045362e9a7adce Reviewed-by: hjk --- src/plugins/docker/dockerdevice.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/plugins/docker/dockerdevice.cpp b/src/plugins/docker/dockerdevice.cpp index dfcf7687468..0ba4e4149f5 100644 --- a/src/plugins/docker/dockerdevice.cpp +++ b/src/plugins/docker/dockerdevice.cpp @@ -1258,8 +1258,9 @@ FilePath DockerDevice::symLinkTarget(const FilePath &filePath) const return {}; return mapToGlobalPath(target); } - QTC_CHECK(false); - return {}; + + const QString output = d->outputForRunInShell({"readlink", {"-n", "-e", filePath.path()}}); + return output.isEmpty() ? FilePath() : filePath.withNewPath(output); } FilePaths DockerDevice::directoryEntries(const FilePath &filePath, From 6fef74a8cb067e69be63090949d1fd7fb081f24b Mon Sep 17 00:00:00 2001 From: Henning Gruendl Date: Fri, 16 Jul 2021 15:55:28 +0200 Subject: [PATCH 05/20] QmlDesigner: Add RGB und HSV picker * Add modes to color picker * Add color picker for RGBA mode * Add color picker for HSVA mode * Add luminance slider for RGBA mode Change-Id: I0bb1dbb67b7c18d156eee0d4e07cfa942162f832 Reviewed-by: Thomas Hartmann --- .../imports/HelperWidgets/ColorEditor.qml | 156 ++++++---- .../imports/HelperWidgets/ColorPicker.qml | 269 +++++++++++++----- .../imports/HelperWidgets/LuminanceSlider.qml | 121 ++++++++ .../imports/HelperWidgets/qmldir | 1 + 4 files changed, 426 insertions(+), 121 deletions(-) create mode 100644 share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/LuminanceSlider.qml diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/ColorEditor.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/ColorEditor.qml index 19746097d41..a34d7ad9bfe 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/ColorEditor.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/ColorEditor.qml @@ -413,7 +413,7 @@ SecondColumnLayout { id: transparentIndicator icon: StudioTheme.Constants.transparent pixelSize: StudioTheme.Values.myIconFontSize * 1.4 - tooltip: qsTr("Transparent TODO") + tooltip: qsTr("Transparent") onClicked: { colorPicker.alpha = 0 colorPicker.updateColor() @@ -575,22 +575,29 @@ SecondColumnLayout { onRightMouseButtonClicked: contextMenu.popup(colorPicker) onColorInvalidated: { - if (colorPicker.saturation > 0.0 && colorPicker.lightness > 0.0) { - hueSpinBox.value = colorPicker.hue + switch (colorPicker.mode) { + case ColorPicker.Mode.HSLA: + hslHueSpinBox.value = colorPicker.hue + hslSaturationSpinBox.value = colorPicker.saturationHSL + hslLightnessSpinBox.value = colorPicker.lightness + hslAlphaSpinBox.value = colorPicker.alpha + break + + case ColorPicker.Mode.RGBA: + redSpinBox.value = (colorPicker.color.r * 255) + greenSpinBox.value = (colorPicker.color.g * 255) + blueSpinBox.value = (colorPicker.color.b * 255) + rgbAlphaSpinBox.value = (colorPicker.alpha * 255) + break + + case ColorPicker.Mode.HSVA: + default: + hsvHueSpinBox.value = colorPicker.hue + hsvSaturationSpinBox.value = colorPicker.saturationHSV + hsvValueSpinBox.value = colorPicker.value + hsvAlphaSpinBox.value = colorPicker.alpha + break } - - if (colorPicker.lightness > 0.0) - saturationSpinBox.value = colorPicker.saturation - else - colorPicker.saturation = saturationSpinBox.value - - lightnessSpinBox.value = colorPicker.lightness - hslaAlphaSpinBox.value = colorPicker.alpha - - redSpinBox.value = (colorPicker.color.r * 255) - greenSpinBox.value = (colorPicker.color.g * 255) - blueSpinBox.value = (colorPicker.color.b * 255) - rgbaAlphaSpinBox.value = (colorPicker.alpha * 255) } } @@ -766,29 +773,22 @@ SecondColumnLayout { + 4 * StudioTheme.Values.colorEditorPopupSpinBoxWidth width: implicitWidth actionIndicatorVisible: false - model: ["RGBA", "HSLA"] - onActivated: { - switch (colorMode.currentText) { - case "RGBA": - rgbaRow.visible = true - hslaRow.visible = false - break - case "HSLA": - rgbaRow.visible = false - hslaRow.visible = true - break - default: - console.log("Unknown color mode selected.") - rgbaRow.visible = true - hslaRow.visible = false - } - } + textRole: "text" + valueRole: "value" + model: [ + { value: ColorPicker.Mode.HSVA, text: "HSVA" }, + { value: ColorPicker.Mode.RGBA, text: "RGBA" }, + { value: ColorPicker.Mode.HSLA, text: "HSLA" } + ] + + onActivated: colorPicker.mode = colorMode.currentValue } } RowLayout { id: rgbaRow + visible: colorPicker.mode === ColorPicker.Mode.RGBA Layout.fillWidth: true spacing: StudioTheme.Values.controlGap @@ -847,7 +847,7 @@ SecondColumnLayout { } DoubleSpinBox { - id: rgbaAlphaSpinBox + id: rgbAlphaSpinBox width: StudioTheme.Values.colorEditorPopupSpinBoxWidth stepSize: 1 @@ -856,7 +856,7 @@ SecondColumnLayout { decimals: 0 onValueModified: { - var tmp = rgbaAlphaSpinBox.value / 255.0 + var tmp = rgbAlphaSpinBox.value / 255.0 if (colorPicker.alpha !== tmp && !colorPicker.block) { colorPicker.alpha = tmp colorPicker.updateColor() @@ -868,49 +868,109 @@ SecondColumnLayout { RowLayout { id: hslaRow - visible: false + visible: colorPicker.mode === ColorPicker.Mode.HSLA Layout.fillWidth: true spacing: StudioTheme.Values.controlGap DoubleSpinBox { - id: hueSpinBox + id: hslHueSpinBox width: StudioTheme.Values.colorEditorPopupSpinBoxWidth onValueModified: { - if (colorPicker.hue !== hueSpinBox.value && !colorPicker.block) { - colorPicker.hue = hueSpinBox.value + if (colorPicker.hue !== hslHueSpinBox.value + && !colorPicker.block) { + colorPicker.hue = hslHueSpinBox.value colorPicker.updateColor() } } } DoubleSpinBox { - id: saturationSpinBox + id: hslSaturationSpinBox width: StudioTheme.Values.colorEditorPopupSpinBoxWidth onValueModified: { - if (colorPicker.saturation !== saturationSpinBox.value && !colorPicker.block) { - colorPicker.saturation = saturationSpinBox.value + if (colorPicker.saturationHSL !== hslSaturationSpinBox.value + && !colorPicker.block) { + colorPicker.saturationHSL = hslSaturationSpinBox.value colorPicker.updateColor() } } } DoubleSpinBox { - id: lightnessSpinBox + id: hslLightnessSpinBox width: StudioTheme.Values.colorEditorPopupSpinBoxWidth onValueModified: { - if (colorPicker.lightness !== lightnessSpinBox.value && !colorPicker.block) { - colorPicker.lightness = lightnessSpinBox.value + if (colorPicker.lightness !== hslLightnessSpinBox.value + && !colorPicker.block) { + colorPicker.lightness = hslLightnessSpinBox.value colorPicker.updateColor() } } } DoubleSpinBox { - id: hslaAlphaSpinBox + id: hslAlphaSpinBox width: StudioTheme.Values.colorEditorPopupSpinBoxWidth onValueModified: { - if (colorPicker.alpha !== hslaAlphaSpinBox.value && !colorPicker.block) { - colorPicker.alpha = hslaAlphaSpinBox.value + if (colorPicker.alpha !== hslAlphaSpinBox.value + && !colorPicker.block) { + colorPicker.alpha = hslAlphaSpinBox.value + colorPicker.updateColor() + } + } + } + } + + RowLayout { + id: hsvaRow + + visible: colorPicker.mode === ColorPicker.Mode.HSVA + Layout.fillWidth: true + spacing: StudioTheme.Values.controlGap + + DoubleSpinBox { + id: hsvHueSpinBox + width: StudioTheme.Values.colorEditorPopupSpinBoxWidth + onValueModified: { + if (colorPicker.hue !== hsvHueSpinBox.value + && !colorPicker.block) { + colorPicker.hue = hsvHueSpinBox.value + colorPicker.updateColor() + } + } + } + + DoubleSpinBox { + id: hsvSaturationSpinBox + width: StudioTheme.Values.colorEditorPopupSpinBoxWidth + onValueModified: { + if (colorPicker.saturationHSV !== hsvSaturationSpinBox.value + && !colorPicker.block) { + colorPicker.saturationHSV = hsvSaturationSpinBox.value + colorPicker.updateColor() + } + } + } + + DoubleSpinBox { + id: hsvValueSpinBox + width: StudioTheme.Values.colorEditorPopupSpinBoxWidth + onValueModified: { + if (colorPicker.value !== hsvValueSpinBox.value + && !colorPicker.block) { + colorPicker.value = hsvValueSpinBox.value + colorPicker.updateColor() + } + } + } + + DoubleSpinBox { + id: hsvAlphaSpinBox + width: StudioTheme.Values.colorEditorPopupSpinBoxWidth + onValueModified: { + if (colorPicker.alpha !== hsvAlphaSpinBox.value + && !colorPicker.block) { + colorPicker.alpha = hsvAlphaSpinBox.value colorPicker.updateColor() } } diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/ColorPicker.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/ColorPicker.qml index 07fc29a64d3..13f43f79955 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/ColorPicker.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/ColorPicker.qml @@ -29,15 +29,26 @@ import StudioTheme 1.0 as StudioTheme Column { id: root + enum Mode { + HSVA, + RGBA, + HSLA + } + + property int mode: ColorPicker.Mode.HSVA property color color - property real alpha: 1 property real hue: 0 - property real saturation: 0 + property real saturationHSL: 0 + property real saturationHSV: 0 property real lightness: 0 + property real value: 0 + + property real alpha: 1 + + property bool achromatic: false property int sliderMargins: 6 - property bool block: false signal updateColor @@ -46,30 +57,84 @@ Column { spacing: 10 - onAlphaChanged: invalidateColor() - onSaturationChanged: invalidateColor() - onLightnessChanged: invalidateColor() - onHueChanged: invalidateColor() - onColorChanged: { - var myAlpha = root.color.a - rgbToHsl(root.color) - root.alpha = myAlpha + onModeChanged: { + switch (root.mode) { + case ColorPicker.Mode.RGBA: + root.color = Qt.rgba(root.color.r, root.color.g, root.color.b, root.alpha) + break + case ColorPicker.Mode.HSLA: + root.color = Qt.hsla(root.hue, root.saturationHSL, root.lightness, root.alpha) + break + case ColorPicker.Mode.HSVA: + default: + root.color = Qt.hsva(root.hue, root.saturationHSV, root.value, root.alpha) + break + } + + gradientOverlay.requestPaint() } + onHueChanged: { + if (root.mode === ColorPicker.Mode.HSLA) + root.color.hslHue = root.hue + else + root.color.hsvHue = root.hue + } + onSaturationHSLChanged: { + root.color.hslSaturation = root.saturationHSL + invalidateColor() + } + onSaturationHSVChanged: { + root.color.hsvSaturation = root.saturationHSV + } + onLightnessChanged: { + root.color.hslLightness = root.lightness + } + onValueChanged: { + root.color.hsvValue = root.value + } + onAlphaChanged: invalidateColor() + onColorChanged: invalidateColor() + function invalidateColor() { if (root.block) return root.block = true - root.color = Qt.hsla(root.hue, - root.saturation, - root.lightness, - root.alpha) + if (root.color.hsvSaturation > 0.0 + && root.color.hsvValue > 0.0 + && root.color.hsvHue !== -1.0) + root.hue = root.color.hsvHue - if (root.saturation > 0.0 && root.lightness > 0.0) - hueSlider.value = root.hue + if (root.color.hslSaturation > 0.0 + && root.color.hslLightness > 0.0 + && root.color.hslHue !== -1.0) + root.hue = root.color.hslHue + if (root.color.hslLightness !== 0.0 && root.color.hslLightness !== 1.0 && !root.achromatic) + root.saturationHSL = root.color.hslSaturation + + if (root.color.hsvValue !== 0.0 && root.color.hsvValue !== 1.0 && !root.achromatic) + root.saturationHSV = root.color.hsvSaturation + + root.lightness = root.color.hslLightness + root.value = root.color.hsvValue + + if (root.color.hslLightness === 0.0 || root.color.hslLightness === 1.0 + || root.color.hsvValue === 0.0 || root.color.hsvValue === 1.0 + || root.color.hsvHue === -1.0 || root.color.hslHue === -1.0) + root.achromatic = true + else + root.achromatic = false + + if (root.mode === ColorPicker.Mode.HSLA) + root.color = Qt.hsla(root.hue, root.saturationHSL, root.lightness, root.alpha) + else + root.color = Qt.hsva(root.hue, root.saturationHSV, root.value, root.alpha) + + luminanceSlider.value = (1.0 - root.value) + hueSlider.value = root.hue opacitySlider.value = (1.0 - root.alpha) root.colorInvalidated() @@ -77,39 +142,51 @@ Column { root.block = false } - function rgbToHsl(color) { - var r = color.r - var g = color.g - var b = color.b + function drawHSVA(ctx) { + for (var row = 0; row < gradientOverlay.height; row++) { + var gradient = ctx.createLinearGradient(0, 0, gradientOverlay.width, 0) + var v = Math.abs(row - gradientOverlay.height) / gradientOverlay.height - var max = Math.max(r, g, b), min = Math.min(r, g, b) - var h, s, l = (max + min) / 2 + gradient.addColorStop(0, Qt.hsva(root.hue, 0, v, 1)) + gradient.addColorStop(1, Qt.hsva(root.hue, 1, v, 1)) - if (max === min) { - h = 0 - s = 0 - } else { - var d = max - min - s = l > 0.5 ? d / (2 - max - min) : d / (max + min) - switch (max) { - case r: h = (g - b) / d + (g < b ? 6 : 0); break; - case g: h = (b - r) / d + 2; break; - case b: h = (r - g) / d + 4; break; - } - - h /= 6 + ctx.fillStyle = gradient + ctx.fillRect(0, row, gradientOverlay.width, 1) } + } - root.block = true + function drawRGBA(ctx) { + var gradient = ctx.createLinearGradient(0, 0, gradientOverlay.width, 0) + gradient.addColorStop(0.000, Qt.rgba(1, 0, 0, 1)) + gradient.addColorStop(0.167, Qt.rgba(1, 1, 0, 1)) + gradient.addColorStop(0.333, Qt.rgba(0, 1, 0, 1)) + gradient.addColorStop(0.500, Qt.rgba(0, 1, 1, 1)) + gradient.addColorStop(0.667, Qt.rgba(0, 0, 1, 1)) + gradient.addColorStop(0.833, Qt.rgba(1, 0, 1, 1)) + gradient.addColorStop(1.000, Qt.rgba(1, 0, 0, 1)) - if (s > 0) - root.hue = h + ctx.fillStyle = gradient + ctx.fillRect(0, 0, gradientOverlay.width, gradientOverlay.height) - root.saturation = s - root.lightness = l + gradient = ctx.createLinearGradient(0, 0, 0, gradientOverlay.height) + gradient.addColorStop(0.000, Qt.rgba(0, 0, 0, 0)) + gradient.addColorStop(1.000, Qt.rgba(1, 1, 1, 1)) - root.block = false - invalidateColor() + ctx.fillStyle = gradient + ctx.fillRect(0, 0, gradientOverlay.width, gradientOverlay.height) + } + + function drawHSLA(ctx) { + for (var row = 0; row < gradientOverlay.height; row++) { + var gradient = ctx.createLinearGradient(0, 0, gradientOverlay.width, 0) + var l = Math.abs(row - gradientOverlay.height) / gradientOverlay.height + + gradient.addColorStop(0, Qt.hsla(root.hue, 0, l, 1)) + gradient.addColorStop(1, Qt.hsla(root.hue, 1, l, 1)) + + ctx.fillStyle = gradient + ctx.fillRect(0, row, gradientOverlay.width, 1) + } } Rectangle { @@ -133,26 +210,30 @@ Column { Canvas { id: gradientOverlay - property real hue: root.hue - anchors.fill: parent - opacity: root.alpha + opacity: root.color.a + + Connections { + target: root + function onHueChanged() { gradientOverlay.requestPaint() } + } - onHueChanged: requestPaint() onPaint: { var ctx = gradientOverlay.getContext('2d') ctx.save() ctx.clearRect(0, 0, gradientOverlay.width, gradientOverlay.height) - for (var row = 0; row < gradientOverlay.height; row++) { - var gradient = ctx.createLinearGradient(0, 0, gradientOverlay.width,0) - var l = Math.abs(row - gradientOverlay.height) / gradientOverlay.height - - gradient.addColorStop(0, Qt.hsla(gradientOverlay.hue, 0, l, 1)) - gradient.addColorStop(1, Qt.hsla(gradientOverlay.hue, 1, l, 1)) - - ctx.fillStyle = gradient - ctx.fillRect(0, row, gradientOverlay.width, 1) + switch (root.mode) { + case ColorPicker.Mode.RGBA: + root.drawRGBA(ctx) + break + case ColorPicker.Mode.HSLA: + root.drawHSLA(ctx) + break + case ColorPicker.Mode.HSVA: + default: + root.drawHSVA(ctx) + break } ctx.restore() @@ -162,25 +243,41 @@ Column { Canvas { id: pickerCross - property real cavnasSaturation: root.saturation - property real canvasLightness: root.lightness property color strokeStyle: "lightGray" opacity: 0.8 anchors.fill: parent antialiasing: true - onCavnasSaturationChanged: requestPaint(); - onCanvasLightnessChanged: requestPaint(); + Connections { + target: root + function onColorInvalidated() { pickerCross.requestPaint() } + function onColorChanged() { pickerCross.requestPaint() } + function onModeChanged() { pickerCross.requestPaint() } + } + onPaint: { var ctx = pickerCross.getContext('2d') - ctx.save() - ctx.clearRect(0, 0, pickerCross.width, pickerCross.height) - var yy = pickerCross.height -root.lightness * pickerCross.height - var xx = root.saturation * pickerCross.width + var yy, xx = 0 + + switch (root.mode) { + case ColorPicker.Mode.RGBA: + yy = pickerCross.height - root.saturationHSV * pickerCross.height + xx = root.hue * pickerCross.width + break + case ColorPicker.Mode.HSLA: + yy = pickerCross.height - root.lightness * pickerCross.height + xx = root.saturationHSL * pickerCross.width + break + case ColorPicker.Mode.HSVA: + default: + yy = pickerCross.height - root.value * pickerCross.height + xx = root.saturationHSV * pickerCross.width + break + } ctx.strokeStyle = pickerCross.strokeStyle ctx.lineWidth = 1 @@ -200,24 +297,37 @@ Column { } MouseArea { - id: mapMouseArea + id: mouseArea anchors.fill: parent preventStealing: true acceptedButtons: Qt.LeftButton | Qt.RightButton onPositionChanged: function(mouse) { - if (pressed && mouse.buttons === Qt.LeftButton) { + if (mouseArea.pressed && mouse.buttons === Qt.LeftButton) { var xx = Math.max(0, Math.min(mouse.x, parent.width)) var yy = Math.max(0, Math.min(mouse.y, parent.height)) - root.lightness = 1.0 - yy / parent.height - root.saturation = xx / parent.width + switch (root.mode) { + case ColorPicker.Mode.RGBA: + root.saturationHSV = 1.0 - yy / parent.height + root.hue = xx / parent.width + break + case ColorPicker.Mode.HSLA: + root.saturationHSL = xx / parent.width + root.lightness = 1.0 - yy / parent.height + break + case ColorPicker.Mode.HSVA: + default: + root.saturationHSV = xx / parent.width + root.value = 1.0 - yy / parent.height + break + } } } onPressed: function(mouse) { if (mouse.button === Qt.LeftButton) - positionChanged(mouse) + mouseArea.positionChanged(mouse) } onReleased: function(mouse) { if (mouse.button === Qt.LeftButton) @@ -233,10 +343,23 @@ Column { HueSlider { id: hueSlider + visible: root.mode !== ColorPicker.Mode.RGBA width: parent.width onValueChanged: { - if (root.hue !== value) - root.hue = value + if (root.hue !== hueSlider.value) + root.hue = hueSlider.value + } + onClicked: root.updateColor() + } + + LuminanceSlider { + id: luminanceSlider + visible: root.mode === ColorPicker.Mode.RGBA + width: parent.width + color: Qt.hsva(root.hue, root.color.hsvSaturation, 1, 1) + onValueChanged: { + if (root.value !== luminanceSlider.value) + root.value = (1.0 - luminanceSlider.value) } onClicked: root.updateColor() } @@ -246,8 +369,8 @@ Column { width: parent.width color: Qt.rgba(root.color.r, root.color.g, root.color.b, 1) onValueChanged: { - if (root.alpha !== value) - root.alpha = (1.0 - value) + if (root.alpha !== opacitySlider.value) + root.alpha = (1.0 - opacitySlider.value) } onClicked: root.updateColor() } diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/LuminanceSlider.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/LuminanceSlider.qml new file mode 100644 index 00000000000..a1fe726a9cd --- /dev/null +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/LuminanceSlider.qml @@ -0,0 +1,121 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +import QtQuick 2.15 +import StudioTheme 1.0 as StudioTheme + +Item { + id: root + + property real value: 1 + property real minimum: 0 + property real maximum: 1 + property bool pressed: mouseArea.pressed + property bool integer: false + property color color + + signal clicked + + height: StudioTheme.Values.hueSliderHeight + + function updatePos() { + if (root.maximum > root.minimum) { + var pos = (track.width - handle.width) * (root.value - root.minimum) / (root.maximum - root.minimum) + return Math.min(Math.max(pos, 0), track.width - handle.width) + } else { + return 0 + } + } + + Item { + id: track + + width: parent.width + height: parent.height + + Image { + id: checkerboard + anchors.fill: parent + source: "images/checkers.png" + fillMode: Image.Tile + } + + Rectangle { + anchors.fill: parent + border.color: StudioTheme.Values.themeControlOutline + border.width: StudioTheme.Values.border + + gradient: Gradient { + orientation: Gradient.Horizontal + GradientStop { position: 0.000; color: root.color } + GradientStop { position: 1.000; color: "black" } + } + } + + Rectangle { + id: handle + width: StudioTheme.Values.hueSliderHandleWidth + height: track.height - 4 + anchors.verticalCenter: parent.verticalCenter + smooth: true + color: "transparent" + radius: 2 + border.color: "black" + border.width: 1 + x: root.updatePos() + y: 2 + z: 1 + + Rectangle { + anchors.fill: parent + anchors.margins: 1 + color: "transparent" + radius: 1 + border.color: "white" + border.width: 1 + } + } + + MouseArea { + id: mouseArea + anchors.fill: parent + preventStealing: true + + function calculateValue() { + var handleX = Math.max(0, Math.min(mouseArea.mouseX, mouseArea.width)) + var realValue = (root.maximum - root.minimum) * handleX / mouseArea.width + root.minimum + root.value = root.integer ? Math.round(realValue) : realValue + } + + onPressed: calculateValue() + onReleased: root.clicked() + onPositionChanged: { + if (pressed) + calculateValue() + } + } + + } +} diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/qmldir b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/qmldir index 13e7de2f885..c0ad517e36c 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/qmldir +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/qmldir @@ -45,6 +45,7 @@ Label 2.0 Label.qml LineEdit 2.0 LineEdit.qml LinkIndicator2D 2.0 LinkIndicator2D.qml ListViewComboBox 2.0 ListViewComboBox.qml +LuminanceSlider 2.0 LuminanceSlider.qml MarginSection 2.0 MarginSection.qml MultiIconLabel 2.0 MultiIconLabel.qml OpacitySlider 2.0 OpacitySlider.qml From 8c1edf2be8beb5501375b6c32acb6377e2bc9306 Mon Sep 17 00:00:00 2001 From: hjk Date: Fri, 16 Jul 2021 13:05:40 +0200 Subject: [PATCH 06/20] Docker: Juggle messages on the Daemon state button around Change-Id: I64cef2458e475a8edd796d6a7ca5f3feb8bd0ac0 Reviewed-by: Christian Stenger --- src/plugins/docker/dockerdevice.cpp | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/plugins/docker/dockerdevice.cpp b/src/plugins/docker/dockerdevice.cpp index 0ba4e4149f5..65180deecd2 100644 --- a/src/plugins/docker/dockerdevice.cpp +++ b/src/plugins/docker/dockerdevice.cpp @@ -363,12 +363,15 @@ public: auto daemonStateLabel = new QLabel(tr("Daemon state:")); m_daemonReset = new QToolButton; m_daemonReset->setIcon(Icons::INFO.icon()); - m_daemonReset->setToolTip(tr("Daemon state not evaluated.")); + m_daemonReset->setToolTip(tr("Clear detected daemon state. " + "It will be automatically re-evaluated next time an access is needed.")); + + m_daemonState = new QLabel(tr("Daemon state not evaluated.")); connect(m_daemonReset, &QToolButton::clicked, this, [this, dockerDevice] { dockerDevice->resetDaemonState(); m_daemonReset->setIcon(Icons::INFO.icon()); - m_daemonReset->setToolTip(tr("Daemon state not evaluated.")); + m_daemonState->setText(tr("Daemon state not evaluated.")); }); m_runAsOutsideUser = new QCheckBox(tr("Run as outside user")); @@ -407,10 +410,11 @@ public: if (!dockerDevice->isDaemonRunning()) { logView->append(tr("Docker daemon appears to be not running.")); - m_daemonReset->setToolTip(tr("Daemon not running. Push to reset the state.")); + m_daemonState->setText(tr("Docker daemon not running.")); m_daemonReset->setIcon(Icons::CRITICAL.icon()); } else { - m_daemonReset->setToolTip(tr("Docker daemon running.")); + logView->append(tr("Docker daemon appears to be running.")); + m_daemonState->setText(tr("Docker daemon running.")); m_daemonReset->setIcon(Icons::OK.icon()); } @@ -431,7 +435,7 @@ public: Form { idLabel, m_idLineEdit, Break(), repoLabel, m_repoLineEdit, Break(), - daemonStateLabel, m_daemonReset, Break(), + daemonStateLabel, m_daemonReset, m_daemonState, Break(), m_runAsOutsideUser, Break(), tr("Paths to mount:"), m_pathsLineEdit, Break(), Column { @@ -449,6 +453,7 @@ private: QLineEdit *m_idLineEdit; QLineEdit *m_repoLineEdit; QToolButton *m_daemonReset; + QLabel *m_daemonState; QCheckBox *m_runAsOutsideUser; QLineEdit *m_pathsLineEdit; From 2dafeadd0c6fffb0c2aec9cde066ba44f347126c Mon Sep 17 00:00:00 2001 From: hjk Date: Fri, 16 Jul 2021 07:42:59 +0200 Subject: [PATCH 07/20] Docker: Enable DockerDevice::removeRecursively() With some half-baked sanity checks. Change-Id: Ic3b9977f2bb8218105c5b7dba26df5cb933840c6 Reviewed-by: Christian Stenger --- src/plugins/docker/dockerdevice.cpp | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/plugins/docker/dockerdevice.cpp b/src/plugins/docker/dockerdevice.cpp index 65180deecd2..35df348c33a 100644 --- a/src/plugins/docker/dockerdevice.cpp +++ b/src/plugins/docker/dockerdevice.cpp @@ -1195,9 +1195,15 @@ bool DockerDevice::removeRecursively(const FilePath &filePath) const LOG("Remove recursively? " << filePath.toUserOutput() << localAccess.toUserOutput() << res); return res; } -// Open this up only when really needed. -// return d->runInContainer({"rm", "-rf", {filePath.path()}}); - return false; + + const QString path = filePath.cleanPath().path(); + // We are expecting this only to be called in a context of build directories or similar. + // Chicken out in some cases that _might_ be user code errors. + QTC_ASSERT(path.startsWith('/'), return false); + const int levelsNeeded = path.startsWith("/home/" ? 4 : 3); + QTC_ASSERT(path.count('/') >= levelsNeeded, return false); + + return d->runInContainer({"rm", {"-rf", "--", path}}); } bool DockerDevice::copyFile(const FilePath &filePath, const FilePath &target) const From 9d08e5f173d968b9c54ce8ee2f17d4a07f6bf3cb Mon Sep 17 00:00:00 2001 From: hjk Date: Fri, 16 Jul 2021 10:59:03 +0200 Subject: [PATCH 08/20] Utils: Keep write channel open by default if we know we'll use it Change-Id: I4591f3e7e3b33ef6ea43d6aa9a5c9246c8bfd2ab Reviewed-by: Christian Stenger --- src/libs/utils/qtcprocess.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libs/utils/qtcprocess.cpp b/src/libs/utils/qtcprocess.cpp index 94c23921bd7..4c55a728524 100644 --- a/src/libs/utils/qtcprocess.cpp +++ b/src/libs/utils/qtcprocess.cpp @@ -984,6 +984,7 @@ void QtcProcess::setExitCodeInterpreter(const ExitCodeInterpreter &interpreter) void QtcProcess::setWriteData(const QByteArray &writeData) { d->m_writeData = writeData; + setKeepWriteChannelOpen(); } #ifdef QT_GUI_LIB From 41355d7cd7a51a3096ae9ae315e7748949579e0d Mon Sep 17 00:00:00 2001 From: hjk Date: Fri, 16 Jul 2021 08:34:04 +0200 Subject: [PATCH 09/20] Docker: Implement DockerDevice::writeFileContents() Change-Id: I686eb6da5ec0a59da3b8c5e32e1ff2f9fa9621b2 Reviewed-by: Christian Stenger --- src/plugins/docker/dockerdevice.cpp | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/src/plugins/docker/dockerdevice.cpp b/src/plugins/docker/dockerdevice.cpp index 35df348c33a..c09d2e38aea 100644 --- a/src/plugins/docker/dockerdevice.cpp +++ b/src/plugins/docker/dockerdevice.cpp @@ -1317,15 +1317,35 @@ QByteArray DockerDevice::fileContents(const FilePath &filePath, qint64 limit, qi return output; } -bool DockerDevice::writeFileContents(const Utils::FilePath &filePath, const QByteArray &data) const +bool DockerDevice::writeFileContents(const FilePath &filePath, const QByteArray &data) const { QTC_ASSERT(handlesFile(filePath), return {}); tryCreateLocalFileAccess(); if (hasLocalFileAccess()) return mapToLocalAccess(filePath).writeFileContents(data); - QTC_CHECK(false); // FIXME: Implement - return {}; +// This following would be the generic Unix solution. +// But it doesn't pass input. FIXME: Why? +// QtcProcess proc; +// proc.setCommand({"dd", {"of=" + filePath.path()}}); +// proc.setWriteData(data); +// runProcess(proc); +// proc.waitForFinished(); + + TemporaryFile tempFile("dockertransport-XXXXXX"); + tempFile.open(); + tempFile.write(data); + + const QString tempName = tempFile.fileName(); + tempFile.close(); + + CommandLine cmd{"docker", {"cp", tempName, d->m_container + ':' + filePath.path()}}; + + QtcProcess proc; + proc.setCommand(cmd); + proc.runBlocking(); + + return proc.exitCode() == 0; } void DockerDevice::runProcess(QtcProcess &process) const From 7aef03134d90c1835e556153d687abe991f00bb9 Mon Sep 17 00:00:00 2001 From: hjk Date: Fri, 16 Jul 2021 10:59:45 +0200 Subject: [PATCH 10/20] Utils: Move writeData handling to QtcProcess::start() So that this doesn't only apply to runBlocking() Change-Id: I6ef7c72e13a5e214c7476ede942662a9893c843c Reviewed-by: Christian Stenger --- src/libs/utils/qtcprocess.cpp | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/src/libs/utils/qtcprocess.cpp b/src/libs/utils/qtcprocess.cpp index 4c55a728524..dc94c31415b 100644 --- a/src/libs/utils/qtcprocess.cpp +++ b/src/libs/utils/qtcprocess.cpp @@ -317,7 +317,14 @@ void QtcProcess::start() { d->clearForRun(); - QTC_CHECK(d->m_writeData.isEmpty()); // FIXME: Use it. + if (!d->m_writeData.isEmpty()) { + connect(d->m_process, &QProcess::started, this, [this] { + const qint64 bytesWritten = write(d->m_writeData); + QTC_CHECK(bytesWritten == d->m_writeData.size()); + d->m_process->waitForBytesWritten(); + closeWriteChannel(); // FIXME: Is this good? + }); + } if (d->m_commandLine.executable().needsDevice()) { QTC_ASSERT(s_deviceHooks.startProcessHook, return); @@ -997,11 +1004,9 @@ static bool isGuiThread() void QtcProcess::runBlocking() { // FIXME: Implement properly + if (d->m_commandLine.executable().needsDevice()) { - - // writeData ? QtcProcess::start(); - waitForFinished(); return; }; @@ -1011,12 +1016,6 @@ void QtcProcess::runBlocking() ExecuteOnDestruction logResult([this] { qCDebug(processLog) << *this; }); if (d->m_processUserEvents) { - if (!d->m_writeData.isEmpty()) { - connect(d->m_process, &QProcess::started, this, [this] { - write(d->m_writeData); - closeWriteChannel(); - }); - } setOpenMode(d->m_writeData.isEmpty() ? QIODevice::ReadOnly : QIODevice::ReadWrite); QtcProcess::start(); From 3aa324ef1f9ffa602991defe851453b90100908b Mon Sep 17 00:00:00 2001 From: hjk Date: Tue, 6 Jul 2021 10:29:29 +0200 Subject: [PATCH 11/20] CMake: Make CMakeConfig a proper class Looks a bit more "object oriented" in my book. Change-Id: I6a3b1b4691ec1c7465f652608678e8f31e7e52a7 Reviewed-by: Cristian Adam --- .../cmakebuildconfiguration.cpp | 17 ++++--- .../cmakeprojectmanager/cmakebuildsystem.cpp | 12 ++--- .../cmakeprojectmanager/cmakeconfigitem.cpp | 21 ++++---- .../cmakeprojectmanager/cmakeconfigitem.h | 31 ++++++++---- .../cmakekitinformation.cpp | 14 +++--- .../cmakeprojectimporter.cpp | 49 ++++++++----------- .../cmakeprojectmanager/configmodel.cpp | 2 +- .../cmakeprojectmanager/fileapireader.cpp | 2 +- 8 files changed, 77 insertions(+), 71 deletions(-) diff --git a/src/plugins/cmakeprojectmanager/cmakebuildconfiguration.cpp b/src/plugins/cmakeprojectmanager/cmakebuildconfiguration.cpp index f2e6dbf9e44..2a1a27683b8 100644 --- a/src/plugins/cmakeprojectmanager/cmakebuildconfiguration.cpp +++ b/src/plugins/cmakeprojectmanager/cmakebuildconfiguration.cpp @@ -515,7 +515,7 @@ void CMakeBuildSettingsWidget::batchEditConfiguration() [expander](const QString &s) { return expander->expand(s); }); - const CMakeConfig config = CMakeConfigItem::itemsFromArguments(expandedLines); + const CMakeConfig config = CMakeConfig::fromArguments(expandedLines); m_configModel->setBatchEditConfiguration(config); }); @@ -1048,7 +1048,7 @@ bool CMakeBuildConfiguration::fromMap(const QVariantMap &map) }(); if (initialCMakeArguments().isEmpty()) { QStringList initialArgs = defaultInitialCMakeArguments(kit(), buildTypeName) - + Utils::transform(conf, [this](const CMakeConfigItem &i) { + + Utils::transform(conf.toList(), [this](const CMakeConfigItem &i) { return i.toArgument(macroExpander()); }); @@ -1110,7 +1110,8 @@ CMakeConfig CMakeBuildConfiguration::configurationChanges() const QStringList CMakeBuildConfiguration::configurationChangesArguments() const { - return Utils::transform(m_configurationChanges, [](const CMakeConfigItem &i) { return i.toArgument(); }); + return Utils::transform(m_configurationChanges.toList(), + [](const CMakeConfigItem &i) { return i.toArgument(); }); } QStringList CMakeBuildConfiguration::initialCMakeArguments() const @@ -1296,9 +1297,9 @@ BuildInfo CMakeBuildConfigurationFactory::createBuildInfo(BuildType buildType) BuildConfiguration::BuildType CMakeBuildConfiguration::buildType() const { - QByteArray cmakeBuildTypeName = CMakeConfigItem::valueOf("CMAKE_BUILD_TYPE", m_configurationFromCMake); + QByteArray cmakeBuildTypeName = m_configurationFromCMake.valueOf("CMAKE_BUILD_TYPE"); if (cmakeBuildTypeName.isEmpty()) { - QByteArray cmakeCfgTypes = CMakeConfigItem::valueOf("CMAKE_CONFIGURATION_TYPES", m_configurationFromCMake); + QByteArray cmakeCfgTypes = m_configurationFromCMake.valueOf("CMAKE_CONFIGURATION_TYPES"); if (!cmakeCfgTypes.isEmpty()) cmakeBuildTypeName = cmakeBuildType().toUtf8(); } @@ -1352,14 +1353,14 @@ QString CMakeBuildConfiguration::cmakeBuildType() const QString errorMessage; config = CMakeBuildSystem::parseCMakeCacheDotTxt(cmakeCacheTxt, &errorMessage); } else { - config = CMakeConfigItem::itemsFromArguments(initialCMakeArguments()); + config = CMakeConfig::fromArguments(initialCMakeArguments()); } } else if (!hasCMakeCache) { - config = CMakeConfigItem::itemsFromArguments(initialCMakeArguments()); + config = CMakeConfig::fromArguments(initialCMakeArguments()); } if (!config.isEmpty() && !isMultiConfig()) { - cmakeBuildType = CMakeConfigItem::stringValueOf("CMAKE_BUILD_TYPE", config); + cmakeBuildType = config.stringValueOf("CMAKE_BUILD_TYPE"); const_cast(this)->setCMakeBuildType(cmakeBuildType); } diff --git a/src/plugins/cmakeprojectmanager/cmakebuildsystem.cpp b/src/plugins/cmakeprojectmanager/cmakebuildsystem.cpp index 37743fceb59..463ebc4cb41 100644 --- a/src/plugins/cmakeprojectmanager/cmakebuildsystem.cpp +++ b/src/plugins/cmakeprojectmanager/cmakebuildsystem.cpp @@ -838,7 +838,7 @@ void CMakeBuildSystem::wireUpConnections() QString errorMessage; const CMakeConfig config = CMakeBuildSystem::parseCMakeCacheDotTxt(cmakeCacheTxt, &errorMessage); if (!config.isEmpty() && errorMessage.isEmpty()) { - QString cmakeBuildTypeName = CMakeConfigItem::stringValueOf("CMAKE_BUILD_TYPE", config); + QString cmakeBuildTypeName = config.stringValueOf("CMAKE_BUILD_TYPE"); cmakeBuildConfiguration()->setCMakeBuildType(cmakeBuildTypeName, true); } } @@ -1057,7 +1057,7 @@ CMakeConfig CMakeBuildSystem::parseCMakeCacheDotTxt(const Utils::FilePath &cache *errorMessage = tr("CMakeCache.txt file not found."); return {}; } - CMakeConfig result = CMakeConfigItem::itemsFromFile(cacheFile, errorMessage); + CMakeConfig result = CMakeConfig::fromFile(cacheFile, errorMessage); if (!errorMessage->isEmpty()) return {}; return result; @@ -1210,7 +1210,7 @@ void CMakeBuildSystem::updateQmlJSCodeModel(const QStringList &extraHeaderPaths, }; const CMakeConfig &cm = cmakeBuildConfiguration()->configurationFromCMake(); - addImports(CMakeConfigItem::stringValueOf("QML_IMPORT_PATH", cm)); + addImports(cm.stringValueOf("QML_IMPORT_PATH")); addImports(kit()->value(QtSupport::KitQmlImportPath::id()).toString()); for (const QString &extraHeaderPath : extraHeaderPaths) @@ -1244,7 +1244,7 @@ void CMakeBuildSystem::updateInitialCMakeExpandableVars() { const CMakeConfig &cm = cmakeBuildConfiguration()->configurationFromCMake(); const CMakeConfig &initialConfig = - CMakeConfigItem::itemsFromArguments(cmakeBuildConfiguration()->initialCMakeArguments()); + CMakeConfig::fromArguments(cmakeBuildConfiguration()->initialCMakeArguments()); CMakeConfig config; @@ -1275,7 +1275,7 @@ void CMakeBuildSystem::updateInitialCMakeExpandableVars() }); if (it != cm.cend()) { - const QByteArray initialValue = CMakeConfigItem::expandedValueOf(kit(), var, initialConfig).toUtf8(); + const QByteArray initialValue = initialConfig.expandedValueOf(kit(), var).toUtf8(); const FilePath initialPath = FilePath::fromString(QString::fromUtf8(initialValue)); const FilePath path = FilePath::fromString(QString::fromUtf8(it->value)); @@ -1299,7 +1299,7 @@ void CMakeBuildSystem::updateInitialCMakeExpandableVars() }); if (it != cm.cend()) { - const QByteArrayList initialValueList = CMakeConfigItem::expandedValueOf(kit(), var, initialConfig).toUtf8().split(';'); + const QByteArrayList initialValueList = initialConfig.expandedValueOf(kit(), var).toUtf8().split(';'); for (const auto &initialValue: initialValueList) { const FilePath initialPath = FilePath::fromString(QString::fromUtf8(initialValue)); diff --git a/src/plugins/cmakeprojectmanager/cmakeconfigitem.cpp b/src/plugins/cmakeprojectmanager/cmakeconfigitem.cpp index 6ccd0ae2368..04d4df6e692 100644 --- a/src/plugins/cmakeprojectmanager/cmakeconfigitem.cpp +++ b/src/plugins/cmakeprojectmanager/cmakeconfigitem.cpp @@ -55,29 +55,28 @@ CMakeConfigItem::CMakeConfigItem(const QByteArray &k, const QByteArray &v) : key(k), value(v) { } -QByteArray CMakeConfigItem::valueOf(const QByteArray &key, const QList &input) +QByteArray CMakeConfig::valueOf(const QByteArray &key) const { - for (auto it = input.constBegin(); it != input.constEnd(); ++it) { + for (auto it = constBegin(); it != constEnd(); ++it) { if (it->key == key) return it->value; } return QByteArray(); } -QString CMakeConfigItem::stringValueOf(const QByteArray &key, const QList &input) +QString CMakeConfig::stringValueOf(const QByteArray &key) const { - return QString::fromUtf8(valueOf(key, input)); + return QString::fromUtf8(valueOf(key)); } -FilePath CMakeConfigItem::filePathValueOf(const QByteArray &key, const QList &input) +FilePath CMakeConfig::filePathValueOf(const QByteArray &key) const { - return FilePath::fromUtf8(valueOf(key, input)); + return FilePath::fromUtf8(valueOf(key)); } -QString CMakeConfigItem::expandedValueOf(const ProjectExplorer::Kit *k, const QByteArray &key, - const QList &input) +QString CMakeConfig::expandedValueOf(const ProjectExplorer::Kit *k, const QByteArray &key) const { - for (auto it = input.constBegin(); it != input.constEnd(); ++it) { + for (auto it = constBegin(); it != constEnd(); ++it) { if (it->key == key) return it->expandedValue(k); } @@ -312,7 +311,7 @@ static CMakeConfigItem unsetItemFromString(const QString &input) return item; } -QList CMakeConfigItem::itemsFromArguments(const QStringList &list) +CMakeConfig CMakeConfig::fromArguments(const QStringList &list) { CMakeConfig result; bool inSet = false; @@ -348,7 +347,7 @@ QList CMakeConfigItem::itemsFromArguments(const QStringList &li return result; } -QList CMakeConfigItem::itemsFromFile(const Utils::FilePath &cacheFile, QString *errorMessage) +CMakeConfig CMakeConfig::fromFile(const Utils::FilePath &cacheFile, QString *errorMessage) { CMakeConfig result; QFile cache(cacheFile.toString()); diff --git a/src/plugins/cmakeprojectmanager/cmakeconfigitem.h b/src/plugins/cmakeprojectmanager/cmakeconfigitem.h index 2f1d8a1eec8..dc43b91cbcc 100644 --- a/src/plugins/cmakeprojectmanager/cmakeconfigitem.h +++ b/src/plugins/cmakeprojectmanager/cmakeconfigitem.h @@ -43,18 +43,16 @@ class Kit; namespace CMakeProjectManager { -class CMAKE_EXPORT CMakeConfigItem { +class CMakeConfig; + +class CMAKE_EXPORT CMakeConfigItem +{ public: enum Type { FILEPATH, PATH, BOOL, STRING, INTERNAL, STATIC, UNINITIALIZED }; CMakeConfigItem(); CMakeConfigItem(const QByteArray &k, Type t, const QByteArray &d, const QByteArray &v, const QStringList &s = {}); CMakeConfigItem(const QByteArray &k, const QByteArray &v); - static QByteArray valueOf(const QByteArray &key, const QList &input); - static QString stringValueOf(const QByteArray &key, const QList &input); - static Utils::FilePath filePathValueOf(const QByteArray &key, const QList &input); - static QString expandedValueOf(const ProjectExplorer::Kit *k, const QByteArray &key, - const QList &input); static QStringList cmakeSplitValue(const QString &in, bool keepEmpty = false); static Type typeStringToType(const QByteArray &typeString); static QString typeToTypeString(const Type t); @@ -66,8 +64,6 @@ public: static bool less(const CMakeConfigItem &a, const CMakeConfigItem &b); static CMakeConfigItem fromString(const QString &s); - static QList itemsFromArguments(const QStringList &list); - static QList itemsFromFile(const Utils::FilePath &input, QString *errorMessage); QString toString(const Utils::MacroExpander *expander = nullptr) const; QString toArgument(const Utils::MacroExpander *expander = nullptr) const; QString toCMakeSetLine(const Utils::MacroExpander *expander = nullptr) const; @@ -83,6 +79,23 @@ public: QByteArray documentation; QStringList values; }; -using CMakeConfig = QList; + +class CMAKE_EXPORT CMakeConfig : public QList +{ +public: + CMakeConfig() = default; + CMakeConfig(const QList &items) : QList(items) {} + CMakeConfig(std::initializer_list items) : QList(items) {} + + const QList &toList() const { return *this; } + + static CMakeConfig fromArguments(const QStringList &list); + static CMakeConfig fromFile(const Utils::FilePath &input, QString *errorMessage); + + QByteArray valueOf(const QByteArray &key) const; + QString stringValueOf(const QByteArray &key) const; + Utils::FilePath filePathValueOf(const QByteArray &key) const; + QString expandedValueOf(const ProjectExplorer::Kit *k, const QByteArray &key) const; +}; } // namespace CMakeProjectManager diff --git a/src/plugins/cmakeprojectmanager/cmakekitinformation.cpp b/src/plugins/cmakeprojectmanager/cmakekitinformation.cpp index ea9fa55db01..2c6956f99b5 100644 --- a/src/plugins/cmakeprojectmanager/cmakekitinformation.cpp +++ b/src/plugins/cmakeprojectmanager/cmakekitinformation.cpp @@ -996,15 +996,15 @@ void CMakeConfigurationKitAspect::setConfiguration(Kit *k, const CMakeConfig &co { if (!k) return; - const QStringList tmp = Utils::transform(config, [](const CMakeConfigItem &i) { return i.toString(); }); + const QStringList tmp = Utils::transform(config.toList(), + [](const CMakeConfigItem &i) { return i.toString(); }); k->setValue(CONFIGURATION_ID, tmp); } QStringList CMakeConfigurationKitAspect::toStringList(const Kit *k) { - QStringList current - = Utils::transform(CMakeConfigurationKitAspect::configuration(k), - [](const CMakeConfigItem &i) { return i.toString(); }); + QStringList current = Utils::transform(CMakeConfigurationKitAspect::configuration(k).toList(), + [](const CMakeConfigItem &i) { return i.toString(); }); current = Utils::filtered(current, [](const QString &s) { return !s.isEmpty(); }); Utils::sort(current); return current; @@ -1023,7 +1023,7 @@ void CMakeConfigurationKitAspect::fromStringList(Kit *k, const QStringList &in) QStringList CMakeConfigurationKitAspect::toArgumentsList(const Kit *k) { - return Utils::transform(CMakeConfigurationKitAspect::configuration(k), + return Utils::transform(CMakeConfigurationKitAspect::configuration(k).toList(), [](const CMakeConfigItem &i) { return i.toArgument(nullptr); }); } @@ -1046,8 +1046,8 @@ QVariant CMakeConfigurationKitAspect::defaultValue(const Kit *k) const { // FIXME: Convert preload scripts CMakeConfig config = defaultConfiguration(k); - const QStringList tmp - = Utils::transform(config, [](const CMakeConfigItem &i) { return i.toString(); }); + const QStringList tmp = Utils::transform(config.toList(), + [](const CMakeConfigItem &i) { return i.toString(); }); return tmp; } diff --git a/src/plugins/cmakeprojectmanager/cmakeprojectimporter.cpp b/src/plugins/cmakeprojectmanager/cmakeprojectimporter.cpp index c86016da97d..2c4e5434c1d 100644 --- a/src/plugins/cmakeprojectmanager/cmakeprojectimporter.cpp +++ b/src/plugins/cmakeprojectmanager/cmakeprojectimporter.cpp @@ -135,20 +135,16 @@ QStringList CMakeProjectImporter::importCandidates() static FilePath qmakeFromCMakeCache(const CMakeConfig &config) { // Qt4 way to define things (more convenient for us, so try this first;-) - FilePath qmake - = FilePath::fromUtf8(CMakeConfigItem::valueOf(QByteArray("QT_QMAKE_EXECUTABLE"), config)); + const FilePath qmake = config.filePathValueOf("QT_QMAKE_EXECUTABLE"); qCDebug(cmInputLog) << "QT_QMAKE_EXECUTABLE=" << qmake.toUserOutput(); if (!qmake.isEmpty()) return qmake; // Check Qt5 settings: oh, the horror! const FilePath qtCMakeDir = [config] { - FilePath tmp = FilePath::fromUtf8( - CMakeConfigItem::valueOf(QByteArray("Qt5Core_DIR"), config)); - if (tmp.isEmpty()) { - tmp = FilePath::fromUtf8( - CMakeConfigItem::valueOf(QByteArray("Qt6Core_DIR"), config)); - } + FilePath tmp = config.filePathValueOf("Qt5Core_DIR"); + if (tmp.isEmpty()) + tmp = config.filePathValueOf("Qt6Core_DIR"); return tmp; }(); qCDebug(cmInputLog) << "QtXCore_DIR=" << qtCMakeDir.toUserOutput(); @@ -206,11 +202,11 @@ static FilePath qmakeFromCMakeCache(const CMakeConfig &config) cmake.setEnvironment(env); cmake.setTimeOutMessageBoxEnabled(false); - QString cmakeGenerator = CMakeConfigItem::stringValueOf(QByteArray("CMAKE_GENERATOR"), config); - FilePath cmakeExecutable = CMakeConfigItem::filePathValueOf(QByteArray("CMAKE_COMMAND"), config); - FilePath cmakeMakeProgram =CMakeConfigItem::filePathValueOf(QByteArray("CMAKE_MAKE_PROGRAM"), config); - FilePath toolchainFile = CMakeConfigItem::filePathValueOf(QByteArray("CMAKE_TOOLCHAIN_FILE"), config); - FilePath hostPath = CMakeConfigItem::filePathValueOf(QByteArray("QT_HOST_PATH"), config); + QString cmakeGenerator = config.stringValueOf(QByteArray("CMAKE_GENERATOR")); + FilePath cmakeExecutable = config.filePathValueOf(QByteArray("CMAKE_COMMAND")); + FilePath cmakeMakeProgram = config.filePathValueOf(QByteArray("CMAKE_MAKE_PROGRAM")); + FilePath toolchainFile = config.filePathValueOf(QByteArray("CMAKE_TOOLCHAIN_FILE")); + FilePath hostPath = config.filePathValueOf(QByteArray("QT_HOST_PATH")); QStringList args; args.push_back("-S"); @@ -270,7 +266,7 @@ static QVector extractToolChainsFromCache(const CMakeConfi } if (!haveCCxxCompiler) { - const QByteArray generator = CMakeConfigItem::valueOf(QByteArray("CMAKE_GENERATOR"), config); + const QByteArray generator = config.valueOf("CMAKE_GENERATOR"); QString cCompilerName; QString cxxCompilerName; if (generator.contains("Visual Studio")) { @@ -282,8 +278,7 @@ static QVector extractToolChainsFromCache(const CMakeConfi } if (!cCompilerName.isEmpty() && !cxxCompilerName.isEmpty()) { - const FilePath linker = FilePath::fromUtf8( - CMakeConfigItem::valueOf(QByteArray("CMAKE_LINKER"), config)); + const FilePath linker = config.filePathValueOf("CMAKE_LINKER"); if (!linker.isEmpty()) { const FilePath compilerPath = linker.parentDir(); result.append({compilerPath.pathAppended(cCompilerName), @@ -315,13 +310,11 @@ QList CMakeProjectImporter::examineDirectory(const FilePath &importPath, return { }; } - QByteArrayList buildConfigurationTypes = {CMakeConfigItem::valueOf("CMAKE_BUILD_TYPE", config)}; + QByteArrayList buildConfigurationTypes = {config.valueOf("CMAKE_BUILD_TYPE")}; if (buildConfigurationTypes.front().isEmpty()) { - QByteArray buildConfigurationTypesString = - CMakeConfigItem::valueOf("CMAKE_CONFIGURATION_TYPES", config); - if (!buildConfigurationTypesString.isEmpty()) { + QByteArray buildConfigurationTypesString = config.valueOf("CMAKE_CONFIGURATION_TYPES"); + if (!buildConfigurationTypesString.isEmpty()) buildConfigurationTypes = buildConfigurationTypesString.split(';'); - } } QList result; @@ -329,7 +322,7 @@ QList CMakeProjectImporter::examineDirectory(const FilePath &importPath, auto data = std::make_unique(); data->cmakeHomeDirectory = - FilePath::fromUserInput(CMakeConfigItem::stringValueOf("CMAKE_HOME_DIRECTORY", config)) + FilePath::fromUserInput(config.stringValueOf("CMAKE_HOME_DIRECTORY")) .canonicalPath(); const FilePath canonicalProjectDirectory = projectDirectory().canonicalPath(); if (data->cmakeHomeDirectory != canonicalProjectDirectory) { @@ -344,12 +337,12 @@ QList CMakeProjectImporter::examineDirectory(const FilePath &importPath, data->buildDirectory = importPath; data->cmakeBuildType = buildType; - data->cmakeBinary = CMakeConfigItem::filePathValueOf("CMAKE_COMMAND", config); - data->generator = CMakeConfigItem::stringValueOf("CMAKE_GENERATOR", config); - data->extraGenerator = CMakeConfigItem::stringValueOf("CMAKE_EXTRA_GENERATOR", config); - data->platform = CMakeConfigItem::stringValueOf("CMAKE_GENERATOR_PLATFORM", config); - data->toolset = CMakeConfigItem::stringValueOf("CMAKE_GENERATOR_TOOLSET", config); - data->sysroot = CMakeConfigItem::filePathValueOf("CMAKE_SYSROOT", config); + data->cmakeBinary = config.filePathValueOf("CMAKE_COMMAND"); + data->generator = config.stringValueOf("CMAKE_GENERATOR"); + data->extraGenerator = config.stringValueOf("CMAKE_EXTRA_GENERATOR"); + data->platform = config.stringValueOf("CMAKE_GENERATOR_PLATFORM"); + data->toolset = config.stringValueOf("CMAKE_GENERATOR_TOOLSET"); + data->sysroot = config.filePathValueOf("CMAKE_SYSROOT"); // Qt: const FilePath qmake = qmakeFromCMakeCache(config); diff --git a/src/plugins/cmakeprojectmanager/configmodel.cpp b/src/plugins/cmakeprojectmanager/configmodel.cpp index c253ece543e..44d29c404ed 100644 --- a/src/plugins/cmakeprojectmanager/configmodel.cpp +++ b/src/plugins/cmakeprojectmanager/configmodel.cpp @@ -203,7 +203,7 @@ QList ConfigModel::configurationForCMake() const void ConfigModel::setConfiguration(const CMakeConfig &config) { - setConfiguration(Utils::transform(config, [](const CMakeConfigItem &i) { + setConfiguration(Utils::transform(config.toList(), [](const CMakeConfigItem &i) { return DataItem(i); })); } diff --git a/src/plugins/cmakeprojectmanager/fileapireader.cpp b/src/plugins/cmakeprojectmanager/fileapireader.cpp index 4d1e11e55e0..18ec80651c3 100644 --- a/src/plugins/cmakeprojectmanager/fileapireader.cpp +++ b/src/plugins/cmakeprojectmanager/fileapireader.cpp @@ -337,7 +337,7 @@ void FileApiReader::writeConfigurationIntoBuildDirectory(const QStringList &conf QByteArray contents; contents.append("# This file is managed by Qt Creator, do not edit!\n\n"); contents.append( - transform(CMakeConfigItem::itemsFromArguments(configurationArguments), + transform(CMakeConfig::fromArguments(configurationArguments).toList(), [](const CMakeConfigItem &item) { return item.toCMakeSetLine(nullptr); }) From 1c3b9e2bfe2fc39c4327610e431a2eccb401f6c1 Mon Sep 17 00:00:00 2001 From: hjk Date: Wed, 21 Jul 2021 10:47:07 +0200 Subject: [PATCH 12/20] Utils: Remove unused function FilePath::resolveSymlinkTarget() Change-Id: I7687f094a1e8054ac8298be754e95252db64c022 Reviewed-by: Christian Stenger --- src/libs/utils/fileutils.h | 1 - 1 file changed, 1 deletion(-) diff --git a/src/libs/utils/fileutils.h b/src/libs/utils/fileutils.h index 772c246ed71..1adf0c06574 100644 --- a/src/libs/utils/fileutils.h +++ b/src/libs/utils/fileutils.h @@ -198,7 +198,6 @@ public: FilePath pathAppended(const QString &str) const; FilePath stringAppended(const QString &str) const; FilePath resolvePath(const QString &fileName) const; - FilePath resolveSymlinkTarget() const; FilePath cleanPath() const; FilePath canonicalPath() const; From c8a6d94a394c0370778ad01a41553e808f291978 Mon Sep 17 00:00:00 2001 From: Thomas Hartmann Date: Wed, 21 Jul 2021 16:07:58 +0200 Subject: [PATCH 13/20] QmlDesigner: Fix missmatch of category titles "My Quick3D Components" was renamed to "My 3D Components". ItemLibraryImport::quick3DAssetsTitle defines the title in a single place. Task-number: QDS-4717 Change-Id: Ib96dcb029ce0b74de64ebdc904d890d66c240d2e Reviewed-by: Thomas Hartmann --- .../qmldesigner/components/itemlibrary/itemlibrarymodel.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/qmldesigner/components/itemlibrary/itemlibrarymodel.cpp b/src/plugins/qmldesigner/components/itemlibrary/itemlibrarymodel.cpp index f233017b706..807821e9789 100644 --- a/src/plugins/qmldesigner/components/itemlibrary/itemlibrarymodel.cpp +++ b/src/plugins/qmldesigner/components/itemlibrary/itemlibrarymodel.cpp @@ -347,7 +347,7 @@ void ItemLibraryModel::update(ItemLibraryInfo *itemLibraryInfo, Model *model) importSection = importHash[entry.requiredImport()]; } - } else if (catName == "My Quick3D Components") { + } else if (catName == ItemLibraryImport::quick3DAssetsTitle()) { importSection = importHash[ItemLibraryImport::quick3DAssetsTitle()]; } else { if (catName.startsWith("Qt Quick - ")) From bbf2dab8cb7c05e3946ef5c0f205566bec1e94be Mon Sep 17 00:00:00 2001 From: Christian Stenger Date: Thu, 22 Jul 2021 07:30:51 +0200 Subject: [PATCH 14/20] CMakePM: Fix build on Windows Amends 3aa324ef1f9ff. Change-Id: Ieef98239ec4fa029da1541b15229a17beeb513f6 Reviewed-by: hjk --- src/plugins/cmakeprojectmanager/cmakeconfigitem.cpp | 5 +++++ src/plugins/cmakeprojectmanager/cmakeconfigitem.h | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/plugins/cmakeprojectmanager/cmakeconfigitem.cpp b/src/plugins/cmakeprojectmanager/cmakeconfigitem.cpp index 04d4df6e692..93e6e6232af 100644 --- a/src/plugins/cmakeprojectmanager/cmakeconfigitem.cpp +++ b/src/plugins/cmakeprojectmanager/cmakeconfigitem.cpp @@ -472,6 +472,11 @@ bool CMakeConfigItem::operator==(const CMakeConfigItem &o) const return o.key == key && o.value == value && o.isUnset == isUnset; } +uint qHash(const CMakeConfigItem &it) +{ + return ::qHash(it.key) ^ ::qHash(it.value) ^ ::qHash(it.isUnset); +} + #if WITH_TESTS } // namespace CMakeProjectManager diff --git a/src/plugins/cmakeprojectmanager/cmakeconfigitem.h b/src/plugins/cmakeprojectmanager/cmakeconfigitem.h index dc43b91cbcc..dae93455d72 100644 --- a/src/plugins/cmakeprojectmanager/cmakeconfigitem.h +++ b/src/plugins/cmakeprojectmanager/cmakeconfigitem.h @@ -43,8 +43,6 @@ class Kit; namespace CMakeProjectManager { -class CMakeConfig; - class CMAKE_EXPORT CMakeConfigItem { public: @@ -80,6 +78,8 @@ public: QStringList values; }; +uint qHash(const CMakeConfigItem &it); // needed for MSVC + class CMAKE_EXPORT CMakeConfig : public QList { public: From c59bddd4df2d93c2844d7b76ac564f79b7bd525d Mon Sep 17 00:00:00 2001 From: Thomas Hartmann Date: Fri, 16 Jul 2021 15:15:02 +0200 Subject: [PATCH 15/20] QmlDesigner: Fix crash for QQuickStyleItem QQuickStyleItem crashes if componentComplete() is called. Change-Id: I1df1b50f6ba95c0eda1ff8e18de85276a6de04f5 Reviewed-by: Thomas Hartmann --- .../qmlprivategate/qmlprivategate_56.cpp | 35 +++++++++++++++---- 1 file changed, 29 insertions(+), 6 deletions(-) diff --git a/share/qtcreator/qml/qmlpuppet/qmlprivategate/qmlprivategate_56.cpp b/share/qtcreator/qml/qmlpuppet/qmlprivategate/qmlprivategate_56.cpp index f0ef76cb19d..8b2f6041657 100644 --- a/share/qtcreator/qml/qmlpuppet/qmlprivategate/qmlprivategate_56.cpp +++ b/share/qtcreator/qml/qmlpuppet/qmlprivategate/qmlprivategate_56.cpp @@ -167,6 +167,26 @@ void registerNodeInstanceMetaObject(QObject *object, QQmlEngine *engine) QQuickDesignerSupportProperties::registerNodeInstanceMetaObject(object, engine); } +static bool isQuickStyleItemMetaObject(const QMetaObject *metaObject) +{ + if (metaObject) { + if (metaObject->className() == QByteArrayLiteral("QQuickStyleItem")) + return true; + + return isQuickStyleItemMetaObject(metaObject->superClass()); + } + + return false; +} + +static bool isQuickStyleItem(QObject *object) +{ + if (object) + return isQuickStyleItemMetaObject(object->metaObject()); + + return false; +} + // This is used in share/qtcreator/qml/qmlpuppet/qml2puppet/instances/objectnodeinstance.cpp QObject *createPrimitive(const QString &typeName, int majorNumber, int minorNumber, QQmlContext *context) { @@ -357,12 +377,15 @@ void doComponentCompleteRecursive(QObject *object, NodeInstanceServer *nodeInsta doComponentCompleteRecursive(child, nodeInstanceServer); } - if (item) { - static_cast(item)->componentComplete(); - } else { - QQmlParserStatus *qmlParserStatus = dynamic_cast< QQmlParserStatus*>(object); - if (qmlParserStatus) - qmlParserStatus->componentComplete(); + if (!isQuickStyleItem(item)) { + qDebug() << Q_FUNC_INFO << item; + if (item) { + static_cast(item)->componentComplete(); + } else { + QQmlParserStatus *qmlParserStatus = dynamic_cast(object); + if (qmlParserStatus) + qmlParserStatus->componentComplete(); + } } } } From dfb0edf4ce267d3b2e991fe051fa0e218d870ac4 Mon Sep 17 00:00:00 2001 From: Christian Stenger Date: Thu, 22 Jul 2021 10:33:23 +0200 Subject: [PATCH 16/20] Docker: Fix logic and Qt6 build Change-Id: I3f0ec2cc86f48578e9dcd2ebfaa685cc577141c3 Reviewed-by: hjk --- src/plugins/docker/dockerdevice.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/docker/dockerdevice.cpp b/src/plugins/docker/dockerdevice.cpp index c09d2e38aea..eb0508d4b2c 100644 --- a/src/plugins/docker/dockerdevice.cpp +++ b/src/plugins/docker/dockerdevice.cpp @@ -1200,7 +1200,7 @@ bool DockerDevice::removeRecursively(const FilePath &filePath) const // We are expecting this only to be called in a context of build directories or similar. // Chicken out in some cases that _might_ be user code errors. QTC_ASSERT(path.startsWith('/'), return false); - const int levelsNeeded = path.startsWith("/home/" ? 4 : 3); + const int levelsNeeded = path.startsWith("/home/") ? 4 : 3; QTC_ASSERT(path.count('/') >= levelsNeeded, return false); return d->runInContainer({"rm", {"-rf", "--", path}}); From 45611e841bed7dbdfef4f6b12ac645fee1c119b2 Mon Sep 17 00:00:00 2001 From: Assam Boudjelthia Date: Sun, 30 May 2021 19:23:39 +0300 Subject: [PATCH 17/20] Android: avoid blocking call to show avd selection for default device If a user already chose a default device to run, we don't need to create the whole AVD selection dialog, but rather straightforward to get the device info if it's already connected, otherwise the dialog opens for selection as usual. Change-Id: I5fee9411d755e89da23a8d365f284e8b52808a07 Reviewed-by: hjk Reviewed-by: Alessandro Portale --- src/plugins/android/androidconfigurations.cpp | 15 +++++-- src/plugins/android/androiddevicedialog.cpp | 43 ++++++++++++------- src/plugins/android/androiddevicedialog.h | 6 ++- 3 files changed, 42 insertions(+), 22 deletions(-) diff --git a/src/plugins/android/androidconfigurations.cpp b/src/plugins/android/androidconfigurations.cpp index d80529c78bb..7b00fb7f4f2 100644 --- a/src/plugins/android/androidconfigurations.cpp +++ b/src/plugins/android/androidconfigurations.cpp @@ -1066,13 +1066,20 @@ AndroidDeviceInfo AndroidConfigurations::showDeviceDialog(Project *project, if (!serialNumber.isEmpty()) break; } + + const AndroidDeviceInfo defaultDevice = AndroidDeviceDialog::defaultDeviceInfo(serialNumber); + if (defaultDevice.isValid()) + return defaultDevice; + AndroidDeviceDialog dialog(apiLevel, abis, serialNumber, Core::ICore::dialogParent()); - AndroidDeviceInfo info = dialog.device(); + AndroidDeviceInfo info = dialog.showAndGetSelectedDevice(); if (dialog.saveDeviceSelection() && info.isValid()) { - const QString serialNumber = info.type == AndroidDeviceInfo::Hardware ? + const QString newSerialNumber = info.type == AndroidDeviceInfo::Hardware ? info.serialNumber : info.avdname; - if (!serialNumber.isEmpty()) - AndroidConfigurations::setDefaultDevice(project, AndroidManager::devicePreferredAbi(info.cpuAbi, abis), serialNumber); + if (!newSerialNumber.isEmpty()) { + const QString preferredAbi = AndroidManager::devicePreferredAbi(info.cpuAbi, abis); + AndroidConfigurations::setDefaultDevice(project, preferredAbi, newSerialNumber); + } } return info; } diff --git a/src/plugins/android/androiddevicedialog.cpp b/src/plugins/android/androiddevicedialog.cpp index 80121444354..9e33808ebb6 100644 --- a/src/plugins/android/androiddevicedialog.cpp +++ b/src/plugins/android/androiddevicedialog.cpp @@ -44,6 +44,8 @@ using namespace Android::Internal; namespace Android { namespace Internal { +QVector AndroidDeviceDialog::m_connectedDevices = {}; + // yeah, writing tree models is fun! class AndroidDeviceModelNode { @@ -481,26 +483,30 @@ AndroidDeviceDialog::~AndroidDeviceDialog() delete m_ui; } -AndroidDeviceInfo AndroidDeviceDialog::device() +AndroidDeviceInfo AndroidDeviceDialog::defaultDeviceInfo(const QString &serialNumber) { + AndroidDeviceDialog::updateConnectedDevicesList(); + + if (serialNumber.isEmpty()) + return {}; + + return Utils::findOrDefault(m_connectedDevices, [serialNumber](const AndroidDeviceInfo &info) { + return info.serialNumber == serialNumber || info.avdname == serialNumber; + }); +} + +AndroidDeviceInfo AndroidDeviceDialog::showAndGetSelectedDevice() +{ + auto dev = defaultDeviceInfo(m_defaultDevice); + if (dev.isValid()) + return dev; + refreshDeviceList(); - if (!m_defaultDevice.isEmpty()) { - auto device = std::find_if(m_connectedDevices.cbegin(), - m_connectedDevices.cend(), - [this](const AndroidDeviceInfo &info) { - return info.serialNumber == m_defaultDevice || - info.avdname == m_defaultDevice; - }); - - if (device != m_connectedDevices.cend()) - return *device; - m_defaultDevice.clear(); - } - if (exec() == QDialog::Accepted) return m_model->device(m_ui->deviceView->currentIndex()); - return AndroidDeviceInfo(); + + return {}; } bool AndroidDeviceDialog::saveDeviceSelection() const @@ -508,11 +514,16 @@ bool AndroidDeviceDialog::saveDeviceSelection() const return m_ui->defaultDeviceCheckBox->isChecked(); } +void AndroidDeviceDialog::updateConnectedDevicesList() +{ + m_connectedDevices = AndroidConfig::connectedDevices(AndroidConfigurations::currentConfig() + .adbToolPath()); +} + void AndroidDeviceDialog::refreshDeviceList() { m_ui->refreshDevicesButton->setEnabled(false); m_progressIndicator->show(); - m_connectedDevices = AndroidConfig::connectedDevices(AndroidConfigurations::currentConfig().adbToolPath()); m_futureWatcherRefreshDevices.setFuture(m_avdManager->avdList()); } diff --git a/src/plugins/android/androiddevicedialog.h b/src/plugins/android/androiddevicedialog.h index 4bb9a55e470..33bd2871e9c 100644 --- a/src/plugins/android/androiddevicedialog.h +++ b/src/plugins/android/androiddevicedialog.h @@ -56,7 +56,8 @@ public: const QString &serialNumber, QWidget *parent = nullptr); ~AndroidDeviceDialog() override; - AndroidDeviceInfo device(); + AndroidDeviceInfo showAndGetSelectedDevice(); + static AndroidDeviceInfo defaultDeviceInfo(const QString &serialNumber); bool saveDeviceSelection() const; @@ -68,6 +69,7 @@ private: void devicesRefreshed(); void enableOkayButton(); void defaultDeviceClear(); + static void updateConnectedDevicesList(); AndroidDeviceModel *m_model; Ui::AndroidDeviceDialog *m_ui; @@ -76,8 +78,8 @@ private: QStringList m_abis; QString m_avdNameFromAdd; QString m_defaultDevice; + static QVector m_connectedDevices; std::unique_ptr m_avdManager; - QVector m_connectedDevices; QFutureWatcher m_futureWatcherAddDevice; QFutureWatcher m_futureWatcherRefreshDevices; }; From 805d19d1edf5d76f97d436161be79f2e39678bda Mon Sep 17 00:00:00 2001 From: Christian Stenger Date: Thu, 22 Jul 2021 11:32:04 +0200 Subject: [PATCH 18/20] Unittest: Remove deleted file Amends baa83725f640. Change-Id: I9a5d3ad9a17dd37cdc39d05d0065022621219ab1 Reviewed-by: Christian Kandeler --- tests/unit/unittest/unittest.pro | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/unit/unittest/unittest.pro b/tests/unit/unittest/unittest.pro index 2c4d4529379..55774712c30 100644 --- a/tests/unit/unittest/unittest.pro +++ b/tests/unit/unittest/unittest.pro @@ -199,7 +199,6 @@ HEADERS += \ mimedatabase-utilities.h \ mockclangcodemodelclient.h \ mockclangcodemodelserver.h \ - mockfilesystem.h \ mockimagecachegenerator.h \ mockimagecachestorage.h \ mocklistmodeleditorview.h \ From fd677101a9f7428ed6e86166e670397921b00989 Mon Sep 17 00:00:00 2001 From: hjk Date: Fri, 16 Jul 2021 11:16:45 +0200 Subject: [PATCH 19/20] Utils: Split FilePath off fileutils.h This now rather central class deserves a suitably named file(pair). The rarer used declarations including otherwise not needed includes are still in fileutils.h. Change-Id: Ib18d72f16322c03f76023fdefcd5f6da35311b8b Reviewed-by: Christian Stenger --- src/libs/utils/CMakeLists.txt | 1 + src/libs/utils/filepath.cpp | 1329 ++++++++++++++++ src/libs/utils/filepath.h | 198 +++ src/libs/utils/fileutils.cpp | 1409 +---------------- src/libs/utils/fileutils.h | 167 +- src/libs/utils/utils-lib.pri | 2 + src/libs/utils/utils.qbs | 2 + .../devicesupport/devicemanager.cpp | 2 +- src/tools/sdktool/CMakeLists.txt | 1 + src/tools/sdktool/sdktool.pro | 2 + src/tools/sdktool/sdktool.qbs | 1 + tests/auto/debugger/gdb.pro | 2 + 12 files changed, 1617 insertions(+), 1499 deletions(-) create mode 100644 src/libs/utils/filepath.cpp create mode 100644 src/libs/utils/filepath.h diff --git a/src/libs/utils/CMakeLists.txt b/src/libs/utils/CMakeLists.txt index a8461a61f33..4f304d6ec29 100644 --- a/src/libs/utils/CMakeLists.txt +++ b/src/libs/utils/CMakeLists.txt @@ -52,6 +52,7 @@ add_qtc_library(Utils filecrumblabel.cpp filecrumblabel.h fileinprojectfinder.cpp fileinprojectfinder.h filenamevalidatinglineedit.cpp filenamevalidatinglineedit.h + filepath.cpp filepath.h filesearch.cpp filesearch.h filesystemwatcher.cpp filesystemwatcher.h fileutils.cpp fileutils.h diff --git a/src/libs/utils/filepath.cpp b/src/libs/utils/filepath.cpp new file mode 100644 index 00000000000..eda575945ae --- /dev/null +++ b/src/libs/utils/filepath.cpp @@ -0,0 +1,1329 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "filepath.h" + +#include "algorithm.h" +#include "commandline.h" +#include "environment.h" +#include "fileutils.h" +#include "hostosinfo.h" +#include "qtcassert.h" +#include "savefile.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef Q_OS_WIN +#ifdef QTCREATOR_PCH_H +#define CALLBACK WINAPI +#endif +#include +#include +#endif + +#ifdef Q_OS_OSX +#include "fileutils_mac.h" +#endif + +QT_BEGIN_NAMESPACE +QDebug operator<<(QDebug dbg, const Utils::FilePath &c) +{ + return dbg << c.toUserOutput(); +} + +QT_END_NAMESPACE + +namespace Utils { + +static DeviceFileHooks s_deviceHooks; + +/*! \class Utils::FileUtils + + \brief The FileUtils class contains file and directory related convenience + functions. + +*/ + +static bool removeRecursivelyLocal(const FilePath &filePath, QString *error) +{ + QTC_ASSERT(!filePath.needsDevice(), return false); + QFileInfo fileInfo = filePath.toFileInfo(); + if (!fileInfo.exists() && !fileInfo.isSymLink()) + return true; + QFile::setPermissions(filePath.toString(), fileInfo.permissions() | QFile::WriteUser); + if (fileInfo.isDir()) { + QDir dir(filePath.toString()); + dir.setPath(dir.canonicalPath()); + if (dir.isRoot()) { + if (error) { + *error = QCoreApplication::translate("Utils::FileUtils", + "Refusing to remove root directory."); + } + return false; + } + if (dir.path() == QDir::home().canonicalPath()) { + if (error) { + *error = QCoreApplication::translate("Utils::FileUtils", + "Refusing to remove your home directory."); + } + return false; + } + + const QStringList fileNames = dir.entryList( + QDir::Files | QDir::Hidden | QDir::System | QDir::Dirs | QDir::NoDotAndDotDot); + for (const QString &fileName : fileNames) { + if (!removeRecursivelyLocal(filePath / fileName, error)) + return false; + } + if (!QDir::root().rmdir(dir.path())) { + if (error) { + *error = QCoreApplication::translate("Utils::FileUtils", "Failed to remove directory \"%1\".") + .arg(filePath.toUserOutput()); + } + return false; + } + } else { + if (!QFile::remove(filePath.toString())) { + if (error) { + *error = QCoreApplication::translate("Utils::FileUtils", "Failed to remove file \"%1\".") + .arg(filePath.toUserOutput()); + } + return false; + } + } + return true; +} + +/*! + Copies the directory specified by \a srcFilePath recursively to \a tgtFilePath. \a tgtFilePath will contain + the target directory, which will be created. Example usage: + + \code + QString error; + book ok = Utils::FileUtils::copyRecursively("/foo/bar", "/foo/baz", &error); + if (!ok) + qDebug() << error; + \endcode + + This will copy the contents of /foo/bar into to the baz directory under /foo, which will be created in the process. + + \note The \a error parameter is optional. + + Returns whether the operation succeeded. +*/ + +bool FileUtils::copyRecursively(const FilePath &srcFilePath, const FilePath &tgtFilePath, QString *error) +{ + return copyRecursively( + srcFilePath, tgtFilePath, error, [](const QFileInfo &src, const QFileInfo &dest, QString *error) { + if (!QFile::copy(src.filePath(), dest.filePath())) { + if (error) { + *error = QCoreApplication::translate("Utils::FileUtils", + "Could not copy file \"%1\" to \"%2\".") + .arg(FilePath::fromFileInfo(src).toUserOutput(), + FilePath::fromFileInfo(dest).toUserOutput()); + } + return false; + } + return true; + }); +} + +/*! + Copies a file specified by \a srcFilePath to \a tgtFilePath only if \a srcFilePath is different + (file contents and last modification time). + + Returns whether the operation succeeded. +*/ + +bool FileUtils::copyIfDifferent(const FilePath &srcFilePath, const FilePath &tgtFilePath) +{ + QTC_ASSERT(srcFilePath.exists(), return false); + QTC_ASSERT(srcFilePath.scheme() == tgtFilePath.scheme(), return false); + QTC_ASSERT(srcFilePath.host() == tgtFilePath.host(), return false); + + if (tgtFilePath.exists()) { + const QDateTime srcModified = srcFilePath.lastModified(); + const QDateTime tgtModified = tgtFilePath.lastModified(); + if (srcModified == tgtModified) { + const QByteArray srcContents = srcFilePath.fileContents(); + const QByteArray tgtContents = srcFilePath.fileContents(); + if (srcContents == tgtContents) + return true; + } + tgtFilePath.removeFile(); + } + + return srcFilePath.copyFile(tgtFilePath); +} + +/*! + If this is a directory, the function will recursively check all files and return + true if one of them is newer than \a timeStamp. If this is a single file, true will + be returned if the file is newer than \a timeStamp. + + Returns whether at least one file in \a filePath has a newer date than + \a timeStamp. +*/ +bool FilePath::isNewerThan(const QDateTime &timeStamp) const +{ + if (!exists() || lastModified() >= timeStamp) + return true; + if (isDir()) { + const FilePaths dirContents = dirEntries(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot); + for (const FilePath &entry : dirContents) { + if (entry.isNewerThan(timeStamp)) + return true; + } + } + return false; +} + +Qt::CaseSensitivity FilePath::caseSensitivity() const +{ + if (m_scheme.isEmpty()) + return HostOsInfo::fileNameCaseSensitivity(); + + // FIXME: This could or possibly should the target device's file name case sensitivity + // into account by diverting to IDevice. However, as this is expensive and we are + // in time-critical path here, we go with "good enough" for now: + // The first approximation is "Anything unusual is not case sensitive" + return Qt::CaseSensitive; +} + +/*! + Recursively resolves symlinks if this is a symlink. + To resolve symlinks anywhere in the path, see canonicalPath. + Unlike QFileInfo::canonicalFilePath(), this function will still return the expected deepest + target file even if the symlink is dangling. + + \note Maximum recursion depth == 16. + + Returns the symlink target file path. +*/ +FilePath FilePath::resolveSymlinks() const +{ + FilePath current = *this; + int links = 16; + while (links--) { + const FilePath target = current.symLinkTarget(); + if (target.isEmpty()) + return current; + current = target; + } + return current; +} + +/*! + Recursively resolves possibly present symlinks in this file name. + Unlike QFileInfo::canonicalFilePath(), this function will not return an empty + string if path doesn't exist. + + Returns the canonical path. +*/ +FilePath FilePath::canonicalPath() const +{ + if (needsDevice()) { + // FIXME: Not a full solution, but it stays on the right device. + return *this; + } + const QString result = toFileInfo().canonicalFilePath(); + if (result.isEmpty()) + return *this; + return FilePath::fromString(result); +} + +FilePath FilePath::operator/(const QString &str) const +{ + return pathAppended(str); +} + +void FilePath::clear() +{ + m_data.clear(); + m_host.clear(); + m_scheme.clear(); +} + +bool FilePath::isEmpty() const +{ + return m_data.isEmpty(); +} + +/*! + Like QDir::toNativeSeparators(), but use prefix '~' instead of $HOME on unix systems when an + absolute path is given. + + Returns the possibly shortened path with native separators. +*/ +QString FilePath::shortNativePath() const +{ + if (HostOsInfo::isAnyUnixHost()) { + const FilePath home = FileUtils::homePath(); + if (isChildOf(home)) { + return QLatin1Char('~') + QDir::separator() + + QDir::toNativeSeparators(relativeChildPath(home).toString()); + } + } + return toUserOutput(); +} + +QString FileUtils::fileSystemFriendlyName(const QString &name) +{ + QString result = name; + result.replace(QRegularExpression(QLatin1String("\\W")), QLatin1String("_")); + result.replace(QRegularExpression(QLatin1String("_+")), QLatin1String("_")); // compact _ + result.remove(QRegularExpression(QLatin1String("^_*"))); // remove leading _ + result.remove(QRegularExpression(QLatin1String("_+$"))); // remove trailing _ + if (result.isEmpty()) + result = QLatin1String("unknown"); + return result; +} + +int FileUtils::indexOfQmakeUnfriendly(const QString &name, int startpos) +{ + static const QRegularExpression checkRegExp(QLatin1String("[^a-zA-Z0-9_.-]")); + return checkRegExp.match(name, startpos).capturedStart(); +} + +QString FileUtils::qmakeFriendlyName(const QString &name) +{ + QString result = name; + + // Remove characters that might trip up a build system (especially qmake): + int pos = indexOfQmakeUnfriendly(result); + while (pos >= 0) { + result[pos] = QLatin1Char('_'); + pos = indexOfQmakeUnfriendly(result, pos); + } + return fileSystemFriendlyName(result); +} + +bool FileUtils::makeWritable(const FilePath &path) +{ + const QString filePath = path.toString(); + return QFile::setPermissions(filePath, QFile::permissions(filePath) | QFile::WriteUser); +} + +// makes sure that capitalization of directories is canonical on Windows and OS X. +// This mimics the logic in QDeclarative_isFileCaseCorrect +QString FileUtils::normalizePathName(const QString &name) +{ +#ifdef Q_OS_WIN + const QString nativeSeparatorName(QDir::toNativeSeparators(name)); + const auto nameC = reinterpret_cast(nativeSeparatorName.utf16()); // MinGW + PIDLIST_ABSOLUTE file; + HRESULT hr = SHParseDisplayName(nameC, NULL, &file, 0, NULL); + if (FAILED(hr)) + return name; + TCHAR buffer[MAX_PATH]; + const bool success = SHGetPathFromIDList(file, buffer); + ILFree(file); + return success ? QDir::fromNativeSeparators(QString::fromUtf16(reinterpret_cast(buffer))) + : name; +#elif defined(Q_OS_OSX) + return Internal::normalizePathName(name); +#else // do not try to handle case-insensitive file systems on Linux + return name; +#endif +} + +static bool isRelativePathHelper(const QString &path, OsType osType) +{ + if (path.startsWith('/')) + return false; + if (osType == OsType::OsTypeWindows) { + if (path.startsWith('\\')) + return false; + // Unlike QFileInfo, this won't accept a relative path with a drive letter. + // Such paths result in a royal mess anyway ... + if (path.length() >= 3 && path.at(1) == ':' && path.at(0).isLetter() + && (path.at(2) == '/' || path.at(2) == '\\')) + return false; + } + return true; +} + +bool FileUtils::isRelativePath(const QString &path) +{ + return isRelativePathHelper(path, HostOsInfo::hostOs()); +} + +bool FilePath::isRelativePath() const +{ + return isRelativePathHelper(m_data, osType()); +} + +FilePath FilePath::resolvePath(const QString &fileName) const +{ + if (FileUtils::isAbsolutePath(fileName)) + return FilePath::fromString(QDir::cleanPath(fileName)); + FilePath result = *this; + result.setPath(QDir::cleanPath(m_data + '/' + fileName)); + return result; +} + +FilePath FilePath::cleanPath() const +{ + FilePath result = *this; + result.setPath(QDir::cleanPath(result.path())); + return result; +} + +FilePath FileUtils::commonPath(const FilePath &oldCommonPath, const FilePath &filePath) +{ + FilePath newCommonPath = oldCommonPath; + while (!newCommonPath.isEmpty() && !filePath.isChildOf(newCommonPath)) + newCommonPath = newCommonPath.parentDir(); + return newCommonPath.canonicalPath(); +} + +FilePath FileUtils::homePath() +{ + return FilePath::fromString(QDir::cleanPath(QDir::homePath())); +} + +bool FileUtils::renameFile(const FilePath &srcFilePath, const FilePath &tgtFilePath) +{ + QTC_ASSERT(!srcFilePath.needsDevice(), return false); + QTC_ASSERT(srcFilePath.scheme() == tgtFilePath.scheme(), return false); + return QFile::rename(srcFilePath.path(), tgtFilePath.path()); +} + + +/*! \class Utils::FilePath + + \brief The FilePath class is a light-weight convenience class for filenames. + + On windows filenames are compared case insensitively. +*/ + +FilePath::FilePath() +{ +} + +/// Constructs a FilePath from \a info +FilePath FilePath::fromFileInfo(const QFileInfo &info) +{ + return FilePath::fromString(info.absoluteFilePath()); +} + +/// \returns a QFileInfo +QFileInfo FilePath::toFileInfo() const +{ + return QFileInfo(m_data); +} + +FilePath FilePath::fromUrl(const QUrl &url) +{ + FilePath fn; + fn.m_scheme = url.scheme(); + fn.m_host = url.host(); + fn.m_data = url.path(); + return fn; +} + +/// \returns a QString for passing on to QString based APIs +QString FilePath::toString() const +{ + if (m_scheme.isEmpty()) + return m_data; + if (m_data.startsWith('/')) + return m_scheme + "://" + m_host + m_data; + return m_scheme + "://" + m_host + "/./" + m_data; +} + +QUrl FilePath::toUrl() const +{ + QUrl url; + url.setScheme(m_scheme); + url.setHost(m_host); + url.setPath(m_data); + return url; +} + +void FileUtils::setDeviceFileHooks(const DeviceFileHooks &hooks) +{ + s_deviceHooks = hooks; +} + +/// \returns a QString to display to the user +/// Converts the separators to the native format +QString FilePath::toUserOutput() const +{ + if (m_scheme.isEmpty()) + return QDir::toNativeSeparators(m_data); + return toString(); +} + +QString FilePath::fileName() const +{ + const QChar slash = QLatin1Char('/'); + return m_data.mid(m_data.lastIndexOf(slash) + 1); +} + +QString FilePath::fileNameWithPathComponents(int pathComponents) const +{ + if (pathComponents < 0) + return m_data; + const QChar slash = QLatin1Char('/'); + int i = m_data.lastIndexOf(slash); + if (pathComponents == 0 || i == -1) + return m_data.mid(i + 1); + int component = i + 1; + // skip adjacent slashes + while (i > 0 && m_data.at(--i) == slash) + ; + while (i >= 0 && --pathComponents >= 0) { + i = m_data.lastIndexOf(slash, i); + component = i + 1; + while (i > 0 && m_data.at(--i) == slash) + ; + } + + // If there are no more slashes before the found one, return the entire string + if (i > 0 && m_data.lastIndexOf(slash, i) != -1) + return m_data.mid(component); + return m_data; +} + +/// \returns the base name of the file without the path. +/// +/// The base name consists of all characters in the file up to +/// (but not including) the first '.' character. + +QString FilePath::baseName() const +{ + const QString &name = fileName(); + return name.left(name.indexOf('.')); +} + +/// \returns the complete base name of the file without the path. +/// +/// The complete base name consists of all characters in the file up to +/// (but not including) the last '.' character + +QString FilePath::completeBaseName() const +{ + const QString &name = fileName(); + return name.left(name.lastIndexOf('.')); +} + +/// \returns the suffix (extension) of the file. +/// +/// The suffix consists of all characters in the file after +/// (but not including) the last '.'. + +QString FilePath::suffix() const +{ + const QString &name = fileName(); + const int index = name.lastIndexOf('.'); + if (index >= 0) + return name.mid(index + 1); + return {}; +} + +/// \returns the complete suffix (extension) of the file. +/// +/// The complete suffix consists of all characters in the file after +/// (but not including) the first '.'. + +QString FilePath::completeSuffix() const +{ + const QString &name = fileName(); + const int index = name.indexOf('.'); + if (index >= 0) + return name.mid(index + 1); + return {}; +} + +void FilePath::setScheme(const QString &scheme) +{ + QTC_CHECK(!scheme.contains('/')); + m_scheme = scheme; +} + +void FilePath::setHost(const QString &host) +{ + QTC_CHECK(!host.contains('/')); + m_host = host; +} + + +/// \returns a bool indicating whether a file with this +/// FilePath exists. +bool FilePath::exists() const +{ + if (needsDevice()) { + QTC_ASSERT(s_deviceHooks.exists, return false); + return s_deviceHooks.exists(*this); + } + return !isEmpty() && QFileInfo::exists(m_data); +} + +/// \returns a bool indicating whether a path is writable. +bool FilePath::isWritableDir() const +{ + if (needsDevice()) { + QTC_ASSERT(s_deviceHooks.isWritableDir, return false); + return s_deviceHooks.isWritableDir(*this); + } + const QFileInfo fi{m_data}; + return exists() && fi.isDir() && fi.isWritable(); +} + +bool FilePath::isWritableFile() const +{ + if (needsDevice()) { + QTC_ASSERT(s_deviceHooks.isWritableFile, return false); + return s_deviceHooks.isWritableFile(*this); + } + const QFileInfo fi{m_data}; + return fi.exists() && fi.isWritable() && !fi.isDir(); +} + +bool FilePath::ensureWritableDir() const +{ + if (needsDevice()) { + QTC_ASSERT(s_deviceHooks.ensureWritableDir, return false); + return s_deviceHooks.ensureWritableDir(*this); + } + const QFileInfo fi{m_data}; + if (exists() && fi.isDir() && fi.isWritable()) + return true; + return QDir().mkpath(m_data); +} + +bool FilePath::ensureExistingFile() const +{ + if (needsDevice()) { + QTC_ASSERT(s_deviceHooks.ensureExistingFile, return false); + return s_deviceHooks.ensureExistingFile(*this); + } + QFile f(m_data); + if (f.exists()) + return true; + f.open(QFile::WriteOnly); + f.close(); + return f.exists(); +} + +bool FilePath::isExecutableFile() const +{ + if (needsDevice()) { + QTC_ASSERT(s_deviceHooks.isExecutableFile, return false); + return s_deviceHooks.isExecutableFile(*this); + } + const QFileInfo fi{m_data}; + return fi.exists() && fi.isExecutable() && !fi.isDir(); +} + +bool FilePath::isReadableFile() const +{ + if (needsDevice()) { + QTC_ASSERT(s_deviceHooks.isReadableFile, return false); + return s_deviceHooks.isReadableFile(*this); + } + const QFileInfo fi{m_data}; + return fi.exists() && fi.isReadable() && !fi.isDir(); +} + +bool FilePath::isReadableDir() const +{ + if (needsDevice()) { + QTC_ASSERT(s_deviceHooks.isReadableDir, return false); + return s_deviceHooks.isReadableDir(*this); + } + const QFileInfo fi{m_data}; + return fi.exists() && fi.isReadable() && fi.isDir(); +} + +bool FilePath::isFile() const +{ + if (needsDevice()) { + QTC_ASSERT(s_deviceHooks.isFile, return false); + return s_deviceHooks.isFile(*this); + } + const QFileInfo fi{m_data}; + return fi.exists() && fi.isFile(); +} + +bool FilePath::isDir() const +{ + if (needsDevice()) { + QTC_ASSERT(s_deviceHooks.isDir, return false); + return s_deviceHooks.isDir(*this); + } + const QFileInfo fi{m_data}; + return fi.exists() && fi.isDir(); +} + +bool FilePath::createDir() const +{ + if (needsDevice()) { + QTC_ASSERT(s_deviceHooks.createDir, return false); + return s_deviceHooks.createDir(*this); + } + QDir dir(m_data); + return dir.mkpath(dir.absolutePath()); +} + +FilePaths FilePath::dirEntries(const QStringList &nameFilters, + QDir::Filters filters, + QDir::SortFlags sort) const +{ + if (needsDevice()) { + QTC_ASSERT(s_deviceHooks.dirEntries, return {}); + return s_deviceHooks.dirEntries(*this, nameFilters, filters, sort); + } + + const QFileInfoList entryInfoList = QDir(m_data).entryInfoList(nameFilters, filters, sort); + return Utils::transform(entryInfoList, &FilePath::fromFileInfo); +} + +FilePaths FilePath::filterEntriesHelper(const FilePath &base, + const QStringList &entries, + const QStringList &nameFilters, + QDir::Filters filters, + QDir::SortFlags sort) +{ + const QList nameRegexps = transform(nameFilters, [](const QString &filter) { + QRegularExpression re; + re.setPattern(QRegularExpression::wildcardToRegularExpression(filter)); + QTC_CHECK(re.isValid()); + return re; + }); + + const auto nameMatches = [&nameRegexps](const QString &fileName) { + for (const QRegularExpression &re : nameRegexps) { + const QRegularExpressionMatch match = re.match(fileName); + if (match.hasMatch()) + return true; + } + return false; + }; + + // FIXME: Handle sort and filters. For now bark on unsupported options. + QTC_CHECK(filters == QDir::NoFilter); + QTC_CHECK(sort == QDir::NoSort); + + FilePaths result; + for (const QString &entry : entries) { + if (!nameMatches(entry)) + continue; + result.append(base.pathAppended(entry)); + } + return result; +} + +QList FilePath::dirEntries(QDir::Filters filters) const +{ + return dirEntries({}, filters); +} + +QByteArray FilePath::fileContents(qint64 maxSize, qint64 offset) const +{ + if (needsDevice()) { + QTC_ASSERT(s_deviceHooks.fileContents, return {}); + return s_deviceHooks.fileContents(*this, maxSize, offset); + } + + const QString path = toString(); + QFile f(path); + if (!f.exists()) + return {}; + + if (!f.open(QFile::ReadOnly)) + return {}; + + if (offset != 0) + f.seek(offset); + + if (maxSize != -1) + return f.read(maxSize); + + return f.readAll(); +} + +bool FilePath::writeFileContents(const QByteArray &data) const +{ + if (needsDevice()) { + QTC_ASSERT(s_deviceHooks.writeFileContents, return {}); + return s_deviceHooks.writeFileContents(*this, data); + } + + QFile file(path()); + QTC_ASSERT(file.open(QFile::WriteOnly | QFile::Truncate), return false); + qint64 res = file.write(data); + return res == data.size(); +} + +bool FilePath::needsDevice() const +{ + return !m_scheme.isEmpty(); +} + +/// \returns an empty FilePath if this is not a symbolic linl +FilePath FilePath::symLinkTarget() const +{ + if (needsDevice()) { + QTC_ASSERT(s_deviceHooks.symLinkTarget, return {}); + return s_deviceHooks.symLinkTarget(*this); + } + const QFileInfo info(m_data); + if (!info.isSymLink()) + return {}; + return FilePath::fromString(info.symLinkTarget()); +} + +FilePath FilePath::withExecutableSuffix() const +{ + FilePath res = *this; + res.setPath(OsSpecificAspects::withExecutableSuffix(osType(), m_data)); + return res; +} + +/// Find the parent directory of a given directory. + +/// Returns an empty FilePath if the current directory is already +/// a root level directory. + +/// \returns \a FilePath with the last segment removed. +FilePath FilePath::parentDir() const +{ + const QString basePath = path(); + if (basePath.isEmpty()) + return FilePath(); + + const QDir base(basePath); + if (base.isRoot()) + return FilePath(); + + const QString path = basePath + QLatin1String("/.."); + const QString parent = QDir::cleanPath(path); + QTC_ASSERT(parent != path, return FilePath()); + + FilePath result = *this; + result.setPath(parent); + return result; +} + +FilePath FilePath::absolutePath() const +{ + FilePath result = *this; + result.m_data = QFileInfo(m_data).absolutePath(); + return result; +} + +FilePath FilePath::absoluteFilePath() const +{ + FilePath result = *this; + result.m_data = QFileInfo(m_data).absoluteFilePath(); + return result; +} + +FilePath FilePath::absoluteFilePath(const FilePath &tail) const +{ + if (isRelativePathHelper(tail.m_data, osType())) + return pathAppended(tail.m_data); + return tail; +} + +/// Constructs an absolute FilePath from this path which +/// is interpreted as being relative to \a anchor. +FilePath FilePath::absoluteFromRelativePath(const FilePath &anchor) const +{ + QDir anchorDir = QFileInfo(anchor.m_data).absoluteDir(); + QString absoluteFilePath = QFileInfo(anchorDir, m_data).canonicalFilePath(); + return FilePath::fromString(absoluteFilePath); +} + +/// Constructs a FilePath from \a filename +/// \a filename is not checked for validity. +FilePath FilePath::fromString(const QString &filename) +{ + FilePath fn; + if (filename.startsWith('/')) { + fn.m_data = filename; // fast track: absolute local paths + } else { + int pos1 = filename.indexOf("://"); + if (pos1 >= 0) { + fn.m_scheme = filename.left(pos1); + pos1 += 3; + int pos2 = filename.indexOf('/', pos1); + if (pos2 == -1) { + fn.m_data = filename.mid(pos1); + } else { + fn.m_host = filename.mid(pos1, pos2 - pos1); + fn.m_data = filename.mid(pos2); + } + if (fn.m_data.startsWith("/./")) + fn.m_data = fn.m_data.mid(3); + } else { + fn.m_data = filename; // treat everything else as local, too. + } + } + return fn; +} + +/// Constructs a FilePath from \a filePath. The \a defaultExtension is appended +/// to \a filename if that does not have an extension already. +/// \a filePath is not checked for validity. +FilePath FilePath::fromStringWithExtension(const QString &filepath, const QString &defaultExtension) +{ + if (filepath.isEmpty() || defaultExtension.isEmpty()) + return FilePath::fromString(filepath); + + QString rc = filepath; + QFileInfo fi(filepath); + // Add extension unless user specified something else + const QChar dot = QLatin1Char('.'); + if (!fi.fileName().contains(dot)) { + if (!defaultExtension.startsWith(dot)) + rc += dot; + rc += defaultExtension; + } + return FilePath::fromString(rc); +} + +/// Constructs a FilePath from \a filePath +/// \a filePath is only passed through QDir::fromNativeSeparators +FilePath FilePath::fromUserInput(const QString &filePath) +{ + QString clean = QDir::fromNativeSeparators(filePath); + if (clean.startsWith(QLatin1String("~/"))) + return FileUtils::homePath().pathAppended(clean.mid(2)); + return FilePath::fromString(clean); +} + +/// Constructs a FilePath from \a filePath, which is encoded as UTF-8. +/// \a filePath is not checked for validity. +FilePath FilePath::fromUtf8(const char *filename, int filenameSize) +{ + return FilePath::fromString(QString::fromUtf8(filename, filenameSize)); +} + +FilePath FilePath::fromVariant(const QVariant &variant) +{ + if (variant.type() == QVariant::Url) + return FilePath::fromUrl(variant.toUrl()); + return FilePath::fromString(variant.toString()); +} + +QVariant FilePath::toVariant() const +{ + return toString(); +} + +QDir FilePath::toDir() const +{ + return QDir(m_data); +} + +bool FilePath::operator==(const FilePath &other) const +{ + return QString::compare(m_data, other.m_data, caseSensitivity()) == 0 + && m_host == other.m_host + && m_scheme == other.m_scheme; +} + +bool FilePath::operator!=(const FilePath &other) const +{ + return !(*this == other); +} + +bool FilePath::operator<(const FilePath &other) const +{ + const int cmp = QString::compare(m_data, other.m_data, caseSensitivity()); + if (cmp != 0) + return cmp < 0; + if (m_host != other.m_host) + return m_host < other.m_host; + return m_scheme < other.m_scheme; +} + +bool FilePath::operator<=(const FilePath &other) const +{ + return !(other < *this); +} + +bool FilePath::operator>(const FilePath &other) const +{ + return other < *this; +} + +bool FilePath::operator>=(const FilePath &other) const +{ + return !(*this < other); +} + +FilePath FilePath::operator+(const QString &s) const +{ + FilePath res = *this; + res.m_data += s; + return res; +} + +/// \returns whether FilePath is a child of \a s +bool FilePath::isChildOf(const FilePath &s) const +{ + if (s.isEmpty()) + return false; + if (!m_data.startsWith(s.m_data, caseSensitivity())) + return false; + if (m_data.size() <= s.m_data.size()) + return false; + // s is root, '/' was already tested in startsWith + if (s.m_data.endsWith(QLatin1Char('/'))) + return true; + // s is a directory, next character should be '/' (/tmpdir is NOT a child of /tmp) + return m_data.at(s.m_data.size()) == QLatin1Char('/'); +} + +/// \overload +bool FilePath::isChildOf(const QDir &dir) const +{ + return isChildOf(FilePath::fromString(dir.absolutePath())); +} + +/// \returns whether FilePath startsWith \a s +bool FilePath::startsWith(const QString &s) const +{ + return m_data.startsWith(s, caseSensitivity()); +} + +/// \returns whether FilePath endsWith \a s +bool FilePath::endsWith(const QString &s) const +{ + return m_data.endsWith(s, caseSensitivity()); +} + +/// \returns the relativeChildPath of FilePath to parent if FilePath is a child of parent +/// \note returns a empty FilePath if FilePath is not a child of parent +/// That is, this never returns a path starting with "../" +FilePath FilePath::relativeChildPath(const FilePath &parent) const +{ + FilePath res; + if (isChildOf(parent)) + res.m_data = m_data.mid(parent.m_data.size() + 1, -1); + return res; +} + +/// \returns the relativePath of FilePath to given \a anchor. +/// Both, FilePath and anchor may be files or directories. +/// Example usage: +/// +/// \code +/// FilePath filePath("/foo/b/ar/file.txt"); +/// FilePath relativePath = filePath.relativePath("/foo/c"); +/// qDebug() << relativePath +/// \endcode +/// +/// The debug output will be "../b/ar/file.txt". +/// +FilePath FilePath::relativePath(const FilePath &anchor) const +{ + const QFileInfo fileInfo(m_data); + QString absolutePath; + QString filename; + if (fileInfo.isFile()) { + absolutePath = fileInfo.absolutePath(); + filename = fileInfo.fileName(); + } else if (fileInfo.isDir()) { + absolutePath = fileInfo.absoluteFilePath(); + } else { + return {}; + } + const QFileInfo anchorInfo(anchor.m_data); + QString absoluteAnchorPath; + if (anchorInfo.isFile()) + absoluteAnchorPath = anchorInfo.absolutePath(); + else if (anchorInfo.isDir()) + absoluteAnchorPath = anchorInfo.absoluteFilePath(); + else + return {}; + QString relativeFilePath = calcRelativePath(absolutePath, absoluteAnchorPath); + if (!filename.isEmpty()) { + if (!relativeFilePath.isEmpty()) + relativeFilePath += '/'; + relativeFilePath += filename; + } + return FilePath::fromString(relativeFilePath); +} + +/// \returns the relativePath of \a absolutePath to given \a absoluteAnchorPath. +/// Both paths must be an absolute path to a directory. Example usage: +/// +/// \code +/// qDebug() << FilePath::calcRelativePath("/foo/b/ar", "/foo/c"); +/// \endcode +/// +/// The debug output will be "../b/ar". +/// +/// \see FilePath::relativePath +/// +QString FilePath::calcRelativePath(const QString &absolutePath, const QString &absoluteAnchorPath) +{ + if (absolutePath.isEmpty() || absoluteAnchorPath.isEmpty()) + return QString(); + // TODO using split() instead of parsing the strings by char index is slow + // and needs more memory (but the easiest implementation for now) + const QStringList splits1 = absolutePath.split('/'); + const QStringList splits2 = absoluteAnchorPath.split('/'); + int i = 0; + while (i < splits1.count() && i < splits2.count() && splits1.at(i) == splits2.at(i)) + ++i; + QString relativePath; + int j = i; + bool addslash = false; + while (j < splits2.count()) { + if (!splits2.at(j).isEmpty()) { + if (addslash) + relativePath += '/'; + relativePath += ".."; + addslash = true; + } + ++j; + } + while (i < splits1.count()) { + if (!splits1.at(i).isEmpty()) { + if (addslash) + relativePath += '/'; + relativePath += splits1.at(i); + addslash = true; + } + ++i; + } + return relativePath; +} + +/*! + Returns a path corresponding to the current object on the + same device as \a deviceTemplate. + + Example usage: + \code + localDir = FilePath::fromString("/tmp/workingdir"); + executable = FilePath::fromUrl("docker://123/bin/ls") + realDir = localDir.onDevice(executable) + assert(realDir == FilePath::fromUrl("docker://123/tmp/workingdir")) + \endcode +*/ +FilePath FilePath::onDevice(const FilePath &deviceTemplate) const +{ + FilePath res; + res.m_data = m_data; + res.m_host = deviceTemplate.m_host; + res.m_scheme = deviceTemplate.m_scheme; + return res; +} + +/*! + Returns a FilePath with local path \a newPath on the same device + as the current object. + + Example usage: + \code + devicePath = FilePath::fromString("docker://123/tmp"); + newPath = devicePath.withNewPath("/bin/ls"); + assert(realDir == FilePath::fromUrl("docker://123/bin/ls")) + \endcode +*/ +FilePath FilePath::withNewPath(const QString &newPath) const +{ + FilePath res; + res.m_data = newPath; + res.m_host = m_host; + res.m_scheme = m_scheme; + return res; +} + +/*! + Searched a binary corresponding to this object in the PATH of + the device implied by this object's scheme and host. + + Example usage: + \code + binary = FilePath::fromUrl("docker://123/./make); + fullPath = binary.searchOnDevice(); + assert(fullPath == FilePath::fromUrl("docker://123/usr/bin/make")) + \endcode +*/ +FilePath FilePath::searchOnDevice(const FilePaths &dirs) const +{ + if (needsDevice()) { + QTC_ASSERT(s_deviceHooks.searchInPath, return {}); + return s_deviceHooks.searchInPath(*this, dirs); + } + return Environment::systemEnvironment().searchInPath(path(), dirs); +} + +Environment FilePath::deviceEnvironment() const +{ + if (needsDevice()) { + QTC_ASSERT(s_deviceHooks.environment, return {}); + return s_deviceHooks.environment(*this); + } + return Environment::systemEnvironment(); +} + +QString FilePath::formatFilePaths(const QList &files, const QString &separator) +{ + const QStringList nativeFiles = Utils::transform(files, &FilePath::toUserOutput); + return nativeFiles.join(separator); +} + +void FilePath::removeDuplicates(QList &files) +{ + // FIXME: Improve. + QStringList list = Utils::transform(files, &FilePath::toString); + list.removeDuplicates(); + files = Utils::transform(list, &FilePath::fromString); +} + +void FilePath::sort(QList &files) +{ + // FIXME: Improve. + QStringList list = Utils::transform(files, &FilePath::toString); + list.sort(); + files = Utils::transform(list, &FilePath::fromString); +} + +FilePath FilePath::pathAppended(const QString &path) const +{ + FilePath fn = *this; + if (path.isEmpty()) + return fn; + if (!fn.m_data.isEmpty() && !fn.m_data.endsWith(QLatin1Char('/'))) + fn.m_data.append('/'); + fn.m_data.append(path); + return fn; +} + +FilePath FilePath::stringAppended(const QString &str) const +{ + FilePath fn = *this; + fn.m_data.append(str); + return fn; +} + +uint FilePath::hash(uint seed) const +{ + if (Utils::HostOsInfo::fileNameCaseSensitivity() == Qt::CaseInsensitive) + return qHash(m_data.toUpper(), seed); + return qHash(m_data, seed); +} + +QDateTime FilePath::lastModified() const +{ + if (needsDevice()) { + QTC_ASSERT(s_deviceHooks.lastModified, return {}); + return s_deviceHooks.lastModified(*this); + } + return toFileInfo().lastModified(); +} + +QFile::Permissions FilePath::permissions() const +{ + if (needsDevice()) { + QTC_ASSERT(s_deviceHooks.permissions, return {}); + return s_deviceHooks.permissions(*this); + } + return toFileInfo().permissions(); +} + +OsType FilePath::osType() const +{ + if (needsDevice()) { + QTC_ASSERT(s_deviceHooks.osType, return {}); + return s_deviceHooks.osType(*this); + } + return HostOsInfo::hostOs(); +} + +bool FilePath::removeFile() const +{ + if (needsDevice()) { + QTC_ASSERT(s_deviceHooks.removeFile, return false); + return s_deviceHooks.removeFile(*this); + } + return QFile::remove(path()); +} + +/*! + Removes the directory this filePath refers too and its subdirectories recursively. + + \note The \a error parameter is optional. + + Returns whether the operation succeeded. +*/ +bool FilePath::removeRecursively(QString *error) const +{ + if (needsDevice()) { + QTC_ASSERT(s_deviceHooks.removeRecursively, return false); + return s_deviceHooks.removeRecursively(*this); + } + return removeRecursivelyLocal(*this, error); +} + +bool FilePath::copyFile(const FilePath &target) const +{ + if (needsDevice()) { + QTC_ASSERT(s_deviceHooks.copyFile, return false); + return s_deviceHooks.copyFile(*this, target); + } + return QFile::copy(path(), target.path()); +} + +bool FilePath::renameFile(const FilePath &target) const +{ + if (needsDevice()) { + QTC_ASSERT(s_deviceHooks.renameFile, return false); + return s_deviceHooks.renameFile(*this, target); + } + return QFile::rename(path(), target.path()); +} + +QTextStream &operator<<(QTextStream &s, const FilePath &fn) +{ + return s << fn.toString(); +} + +} // namespace Utils + +std::hash::result_type + std::hash::operator()(const std::hash::argument_type &fn) const +{ + if (fn.caseSensitivity() == Qt::CaseInsensitive) + return hash()(fn.toString().toUpper().toStdString()); + return hash()(fn.toString().toStdString()); +} diff --git a/src/libs/utils/filepath.h b/src/libs/utils/filepath.h new file mode 100644 index 00000000000..d53357057db --- /dev/null +++ b/src/libs/utils/filepath.h @@ -0,0 +1,198 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include "utils_global.h" + +#include "hostosinfo.h" + +#include +#include + +#include +#include + +QT_BEGIN_NAMESPACE +class QDateTime; +class QDebug; +class QFileInfo; +class QUrl; +QT_END_NAMESPACE + +class tst_fileutils; // This becomes a friend of Utils::FilePath for testing private methods. + +namespace Utils { + +class Environment; + +class QTCREATOR_UTILS_EXPORT FilePath +{ +public: + FilePath(); + + static FilePath fromString(const QString &filepath); + static FilePath fromFileInfo(const QFileInfo &info); + static FilePath fromStringWithExtension(const QString &filepath, const QString &defaultExtension); + static FilePath fromUserInput(const QString &filepath); + static FilePath fromUtf8(const char *filepath, int filepathSize = -1); + static FilePath fromVariant(const QVariant &variant); + + QString toString() const; + FilePath onDevice(const FilePath &deviceTemplate) const; + FilePath withNewPath(const QString &newPath) const; + + QFileInfo toFileInfo() const; + QVariant toVariant() const; + QDir toDir() const; + + QString toUserOutput() const; + QString shortNativePath() const; + + QString fileName() const; + QString fileNameWithPathComponents(int pathComponents) const; + + QString baseName() const; + QString completeBaseName() const; + QString suffix() const; + QString completeSuffix() const; + + QString scheme() const { return m_scheme; } + void setScheme(const QString &scheme); + + QString host() const { return m_host; } + void setHost(const QString &host); + + QString path() const { return m_data; } + void setPath(const QString &path) { m_data = path; } + + bool needsDevice() const; + bool exists() const; + + bool isWritablePath() const { return isWritableDir(); } // Remove. + bool isWritableDir() const; + bool isWritableFile() const; + bool ensureWritableDir() const; + bool ensureExistingFile() const; + bool isExecutableFile() const; + bool isReadableFile() const; + bool isReadableDir() const; + bool isRelativePath() const; + bool isAbsolutePath() const { return !isRelativePath(); } + bool isFile() const; + bool isDir() const; + + bool createDir() const; + QList dirEntries(const QStringList &nameFilters, + QDir::Filters filters, + QDir::SortFlags sort = QDir::NoSort) const; + QList dirEntries(QDir::Filters filters) const; + QByteArray fileContents(qint64 maxSize = -1, qint64 offset = 0) const; + bool writeFileContents(const QByteArray &data) const; + + FilePath parentDir() const; + FilePath absolutePath() const; + FilePath absoluteFilePath() const; + FilePath absoluteFilePath(const FilePath &tail) const; + FilePath absoluteFromRelativePath(const FilePath &anchor) const; + + bool operator==(const FilePath &other) const; + bool operator!=(const FilePath &other) const; + bool operator<(const FilePath &other) const; + bool operator<=(const FilePath &other) const; + bool operator>(const FilePath &other) const; + bool operator>=(const FilePath &other) const; + FilePath operator+(const QString &s) const; + + bool isChildOf(const FilePath &s) const; + bool isChildOf(const QDir &dir) const; + bool startsWith(const QString &s) const; + bool endsWith(const QString &s) const; + + bool isNewerThan(const QDateTime &timeStamp) const; + QDateTime lastModified() const; + QFile::Permissions permissions() const; + OsType osType() const; + bool removeFile() const; + bool removeRecursively(QString *error = nullptr) const; + bool copyFile(const FilePath &target) const; + bool renameFile(const FilePath &target) const; + + Qt::CaseSensitivity caseSensitivity() const; + + FilePath relativeChildPath(const FilePath &parent) const; + FilePath relativePath(const FilePath &anchor) const; + FilePath pathAppended(const QString &str) const; + FilePath stringAppended(const QString &str) const; + FilePath resolvePath(const QString &fileName) const; + FilePath cleanPath() const; + + FilePath canonicalPath() const; + FilePath symLinkTarget() const; + FilePath resolveSymlinks() const; + FilePath withExecutableSuffix() const; + + FilePath operator/(const QString &str) const; + + void clear(); + bool isEmpty() const; + + uint hash(uint seed) const; + + // NOTE: Most FilePath operations on FilePath created from URL currently + // do not work. Among the working are .toVariant() and .toUrl(). + static FilePath fromUrl(const QUrl &url); + QUrl toUrl() const; + + FilePath searchOnDevice(const QList &dirs) const; + Environment deviceEnvironment() const; + + static QString formatFilePaths(const QList &files, const QString &separator); + static void removeDuplicates(QList &files); + static void sort(QList &files); + + static QList filterEntriesHelper(const FilePath &base, + const QStringList &entries, + const QStringList &nameFilters, + QDir::Filters filters, + QDir::SortFlags sort); +private: + friend class ::tst_fileutils; + static QString calcRelativePath(const QString &absolutePath, const QString &absoluteAnchorPath); + + QString m_scheme; + QString m_host; + QString m_data; +}; + +using FilePaths = QList; + +} // namespace Utils + +QT_BEGIN_NAMESPACE +QTCREATOR_UTILS_EXPORT QDebug operator<<(QDebug dbg, const Utils::FilePath &c); +QT_END_NAMESPACE + +Q_DECLARE_METATYPE(Utils::FilePath) diff --git a/src/libs/utils/fileutils.cpp b/src/libs/utils/fileutils.cpp index fceca709372..54f3c1ddf7b 100644 --- a/src/libs/utils/fileutils.cpp +++ b/src/libs/utils/fileutils.cpp @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2021 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of Qt Creator. @@ -57,438 +57,9 @@ #include "fileutils_mac.h" #endif -QT_BEGIN_NAMESPACE -QDebug operator<<(QDebug dbg, const Utils::FilePath &c) -{ - return dbg << c.toUserOutput(); -} - -QT_END_NAMESPACE - namespace Utils { -static DeviceFileHooks s_deviceHooks; - -/*! \class Utils::FileUtils - - \brief The FileUtils class contains file and directory related convenience - functions. - -*/ - -static bool removeRecursivelyLocal(const FilePath &filePath, QString *error) -{ - QTC_ASSERT(!filePath.needsDevice(), return false); - QFileInfo fileInfo = filePath.toFileInfo(); - if (!fileInfo.exists() && !fileInfo.isSymLink()) - return true; - QFile::setPermissions(filePath.toString(), fileInfo.permissions() | QFile::WriteUser); - if (fileInfo.isDir()) { - QDir dir(filePath.toString()); - dir.setPath(dir.canonicalPath()); - if (dir.isRoot()) { - if (error) { - *error = QCoreApplication::translate("Utils::FileUtils", - "Refusing to remove root directory."); - } - return false; - } - if (dir.path() == QDir::home().canonicalPath()) { - if (error) { - *error = QCoreApplication::translate("Utils::FileUtils", - "Refusing to remove your home directory."); - } - return false; - } - - const QStringList fileNames = dir.entryList( - QDir::Files | QDir::Hidden | QDir::System | QDir::Dirs | QDir::NoDotAndDotDot); - for (const QString &fileName : fileNames) { - if (!removeRecursivelyLocal(filePath / fileName, error)) - return false; - } - if (!QDir::root().rmdir(dir.path())) { - if (error) { - *error = QCoreApplication::translate("Utils::FileUtils", "Failed to remove directory \"%1\".") - .arg(filePath.toUserOutput()); - } - return false; - } - } else { - if (!QFile::remove(filePath.toString())) { - if (error) { - *error = QCoreApplication::translate("Utils::FileUtils", "Failed to remove file \"%1\".") - .arg(filePath.toUserOutput()); - } - return false; - } - } - return true; -} - -/*! - Copies the directory specified by \a srcFilePath recursively to \a tgtFilePath. \a tgtFilePath will contain - the target directory, which will be created. Example usage: - - \code - QString error; - book ok = Utils::FileUtils::copyRecursively("/foo/bar", "/foo/baz", &error); - if (!ok) - qDebug() << error; - \endcode - - This will copy the contents of /foo/bar into to the baz directory under /foo, which will be created in the process. - - \note The \a error parameter is optional. - - Returns whether the operation succeeded. -*/ - -bool FileUtils::copyRecursively(const FilePath &srcFilePath, const FilePath &tgtFilePath, QString *error) -{ - return copyRecursively( - srcFilePath, tgtFilePath, error, [](const QFileInfo &src, const QFileInfo &dest, QString *error) { - if (!QFile::copy(src.filePath(), dest.filePath())) { - if (error) { - *error = QCoreApplication::translate("Utils::FileUtils", - "Could not copy file \"%1\" to \"%2\".") - .arg(FilePath::fromFileInfo(src).toUserOutput(), - FilePath::fromFileInfo(dest).toUserOutput()); - } - return false; - } - return true; - }); -} - -/*! - Copies a file specified by \a srcFilePath to \a tgtFilePath only if \a srcFilePath is different - (file contents and last modification time). - - Returns whether the operation succeeded. -*/ - -bool FileUtils::copyIfDifferent(const FilePath &srcFilePath, const FilePath &tgtFilePath) -{ - QTC_ASSERT(srcFilePath.exists(), return false); - QTC_ASSERT(srcFilePath.scheme() == tgtFilePath.scheme(), return false); - QTC_ASSERT(srcFilePath.host() == tgtFilePath.host(), return false); - - if (tgtFilePath.exists()) { - const QDateTime srcModified = srcFilePath.lastModified(); - const QDateTime tgtModified = tgtFilePath.lastModified(); - if (srcModified == tgtModified) { - const QByteArray srcContents = srcFilePath.fileContents(); - const QByteArray tgtContents = srcFilePath.fileContents(); - if (srcContents == tgtContents) - return true; - } - tgtFilePath.removeFile(); - } - - return srcFilePath.copyFile(tgtFilePath); -} - -/*! - If this is a directory, the function will recursively check all files and return - true if one of them is newer than \a timeStamp. If this is a single file, true will - be returned if the file is newer than \a timeStamp. - - Returns whether at least one file in \a filePath has a newer date than - \a timeStamp. -*/ -bool FilePath::isNewerThan(const QDateTime &timeStamp) const -{ - if (!exists() || lastModified() >= timeStamp) - return true; - if (isDir()) { - const FilePaths dirContents = dirEntries(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot); - for (const FilePath &entry : dirContents) { - if (entry.isNewerThan(timeStamp)) - return true; - } - } - return false; -} - -Qt::CaseSensitivity FilePath::caseSensitivity() const -{ - if (m_scheme.isEmpty()) - return HostOsInfo::fileNameCaseSensitivity(); - - // FIXME: This could or possibly should the target device's file name case sensitivity - // into account by diverting to IDevice. However, as this is expensive and we are - // in time-critical path here, we go with "good enough" for now: - // The first approximation is "Anything unusual is not case sensitive" - return Qt::CaseSensitive; -} - -/*! - Recursively resolves symlinks if this is a symlink. - To resolve symlinks anywhere in the path, see canonicalPath. - Unlike QFileInfo::canonicalFilePath(), this function will still return the expected deepest - target file even if the symlink is dangling. - - \note Maximum recursion depth == 16. - - Returns the symlink target file path. -*/ -FilePath FilePath::resolveSymlinks() const -{ - FilePath current = *this; - int links = 16; - while (links--) { - const FilePath target = current.symLinkTarget(); - if (target.isEmpty()) - return current; - current = target; - } - return current; -} - -/*! - Recursively resolves possibly present symlinks in this file name. - Unlike QFileInfo::canonicalFilePath(), this function will not return an empty - string if path doesn't exist. - - Returns the canonical path. -*/ -FilePath FilePath::canonicalPath() const -{ - if (needsDevice()) { - // FIXME: Not a full solution, but it stays on the right device. - return *this; - } - const QString result = toFileInfo().canonicalFilePath(); - if (result.isEmpty()) - return *this; - return FilePath::fromString(result); -} - -FilePath FilePath::operator/(const QString &str) const -{ - return pathAppended(str); -} - -void FilePath::clear() -{ - m_data.clear(); - m_host.clear(); - m_scheme.clear(); -} - -bool FilePath::isEmpty() const -{ - return m_data.isEmpty(); -} - -/*! - Like QDir::toNativeSeparators(), but use prefix '~' instead of $HOME on unix systems when an - absolute path is given. - - Returns the possibly shortened path with native separators. -*/ -QString FilePath::shortNativePath() const -{ - if (HostOsInfo::isAnyUnixHost()) { - const FilePath home = FileUtils::homePath(); - if (isChildOf(home)) { - return QLatin1Char('~') + QDir::separator() - + QDir::toNativeSeparators(relativeChildPath(home).toString()); - } - } - return toUserOutput(); -} - -QString FileUtils::fileSystemFriendlyName(const QString &name) -{ - QString result = name; - result.replace(QRegularExpression(QLatin1String("\\W")), QLatin1String("_")); - result.replace(QRegularExpression(QLatin1String("_+")), QLatin1String("_")); // compact _ - result.remove(QRegularExpression(QLatin1String("^_*"))); // remove leading _ - result.remove(QRegularExpression(QLatin1String("_+$"))); // remove trailing _ - if (result.isEmpty()) - result = QLatin1String("unknown"); - return result; -} - -int FileUtils::indexOfQmakeUnfriendly(const QString &name, int startpos) -{ - static const QRegularExpression checkRegExp(QLatin1String("[^a-zA-Z0-9_.-]")); - return checkRegExp.match(name, startpos).capturedStart(); -} - -QString FileUtils::qmakeFriendlyName(const QString &name) -{ - QString result = name; - - // Remove characters that might trip up a build system (especially qmake): - int pos = indexOfQmakeUnfriendly(result); - while (pos >= 0) { - result[pos] = QLatin1Char('_'); - pos = indexOfQmakeUnfriendly(result, pos); - } - return fileSystemFriendlyName(result); -} - -bool FileUtils::makeWritable(const FilePath &path) -{ - const QString filePath = path.toString(); - return QFile::setPermissions(filePath, QFile::permissions(filePath) | QFile::WriteUser); -} - -// makes sure that capitalization of directories is canonical on Windows and OS X. -// This mimics the logic in QDeclarative_isFileCaseCorrect -QString FileUtils::normalizePathName(const QString &name) -{ -#ifdef Q_OS_WIN - const QString nativeSeparatorName(QDir::toNativeSeparators(name)); - const auto nameC = reinterpret_cast(nativeSeparatorName.utf16()); // MinGW - PIDLIST_ABSOLUTE file; - HRESULT hr = SHParseDisplayName(nameC, NULL, &file, 0, NULL); - if (FAILED(hr)) - return name; - TCHAR buffer[MAX_PATH]; - const bool success = SHGetPathFromIDList(file, buffer); - ILFree(file); - return success ? QDir::fromNativeSeparators(QString::fromUtf16(reinterpret_cast(buffer))) - : name; -#elif defined(Q_OS_OSX) - return Internal::normalizePathName(name); -#else // do not try to handle case-insensitive file systems on Linux - return name; -#endif -} - -static bool isRelativePathHelper(const QString &path, OsType osType) -{ - if (path.startsWith('/')) - return false; - if (osType == OsType::OsTypeWindows) { - if (path.startsWith('\\')) - return false; - // Unlike QFileInfo, this won't accept a relative path with a drive letter. - // Such paths result in a royal mess anyway ... - if (path.length() >= 3 && path.at(1) == ':' && path.at(0).isLetter() - && (path.at(2) == '/' || path.at(2) == '\\')) - return false; - } - return true; -} - -bool FileUtils::isRelativePath(const QString &path) -{ - return isRelativePathHelper(path, HostOsInfo::hostOs()); -} - -bool FilePath::isRelativePath() const -{ - return isRelativePathHelper(m_data, osType()); -} - -FilePath FilePath::resolvePath(const QString &fileName) const -{ - if (FileUtils::isAbsolutePath(fileName)) - return FilePath::fromString(QDir::cleanPath(fileName)); - FilePath result = *this; - result.setPath(QDir::cleanPath(m_data + '/' + fileName)); - return result; -} - -FilePath FilePath::cleanPath() const -{ - FilePath result = *this; - result.setPath(QDir::cleanPath(result.path())); - return result; -} - -FilePath FileUtils::commonPath(const FilePath &oldCommonPath, const FilePath &filePath) -{ - FilePath newCommonPath = oldCommonPath; - while (!newCommonPath.isEmpty() && !filePath.isChildOf(newCommonPath)) - newCommonPath = newCommonPath.parentDir(); - return newCommonPath.canonicalPath(); -} - -// Copied from qfilesystemengine_win.cpp -#ifdef Q_OS_WIN - -// File ID for Windows up to version 7. -static inline QByteArray fileIdWin7(HANDLE handle) -{ - BY_HANDLE_FILE_INFORMATION info; - if (GetFileInformationByHandle(handle, &info)) { - char buffer[sizeof "01234567:0123456701234567\0"]; - qsnprintf(buffer, sizeof(buffer), "%lx:%08lx%08lx", - info.dwVolumeSerialNumber, - info.nFileIndexHigh, - info.nFileIndexLow); - return QByteArray(buffer); - } - return QByteArray(); -} - -// File ID for Windows starting from version 8. -static QByteArray fileIdWin8(HANDLE handle) -{ - QByteArray result; - FILE_ID_INFO infoEx; - if (GetFileInformationByHandleEx(handle, - static_cast(18), // FileIdInfo in Windows 8 - &infoEx, sizeof(FILE_ID_INFO))) { - result = QByteArray::number(infoEx.VolumeSerialNumber, 16); - result += ':'; - // Note: MinGW-64's definition of FILE_ID_128 differs from the MSVC one. - result += QByteArray(reinterpret_cast(&infoEx.FileId), int(sizeof(infoEx.FileId))).toHex(); - } - return result; -} - -static QByteArray fileIdWin(HANDLE fHandle) -{ - return QOperatingSystemVersion::current() >= QOperatingSystemVersion::Windows8 ? - fileIdWin8(HANDLE(fHandle)) : fileIdWin7(HANDLE(fHandle)); -} -#endif - -QByteArray FileUtils::fileId(const FilePath &fileName) -{ - QByteArray result; - -#ifdef Q_OS_WIN - const HANDLE handle = - CreateFile((wchar_t*)fileName.toUserOutput().utf16(), 0, - FILE_SHARE_READ, NULL, OPEN_EXISTING, - FILE_FLAG_BACKUP_SEMANTICS, NULL); - if (handle != INVALID_HANDLE_VALUE) { - result = fileIdWin(handle); - CloseHandle(handle); - } -#else // Copied from qfilesystemengine_unix.cpp - if (Q_UNLIKELY(fileName.isEmpty())) - return result; - - QT_STATBUF statResult; - if (QT_STAT(fileName.toString().toLocal8Bit().constData(), &statResult)) - return result; - result = QByteArray::number(quint64(statResult.st_dev), 16); - result += ':'; - result += QByteArray::number(quint64(statResult.st_ino)); -#endif - return result; -} - -FilePath FileUtils::homePath() -{ - return FilePath::fromString(QDir::cleanPath(QDir::homePath())); -} - -bool FileUtils::renameFile(const FilePath &srcFilePath, const FilePath &tgtFilePath) -{ - QTC_ASSERT(!srcFilePath.needsDevice(), return false); - QTC_ASSERT(srcFilePath.scheme() == tgtFilePath.scheme(), return false); - return QFile::rename(srcFilePath.path(), tgtFilePath.path()); -} +// FileReader QByteArray FileReader::fetchQrc(const QString &fileName) { @@ -544,6 +115,8 @@ bool FileReader::fetch(const FilePath &filePath, QIODevice::OpenMode mode, QWidg } #endif // QT_GUI_LIB +// FileSaver + FileSaverBase::FileSaverBase() = default; FileSaverBase::~FileSaverBase() = default; @@ -619,6 +192,7 @@ bool FileSaverBase::setResult(QXmlStreamWriter *stream) return setResult(!stream->hasError()); } +// FileSaver FileSaver::FileSaver(const FilePath &filePath, QIODevice::OpenMode mode) { @@ -692,904 +266,6 @@ TempFileSaver::~TempFileSaver() QFile::remove(m_filePath.toString()); } -/*! \class Utils::FilePath - - \brief The FilePath class is a light-weight convenience class for filenames. - - On windows filenames are compared case insensitively. -*/ - -FilePath::FilePath() -{ -} - -/// Constructs a FilePath from \a info -FilePath FilePath::fromFileInfo(const QFileInfo &info) -{ - return FilePath::fromString(info.absoluteFilePath()); -} - -/// \returns a QFileInfo -QFileInfo FilePath::toFileInfo() const -{ - return QFileInfo(m_data); -} - -FilePath FilePath::fromUrl(const QUrl &url) -{ - FilePath fn; - fn.m_scheme = url.scheme(); - fn.m_host = url.host(); - fn.m_data = url.path(); - return fn; -} - -/// \returns a QString for passing on to QString based APIs -QString FilePath::toString() const -{ - if (m_scheme.isEmpty()) - return m_data; - if (m_data.startsWith('/')) - return m_scheme + "://" + m_host + m_data; - return m_scheme + "://" + m_host + "/./" + m_data; -} - -QUrl FilePath::toUrl() const -{ - QUrl url; - url.setScheme(m_scheme); - url.setHost(m_host); - url.setPath(m_data); - return url; -} - -void FilePath::setDeviceFileHooks(const DeviceFileHooks &hooks) -{ - s_deviceHooks = hooks; -} - -/// \returns a QString to display to the user -/// Converts the separators to the native format -QString FilePath::toUserOutput() const -{ - if (m_scheme.isEmpty()) - return QDir::toNativeSeparators(m_data); - return toString(); -} - -QString FilePath::fileName() const -{ - const QChar slash = QLatin1Char('/'); - return m_data.mid(m_data.lastIndexOf(slash) + 1); -} - -QString FilePath::fileNameWithPathComponents(int pathComponents) const -{ - if (pathComponents < 0) - return m_data; - const QChar slash = QLatin1Char('/'); - int i = m_data.lastIndexOf(slash); - if (pathComponents == 0 || i == -1) - return m_data.mid(i + 1); - int component = i + 1; - // skip adjacent slashes - while (i > 0 && m_data.at(--i) == slash) - ; - while (i >= 0 && --pathComponents >= 0) { - i = m_data.lastIndexOf(slash, i); - component = i + 1; - while (i > 0 && m_data.at(--i) == slash) - ; - } - - // If there are no more slashes before the found one, return the entire string - if (i > 0 && m_data.lastIndexOf(slash, i) != -1) - return m_data.mid(component); - return m_data; -} - -/// \returns the base name of the file without the path. -/// -/// The base name consists of all characters in the file up to -/// (but not including) the first '.' character. - -QString FilePath::baseName() const -{ - const QString &name = fileName(); - return name.left(name.indexOf('.')); -} - -/// \returns the complete base name of the file without the path. -/// -/// The complete base name consists of all characters in the file up to -/// (but not including) the last '.' character - -QString FilePath::completeBaseName() const -{ - const QString &name = fileName(); - return name.left(name.lastIndexOf('.')); -} - -/// \returns the suffix (extension) of the file. -/// -/// The suffix consists of all characters in the file after -/// (but not including) the last '.'. - -QString FilePath::suffix() const -{ - const QString &name = fileName(); - const int index = name.lastIndexOf('.'); - if (index >= 0) - return name.mid(index + 1); - return {}; -} - -/// \returns the complete suffix (extension) of the file. -/// -/// The complete suffix consists of all characters in the file after -/// (but not including) the first '.'. - -QString FilePath::completeSuffix() const -{ - const QString &name = fileName(); - const int index = name.indexOf('.'); - if (index >= 0) - return name.mid(index + 1); - return {}; -} - -void FilePath::setScheme(const QString &scheme) -{ - QTC_CHECK(!scheme.contains('/')); - m_scheme = scheme; -} - -void FilePath::setHost(const QString &host) -{ - QTC_CHECK(!host.contains('/')); - m_host = host; -} - - -/// \returns a bool indicating whether a file with this -/// FilePath exists. -bool FilePath::exists() const -{ - if (needsDevice()) { - QTC_ASSERT(s_deviceHooks.exists, return false); - return s_deviceHooks.exists(*this); - } - return !isEmpty() && QFileInfo::exists(m_data); -} - -/// \returns a bool indicating whether a path is writable. -bool FilePath::isWritableDir() const -{ - if (needsDevice()) { - QTC_ASSERT(s_deviceHooks.isWritableDir, return false); - return s_deviceHooks.isWritableDir(*this); - } - const QFileInfo fi{m_data}; - return exists() && fi.isDir() && fi.isWritable(); -} - -bool FilePath::isWritableFile() const -{ - if (needsDevice()) { - QTC_ASSERT(s_deviceHooks.isWritableFile, return false); - return s_deviceHooks.isWritableFile(*this); - } - const QFileInfo fi{m_data}; - return fi.exists() && fi.isWritable() && !fi.isDir(); -} - -bool FilePath::ensureWritableDir() const -{ - if (needsDevice()) { - QTC_ASSERT(s_deviceHooks.ensureWritableDir, return false); - return s_deviceHooks.ensureWritableDir(*this); - } - const QFileInfo fi{m_data}; - if (exists() && fi.isDir() && fi.isWritable()) - return true; - return QDir().mkpath(m_data); -} - -bool FilePath::ensureExistingFile() const -{ - if (needsDevice()) { - QTC_ASSERT(s_deviceHooks.ensureExistingFile, return false); - return s_deviceHooks.ensureExistingFile(*this); - } - QFile f(m_data); - if (f.exists()) - return true; - f.open(QFile::WriteOnly); - f.close(); - return f.exists(); -} - -bool FilePath::isExecutableFile() const -{ - if (needsDevice()) { - QTC_ASSERT(s_deviceHooks.isExecutableFile, return false); - return s_deviceHooks.isExecutableFile(*this); - } - const QFileInfo fi{m_data}; - return fi.exists() && fi.isExecutable() && !fi.isDir(); -} - -bool FilePath::isReadableFile() const -{ - if (needsDevice()) { - QTC_ASSERT(s_deviceHooks.isReadableFile, return false); - return s_deviceHooks.isReadableFile(*this); - } - const QFileInfo fi{m_data}; - return fi.exists() && fi.isReadable() && !fi.isDir(); -} - -bool FilePath::isReadableDir() const -{ - if (needsDevice()) { - QTC_ASSERT(s_deviceHooks.isReadableDir, return false); - return s_deviceHooks.isReadableDir(*this); - } - const QFileInfo fi{m_data}; - return fi.exists() && fi.isReadable() && fi.isDir(); -} - -bool FilePath::isFile() const -{ - if (needsDevice()) { - QTC_ASSERT(s_deviceHooks.isFile, return false); - return s_deviceHooks.isFile(*this); - } - const QFileInfo fi{m_data}; - return fi.exists() && fi.isFile(); -} - -bool FilePath::isDir() const -{ - if (needsDevice()) { - QTC_ASSERT(s_deviceHooks.isDir, return false); - return s_deviceHooks.isDir(*this); - } - const QFileInfo fi{m_data}; - return fi.exists() && fi.isDir(); -} - -bool FilePath::createDir() const -{ - if (needsDevice()) { - QTC_ASSERT(s_deviceHooks.createDir, return false); - return s_deviceHooks.createDir(*this); - } - QDir dir(m_data); - return dir.mkpath(dir.absolutePath()); -} - -FilePaths FilePath::dirEntries(const QStringList &nameFilters, - QDir::Filters filters, - QDir::SortFlags sort) const -{ - if (needsDevice()) { - QTC_ASSERT(s_deviceHooks.dirEntries, return {}); - return s_deviceHooks.dirEntries(*this, nameFilters, filters, sort); - } - - const QFileInfoList entryInfoList = QDir(m_data).entryInfoList(nameFilters, filters, sort); - return Utils::transform(entryInfoList, &FilePath::fromFileInfo); -} - -FilePaths FilePath::filterEntriesHelper(const FilePath &base, - const QStringList &entries, - const QStringList &nameFilters, - QDir::Filters filters, - QDir::SortFlags sort) -{ - const QList nameRegexps = transform(nameFilters, [](const QString &filter) { - QRegularExpression re; - re.setPattern(QRegularExpression::wildcardToRegularExpression(filter)); - QTC_CHECK(re.isValid()); - return re; - }); - - const auto nameMatches = [&nameRegexps](const QString &fileName) { - for (const QRegularExpression &re : nameRegexps) { - const QRegularExpressionMatch match = re.match(fileName); - if (match.hasMatch()) - return true; - } - return false; - }; - - // FIXME: Handle sort and filters. For now bark on unsupported options. - QTC_CHECK(filters == QDir::NoFilter); - QTC_CHECK(sort == QDir::NoSort); - - FilePaths result; - for (const QString &entry : entries) { - if (!nameMatches(entry)) - continue; - result.append(base.pathAppended(entry)); - } - return result; -} - -QList FilePath::dirEntries(QDir::Filters filters) const -{ - return dirEntries({}, filters); -} - -QByteArray FilePath::fileContents(qint64 maxSize, qint64 offset) const -{ - if (needsDevice()) { - QTC_ASSERT(s_deviceHooks.fileContents, return {}); - return s_deviceHooks.fileContents(*this, maxSize, offset); - } - - const QString path = toString(); - QFile f(path); - if (!f.exists()) - return {}; - - if (!f.open(QFile::ReadOnly)) - return {}; - - if (offset != 0) - f.seek(offset); - - if (maxSize != -1) - return f.read(maxSize); - - return f.readAll(); -} - -bool FilePath::writeFileContents(const QByteArray &data) const -{ - if (needsDevice()) { - QTC_ASSERT(s_deviceHooks.writeFileContents, return {}); - return s_deviceHooks.writeFileContents(*this, data); - } - - QFile file(path()); - QTC_ASSERT(file.open(QFile::WriteOnly | QFile::Truncate), return false); - qint64 res = file.write(data); - return res == data.size(); -} - -bool FilePath::needsDevice() const -{ - return !m_scheme.isEmpty(); -} - -/// \returns an empty FilePath if this is not a symbolic linl -FilePath FilePath::symLinkTarget() const -{ - if (needsDevice()) { - QTC_ASSERT(s_deviceHooks.symLinkTarget, return {}); - return s_deviceHooks.symLinkTarget(*this); - } - const QFileInfo info(m_data); - if (!info.isSymLink()) - return {}; - return FilePath::fromString(info.symLinkTarget()); -} - -FilePath FilePath::withExecutableSuffix() const -{ - FilePath res = *this; - res.setPath(OsSpecificAspects::withExecutableSuffix(osType(), m_data)); - return res; -} - -/// Find the parent directory of a given directory. - -/// Returns an empty FilePath if the current directory is already -/// a root level directory. - -/// \returns \a FilePath with the last segment removed. -FilePath FilePath::parentDir() const -{ - const QString basePath = path(); - if (basePath.isEmpty()) - return FilePath(); - - const QDir base(basePath); - if (base.isRoot()) - return FilePath(); - - const QString path = basePath + QLatin1String("/.."); - const QString parent = QDir::cleanPath(path); - QTC_ASSERT(parent != path, return FilePath()); - - FilePath result = *this; - result.setPath(parent); - return result; -} - -FilePath FilePath::absolutePath() const -{ - FilePath result = *this; - result.m_data = QFileInfo(m_data).absolutePath(); - return result; -} - -FilePath FilePath::absoluteFilePath() const -{ - FilePath result = *this; - result.m_data = QFileInfo(m_data).absoluteFilePath(); - return result; -} - -FilePath FilePath::absoluteFilePath(const FilePath &tail) const -{ - if (isRelativePathHelper(tail.m_data, osType())) - return pathAppended(tail.m_data); - return tail; -} - -/// Constructs an absolute FilePath from this path which -/// is interpreted as being relative to \a anchor. -FilePath FilePath::absoluteFromRelativePath(const FilePath &anchor) const -{ - QDir anchorDir = QFileInfo(anchor.m_data).absoluteDir(); - QString absoluteFilePath = QFileInfo(anchorDir, m_data).canonicalFilePath(); - return FilePath::fromString(absoluteFilePath); -} - -/// Constructs a FilePath from \a filename -/// \a filename is not checked for validity. -FilePath FilePath::fromString(const QString &filename) -{ - FilePath fn; - if (filename.startsWith('/')) { - fn.m_data = filename; // fast track: absolute local paths - } else { - int pos1 = filename.indexOf("://"); - if (pos1 >= 0) { - fn.m_scheme = filename.left(pos1); - pos1 += 3; - int pos2 = filename.indexOf('/', pos1); - if (pos2 == -1) { - fn.m_data = filename.mid(pos1); - } else { - fn.m_host = filename.mid(pos1, pos2 - pos1); - fn.m_data = filename.mid(pos2); - } - if (fn.m_data.startsWith("/./")) - fn.m_data = fn.m_data.mid(3); - } else { - fn.m_data = filename; // treat everything else as local, too. - } - } - return fn; -} - -/// Constructs a FilePath from \a filePath. The \a defaultExtension is appended -/// to \a filename if that does not have an extension already. -/// \a filePath is not checked for validity. -FilePath FilePath::fromStringWithExtension(const QString &filepath, const QString &defaultExtension) -{ - if (filepath.isEmpty() || defaultExtension.isEmpty()) - return FilePath::fromString(filepath); - - QString rc = filepath; - QFileInfo fi(filepath); - // Add extension unless user specified something else - const QChar dot = QLatin1Char('.'); - if (!fi.fileName().contains(dot)) { - if (!defaultExtension.startsWith(dot)) - rc += dot; - rc += defaultExtension; - } - return FilePath::fromString(rc); -} - -/// Constructs a FilePath from \a filePath -/// \a filePath is only passed through QDir::fromNativeSeparators -FilePath FilePath::fromUserInput(const QString &filePath) -{ - QString clean = QDir::fromNativeSeparators(filePath); - if (clean.startsWith(QLatin1String("~/"))) - return FileUtils::homePath().pathAppended(clean.mid(2)); - return FilePath::fromString(clean); -} - -/// Constructs a FilePath from \a filePath, which is encoded as UTF-8. -/// \a filePath is not checked for validity. -FilePath FilePath::fromUtf8(const char *filename, int filenameSize) -{ - return FilePath::fromString(QString::fromUtf8(filename, filenameSize)); -} - -FilePath FilePath::fromVariant(const QVariant &variant) -{ - if (variant.type() == QVariant::Url) - return FilePath::fromUrl(variant.toUrl()); - return FilePath::fromString(variant.toString()); -} - -QVariant FilePath::toVariant() const -{ - return toString(); -} - -QDir FilePath::toDir() const -{ - return QDir(m_data); -} - -bool FilePath::operator==(const FilePath &other) const -{ - return QString::compare(m_data, other.m_data, caseSensitivity()) == 0 - && m_host == other.m_host - && m_scheme == other.m_scheme; -} - -bool FilePath::operator!=(const FilePath &other) const -{ - return !(*this == other); -} - -bool FilePath::operator<(const FilePath &other) const -{ - const int cmp = QString::compare(m_data, other.m_data, caseSensitivity()); - if (cmp != 0) - return cmp < 0; - if (m_host != other.m_host) - return m_host < other.m_host; - return m_scheme < other.m_scheme; -} - -bool FilePath::operator<=(const FilePath &other) const -{ - return !(other < *this); -} - -bool FilePath::operator>(const FilePath &other) const -{ - return other < *this; -} - -bool FilePath::operator>=(const FilePath &other) const -{ - return !(*this < other); -} - -FilePath FilePath::operator+(const QString &s) const -{ - FilePath res = *this; - res.m_data += s; - return res; -} - -/// \returns whether FilePath is a child of \a s -bool FilePath::isChildOf(const FilePath &s) const -{ - if (s.isEmpty()) - return false; - if (!m_data.startsWith(s.m_data, caseSensitivity())) - return false; - if (m_data.size() <= s.m_data.size()) - return false; - // s is root, '/' was already tested in startsWith - if (s.m_data.endsWith(QLatin1Char('/'))) - return true; - // s is a directory, next character should be '/' (/tmpdir is NOT a child of /tmp) - return m_data.at(s.m_data.size()) == QLatin1Char('/'); -} - -/// \overload -bool FilePath::isChildOf(const QDir &dir) const -{ - return isChildOf(FilePath::fromString(dir.absolutePath())); -} - -/// \returns whether FilePath startsWith \a s -bool FilePath::startsWith(const QString &s) const -{ - return m_data.startsWith(s, caseSensitivity()); -} - -/// \returns whether FilePath endsWith \a s -bool FilePath::endsWith(const QString &s) const -{ - return m_data.endsWith(s, caseSensitivity()); -} - -/// \returns the relativeChildPath of FilePath to parent if FilePath is a child of parent -/// \note returns a empty FilePath if FilePath is not a child of parent -/// That is, this never returns a path starting with "../" -FilePath FilePath::relativeChildPath(const FilePath &parent) const -{ - FilePath res; - if (isChildOf(parent)) - res.m_data = m_data.mid(parent.m_data.size() + 1, -1); - return res; -} - -/// \returns the relativePath of FilePath to given \a anchor. -/// Both, FilePath and anchor may be files or directories. -/// Example usage: -/// -/// \code -/// FilePath filePath("/foo/b/ar/file.txt"); -/// FilePath relativePath = filePath.relativePath("/foo/c"); -/// qDebug() << relativePath -/// \endcode -/// -/// The debug output will be "../b/ar/file.txt". -/// -FilePath FilePath::relativePath(const FilePath &anchor) const -{ - const QFileInfo fileInfo(m_data); - QString absolutePath; - QString filename; - if (fileInfo.isFile()) { - absolutePath = fileInfo.absolutePath(); - filename = fileInfo.fileName(); - } else if (fileInfo.isDir()) { - absolutePath = fileInfo.absoluteFilePath(); - } else { - return {}; - } - const QFileInfo anchorInfo(anchor.m_data); - QString absoluteAnchorPath; - if (anchorInfo.isFile()) - absoluteAnchorPath = anchorInfo.absolutePath(); - else if (anchorInfo.isDir()) - absoluteAnchorPath = anchorInfo.absoluteFilePath(); - else - return {}; - QString relativeFilePath = calcRelativePath(absolutePath, absoluteAnchorPath); - if (!filename.isEmpty()) { - if (!relativeFilePath.isEmpty()) - relativeFilePath += '/'; - relativeFilePath += filename; - } - return FilePath::fromString(relativeFilePath); -} - -/// \returns the relativePath of \a absolutePath to given \a absoluteAnchorPath. -/// Both paths must be an absolute path to a directory. Example usage: -/// -/// \code -/// qDebug() << FilePath::calcRelativePath("/foo/b/ar", "/foo/c"); -/// \endcode -/// -/// The debug output will be "../b/ar". -/// -/// \see FilePath::relativePath -/// -QString FilePath::calcRelativePath(const QString &absolutePath, const QString &absoluteAnchorPath) -{ - if (absolutePath.isEmpty() || absoluteAnchorPath.isEmpty()) - return QString(); - // TODO using split() instead of parsing the strings by char index is slow - // and needs more memory (but the easiest implementation for now) - const QStringList splits1 = absolutePath.split('/'); - const QStringList splits2 = absoluteAnchorPath.split('/'); - int i = 0; - while (i < splits1.count() && i < splits2.count() && splits1.at(i) == splits2.at(i)) - ++i; - QString relativePath; - int j = i; - bool addslash = false; - while (j < splits2.count()) { - if (!splits2.at(j).isEmpty()) { - if (addslash) - relativePath += '/'; - relativePath += ".."; - addslash = true; - } - ++j; - } - while (i < splits1.count()) { - if (!splits1.at(i).isEmpty()) { - if (addslash) - relativePath += '/'; - relativePath += splits1.at(i); - addslash = true; - } - ++i; - } - return relativePath; -} - -/*! - Returns a path corresponding to the current object on the - same device as \a deviceTemplate. - - Example usage: - \code - localDir = FilePath::fromString("/tmp/workingdir"); - executable = FilePath::fromUrl("docker://123/bin/ls") - realDir = localDir.onDevice(executable) - assert(realDir == FilePath::fromUrl("docker://123/tmp/workingdir")) - \endcode -*/ -FilePath FilePath::onDevice(const FilePath &deviceTemplate) const -{ - FilePath res; - res.m_data = m_data; - res.m_host = deviceTemplate.m_host; - res.m_scheme = deviceTemplate.m_scheme; - return res; -} - -/*! - Returns a FilePath with local path \a newPath on the same device - as the current object. - - Example usage: - \code - devicePath = FilePath::fromString("docker://123/tmp"); - newPath = devicePath.withNewPath("/bin/ls"); - assert(realDir == FilePath::fromUrl("docker://123/bin/ls")) - \endcode -*/ -FilePath FilePath::withNewPath(const QString &newPath) const -{ - FilePath res; - res.m_data = newPath; - res.m_host = m_host; - res.m_scheme = m_scheme; - return res; -} - -/*! - Searched a binary corresponding to this object in the PATH of - the device implied by this object's scheme and host. - - Example usage: - \code - binary = FilePath::fromUrl("docker://123/./make); - fullPath = binary.searchOnDevice(); - assert(fullPath == FilePath::fromUrl("docker://123/usr/bin/make")) - \endcode -*/ -FilePath FilePath::searchOnDevice(const FilePaths &dirs) const -{ - if (needsDevice()) { - QTC_ASSERT(s_deviceHooks.searchInPath, return {}); - return s_deviceHooks.searchInPath(*this, dirs); - } - return Environment::systemEnvironment().searchInPath(path(), dirs); -} - -Environment FilePath::deviceEnvironment() const -{ - if (needsDevice()) { - QTC_ASSERT(s_deviceHooks.environment, return {}); - return s_deviceHooks.environment(*this); - } - return Environment::systemEnvironment(); -} - -QString FilePath::formatFilePaths(const QList &files, const QString &separator) -{ - const QStringList nativeFiles = Utils::transform(files, &FilePath::toUserOutput); - return nativeFiles.join(separator); -} - -void FilePath::removeDuplicates(QList &files) -{ - // FIXME: Improve. - QStringList list = Utils::transform(files, &FilePath::toString); - list.removeDuplicates(); - files = Utils::transform(list, &FilePath::fromString); -} - -void FilePath::sort(QList &files) -{ - // FIXME: Improve. - QStringList list = Utils::transform(files, &FilePath::toString); - list.sort(); - files = Utils::transform(list, &FilePath::fromString); -} - -FilePath FilePath::pathAppended(const QString &path) const -{ - FilePath fn = *this; - if (path.isEmpty()) - return fn; - if (!fn.m_data.isEmpty() && !fn.m_data.endsWith(QLatin1Char('/'))) - fn.m_data.append('/'); - fn.m_data.append(path); - return fn; -} - -FilePath FilePath::stringAppended(const QString &str) const -{ - FilePath fn = *this; - fn.m_data.append(str); - return fn; -} - -uint FilePath::hash(uint seed) const -{ - if (Utils::HostOsInfo::fileNameCaseSensitivity() == Qt::CaseInsensitive) - return qHash(m_data.toUpper(), seed); - return qHash(m_data, seed); -} - -QDateTime FilePath::lastModified() const -{ - if (needsDevice()) { - QTC_ASSERT(s_deviceHooks.lastModified, return {}); - return s_deviceHooks.lastModified(*this); - } - return toFileInfo().lastModified(); -} - -QFile::Permissions FilePath::permissions() const -{ - if (needsDevice()) { - QTC_ASSERT(s_deviceHooks.permissions, return {}); - return s_deviceHooks.permissions(*this); - } - return toFileInfo().permissions(); -} - -OsType FilePath::osType() const -{ - if (needsDevice()) { - QTC_ASSERT(s_deviceHooks.osType, return {}); - return s_deviceHooks.osType(*this); - } - return HostOsInfo::hostOs(); -} - -bool FilePath::removeFile() const -{ - if (needsDevice()) { - QTC_ASSERT(s_deviceHooks.removeFile, return false); - return s_deviceHooks.removeFile(*this); - } - return QFile::remove(path()); -} - -/*! - Removes the directory this filePath refers too and its subdirectories recursively. - - \note The \a error parameter is optional. - - Returns whether the operation succeeded. -*/ -bool FilePath::removeRecursively(QString *error) const -{ - if (needsDevice()) { - QTC_ASSERT(s_deviceHooks.removeRecursively, return false); - return s_deviceHooks.removeRecursively(*this); - } - return removeRecursivelyLocal(*this, error); -} - -bool FilePath::copyFile(const FilePath &target) const -{ - if (needsDevice()) { - QTC_ASSERT(s_deviceHooks.copyFile, return false); - return s_deviceHooks.copyFile(*this, target); - } - return QFile::copy(path(), target.path()); -} - -bool FilePath::renameFile(const FilePath &target) const -{ - if (needsDevice()) { - QTC_ASSERT(s_deviceHooks.renameFile, return false); - return s_deviceHooks.renameFile(*this, target); - } - return QFile::rename(path(), target.path()); -} - -QTextStream &operator<<(QTextStream &s, const FilePath &fn) -{ - return s << fn.toString(); -} - #ifdef QT_GUI_LIB FileUtils::CopyAskingForOverwrite::CopyAskingForOverwrite( QWidget *dialogParent, const std::function &postOperation) @@ -1652,6 +328,74 @@ FilePaths FileUtils::CopyAskingForOverwrite::files() const } #endif // QT_GUI_LIB +// Copied from qfilesystemengine_win.cpp +#ifdef Q_OS_WIN + +// File ID for Windows up to version 7. +static inline QByteArray fileIdWin7(HANDLE handle) +{ + BY_HANDLE_FILE_INFORMATION info; + if (GetFileInformationByHandle(handle, &info)) { + char buffer[sizeof "01234567:0123456701234567\0"]; + qsnprintf(buffer, sizeof(buffer), "%lx:%08lx%08lx", + info.dwVolumeSerialNumber, + info.nFileIndexHigh, + info.nFileIndexLow); + return QByteArray(buffer); + } + return QByteArray(); +} + +// File ID for Windows starting from version 8. +static QByteArray fileIdWin8(HANDLE handle) +{ + QByteArray result; + FILE_ID_INFO infoEx; + if (GetFileInformationByHandleEx(handle, + static_cast(18), // FileIdInfo in Windows 8 + &infoEx, sizeof(FILE_ID_INFO))) { + result = QByteArray::number(infoEx.VolumeSerialNumber, 16); + result += ':'; + // Note: MinGW-64's definition of FILE_ID_128 differs from the MSVC one. + result += QByteArray(reinterpret_cast(&infoEx.FileId), int(sizeof(infoEx.FileId))).toHex(); + } + return result; +} + +static QByteArray fileIdWin(HANDLE fHandle) +{ + return QOperatingSystemVersion::current() >= QOperatingSystemVersion::Windows8 ? + fileIdWin8(HANDLE(fHandle)) : fileIdWin7(HANDLE(fHandle)); +} +#endif + +QByteArray FileUtils::fileId(const FilePath &fileName) +{ + QByteArray result; + +#ifdef Q_OS_WIN + const HANDLE handle = + CreateFile((wchar_t*)fileName.toUserOutput().utf16(), 0, + FILE_SHARE_READ, NULL, OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS, NULL); + if (handle != INVALID_HANDLE_VALUE) { + result = fileIdWin(handle); + CloseHandle(handle); + } +#else // Copied from qfilesystemengine_unix.cpp + if (Q_UNLIKELY(fileName.isEmpty())) + return result; + + QT_STATBUF statResult; + if (QT_STAT(fileName.toString().toLocal8Bit().constData(), &statResult)) + return result; + result = QByteArray::number(quint64(statResult.st_dev), 16); + result += ':'; + result += QByteArray::number(quint64(statResult.st_ino)); +#endif + return result; +} + #ifdef Q_OS_WIN template <> void withNtfsPermissions(const std::function &task) @@ -1664,10 +408,3 @@ void withNtfsPermissions(const std::function &task) } // namespace Utils -std::hash::result_type - std::hash::operator()(const std::hash::argument_type &fn) const -{ - if (fn.caseSensitivity() == Qt::CaseInsensitive) - return hash()(fn.toString().toUpper().toStdString()); - return hash()(fn.toString().toStdString()); -} diff --git a/src/libs/utils/fileutils.h b/src/libs/utils/fileutils.h index 1adf0c06574..9901f678763 100644 --- a/src/libs/utils/fileutils.h +++ b/src/libs/utils/fileutils.h @@ -27,6 +27,7 @@ #include "utils_global.h" +#include "filepath.h" #include "hostosinfo.h" #include @@ -40,33 +41,17 @@ #include #include -namespace Utils { -class Environment; -class FilePath; -} // Utils - QT_BEGIN_NAMESPACE class QDataStream; -class QDateTime; -class QDir; -class QFile; -class QFileInfo; -class QTemporaryFile; class QTextStream; class QWidget; -QTCREATOR_UTILS_EXPORT QDebug operator<<(QDebug dbg, const Utils::FilePath &c); - // for withNtfsPermissions #ifdef Q_OS_WIN extern Q_CORE_EXPORT int qt_ntfs_permission_lookup; #endif - QT_END_NAMESPACE -// tst_fileutils becomes a friend of Utils::FilePath for testing private method -class tst_fileutils; - namespace Utils { class DeviceFileHooks @@ -99,151 +84,6 @@ public: std::function environment; }; -class QTCREATOR_UTILS_EXPORT FilePath -{ -public: - FilePath(); - - static FilePath fromString(const QString &filepath); - static FilePath fromFileInfo(const QFileInfo &info); - static FilePath fromStringWithExtension(const QString &filepath, const QString &defaultExtension); - static FilePath fromUserInput(const QString &filepath); - static FilePath fromUtf8(const char *filepath, int filepathSize = -1); - static FilePath fromVariant(const QVariant &variant); - - QString toString() const; - FilePath onDevice(const FilePath &deviceTemplate) const; - FilePath withNewPath(const QString &newPath) const; - - QFileInfo toFileInfo() const; - QVariant toVariant() const; - QDir toDir() const; - - QString toUserOutput() const; - QString shortNativePath() const; - - QString fileName() const; - QString fileNameWithPathComponents(int pathComponents) const; - - QString baseName() const; - QString completeBaseName() const; - QString suffix() const; - QString completeSuffix() const; - - QString scheme() const { return m_scheme; } - void setScheme(const QString &scheme); - - QString host() const { return m_host; } - void setHost(const QString &host); - - QString path() const { return m_data; } - void setPath(const QString &path) { m_data = path; } - - bool needsDevice() const; - bool exists() const; - - bool isWritablePath() const { return isWritableDir(); } // Remove. - bool isWritableDir() const; - bool isWritableFile() const; - bool ensureWritableDir() const; - bool ensureExistingFile() const; - bool isExecutableFile() const; - bool isReadableFile() const; - bool isReadableDir() const; - bool isRelativePath() const; - bool isAbsolutePath() const { return !isRelativePath(); } - bool isFile() const; - bool isDir() const; - - bool createDir() const; - QList dirEntries(const QStringList &nameFilters, - QDir::Filters filters, - QDir::SortFlags sort = QDir::NoSort) const; - QList dirEntries(QDir::Filters filters) const; - QByteArray fileContents(qint64 maxSize = -1, qint64 offset = 0) const; - bool writeFileContents(const QByteArray &data) const; - - FilePath parentDir() const; - FilePath absolutePath() const; - FilePath absoluteFilePath() const; - FilePath absoluteFilePath(const FilePath &tail) const; - FilePath absoluteFromRelativePath(const FilePath &anchor) const; - - bool operator==(const FilePath &other) const; - bool operator!=(const FilePath &other) const; - bool operator<(const FilePath &other) const; - bool operator<=(const FilePath &other) const; - bool operator>(const FilePath &other) const; - bool operator>=(const FilePath &other) const; - FilePath operator+(const QString &s) const; - - bool isChildOf(const FilePath &s) const; - bool isChildOf(const QDir &dir) const; - bool startsWith(const QString &s) const; - bool endsWith(const QString &s) const; - - bool isNewerThan(const QDateTime &timeStamp) const; - QDateTime lastModified() const; - QFile::Permissions permissions() const; - OsType osType() const; - bool removeFile() const; - bool removeRecursively(QString *error = nullptr) const; - bool copyFile(const FilePath &target) const; - bool renameFile(const FilePath &target) const; - - Qt::CaseSensitivity caseSensitivity() const; - - FilePath relativeChildPath(const FilePath &parent) const; - FilePath relativePath(const FilePath &anchor) const; - FilePath pathAppended(const QString &str) const; - FilePath stringAppended(const QString &str) const; - FilePath resolvePath(const QString &fileName) const; - FilePath cleanPath() const; - - FilePath canonicalPath() const; - FilePath symLinkTarget() const; - FilePath resolveSymlinks() const; - FilePath withExecutableSuffix() const; - - FilePath operator/(const QString &str) const; - - void clear(); - bool isEmpty() const; - - uint hash(uint seed) const; - - // NOTE: Most FilePath operations on FilePath created from URL currently - // do not work. Among the working are .toVariant() and .toUrl(). - static FilePath fromUrl(const QUrl &url); - QUrl toUrl() const; - - static void setDeviceFileHooks(const DeviceFileHooks &hooks); - - FilePath searchOnDevice(const QList &dirs) const; - Environment deviceEnvironment() const; - - static QString formatFilePaths(const QList &files, const QString &separator); - static void removeDuplicates(QList &files); - static void sort(QList &files); - - static QList filterEntriesHelper(const FilePath &base, - const QStringList &entries, - const QStringList &nameFilters, - QDir::Filters filters, - QDir::SortFlags sort); -private: - friend class ::tst_fileutils; - static QString calcRelativePath(const QString &absolutePath, const QString &absoluteAnchorPath); - - QString m_scheme; - QString m_host; - QString m_data; -}; - -QTCREATOR_UTILS_EXPORT QTextStream &operator<<(QTextStream &s, const FilePath &fn); - -using FilePaths = QList; - class QTCREATOR_UTILS_EXPORT FileUtils { public: #ifdef QT_GUI_LIB @@ -286,6 +126,8 @@ public: static QByteArray fileId(const FilePath &fileName); static FilePath homePath(); static bool renameFile(const FilePath &srcFilePath, const FilePath &tgtFilePath); + + static void setDeviceFileHooks(const DeviceFileHooks &hooks); }; template @@ -431,6 +273,8 @@ private: bool m_autoRemove = true; }; +QTCREATOR_UTILS_EXPORT QTextStream &operator<<(QTextStream &s, const FilePath &fn); + inline uint qHash(const Utils::FilePath &a, uint seed = 0) { return a.hash(seed); } } // namespace Utils @@ -444,4 +288,3 @@ template<> struct QTCREATOR_UTILS_EXPORT hash }; } // namespace std -Q_DECLARE_METATYPE(Utils::FilePath) diff --git a/src/libs/utils/utils-lib.pri b/src/libs/utils/utils-lib.pri index b3d6a7a6dd0..4bc4f9e0158 100644 --- a/src/libs/utils/utils-lib.pri +++ b/src/libs/utils/utils-lib.pri @@ -62,6 +62,7 @@ SOURCES += \ $$PWD/fancylineedit.cpp \ $$PWD/qtcolorbutton.cpp \ $$PWD/savefile.cpp \ + $$PWD/filepath.cpp \ $$PWD/fileutils.cpp \ $$PWD/textfileformat.cpp \ $$PWD/consoleprocess.cpp \ @@ -193,6 +194,7 @@ HEADERS += \ $$PWD/qtcolorbutton.h \ $$PWD/consoleprocess.h \ $$PWD/savefile.h \ + $$PWD/filepath.h \ $$PWD/fileutils.h \ $$PWD/textfileformat.h \ $$PWD/uncommentselection.h \ diff --git a/src/libs/utils/utils.qbs b/src/libs/utils/utils.qbs index 7a78cb1f414..91f5be066f5 100644 --- a/src/libs/utils/utils.qbs +++ b/src/libs/utils/utils.qbs @@ -115,6 +115,8 @@ Project { "fileinprojectfinder.h", "filenamevalidatinglineedit.cpp", "filenamevalidatinglineedit.h", + "filepath.cpp", + "filepath.h", "filesearch.cpp", "filesearch.h", "filesystemwatcher.cpp", diff --git a/src/plugins/projectexplorer/devicesupport/devicemanager.cpp b/src/plugins/projectexplorer/devicesupport/devicemanager.cpp index 41ca11cd90e..08260700a18 100644 --- a/src/plugins/projectexplorer/devicesupport/devicemanager.cpp +++ b/src/plugins/projectexplorer/devicesupport/devicemanager.cpp @@ -525,7 +525,7 @@ DeviceManager::DeviceManager(bool isInstance) : d(std::make_uniquesystemEnvironment(); }; - FilePath::setDeviceFileHooks(deviceHooks); + FileUtils::setDeviceFileHooks(deviceHooks); DeviceProcessHooks processHooks; diff --git a/src/tools/sdktool/CMakeLists.txt b/src/tools/sdktool/CMakeLists.txt index dc2320a92c4..e5197c59526 100644 --- a/src/tools/sdktool/CMakeLists.txt +++ b/src/tools/sdktool/CMakeLists.txt @@ -79,6 +79,7 @@ extend_qtc_executable(sdktool DEFINES QTCREATOR_UTILS_STATIC_LIB SOURCES environment.cpp environment.h + filepath.cpp filepath.h fileutils.cpp fileutils.h hostosinfo.cpp hostosinfo.h namevaluedictionary.cpp namevaluedictionary.h diff --git a/src/tools/sdktool/sdktool.pro b/src/tools/sdktool/sdktool.pro index 0b58755a1e5..aa1edfb504b 100644 --- a/src/tools/sdktool/sdktool.pro +++ b/src/tools/sdktool/sdktool.pro @@ -32,6 +32,7 @@ SOURCES += \ rmtoolchainoperation.cpp \ settings.cpp \ $$UTILS/environment.cpp \ + $$UTILS/filepath.cpp \ $$UTILS/fileutils.cpp \ $$UTILS/hostosinfo.cpp \ $$UTILS/namevaluedictionary.cpp \ @@ -65,6 +66,7 @@ HEADERS += \ rmtoolchainoperation.h \ settings.h \ $$UTILS/environment.h \ + $$UTILS/filepath.h \ $$UTILS/fileutils.h \ $$UTILS/hostosinfo.h \ $$UTILS/namevaluedictionary.h \ diff --git a/src/tools/sdktool/sdktool.qbs b/src/tools/sdktool/sdktool.qbs index a6fd9ed8b84..bf9b42dfb12 100644 --- a/src/tools/sdktool/sdktool.qbs +++ b/src/tools/sdktool/sdktool.qbs @@ -70,6 +70,7 @@ QtcTool { files: [ "commandline.cpp", "commandline.h", "environment.cpp", "environment.h", + "filepath.cpp", "filepath.h", "fileutils.cpp", "fileutils.h", "hostosinfo.cpp", "hostosinfo.h", "namevaluedictionary.cpp", "namevaluedictionary.h", diff --git a/tests/auto/debugger/gdb.pro b/tests/auto/debugger/gdb.pro index d09e1a76745..80e1322288c 100644 --- a/tests/auto/debugger/gdb.pro +++ b/tests/auto/debugger/gdb.pro @@ -19,6 +19,7 @@ HEADERS += \ SOURCES += \ $$UTILSDIR/commandline.cpp \ $$UTILSDIR/environment.cpp \ + $$UTILSDIR/filepath.cpp \ $$UTILSDIR/fileutils.cpp \ $$UTILSDIR/hostosinfo.cpp \ $$UTILSDIR/namevaluedictionary.cpp \ @@ -31,6 +32,7 @@ HEADERS += \ HEADERS += \ $$UTILSDIR/commandline.h \ $$UTILSDIR/environment.h \ + $$UTILSDIR/filepath.h \ $$UTILSDIR/fileutils.h \ $$UTILSDIR/hostosinfo.h \ $$UTILSDIR/namevaluedictionary.h \ From a0da3ce69fc371eb86ba9740cf0c56179eea1c4e Mon Sep 17 00:00:00 2001 From: Christian Stenger Date: Thu, 22 Jul 2021 14:08:53 +0200 Subject: [PATCH 20/20] Utils: Move filterEntriesHelper() out of FilePath Currently only used inside DockerDevice and it fails to build when building sdktool with old Qt which is still necessary. Change-Id: Ic48414f0cb8f0955e7561f7484e3ea1c726c816a Reviewed-by: hjk --- src/libs/utils/filepath.cpp | 34 -------------------------- src/libs/utils/filepath.h | 5 ---- src/libs/utils/fileutils.cpp | 1 - src/plugins/docker/dockerdevice.cpp | 38 ++++++++++++++++++++++++++++- 4 files changed, 37 insertions(+), 41 deletions(-) diff --git a/src/libs/utils/filepath.cpp b/src/libs/utils/filepath.cpp index eda575945ae..a5264d17987 100644 --- a/src/libs/utils/filepath.cpp +++ b/src/libs/utils/filepath.cpp @@ -710,40 +710,6 @@ FilePaths FilePath::dirEntries(const QStringList &nameFilters, return Utils::transform(entryInfoList, &FilePath::fromFileInfo); } -FilePaths FilePath::filterEntriesHelper(const FilePath &base, - const QStringList &entries, - const QStringList &nameFilters, - QDir::Filters filters, - QDir::SortFlags sort) -{ - const QList nameRegexps = transform(nameFilters, [](const QString &filter) { - QRegularExpression re; - re.setPattern(QRegularExpression::wildcardToRegularExpression(filter)); - QTC_CHECK(re.isValid()); - return re; - }); - - const auto nameMatches = [&nameRegexps](const QString &fileName) { - for (const QRegularExpression &re : nameRegexps) { - const QRegularExpressionMatch match = re.match(fileName); - if (match.hasMatch()) - return true; - } - return false; - }; - - // FIXME: Handle sort and filters. For now bark on unsupported options. - QTC_CHECK(filters == QDir::NoFilter); - QTC_CHECK(sort == QDir::NoSort); - - FilePaths result; - for (const QString &entry : entries) { - if (!nameMatches(entry)) - continue; - result.append(base.pathAppended(entry)); - } - return result; -} QList FilePath::dirEntries(QDir::Filters filters) const { diff --git a/src/libs/utils/filepath.h b/src/libs/utils/filepath.h index d53357057db..6ece9ea162a 100644 --- a/src/libs/utils/filepath.h +++ b/src/libs/utils/filepath.h @@ -173,11 +173,6 @@ public: static void removeDuplicates(QList &files); static void sort(QList &files); - static QList filterEntriesHelper(const FilePath &base, - const QStringList &entries, - const QStringList &nameFilters, - QDir::Filters filters, - QDir::SortFlags sort); private: friend class ::tst_fileutils; static QString calcRelativePath(const QString &absolutePath, const QString &absoluteAnchorPath); diff --git a/src/libs/utils/fileutils.cpp b/src/libs/utils/fileutils.cpp index 54f3c1ddf7b..ff7745761c8 100644 --- a/src/libs/utils/fileutils.cpp +++ b/src/libs/utils/fileutils.cpp @@ -36,7 +36,6 @@ #include #include #include -#include #include #include #include diff --git a/src/plugins/docker/dockerdevice.cpp b/src/plugins/docker/dockerdevice.cpp index eb0508d4b2c..bacbc36204c 100644 --- a/src/plugins/docker/dockerdevice.cpp +++ b/src/plugins/docker/dockerdevice.cpp @@ -70,6 +70,7 @@ #include #include #include +#include #include #include #include @@ -1274,6 +1275,41 @@ FilePath DockerDevice::symLinkTarget(const FilePath &filePath) const return output.isEmpty() ? FilePath() : filePath.withNewPath(output); } +static FilePaths filterEntriesHelper(const FilePath &base, + const QStringList &entries, + const QStringList &nameFilters, + QDir::Filters filters, + QDir::SortFlags sort) +{ + const QList nameRegexps = transform(nameFilters, [](const QString &filter) { + QRegularExpression re; + re.setPattern(QRegularExpression::wildcardToRegularExpression(filter)); + QTC_CHECK(re.isValid()); + return re; + }); + + const auto nameMatches = [&nameRegexps](const QString &fileName) { + for (const QRegularExpression &re : nameRegexps) { + const QRegularExpressionMatch match = re.match(fileName); + if (match.hasMatch()) + return true; + } + return false; + }; + + // FIXME: Handle sort and filters. For now bark on unsupported options. + QTC_CHECK(filters == QDir::NoFilter); + QTC_CHECK(sort == QDir::NoSort); + + FilePaths result; + for (const QString &entry : entries) { + if (!nameMatches(entry)) + continue; + result.append(base.pathAppended(entry)); + } + return result; +} + FilePaths DockerDevice::directoryEntries(const FilePath &filePath, const QStringList &nameFilters, QDir::Filters filters, @@ -1290,7 +1326,7 @@ FilePaths DockerDevice::directoryEntries(const FilePath &filePath, const QString output = d->outputForRunInShell({"ls", {"-1", "-b", "--", filePath.path()}}); QStringList entries = output.split('\n', Qt::SkipEmptyParts); - return FilePath::filterEntriesHelper(filePath, entries, nameFilters, filters, sort); + return filterEntriesHelper(filePath, entries, nameFilters, filters, sort); } QByteArray DockerDevice::fileContents(const FilePath &filePath, qint64 limit, qint64 offset) const