diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/app/qbs-setup-toolchains/gccprobe.cpp | 265 | ||||
-rw-r--r-- | src/app/qbs-setup-toolchains/gccprobe.h | 1 | ||||
-rw-r--r-- | src/app/qbs-setup-toolchains/probe.cpp | 141 | ||||
-rw-r--r-- | src/app/qbs-setup-toolchains/probe.h | 4 |
4 files changed, 321 insertions, 90 deletions
diff --git a/src/app/qbs-setup-toolchains/gccprobe.cpp b/src/app/qbs-setup-toolchains/gccprobe.cpp index 2f2a22465..e72e39bbb 100644 --- a/src/app/qbs-setup-toolchains/gccprobe.cpp +++ b/src/app/qbs-setup-toolchains/gccprobe.cpp @@ -85,10 +85,10 @@ static QStringList validMinGWMachines() QStringLiteral("amd64-mingw32msvc")}; } -static QString gccMachineName(const QString &compilerFilePath) +static QString gccMachineName(const QFileInfo &compiler) { - return qsystem(compilerFilePath, QStringList() - << QStringLiteral("-dumpmachine")).trimmed(); + return qsystem(compiler.absoluteFilePath(), {QStringLiteral("-dumpmachine")}) + .trimmed(); } static QStringList standardCompilerFileNames() @@ -99,63 +99,116 @@ static QStringList standardCompilerFileNames() HostOsInfo::appendExecutableSuffix(QStringLiteral("clang++"))}; } -static void setCommonProperties(Profile &profile, const QString &compilerFilePath, - const QStringList &toolchainTypes) +class ToolchainDetails { - const QFileInfo cfi(compilerFilePath); - const QString compilerName = cfi.fileName(); +public: + explicit ToolchainDetails(const QFileInfo &compiler) + { + auto baseName = compiler.completeBaseName(); + if (baseName.contains(QRegExp(QLatin1String("\\d$")))) { + const auto dashIndex = baseName.lastIndexOf(QLatin1Char('-')); + version = baseName.mid(dashIndex + 1); + baseName = baseName.left(dashIndex); + } + + const auto dashIndex = baseName.lastIndexOf(QLatin1Char('-')); + suffix = baseName.mid(dashIndex + 1); + prefix = baseName.left(dashIndex + 1); + } + QString prefix; + QString suffix; + QString version; +}; + +static void setCommonProperties(Profile &profile, const QFileInfo &compiler, + const QStringList &toolchainTypes, const ToolchainDetails &details) +{ if (toolchainTypes.contains(QStringLiteral("mingw"))) profile.setValue(QStringLiteral("qbs.targetPlatform"), QStringLiteral("windows")); - const QString prefix = compilerName.left( - compilerName.lastIndexOf(QLatin1Char('-')) + 1); - if (!prefix.isEmpty()) - profile.setValue(QStringLiteral("cpp.toolchainPrefix"), prefix); + if (!details.prefix.isEmpty()) + profile.setValue(QStringLiteral("cpp.toolchainPrefix"), details.prefix); profile.setValue(QStringLiteral("cpp.toolchainInstallPath"), - cfi.absolutePath()); + compiler.absolutePath()); profile.setValue(QStringLiteral("qbs.toolchain"), toolchainTypes); - const QString suffix = compilerName.right(compilerName.size() - prefix.size()); - if (!standardCompilerFileNames().contains(suffix)) + if (!standardCompilerFileNames().contains( + HostOsInfo::appendExecutableSuffix(details.suffix))) { qWarning("%s", qPrintable( QStringLiteral("'%1' is not a standard compiler file name; " "you must set the cpp.cCompilerName and " "cpp.cxxCompilerName properties of this profile " - "manually").arg(compilerName))); + "manually").arg(compiler.fileName()))); + } } class ToolPathSetup { public: - explicit ToolPathSetup(Profile *profile, QString path, QString toolchainPrefix) + explicit ToolPathSetup(Profile *profile, QString path, ToolchainDetails details) : m_profile(profile), m_compilerDirPath(std::move(path)), - m_toolchainPrefix(std::move(toolchainPrefix)) + m_details(std::move(details)) { } void apply(const QString &toolName, const QString &propertyName) const { - QString toolFileName = m_toolchainPrefix - + HostOsInfo::appendExecutableSuffix(toolName); - if (QFile::exists(m_compilerDirPath + QLatin1Char('/') + toolFileName)) - return; - const QString toolFilePath = findExecutable(toolFileName); - if (toolFilePath.isEmpty()) { + // Check for full tool name at first (includes suffix and version). + QString filePath = toolFilePath(toolName, UseFullToolName); + if (filePath.isEmpty()) { + // Check for base tool name at second (without of suffix and version). + filePath = toolFilePath(toolName, UseBaseToolName); + if (filePath.isEmpty()) { + // Check for short tool name at third (only a tool name). + filePath = toolFilePath(toolName, UseOnlyShortToolName); + } + } + + if (filePath.isEmpty()) { qWarning("%s", qPrintable( - QStringLiteral("'%1' exists neither in '%2' nor in PATH.") - .arg(toolFileName, m_compilerDirPath))); + QStringLiteral("'%1' not found in '%2'. " + "Qbs will try to find it in PATH at build time.") + .arg(toolName, m_compilerDirPath))); } - m_profile->setValue(propertyName, toolFilePath); + + m_profile->setValue(propertyName, filePath); } private: + enum ToolNameParts : quint8 { + UseOnlyShortToolName = 0x0, + UseToolPrefix = 0x01, + UseToolSuffix = 0x02, + UseToolVersion = 0x04, + UseFullToolName = UseToolPrefix | UseToolSuffix | UseToolVersion, + UseBaseToolName = UseToolPrefix, + }; + + QString toolFilePath(const QString &toolName, int parts) const + { + QString fileName; + if ((parts & UseToolPrefix) && !m_details.prefix.isEmpty()) + fileName += m_details.prefix; + if ((parts & UseToolSuffix) && !m_details.suffix.isEmpty()) + fileName += m_details.suffix + QLatin1Char('-'); + fileName += toolName; + if ((parts & UseToolVersion) && !m_details.version.isEmpty()) + fileName += QLatin1Char('-') + m_details.version; + + fileName = HostOsInfo::appendExecutableSuffix(fileName); + QString filePath = QDir(m_compilerDirPath).absoluteFilePath(fileName); + if (QFile::exists(filePath)) + return filePath; + return {}; + } + Profile * const m_profile; QString m_compilerDirPath; - QString m_toolchainPrefix; + ToolchainDetails m_details; }; static bool doesProfileTargetOS(const Profile &profile, const QString &os) @@ -169,12 +222,48 @@ static bool doesProfileTargetOS(const Profile &profile, const QString &os) return Internal::contains(HostOsInfo::hostOSIdentifiers(), os.toStdString()); } +static QString buildProfileName(const QFileInfo &cfi) +{ + // We need to replace a dot-separated compiler version string + // with a underscore-separated string, because the profile + // name does not allow a dots. + auto result = cfi.completeBaseName(); + result.replace(QLatin1Char('.'), QLatin1Char('_')); + return result; +} + +static QStringList buildCompilerNameFilters(const QString &compilerName) +{ + QStringList filters = { + // "clang", "gcc" + compilerName, + // "clang-8", "gcc-5" + compilerName + QLatin1String("-[1-9]*"), + // "avr-gcc" + QLatin1String("*-") + compilerName, + // "arm-none-eabi-gcc" + QLatin1String("*-*-*-") + compilerName, + // "arm-none-eabi-gcc-9.1.0" + QLatin1String("*-*-*-") + compilerName + QLatin1String("-[1-9]*"), + // "x86_64-pc-linux-gnu-gcc" + QLatin1String("*-*-*-*-") + compilerName, + // "x86_64-pc-linux-gnu-gcc-7.4.1" + QLatin1String("*-*-*-*-") + compilerName + QLatin1String("-[1-9]*") + }; + + std::transform(filters.begin(), filters.end(), filters.begin(), + [](const auto &filter) { + return HostOsInfo::appendExecutableSuffix(filter); + }); + + return filters; +} + Profile createGccProfile(const QFileInfo &compiler, Settings *settings, const QStringList &toolchainTypes, const QString &profileName) { - const auto compilerFilePath = compiler.absoluteFilePath(); - const QString machineName = gccMachineName(compilerFilePath); + const QString machineName = gccMachineName(compiler); if (toolchainTypes.contains(QLatin1String("mingw"))) { if (!validMinGWMachines().contains(machineName)) { @@ -185,29 +274,31 @@ Profile createGccProfile(const QFileInfo &compiler, Settings *settings, Profile profile(!profileName.isEmpty() ? profileName : machineName, settings); profile.removeProfile(); - setCommonProperties(profile, compilerFilePath, toolchainTypes); - - // Check whether auxiliary tools reside within the toolchain's install path. - // This might not be the case when using icecc or another compiler wrapper. - const QString compilerDirPath = compiler.absolutePath(); - const ToolPathSetup toolPathSetup( - &profile, compilerDirPath, - profile.value(QStringLiteral("cpp.toolchainPrefix")) - .toString()); - toolPathSetup.apply(QStringLiteral("ar"), QStringLiteral("cpp.archiverPath")); - toolPathSetup.apply(QStringLiteral("as"), QStringLiteral("cpp.assemblerPath")); - toolPathSetup.apply(QStringLiteral("nm"), QStringLiteral("cpp.nmPath")); - if (doesProfileTargetOS(profile, QStringLiteral("darwin"))) - toolPathSetup.apply(QStringLiteral("dsymutil"), - QStringLiteral("cpp.dsymutilPath")); - else - toolPathSetup.apply(QStringLiteral("objcopy"), - QStringLiteral("cpp.objcopyPath")); - toolPathSetup.apply(QStringLiteral("strip"), - QStringLiteral("cpp.stripPath")); + + const ToolchainDetails details(compiler); + + setCommonProperties(profile, compiler, toolchainTypes, details); + + if (!toolchainTypes.contains(QLatin1String("clang"))) { + // Check whether auxiliary tools reside within the toolchain's install path. + // This might not be the case when using icecc or another compiler wrapper. + const QString compilerDirPath = compiler.absolutePath(); + const ToolPathSetup toolPathSetup(&profile, compilerDirPath, details); + toolPathSetup.apply(QStringLiteral("ar"), QStringLiteral("cpp.archiverPath")); + toolPathSetup.apply(QStringLiteral("as"), QStringLiteral("cpp.assemblerPath")); + toolPathSetup.apply(QStringLiteral("nm"), QStringLiteral("cpp.nmPath")); + if (doesProfileTargetOS(profile, QStringLiteral("darwin"))) + toolPathSetup.apply(QStringLiteral("dsymutil"), + QStringLiteral("cpp.dsymutilPath")); + else + toolPathSetup.apply(QStringLiteral("objcopy"), + QStringLiteral("cpp.objcopyPath")); + toolPathSetup.apply(QStringLiteral("strip"), + QStringLiteral("cpp.stripPath")); + } qbsInfo() << Tr::tr("Profile '%1' created for '%2'.") - .arg(profile.name(), compilerFilePath); + .arg(profile.name(), compiler.absoluteFilePath()); return profile; } @@ -215,38 +306,54 @@ void gccProbe(Settings *settings, QList<Profile> &profiles, const QString &compi { qbsInfo() << Tr::tr("Trying to detect %1...").arg(compilerName); - const QString crossCompilePrefix = QString::fromLocal8Bit(qgetenv("CROSS_COMPILE")); - const QString compilerFilePath = findExecutable(crossCompilePrefix + compilerName); - const QFileInfo cfi(compilerFilePath); - if (!cfi.exists()) { - qbsError() << Tr::tr("%1 not found.").arg(compilerName); - return; - } - const QString profileName = cfi.completeBaseName(); - const QStringList toolchainTypes = toolchainTypeFromCompilerName(compilerName); - profiles.push_back(createGccProfile(compilerFilePath, settings, - toolchainTypes, profileName)); -} + const QStringList searchPaths = systemSearchPaths(); -void mingwProbe(Settings *settings, QList<Profile> &profiles) -{ - // List of possible compiler binary names for this platform - QStringList compilerNames; - if (HostOsInfo::isWindowsHost()) { - compilerNames << QStringLiteral("gcc"); - } else { - const auto machineNames = validMinGWMachines(); - for (const QString &machineName : machineNames) { - compilerNames << machineName + QLatin1String("-gcc"); + std::vector<QFileInfo> candidates; + const auto filters = buildCompilerNameFilters(compilerName); + for (const auto &searchPath : searchPaths) { + const QDir dir(searchPath); + const QStringList fileNames = dir.entryList( + filters, QDir::Files | QDir::Executable); + for (const QString &fileName : fileNames) { + // Ignore unexpected compiler names. + if (fileName.startsWith(QLatin1String("c89-gcc")) + || fileName.startsWith(QLatin1String("c99-gcc"))) { + continue; + } + const QFileInfo candidate = dir.filePath(fileName); + // Filter duplicates. + const auto existingEnd = candidates.end(); + const auto existingIt = std::find_if( + candidates.begin(), existingEnd, + [candidate](const QFileInfo &existing) { + return isSameExecutable(candidate.absoluteFilePath(), + existing.absoluteFilePath()); + }); + if (existingIt == existingEnd) { + // No duplicates are found, just add a new candidate. + candidates.push_back(candidate); + } else { + // Replace the existing entry if a candidate name more than + // an existing name. + const auto candidateName = candidate.completeBaseName(); + const auto existingName = existingIt->completeBaseName(); + if (candidateName > existingName) + *existingIt = candidate; + } } } - for (const QString &compilerName : qAsConst(compilerNames)) { - const QString gccPath - = findExecutable(HostOsInfo::appendExecutableSuffix(compilerName)); - if (!gccPath.isEmpty()) - profiles.push_back(createGccProfile( - gccPath, settings, - canonicalToolchain(QStringLiteral("mingw")))); + if (candidates.empty()) { + qbsInfo() << Tr::tr("No %1 toolchains found.").arg(compilerName); + return; + } + + for (const auto &candidate : qAsConst(candidates)) { + const QStringList toolchainTypes = toolchainTypeFromCompilerName( + candidate.baseName()); + const QString profileName = buildProfileName(candidate); + auto profile = createGccProfile(candidate, settings, + toolchainTypes, profileName); + profiles.push_back(std::move(profile)); } } diff --git a/src/app/qbs-setup-toolchains/gccprobe.h b/src/app/qbs-setup-toolchains/gccprobe.h index 6ce5938d0..4344c5836 100644 --- a/src/app/qbs-setup-toolchains/gccprobe.h +++ b/src/app/qbs-setup-toolchains/gccprobe.h @@ -58,6 +58,5 @@ qbs::Profile createGccProfile(const QFileInfo &compiler, void gccProbe(qbs::Settings *settings, QList<qbs::Profile> &profiles, const QString &compilerName); -void mingwProbe(qbs::Settings *settings, QList<qbs::Profile> &profiles); #endif // Header guard diff --git a/src/app/qbs-setup-toolchains/probe.cpp b/src/app/qbs-setup-toolchains/probe.cpp index d1b473e0a..f041568a9 100644 --- a/src/app/qbs-setup-toolchains/probe.cpp +++ b/src/app/qbs-setup-toolchains/probe.cpp @@ -54,8 +54,20 @@ #include <tools/toolchains.h> #include <QtCore/qdir.h> +#include <QtCore/qoperatingsystemversion.h> #include <QtCore/qtextstream.h> +#ifdef Q_OS_WIN +// We need defines for Windows 8. +#undef _WIN32_WINNT +#define _WIN32_WINNT _WIN32_WINNT_WIN8 + +#include <qt_windows.h> +#include <ShlObj.h> +#else +#include <qplatformdefs.h> +#endif // Q_OS_WIN + using namespace qbs; using Internal::HostOsInfo; using Internal::Tr; @@ -63,6 +75,11 @@ using Internal::Tr; static QTextStream qStdout(stdout); static QTextStream qStderr(stderr); +QStringList systemSearchPaths() +{ + return QString::fromLocal8Bit(qgetenv("PATH")).split(HostOsInfo::pathListSeparator()); +} + QString findExecutable(const QString &fileName) { QString fullFileName = fileName; @@ -70,8 +87,7 @@ QString findExecutable(const QString &fileName) && !fileName.endsWith(QLatin1String(".exe"), Qt::CaseInsensitive)) { fullFileName += QLatin1String(".exe"); } - const QString path = QString::fromLocal8Bit(qgetenv("PATH")); - const auto ppaths = path.split(HostOsInfo::pathListSeparator()); + const auto ppaths = systemSearchPaths(); for (const QString &ppath : ppaths) { const QString fullPath = ppath + QLatin1Char('/') + fullFileName; if (QFileInfo::exists(fullPath)) @@ -109,16 +125,13 @@ void probe(Settings *settings) if (HostOsInfo::isWindowsHost()) { msvcProbe(settings, profiles); clangClProbe(settings, profiles); - } else { - gccProbe(settings, profiles, QStringLiteral("gcc")); - gccProbe(settings, profiles, QStringLiteral("clang")); - - if (HostOsInfo::isMacosHost()) { - xcodeProbe(settings, profiles); - } + } else if (HostOsInfo::isMacosHost()) { + xcodeProbe(settings, profiles); } - mingwProbe(settings, profiles); + gccProbe(settings, profiles, QStringLiteral("gcc")); + gccProbe(settings, profiles, QStringLiteral("clang")); + iarProbe(settings, profiles); keilProbe(settings, profiles); sdccProbe(settings, profiles); @@ -180,3 +193,111 @@ int extractVersion(const QByteArray ¯oDump, const QByteArray &keyToken) .toInt(); return version; } + +static QString resolveSymlinks(const QString &filePath) +{ + QFileInfo fi(filePath); + int links = 16; + while (links-- && fi.isSymLink()) + fi.setFile(fi.dir(), fi.symLinkTarget()); + if (links <= 0) + return {}; + return fi.filePath(); +} + +// 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 {}; +} + +// File ID for Windows starting from version 8. +static QByteArray fileIdWin8(HANDLE handle) +{ + QByteArray result; + FILE_ID_INFO infoEx = {}; + if (::GetFileInformationByHandleEx( + handle, + static_cast<FILE_INFO_BY_HANDLE_CLASS>(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<const char *>(&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)); +} + +static QByteArray fileId(const QString &filePath) +{ + QByteArray result; + const HANDLE handle = ::CreateFile( + reinterpret_cast<const wchar_t*>(filePath.utf16()), 0, + FILE_SHARE_READ, nullptr, OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS, nullptr); + if (handle != INVALID_HANDLE_VALUE) { + result = fileIdWin(handle); + ::CloseHandle(handle); + } + return result; +} + +static qint64 fileSize(const QString &filePath) +{ + return QFileInfo(filePath).size(); +} + +#else + +static QByteArray fileId(const QString &filePath) +{ + QByteArray result; + if (Q_UNLIKELY(filePath.isEmpty())) + return {}; + QT_STATBUF statResult = {}; + if (QT_STAT(filePath.toLocal8Bit().constData(), &statResult)) + return {}; + result = QByteArray::number(quint64(statResult.st_dev), 16); + result += ':'; + result += QByteArray::number(quint64(statResult.st_ino)); + return result; +} + +#endif // Q_OS_WIN + +bool isSameExecutable(const QString &filePath1, const QString &filePath2) +{ + if (filePath1 == filePath2) + return true; + if (resolveSymlinks(filePath1) == resolveSymlinks(filePath2)) + return true; + if (fileId(filePath1) == fileId(filePath2)) + return true; + +#ifdef Q_OS_WIN + if (fileSize(filePath1) == fileSize(filePath2)) + return true; +#endif + + return false; +} diff --git a/src/app/qbs-setup-toolchains/probe.h b/src/app/qbs-setup-toolchains/probe.h index 3dfb3f103..235d7a899 100644 --- a/src/app/qbs-setup-toolchains/probe.h +++ b/src/app/qbs-setup-toolchains/probe.h @@ -53,6 +53,8 @@ QT_END_NAMESPACE namespace qbs { class Settings; } +QStringList systemSearchPaths(); + QString findExecutable(const QString &fileName); QStringList toolchainTypeFromCompilerName(const QString &compilerName); @@ -77,4 +79,6 @@ inline bool operator<(const ToolchainInstallInfo &lhs, const ToolchainInstallInf int extractVersion(const QByteArray ¯oDump, const QByteArray &keyToken); +bool isSameExecutable(const QString &exe1, const QString &exe2); + #endif // Header guard |