aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorIvan Komissarov <ABBAPOH@gmail.com>2020-02-02 07:06:57 +0100
committerIvan Komissarov <ABBAPOH@gmail.com>2020-02-14 18:19:21 +0000
commit5adf0d5e8928c1d195d0725195fda86c21e88598 (patch)
treedefac862563935ec37ecd556a5e00e045fe6d1ca
parentbc01b6cc3dc1ee77cfcc3438d8e0d8985016cb65 (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.qbs71
-rw-r--r--share/qbs/modules/cpp/windows-msvc.qbs3
-rw-r--r--src/app/qbs-setup-toolchains/clangclprobe.cpp5
-rw-r--r--src/app/qbs-setup-toolchains/msvcprobe.cpp293
-rw-r--r--src/app/qbs-setup-toolchains/msvcprobe.h10
-rw-r--r--src/lib/corelib/jsextensions/utilitiesextension.cpp40
-rw-r--r--src/lib/corelib/tools/msvcinfo.cpp355
-rw-r--r--src/lib/corelib/tools/msvcinfo.h26
-rw-r--r--tests/auto/blackbox/testdata/empty-profile/empty-profile.qbs3
-rw-r--r--tests/auto/blackbox/testdata/empty-profile/main.cpp1
-rw-r--r--tests/auto/blackbox/tst_blackbox.cpp11
-rw-r--r--tests/auto/blackbox/tst_blackbox.h1
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();