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) ...@@ -46,8 +46,12 @@ static QPair<Utils::FileName, int> parseFileName(const QString &input)
if (fileName.endsWith(QLatin1Char(')'))) { if (fileName.endsWith(QLatin1Char(')'))) {
int pos = fileName.lastIndexOf(QLatin1Char('(')); int pos = fileName.lastIndexOf(QLatin1Char('('));
if (pos >= 0) { 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; 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) { if (ok) {
fileName = fileName.left(pos); fileName = fileName.left(pos);
linenumber = n; linenumber = n;
...@@ -191,6 +195,101 @@ void MsvcParser::doFlush() ...@@ -191,6 +195,101 @@ void MsvcParser::doFlush()
emit addTask(t, m_lines, 1); 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: // Unit tests:
#ifdef WITH_TESTS #ifdef WITH_TESTS
...@@ -423,4 +522,93 @@ void ProjectExplorerPlugin::testMsvcOutputParsers() ...@@ -423,4 +522,93 @@ void ProjectExplorerPlugin::testMsvcOutputParsers()
outputLines); 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 #endif // WITH_TEST
...@@ -54,4 +54,22 @@ private: ...@@ -54,4 +54,22 @@ private:
int m_lines = 0; 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 } // namespace ProjectExplorer
...@@ -339,8 +339,15 @@ Utils::Environment MsvcToolChain::readEnvironmentSetting(Utils::Environment& env ...@@ -339,8 +339,15 @@ Utils::Environment MsvcToolChain::readEnvironmentSetting(Utils::Environment& env
MsvcToolChain::MsvcToolChain(const QString &name, const Abi &abi, MsvcToolChain::MsvcToolChain(const QString &name, const Abi &abi,
const QString &varsBat, const QString &varsBatArg, Detection d) : const QString &varsBat, const QString &varsBatArg, Detection d) :
AbstractMsvcToolChain(Constants::MSVC_TOOLCHAIN_TYPEID, d, abi, varsBat), MsvcToolChain(Constants::MSVC_TOOLCHAIN_TYPEID, name, abi, varsBat, varsBatArg, d)
m_varsBatArg(varsBatArg) {
}
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()); Q_ASSERT(!name.isEmpty());
...@@ -355,18 +362,13 @@ bool MsvcToolChain::isValid() const ...@@ -355,18 +362,13 @@ bool MsvcToolChain::isValid() const
return QFileInfo::exists(vcVarsBat); return QFileInfo::exists(vcVarsBat);
} }
MsvcToolChain::MsvcToolChain() : MsvcToolChain::MsvcToolChain(Core::Id typeId)
AbstractMsvcToolChain(Constants::MSVC_TOOLCHAIN_TYPEID, ManualDetection) : 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 QString MsvcToolChain::typeDisplayName() const
...@@ -445,24 +447,27 @@ ToolChain *MsvcToolChain::clone() 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) : MsvcBasedToolChainConfigWidget::MsvcBasedToolChainConfigWidget(ToolChain *tc)
ToolChainConfigWidget(tc), : ToolChainConfigWidget(tc)
m_varsBatDisplayLabel(new QLabel(this)) , 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_varsBatDisplayLabel->setTextInteractionFlags(Qt::TextBrowserInteraction);
m_mainLayout->addRow(tr("Initialization:"), m_varsBatDisplayLabel); 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); QTC_ASSERT(tc, return);
m_nameDisplayLabel->setText(tc->displayName());
QString varsBatDisplay = QDir::toNativeSeparators(tc->varsBat()); QString varsBatDisplay = QDir::toNativeSeparators(tc->varsBat());
if (!tc->varsBatArg().isEmpty()) { if (!tc->varsBatArg().isEmpty()) {
varsBatDisplay += QLatin1Char(' '); varsBatDisplay += QLatin1Char(' ');
...@@ -471,6 +476,118 @@ void MsvcToolChainConfigWidget::setFromToolChain() ...@@ -471,6 +476,118 @@ void MsvcToolChainConfigWidget::setFromToolChain()
m_varsBatDisplayLabel->setText(varsBatDisplay); 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 // MsvcToolChainFactory
// -------------------------------------------------------------------------- // --------------------------------------------------------------------------
...@@ -579,6 +696,38 @@ static void detectCppBuildTools(QList<ToolChain *> *list) ...@@ -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 *> MsvcToolChainFactory::autoDetect(const QList<ToolChain *> &alreadyKnown)
{ {
QList<ToolChain *> results; QList<ToolChain *> results;
...@@ -671,6 +820,8 @@ QList<ToolChain *> MsvcToolChainFactory::autoDetect(const QList<ToolChain *> &al ...@@ -671,6 +820,8 @@ QList<ToolChain *> MsvcToolChainFactory::autoDetect(const QList<ToolChain *> &al
detectCppBuildTools(&results); detectCppBuildTools(&results);
detectClangClToolChain(&results);
return results; return results;
} }
...@@ -684,7 +835,26 @@ bool MsvcToolChain::operator ==(const ToolChain &other) const ...@@ -684,7 +835,26 @@ bool MsvcToolChain::operator ==(const ToolChain &other) const
bool MsvcToolChainFactory::canRestore(const QVariantMap &data) 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 } // namespace Internal
......
...@@ -52,13 +52,14 @@ public: ...@@ -52,13 +52,14 @@ public:
amd64_arm amd64_arm
}; };
MsvcToolChain(const QString &name, const Abi &abi, explicit MsvcToolChain(const QString &name, const Abi &abi,
const QString &varsBat, const QString &varsBatArg, Detection d = ManualDetection); const QString &varsBat, const QString &varsBatArg,
Detection d = ManualDetection);
MsvcToolChain();
bool isValid() const override; bool isValid() const override;
Utils::FileNameList suggestedMkspecList() const override; Utils::FileNameList suggestedMkspecList() const override;
static MsvcToolChain *readFromMap(const QVariantMap &data);
QString typeDisplayName() const override; QString typeDisplayName() const override;
QVariantMap toMap() const override; QVariantMap toMap() const override;
...@@ -73,16 +74,46 @@ public: ...@@ -73,16 +74,46 @@ public:
bool operator == (const ToolChain &) const override; bool operator == (const ToolChain &) const override;
protected: 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);