diff options
author | Ivan Komissarov <ABBAPOH@gmail.com> | 2020-02-02 07:06:57 +0100 |
---|---|---|
committer | Ivan Komissarov <ABBAPOH@gmail.com> | 2020-02-14 18:19:21 +0000 |
commit | 5adf0d5e8928c1d195d0725195fda86c21e88598 (patch) | |
tree | defac862563935ec37ecd556a5e00e045fe6d1ca | |
parent | bc01b6cc3dc1ee77cfcc3438d8e0d8985016cb65 (diff) |
Autotedect MSVC compiler by using a probe
This allows to build projects without calling "qbs setup-toolchains"
first by simply calling "qbs build".
Change-Id: Iba4af8bf77d0ee5d209564ea371328d3c6cf2aa2
Reviewed-by: Christian Kandeler <christian.kandeler@qt.io>
-rw-r--r-- | share/qbs/imports/qbs/Probes/ClBinaryProbe.qbs | 71 | ||||
-rw-r--r-- | share/qbs/modules/cpp/windows-msvc.qbs | 3 | ||||
-rw-r--r-- | src/app/qbs-setup-toolchains/clangclprobe.cpp | 5 | ||||
-rw-r--r-- | src/app/qbs-setup-toolchains/msvcprobe.cpp | 293 | ||||
-rw-r--r-- | src/app/qbs-setup-toolchains/msvcprobe.h | 10 | ||||
-rw-r--r-- | src/lib/corelib/jsextensions/utilitiesextension.cpp | 40 | ||||
-rw-r--r-- | src/lib/corelib/tools/msvcinfo.cpp | 355 | ||||
-rw-r--r-- | src/lib/corelib/tools/msvcinfo.h | 26 | ||||
-rw-r--r-- | tests/auto/blackbox/testdata/empty-profile/empty-profile.qbs | 3 | ||||
-rw-r--r-- | tests/auto/blackbox/testdata/empty-profile/main.cpp | 1 | ||||
-rw-r--r-- | tests/auto/blackbox/tst_blackbox.cpp | 11 | ||||
-rw-r--r-- | tests/auto/blackbox/tst_blackbox.h | 1 |
12 files changed, 516 insertions, 303 deletions
diff --git a/share/qbs/imports/qbs/Probes/ClBinaryProbe.qbs b/share/qbs/imports/qbs/Probes/ClBinaryProbe.qbs new file mode 100644 index 000000000..bcaa9d1f7 --- /dev/null +++ b/share/qbs/imports/qbs/Probes/ClBinaryProbe.qbs @@ -0,0 +1,71 @@ +/**************************************************************************** +** +** Copyright (C) 2020 Ivan Komissarov (abbapoh@gmail.com) +** Contact: http://www.qt.io/licensing +** +** This file is part of Qbs. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms and +** conditions see http://www.qt.io/terms-conditions. For further information +** use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +import qbs.FileInfo +import qbs.ModUtils +import qbs.Utilities +import "path-probe.js" as PathProbeConfigure + +BinaryProbe { + // input + property string preferredArchitecture; + + configure: { + var _selectors; + var results = PathProbeConfigure.configure(_selectors, names, nameSuffixes, nameFilter, + candidateFilter, searchPaths, pathSuffixes, + platformSearchPaths, environmentPaths, + platformEnvironmentPaths, pathListSeparator); + if (!results.found) { + var msvcs = Utilities.installedMSVCs(preferredArchitecture); + if (msvcs.length >= 1) { + var result = {}; + result.fileName = "cl.exe"; + result.path = msvcs[0].binPath; + result.filePath = FileInfo.joinPaths(path, fileName); + result.candidatePaths = result.filePath; + results.found = true; + results.files = [result]; + } + } + + found = results.found; + allResults = results.files; + + if (allResults.length === 1) { + var result = allResults[0]; + candidatePaths = result.candidatePaths; + path = result.path; + filePath = result.filePath; + fileName = result.fileName; + } + + } +} diff --git a/share/qbs/modules/cpp/windows-msvc.qbs b/share/qbs/modules/cpp/windows-msvc.qbs index 59032d28a..d5b5baf92 100644 --- a/share/qbs/modules/cpp/windows-msvc.qbs +++ b/share/qbs/modules/cpp/windows-msvc.qbs @@ -38,8 +38,9 @@ MsvcBaseModule { qbs.toolchain && qbs.toolchain.contains('msvc') priority: 50 - Probes.BinaryProbe { + Probes.ClBinaryProbe { id: compilerPathProbe + preferredArchitecture: qbs.architecture condition: !toolchainInstallPath && !_skipAllChecks names: ["cl"] } diff --git a/src/app/qbs-setup-toolchains/clangclprobe.cpp b/src/app/qbs-setup-toolchains/clangclprobe.cpp index 07e45d8d4..ee445a2fb 100644 --- a/src/app/qbs-setup-toolchains/clangclprobe.cpp +++ b/src/app/qbs-setup-toolchains/clangclprobe.cpp @@ -45,6 +45,7 @@ #include <logging/translator.h> #include <tools/hostosinfo.h> +#include <tools/msvcinfo.h> #include <tools/profile.h> #include <tools/qttools.h> #include <tools/settings.h> @@ -55,6 +56,8 @@ using qbs::Settings; using qbs::Profile; using qbs::Internal::HostOsInfo; +using qbs::Internal::MSVC; +using qbs::Internal::MSVCInstallInfo; using qbs::Internal::Tr; @@ -87,7 +90,7 @@ Profile createProfileHelper( std::vector<MSVCInstallInfo> compatibleMsvcs() { - auto msvcs = installedMSVCs(); + auto msvcs = MSVCInstallInfo::installedMSVCs(ConsoleLogger::instance()); auto filter = [](const MSVCInstallInfo &info) { const auto versions = info.version.split(QLatin1Char('.')); diff --git a/src/app/qbs-setup-toolchains/msvcprobe.cpp b/src/app/qbs-setup-toolchains/msvcprobe.cpp index c6061c2fa..8cf1d4de1 100644 --- a/src/app/qbs-setup-toolchains/msvcprobe.cpp +++ b/src/app/qbs-setup-toolchains/msvcprobe.cpp @@ -50,15 +50,10 @@ #include <tools/qttools.h> #include <tools/settings.h> #include <tools/version.h> -#include <tools/visualstudioversioninfo.h> #include <tools/vsenvironmentdetector.h> #include <QtCore/qdir.h> #include <QtCore/qfileinfo.h> -#include <QtCore/qjsonarray.h> -#include <QtCore/qjsondocument.h> -#include <QtCore/qjsonobject.h> -#include <QtCore/qprocess.h> #include <QtCore/qsettings.h> #include <QtCore/qstringlist.h> @@ -99,74 +94,6 @@ static void addMSVCPlatform(Settings *settings, std::vector<Profile> &profiles, profiles.push_back(p); } -static QString formatVswhereOutput(const QString &out, const QString &err) -{ - QString ret; - if (!out.isEmpty()) { - ret.append(Tr::tr("stdout")).append(QLatin1String(":\n")); - for (const QString &line : out.split(QLatin1Char('\n'))) - ret.append(QLatin1Char('\t')).append(line).append(QLatin1Char('\n')); - } - if (!err.isEmpty()) { - ret.append(Tr::tr("stderr")).append(QLatin1String(":\n")); - for (const QString &line : err.split(QLatin1Char('\n'))) - ret.append(QLatin1Char('\t')).append(line).append(QLatin1Char('\n')); - } - return ret; -} - -struct MSVCArchInfo -{ - QString arch; - QString binPath; -}; - -static std::vector<MSVCArchInfo> findSupportedArchitectures(const MSVC &msvc) -{ - std::vector<MSVCArchInfo> result; - auto addResult = [&result](const MSVCArchInfo &ai) { - if (QFile::exists(ai.binPath + QLatin1String("/cl.exe"))) - result.push_back(ai); - }; - if (msvc.internalVsVersion.majorVersion() < 15) { - static const QStringList knownArchitectures = QStringList() - << QStringLiteral("x86") - << QStringLiteral("amd64_x86") - << QStringLiteral("amd64") - << QStringLiteral("x86_amd64") - << QStringLiteral("ia64") - << QStringLiteral("x86_ia64") - << QStringLiteral("x86_arm") - << QStringLiteral("amd64_arm"); - for (const QString &knownArchitecture : knownArchitectures) { - MSVCArchInfo ai; - ai.arch = knownArchitecture; - ai.binPath = msvc.binPathForArchitecture(knownArchitecture); - addResult(ai); - } - } else { - QDir vcInstallDir(msvc.vcInstallPath); - const auto hostArchs = vcInstallDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot); - for (const QString &hostArch : hostArchs) { - QDir subdir = vcInstallDir; - if (!subdir.cd(hostArch)) - continue; - const QString shortHostArch = hostArch.mid(4).toLower(); - const auto archs = subdir.entryList(QDir::Dirs | QDir::NoDotAndDotDot); - for (const QString &arch : archs) { - MSVCArchInfo ai; - ai.binPath = subdir.absoluteFilePath(arch); - if (shortHostArch == arch) - ai.arch = arch; - else - ai.arch = shortHostArch + QLatin1Char('_') + arch; - addResult(ai); - } - } - } - return result; -} - static QString wow6432Key() { #ifdef Q_OS_WIN64 @@ -176,205 +103,6 @@ static QString wow6432Key() #endif } -static QString vswhereFilePath() -{ - static const std::vector<const char *> envVarCandidates{"ProgramFiles", "ProgramFiles(x86)"}; - for (const char * const envVar : envVarCandidates) { - const QString value = QDir::fromNativeSeparators(QString::fromLocal8Bit(qgetenv(envVar))); - const QString cmd = value - + QStringLiteral("/Microsoft Visual Studio/Installer/vswhere.exe"); - if (QFileInfo(cmd).exists()) - return cmd; - } - return {}; -} - -enum class ProductType { VisualStudio, BuildTools }; -static std::vector<MSVCInstallInfo> retrieveInstancesFromVSWhere(ProductType productType) -{ - std::vector<MSVCInstallInfo> result; - const QString cmd = vswhereFilePath(); - if (cmd.isEmpty()) - return result; - QProcess vsWhere; - QStringList args = productType == ProductType::VisualStudio - ? QStringList({QStringLiteral("-all"), QStringLiteral("-legacy"), - QStringLiteral("-prerelease")}) - : QStringList({QStringLiteral("-products"), - QStringLiteral("Microsoft.VisualStudio.Product.BuildTools")}); - args << QStringLiteral("-format") << QStringLiteral("json") << QStringLiteral("-utf8"); - vsWhere.start(cmd, args); - if (!vsWhere.waitForStarted(-1)) - return result; - if (!vsWhere.waitForFinished(-1)) { - qbsWarning() << Tr::tr("The vswhere tool failed to run").append(QLatin1String(": ")) - .append(vsWhere.errorString()); - return result; - } - if (vsWhere.exitCode() != 0) { - const QString stdOut = QString::fromLocal8Bit(vsWhere.readAllStandardOutput()); - const QString stdErr = QString::fromLocal8Bit(vsWhere.readAllStandardError()); - qbsWarning() << Tr::tr("The vswhere tool failed to run").append(QLatin1String(".\n")) - .append(formatVswhereOutput(stdOut, stdErr)); - return result; - } - QJsonParseError parseError; - QJsonDocument jsonOutput = QJsonDocument::fromJson(vsWhere.readAllStandardOutput(), - &parseError); - if (parseError.error != QJsonParseError::NoError) { - qbsWarning() << Tr::tr("The vswhere tool produced invalid JSON output: %1") - .arg(parseError.errorString()); - return result; - } - const auto jsonArray = jsonOutput.array(); - for (const QJsonValue &v : jsonArray) { - const QJsonObject o = v.toObject(); - MSVCInstallInfo info; - info.version = o.value(QStringLiteral("installationVersion")).toString(); - if (productType == ProductType::BuildTools) { - // For build tools, the version is e.g. "15.8.28010.2036", rather than "15.0". - const int dotIndex = info.version.indexOf(QLatin1Char('.')); - if (dotIndex != -1) - info.version = info.version.left(dotIndex); - } - info.installDir = o.value(QStringLiteral("installationPath")).toString(); - if (!info.version.isEmpty() && !info.installDir.isEmpty()) - result.push_back(info); - } - return result; -} - -static std::vector<MSVCInstallInfo> installedMSVCsFromVsWhere() -{ - const std::vector<MSVCInstallInfo> vsInstallations - = retrieveInstancesFromVSWhere(ProductType::VisualStudio); - const std::vector<MSVCInstallInfo> buildToolInstallations - = retrieveInstancesFromVSWhere(ProductType::BuildTools); - std::vector<MSVCInstallInfo> all; - std::copy(vsInstallations.begin(), vsInstallations.end(), std::back_inserter(all)); - std::copy(buildToolInstallations.begin(), buildToolInstallations.end(), - std::back_inserter(all)); - return all; -} - -static std::vector<MSVCInstallInfo> installedMSVCsFromRegistry() -{ - std::vector<MSVCInstallInfo> result; - - // Detect Visual Studio - const QSettings vsRegistry( - QStringLiteral("HKEY_LOCAL_MACHINE\\SOFTWARE") + wow6432Key() - + QStringLiteral("\\Microsoft\\VisualStudio\\SxS\\VS7"), - QSettings::NativeFormat); - const auto vsNames = vsRegistry.childKeys(); - for (const QString &vsName : vsNames) { - MSVCInstallInfo entry; - entry.version = vsName; - entry.installDir = vsRegistry.value(vsName).toString(); - result.push_back(entry); - } - - // Detect Visual C++ Build Tools - QSettings vcbtRegistry( - QStringLiteral("HKEY_LOCAL_MACHINE\\SOFTWARE") + wow6432Key() - + QStringLiteral("\\Microsoft\\VisualCppBuildTools"), - QSettings::NativeFormat); - const QStringList &vcbtRegistryChildGroups = vcbtRegistry.childGroups(); - for (const QString &childGroup : vcbtRegistryChildGroups) { - vcbtRegistry.beginGroup(childGroup); - bool ok; - int installed = vcbtRegistry.value(QStringLiteral("Installed")).toInt(&ok); - if (ok && installed) { - MSVCInstallInfo entry; - entry.version = childGroup; - const QSettings vsRegistry( - QStringLiteral("HKEY_LOCAL_MACHINE\\SOFTWARE") + wow6432Key() - + QStringLiteral("\\Microsoft\\VisualStudio\\") + childGroup - + QStringLiteral("\\Setup\\VC"), - QSettings::NativeFormat); - entry.installDir = vsRegistry.value(QStringLiteral("ProductDir")).toString(); - result.push_back(entry); - } - vcbtRegistry.endGroup(); - } - - return result; -} - -QString MSVCInstallInfo::findVcvarsallBat() const -{ - static const auto vcvarsall2017 = QStringLiteral("VC/Auxiliary/Build/vcvarsall.bat"); - // 2015, 2013 and 2012 - static const auto vcvarsallOld = QStringLiteral("VC/vcvarsall.bat"); - QDir dir(installDir); - if (dir.exists(vcvarsall2017)) - return dir.absoluteFilePath(vcvarsall2017); - if (dir.exists(vcvarsallOld)) - return dir.absoluteFilePath(vcvarsallOld); - return {}; -} - -std::vector<MSVCInstallInfo> installedMSVCs() -{ - const auto installInfos = installedMSVCsFromVsWhere(); - if (installInfos.empty()) - return installedMSVCsFromRegistry(); - return installInfos; -} - -static std::vector<MSVC> installedCompilers() -{ - std::vector<MSVC> msvcs; - std::vector<MSVCInstallInfo> installInfos = installedMSVCsFromVsWhere(); - if (installInfos.empty()) - installInfos = installedMSVCsFromRegistry(); - for (const MSVCInstallInfo &installInfo : installInfos) { - MSVC msvc; - msvc.internalVsVersion = Version::fromString(installInfo.version, true); - if (!msvc.internalVsVersion.isValid()) - continue; - - QDir vsInstallDir(installInfo.installDir); - msvc.vsInstallPath = vsInstallDir.absolutePath(); - if (vsInstallDir.dirName() != QStringLiteral("VC") - && !vsInstallDir.cd(QStringLiteral("VC"))) { - continue; - } - - msvc.version = QString::number(Internal::VisualStudioVersionInfo( - msvc.internalVsVersion).marketingVersion()); - if (msvc.version.isEmpty()) { - qbsWarning() << Tr::tr("Unknown MSVC version %1 found.").arg(installInfo.version); - continue; - } - - if (msvc.internalVsVersion.majorVersion() < 15) { - QDir vcInstallDir = vsInstallDir; - if (!vcInstallDir.cd(QStringLiteral("bin"))) - continue; - msvc.vcInstallPath = vcInstallDir.absolutePath(); - msvcs.push_back(msvc); - } else { - QDir vcInstallDir = vsInstallDir; - vcInstallDir.cd(QStringLiteral("Tools/MSVC")); - const auto vcVersionStrs = vcInstallDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot); - for (const QString &vcVersionStr : vcVersionStrs) { - const Version vcVersion = Version::fromString(vcVersionStr); - if (!vcVersion.isValid()) - continue; - QDir specificVcInstallDir = vcInstallDir; - if (!specificVcInstallDir.cd(vcVersionStr) - || !specificVcInstallDir.cd(QStringLiteral("bin"))) { - continue; - } - msvc.vcInstallPath = specificVcInstallDir.absolutePath(); - msvcs.push_back(msvc); - } - } - } - return msvcs; -} - void msvcProbe(Settings *settings, std::vector<Profile> &profiles) { qbsInfo() << Tr::tr("Detecting MSVC toolchains..."); @@ -400,7 +128,7 @@ void msvcProbe(Settings *settings, std::vector<Profile> &profiles) sdk.vcInstallPath.chop(1); if (sdk.isDefault) defaultWinSDK = sdk; - const auto ais = findSupportedArchitectures(sdk); + const auto ais = MSVC::findSupportedArchitectures(sdk); for (const MSVCArchInfo &ai : ais) { WinSDK specificSDK = sdk; specificSDK.architecture = ai.arch; @@ -418,24 +146,7 @@ void msvcProbe(Settings *settings, std::vector<Profile> &profiles) } // 2) Installed MSVCs - std::vector<MSVC> msvcs; - const auto instMsvcs = installedCompilers(); - for (const MSVC &msvc : instMsvcs) { - if (msvc.internalVsVersion.majorVersion() < 15) { - // Check existence of various install scripts - const QString vcvars32bat = msvc.vcInstallPath + QLatin1String("/vcvars32.bat"); - if (!QFileInfo(vcvars32bat).isFile()) - continue; - } - - const auto ais = findSupportedArchitectures(msvc); - for (const MSVCArchInfo &ai : ais) { - MSVC specificMSVC = msvc; - specificMSVC.architecture = ai.arch; - specificMSVC.binPath = ai.binPath; - msvcs.push_back(specificMSVC); - } - } + std::vector<MSVC> msvcs = MSVC::installedCompilers(ConsoleLogger::instance()); for (const MSVC &msvc : qAsConst(msvcs)) { qbsInfo() << Tr::tr(" MSVC %1 (%2) detected in\n" diff --git a/src/app/qbs-setup-toolchains/msvcprobe.h b/src/app/qbs-setup-toolchains/msvcprobe.h index 3120a96f2..981bbc561 100644 --- a/src/app/qbs-setup-toolchains/msvcprobe.h +++ b/src/app/qbs-setup-toolchains/msvcprobe.h @@ -53,16 +53,6 @@ class Profile; class Settings; } -struct MSVCInstallInfo -{ - QString version; - QString installDir; - - QString findVcvarsallBat() const; -}; - -std::vector<MSVCInstallInfo> installedMSVCs(); - void createMsvcProfile(const QFileInfo &compiler, qbs::Settings *settings, const QString &profileName); diff --git a/src/lib/corelib/jsextensions/utilitiesextension.cpp b/src/lib/corelib/jsextensions/utilitiesextension.cpp index 80ff6539e..2d29cb7c5 100644 --- a/src/lib/corelib/jsextensions/utilitiesextension.cpp +++ b/src/lib/corelib/jsextensions/utilitiesextension.cpp @@ -110,6 +110,7 @@ public: static QScriptValue js_signingIdentities(QScriptContext *context, QScriptEngine *engine); static QScriptValue js_msvcCompilerInfo(QScriptContext *context, QScriptEngine *engine); static QScriptValue js_clangClCompilerInfo(QScriptContext *context, QScriptEngine *engine); + static QScriptValue js_installedMSVCs(QScriptContext *context, QScriptEngine *engine); static QScriptValue js_versionCompare(QScriptContext *context, QScriptEngine *engine); @@ -551,6 +552,43 @@ QScriptValue UtilitiesExtension::js_clangClCompilerInfo(QScriptContext *context, #endif } +QScriptValue UtilitiesExtension::js_installedMSVCs(QScriptContext *context, QScriptEngine *engine) +{ +#ifndef Q_OS_WIN + Q_UNUSED(engine); + return context->throwError(QScriptContext::UnknownError, + QStringLiteral("installedMSVCs is not available on this platform")); +#else + if (Q_UNLIKELY(context->argumentCount() != 1)) { + return context->throwError(QScriptContext::SyntaxError, + QStringLiteral("installedMSVCs expects 1 arguments")); + } + + const auto value0 = context->argument(0); + const auto hostArch = QString::fromStdString(HostOsInfo::hostOSArchitecture()); + const auto preferredArch = !value0.isNull() && !value0.isUndefined() + ? value0.toString() + : hostArch; + + class LogSink : public ILogSink { + void doPrintMessage(LoggerLevel, const QString &, const QString &) override { } + } dummySink; + Logger dummyLogger(&dummySink); + auto msvcs = MSVC::installedCompilers(dummyLogger); + + const auto predicate = [&preferredArch, &hostArch](const MSVC &msvc) + { + auto archPair = MSVC::getHostTargetArchPair(msvc.architecture); + return archPair.first != hostArch || preferredArch != archPair.second; + }; + msvcs.erase(std::remove_if(msvcs.begin(), msvcs.end(), predicate), msvcs.end()); + QVariantList result; + for (const auto &msvc: msvcs) + result.append(msvc.toVariantMap()); + return engine->toScriptValue(result); +#endif +} + QScriptValue UtilitiesExtension::js_versionCompare(QScriptContext *context, QScriptEngine *engine) { if (context->argumentCount() == 2) { @@ -851,6 +889,8 @@ void initializeJsExtensionUtilities(QScriptValue extensionObject) engine->newFunction(UtilitiesExtension::js_msvcCompilerInfo, 1)); environmentObj.setProperty(QStringLiteral("clangClCompilerInfo"), engine->newFunction(UtilitiesExtension::js_clangClCompilerInfo, 1)); + environmentObj.setProperty(QStringLiteral("installedMSVCs"), + engine->newFunction(UtilitiesExtension::js_installedMSVCs, 1)); environmentObj.setProperty(QStringLiteral("versionCompare"), engine->newFunction(UtilitiesExtension::js_versionCompare, 2)); environmentObj.setProperty(QStringLiteral("qmlTypeInfo"), diff --git a/src/lib/corelib/tools/msvcinfo.cpp b/src/lib/corelib/tools/msvcinfo.cpp index 9ece7b8be..462f921a4 100644 --- a/src/lib/corelib/tools/msvcinfo.cpp +++ b/src/lib/corelib/tools/msvcinfo.cpp @@ -38,14 +38,20 @@ ****************************************************************************/ #include "msvcinfo.h" +#include "visualstudioversioninfo.h" +#include <logging/logger.h> #include <tools/error.h> #include <tools/profile.h> #include <tools/stringconstants.h> #include <QtCore/qbytearray.h> #include <QtCore/qdir.h> +#include <QtCore/qjsonarray.h> +#include <QtCore/qjsondocument.h> +#include <QtCore/qjsonobject.h> #include <QtCore/qprocess.h> +#include <QtCore/qsettings.h> #include <QtCore/qstringlist.h> #include <QtCore/qtemporaryfile.h> @@ -270,6 +276,216 @@ static QVariantMap getClangClDefines( #endif } +static QString formatVswhereOutput(const QString &out, const QString &err) +{ + QString ret; + if (!out.isEmpty()) { + ret.append(Tr::tr("stdout")).append(QLatin1String(":\n")); + for (const QString &line : out.split(QLatin1Char('\n'))) + ret.append(QLatin1Char('\t')).append(line).append(QLatin1Char('\n')); + } + if (!err.isEmpty()) { + ret.append(Tr::tr("stderr")).append(QLatin1String(":\n")); + for (const QString &line : err.split(QLatin1Char('\n'))) + ret.append(QLatin1Char('\t')).append(line).append(QLatin1Char('\n')); + } + return ret; +} + +static QString wow6432Key() +{ +#ifdef Q_OS_WIN64 + return QStringLiteral("\\Wow6432Node"); +#else + return {}; +#endif +} + +static QString vswhereFilePath() +{ + static const std::vector<const char *> envVarCandidates{"ProgramFiles", "ProgramFiles(x86)"}; + for (const char * const envVar : envVarCandidates) { + const QString value = QDir::fromNativeSeparators(QString::fromLocal8Bit(qgetenv(envVar))); + const QString cmd = value + + QStringLiteral("/Microsoft Visual Studio/Installer/vswhere.exe"); + if (QFileInfo(cmd).exists()) + return cmd; + } + return {}; +} + +enum class ProductType { VisualStudio, BuildTools }; +static std::vector<MSVCInstallInfo> retrieveInstancesFromVSWhere( + ProductType productType, Logger &logger) +{ + std::vector<MSVCInstallInfo> result; + const QString cmd = vswhereFilePath(); + if (cmd.isEmpty()) + return result; + QProcess vsWhere; + QStringList args = productType == ProductType::VisualStudio + ? QStringList({QStringLiteral("-all"), QStringLiteral("-legacy"), + QStringLiteral("-prerelease")}) + : QStringList({QStringLiteral("-products"), + QStringLiteral("Microsoft.VisualStudio.Product.BuildTools")}); + args << QStringLiteral("-format") << QStringLiteral("json") << QStringLiteral("-utf8"); + vsWhere.start(cmd, args); + if (!vsWhere.waitForStarted(-1)) + return result; + if (!vsWhere.waitForFinished(-1)) { + logger.qbsWarning() << Tr::tr("The vswhere tool failed to run").append(QLatin1String(": ")) + .append(vsWhere.errorString()); + return result; + } + if (vsWhere.exitCode() != 0) { + const QString stdOut = QString::fromLocal8Bit(vsWhere.readAllStandardOutput()); + const QString stdErr = QString::fromLocal8Bit(vsWhere.readAllStandardError()); + logger.qbsWarning() << Tr::tr("The vswhere tool failed to run").append(QLatin1String(".\n")) + .append(formatVswhereOutput(stdOut, stdErr)); + return result; + } + QJsonParseError parseError; + QJsonDocument jsonOutput = QJsonDocument::fromJson(vsWhere.readAllStandardOutput(), + &parseError); + if (parseError.error != QJsonParseError::NoError) { + logger.qbsWarning() << Tr::tr("The vswhere tool produced invalid JSON output: %1") + .arg(parseError.errorString()); + return result; + } + const auto jsonArray = jsonOutput.array(); + for (const QJsonValue &v : jsonArray) { + const QJsonObject o = v.toObject(); + MSVCInstallInfo info; + info.version = o.value(QStringLiteral("installationVersion")).toString(); + if (productType == ProductType::BuildTools) { + // For build tools, the version is e.g. "15.8.28010.2036", rather than "15.0". + const int dotIndex = info.version.indexOf(QLatin1Char('.')); + if (dotIndex != -1) + info.version = info.version.left(dotIndex); + } + info.installDir = o.value(QStringLiteral("installationPath")).toString(); + if (!info.version.isEmpty() && !info.installDir.isEmpty()) + result.push_back(info); + } + return result; +} + +static std::vector<MSVCInstallInfo> installedMSVCsFromVsWhere(Logger &logger) +{ + const std::vector<MSVCInstallInfo> vsInstallations + = retrieveInstancesFromVSWhere(ProductType::VisualStudio, logger); + const std::vector<MSVCInstallInfo> buildToolInstallations + = retrieveInstancesFromVSWhere(ProductType::BuildTools, logger); + std::vector<MSVCInstallInfo> all; + std::copy(vsInstallations.begin(), vsInstallations.end(), std::back_inserter(all)); + std::copy(buildToolInstallations.begin(), buildToolInstallations.end(), + std::back_inserter(all)); + return all; +} + +static std::vector<MSVCInstallInfo> installedMSVCsFromRegistry() +{ + std::vector<MSVCInstallInfo> result; + + // Detect Visual Studio + const QSettings vsRegistry( + QStringLiteral("HKEY_LOCAL_MACHINE\\SOFTWARE") + wow6432Key() + + QStringLiteral("\\Microsoft\\VisualStudio\\SxS\\VS7"), + QSettings::NativeFormat); + const auto vsNames = vsRegistry.childKeys(); + for (const QString &vsName : vsNames) { + MSVCInstallInfo entry; + entry.version = vsName; + entry.installDir = vsRegistry.value(vsName).toString(); + result.push_back(entry); + } + + // Detect Visual C++ Build Tools + QSettings vcbtRegistry( + QStringLiteral("HKEY_LOCAL_MACHINE\\SOFTWARE") + wow6432Key() + + QStringLiteral("\\Microsoft\\VisualCppBuildTools"), + QSettings::NativeFormat); + const QStringList &vcbtRegistryChildGroups = vcbtRegistry.childGroups(); + for (const QString &childGroup : vcbtRegistryChildGroups) { + vcbtRegistry.beginGroup(childGroup); + bool ok; + int installed = vcbtRegistry.value(QStringLiteral("Installed")).toInt(&ok); + if (ok && installed) { + MSVCInstallInfo entry; + entry.version = childGroup; + const QSettings vsRegistry( + QStringLiteral("HKEY_LOCAL_MACHINE\\SOFTWARE") + wow6432Key() + + QStringLiteral("\\Microsoft\\VisualStudio\\") + childGroup + + QStringLiteral("\\Setup\\VC"), + QSettings::NativeFormat); + entry.installDir = vsRegistry.value(QStringLiteral("ProductDir")).toString(); + result.push_back(entry); + } + vcbtRegistry.endGroup(); + } + + return result; +} + +/* + Returns the list of compilers present in all MSVC installations + (Visual Studios or Build Tools) without the architecture, e.g. + [VC\Tools\MSVC\14.16.27023, VC\Tools\MSVC\14.14.26428, ...] +*/ +static std::vector<MSVC> installedCompilersHelper(Logger &logger) +{ + std::vector<MSVC> msvcs; + std::vector<MSVCInstallInfo> installInfos = installedMSVCsFromVsWhere(logger); + if (installInfos.empty()) + installInfos = installedMSVCsFromRegistry(); + for (const MSVCInstallInfo &installInfo : installInfos) { + MSVC msvc; + msvc.internalVsVersion = Version::fromString(installInfo.version, true); + if (!msvc.internalVsVersion.isValid()) + continue; + + QDir vsInstallDir(installInfo.installDir); + msvc.vsInstallPath = vsInstallDir.absolutePath(); + if (vsInstallDir.dirName() != QStringLiteral("VC") + && !vsInstallDir.cd(QStringLiteral("VC"))) { + continue; + } + + msvc.version = QString::number(Internal::VisualStudioVersionInfo( + msvc.internalVsVersion).marketingVersion()); + if (msvc.version.isEmpty()) { + logger.qbsWarning() + << Tr::tr("Unknown MSVC version %1 found.").arg(installInfo.version); + continue; + } + + if (msvc.internalVsVersion.majorVersion() < 15) { + QDir vcInstallDir = vsInstallDir; + if (!vcInstallDir.cd(QStringLiteral("bin"))) + continue; + msvc.vcInstallPath = vcInstallDir.absolutePath(); + msvcs.push_back(msvc); + } else { + QDir vcInstallDir = vsInstallDir; + vcInstallDir.cd(QStringLiteral("Tools/MSVC")); + const auto vcVersionStrs = vcInstallDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot); + for (const QString &vcVersionStr : vcVersionStrs) { + const Version vcVersion = Version::fromString(vcVersionStr); + if (!vcVersion.isValid()) + continue; + QDir specificVcInstallDir = vcInstallDir; + if (!specificVcInstallDir.cd(vcVersionStr) + || !specificVcInstallDir.cd(QStringLiteral("bin"))) { + continue; + } + msvc.vcInstallPath = specificVcInstallDir.absolutePath(); + msvcs.push_back(msvc); + } + } + } + return msvcs; +} + void MSVC::init() { determineCompilerVersion(); @@ -288,6 +504,28 @@ QString MSVC::architectureFromClPath(const QString &clPath) return parentDirName; } +QString MSVC::canonicalArchitecture(const QString &arch) +{ + if (arch == QLatin1String("x64") || arch == QLatin1String("amd64")) + return QStringLiteral("x86_64"); + return arch; +} + +std::pair<QString, QString> MSVC::getHostTargetArchPair(const QString &arch) +{ + QString hostArch; + QString targetArch; + const int index = arch.indexOf(QLatin1Char('_')); + if (index != -1) { + hostArch = arch.mid(0, index); + targetArch = arch.mid(index); + } else { + hostArch = arch; + targetArch = arch; + } + return {canonicalArchitecture(hostArch), canonicalArchitecture(targetArch)}; +} + QString MSVC::binPathForArchitecture(const QString &arch) const { QString archSubDir; @@ -313,6 +551,102 @@ QVariantMap MSVC::compilerDefines(const QString &compilerFilePath, return getMsvcDefines(compilerFilePath, environment, language); } +std::vector<MSVCArchInfo> MSVC::findSupportedArchitectures(const MSVC &msvc) +{ + std::vector<MSVCArchInfo> result; + auto addResult = [&result](const MSVCArchInfo &ai) { + if (QFile::exists(ai.binPath + QLatin1String("/cl.exe"))) + result.push_back(ai); + }; + if (msvc.internalVsVersion.majorVersion() < 15) { + static const QStringList knownArchitectures = QStringList() + << QStringLiteral("x86") + << QStringLiteral("amd64_x86") + << QStringLiteral("amd64") + << QStringLiteral("x86_amd64") + << QStringLiteral("ia64") + << QStringLiteral("x86_ia64") + << QStringLiteral("x86_arm") + << QStringLiteral("amd64_arm"); + for (const QString &knownArchitecture : knownArchitectures) { + MSVCArchInfo ai; + ai.arch = knownArchitecture; + ai.binPath = msvc.binPathForArchitecture(knownArchitecture); + addResult(ai); + } + } else { + QDir vcInstallDir(msvc.vcInstallPath); + const auto hostArchs = vcInstallDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot); + for (const QString &hostArch : hostArchs) { + QDir subdir = vcInstallDir; + if (!subdir.cd(hostArch)) + continue; + const QString shortHostArch = hostArch.mid(4).toLower(); + const auto archs = subdir.entryList(QDir::Dirs | QDir::NoDotAndDotDot); + for (const QString &arch : archs) { + MSVCArchInfo ai; + ai.binPath = subdir.absoluteFilePath(arch); + if (shortHostArch == arch) + ai.arch = arch; + else + ai.arch = shortHostArch + QLatin1Char('_') + arch; + addResult(ai); + } + } + } + return result; +} + +QVariantMap MSVC::toVariantMap() const +{ + return { + {QStringLiteral("version"), version}, + {QStringLiteral("internalVsVersion"), internalVsVersion.toString()}, + {QStringLiteral("vsInstallPath"), vsInstallPath}, + {QStringLiteral("vcInstallPath"), vcInstallPath}, + {QStringLiteral("binPath"), binPath}, + {QStringLiteral("architecture"), architecture}, + }; +} + +/*! + \internal + Returns the list of all compilers present in all MSVC installations + separated by host/target arch, e.g. + [ + VC\Tools\MSVC\14.16.27023\bin\Hostx64\x64, + VC\Tools\MSVC\14.16.27023\bin\Hostx64\x86, + VC\Tools\MSVC\14.16.27023\bin\Hostx64\arm, + VC\Tools\MSVC\14.16.27023\bin\Hostx64\arm64, + VC\Tools\MSVC\14.16.27023\bin\Hostx86\x64, + ... + ] + \note that MSVC.architecture can be either "x64" or "amd64" (depending on the MSVC version) + in case of 64-bit platform (but we use the "x86_64" name...) +*/ +std::vector<MSVC> MSVC::installedCompilers(Logger &logger) +{ + std::vector<MSVC> msvcs; + const auto instMsvcs = installedCompilersHelper(logger); + for (const MSVC &msvc : instMsvcs) { + if (msvc.internalVsVersion.majorVersion() < 15) { + // Check existence of various install scripts + const QString vcvars32bat = msvc.vcInstallPath + QLatin1String("/vcvars32.bat"); + if (!QFileInfo(vcvars32bat).isFile()) + continue; + } + + const auto ais = findSupportedArchitectures(msvc); + for (const MSVCArchInfo &ai : ais) { + MSVC specificMSVC = msvc; + specificMSVC.architecture = ai.arch; + specificMSVC.binPath = ai.binPath; + msvcs.push_back(specificMSVC); + } + } + return msvcs; +} + void MSVC::determineCompilerVersion() { QString cppFilePath; @@ -340,3 +674,24 @@ void MSVC::determineCompilerVersion() compilerVersion = Version(versionStr.mid(0, 2).toInt(), versionStr.mid(2, 2).toInt(), versionStr.mid(4).toInt()); } + +QString MSVCInstallInfo::findVcvarsallBat() const +{ + static const auto vcvarsall2017 = QStringLiteral("VC/Auxiliary/Build/vcvarsall.bat"); + // 2015, 2013 and 2012 + static const auto vcvarsallOld = QStringLiteral("VC/vcvarsall.bat"); + QDir dir(installDir); + if (dir.exists(vcvarsall2017)) + return dir.absoluteFilePath(vcvarsall2017); + if (dir.exists(vcvarsallOld)) + return dir.absoluteFilePath(vcvarsallOld); + return {}; +} + +std::vector<MSVCInstallInfo> MSVCInstallInfo::installedMSVCs(Logger &logger) +{ + const auto installInfos = installedMSVCsFromVsWhere(logger); + if (installInfos.empty()) + return installedMSVCsFromRegistry(); + return installInfos; +} diff --git a/src/lib/corelib/tools/msvcinfo.h b/src/lib/corelib/tools/msvcinfo.h index 171afeb29..de4470bf0 100644 --- a/src/lib/corelib/tools/msvcinfo.h +++ b/src/lib/corelib/tools/msvcinfo.h @@ -53,6 +53,14 @@ namespace qbs { namespace Internal { +class Logger; + +struct MSVCArchInfo +{ + QString arch; + QString binPath; +}; + /** * Represents one MSVC installation for one specific target architecture. * There are potentially multiple MSVCs in one Visual Studio installation. @@ -90,11 +98,19 @@ public: QBS_EXPORT void init(); QBS_EXPORT static QString architectureFromClPath(const QString &clPath); + QBS_EXPORT static QString canonicalArchitecture(const QString &arch); + QBS_EXPORT static std::pair<QString, QString> getHostTargetArchPair(const QString &arch); QBS_EXPORT QString binPathForArchitecture(const QString &arch) const; QBS_EXPORT QString clPathForArchitecture(const QString &arch) const; QBS_EXPORT QVariantMap compilerDefines(const QString &compilerFilePath, CompilerLanguage language) const; + QBS_EXPORT static std::vector<MSVCArchInfo> findSupportedArchitectures(const MSVC &msvc); + + QBS_EXPORT QVariantMap toVariantMap() const; + + QBS_EXPORT static std::vector<MSVC> installedCompilers(Logger &logger); + private: void determineCompilerVersion(); }; @@ -110,6 +126,16 @@ public: } }; +struct QBS_EXPORT MSVCInstallInfo +{ + QString version; + QString installDir; + + QString findVcvarsallBat() const; + + static std::vector<MSVCInstallInfo> installedMSVCs(Logger &logger); +}; + } // namespace Internal } // namespace qbs diff --git a/tests/auto/blackbox/testdata/empty-profile/empty-profile.qbs b/tests/auto/blackbox/testdata/empty-profile/empty-profile.qbs new file mode 100644 index 000000000..da7536315 --- /dev/null +++ b/tests/auto/blackbox/testdata/empty-profile/empty-profile.qbs @@ -0,0 +1,3 @@ +CppApplication { + files: ["main.cpp"] +} diff --git a/tests/auto/blackbox/testdata/empty-profile/main.cpp b/tests/auto/blackbox/testdata/empty-profile/main.cpp new file mode 100644 index 000000000..76e819701 --- /dev/null +++ b/tests/auto/blackbox/testdata/empty-profile/main.cpp @@ -0,0 +1 @@ +int main() { return 0; } diff --git a/tests/auto/blackbox/tst_blackbox.cpp b/tests/auto/blackbox/tst_blackbox.cpp index 797be107f..121f60dd8 100644 --- a/tests/auto/blackbox/tst_blackbox.cpp +++ b/tests/auto/blackbox/tst_blackbox.cpp @@ -3438,6 +3438,17 @@ void TestBlackbox::dynamicRuleOutputs() QVERIFY(!QFile::exists(sourceFile2)); } +void TestBlackbox::emptyProfile() +{ + QDir::setCurrent(testDataDir + "/empty-profile"); + + QTemporaryDir tempDir; + QbsRunParameters params; + params.profile.clear(); + params.settingsDir = tempDir.path(); // should be no settings here + QCOMPARE(runQbs(params), 0); +} + void TestBlackbox::erroneousFiles_data() { QTest::addColumn<QString>("errorMessage"); diff --git a/tests/auto/blackbox/tst_blackbox.h b/tests/auto/blackbox/tst_blackbox.h index 9842ded39..2b83b14e3 100644 --- a/tests/auto/blackbox/tst_blackbox.h +++ b/tests/auto/blackbox/tst_blackbox.h @@ -95,6 +95,7 @@ private slots: void dynamicMultiplexRule(); void dynamicProject(); void dynamicRuleOutputs(); + void emptyProfile(); void enableExceptions(); void enableExceptions_data(); void enableRtti(); |