Commit eb4d1584 authored by Friedemann Kleint's avatar Friedemann Kleint
Browse files

Add experimental clang-cl based toolchain.



Add new ClangClToolChain, which is autodetect by checking the registry
key created by LLVM and trying to find a suitable version of MSVC2015.

Add ClangClParser for parsing the output.

[ChangeLog][ProjectExplorer] Added experimental support for clang-cl.

Task-number: QTBUG-50804
Task-number: QTCREATORBUG-15641
Change-Id: Id34341570e935afc8ef6a104421d4abb5892176f
Reviewed-by: default avatarTobias Hunger <tobias.hunger@theqtcompany.com>
parent d3c23c33
......@@ -46,8 +46,12 @@ static QPair<Utils::FileName, int> parseFileName(const QString &input)
if (fileName.endsWith(QLatin1Char(')'))) {
int pos = fileName.lastIndexOf(QLatin1Char('('));
if (pos >= 0) {
// clang-cl gives column, too: "foo.cpp(34,1)" as opposed to MSVC "foo.cpp(34)".
int endPos = fileName.indexOf(QLatin1Char(','), pos + 1);
if (endPos < 0)
endPos = fileName.size() - 1;
bool ok = false;
int n = fileName.midRef(pos + 1, fileName.count() - pos - 2).toInt(&ok);
const int n = fileName.midRef(pos + 1, endPos - pos - 1).toInt(&ok);
if (ok) {
fileName = fileName.left(pos);
linenumber = n;
......@@ -191,6 +195,101 @@ void MsvcParser::doFlush()
emit addTask(t, m_lines, 1);
}
// --------------------------------------------------------------------------
// ClangClParser: The compiler errors look similar to MSVC, except that the
// column number is also given and there are no 4digit CXXXX error numbers.
// They are output to stderr.
// --------------------------------------------------------------------------
// ".\qwindowsgdinativeinterface.cpp(48,3) : error: unknown type name 'errr'"
static inline QString clangClCompilePattern()
{
return QLatin1Char('^') + QLatin1String(FILE_POS_PATTERN)
+ QLatin1String(" (warning|error): (")
+ QLatin1String(".*)$");
}
ClangClParser::ClangClParser()
: m_compileRegExp(clangClCompilePattern())
{
setObjectName(QLatin1String("ClangClParser"));
QTC_CHECK(m_compileRegExp.isValid());
}
void ClangClParser::stdOutput(const QString &line)
{
if (handleNmakeJomMessage(line, &m_lastTask)) {
m_linkedLines = 1;
doFlush();
return;
}
IOutputParser::stdOutput(line);
}
// Check for a code marker '~~~~ ^ ~~~~~~~~~~~~' underlining above code.
static inline bool isClangCodeMarker(const QString &trimmedLine)
{
return trimmedLine.constEnd() ==
std::find_if(trimmedLine.constBegin(), trimmedLine.constEnd(),
[] (QChar c) { return c != QLatin1Char(' ') && c != QLatin1Char('^') && c != QLatin1Char('~'); });
}
void ClangClParser::stdError(const QString &lineIn)
{
const QString line = IOutputParser::rightTrimmed(lineIn); // Strip \r\n.
if (handleNmakeJomMessage(line, &m_lastTask)) {
m_linkedLines = 1;
doFlush();
return;
}
// Finish a sequence of warnings/errors: "2 warnings generated."
if (!line.isEmpty() && line.at(0).isDigit() && line.endsWith(QLatin1String("generated."))) {
doFlush();
return;
}
// Start a new error message by a sequence of "In file included from " which is to be skipped.
if (line.startsWith(QLatin1String("In file included from "))) {
doFlush();
return;
}
QRegularExpressionMatch match = m_compileRegExp.match(line);
if (match.hasMatch()) {
doFlush();
const QPair<Utils::FileName, int> position = parseFileName(match.captured(1));
m_lastTask = Task(taskType(match.captured(2)), match.captured(3).trimmed(),
position.first, position.second,
Constants::TASK_CATEGORY_COMPILE);
m_linkedLines = 1;
return;
}
if (!m_lastTask.isNull()) {
const QString trimmed = line.trimmed();
if (isClangCodeMarker(trimmed)) {
doFlush();
return;
}
m_lastTask.description.append(QLatin1Char('\n'));
m_lastTask.description.append(trimmed);
++m_linkedLines;
return;
}
IOutputParser::stdError(lineIn);
}
void ClangClParser::doFlush()
{
if (!m_lastTask.isNull()) {
emit addTask(m_lastTask, m_linkedLines, 1);
m_lastTask.clear();
}
}
// Unit tests:
#ifdef WITH_TESTS
......@@ -423,4 +522,93 @@ void ProjectExplorerPlugin::testMsvcOutputParsers()
outputLines);
}
void ProjectExplorerPlugin::testClangClOutputParsers_data()
{
QTest::addColumn<QString>("input");
QTest::addColumn<OutputParserTester::Channel>("inputChannel");
QTest::addColumn<QString>("childStdOutLines");
QTest::addColumn<QString>("childStdErrLines");
QTest::addColumn<QList<Task> >("tasks");
QTest::addColumn<QString>("outputLines");
const QString warning1 = QLatin1String(
"private field 'm_version' is not used [-Wunused-private-field]\n"
"const int m_version; //! majorVersion<<8 + minorVersion");
const QString warning2 = QLatin1String(
"unused variable 'formatTextPlainC' [-Wunused-const-variable]\n"
"static const char formatTextPlainC[] = \"text/plain\";");
const QString warning3 = QLatin1String(
"unused variable 'formatTextHtmlC' [-Wunused-const-variable]\n"
"static const char formatTextHtmlC[] = \"text/html\";");
const QString error1 = QLatin1String(
"unknown type name 'errr'\n"
" errr");
const QString expectedError1 = QLatin1String(
"unknown type name 'errr'\n"
"errr"); // Line 2 trimmed.
const QString error2 = QLatin1String(
"expected unqualified-id\n"
"void *QWindowsGdiNativeInterface::nativeResourceForBackingStore(const QByteArray &resource, QBackingStore *bs)");
const QString clangClCompilerLog = QLatin1String(
"In file included from .\\qwindowseglcontext.cpp:40:\n"
"./qwindowseglcontext.h(282,15) : warning: ") + warning1 + QLatin1String("\n"
"5 warnings generated.\n"
".\\qwindowsclipboard.cpp(60,19) : warning: ") + warning2 + QLatin1String("\n"
" ^\n"
".\\qwindowsclipboard.cpp(61,19) : warning: ") + warning3 + QLatin1String("\n"
" ^\n"
"2 warnings generated.\n"
".\\qwindowsgdinativeinterface.cpp(48,3) : error: ") + error1 + QLatin1String("\n"
" ^\n"
".\\qwindowsgdinativeinterface.cpp(51,1) : error: ") + error2 + QLatin1String("\n"
"^\n"
"2 errors generated.\n");
const QString ignoredStderr = QLatin1String(
"NMAKE : fatal error U1077: 'D:\\opt\\LLVM64_390\\bin\\clang-cl.EXE' : return code '0x1'\n"
"Stop.");
const QString input = clangClCompilerLog + ignoredStderr;
const QString expectedStderr = ignoredStderr + QLatin1Char('\n');
QTest::newRow("error")
<< input
<< OutputParserTester::STDERR
<< QString() << expectedStderr
<< (QList<Task>()
<< Task(Task::Warning, warning1,
Utils::FileName::fromUserInput(QLatin1String("./qwindowseglcontext.h")), 282,
Constants::TASK_CATEGORY_COMPILE)
<< Task(Task::Warning, warning2,
Utils::FileName::fromUserInput(QLatin1String(".\\qwindowsclipboard.cpp")), 60,
Constants::TASK_CATEGORY_COMPILE)
<< Task(Task::Warning, warning3,
Utils::FileName::fromUserInput(QLatin1String(".\\qwindowsclipboard.cpp")), 61,
Constants::TASK_CATEGORY_COMPILE)
<< Task(Task::Error, expectedError1,
Utils::FileName::fromUserInput(QLatin1String(".\\qwindowsgdinativeinterface.cpp")), 48,
Constants::TASK_CATEGORY_COMPILE)
<< Task(Task::Error, error2,
Utils::FileName::fromUserInput(QLatin1String(".\\qwindowsgdinativeinterface.cpp")), 51,
Constants::TASK_CATEGORY_COMPILE))
<< QString();
}
void ProjectExplorerPlugin::testClangClOutputParsers()
{
OutputParserTester testbench;
testbench.appendOutputParser(new ClangClParser);
QFETCH(QString, input);
QFETCH(OutputParserTester::Channel, inputChannel);
QFETCH(QList<Task>, tasks);
QFETCH(QString, childStdOutLines);
QFETCH(QString, childStdErrLines);
QFETCH(QString, outputLines);
testbench.testParsing(input, inputChannel,
tasks, childStdOutLines, childStdErrLines,
outputLines);
}
#endif // WITH_TEST
......@@ -54,4 +54,22 @@ private:
int m_lines = 0;
};
class PROJECTEXPLORER_EXPORT ClangClParser : public ProjectExplorer::IOutputParser
{
Q_OBJECT
public:
ClangClParser();
void stdOutput(const QString &line) override;
void stdError(const QString &line) override;
private:
void doFlush() override;
const QRegularExpression m_compileRegExp;
Task m_lastTask;
int m_linkedLines = 0;
};
} // namespace ProjectExplorer
......@@ -339,8 +339,15 @@ Utils::Environment MsvcToolChain::readEnvironmentSetting(Utils::Environment& env
MsvcToolChain::MsvcToolChain(const QString &name, const Abi &abi,
const QString &varsBat, const QString &varsBatArg, Detection d) :
AbstractMsvcToolChain(Constants::MSVC_TOOLCHAIN_TYPEID, d, abi, varsBat),
m_varsBatArg(varsBatArg)
MsvcToolChain(Constants::MSVC_TOOLCHAIN_TYPEID, name, abi, varsBat, varsBatArg, d)
{
}
MsvcToolChain::MsvcToolChain(Core::Id typeId, const QString &name, const Abi &abi,
const QString &varsBat, const QString &varsBatArg,
Detection d)
: AbstractMsvcToolChain(typeId, d, abi, varsBat)
, m_varsBatArg(varsBatArg)
{
Q_ASSERT(!name.isEmpty());
......@@ -355,18 +362,13 @@ bool MsvcToolChain::isValid() const
return QFileInfo::exists(vcVarsBat);
}
MsvcToolChain::MsvcToolChain() :
AbstractMsvcToolChain(Constants::MSVC_TOOLCHAIN_TYPEID, ManualDetection)
MsvcToolChain::MsvcToolChain(Core::Id typeId)
: AbstractMsvcToolChain(typeId, ManualDetection)
{
}
MsvcToolChain *MsvcToolChain::readFromMap(const QVariantMap &data)
MsvcToolChain::MsvcToolChain() : MsvcToolChain(Constants::MSVC_TOOLCHAIN_TYPEID)
{
MsvcToolChain *tc = new MsvcToolChain;
if (tc->fromMap(data))
return tc;
delete tc;
return 0;
}
QString MsvcToolChain::typeDisplayName() const
......@@ -445,24 +447,27 @@ ToolChain *MsvcToolChain::clone() const
}
// --------------------------------------------------------------------------
// MsvcToolChainConfigWidget
// MsvcBasedToolChainConfigWidget: Creates a simple GUI without error label
// to display name and varsBat. Derived classes should add the error label and
// call setFromMsvcToolChain().
// --------------------------------------------------------------------------
MsvcToolChainConfigWidget::MsvcToolChainConfigWidget(ToolChain *tc) :
ToolChainConfigWidget(tc),
m_varsBatDisplayLabel(new QLabel(this))
MsvcBasedToolChainConfigWidget::MsvcBasedToolChainConfigWidget(ToolChain *tc)
: ToolChainConfigWidget(tc)
, m_nameDisplayLabel(new QLabel(this))
, m_varsBatDisplayLabel(new QLabel(this))
{
m_mainLayout->addRow(new QLabel(tc->displayName()));
m_nameDisplayLabel->setTextInteractionFlags(Qt::TextBrowserInteraction);
m_mainLayout->addRow(m_nameDisplayLabel);
m_varsBatDisplayLabel->setTextInteractionFlags(Qt::TextBrowserInteraction);
m_mainLayout->addRow(tr("Initialization:"), m_varsBatDisplayLabel);
addErrorLabel();
setFromToolChain();
}
void MsvcToolChainConfigWidget::setFromToolChain()
void MsvcBasedToolChainConfigWidget::setFromMsvcToolChain()
{
MsvcToolChain *tc = static_cast<MsvcToolChain *>(toolChain());
const MsvcToolChain *tc = static_cast<const MsvcToolChain *>(toolChain());
QTC_ASSERT(tc, return);
m_nameDisplayLabel->setText(tc->displayName());
QString varsBatDisplay = QDir::toNativeSeparators(tc->varsBat());
if (!tc->varsBatArg().isEmpty()) {
varsBatDisplay += QLatin1Char(' ');
......@@ -471,6 +476,118 @@ void MsvcToolChainConfigWidget::setFromToolChain()
m_varsBatDisplayLabel->setText(varsBatDisplay);
}
// --------------------------------------------------------------------------
// MsvcToolChainConfigWidget
// --------------------------------------------------------------------------
MsvcToolChainConfigWidget::MsvcToolChainConfigWidget(ToolChain *tc) :
MsvcBasedToolChainConfigWidget(tc)
{
addErrorLabel();
setFromMsvcToolChain();
}
// --------------------------------------------------------------------------
// ClangClToolChainConfigWidget
// --------------------------------------------------------------------------
ClangClToolChainConfigWidget::ClangClToolChainConfigWidget(ToolChain *tc)
: MsvcBasedToolChainConfigWidget(tc)
, m_llvmDirLabel(new QLabel(this))
{
m_llvmDirLabel->setTextInteractionFlags(Qt::TextBrowserInteraction);
m_mainLayout->addRow(tr("LLVM:"), m_llvmDirLabel);
addErrorLabel();
setFromClangClToolChain();
}
void ClangClToolChainConfigWidget::setFromClangClToolChain()
{
setFromMsvcToolChain();
const ClangClToolChain *tc = static_cast<const ClangClToolChain *>(toolChain());
QTC_ASSERT(tc, return);
m_llvmDirLabel->setText(QDir::toNativeSeparators(tc-> llvmDir()));
}
// --------------------------------------------------------------------------
// ClangClToolChain, piggy-backing on MSVC2015 and providing the compiler
// clang-cl.exe as a [to some extent] compatible drop-in replacement for cl.
// --------------------------------------------------------------------------
static const char clangClBinary[] = "clang-cl.exe";
ClangClToolChain::ClangClToolChain(const QString &name, const QString &llvmDir,
const Abi &abi,
const QString &varsBat, const QString &varsBatArg,
Detection d)
: MsvcToolChain(Constants::CLANG_CL_TOOLCHAIN_TYPEID, name, abi, varsBat, varsBatArg, d)
, m_llvmDir(llvmDir)
, m_compiler(Utils::FileName::fromString(m_llvmDir + QStringLiteral("/bin/") + QLatin1String(clangClBinary)))
{
}
ClangClToolChain::ClangClToolChain()
: MsvcToolChain(Constants::CLANG_CL_TOOLCHAIN_TYPEID)
{
}
bool ClangClToolChain::isValid() const
{
return MsvcToolChain::isValid() && m_compiler.exists();
}
void ClangClToolChain::addToEnvironment(Utils::Environment &env) const
{
MsvcToolChain::addToEnvironment(env);
env.prependOrSetPath(m_llvmDir + QStringLiteral("/bin"));
}
QString ClangClToolChain::typeDisplayName() const
{
return QCoreApplication::translate("ProjectExplorer::ClangToolChainFactory", "Clang");
}
QList<Utils::FileName> ClangClToolChain::suggestedMkspecList() const
{
const QString mkspec = QLatin1String("win32-clang-") + Abi::toString(targetAbi().osFlavor());
return QList<Utils::FileName>{Utils::FileName::fromString(mkspec)};
}
IOutputParser *ClangClToolChain::outputParser() const
{
return new ClangClParser;
}
ToolChain *ClangClToolChain::clone() const
{
return new ClangClToolChain(*this);
}
static inline QString llvmDirKey() { return QStringLiteral("ProjectExplorer.ClangClToolChain.LlvmDir"); }
QVariantMap ClangClToolChain::toMap() const
{
QVariantMap result = MsvcToolChain::toMap();
result.insert(llvmDirKey(), m_llvmDir);
return result;
}
bool ClangClToolChain::fromMap(const QVariantMap &data)
{
if (!MsvcToolChain::fromMap(data))
return false;
const QString llvmDir = data.value(llvmDirKey()).toString();
if (llvmDir.isEmpty())
return false;
m_llvmDir = llvmDir;
return true;
}
ToolChainConfigWidget *ClangClToolChain::configurationWidget()
{
return new ClangClToolChainConfigWidget(this);
}
// --------------------------------------------------------------------------
// MsvcToolChainFactory
// --------------------------------------------------------------------------
......@@ -579,6 +696,38 @@ static void detectCppBuildTools(QList<ToolChain *> *list)
}
}
// Detect Clang-cl on top of MSVC2015.
static void detectClangClToolChain(QList<ToolChain *> *list)
{
#ifdef Q_OS_WIN64
const char registryNode[] = "HKEY_LOCAL_MACHINE\\SOFTWARE\\WOW6432Node\\LLVM\\LLVM";
#else
const char registryNode[] = "HKEY_LOCAL_MACHINE\\SOFTWARE\\LLVM\\LLVM";
#endif
const QSettings registry(QLatin1String(registryNode), QSettings::NativeFormat);
if (registry.status() != QSettings::NoError)
return;
const QString path = QDir::cleanPath(registry.value(QStringLiteral(".")).toString());
if (path.isEmpty())
return;
const unsigned char wordWidth = Utils::is64BitWindowsBinary(path + QStringLiteral("/bin/") + QLatin1String(clangClBinary))
? 64 : 32;
const auto it = std::find_if(list->cbegin(), list->cend(),
[wordWidth] (ToolChain *tc) { const Abi abi = tc->targetAbi();
return abi.osFlavor() == Abi::WindowsMsvc2015Flavor
&& wordWidth == abi.wordWidth();} );
if (it == list->cend())
qWarning("Unable to find a suitable MSVC version for \"%s\".", qPrintable(QDir::toNativeSeparators(path)));
const MsvcToolChain *msvcToolChain = static_cast<MsvcToolChain *>(*it);
const QString name = QStringLiteral("LLVM ") + QString::number(wordWidth)
+ QStringLiteral("bit based on MSVC2015");
list->append(new ClangClToolChain(name, path, msvcToolChain->targetAbi(),
msvcToolChain->varsBat(), msvcToolChain->varsBatArg(),
ToolChain::AutoDetection));
}
QList<ToolChain *> MsvcToolChainFactory::autoDetect(const QList<ToolChain *> &alreadyKnown)
{
QList<ToolChain *> results;
......@@ -671,6 +820,8 @@ QList<ToolChain *> MsvcToolChainFactory::autoDetect(const QList<ToolChain *> &al
detectCppBuildTools(&results);
detectClangClToolChain(&results);
return results;
}
......@@ -684,7 +835,26 @@ bool MsvcToolChain::operator ==(const ToolChain &other) const
bool MsvcToolChainFactory::canRestore(const QVariantMap &data)
{
return typeIdFromMap(data) == Constants::MSVC_TOOLCHAIN_TYPEID;
const Core::Id id = typeIdFromMap(data);
return id == Constants::MSVC_TOOLCHAIN_TYPEID || id == Constants::CLANG_CL_TOOLCHAIN_TYPEID;
}
template <class ToolChainType>
ToolChainType *readFromMap(const QVariantMap &data)
{
ToolChainType *result = new ToolChainType;
if (result->fromMap(data))
return result;
delete result;
return nullptr;
}
ToolChain *MsvcToolChainFactory::restore(const QVariantMap &data)
{
const Core::Id id = typeIdFromMap(data);
if (id == Constants::CLANG_CL_TOOLCHAIN_TYPEID)
return readFromMap<ClangClToolChain>(data);
return readFromMap<MsvcToolChain>(data);
}
} // namespace Internal
......
......@@ -52,13 +52,14 @@ public:
amd64_arm
};
MsvcToolChain(const QString &name, const Abi &abi,
const QString &varsBat, const QString &varsBatArg, Detection d = ManualDetection);
explicit MsvcToolChain(const QString &name, const Abi &abi,
const QString &varsBat, const QString &varsBatArg,
Detection d = ManualDetection);
MsvcToolChain();
bool isValid() const override;
Utils::FileNameList suggestedMkspecList() const override;
static MsvcToolChain *readFromMap(const QVariantMap &data);
QString typeDisplayName() const override;
QVariantMap toMap() const override;
......@@ -73,16 +74,46 @@ public:
bool operator == (const ToolChain &) const override;
protected:
explicit MsvcToolChain(Core::Id typeId, const QString &name, const Abi &abi,
const QString &varsBat, const QString &varsBatArg,
Detection d);
explicit MsvcToolChain(Core::Id typeId);
Utils::Environment readEnvironmentSetting(Utils::Environment& env) const override;
QByteArray msvcPredefinedMacros(const QStringList cxxflags,
const Utils::Environment &env) const override;
private:
MsvcToolChain();
QString m_varsBatArg; // Argument
};
class ClangClToolChain : public MsvcToolChain
{
public:
explicit ClangClToolChain(const QString &name, const QString &llvmDir,
const Abi &abi,
const QString &varsBat, const QString &varsBatArg,
Detection d = ManualDetection);
ClangClToolChain();
bool isValid() const override;
QString typeDisplayName() const override;
QList<Utils::FileName> suggestedMkspecList() const override;
void addToEnvironment(Utils::Environment &env) const override;
Utils::FileName compilerCommand() const override { return m_compiler; }
IOutputParser *outputParser() const override;
ToolChain *clone() const override;
QVariantMap toMap() const override;
bool fromMap(const QVariantMap &data) override;
ToolChainConfigWidget *configurationWidget() override;
QString llvmDir() const { return m_llvmDir; }
private:
QString m_llvmDir;
Utils::FileName m_compiler;
};
// --------------------------------------------------------------------------
// MsvcToolChainFactory
// --------------------------------------------------------------------------
......@@ -97,8 +128,7 @@ public:
QList<ToolChain *> autoDetect(const QList<ToolChain *> &alreadyKnown) override;
bool canRestore(const QVariantMap &data) override;
ToolChain *restore(const QVariantMap &data) override
{ return MsvcToolChain::readFromMap(data); }
ToolChain *restore(const QVariantMap &data) override;
ToolChainConfigWidget *configurationWidget(ToolChain *);
static QString vcVarsBatFor(const QString &basePath, const QString &toolchainName);
......@@ -108,26 +138,60 @@ private:
};
// --------------------------------------------------------------------------
// MsvcToolChainConfigWidget
// MsvcBasedToolChainConfigWidget
// --------------------------------------------------------------------------
class MsvcToolChainConfigWidget : public ToolChainConfigWidget
class MsvcBasedToolChainConfigWidget : public ToolChainConfigWidget
{
Q_OBJECT
public:
MsvcToolChainConfigWidget(ToolChain *);