diff options
Diffstat (limited to 'src/app/qbs-setup-toolchains/gccprobe.cpp')
-rw-r--r-- | src/app/qbs-setup-toolchains/gccprobe.cpp | 515 |
1 files changed, 515 insertions, 0 deletions
diff --git a/src/app/qbs-setup-toolchains/gccprobe.cpp b/src/app/qbs-setup-toolchains/gccprobe.cpp new file mode 100644 index 000000000..a8482f8d0 --- /dev/null +++ b/src/app/qbs-setup-toolchains/gccprobe.cpp @@ -0,0 +1,515 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qbs. +** +** $QT_BEGIN_LICENSE:LGPL$ +** 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 Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** 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-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "probe.h" +#include "gccprobe.h" + +#include "../shared/logging/consolelogger.h" + +#include <logging/translator.h> + +#include <tools/hostosinfo.h> +#include <tools/profile.h> +#include <tools/settings.h> +#include <tools/toolchains.h> + +#include <QtCore/qdir.h> +#include <QtCore/qprocess.h> + +#include <algorithm> + +using namespace qbs; +using Internal::HostOsInfo; +using Internal::Tr; + +static QString qsystem(const QString &exe, const QStringList &args = QStringList()) +{ + QProcess p; + p.setProcessChannelMode(QProcess::MergedChannels); + p.start(exe, args); + if (!p.waitForStarted()) { + throw qbs::ErrorInfo(Tr::tr("Failed to start compiler '%1': %2") + .arg(exe, p.errorString())); + } + if (!p.waitForFinished(-1) || p.exitCode() != 0) + throw qbs::ErrorInfo(Tr::tr("Failed to run compiler '%1': %2") + .arg(exe, p.errorString())); + return QString::fromLocal8Bit(p.readAll()); +} + +static QStringList validMinGWMachines() +{ + // List of MinGW machine names (gcc -dumpmachine) recognized by Qbs + return {QStringLiteral("mingw32"), + QStringLiteral("mingw64"), + QStringLiteral("i686-w64-mingw32"), + QStringLiteral("x86_64-w64-mingw32"), + QStringLiteral("i686-w64-mingw32.shared"), + QStringLiteral("x86_64-w64-mingw32.shared"), + QStringLiteral("i686-w64-mingw32.static"), + QStringLiteral("x86_64-w64-mingw32.static"), + QStringLiteral("i586-mingw32msvc"), + QStringLiteral("amd64-mingw32msvc")}; +} + +static QString gccMachineName(const QFileInfo &compiler) +{ + return qsystem(compiler.absoluteFilePath(), {QStringLiteral("-dumpmachine")}) + .trimmed(); +} + +static QStringList standardCompilerFileNames() +{ + return {HostOsInfo::appendExecutableSuffix(QStringLiteral("gcc")), + HostOsInfo::appendExecutableSuffix(QStringLiteral("g++")), + HostOsInfo::appendExecutableSuffix(QStringLiteral("clang")), + HostOsInfo::appendExecutableSuffix(QStringLiteral("clang++"))}; +} + +class ToolchainDetails +{ +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")); + + if (!details.prefix.isEmpty()) + profile.setValue(QStringLiteral("cpp.toolchainPrefix"), details.prefix); + + profile.setValue(QStringLiteral("cpp.toolchainInstallPath"), + compiler.absolutePath()); + profile.setValue(QStringLiteral("qbs.toolchain"), toolchainTypes); + + 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(compiler.fileName()))); + } +} + +class ToolPathSetup +{ +public: + explicit ToolPathSetup(Profile *profile, QString path, ToolchainDetails details) + : m_profile(profile), + m_compilerDirPath(std::move(path)), + m_details(std::move(details)) + { + } + + void apply(const QString &toolName, const QString &propertyName) const + { + // 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' not found in '%2'. " + "Qbs will try to find it in PATH at build time.") + .arg(toolName, m_compilerDirPath))); + } else { + 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; + ToolchainDetails m_details; +}; + +static bool doesProfileTargetOS(const Profile &profile, const QString &os) +{ + const auto target = profile.value(QStringLiteral("qbs.targetPlatform")); + if (target.isValid()) { + return Internal::contains(HostOsInfo::canonicalOSIdentifiers( + target.toString().toStdString()), + os.toStdString()); + } + 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, + // "avr-gcc-5.4.0" + QLatin1String("*-") + compilerName + QLatin1String("-[1-9]*"), + // "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; +} + +static QStringList gnuRegistrySearchPaths() +{ + if (!HostOsInfo::isWindowsHost()) + return {}; + + // Registry token for the "GNU Tools for ARM Embedded Processors". + static const char kRegistryToken[] = "HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\" \ + "Windows\\CurrentVersion\\Uninstall\\"; + + QStringList searchPaths; + + QSettings registry(QLatin1String(kRegistryToken), QSettings::NativeFormat); + const auto productGroups = registry.childGroups(); + for (const QString &productKey : productGroups) { + if (!productKey.startsWith( + QLatin1String("GNU Tools for ARM Embedded Processors"))) { + continue; + } + registry.beginGroup(productKey); + QString uninstallFilePath = registry.value( + QLatin1String("UninstallString")).toString(); + if (uninstallFilePath.startsWith(QLatin1Char('"'))) + uninstallFilePath.remove(0, 1); + if (uninstallFilePath.endsWith(QLatin1Char('"'))) + uninstallFilePath.remove(uninstallFilePath.size() - 1, 1); + registry.endGroup(); + + const QString toolkitRootPath = QFileInfo(uninstallFilePath).path(); + const QString toolchainPath = toolkitRootPath + QLatin1String("/bin"); + searchPaths.push_back(toolchainPath); + } + + return searchPaths; +} + +static QStringList atmelRegistrySearchPaths() +{ + if (!HostOsInfo::isWindowsHost()) + return {}; + + // Registry token for the "Atmel" toolchains, e.g. provided by installed + // "Atmel Studio" IDE. + static const char kRegistryToken[] = "HKEY_LOCAL_MACHINE\\SOFTWARE\\Atmel\\"; + + QStringList searchPaths; + QSettings registry(QLatin1String(kRegistryToken), QSettings::NativeFormat); + + // This code enumerate the installed toolchains provided + // by the Atmel Studio v6.x. + const auto toolchainGroups = registry.childGroups(); + for (const QString &toolchainKey : toolchainGroups) { + if (!toolchainKey.endsWith(QLatin1String("GCC"))) + continue; + registry.beginGroup(toolchainKey); + const auto entries = registry.childGroups(); + for (const auto &entryKey : entries) { + registry.beginGroup(entryKey); + const QString installDir = registry.value( + QStringLiteral("Native/InstallDir")).toString(); + const QString version = registry.value( + QStringLiteral("Native/Version")).toString(); + registry.endGroup(); + + QString toolchainPath = installDir + + QLatin1String("/Atmel Toolchain/") + + toolchainKey + QLatin1String("/Native/") + + version; + if (toolchainKey.startsWith(QLatin1String("ARM"))) + toolchainPath += QLatin1String("/arm-gnu-toolchain"); + else if (toolchainKey.startsWith(QLatin1String("AVR32"))) + toolchainPath += QLatin1String("/avr32-gnu-toolchain"); + else if (toolchainKey.startsWith(QLatin1String("AVR8)"))) + toolchainPath += QLatin1String("/avr8-gnu-toolchain"); + else + break; + + toolchainPath += QLatin1String("/bin"); + + if (QFileInfo::exists(toolchainPath)) { + searchPaths.push_back(toolchainPath); + break; + } + } + registry.endGroup(); + } + + // This code enumerate the installed toolchains provided + // by the Atmel Studio v7. + registry.beginGroup(QStringLiteral("AtmelStudio")); + const auto productVersions = registry.childGroups(); + for (const auto &productVersionKey : productVersions) { + registry.beginGroup(productVersionKey); + const QString installDir = registry.value( + QStringLiteral("InstallDir")).toString(); + registry.endGroup(); + + const QStringList knownToolchainSubdirs = { + QStringLiteral("/toolchain/arm/arm-gnu-toolchain/bin/"), + QStringLiteral("/toolchain/avr8/avr8-gnu-toolchain/bin/"), + QStringLiteral("/toolchain/avr32/avr32-gnu-toolchain/bin/"), + }; + + for (const auto &subdir : knownToolchainSubdirs) { + const QString toolchainPath = installDir + subdir; + if (!QFileInfo::exists(toolchainPath)) + continue; + searchPaths.push_back(toolchainPath); + } + } + registry.endGroup(); + + return searchPaths; +} + +Profile createGccProfile(const QFileInfo &compiler, Settings *settings, + const QStringList &toolchainTypes, + const QString &profileName) +{ + const QString machineName = gccMachineName(compiler); + + if (toolchainTypes.contains(QLatin1String("mingw"))) { + if (!validMinGWMachines().contains(machineName)) { + throw ErrorInfo(Tr::tr("Detected gcc platform '%1' is not supported.") + .arg(machineName)); + } + } + + Profile profile(!profileName.isEmpty() ? profileName : machineName, settings); + profile.removeProfile(); + + const ToolchainDetails details(compiler); + + setCommonProperties(profile, compiler, toolchainTypes, details); + + if (HostOsInfo::isWindowsHost() && toolchainTypes.contains(QLatin1String("clang"))) { + const QStringList profileNames = settings->profiles(); + bool foundMingw = false; + for (const QString &profileName : profileNames) { + const Profile otherProfile(profileName, settings); + if (otherProfile.value(QLatin1String("qbs.toolchainType")).toString() + == QLatin1String("mingw") + || otherProfile.value(QLatin1String("qbs.toolchain")) + .toStringList().contains(QLatin1String("mingw"))) { + const QFileInfo tcDir(otherProfile.value(QLatin1String("cpp.toolchainInstallPath")) + .toString()); + if (!tcDir.fileName().isEmpty() && tcDir.exists()) { + profile.setValue(QLatin1String("qbs.sysroot"), tcDir.path()); + foundMingw = true; + break; + } + } + } + if (!foundMingw) { + qbsWarning() << Tr::tr("Using clang on Windows requires a mingw installation. " + "Please set qbs.sysroot accordingly for profile '%1'.") + .arg(profile.name()); + } + } + + 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(), compiler.absoluteFilePath()); + return profile; +} + +void gccProbe(Settings *settings, QList<Profile> &profiles, const QString &compilerName) +{ + qbsInfo() << Tr::tr("Trying to detect %1...").arg(compilerName); + + QStringList searchPaths; + searchPaths << systemSearchPaths() << gnuRegistrySearchPaths() + << atmelRegistrySearchPaths(); + + 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; + } + } + } + + if (candidates.empty()) { + qbsInfo() << Tr::tr("No %1 toolchains found.").arg(compilerName); + return; + } + + // Sort candidates so that mingw comes first. Information from mingw profiles is potentially + // used for setting up clang profiles. + if (HostOsInfo::isWindowsHost()) { + std::sort(candidates.begin(), candidates.end(), + [](const QFileInfo &fi1, const QFileInfo &fi2) { + return fi1.absoluteFilePath().contains(QLatin1String("mingw")) + && !fi2.absoluteFilePath().contains(QLatin1String("mingw")); + }); + } + + 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)); + } +} |