forked from qt-creator/qt-creator
Utils: Make UnixDeviceFileAccess macOS compatible
"stat" on macOS has slightly different formatting options. Also adds unittests for UnixDeviceFileAccess Task-number: QTCREATORBUG-28142 Change-Id: Ib42fc1c22ef2771365e915df34f2286e2c705568 Reviewed-by: hjk <hjk@qt.io> Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org> Reviewed-by: <github-actions-qt-creator@cristianadam.eu>
This commit is contained in:
@@ -8,6 +8,7 @@
|
||||
#include "environment.h"
|
||||
#include "expected.h"
|
||||
#include "hostosinfo.h"
|
||||
#include "osspecificaspects.h"
|
||||
#include "qtcassert.h"
|
||||
#include "utilstr.h"
|
||||
|
||||
@@ -820,7 +821,7 @@ QByteArray DesktopDeviceFileAccess::fileId(const FilePath &filePath) const
|
||||
|
||||
OsType DesktopDeviceFileAccess::osType(const FilePath &filePath) const
|
||||
{
|
||||
Q_UNUSED(filePath);
|
||||
Q_UNUSED(filePath)
|
||||
return HostOsInfo::hostOs();
|
||||
}
|
||||
|
||||
@@ -1054,10 +1055,28 @@ expected_str<FilePath> UnixDeviceFileAccess::createTempFile(const FilePath &file
|
||||
return newPath;
|
||||
}
|
||||
|
||||
OsType UnixDeviceFileAccess::osType() const
|
||||
{
|
||||
if (m_osType)
|
||||
return *m_osType;
|
||||
|
||||
const RunResult result = runInShell({"uname", {"-s"}, OsType::OsTypeLinux});
|
||||
QTC_ASSERT(result.exitCode == 0, return OsTypeLinux);
|
||||
const QString osName = QString::fromUtf8(result.stdOut).trimmed();
|
||||
if (osName == "Darwin")
|
||||
m_osType = OsTypeMac;
|
||||
else if (osName == "Linux")
|
||||
m_osType = OsTypeLinux;
|
||||
else
|
||||
m_osType = OsTypeOtherUnix;
|
||||
|
||||
return *m_osType;
|
||||
}
|
||||
|
||||
OsType UnixDeviceFileAccess::osType(const FilePath &filePath) const
|
||||
{
|
||||
Q_UNUSED(filePath)
|
||||
return OsTypeLinux;
|
||||
Q_UNUSED(filePath);
|
||||
return osType();
|
||||
}
|
||||
|
||||
QDateTime UnixDeviceFileAccess::lastModified(const FilePath &filePath) const
|
||||
@@ -1069,10 +1088,19 @@ QDateTime UnixDeviceFileAccess::lastModified(const FilePath &filePath) const
|
||||
return dt;
|
||||
}
|
||||
|
||||
QStringList UnixDeviceFileAccess::statArgs(const FilePath &filePath,
|
||||
const QString &linuxFormat,
|
||||
const QString &macFormat) const
|
||||
{
|
||||
return (osType() == OsTypeMac ? QStringList{"-f", macFormat} : QStringList{"-c", linuxFormat})
|
||||
<< "-L" << filePath.path();
|
||||
}
|
||||
|
||||
QFile::Permissions UnixDeviceFileAccess::permissions(const FilePath &filePath) const
|
||||
{
|
||||
const RunResult result = runInShell(
|
||||
{"stat", {"-L", "-c", "%a", filePath.path()}, OsType::OsTypeLinux});
|
||||
QStringList args = statArgs(filePath, "%a", "%p");
|
||||
|
||||
const RunResult result = runInShell({"stat", args, OsType::OsTypeLinux});
|
||||
const uint bits = result.stdOut.toUInt(nullptr, 8);
|
||||
QFileDevice::Permissions perm = {};
|
||||
#define BIT(n, p) \
|
||||
@@ -1100,8 +1128,8 @@ bool UnixDeviceFileAccess::setPermissions(const FilePath &filePath, QFile::Permi
|
||||
|
||||
qint64 UnixDeviceFileAccess::fileSize(const FilePath &filePath) const
|
||||
{
|
||||
const RunResult result = runInShell(
|
||||
{"stat", {"-L", "-c", "%s", filePath.path()}, OsType::OsTypeLinux});
|
||||
const QStringList args = statArgs(filePath, "%s", "%z");
|
||||
const RunResult result = runInShell({"stat", args, OsType::OsTypeLinux});
|
||||
return result.stdOut.toLongLong();
|
||||
}
|
||||
|
||||
@@ -1113,8 +1141,9 @@ qint64 UnixDeviceFileAccess::bytesAvailable(const FilePath &filePath) const
|
||||
|
||||
QByteArray UnixDeviceFileAccess::fileId(const FilePath &filePath) const
|
||||
{
|
||||
const RunResult result = runInShell(
|
||||
{"stat", {"-L", "-c", "%D:%i", filePath.path()}, OsType::OsTypeLinux});
|
||||
const QStringList args = statArgs(filePath, "%D:%i", "%d:%i");
|
||||
|
||||
const RunResult result = runInShell({"stat", args, OsType::OsTypeLinux});
|
||||
if (result.exitCode != 0)
|
||||
return {};
|
||||
|
||||
@@ -1136,9 +1165,12 @@ FilePathInfo UnixDeviceFileAccess::filePathInfo(const FilePath &filePath) const
|
||||
|
||||
return r;
|
||||
}
|
||||
const RunResult stat = runInShell(
|
||||
{"stat", {"-L", "-c", "%f %Y %s", filePath.path()}, OsType::OsTypeLinux});
|
||||
return FileUtils::filePathInfoFromTriple(QString::fromLatin1(stat.stdOut));
|
||||
|
||||
const QStringList args = statArgs(filePath, "%f %Y %s", "%p %m %z");
|
||||
|
||||
const RunResult stat = runInShell({"stat", args, OsType::OsTypeLinux});
|
||||
return FileUtils::filePathInfoFromTriple(QString::fromLatin1(stat.stdOut),
|
||||
osType() != OsTypeMac);
|
||||
}
|
||||
|
||||
// returns whether 'find' could be used.
|
||||
@@ -1152,8 +1184,13 @@ bool UnixDeviceFileAccess::iterateWithFind(const FilePath &filePath,
|
||||
|
||||
// TODO: Using stat -L will always return the link target, not the link itself.
|
||||
// We may wan't to add the information that it is a link at some point.
|
||||
|
||||
const QString statFormat = osType() == OsTypeMac
|
||||
? QLatin1String("-f \"%p %m %z\"") : QLatin1String("-c \"%f %Y %s\"");
|
||||
|
||||
if (callBack.index() == 1)
|
||||
cmdLine.addArgs(R"(-exec echo -n \"{}\"" " \; -exec stat -L -c "%f %Y %s" "{}" \;)",
|
||||
cmdLine.addArgs(QString(R"(-exec echo -n \"{}\"" " \; -exec stat -L %1 "{}" \;)")
|
||||
.arg(statFormat),
|
||||
CommandLine::Raw);
|
||||
|
||||
const RunResult result = runInShell(cmdLine);
|
||||
@@ -1175,23 +1212,26 @@ bool UnixDeviceFileAccess::iterateWithFind(const FilePath &filePath,
|
||||
if (entries.isEmpty())
|
||||
return true;
|
||||
|
||||
const auto toFilePath = [&filePath, &callBack](const QString &entry) {
|
||||
if (callBack.index() == 0)
|
||||
return std::get<0>(callBack)(filePath.withNewPath(entry));
|
||||
const int modeBase = osType() == OsTypeMac ? 8 : 16;
|
||||
|
||||
const QString fileName = entry.mid(1, entry.lastIndexOf('\"') - 1);
|
||||
const QString infos = entry.mid(fileName.length() + 3);
|
||||
const auto toFilePath =
|
||||
[&filePath, &callBack, modeBase](const QString &entry) {
|
||||
if (callBack.index() == 0)
|
||||
return std::get<0>(callBack)(filePath.withNewPath(entry));
|
||||
|
||||
const FilePathInfo fi = FileUtils::filePathInfoFromTriple(infos);
|
||||
if (!fi.fileFlags)
|
||||
return IterationPolicy::Continue;
|
||||
const QString fileName = entry.mid(1, entry.lastIndexOf('\"') - 1);
|
||||
const QString infos = entry.mid(fileName.length() + 3);
|
||||
|
||||
const FilePath fp = filePath.withNewPath(fileName);
|
||||
// Do not return the entry for the directory we are searching in.
|
||||
if (fp.path() == filePath.path())
|
||||
return IterationPolicy::Continue;
|
||||
return std::get<1>(callBack)(fp, fi);
|
||||
};
|
||||
const FilePathInfo fi = FileUtils::filePathInfoFromTriple(infos, modeBase);
|
||||
if (!fi.fileFlags)
|
||||
return IterationPolicy::Continue;
|
||||
|
||||
const FilePath fp = filePath.withNewPath(fileName);
|
||||
// Do not return the entry for the directory we are searching in.
|
||||
if (fp.path() == filePath.path())
|
||||
return IterationPolicy::Continue;
|
||||
return std::get<1>(callBack)(fp, fi);
|
||||
};
|
||||
|
||||
// Remove the first line, this can be the directory we are searching in.
|
||||
// as long as we do not specify "mindepth > 0"
|
||||
|
||||
@@ -3,10 +3,13 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "hostosinfo.h"
|
||||
#include "utils_global.h"
|
||||
|
||||
#include "fileutils.h"
|
||||
|
||||
class tst_unixdevicefileaccess; // For testing.
|
||||
|
||||
namespace Utils {
|
||||
|
||||
// Base class including dummy implementation usable as fallback.
|
||||
@@ -19,6 +22,7 @@ public:
|
||||
|
||||
protected:
|
||||
friend class FilePath;
|
||||
friend class ::tst_unixdevicefileaccess; // For testing.
|
||||
|
||||
virtual QString mapToDevicePath(const QString &hostPath) const;
|
||||
|
||||
@@ -204,8 +208,14 @@ private:
|
||||
const FileFilter &filter,
|
||||
QStringList *found) const;
|
||||
|
||||
Utils::OsType osType() const;
|
||||
QStringList statArgs(const FilePath &filePath,
|
||||
const QString &linuxFormat,
|
||||
const QString &macFormat) const;
|
||||
|
||||
mutable bool m_tryUseFind = true;
|
||||
mutable std::optional<bool> m_hasMkTemp;
|
||||
mutable std::optional<Utils::OsType> m_osType;
|
||||
};
|
||||
|
||||
} // Utils
|
||||
|
||||
@@ -586,8 +586,7 @@ FilePaths FileUtils::getOpenFilePaths(QWidget *parent,
|
||||
|
||||
#endif // QT_WIDGETS_LIB
|
||||
|
||||
// Converts a hex string of the st_mode field of a stat structure to FileFlags.
|
||||
FilePathInfo::FileFlags fileInfoFlagsfromStatRawModeHex(const QString &hexString)
|
||||
FilePathInfo::FileFlags fileInfoFlagsfromStatMode(const QString &hexString, int modeBase)
|
||||
{
|
||||
// Copied from stat.h
|
||||
enum st_mode {
|
||||
@@ -617,7 +616,7 @@ FilePathInfo::FileFlags fileInfoFlagsfromStatRawModeHex(const QString &hexString
|
||||
};
|
||||
|
||||
bool ok = false;
|
||||
uint mode = hexString.toUInt(&ok, 16);
|
||||
uint mode = hexString.toUInt(&ok, modeBase);
|
||||
|
||||
QTC_ASSERT(ok, return {});
|
||||
|
||||
@@ -657,13 +656,13 @@ FilePathInfo::FileFlags fileInfoFlagsfromStatRawModeHex(const QString &hexString
|
||||
return result;
|
||||
}
|
||||
|
||||
FilePathInfo FileUtils::filePathInfoFromTriple(const QString &infos)
|
||||
FilePathInfo FileUtils::filePathInfoFromTriple(const QString &infos, int modeBase)
|
||||
{
|
||||
const QStringList parts = infos.split(' ', Qt::SkipEmptyParts);
|
||||
if (parts.size() != 3)
|
||||
return {};
|
||||
|
||||
FilePathInfo::FileFlags flags = fileInfoFlagsfromStatRawModeHex(parts[0]);
|
||||
FilePathInfo::FileFlags flags = fileInfoFlagsfromStatMode(parts[0], modeBase);
|
||||
|
||||
const QDateTime dt = QDateTime::fromSecsSinceEpoch(parts[1].toLongLong(), Qt::UTC);
|
||||
qint64 size = parts[2].toLongLong();
|
||||
|
||||
@@ -83,7 +83,7 @@ public:
|
||||
|
||||
static qint64 bytesAvailableFromDFOutput(const QByteArray &dfOutput);
|
||||
|
||||
static FilePathInfo filePathInfoFromTriple(const QString &infos);
|
||||
static FilePathInfo filePathInfoFromTriple(const QString &infos, int modeBase);
|
||||
|
||||
#ifdef QT_WIDGETS_LIB
|
||||
static void setDialogParentGetter(const std::function<QWidget *()> &getter);
|
||||
|
||||
@@ -17,3 +17,4 @@ add_subdirectory(stringutils)
|
||||
add_subdirectory(tasktree)
|
||||
add_subdirectory(templateengine)
|
||||
add_subdirectory(treemodel)
|
||||
add_subdirectory(unixdevicefileaccess)
|
||||
|
||||
@@ -172,7 +172,7 @@ void tst_fileutils::filePathInfoFromTriple()
|
||||
QFETCH(QString, statoutput);
|
||||
QFETCH(FilePathInfo, expected);
|
||||
|
||||
const FilePathInfo result = FileUtils::filePathInfoFromTriple(statoutput);
|
||||
const FilePathInfo result = FileUtils::filePathInfoFromTriple(statoutput, 16);
|
||||
|
||||
QCOMPARE(result, expected);
|
||||
}
|
||||
|
||||
4
tests/auto/utils/unixdevicefileaccess/CMakeLists.txt
Normal file
4
tests/auto/utils/unixdevicefileaccess/CMakeLists.txt
Normal file
@@ -0,0 +1,4 @@
|
||||
add_qtc_test(tst_utils_unixdevicefileaccess
|
||||
DEPENDS Utils
|
||||
SOURCES tst_unixdevicefileaccess.cpp
|
||||
)
|
||||
@@ -0,0 +1,84 @@
|
||||
// Copyright (C) 2016 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||
|
||||
#include <QDebug>
|
||||
#include <QRandomGenerator>
|
||||
#include <QtCore/qiodevice.h>
|
||||
#include <QtTest>
|
||||
|
||||
#include <utils/commandline.h>
|
||||
#include <utils/devicefileaccess.h>
|
||||
#include <utils/hostosinfo.h>
|
||||
#include <utils/link.h>
|
||||
|
||||
//TESTED_COMPONENT=src/libs/utils
|
||||
using namespace Utils;
|
||||
|
||||
namespace QTest {
|
||||
template<>
|
||||
char *toString(const FilePath &filePath)
|
||||
{
|
||||
return qstrdup(filePath.toString().toLocal8Bit().constData());
|
||||
}
|
||||
} // namespace QTest
|
||||
|
||||
class TestDFA : public UnixDeviceFileAccess
|
||||
{
|
||||
public:
|
||||
using UnixDeviceFileAccess::UnixDeviceFileAccess;
|
||||
|
||||
virtual RunResult runInShell(const CommandLine &cmdLine,
|
||||
const QByteArray &inputData = {}) const override
|
||||
{
|
||||
QProcess p;
|
||||
p.setProgram(cmdLine.executable().toString());
|
||||
p.setArguments(cmdLine.splitArguments());
|
||||
p.setProcessChannelMode(QProcess::SeparateChannels);
|
||||
|
||||
p.start();
|
||||
p.waitForStarted();
|
||||
if (inputData.size() > 0) {
|
||||
p.write(inputData);
|
||||
p.closeWriteChannel();
|
||||
}
|
||||
p.waitForFinished();
|
||||
return {p.exitCode(), p.readAllStandardOutput(), p.readAllStandardError()};
|
||||
}
|
||||
};
|
||||
|
||||
class tst_unixdevicefileaccess : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private slots:
|
||||
void initTestCase()
|
||||
{
|
||||
if (HostOsInfo::isWindowsHost())
|
||||
QSKIP("This test is only for Unix hosts");
|
||||
|
||||
m_fileSizeTestFile.writeFileContents(QByteArray(1024, 'a'));
|
||||
}
|
||||
|
||||
void osType()
|
||||
{
|
||||
const auto osType = m_dfaPtr->osType({});
|
||||
QCOMPARE(osType, HostOsInfo::hostOs());
|
||||
}
|
||||
|
||||
void fileSize()
|
||||
{
|
||||
const auto size = m_dfaPtr->fileSize(m_fileSizeTestFile);
|
||||
QCOMPARE(size, 1024);
|
||||
}
|
||||
|
||||
private:
|
||||
TestDFA m_dfa;
|
||||
DeviceFileAccess *m_dfaPtr = &m_dfa;
|
||||
|
||||
QTemporaryDir m_tempDir;
|
||||
FilePath m_fileSizeTestFile = FilePath::fromString(m_tempDir.filePath("size-test"));
|
||||
};
|
||||
|
||||
QTEST_GUILESS_MAIN(tst_unixdevicefileaccess)
|
||||
|
||||
#include "tst_unixdevicefileaccess.moc"
|
||||
@@ -0,0 +1,11 @@
|
||||
import qbs
|
||||
|
||||
QtcAutotest {
|
||||
name: "UnixDeviceFileAccess autotest"
|
||||
Depends { name: "Utils" }
|
||||
Properties {
|
||||
condition: qbs.toolchain.contains("gcc")
|
||||
cpp.cxxFlags: base.concat(["-Wno-trigraphs"])
|
||||
}
|
||||
files: "tst_unixdevicefileaccess.cpp"
|
||||
}
|
||||
@@ -22,5 +22,6 @@ Project {
|
||||
"tasktree/tasktree.qbs",
|
||||
"templateengine/templateengine.qbs",
|
||||
"treemodel/treemodel.qbs",
|
||||
"unixdevicefileaccess/unixdevicefileaccess.qbs",
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user