Files
qt-creator/src/plugins/projectexplorer/gcctoolchain.cpp

2190 lines
86 KiB
C++
Raw Normal View History

// Copyright (C) 2017 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "gcctoolchain.h"
#include "abiwidget.h"
#include "clangparser.h"
#include "devicesupport/idevice.h"
#include "gccparser.h"
#include "linuxiccparser.h"
#include "projectexplorertr.h"
#include "projectmacro.h"
#include "toolchainconfigwidget.h"
#include "toolchainmanager.h"
#include <coreplugin/icore.h>
#include <coreplugin/messagemanager.h>
#include <utils/algorithm.h>
#include <utils/environment.h>
#include <utils/hostosinfo.h>
2011-02-28 16:50:14 +01:00
#include <utils/pathchooser.h>
#include <utils/process.h>
#include <utils/qtcassert.h>
#include <QBuffer>
#include <QCheckBox>
#include <QComboBox>
#include <QCoreApplication>
#include <QDir>
#include <QFileInfo>
#include <QFormLayout>
#include <QHBoxLayout>
#include <QLineEdit>
#include <QLoggingCategory>
#include <QRegularExpression>
#include <QTimer>
#include <memory>
namespace {
static Q_LOGGING_CATEGORY(gccLog, "qtc.projectexplorer.toolchain.gcc", QtWarningMsg);
} // namespace
using namespace Utils;
namespace ProjectExplorer {
namespace Internal {
class TargetTripleWidget;
class GccToolChainConfigWidget : public ToolChainConfigWidget
{
public:
explicit GccToolChainConfigWidget(GccToolChain *tc);
private:
void handleCompilerCommandChange();
void handlePlatformCodeGenFlagsChange();
void handlePlatformLinkerFlagsChange();
void applyImpl() override;
void discardImpl() override { setFromToolchain(); }
bool isDirtyImpl() const override;
void makeReadOnlyImpl() override;
void setFromToolchain();
void updateParentToolChainComboBox(); // Clang
AbiWidget *m_abiWidget;
GccToolChain::SubType m_subType = GccToolChain::RealGcc;
PathChooser *m_compilerCommand;
QLineEdit *m_platformCodeGenFlagsLineEdit;
QLineEdit *m_platformLinkerFlagsLineEdit;
TargetTripleWidget * const m_targetTripleWidget;
bool m_isReadOnly = false;
ProjectExplorer::Macros m_macros;
// Clang only
QList<QMetaObject::Connection> m_parentToolChainConnections;
QComboBox *m_parentToolchainCombo = nullptr;
};
} // namespace Internal
using namespace Internal;
// --------------------------------------------------------------------------
// Helpers:
// --------------------------------------------------------------------------
const char compilerPlatformCodeGenFlagsKeyC[] = "ProjectExplorer.GccToolChain.PlatformCodeGenFlags";
const char compilerPlatformLinkerFlagsKeyC[] = "ProjectExplorer.GccToolChain.PlatformLinkerFlags";
const char targetAbiKeyC[] = "ProjectExplorer.GccToolChain.TargetAbi";
const char originalTargetTripleKeyC[] = "ProjectExplorer.GccToolChain.OriginalTargetTriple";
const char supportedAbisKeyC[] = "ProjectExplorer.GccToolChain.SupportedAbis";
const char parentToolChainIdKeyC[] = "ProjectExplorer.ClangToolChain.ParentToolChainId";
const char priorityKeyC[] = "ProjectExplorer.ClangToolChain.Priority";
const char binaryRegexp[] = "(?:^|-|\\b)(?:gcc|g\\+\\+|clang(?:\\+\\+)?)(?:-([\\d.]+))?$";
static QString runGcc(const FilePath &gcc, const QStringList &arguments, const Environment &env)
{
if (!gcc.isExecutableFile())
return {};
Process cpp;
Environment environment(env);
environment.setupEnglishOutput();
cpp.setEnvironment(environment);
cpp.setTimeoutS(10);
cpp.setCommand({gcc, arguments});
cpp.runBlocking();
if (cpp.result() != ProcessResult::FinishedWithSuccess || cpp.exitCode() != 0) {
Core::MessageManager::writeFlashing({"Compiler feature detection failure!",
cpp.exitMessage(),
cpp.allOutput()});
return {};
}
return cpp.allOutput();
}
static ProjectExplorer::Macros gccPredefinedMacros(const FilePath &gcc,
const QStringList &args,
const Environment &env)
{
QStringList arguments = args;
arguments << "-";
ProjectExplorer::Macros predefinedMacros = Macro::toMacros(runGcc(gcc, arguments, env).toUtf8());
// Sanity check in case we get an error message instead of real output:
QTC_CHECK(predefinedMacros.isEmpty()
|| predefinedMacros.front().type == ProjectExplorer::MacroType::Define);
if (HostOsInfo::isMacHost()) {
// Turn off flag indicating Apple's blocks support
const ProjectExplorer::Macro blocksDefine("__BLOCKS__", "1");
const ProjectExplorer::Macro blocksUndefine("__BLOCKS__", ProjectExplorer::MacroType::Undefine);
const int idx = predefinedMacros.indexOf(blocksDefine);
if (idx != -1)
predefinedMacros[idx] = blocksUndefine;
// Define __strong and __weak (used for Apple's GC extension of C) to be empty
predefinedMacros.append({"__strong"});
predefinedMacros.append({"__weak"});
}
return predefinedMacros;
}
HeaderPaths GccToolChain::gccHeaderPaths(const FilePath &gcc,
const QStringList &arguments,
const Environment &env)
{
HeaderPaths builtInHeaderPaths;
QByteArray line;
QByteArray data = runGcc(gcc, arguments, env).toUtf8();
QBuffer cpp(&data);
cpp.open(QIODevice::ReadOnly);
while (cpp.canReadLine()) {
line = cpp.readLine();
if (line.startsWith("#include"))
break;
}
if (!line.isEmpty() && line.startsWith("#include")) {
auto kind = HeaderPathType::User;
while (cpp.canReadLine()) {
line = cpp.readLine();
if (line.startsWith("#include")) {
kind = HeaderPathType::BuiltIn;
} else if (! line.isEmpty() && QChar(line.at(0)).isSpace()) {
HeaderPathType thisHeaderKind = kind;
line = line.trimmed();
const int index = line.indexOf(" (framework directory)");
if (index != -1) {
line.truncate(index);
thisHeaderKind = HeaderPathType::Framework;
}
const FilePath headerPath
= gcc.withNewPath(QString::fromUtf8(line)).canonicalPath();
if (!headerPath.isEmpty())
builtInHeaderPaths.append({headerPath, thisHeaderKind});
} else if (line.startsWith("End of search list.")) {
break;
} else {
qWarning("%s: Ignoring line: %s", __FUNCTION__, line.constData());
}
}
}
return builtInHeaderPaths;
}
static Abis guessGccAbi(const QString &m, const ProjectExplorer::Macros &macros)
{
Abis abiList;
Abi guessed = Abi::abiFromTargetTriplet(m);
if (guessed.isNull())
return abiList;
Abi::Architecture arch = guessed.architecture();
Abi::OS os = guessed.os();
Abi::OSFlavor flavor = guessed.osFlavor();
Abi::BinaryFormat format = guessed.binaryFormat();
int width = guessed.wordWidth();
const Macro sizeOfMacro = Utils::findOrDefault(macros, [](const Macro &m) { return m.key == "__SIZEOF_SIZE_T__"; });
if (sizeOfMacro.isValid() && sizeOfMacro.type == MacroType::Define)
width = sizeOfMacro.value.toInt() * 8;
const Macro &mscVerMacro = Utils::findOrDefault(macros, [](const Macro &m) { return m.key == "_MSC_VER"; });
if (mscVerMacro.type == MacroType::Define) {
const int msvcVersion = mscVerMacro.value.toInt();
flavor = Abi::flavorForMsvcVersion(msvcVersion);
}
if (os == Abi::DarwinOS) {
// Apple does PPC and x86!
abiList << Abi(arch, os, flavor, format, width);
abiList << Abi(arch, os, flavor, format, width == 64 ? 32 : 64);
} else if (arch == Abi::X86Architecture && (width == 0 || width == 64)) {
abiList << Abi(arch, os, flavor, format, 64);
if (width != 64 || (!m.contains("mingw")
&& ToolChainManager::detectionSettings().detectX64AsX32)) {
abiList << Abi(arch, os, flavor, format, 32);
}
} else {
abiList << Abi(arch, os, flavor, format, width);
}
return abiList;
}
static GccToolChain::DetectedAbisResult guessGccAbi(const FilePath &path,
const Environment &env,
const Macros &macros,
const QStringList &extraArgs = {})
{
if (path.isEmpty())
return GccToolChain::DetectedAbisResult();
QStringList arguments = extraArgs;
arguments << "-dumpmachine";
QString machine = runGcc(path, arguments, env).trimmed().section('\n', 0, 0, QString::SectionSkipEmpty);
if (machine.isEmpty()) {
// ICC does not implement the -dumpmachine option on macOS.
if (HostOsInfo::isMacHost() && (path.fileName() == "icc" || path.fileName() == "icpc"))
return GccToolChain::DetectedAbisResult({Abi::hostAbi()});
return GccToolChain::DetectedAbisResult(); // no need to continue if running failed once...
}
return GccToolChain::DetectedAbisResult(guessGccAbi(machine, macros), machine);
}
static QString gccVersion(const FilePath &path,
const Environment &env,
const QStringList &extraArgs)
{
QStringList arguments = extraArgs;
arguments << "-dumpversion";
return runGcc(path, arguments, env).trimmed();
}
static FilePath gccInstallDir(const FilePath &compiler,
const Environment &env,
const QStringList &extraArgs = {})
{
QStringList arguments = extraArgs;
arguments << "-print-search-dirs";
QString output = runGcc(compiler, arguments, env).trimmed();
// Expected output looks like this:
// install: /usr/lib/gcc/x86_64-linux-gnu/7/
// ...
// Note that clang also supports "-print-search-dirs". However, the
// install dir is not part of the output (tested with clang-8/clang-9).
const QString prefix = "install: ";
const QString line = QTextStream(&output).readLine();
if (!line.startsWith(prefix))
return {};
return compiler.withNewPath(QDir::cleanPath(line.mid(prefix.size())));
}
// --------------------------------------------------------------------------
// GccToolChain
// --------------------------------------------------------------------------
GccToolChain::GccToolChain(Utils::Id typeId, SubType subType)
: ToolChain(typeId), m_subType(subType)
{
setTypeDisplayName(Tr::tr("GCC"));
setTargetAbiKey(targetAbiKeyC);
setCompilerCommandKey("ProjectExplorer.GccToolChain.Path");
if (m_subType == MinGW) {
setTypeDisplayName(Tr::tr("MinGW"));
} else if (m_subType == Clang) {
setTypeDisplayName(Tr::tr("Clang"));
syncAutodetectedWithParentToolchains();
}
}
GccToolChain::~GccToolChain()
{
if (m_subType == Clang) {
QObject::disconnect(m_thisToolchainRemovedConnection);
QObject::disconnect(m_mingwToolchainAddedConnection);
}
}
void GccToolChain::setSupportedAbis(const Abis &abis)
{
if (m_supportedAbis == abis)
return;
m_supportedAbis = abis;
toolChainUpdated();
}
void GccToolChain::setOriginalTargetTriple(const QString &targetTriple)
{
if (m_originalTargetTriple == targetTriple)
return;
m_originalTargetTriple = targetTriple;
toolChainUpdated();
}
void GccToolChain::setInstallDir(const Utils::FilePath &installDir)
{
if (m_installDir == installDir)
return;
m_installDir = installDir;
toolChainUpdated();
}
QString GccToolChain::defaultDisplayName() const
{
QString type = typeDisplayName();
const QRegularExpression regexp(binaryRegexp);
const QRegularExpressionMatch match = regexp.match(compilerCommand().fileName());
if (match.lastCapturedIndex() >= 1)
type += ' ' + match.captured(1);
const Abi abi = targetAbi();
if (abi.architecture() == Abi::UnknownArchitecture || abi.wordWidth() == 0)
return type;
return Tr::tr("%1 (%2, %3 %4 at %5)").arg(type,
ToolChainManager::displayNameOfLanguageId(language()),
Abi::toString(abi.architecture()),
Abi::toString(abi.wordWidth()),
compilerCommand().toUserOutput());
}
LanguageExtensions GccToolChain::defaultLanguageExtensions() const
{
return LanguageExtension::Gnu;
}
static const Toolchains mingwToolChains()
{
return ToolChainManager::toolchains([](const ToolChain *tc) -> bool {
return tc->typeId() == Constants::MINGW_TOOLCHAIN_TYPEID;
});
}
static const GccToolChain *mingwToolChainFromId(const QByteArray &id)
{
if (id.isEmpty())
return nullptr;
for (const ToolChain *tc : mingwToolChains()) {
if (tc->id() == id)
return static_cast<const GccToolChain *>(tc);
}
return nullptr;
}
QString GccToolChain::originalTargetTriple() const
{
if (m_subType == Clang) {
if (const GccToolChain *parentTC = mingwToolChainFromId(m_parentToolChainId))
return parentTC->originalTargetTriple();
}
if (m_originalTargetTriple.isEmpty())
m_originalTargetTriple = detectSupportedAbis().originalTargetTriple;
return m_originalTargetTriple;
}
QString GccToolChain::version() const
{
if (m_version.isEmpty())
m_version = detectVersion();
return m_version;
}
FilePath GccToolChain::installDir() const
{
if (m_installDir.isEmpty())
m_installDir = detectInstallDir();
return m_installDir;
}
Abis GccToolChain::supportedAbis() const
{
return m_supportedAbis;
}
static bool isNetworkCompiler(const QString &dirPath)
{
return dirPath.contains("icecc") || dirPath.contains("distcc");
}
static Utils::FilePath findLocalCompiler(const Utils::FilePath &compilerPath,
const Environment &env)
{
// Find the "real" compiler if icecc, distcc or similar are in use. Ignore ccache, since that
// is local already.
// Get the path to the compiler, ignoring direct calls to icecc and distcc as we cannot
// do anything about those.
if (!isNetworkCompiler(compilerPath.parentDir().path()))
return compilerPath;
// Filter out network compilers
const FilePaths pathComponents = Utils::filtered(env.path(), [] (const FilePath &dirPath) {
return !isNetworkCompiler(dirPath.path());
});
// This effectively searches the PATH twice, once via pathComponents and once via PATH itself:
// searchInPath filters duplicates, so that will not hurt.
const Utils::FilePath path = env.searchInPath(compilerPath.fileName(), pathComponents);
return path.isEmpty() ? compilerPath : path;
}
// For querying operations such as -dM
static QStringList filteredFlags(const QStringList &allFlags, bool considerSysroot)
{
QStringList filtered;
for (int i = 0; i < allFlags.size(); ++i) {
const QString &a = allFlags.at(i);
if (a.startsWith("--gcc-toolchain=")) {
filtered << a;
} else if (a == "-arch") {
if (++i < allFlags.length() && !filtered.contains(a))
filtered << a << allFlags.at(i);
} else if (a == "-Xclang") {
filtered << a;
} else if ((considerSysroot && (a == "--sysroot" || a == "-isysroot"))
|| a == "-D" || a == "-U"
|| a == "-gcc-toolchain" || a == "-target" || a == "-mllvm" || a == "-isystem") {
if (++i < allFlags.length())
filtered << a << allFlags.at(i);
} else if (a.startsWith("-m") || a.startsWith("-f") || a.startsWith("-O")
|| a.startsWith("-std=") || a.startsWith("-stdlib=")
|| a.startsWith("-specs=") || a == "-ansi" || a == "-undef"
|| a.startsWith("-D") || a.startsWith("-U")
|| a.startsWith("-stdlib=") || a.startsWith("-B")
|| a.startsWith("--target=")
|| (a.startsWith("-isystem") && a.length() > 8)
|| a == "-Wno-deprecated"
|| a == "-nostdinc" || a == "-nostdinc++") {
filtered << a;
}
}
return filtered;
}
ToolChain::MacroInspectionRunner GccToolChain::createMacroInspectionRunner() const
CppTools/ProjectManagers: Reduce ui blocking when loading projects ${AnyProject}::updateCppCodeModel() did two potentially not that cheap operations in the ui thread: (1) Querying the MimeDatabase for the mime type for the source files of the project. In 99.9% of the cases no files need to be read for this as the file extension will resolve the type. The expensiveness comes from the sheer number of files that can occur. (2) Calling compilers with the "(sub)project's compiler command line" to determine the macros. While the caches avoid redundant calls, the number of the unique compiler calls makes this still a ui-freezing experience. These two operations are moved into a worker thread. For this, the expensive compiler calls are encapsulated in thread safe lambdas ("runners") in order to keep the "mutexed" data minimal. The original API calls of the toolchains are implemented in terms of the runners. While adapting the project managers, remove also the calls to setProjectLanguage(). These are redundant because all of the project managers already set a proper value in the constructor. Also, currently there is no need (client) to report back detection of C sources in project parts. This also keeps CppProjectUpdater simple. There is still room for improvement: * Run the compiler calls in parallel instead of sequence. * Ensure that the mime type for a file is determined exactly once. Change-Id: I2efc4e132ee88e3c8f264012ec8fafe3d86c404f Reviewed-by: Tobias Hunger <tobias.hunger@qt.io>
2017-02-06 16:59:53 +01:00
{
// Using a clean environment breaks ccache/distcc/etc.
Environment env = compilerCommand().deviceEnvironment();
CppTools/ProjectManagers: Reduce ui blocking when loading projects ${AnyProject}::updateCppCodeModel() did two potentially not that cheap operations in the ui thread: (1) Querying the MimeDatabase for the mime type for the source files of the project. In 99.9% of the cases no files need to be read for this as the file extension will resolve the type. The expensiveness comes from the sheer number of files that can occur. (2) Calling compilers with the "(sub)project's compiler command line" to determine the macros. While the caches avoid redundant calls, the number of the unique compiler calls makes this still a ui-freezing experience. These two operations are moved into a worker thread. For this, the expensive compiler calls are encapsulated in thread safe lambdas ("runners") in order to keep the "mutexed" data minimal. The original API calls of the toolchains are implemented in terms of the runners. While adapting the project managers, remove also the calls to setProjectLanguage(). These are redundant because all of the project managers already set a proper value in the constructor. Also, currently there is no need (client) to report back detection of C sources in project parts. This also keeps CppProjectUpdater simple. There is still room for improvement: * Run the compiler calls in parallel instead of sequence. * Ensure that the mime type for a file is determined exactly once. Change-Id: I2efc4e132ee88e3c8f264012ec8fafe3d86c404f Reviewed-by: Tobias Hunger <tobias.hunger@qt.io>
2017-02-06 16:59:53 +01:00
addToEnvironment(env);
const QStringList platformCodeGenFlags = m_platformCodeGenFlags;
OptionsReinterpreter reinterpretOptions = m_optionsReinterpreter;
QTC_CHECK(reinterpretOptions);
MacrosCache macroCache = predefinedMacrosCache();
Utils::Id lang = language();
CppTools/ProjectManagers: Reduce ui blocking when loading projects ${AnyProject}::updateCppCodeModel() did two potentially not that cheap operations in the ui thread: (1) Querying the MimeDatabase for the mime type for the source files of the project. In 99.9% of the cases no files need to be read for this as the file extension will resolve the type. The expensiveness comes from the sheer number of files that can occur. (2) Calling compilers with the "(sub)project's compiler command line" to determine the macros. While the caches avoid redundant calls, the number of the unique compiler calls makes this still a ui-freezing experience. These two operations are moved into a worker thread. For this, the expensive compiler calls are encapsulated in thread safe lambdas ("runners") in order to keep the "mutexed" data minimal. The original API calls of the toolchains are implemented in terms of the runners. While adapting the project managers, remove also the calls to setProjectLanguage(). These are redundant because all of the project managers already set a proper value in the constructor. Also, currently there is no need (client) to report back detection of C sources in project parts. This also keeps CppProjectUpdater simple. There is still room for improvement: * Run the compiler calls in parallel instead of sequence. * Ensure that the mime type for a file is determined exactly once. Change-Id: I2efc4e132ee88e3c8f264012ec8fafe3d86c404f Reviewed-by: Tobias Hunger <tobias.hunger@qt.io>
2017-02-06 16:59:53 +01:00
/*
* Asks compiler for set of predefined macros
* flags are the compiler flags collected from project settings
* returns the list of defines, one per line, e.g. "#define __GXX_WEAK__ 1"
* Note: changing compiler flags sometimes changes macros set, e.g. -fopenmp
* adds _OPENMP macro, for full list of macro search by word "when" on this page:
* http://gcc.gnu.org/onlinedocs/cpp/Common-Predefined-Macros.html
*
* This runner must be thread-safe!
*/
return [env, compilerCommand = compilerCommand(),
platformCodeGenFlags, reinterpretOptions, macroCache, lang]
(const QStringList &flags) {
QStringList allFlags = platformCodeGenFlags + flags; // add only cxxflags is empty?
QStringList arguments = gccPredefinedMacrosOptions(lang) + filteredFlags(allFlags, true);
CppTools/ProjectManagers: Reduce ui blocking when loading projects ${AnyProject}::updateCppCodeModel() did two potentially not that cheap operations in the ui thread: (1) Querying the MimeDatabase for the mime type for the source files of the project. In 99.9% of the cases no files need to be read for this as the file extension will resolve the type. The expensiveness comes from the sheer number of files that can occur. (2) Calling compilers with the "(sub)project's compiler command line" to determine the macros. While the caches avoid redundant calls, the number of the unique compiler calls makes this still a ui-freezing experience. These two operations are moved into a worker thread. For this, the expensive compiler calls are encapsulated in thread safe lambdas ("runners") in order to keep the "mutexed" data minimal. The original API calls of the toolchains are implemented in terms of the runners. While adapting the project managers, remove also the calls to setProjectLanguage(). These are redundant because all of the project managers already set a proper value in the constructor. Also, currently there is no need (client) to report back detection of C sources in project parts. This also keeps CppProjectUpdater simple. There is still room for improvement: * Run the compiler calls in parallel instead of sequence. * Ensure that the mime type for a file is determined exactly once. Change-Id: I2efc4e132ee88e3c8f264012ec8fafe3d86c404f Reviewed-by: Tobias Hunger <tobias.hunger@qt.io>
2017-02-06 16:59:53 +01:00
arguments = reinterpretOptions(arguments);
const std::optional<MacroInspectionReport> cachedMacros = macroCache->check(arguments);
if (cachedMacros)
return cachedMacros.value();
const Macros macros = gccPredefinedMacros(findLocalCompiler(compilerCommand, env),
arguments,
env);
CppTools/ProjectManagers: Reduce ui blocking when loading projects ${AnyProject}::updateCppCodeModel() did two potentially not that cheap operations in the ui thread: (1) Querying the MimeDatabase for the mime type for the source files of the project. In 99.9% of the cases no files need to be read for this as the file extension will resolve the type. The expensiveness comes from the sheer number of files that can occur. (2) Calling compilers with the "(sub)project's compiler command line" to determine the macros. While the caches avoid redundant calls, the number of the unique compiler calls makes this still a ui-freezing experience. These two operations are moved into a worker thread. For this, the expensive compiler calls are encapsulated in thread safe lambdas ("runners") in order to keep the "mutexed" data minimal. The original API calls of the toolchains are implemented in terms of the runners. While adapting the project managers, remove also the calls to setProjectLanguage(). These are redundant because all of the project managers already set a proper value in the constructor. Also, currently there is no need (client) to report back detection of C sources in project parts. This also keeps CppProjectUpdater simple. There is still room for improvement: * Run the compiler calls in parallel instead of sequence. * Ensure that the mime type for a file is determined exactly once. Change-Id: I2efc4e132ee88e3c8f264012ec8fafe3d86c404f Reviewed-by: Tobias Hunger <tobias.hunger@qt.io>
2017-02-06 16:59:53 +01:00
const auto report = MacroInspectionReport{macros, languageVersion(lang, macros)};
macroCache->insert(arguments, report);
qCDebug(gccLog) << "MacroInspectionReport for code model:";
qCDebug(gccLog) << "Language version:" << static_cast<int>(report.languageVersion);
for (const Macro &m : macros) {
qCDebug(gccLog) << compilerCommand.toUserOutput()
<< (lang == Constants::CXX_LANGUAGE_ID ? ": C++ [" : ": C [")
<< arguments.join(", ") << "]"
<< QString::fromUtf8(m.toByteArray());
}
return report;
CppTools/ProjectManagers: Reduce ui blocking when loading projects ${AnyProject}::updateCppCodeModel() did two potentially not that cheap operations in the ui thread: (1) Querying the MimeDatabase for the mime type for the source files of the project. In 99.9% of the cases no files need to be read for this as the file extension will resolve the type. The expensiveness comes from the sheer number of files that can occur. (2) Calling compilers with the "(sub)project's compiler command line" to determine the macros. While the caches avoid redundant calls, the number of the unique compiler calls makes this still a ui-freezing experience. These two operations are moved into a worker thread. For this, the expensive compiler calls are encapsulated in thread safe lambdas ("runners") in order to keep the "mutexed" data minimal. The original API calls of the toolchains are implemented in terms of the runners. While adapting the project managers, remove also the calls to setProjectLanguage(). These are redundant because all of the project managers already set a proper value in the constructor. Also, currently there is no need (client) to report back detection of C sources in project parts. This also keeps CppProjectUpdater simple. There is still room for improvement: * Run the compiler calls in parallel instead of sequence. * Ensure that the mime type for a file is determined exactly once. Change-Id: I2efc4e132ee88e3c8f264012ec8fafe3d86c404f Reviewed-by: Tobias Hunger <tobias.hunger@qt.io>
2017-02-06 16:59:53 +01:00
};
}
/**
* @brief Parses gcc flags -std=*, -fopenmp, -fms-extensions.
* @see http://gcc.gnu.org/onlinedocs/gcc/C-Dialect-Options.html
*/
LanguageExtensions GccToolChain::languageExtensions(const QStringList &cxxflags) const
{
LanguageExtensions extensions = defaultLanguageExtensions();
const QStringList allCxxflags = m_platformCodeGenFlags + cxxflags; // add only cxxflags is empty?
for (const QString &flag : allCxxflags) {
if (flag.startsWith("-std=")) {
const QByteArray std = flag.mid(5).toLatin1();
if (std.startsWith("gnu"))
extensions |= LanguageExtension::Gnu;
else
extensions &= ~LanguageExtensions(LanguageExtension::Gnu);
} else if (flag == "-fopenmp") {
extensions |= LanguageExtension::OpenMP;
} else if (flag == "-fms-extensions") {
extensions |= LanguageExtension::Microsoft;
}
}
// Clang knows -fborland-extensions".
if (m_subType == Clang && cxxflags.contains("-fborland-extensions"))
extensions |= LanguageExtension::Borland;
return extensions;
}
WarningFlags GccToolChain::warningFlags(const QStringList &cflags) const
{
// based on 'LC_ALL="en" gcc -Q --help=warnings | grep enabled'
WarningFlags flags(WarningFlags::Deprecated | WarningFlags::IgnoredQualifiers
| WarningFlags::SignedComparison | WarningFlags::UninitializedVars);
WarningFlags groupWall(WarningFlags::All | WarningFlags::UnknownPragma | WarningFlags::UnusedFunctions
| WarningFlags::UnusedLocals | WarningFlags::UnusedResult | WarningFlags::UnusedValue
| WarningFlags::SignedComparison | WarningFlags::UninitializedVars);
WarningFlags groupWextra(WarningFlags::Extra | WarningFlags::IgnoredQualifiers | WarningFlags::UnusedParams);
for (int end = cflags.size(), i = 0; i != end; ++i) {
const QString &flag = cflags[i];
if (flag == "--all-warnings")
flags |= groupWall;
else if (flag == "--extra-warnings")
flags |= groupWextra;
WarningFlagAdder add(flag, flags);
if (add.triggered())
continue;
// supported by clang too
add("error", WarningFlags::AsErrors);
add("all", groupWall);
add("extra", groupWextra);
add("deprecated", WarningFlags::Deprecated);
add("effc++", WarningFlags::EffectiveCxx);
add("ignored-qualifiers", WarningFlags::IgnoredQualifiers);
add("non-virtual-dtor", WarningFlags::NonVirtualDestructor);
add("overloaded-virtual", WarningFlags::OverloadedVirtual);
add("shadow", WarningFlags::HiddenLocals);
add("sign-compare", WarningFlags::SignedComparison);
add("unknown-pragmas", WarningFlags::UnknownPragma);
add("unused", WarningFlags::UnusedFunctions | WarningFlags::UnusedLocals | WarningFlags::UnusedParams
| WarningFlags::UnusedResult | WarningFlags::UnusedValue);
add("unused-function", WarningFlags::UnusedFunctions);
add("unused-variable", WarningFlags::UnusedLocals);
add("unused-parameter", WarningFlags::UnusedParams);
add("unused-result", WarningFlags::UnusedResult);
add("unused-value", WarningFlags::UnusedValue);
add("uninitialized", WarningFlags::UninitializedVars);
}
if (m_subType == Clang) {
for (int end = cflags.size(), i = 0; i != end; ++i) {
const QString &flag = cflags[i];
if (flag == "-Wdocumentation")
flags |= WarningFlags::Documentation;
if (flag == "-Wno-documentation")
flags &= ~WarningFlags::Documentation;
}
}
return flags;
}
FilePaths GccToolChain::includedFiles(const QStringList &flags, const FilePath &directoryPath) const
{
return ToolChain::includedFiles("-include", flags, directoryPath, PossiblyConcatenatedFlag::No);
}
QStringList GccToolChain::gccPrepareArguments(const QStringList &flags,
const FilePath &sysRoot,
const QStringList &platformCodeGenFlags,
Id languageId,
OptionsReinterpreter reinterpretOptions)
{
QStringList arguments;
const bool hasKitSysroot = !sysRoot.isEmpty();
if (hasKitSysroot)
arguments.append(QString("--sysroot=%1").arg(sysRoot.nativePath()));
QStringList allFlags;
allFlags << platformCodeGenFlags << flags;
arguments += filteredFlags(allFlags, !hasKitSysroot);
arguments << languageOption(languageId) << "-E" << "-v" << "-";
arguments = reinterpretOptions(arguments);
return arguments;
}
// NOTE: extraHeaderPathsFunction must NOT capture this or it's members!!!
void GccToolChain::initExtraHeaderPathsFunction(ExtraHeaderPathsFunction &&extraHeaderPathsFunction) const
{
m_extraHeaderPathsFunction = std::move(extraHeaderPathsFunction);
}
HeaderPaths GccToolChain::builtInHeaderPaths(const Utils::Environment &env,
const Utils::FilePath &compilerCommand,
const QStringList &platformCodeGenFlags,
OptionsReinterpreter reinterpretOptions,
HeaderPathsCache headerCache,
Utils::Id languageId,
ExtraHeaderPathsFunction extraHeaderPathsFunction,
const QStringList &flags,
const Utils::FilePath &sysRoot,
const QString &originalTargetTriple)
{
QStringList arguments = gccPrepareArguments(flags,
sysRoot,
platformCodeGenFlags,
languageId,
reinterpretOptions);
// Must be clang case only.
if (!originalTargetTriple.isEmpty())
arguments << "-target" << originalTargetTriple;
const std::optional<HeaderPaths> cachedPaths = headerCache->check({env, arguments});
if (cachedPaths)
return cachedPaths.value();
HeaderPaths paths = gccHeaderPaths(findLocalCompiler(compilerCommand, env),
arguments,
env);
extraHeaderPathsFunction(paths);
headerCache->insert({env, arguments}, paths);
qCDebug(gccLog) << "Reporting header paths to code model:";
for (const HeaderPath &hp : std::as_const(paths)) {
qCDebug(gccLog) << compilerCommand.toUserOutput()
<< (languageId == Constants::CXX_LANGUAGE_ID ? ": C++ [" : ": C [")
<< arguments.join(", ") << "]" << hp.path;
}
return paths;
}
ToolChain::BuiltInHeaderPathsRunner GccToolChain::createBuiltInHeaderPathsRunner(
const Environment &env) const
{
CppTools/ProjectManagers: Reduce ui blocking when loading projects ${AnyProject}::updateCppCodeModel() did two potentially not that cheap operations in the ui thread: (1) Querying the MimeDatabase for the mime type for the source files of the project. In 99.9% of the cases no files need to be read for this as the file extension will resolve the type. The expensiveness comes from the sheer number of files that can occur. (2) Calling compilers with the "(sub)project's compiler command line" to determine the macros. While the caches avoid redundant calls, the number of the unique compiler calls makes this still a ui-freezing experience. These two operations are moved into a worker thread. For this, the expensive compiler calls are encapsulated in thread safe lambdas ("runners") in order to keep the "mutexed" data minimal. The original API calls of the toolchains are implemented in terms of the runners. While adapting the project managers, remove also the calls to setProjectLanguage(). These are redundant because all of the project managers already set a proper value in the constructor. Also, currently there is no need (client) to report back detection of C sources in project parts. This also keeps CppProjectUpdater simple. There is still room for improvement: * Run the compiler calls in parallel instead of sequence. * Ensure that the mime type for a file is determined exactly once. Change-Id: I2efc4e132ee88e3c8f264012ec8fafe3d86c404f Reviewed-by: Tobias Hunger <tobias.hunger@qt.io>
2017-02-06 16:59:53 +01:00
// Using a clean environment breaks ccache/distcc/etc.
Environment fullEnv = env;
addToEnvironment(fullEnv);
CppTools/ProjectManagers: Reduce ui blocking when loading projects ${AnyProject}::updateCppCodeModel() did two potentially not that cheap operations in the ui thread: (1) Querying the MimeDatabase for the mime type for the source files of the project. In 99.9% of the cases no files need to be read for this as the file extension will resolve the type. The expensiveness comes from the sheer number of files that can occur. (2) Calling compilers with the "(sub)project's compiler command line" to determine the macros. While the caches avoid redundant calls, the number of the unique compiler calls makes this still a ui-freezing experience. These two operations are moved into a worker thread. For this, the expensive compiler calls are encapsulated in thread safe lambdas ("runners") in order to keep the "mutexed" data minimal. The original API calls of the toolchains are implemented in terms of the runners. While adapting the project managers, remove also the calls to setProjectLanguage(). These are redundant because all of the project managers already set a proper value in the constructor. Also, currently there is no need (client) to report back detection of C sources in project parts. This also keeps CppProjectUpdater simple. There is still room for improvement: * Run the compiler calls in parallel instead of sequence. * Ensure that the mime type for a file is determined exactly once. Change-Id: I2efc4e132ee88e3c8f264012ec8fafe3d86c404f Reviewed-by: Tobias Hunger <tobias.hunger@qt.io>
2017-02-06 16:59:53 +01:00
if (m_subType == Clang) {
// This runner must be thread-safe!
return [fullEnv,
compilerCommand = compilerCommand(),
platformCodeGenFlags = m_platformCodeGenFlags,
reinterpretOptions = m_optionsReinterpreter,
headerCache = headerPathsCache(),
languageId = language(),
extraHeaderPathsFunction = m_extraHeaderPathsFunction](const QStringList &flags,
const FilePath &sysRoot,
const QString &target) {
return builtInHeaderPaths(fullEnv,
compilerCommand,
platformCodeGenFlags,
reinterpretOptions,
headerCache,
languageId,
extraHeaderPathsFunction,
flags,
sysRoot,
target);
};
}
CppTools/ProjectManagers: Reduce ui blocking when loading projects ${AnyProject}::updateCppCodeModel() did two potentially not that cheap operations in the ui thread: (1) Querying the MimeDatabase for the mime type for the source files of the project. In 99.9% of the cases no files need to be read for this as the file extension will resolve the type. The expensiveness comes from the sheer number of files that can occur. (2) Calling compilers with the "(sub)project's compiler command line" to determine the macros. While the caches avoid redundant calls, the number of the unique compiler calls makes this still a ui-freezing experience. These two operations are moved into a worker thread. For this, the expensive compiler calls are encapsulated in thread safe lambdas ("runners") in order to keep the "mutexed" data minimal. The original API calls of the toolchains are implemented in terms of the runners. While adapting the project managers, remove also the calls to setProjectLanguage(). These are redundant because all of the project managers already set a proper value in the constructor. Also, currently there is no need (client) to report back detection of C sources in project parts. This also keeps CppProjectUpdater simple. There is still room for improvement: * Run the compiler calls in parallel instead of sequence. * Ensure that the mime type for a file is determined exactly once. Change-Id: I2efc4e132ee88e3c8f264012ec8fafe3d86c404f Reviewed-by: Tobias Hunger <tobias.hunger@qt.io>
2017-02-06 16:59:53 +01:00
// This runner must be thread-safe!
return [fullEnv,
compilerCommand = compilerCommand(),
platformCodeGenFlags = m_platformCodeGenFlags,
reinterpretOptions = m_optionsReinterpreter,
headerCache = headerPathsCache(),
languageId = language(),
extraHeaderPathsFunction = m_extraHeaderPathsFunction](const QStringList &flags,
const FilePath &sysRoot,
const QString &) {
return builtInHeaderPaths(fullEnv,
compilerCommand,
platformCodeGenFlags,
reinterpretOptions,
headerCache,
languageId,
extraHeaderPathsFunction,
flags,
sysRoot,
/*originalTargetTriple=*/""); // Must be empty for gcc.
CppTools/ProjectManagers: Reduce ui blocking when loading projects ${AnyProject}::updateCppCodeModel() did two potentially not that cheap operations in the ui thread: (1) Querying the MimeDatabase for the mime type for the source files of the project. In 99.9% of the cases no files need to be read for this as the file extension will resolve the type. The expensiveness comes from the sheer number of files that can occur. (2) Calling compilers with the "(sub)project's compiler command line" to determine the macros. While the caches avoid redundant calls, the number of the unique compiler calls makes this still a ui-freezing experience. These two operations are moved into a worker thread. For this, the expensive compiler calls are encapsulated in thread safe lambdas ("runners") in order to keep the "mutexed" data minimal. The original API calls of the toolchains are implemented in terms of the runners. While adapting the project managers, remove also the calls to setProjectLanguage(). These are redundant because all of the project managers already set a proper value in the constructor. Also, currently there is no need (client) to report back detection of C sources in project parts. This also keeps CppProjectUpdater simple. There is still room for improvement: * Run the compiler calls in parallel instead of sequence. * Ensure that the mime type for a file is determined exactly once. Change-Id: I2efc4e132ee88e3c8f264012ec8fafe3d86c404f Reviewed-by: Tobias Hunger <tobias.hunger@qt.io>
2017-02-06 16:59:53 +01:00
};
}
void GccToolChain::addCommandPathToEnvironment(const FilePath &command, Environment &env)
{
env.prependOrSetPath(command.parentDir());
}
void GccToolChain::addToEnvironment(Environment &env) const
{
// On Windows gcc invokes cc1plus which is in libexec directory.
// cc1plus depends on libwinpthread-1.dll which is in bin, so bin must be in the PATH.
if (compilerCommand().osType() == OsTypeWindows)
addCommandPathToEnvironment(compilerCommand(), env);
if (m_subType == Clang) {
const QString sysroot = sysRoot();
if (!sysroot.isEmpty())
env.prependOrSetPath(FilePath::fromString(sysroot) / "bin");
// Clang takes PWD as basis for debug info, if set.
// When running Qt Creator from a shell, PWD is initially set to an "arbitrary" value.
// Since the tools are not called through a shell, PWD is never changed to the actual cwd,
// so we better make sure PWD is empty to begin with
env.unset("PWD");
}
}
QStringList GccToolChain::suggestedMkspecList() const
{
if (m_subType == MinGW) {
if (HostOsInfo::isWindowsHost())
return {"win32-g++"};
if (HostOsInfo::isLinuxHost()) {
if (version().startsWith("4.6."))
return {"win32-g++-4.6-cross", "unsupported/win32-g++-4.6-cross"};
return {"win32-g++-cross", "unsupported/win32-g++-cross"};
}
return {};
}
if (m_subType == Clang) {
if (const ToolChain * const parentTc = ToolChainManager::findToolChain(m_parentToolChainId))
return parentTc->suggestedMkspecList();
const Abi abi = targetAbi();
if (abi.os() == Abi::DarwinOS)
return {"macx-clang", "macx-clang-32", "unsupported/macx-clang", "macx-ios-clang"};
if (abi.os() == Abi::LinuxOS)
return {"linux-clang", "unsupported/linux-clang"};
if (abi.os() == Abi::WindowsOS)
return {"win32-clang-g++"};
if (abi.architecture() == Abi::AsmJsArchitecture && abi.binaryFormat() == Abi::EmscriptenFormat)
return {"wasm-emscripten"};
return {}; // Note: Not supported by Qt yet, so default to the mkspec the Qt was build with
}
const Abi abi = targetAbi();
const Abi host = Abi::hostAbi();
// Cross compile: Leave the mkspec alone!
if (abi.architecture() != host.architecture()
|| abi.os() != host.os()
|| abi.osFlavor() != host.osFlavor()) // Note: This can fail:-(
return {};
if (abi.os() == Abi::DarwinOS) {
QString v = version();
// prefer versioned g++ on macOS. This is required to enable building for older macOS versions
if (v.startsWith("4.0") && compilerCommand().endsWith("-4.0"))
return {"macx-g++40"};
if (v.startsWith("4.2") && compilerCommand().endsWith("-4.2"))
return {"macx-g++42"};
return {"macx-g++"};
}
if (abi.os() == Abi::LinuxOS) {
if (abi.osFlavor() != Abi::GenericFlavor)
return {}; // most likely not a desktop, so leave the mkspec alone.
if (abi.wordWidth() == host.wordWidth()) {
// no need to explicitly set the word width, but provide that mkspec anyway to make sure
// that the correct compiler is picked if a mkspec with a wordwidth is given.
return {"linux-g++", "linux-g++-" + QString::number(targetAbi().wordWidth())};
}
return {"linux-g++-" + QString::number(targetAbi().wordWidth())};
}
if (abi.os() == Abi::BsdOS && abi.osFlavor() == Abi::FreeBsdFlavor)
return {"freebsd-g++"};
return {};
}
static FilePath mingwAwareMakeCommand(const Environment &environment)
{
const QStringList makes
= HostOsInfo::isWindowsHost() ? QStringList({"mingw32-make.exe", "make.exe"}) : QStringList({"make"});
FilePath tmp;
for (const QString &make : makes) {
tmp = environment.searchInPath(make);
if (!tmp.isEmpty())
return tmp;
}
return FilePath::fromString(makes.first());
}
FilePath GccToolChain::makeCommand(const Environment &environment) const
{
if (m_subType == Clang || m_subType == MinGW)
return mingwAwareMakeCommand(environment);
const FilePath tmp = environment.searchInPath("make");
return tmp.isEmpty() ? "make" : tmp;
}
QList<OutputLineParser *> GccToolChain::createOutputParsers() const
{
if (m_subType == Clang)
return ClangParser::clangParserSuite();
return GccParser::gccParserSuite();
}
void GccToolChain::resetToolChain(const FilePath &path)
{
bool resetDisplayName = (displayName() == defaultDisplayName());
setCompilerCommand(path);
const Abi currentAbi = targetAbi();
const DetectedAbisResult detectedAbis = detectSupportedAbis();
m_supportedAbis = detectedAbis.supportedAbis;
m_originalTargetTriple = detectedAbis.originalTargetTriple;
m_installDir = installDir();
if (m_supportedAbis.isEmpty())
setTargetAbiNoSignal(Abi());
else if (!m_supportedAbis.contains(currentAbi))
setTargetAbiNoSignal(m_supportedAbis.at(0));
if (resetDisplayName)
setDisplayName(defaultDisplayName()); // calls toolChainUpdated()!
else
toolChainUpdated();
}
void GccToolChain::setPlatformCodeGenFlags(const QStringList &flags)
{
if (flags != m_platformCodeGenFlags) {
m_platformCodeGenFlags = flags;
toolChainUpdated();
}
}
QStringList GccToolChain::extraCodeModelFlags() const
{
return platformCodeGenFlags();
}
/*!
Code gen flags that have to be passed to the compiler.
*/
QStringList GccToolChain::platformCodeGenFlags() const
{
return m_platformCodeGenFlags;
}
void GccToolChain::setPlatformLinkerFlags(const QStringList &flags)
{
if (flags != m_platformLinkerFlags) {
m_platformLinkerFlags = flags;
toolChainUpdated();
}
}
/*!
Flags that have to be passed to the linker.
For example: \c{-arch armv7}
*/
QStringList GccToolChain::platformLinkerFlags() const
{
return m_platformLinkerFlags;
}
void GccToolChain::toMap(Store &data) const
{
ToolChain::toMap(data);
data.insert(compilerPlatformCodeGenFlagsKeyC, m_platformCodeGenFlags);
data.insert(compilerPlatformLinkerFlagsKeyC, m_platformLinkerFlags);
data.insert(originalTargetTripleKeyC, m_originalTargetTriple);
data.insert(supportedAbisKeyC, Utils::transform<QStringList>(m_supportedAbis, &Abi::toString));
if (m_subType == Clang) {
data.insert(parentToolChainIdKeyC, m_parentToolChainId);
data.insert(priorityKeyC, m_priority);
}
}
void GccToolChain::fromMap(const Store &data)
{
ToolChain::fromMap(data);
if (hasError())
return;
m_platformCodeGenFlags = data.value(compilerPlatformCodeGenFlagsKeyC).toStringList();
m_platformLinkerFlags = data.value(compilerPlatformLinkerFlagsKeyC).toStringList();
m_originalTargetTriple = data.value(originalTargetTripleKeyC).toString();
const QStringList abiList = data.value(supportedAbisKeyC).toStringList();
m_supportedAbis.clear();
for (const QString &a : abiList)
m_supportedAbis.append(Abi::fromString(a));
const QString targetAbiString = data.value(targetAbiKeyC).toString();
if (targetAbiString.isEmpty())
resetToolChain(compilerCommand());
if (m_subType == Clang) {
m_parentToolChainId = data.value(parentToolChainIdKeyC).toByteArray();
m_priority = data.value(priorityKeyC, PriorityNormal).toInt();
syncAutodetectedWithParentToolchains();
}
}
bool GccToolChain::operator ==(const ToolChain &other) const
{
if (!ToolChain::operator ==(other))
return false;
auto gccTc = static_cast<const GccToolChain *>(&other);
return compilerCommand() == gccTc->compilerCommand() && targetAbi() == gccTc->targetAbi()
&& m_platformCodeGenFlags == gccTc->m_platformCodeGenFlags
&& m_platformLinkerFlags == gccTc->m_platformLinkerFlags;
}
std::unique_ptr<ToolChainConfigWidget> GccToolChain::createConfigurationWidget()
{
return std::make_unique<GccToolChainConfigWidget>(this);
}
void GccToolChain::updateSupportedAbis() const
{
if (m_supportedAbis.isEmpty()) {
const DetectedAbisResult detected = detectSupportedAbis();
m_supportedAbis = detected.supportedAbis;
m_originalTargetTriple = detected.originalTargetTriple;
}
}
CppTools/ProjectManagers: Reduce ui blocking when loading projects ${AnyProject}::updateCppCodeModel() did two potentially not that cheap operations in the ui thread: (1) Querying the MimeDatabase for the mime type for the source files of the project. In 99.9% of the cases no files need to be read for this as the file extension will resolve the type. The expensiveness comes from the sheer number of files that can occur. (2) Calling compilers with the "(sub)project's compiler command line" to determine the macros. While the caches avoid redundant calls, the number of the unique compiler calls makes this still a ui-freezing experience. These two operations are moved into a worker thread. For this, the expensive compiler calls are encapsulated in thread safe lambdas ("runners") in order to keep the "mutexed" data minimal. The original API calls of the toolchains are implemented in terms of the runners. While adapting the project managers, remove also the calls to setProjectLanguage(). These are redundant because all of the project managers already set a proper value in the constructor. Also, currently there is no need (client) to report back detection of C sources in project parts. This also keeps CppProjectUpdater simple. There is still room for improvement: * Run the compiler calls in parallel instead of sequence. * Ensure that the mime type for a file is determined exactly once. Change-Id: I2efc4e132ee88e3c8f264012ec8fafe3d86c404f Reviewed-by: Tobias Hunger <tobias.hunger@qt.io>
2017-02-06 16:59:53 +01:00
void GccToolChain::setOptionsReinterpreter(const OptionsReinterpreter &optionsReinterpreter)
{
m_optionsReinterpreter = optionsReinterpreter;
}
GccToolChain::DetectedAbisResult GccToolChain::detectSupportedAbis() const
{
Environment env = compilerCommand().deviceEnvironment();
addToEnvironment(env);
ProjectExplorer::Macros macros = createMacroInspectionRunner()({}).macros;
return guessGccAbi(findLocalCompiler(compilerCommand(), env),
env,
macros,
platformCodeGenFlags());
}
QString GccToolChain::detectVersion() const
{
Environment env = compilerCommand().deviceEnvironment();
addToEnvironment(env);
return gccVersion(findLocalCompiler(compilerCommand(), env), env,
filteredFlags(platformCodeGenFlags(), true));
}
Utils::FilePath GccToolChain::detectInstallDir() const
{
Environment env = compilerCommand().deviceEnvironment();
addToEnvironment(env);
return gccInstallDir(findLocalCompiler(compilerCommand(), env), env,
filteredFlags(platformCodeGenFlags(), true));
}
// --------------------------------------------------------------------------
// GccToolChainFactory
// --------------------------------------------------------------------------
static Utils::FilePaths gnuSearchPathsFromRegistry()
{
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\\";
Utils::FilePaths searchPaths;
QSettings registry(kRegistryToken, QSettings::NativeFormat);
const auto productGroups = registry.childGroups();
for (const QString &productKey : productGroups) {
if (!productKey.startsWith("GNU Tools for ARM Embedded Processors"))
continue;
registry.beginGroup(productKey);
QString uninstallFilePath = registry.value("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(FilePath::fromString(toolchainPath));
}
return searchPaths;
}
static Utils::FilePaths atmelSearchPathsFromRegistry()
{
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\\";
Utils::FilePaths searchPaths;
QSettings registry(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("GCC"))
continue;
registry.beginGroup(toolchainKey);
const auto entries = registry.childGroups();
for (const auto &entryKey : entries) {
registry.beginGroup(entryKey);
const QString installDir = registry.value("Native/InstallDir").toString();
const QString version = registry.value("Native/Version").toString();
registry.endGroup();
QString toolchainPath = installDir
+ QLatin1String("/Atmel Toolchain/")
+ toolchainKey + QLatin1String("/Native/")
+ version;
if (toolchainKey.startsWith("ARM"))
toolchainPath += QLatin1String("/arm-gnu-toolchain");
else if (toolchainKey.startsWith("AVR32"))
toolchainPath += QLatin1String("/avr32-gnu-toolchain");
else if (toolchainKey.startsWith("AVR8"))
toolchainPath += QLatin1String("/avr8-gnu-toolchain");
else
break;
toolchainPath += QLatin1String("/bin");
const FilePath path = FilePath::fromString(toolchainPath);
if (path.exists()) {
searchPaths.push_back(FilePath::fromString(toolchainPath));
break;
}
}
registry.endGroup();
}
// This code enumerate the installed toolchains provided
// by the Atmel Studio v7.
registry.beginGroup("AtmelStudio");
const auto productVersions = registry.childGroups();
for (const auto &productVersionKey : productVersions) {
registry.beginGroup(productVersionKey);
const QString installDir = registry.value("InstallDir").toString();
registry.endGroup();
const QStringList knownToolchainSubdirs = {
"/toolchain/arm/arm-gnu-toolchain/bin/",
"/toolchain/avr8/avr8-gnu-toolchain/bin/",
"/toolchain/avr32/avr32-gnu-toolchain/bin/",
};
for (const auto &subdir : knownToolchainSubdirs) {
const QString toolchainPath = installDir + subdir;
const FilePath path = FilePath::fromString(toolchainPath);
if (!path.exists())
continue;
searchPaths.push_back(path);
}
}
registry.endGroup();
return searchPaths;
}
static Utils::FilePaths renesasRl78SearchPathsFromRegistry()
{
if (!HostOsInfo::isWindowsHost())
return {};
// Registry token for the "Renesas RL78" toolchain.
static const char kRegistryToken[] = "HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\" \
"Windows\\CurrentVersion\\Uninstall";
Utils::FilePaths searchPaths;
QSettings registry(QLatin1String(kRegistryToken), QSettings::NativeFormat);
const auto productGroups = registry.childGroups();
for (const QString &productKey : productGroups) {
if (!productKey.startsWith("GCC for Renesas RL78"))
continue;
registry.beginGroup(productKey);
const QString installLocation = registry.value("InstallLocation").toString();
registry.endGroup();
if (installLocation.isEmpty())
continue;
const FilePath toolchainPath = FilePath::fromUserInput(installLocation)
.pathAppended("rl78-elf/rl78-elf/bin");
if (!toolchainPath.exists())
continue;
searchPaths.push_back(toolchainPath);
}
return searchPaths;
}
GccToolChainFactory::GccToolChainFactory()
{
setDisplayName(Tr::tr("GCC"));
setSupportedToolChainType(Constants::GCC_TOOLCHAIN_TYPEID);
setSupportedLanguages({Constants::C_LANGUAGE_ID, Constants::CXX_LANGUAGE_ID});
setToolchainConstructor([] { return new GccToolChain(Constants::GCC_TOOLCHAIN_TYPEID); });
setUserCreatable(true);
}
Toolchains GccToolChainFactory::autoDetect(const ToolchainDetector &detector) const
{
// GCC is almost never what you want on macOS, but it is by default found in /usr/bin
if (HostOsInfo::isMacHost() && detector.device->type() == Constants::DESKTOP_DEVICE_TYPE)
return {};
Toolchains tcs;
static const auto tcChecker = [](const ToolChain *tc) {
return tc->targetAbi().osFlavor() != Abi::WindowsMSysFlavor
&& tc->compilerCommand().fileName() != "c89-gcc"
&& tc->compilerCommand().fileName() != "c99-gcc";
};
tcs.append(autoDetectToolchains("g++",
DetectVariants::Yes,
Constants::CXX_LANGUAGE_ID,
Constants::GCC_TOOLCHAIN_TYPEID,
detector,
toolchainConstructor(),
tcChecker));
tcs.append(autoDetectToolchains("gcc",
DetectVariants::Yes,
Constants::C_LANGUAGE_ID,
Constants::GCC_TOOLCHAIN_TYPEID,
detector,
toolchainConstructor(),
tcChecker));
return tcs;
}
Toolchains GccToolChainFactory::detectForImport(const ToolChainDescription &tcd) const
{
const QString fileName = tcd.compilerPath.completeBaseName();
const QString resolvedSymlinksFileName = tcd.compilerPath.resolveSymlinks().completeBaseName();
const bool isCCompiler = tcd.language == Constants::C_LANGUAGE_ID
&& (fileName.startsWith("gcc")
|| fileName.endsWith("gcc")
|| (fileName == "cc" && !resolvedSymlinksFileName.contains("clang")));
const bool isCxxCompiler = tcd.language == Constants::CXX_LANGUAGE_ID
&& (fileName.startsWith("g++")
|| fileName.endsWith("g++")
|| (fileName == "c++" && !resolvedSymlinksFileName.contains("clang")));
if (isCCompiler || isCxxCompiler) {
return autoDetectToolChain(tcd, toolchainConstructor(), [](const ToolChain *tc) {
return tc->targetAbi().osFlavor() != Abi::WindowsMSysFlavor;
});
}
return {};
}
static FilePaths findCompilerCandidates(const ToolchainDetector &detector,
const QString &compilerName,
bool detectVariants)
{
const IDevice::ConstPtr device = detector.device;
const QFileInfo fi(compilerName);
if (device->type() == ProjectExplorer::Constants::DESKTOP_DEVICE_TYPE && fi.isAbsolute() && fi.isFile())
return {FilePath::fromString(compilerName)};
QStringList nameFilters(compilerName);
if (detectVariants) {
nameFilters
<< compilerName + "-[1-9]*" // "clang-8", "gcc-5"
<< ("*-" + compilerName) // "avr-gcc", "avr32-gcc"
<< ("*-" + compilerName + "-[1-9]*")// "avr-gcc-4.8.1", "avr32-gcc-4.4.7"
<< ("*-*-*-" + compilerName) // "arm-none-eabi-gcc"
<< ("*-*-*-" + compilerName + "-[1-9]*") // "arm-none-eabi-gcc-9.1.0"
<< ("*-*-*-*-" + compilerName) // "x86_64-pc-linux-gnu-gcc"
<< ("*-*-*-*-" + compilerName
+ "-[1-9]*"); // "x86_64-pc-linux-gnu-gcc-7.4.1"
}
const Utils::OsType os = device ? device->osType() : HostOsInfo::hostOs();
nameFilters = transform(nameFilters, [os](const QString &baseName) {
return OsSpecificAspects::withExecutableSuffix(os, baseName);
});
FilePaths compilerPaths;
FilePaths searchPaths = detector.searchPaths;
if (device && device->type() != ProjectExplorer::Constants::DESKTOP_DEVICE_TYPE) {
if (searchPaths.isEmpty())
searchPaths = device->systemEnvironment().path();
searchPaths = Utils::transform(searchPaths, [&](const FilePath &onDevice) {
return device->filePath(onDevice.path());
});
} else if (searchPaths.isEmpty()) {
searchPaths = Environment::systemEnvironment().path();
searchPaths << gnuSearchPathsFromRegistry();
searchPaths << atmelSearchPathsFromRegistry();
searchPaths << renesasRl78SearchPathsFromRegistry();
if (HostOsInfo::isMacHost()) {
searchPaths << "/opt/homebrew/opt/ccache/libexec" // homebrew arm
<< "/usr/local/opt/ccache/libexec" // homebrew intel
<< "/opt/local/libexec/ccache"; // macports, no links are created automatically though
}
if (HostOsInfo::isAnyUnixHost()) {
FilePath ccachePath = "/usr/lib/ccache/bin";
if (!ccachePath.exists())
ccachePath = "/usr/lib/ccache";
if (ccachePath.exists() && !searchPaths.contains(ccachePath))
searchPaths << ccachePath;
}
}
for (const FilePath &searchPath : std::as_const(searchPaths)) {
static const QRegularExpression regexp(binaryRegexp);
const auto callBack = [os, &compilerPaths, compilerName](const FilePath &candidate) {
if (candidate.fileName() == OsSpecificAspects::withExecutableSuffix(os, compilerName)
|| regexp.match(candidate.path()).hasMatch()) {
compilerPaths << candidate;
}
return IterationPolicy::Continue;
};
searchPath.iterateDirectory(callBack, {nameFilters, QDir::Files | QDir::Executable});
}
return compilerPaths;
}
Toolchains GccToolChainFactory::autoDetectToolchains(const QString &compilerName,
DetectVariants detectVariants,
const Id language,
const Id requiredTypeId,
const ToolchainDetector &detector,
const ToolChainConstructor &constructor,
const ToolchainChecker &checker)
{
const FilePaths compilerPaths =
findCompilerCandidates(detector, compilerName, detectVariants == DetectVariants::Yes);
Toolchains existingCandidates = filtered(detector.alreadyKnown,
[language](const ToolChain *tc) { return tc->language() == language; });
Toolchains result;
for (const FilePath &compilerPath : std::as_const(compilerPaths)) {
bool alreadyExists = false;
for (ToolChain * const existingTc : existingCandidates) {
// We have a match if the existing toolchain ultimately refers to the same file
// as the candidate path, either directly or via a hard or soft link.
// Exceptions:
// - clang++ is often a soft link to clang, but behaves differently.
// - ccache and icecc also create soft links that must not be followed here.
bool existingTcMatches = false;
const FilePath existingCommand = existingTc->compilerCommand();
if ((requiredTypeId == Constants::CLANG_TOOLCHAIN_TYPEID
&& ((language == Constants::CXX_LANGUAGE_ID && !existingCommand.fileName().contains("clang++"))
|| (language == Constants::C_LANGUAGE_ID && !existingCommand.baseName().endsWith("clang"))))
|| compilerPath.toString().contains("icecc")
|| compilerPath.toString().contains("ccache")) {
existingTcMatches = existingCommand == compilerPath;
} else {
existingTcMatches = existingCommand.isSameExecutable(compilerPath);
if (!existingTcMatches
&& HostOsInfo::isWindowsHost()
&& !existingCommand.needsDevice()
&& !compilerPath.needsDevice()) {
existingTcMatches = existingCommand.fileSize()
== compilerPath.fileSize();
}
}
if (existingTcMatches) {
if (existingTc->typeId() == requiredTypeId && (!checker || checker(existingTc))
&& !result.contains(existingTc)) {
result << existingTc;
}
alreadyExists = true;
}
}
if (!alreadyExists) {
const QList<ToolChain *> newToolchains
= autoDetectToolChain({compilerPath, language}, constructor, checker);
result << newToolchains;
existingCandidates << newToolchains;
}
}
return result;
}
Toolchains GccToolChainFactory::autoDetectToolChain(const ToolChainDescription &tcd,
const ToolChainConstructor &constructor,
const ToolchainChecker &checker)
{
Toolchains result;
Environment systemEnvironment = tcd.compilerPath.deviceEnvironment();
GccToolChain::addCommandPathToEnvironment(tcd.compilerPath, systemEnvironment);
const FilePath localCompilerPath = findLocalCompiler(tcd.compilerPath, systemEnvironment);
if (ToolChainManager::isBadToolchain(localCompilerPath))
return result;
Macros macros
= gccPredefinedMacros(localCompilerPath, gccPredefinedMacrosOptions(tcd.language),
systemEnvironment);
if (macros.isEmpty()) {
ToolChainManager::addBadToolchain(localCompilerPath);
return result;
}
const GccToolChain::DetectedAbisResult detectedAbis = guessGccAbi(localCompilerPath,
systemEnvironment,
macros);
for (const Abi &abi : detectedAbis.supportedAbis) {
std::unique_ptr<GccToolChain> tc(dynamic_cast<GccToolChain *>(constructor()));
if (!tc)
return result;
tc->setLanguage(tcd.language);
tc->setDetection(ToolChain::AutoDetection);
tc->predefinedMacrosCache()
->insert(QStringList(),
ToolChain::MacroInspectionReport{macros,
ToolChain::languageVersion(tcd.language, macros)});
tc->setCompilerCommand(tcd.compilerPath);
tc->setSupportedAbis(detectedAbis.supportedAbis);
tc->setTargetAbi(abi);
tc->setOriginalTargetTriple(detectedAbis.originalTargetTriple);
tc->setDisplayName(tc->defaultDisplayName()); // reset displayname
if (!checker || checker(tc.get()))
result.append(tc.release());
}
return result;
}
// --------------------------------------------------------------------------
// GccToolChainConfigWidget
// --------------------------------------------------------------------------
namespace Internal {
class TargetTripleWidget : public QWidget
{
Q_OBJECT
public:
TargetTripleWidget(const ToolChain *toolchain)
{
const auto layout = new QHBoxLayout(this);
layout->setContentsMargins(0, 0, 0, 0);
m_tripleLineEdit.setEnabled(false);
m_overrideCheckBox.setText(Tr::tr("Override for code model"));
m_overrideCheckBox.setToolTip(Tr::tr("Enable in the rare case that the code model\n"
"fails because Clang does not understand the target architecture."));
layout->addWidget(&m_tripleLineEdit, 1);
layout->addWidget(&m_overrideCheckBox);
layout->addStretch(1);
connect(&m_tripleLineEdit, &QLineEdit::textEdited, this, &TargetTripleWidget::valueChanged);
connect(&m_overrideCheckBox, &QCheckBox::toggled,
&m_tripleLineEdit, &QLineEdit::setEnabled);
m_tripleLineEdit.setText(toolchain->effectiveCodeModelTargetTriple());
m_overrideCheckBox.setChecked(!toolchain->explicitCodeModelTargetTriple().isEmpty());
}
QString explicitCodeModelTargetTriple() const
{
if (m_overrideCheckBox.isChecked())
return m_tripleLineEdit.text();
return {};
}
signals:
void valueChanged();
private:
QLineEdit m_tripleLineEdit;
QCheckBox m_overrideCheckBox;
};
}
GccToolChainConfigWidget::GccToolChainConfigWidget(GccToolChain *tc) :
ToolChainConfigWidget(tc),
m_abiWidget(new AbiWidget),
m_subType(tc->m_subType),
m_compilerCommand(new PathChooser),
m_targetTripleWidget(new TargetTripleWidget(tc))
{
const QStringList gnuVersionArgs = QStringList("--version");
m_compilerCommand->setExpectedKind(PathChooser::ExistingCommand);
m_compilerCommand->setCommandVersionArguments(gnuVersionArgs);
m_compilerCommand->setHistoryCompleter("PE.Gcc.Command.History");
m_compilerCommand->setAllowPathFromDevice(true);
m_mainLayout->addRow(Tr::tr("&Compiler path:"), m_compilerCommand);
m_platformCodeGenFlagsLineEdit = new QLineEdit(this);
m_platformCodeGenFlagsLineEdit->setText(ProcessArgs::joinArgs(tc->platformCodeGenFlags()));
m_mainLayout->addRow(Tr::tr("Platform codegen flags:"), m_platformCodeGenFlagsLineEdit);
m_platformLinkerFlagsLineEdit = new QLineEdit(this);
m_platformLinkerFlagsLineEdit->setText(ProcessArgs::joinArgs(tc->platformLinkerFlags()));
m_mainLayout->addRow(Tr::tr("Platform linker flags:"), m_platformLinkerFlagsLineEdit);
m_mainLayout->addRow(Tr::tr("&ABI:"), m_abiWidget);
m_mainLayout->addRow(Tr::tr("Target triple:"), m_targetTripleWidget);
m_abiWidget->setEnabled(false);
addErrorLabel();
setFromToolchain();
connect(m_compilerCommand, &PathChooser::rawPathChanged,
this, &GccToolChainConfigWidget::handleCompilerCommandChange);
connect(m_platformCodeGenFlagsLineEdit, &QLineEdit::editingFinished,
this, &GccToolChainConfigWidget::handlePlatformCodeGenFlagsChange);
connect(m_platformLinkerFlagsLineEdit, &QLineEdit::editingFinished,
this, &GccToolChainConfigWidget::handlePlatformLinkerFlagsChange);
connect(m_abiWidget, &AbiWidget::abiChanged, this, &ToolChainConfigWidget::dirty);
connect(m_targetTripleWidget, &TargetTripleWidget::valueChanged,
this, &ToolChainConfigWidget::dirty);
if (m_subType == GccToolChain::Clang) {
if (!HostOsInfo::isWindowsHost() || tc->typeId() != Constants::CLANG_TOOLCHAIN_TYPEID)
return;
// Remove m_abiWidget row because the parent toolchain abi is going to be used.
m_mainLayout->removeRow(m_mainLayout->rowCount() - 3); // FIXME: Do something sane instead.
m_abiWidget = nullptr;
m_parentToolchainCombo = new QComboBox(this);
m_mainLayout->insertRow(m_mainLayout->rowCount() - 1,
Tr::tr("Parent toolchain:"),
m_parentToolchainCombo);
ToolChainManager *tcManager = ToolChainManager::instance();
m_parentToolChainConnections.append(
connect(tcManager, &ToolChainManager::toolChainUpdated, this, [this](ToolChain *tc) {
if (tc->typeId() == Constants::MINGW_TOOLCHAIN_TYPEID)
updateParentToolChainComboBox();
}));
m_parentToolChainConnections.append(
connect(tcManager, &ToolChainManager::toolChainAdded, this, [this](ToolChain *tc) {
if (tc->typeId() == Constants::MINGW_TOOLCHAIN_TYPEID)
updateParentToolChainComboBox();
}));
m_parentToolChainConnections.append(
connect(tcManager, &ToolChainManager::toolChainRemoved, this, [this](ToolChain *tc) {
if (tc->id() == toolChain()->id()) {
for (QMetaObject::Connection &connection : m_parentToolChainConnections)
QObject::disconnect(connection);
return;
}
if (tc->typeId() == Constants::MINGW_TOOLCHAIN_TYPEID)
updateParentToolChainComboBox();
}));
updateParentToolChainComboBox();
}
}
void GccToolChainConfigWidget::applyImpl()
{
if (toolChain()->isAutoDetected())
return;
auto tc = static_cast<GccToolChain *>(toolChain());
Q_ASSERT(tc);
QString displayName = tc->displayName();
tc->setCompilerCommand(m_compilerCommand->filePath());
if (m_abiWidget) {
tc->setSupportedAbis(m_abiWidget->supportedAbis());
tc->setTargetAbi(m_abiWidget->currentAbi());
}
tc->setInstallDir(tc->detectInstallDir());
tc->setOriginalTargetTriple(tc->detectSupportedAbis().originalTargetTriple);
tc->setExplicitCodeModelTargetTriple(m_targetTripleWidget->explicitCodeModelTargetTriple());
tc->setDisplayName(displayName); // reset display name
tc->setPlatformCodeGenFlags(splitString(m_platformCodeGenFlagsLineEdit->text()));
tc->setPlatformLinkerFlags(splitString(m_platformLinkerFlagsLineEdit->text()));
if (m_macros.isEmpty())
return;
tc->predefinedMacrosCache()
->insert(tc->platformCodeGenFlags(),
ToolChain::MacroInspectionReport{m_macros,
ToolChain::languageVersion(tc->language(),
m_macros)});
if (m_subType == GccToolChain::Clang && m_parentToolchainCombo) {
tc->m_parentToolChainId.clear();
const QByteArray parentId = m_parentToolchainCombo->currentData().toByteArray();
if (!parentId.isEmpty()) {
for (const ToolChain *mingwTC : mingwToolChains()) {
if (parentId == mingwTC->id()) {
tc->m_parentToolChainId = mingwTC->id();
tc->setTargetAbi(mingwTC->targetAbi());
tc->setSupportedAbis(mingwTC->supportedAbis());
break;
}
}
}
}
}
void GccToolChainConfigWidget::setFromToolchain()
{
// subwidgets are not yet connected!
QSignalBlocker blocker(this);
auto tc = static_cast<GccToolChain *>(toolChain());
m_compilerCommand->setFilePath(tc->compilerCommand());
m_platformCodeGenFlagsLineEdit->setText(ProcessArgs::joinArgs(tc->platformCodeGenFlags(),
HostOsInfo::hostOs()));
m_platformLinkerFlagsLineEdit->setText(ProcessArgs::joinArgs(tc->platformLinkerFlags(),
HostOsInfo::hostOs()));
if (m_abiWidget) {
m_abiWidget->setAbis(tc->supportedAbis(), tc->targetAbi());
if (!m_isReadOnly && !m_compilerCommand->filePath().toString().isEmpty())
m_abiWidget->setEnabled(true);
}
if (m_parentToolchainCombo)
updateParentToolChainComboBox();
}
bool GccToolChainConfigWidget::isDirtyImpl() const
{
auto tc = static_cast<GccToolChain *>(toolChain());
if (m_compilerCommand->filePath() != tc->compilerCommand()
|| m_platformCodeGenFlagsLineEdit->text()
!= ProcessArgs::joinArgs(tc->platformCodeGenFlags())
|| m_platformLinkerFlagsLineEdit->text()
!= ProcessArgs::joinArgs(tc->platformLinkerFlags())
|| m_targetTripleWidget->explicitCodeModelTargetTriple()
!= tc->explicitCodeModelTargetTriple()
|| (m_abiWidget && m_abiWidget->currentAbi() != tc->targetAbi())) {
return true;
}
if (!m_parentToolchainCombo)
return false;
const GccToolChain *parentTC = mingwToolChainFromId(tc->m_parentToolChainId);
const QByteArray parentId = parentTC ? parentTC->id() : QByteArray();
return parentId != m_parentToolchainCombo->currentData();
}
void GccToolChainConfigWidget::makeReadOnlyImpl()
{
m_compilerCommand->setReadOnly(true);
if (m_abiWidget)
m_abiWidget->setEnabled(false);
m_platformCodeGenFlagsLineEdit->setEnabled(false);
m_platformLinkerFlagsLineEdit->setEnabled(false);
m_targetTripleWidget->setEnabled(false);
m_isReadOnly = true;
if (m_parentToolchainCombo)
m_parentToolchainCombo->setEnabled(false);
}
void GccToolChainConfigWidget::handleCompilerCommandChange()
{
if (!m_abiWidget)
return;
bool haveCompiler = false;
Abi currentAbi = m_abiWidget->currentAbi();
bool customAbi = m_abiWidget->isCustomAbi() && m_abiWidget->isEnabled();
FilePath path = m_compilerCommand->filePath();
Abis abiList;
if (!path.isEmpty()) {
haveCompiler = path.isExecutableFile();
}
if (haveCompiler) {
Environment env = path.deviceEnvironment();
GccToolChain::addCommandPathToEnvironment(path, env);
QStringList args = gccPredefinedMacrosOptions(Constants::CXX_LANGUAGE_ID)
+ splitString(m_platformCodeGenFlagsLineEdit->text());
const FilePath localCompilerPath = findLocalCompiler(path, env);
m_macros = gccPredefinedMacros(localCompilerPath, args, env);
abiList = guessGccAbi(localCompilerPath, env, m_macros,
splitString(m_platformCodeGenFlagsLineEdit->text())).supportedAbis;
}
m_abiWidget->setEnabled(haveCompiler);
// Find a good ABI for the new compiler:
Abi newAbi;
if (customAbi || abiList.contains(currentAbi))
newAbi = currentAbi;
m_abiWidget->setAbis(abiList, newAbi);
emit dirty();
}
void GccToolChainConfigWidget::handlePlatformCodeGenFlagsChange()
{
QString str1 = m_platformCodeGenFlagsLineEdit->text();
QString str2 = ProcessArgs::joinArgs(splitString(str1));
if (str1 != str2)
m_platformCodeGenFlagsLineEdit->setText(str2);
else
handleCompilerCommandChange();
}
void GccToolChainConfigWidget::handlePlatformLinkerFlagsChange()
{
QString str1 = m_platformLinkerFlagsLineEdit->text();
QString str2 = ProcessArgs::joinArgs(splitString(str1));
if (str1 != str2)
m_platformLinkerFlagsLineEdit->setText(str2);
else
emit dirty();
}
// --------------------------------------------------------------------------
// ClangToolChain
// --------------------------------------------------------------------------
void GccToolChain::syncAutodetectedWithParentToolchains()
{
if (!HostOsInfo::isWindowsHost() || typeId() != Constants::CLANG_TOOLCHAIN_TYPEID
|| !isAutoDetected()) {
return;
}
QObject::disconnect(m_thisToolchainRemovedConnection);
QObject::disconnect(m_mingwToolchainAddedConnection);
if (!ToolChainManager::isLoaded()) {
connect(ToolChainManager::instance(), &ToolChainManager::toolChainsLoaded, this,
[id = id()] {
if (ToolChain * const tc = ToolChainManager::findToolChain(id)) {
if (tc->typeId() == Constants::CLANG_TOOLCHAIN_TYPEID)
static_cast<GccToolChain *>(tc)->syncAutodetectedWithParentToolchains();
}
});
return;
}
if (!mingwToolChainFromId(m_parentToolChainId)) {
const QList<ToolChain *> mingwTCs = mingwToolChains();
m_parentToolChainId = mingwTCs.isEmpty() ? QByteArray() : mingwTCs.front()->id();
}
// Subscribe only autodetected toolchains.
ToolChainManager *tcManager = ToolChainManager::instance();
m_mingwToolchainAddedConnection
= connect(tcManager, &ToolChainManager::toolChainAdded, this, [this](ToolChain *tc) {
if (tc->typeId() == Constants::MINGW_TOOLCHAIN_TYPEID
&& !mingwToolChainFromId(m_parentToolChainId)) {
m_parentToolChainId = tc->id();
}
});
m_thisToolchainRemovedConnection
= connect(tcManager, &ToolChainManager::toolChainRemoved, this, [this](ToolChain *tc) {
if (tc == this) {
QObject::disconnect(m_thisToolchainRemovedConnection);
QObject::disconnect(m_mingwToolchainAddedConnection);
} else if (m_parentToolChainId == tc->id()) {
const QList<ToolChain *> mingwTCs = mingwToolChains();
m_parentToolChainId = mingwTCs.isEmpty() ? QByteArray() : mingwTCs.front()->id();
}
});
}
bool GccToolChain::matchesCompilerCommand(const FilePath &command) const
{
if (m_subType == Clang) {
if (!m_resolvedCompilerCommand) {
m_resolvedCompilerCommand = FilePath();
if (HostOsInfo::isMacHost()
&& compilerCommand().parentDir() == FilePath::fromString("/usr/bin")) {
Process xcrun;
xcrun.setCommand({"/usr/bin/xcrun", {"-f", compilerCommand().fileName()}});
xcrun.runBlocking();
const FilePath output = FilePath::fromString(xcrun.cleanedStdOut().trimmed());
if (output.isExecutableFile() && output != compilerCommand())
m_resolvedCompilerCommand = output;
}
}
if (!m_resolvedCompilerCommand->isEmpty()
&& m_resolvedCompilerCommand->isSameExecutable(command))
return true;
}
return ToolChain::matchesCompilerCommand(command);
}
QString GccToolChain::sysRoot() const
{
if (m_subType == Clang) {
if (const GccToolChain *parentTC = mingwToolChainFromId(m_parentToolChainId)) {
const FilePath mingwCompiler = parentTC->compilerCommand();
return mingwCompiler.parentDir().parentDir().toString();
}
}
return {};
}
// --------------------------------------------------------------------------
// ClangToolChainFactory
// --------------------------------------------------------------------------
ClangToolChainFactory::ClangToolChainFactory()
{
setDisplayName(Tr::tr("Clang"));
setSupportedToolChainType(Constants::CLANG_TOOLCHAIN_TYPEID);
setSupportedLanguages({Constants::CXX_LANGUAGE_ID, Constants::C_LANGUAGE_ID});
setToolchainConstructor([] {
return new GccToolChain(Constants::CLANG_TOOLCHAIN_TYPEID, GccToolChain::Clang);
});
}
Toolchains ClangToolChainFactory::autoDetect(const ToolchainDetector &detector) const
{
Toolchains tcs;
Toolchains known = detector.alreadyKnown;
tcs.append(autoDetectToolchains("clang++",
DetectVariants::Yes,
Constants::CXX_LANGUAGE_ID,
Constants::CLANG_TOOLCHAIN_TYPEID,
detector,
toolchainConstructor()));
tcs.append(autoDetectToolchains("clang",
DetectVariants::Yes,
Constants::C_LANGUAGE_ID,
Constants::CLANG_TOOLCHAIN_TYPEID,
detector,
toolchainConstructor()));
known.append(tcs);
const FilePath compilerPath = Core::ICore::clangExecutable(CLANG_BINDIR);
if (!compilerPath.isEmpty()) {
const FilePath clang = compilerPath.parentDir().pathAppended("clang").withExecutableSuffix();
tcs.append(
autoDetectToolchains(clang.toString(),
DetectVariants::No,
Constants::C_LANGUAGE_ID,
Constants::CLANG_TOOLCHAIN_TYPEID,
ToolchainDetector(known, detector.device, detector.searchPaths),
toolchainConstructor()));
}
return tcs;
}
Toolchains ClangToolChainFactory::detectForImport(const ToolChainDescription &tcd) const
{
const QString fileName = tcd.compilerPath.completeBaseName();
const QString resolvedSymlinksFileName = tcd.compilerPath.resolveSymlinks().completeBaseName();
const bool isCCompiler = tcd.language == Constants::C_LANGUAGE_ID
&& ((fileName.startsWith("clang") && !fileName.startsWith("clang++"))
|| (fileName == "cc" && resolvedSymlinksFileName.contains("clang")));
const bool isCxxCompiler = tcd.language == Constants::CXX_LANGUAGE_ID
&& (fileName.startsWith("clang++")
|| (fileName == "c++" && resolvedSymlinksFileName.contains("clang")));
if (isCCompiler || isCxxCompiler) {
return autoDetectToolChain(tcd, toolchainConstructor());
}
return {};
}
void GccToolChainConfigWidget::updateParentToolChainComboBox()
{
QTC_ASSERT(m_parentToolchainCombo, return);
auto *tc = static_cast<GccToolChain *>(toolChain());
QByteArray parentId = m_parentToolchainCombo->currentData().toByteArray();
if (tc->isAutoDetected() || m_parentToolchainCombo->count() == 0)
parentId = tc->m_parentToolChainId;
const GccToolChain *parentTC = mingwToolChainFromId(parentId);
m_parentToolchainCombo->clear();
m_parentToolchainCombo->addItem(parentTC ? parentTC->displayName() : QString(),
parentTC ? parentId : QByteArray());
if (tc->isAutoDetected())
return;
for (const ToolChain *mingwTC : mingwToolChains()) {
if (mingwTC->id() == parentId)
continue;
if (mingwTC->language() != tc->language())
continue;
m_parentToolchainCombo->addItem(mingwTC->displayName(), mingwTC->id());
}
}
// --------------------------------------------------------------------------
// MingwToolChainFactory
// --------------------------------------------------------------------------
MingwToolChainFactory::MingwToolChainFactory()
{
setDisplayName(Tr::tr("MinGW"));
setSupportedToolChainType(Constants::MINGW_TOOLCHAIN_TYPEID);
setSupportedLanguages({Constants::CXX_LANGUAGE_ID, Constants::C_LANGUAGE_ID});
setToolchainConstructor([] {
return new GccToolChain(Constants::MINGW_TOOLCHAIN_TYPEID, GccToolChain::MinGW);
});
}
Toolchains MingwToolChainFactory::autoDetect(const ToolchainDetector &detector) const
{
static const auto tcChecker = [](const ToolChain *tc) {
return tc->targetAbi().osFlavor() == Abi::WindowsMSysFlavor;
};
Toolchains result = autoDetectToolchains("g++",
DetectVariants::Yes,
Constants::CXX_LANGUAGE_ID,
Constants::MINGW_TOOLCHAIN_TYPEID,
detector,
toolchainConstructor(),
tcChecker);
result += autoDetectToolchains("gcc",
DetectVariants::Yes,
Constants::C_LANGUAGE_ID,
Constants::MINGW_TOOLCHAIN_TYPEID,
detector,
toolchainConstructor(),
tcChecker);
return result;
}
Toolchains MingwToolChainFactory::detectForImport(const ToolChainDescription &tcd) const
{
const QString fileName = tcd.compilerPath.completeBaseName();
const bool cCompiler = tcd.language == Constants::C_LANGUAGE_ID
&& ((fileName.startsWith("gcc") || fileName.endsWith("gcc"))
|| fileName == "cc");
const bool cxxCompiler = tcd.language == Constants::CXX_LANGUAGE_ID
&& ((fileName.startsWith("g++") || fileName.endsWith("g++"))
|| (fileName.startsWith("c++") || fileName.endsWith("c++")));
if (cCompiler || cxxCompiler) {
return autoDetectToolChain(tcd, toolchainConstructor(), [](const ToolChain *tc) {
return tc->targetAbi().osFlavor() == Abi::WindowsMSysFlavor;
});
}
return {};
}
// --------------------------------------------------------------------------
// LinuxIccToolChain
// --------------------------------------------------------------------------
LinuxIccToolChain::LinuxIccToolChain() :
GccToolChain(Constants::LINUXICC_TOOLCHAIN_TYPEID)
{
setTypeDisplayName(Tr::tr("ICC"));
}
/**
* Similar to \a GccToolchain::languageExtensions, but uses "-openmp" instead of
* "-fopenmp" and "-fms-dialect[=ver]" instead of "-fms-extensions".
* @see UNIX manual for "icc"
*/
LanguageExtensions LinuxIccToolChain::languageExtensions(const QStringList &cxxflags) const
{
QStringList copy = cxxflags;
copy.removeAll("-fopenmp");
copy.removeAll("-fms-extensions");
LanguageExtensions extensions = GccToolChain::languageExtensions(cxxflags);
if (cxxflags.contains("-openmp"))
extensions |= LanguageExtension::OpenMP;
if (cxxflags.contains("-fms-dialect")
|| cxxflags.contains("-fms-dialect=8")
|| cxxflags.contains("-fms-dialect=9")
|| cxxflags.contains("-fms-dialect=10"))
extensions |= LanguageExtension::Microsoft;
return extensions;
}
QList<OutputLineParser *> LinuxIccToolChain::createOutputParsers() const
{
return LinuxIccParser::iccParserSuite();
}
QStringList LinuxIccToolChain::suggestedMkspecList() const
{
return {QString("linux-icc-%1").arg(targetAbi().wordWidth())};
}
// --------------------------------------------------------------------------
// LinuxIccToolChainFactory
// --------------------------------------------------------------------------
LinuxIccToolChainFactory::LinuxIccToolChainFactory()
{
setDisplayName(Tr::tr("ICC"));
setSupportedToolChainType(Constants::LINUXICC_TOOLCHAIN_TYPEID);
setSupportedLanguages({Constants::CXX_LANGUAGE_ID, Constants::C_LANGUAGE_ID});
setToolchainConstructor([] { return new LinuxIccToolChain; });
}
Toolchains LinuxIccToolChainFactory::autoDetect(const ToolchainDetector &detector) const
{
Toolchains result = autoDetectToolchains("icpc",
DetectVariants::No,
Constants::CXX_LANGUAGE_ID,
Constants::LINUXICC_TOOLCHAIN_TYPEID,
detector,
toolchainConstructor());
result += autoDetectToolchains("icc",
DetectVariants::Yes,
Constants::C_LANGUAGE_ID,
Constants::LINUXICC_TOOLCHAIN_TYPEID,
detector,
toolchainConstructor());
return result;
}
Toolchains LinuxIccToolChainFactory::detectForImport(const ToolChainDescription &tcd) const
{
const QString fileName = tcd.compilerPath.completeBaseName();
if ((tcd.language == Constants::CXX_LANGUAGE_ID && fileName.startsWith("icpc")) ||
(tcd.language == Constants::C_LANGUAGE_ID && fileName.startsWith("icc"))) {
return autoDetectToolChain(tcd, toolchainConstructor());
}
return {};
}
GccToolChain::WarningFlagAdder::WarningFlagAdder(const QString &flag, WarningFlags &flags) :
m_flags(flags)
{
if (!flag.startsWith("-W")) {
m_triggered = true;
return;
}
m_doesEnable = !flag.startsWith("-Wno-");
if (m_doesEnable)
m_flagUtf8 = flag.mid(2).toUtf8();
else
m_flagUtf8 = flag.mid(5).toUtf8();
}
void GccToolChain::WarningFlagAdder::operator ()(const char name[], WarningFlags flagsSet)
{
if (m_triggered)
return;
if (0 == strcmp(m_flagUtf8.data(), name))
{
m_triggered = true;
if (m_doesEnable)
m_flags |= flagsSet;
else
m_flags &= ~flagsSet;
}
}
bool GccToolChain::WarningFlagAdder::triggered() const
{
return m_triggered;
}
} // namespace ProjectExplorer
// Unit tests:
#ifdef WITH_TESTS
# include "projectexplorer.h"
# include <QTest>
# include <QUrl>
namespace ProjectExplorer {
void ProjectExplorerPlugin::testGccAbiGuessing_data()
{
QTest::addColumn<QString>("input");
QTest::addColumn<QByteArray>("macros");
QTest::addColumn<QStringList>("abiList");
QTest::newRow("invalid input")
<< QString::fromLatin1("Some text")
<< QByteArray("")
<< (QStringList());
QTest::newRow("empty input")
<< QString::fromLatin1("")
<< QByteArray("")
<< (QStringList());
QTest::newRow("empty input (with macros)")
<< QString::fromLatin1("")
<< QByteArray("#define __SIZEOF_SIZE_T__ 8\n#define __Something\n")
<< (QStringList());
QTest::newRow("broken input -- 64bit")
<< QString::fromLatin1("arm-none-foo-gnueabi")
<< QByteArray("#define __SIZEOF_SIZE_T__ 8\n#define __Something\n")
<< QStringList({"arm-baremetal-generic-elf-64bit"});
QTest::newRow("broken input -- 32bit")
<< QString::fromLatin1("arm-none-foo-gnueabi")
<< QByteArray("#define __SIZEOF_SIZE_T__ 4\n#define __Something\n")
<< QStringList({"arm-baremetal-generic-elf-32bit"});
QTest::newRow("totally broken input -- 32bit")
<< QString::fromLatin1("foo-bar-foo")
<< QByteArray("#define __SIZEOF_SIZE_T__ 4\n#define __Something\n")
<< QStringList();
QTest::newRow("Linux 1 (32bit intel)")
<< QString::fromLatin1("i686-linux-gnu")
<< QByteArray("#define __SIZEOF_SIZE_T__ 4\n")
<< QStringList({"x86-linux-generic-elf-32bit"});
QTest::newRow("Linux 2 (32bit intel)")
<< QString::fromLatin1("i486-linux-gnu")
<< QByteArray("#define __SIZEOF_SIZE_T__ 4\n")
<< QStringList({"x86-linux-generic-elf-32bit"});
QTest::newRow("Linux 3 (64bit intel)")
<< QString::fromLatin1("x86_64-linux-gnu")
<< QByteArray("#define __SIZEOF_SIZE_T__ 8\n")
<< QStringList("x86-linux-generic-elf-64bit");
QTest::newRow("Linux 3 (64bit intel -- non 64bit)")
<< QString::fromLatin1("x86_64-linux-gnu")
<< QByteArray("#define __SIZEOF_SIZE_T__ 4\n")
<< QStringList({"x86-linux-generic-elf-32bit"});
QTest::newRow("Linux 4 (32bit mips)")
<< QString::fromLatin1("mipsel-linux-uclibc")
<< QByteArray("#define __SIZEOF_SIZE_T__ 4")
<< QStringList({"mips-linux-generic-elf-32bit"});
QTest::newRow("Linux 5 (QTCREATORBUG-4690)") // from QTCREATORBUG-4690
<< QString::fromLatin1("x86_64-redhat-linux6E")
<< QByteArray("#define __SIZEOF_SIZE_T__ 8\n")
<< QStringList("x86-linux-generic-elf-64bit");
QTest::newRow("Linux 6 (QTCREATORBUG-4690)") // from QTCREATORBUG-4690
<< QString::fromLatin1("x86_64-redhat-linux")
<< QByteArray("#define __SIZEOF_SIZE_T__ 8\n")
<< QStringList("x86-linux-generic-elf-64bit");
QTest::newRow("Linux 7 (arm)")
<< QString::fromLatin1("armv5tl-montavista-linux-gnueabi")
<< QByteArray("#define __SIZEOF_SIZE_T__ 4\n")
<< QStringList({"arm-linux-generic-elf-32bit"});
QTest::newRow("Linux 8 (arm)")
<< QString::fromLatin1("arm-angstrom-linux-gnueabi")
<< QByteArray("#define __SIZEOF_SIZE_T__ 4\n")
<< QStringList({"arm-linux-generic-elf-32bit"});
QTest::newRow("Linux 9 (ppc)")
<< QString::fromLatin1("powerpc-nsg-linux")
<< QByteArray("#define __SIZEOF_SIZE_T__ 4\n")
<< QStringList({"ppc-linux-generic-elf-32bit"});
QTest::newRow("Linux 10 (ppc 64bit)")
<< QString::fromLatin1("powerpc64-suse-linux")
<< QByteArray("#define __SIZEOF_SIZE_T__ 8\n")
<< QStringList({"ppc-linux-generic-elf-64bit"});
QTest::newRow("Linux 11 (64bit mips)")
<< QString::fromLatin1("mips64el-linux-uclibc")
<< QByteArray("#define __SIZEOF_SIZE_T__ 8")
<< QStringList({"mips-linux-generic-elf-64bit"});
QTest::newRow("Mingw 1 (32bit)")
<< QString::fromLatin1("i686-w64-mingw32")
<< QByteArray("#define __SIZEOF_SIZE_T__ 4\r\n")
<< QStringList({"x86-windows-msys-pe-32bit"});
QTest::newRow("Mingw 2 (64bit)")
<< QString::fromLatin1("i686-w64-mingw32")
<< QByteArray("#define __SIZEOF_SIZE_T__ 8\r\n")
<< QStringList({"x86-windows-msys-pe-64bit"});
QTest::newRow("Mingw 3 (32 bit)")
<< QString::fromLatin1("mingw32")
<< QByteArray("#define __SIZEOF_SIZE_T__ 4\r\n")
<< QStringList({"x86-windows-msys-pe-32bit"});
QTest::newRow("Cross Mingw 1 (64bit)")
<< QString::fromLatin1("amd64-mingw32msvc")
<< QByteArray("#define __SIZEOF_SIZE_T__ 8\r\n")
<< QStringList({"x86-windows-msys-pe-64bit"});
QTest::newRow("Cross Mingw 2 (32bit)")
<< QString::fromLatin1("i586-mingw32msvc")
<< QByteArray("#define __SIZEOF_SIZE_T__ 4\r\n")
<< QStringList({"x86-windows-msys-pe-32bit"});
QTest::newRow("Clang 1: windows")
<< QString::fromLatin1("x86_64-pc-win32")
<< QByteArray("#define __SIZEOF_SIZE_T__ 8\r\n")
<< QStringList("x86-windows-msys-pe-64bit");
QTest::newRow("Clang 1: linux")
<< QString::fromLatin1("x86_64-unknown-linux-gnu")
<< QByteArray("#define __SIZEOF_SIZE_T__ 8\n")
<< QStringList("x86-linux-generic-elf-64bit");
QTest::newRow("Mac 1")
<< QString::fromLatin1("i686-apple-darwin10")
<< QByteArray("#define __SIZEOF_SIZE_T__ 8\n")
<< QStringList({"x86-darwin-generic-mach_o-64bit", "x86-darwin-generic-mach_o-32bit"});
QTest::newRow("Mac 2")
<< QString::fromLatin1("powerpc-apple-darwin10")
<< QByteArray("#define __SIZEOF_SIZE_T__ 8\n")
<< QStringList({"ppc-darwin-generic-mach_o-64bit", "ppc-darwin-generic-mach_o-32bit"});
QTest::newRow("Mac 3")
<< QString::fromLatin1("i686-apple-darwin9")
<< QByteArray("#define __SIZEOF_SIZE_T__ 4\n")
<< QStringList({"x86-darwin-generic-mach_o-32bit", "x86-darwin-generic-mach_o-64bit"});
QTest::newRow("Mac IOS")
<< QString::fromLatin1("arm-apple-darwin9")
<< QByteArray("#define __SIZEOF_SIZE_T__ 4\n")
<< QStringList({"arm-darwin-generic-mach_o-32bit", "arm-darwin-generic-mach_o-64bit"});
QTest::newRow("Intel 1")
<< QString::fromLatin1("86_64 x86_64 GNU/Linux")
<< QByteArray("#define __SIZEOF_SIZE_T__ 8\n")
<< QStringList("x86-linux-generic-elf-64bit");
QTest::newRow("FreeBSD 1")
<< QString::fromLatin1("i386-portbld-freebsd9.0")
<< QByteArray("#define __SIZEOF_SIZE_T__ 4\n")
<< QStringList({"x86-bsd-freebsd-elf-32bit"});
QTest::newRow("FreeBSD 2")
<< QString::fromLatin1("i386-undermydesk-freebsd")
<< QByteArray("#define __SIZEOF_SIZE_T__ 4\n")
<< QStringList({"x86-bsd-freebsd-elf-32bit"});
}
void ProjectExplorerPlugin::testGccAbiGuessing()
{
QFETCH(QString, input);
QFETCH(QByteArray, macros);
QFETCH(QStringList, abiList);
const Abis al = guessGccAbi(input, ProjectExplorer::Macro::toMacros(macros));
QCOMPARE(al.count(), abiList.count());
for (int i = 0; i < al.count(); ++i)
QCOMPARE(al.at(i).toString(), abiList.at(i));
}
} // namespace ProjectExplorer
#endif
#include <gcctoolchain.moc>