qmake: rewrite msvc/nmake cross-build environment setup

rather than reproducing vcvarsall.bat's functionality as hard-wired code
in the nmake generator, just invoke the actual script from
toolchain.prf. this is much easier, more future proof, and - critically
- makes the detected variables available to configure's new library &
header search facilities.

[ChangeLog][Important Behavior Changes][qmake][WinRT] Cross-builds will
now ignore pre-set values of %INCLUDE% and %LIB% when building target
executables. If necessary, use configure's -I and -L switches when
building Qt, and pass QMAKE_INCDIR and QMAKE_LIBDIR on qmake's command
line when building own projects.

Change-Id: I36f53e8880d6523f3f6f7a44d40d87d04bd06854
Reviewed-by: Thomas Miller <thomaslmiller91@gmail.com>
Reviewed-by: Oliver Wolff <oliver.wolff@qt.io>
This commit is contained in:
Oswald Buddenhagen 2018-12-17 20:57:18 +01:00 committed by Oswald Buddenhagen
parent ef14c3dc1a
commit 45e4dfb449
9 changed files with 181 additions and 216 deletions

View File

@ -0,0 +1,44 @@
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
::
:: Copyright (C) 2018 The Qt Company Ltd.
:: Contact: https://www.qt.io/licensing/
::
:: This file is part of the tools applications of the Qt Toolkit.
::
:: $QT_BEGIN_LICENSE:GPL-EXCEPT$
:: 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 https://www.qt.io/terms-conditions. For further
:: information use the contact form at https://www.qt.io/contact-us.
::
:: GNU General Public License Usage
:: Alternatively, this file may be used under the terms of the GNU
:: General Public License version 3 as published by the Free Software
:: Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
:: included in the packaging of this file. Please review the following
:: information to ensure the GNU General Public License requirements will
:: be met: https://www.gnu.org/licenses/gpl-3.0.html.
::
:: $QT_END_LICENSE$
::
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
@echo off
REM We clear INCLUDE and LIB, because we want to obtain pristine values.
REM PATH cannot be cleared, because then the script does not even run,
REM and it would be counterproductive anyway (see toolchain.prf).
set INCLUDE=
set LIB=
call %* || exit 1
REM VS2015 does not set errorlevel in case of failure.
if "%INCLUDE%" == "" exit 1
echo =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=
echo %INCLUDE%
echo %LIB%
echo %PATH%

View File

@ -1,3 +1,13 @@
defineTest(qtToolchainError) {
msg = \
$$1 \
"===================" \
$$2 \
"===================" \
$$3
error($$join(msg, $$escape_expand(\\n)))
}
defineTest(qtCompilerError) {
!cross_compile: \
what =
@ -5,13 +15,8 @@ defineTest(qtCompilerError) {
what = " host"
else: \
what = " target"
msg = \
"Cannot run$$what compiler '$$1'. Output:" \
"===================" \
$$2 \
"===================" \
"Maybe you forgot to setup the environment?"
error($$join(msg, $$escape_expand(\\n)))
qtToolchainError("Cannot run$$what compiler '$$1'. Output:", $$2, \
"Maybe you forgot to setup the environment?")
}
cross_compile:host_build: \
@ -125,6 +130,36 @@ defineReplace(qtMakeExpand) {
}
}
defineReplace(qtSplitPathList) {
paths = $$split(1, $$QMAKE_DIRLIST_SEP)
ret =
for (p, paths): \
ret += $$clean_path($$p)
return($$ret)
}
defineReplace(qtNmakePathList) {
paths =
for (p, 1): \
paths += $$shell_path($$p)
paths ~= s,$${LITERAL_HASH},^$${LITERAL_HASH},g
paths ~= s,\\$,\$\$,g
return($$join(paths, $$QMAKE_DIRLIST_SEP))
}
msvc {
arch = $$lower($$VCPROJ_ARCH)
equals(arch, x64): \ # may be "win32" or undefined
arch = amd64
else: !equals(arch, arm):!equals(arch, arm64): \ # may be "win32" or undefined
arch = x86
# Consider only WinRT and ARM64 desktop builds to be cross-builds -
# the host is assumed to be Intel and capable of running the target
# executables (so building for x64 on x86 will break).
winrt|equals(arch, arm64): \
CONFIG += msvc_cross
}
isEmpty($${target_prefix}.INCDIRS) {
#
# Get default include and library paths from compiler
@ -264,9 +299,89 @@ isEmpty($${target_prefix}.INCDIRS) {
}
}
}
} else: msvc_cross {
# Use a batch file, because %VAR% in the system() call expands to
# the pre-script-call value, and !VAR! cannot be enabled outside
# a batch file without invoking another shell instance.
cmd = $$system_quote($$system_path($$PWD/data/dumpvcvars.bat))
hostArch = $$QMAKE_HOST.arch
equals(hostArch, x86_64): \
hostArch = amd64
!equals(arch, $$hostArch): \
arch = $${hostArch}_$$arch
isEmpty(MSVC_VER): \
error("Mkspec does not specify MSVC_VER. Cannot continue.")
versionAtLeast(MSVC_VER, 15.0) {
dir = $$(VSINSTALLDIR)
isEmpty(dir): \
dir = $$read_registry(HKLM, \
"Software\\Microsoft\\VisualStudio\\SxS\\VS7\\$$MSVC_VER", 32)
isEmpty(dir): \
error("Failed to find the Visual Studio installation directory.")
cmd += $$system_quote($$dir\\VC\\Auxiliary\\Build\\vcvarsall.bat) $$arch
} else {
dir = $$(VCINSTALLDIR)
isEmpty(dir): \
dir = $$read_registry(HKLM, \
"Software\\Microsoft\\VisualStudio\\$$MSVC_VER\\Setup\\VC\\ProductDir", 32)
isEmpty(dir): \
error("Failed to find the Visual C installation directory.")
cmd += $$system_quote($$dir\\vcvarsall.bat) $$arch
}
winrt: cmd += store
isEmpty(WINSDK_VER): \
error("Mkspec does not specify WINSDK_VER. Cannot continue.")
# We prefer the environment variable, because that may work around
# a broken registry entry after uninstalling a newer SDK.
# However, we do that only if the major+minor SDK version matches
# the one requested by the mkspec, as we might be building for a
# newer target than the host.
winsdk_ver = $$(WindowsSDKVersion)
!isEmpty(winsdk_ver) {
winsdk_ver ~= s,\\\\$,, # Work around SDK breakage.
!equals(WINSDK_VER, $$replace(winsdk_ver, ^(\\d+\\.\\d+).*$, \\1)): \
winsdk_ver =
}
!isEmpty(winsdk_ver) {
cmd += $$winsdk_ver
} else {
winsdk_ver = $$read_registry(HKLM, \
"Software\\Microsoft\\Microsoft SDKs\\Windows\\v$$WINSDK_VER\\ProductVersion", 32)
isEmpty(winsdk_ver): \
error("Windows SDK $$WINSDK_VER requested by mkspec is not installed. Cannot continue.")
cmd += $${winsdk_ver}.0
}
output = $$system("$$cmd 2>&1", lines, ec)
!equals(ec, 0): \
qtToolchainError("SDK setup script failed. Output:", $$output, \
"Command was: $$cmd")
lines = $$output
for(ever) {
isEmpty(lines): \
break()
line = $$take_first(lines)
equals(line, "=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+="): \
break()
}
!count(lines, 3): \
qtToolchainError("SDK setup script returned unexpected output:", $$output, \
"Command was: $$cmd")
# These contain only paths for the target.
QMAKE_DEFAULT_INCDIRS = $$qtSplitPathList($$member(lines, 0))
QMAKE_DEFAULT_LIBDIRS = $$qtSplitPathList($$member(lines, 1))
# PATH is inherently for the host, and paths that are not shadowed
# by vcvarsall.bat are assumed to contain only tools that work for
# both host and target builds.
QMAKE_DEFAULT_PATH = $$qtSplitPathList($$member(lines, 2))
# We de-duplicate, because the script just prepends to the paths for
# the host, some of which are identical to the ones for the target.
QMAKE_DEFAULT_PATH = $$unique(QMAKE_DEFAULT_PATH)
} else: msvc {
# This doesn't differentiate between host and target,
# but neither do the compilers.
LIB = $$getenv("LIB")
QMAKE_DEFAULT_LIBDIRS = $$split(LIB, $$QMAKE_DIRLIST_SEP)
INCLUDE = $$getenv("INCLUDE")
@ -283,9 +398,22 @@ isEmpty($${target_prefix}.INCDIRS) {
cache($${target_prefix}.INCDIRS, set stash, QMAKE_DEFAULT_INCDIRS)
!isEmpty(QMAKE_DEFAULT_LIBDIRS): \
cache($${target_prefix}.LIBDIRS, set stash, QMAKE_DEFAULT_LIBDIRS)
!isEmpty(QMAKE_DEFAULT_PATH): \
cache($${target_prefix}.PATH, set stash, QMAKE_DEFAULT_PATH)
} else {
QMAKE_DEFAULT_INCDIRS = $$eval($${target_prefix}.INCDIRS)
QMAKE_DEFAULT_LIBDIRS = $$eval($${target_prefix}.LIBDIRS)
QMAKE_DEFAULT_PATH = $$eval($${target_prefix}.PATH)
}
msvc_cross {
qmake_inc_exp.name = INCLUDE
qmake_inc_exp.value = $$qtNmakePathList($$QMAKE_DEFAULT_INCDIRS)
qmake_lib_exp.name = LIB
qmake_lib_exp.value = $$qtNmakePathList($$QMAKE_DEFAULT_LIBDIRS)
qmake_path_exp.name = PATH
qmake_path_exp.value = $$qtNmakePathList($$QMAKE_DEFAULT_PATH)
QMAKE_EXPORTED_VARIABLES += qmake_inc_exp qmake_lib_exp qmake_path_exp
}
unset(target_prefix)

View File

@ -15,6 +15,5 @@ QMAKE_LIBS += windowscodecs.lib WindowsApp.lib runtimeobject.lib One
VCPROJ_ARCH = ARM
WINSDK_VER = 10.0
WINTARGET_VER = winv10.0
WINRT_MANIFEST = $$PWD/../common/winrt_winphone/manifests/10.0/AppxManifest.xml.in
WINRT_MANIFEST.architecture = arm

View File

@ -15,6 +15,5 @@ QMAKE_LIBS += windowscodecs.lib WindowsApp.lib runtimeobject.lib One
VCPROJ_ARCH = ARM
WINSDK_VER = 10.0
WINTARGET_VER = winv10.0
WINRT_MANIFEST = $$PWD/../common/winrt_winphone/manifests/10.0/AppxManifest.xml.in
WINRT_MANIFEST.architecture = arm

View File

@ -15,6 +15,5 @@ QMAKE_LIBS += windowscodecs.lib WindowsApp.lib runtimeobject.lib One
VCPROJ_ARCH = x64
WINSDK_VER = 10.0
WINTARGET_VER = winv10.0
WINRT_MANIFEST = $$PWD/../common/winrt_winphone/manifests/10.0/AppxManifest.xml.in
WINRT_MANIFEST.architecture = x64

View File

@ -15,6 +15,5 @@ QMAKE_LIBS += windowscodecs.lib WindowsApp.lib runtimeobject.lib One
VCPROJ_ARCH = x64
WINSDK_VER = 10.0
WINTARGET_VER = winv10.0
WINRT_MANIFEST = $$PWD/../common/winrt_winphone/manifests/10.0/AppxManifest.xml.in
WINRT_MANIFEST.architecture = x64

View File

@ -14,6 +14,5 @@ QMAKE_LFLAGS += /SAFESEH /MACHINE:X86 /NODEFAULTLIB:kernel32.lib
QMAKE_LIBS += windowscodecs.lib WindowsApp.lib runtimeobject.lib OneCore.lib
VCPROJ_ARCH = Win32
WINSDK_VER = 10.0
WINTARGET_VER = winv10.0
WINRT_MANIFEST = $$PWD/../common/winrt_winphone/manifests/10.0/AppxManifest.xml.in
WINRT_MANIFEST.architecture = x86

View File

@ -14,6 +14,5 @@ QMAKE_LFLAGS += /SAFESEH /MACHINE:X86 /NODEFAULTLIB:kernel32.lib
QMAKE_LIBS += windowscodecs.lib WindowsApp.lib runtimeobject.lib OneCore.lib
VCPROJ_ARCH = Win32
WINSDK_VER = 10.0
WINTARGET_VER = winv10.0
WINRT_MANIFEST = $$PWD/../common/winrt_winphone/manifests/10.0/AppxManifest.xml.in
WINRT_MANIFEST.architecture = x86

View File

@ -34,23 +34,10 @@
#include <qdiriterator.h>
#include <qset.h>
#include <registry_p.h>
#include <time.h>
QT_BEGIN_NAMESPACE
static QString nmakePathList(const QStringList &list)
{
QStringList pathList;
pathList.reserve(list.size());
for (const QString &path : list)
pathList.append(QDir::cleanPath(path));
return QDir::toNativeSeparators(pathList.join(QLatin1Char(';')))
.replace('#', QLatin1String("^#")).replace('$', QLatin1String("$$"));
}
NmakeMakefileGenerator::NmakeMakefileGenerator() : usePCH(false), usePCHC(false)
{
@ -70,194 +57,6 @@ NmakeMakefileGenerator::writeMakefile(QTextStream &t)
if(Option::mkfile::do_stub_makefile)
return MakefileGenerator::writeStubMakefile(t);
#endif
if (!project->isHostBuild()) {
const QString msvcVer = project->first("MSVC_VER").toQString();
if (msvcVer.isEmpty()) {
fprintf(stderr, "Mkspec does not specify MSVC_VER. Cannot continue.\n");
return false;
}
bool winrtBuild = false;
bool crossPlatformDesktopBuild = false;
QString arch = project->first("VCPROJ_ARCH").toQString().toLower();
if (project->isActiveConfig(QStringLiteral("winrt"))) {
winrtBuild = true;
// Only add explicit support for arm64 cross-platform desktop builds.
} else if ((arch == QLatin1String("arm64")) && (msvcVer == QStringLiteral("15.0"))) {
crossPlatformDesktopBuild = true;
}
if (winrtBuild || crossPlatformDesktopBuild) {
QString compiler;
QString compilerArch;
const ProStringList hostArch = project->values("QMAKE_TARGET.arch");
if (msvcVer == QStringLiteral("15.0")) {
if (hostArch.contains("x86_64"))
compiler = QStringLiteral("HostX64/");
else
compiler = QStringLiteral("HostX86/");
if (arch == QLatin1String("arm")) {
compiler += QStringLiteral("arm");
compilerArch = QStringLiteral("arm");
} else if (arch == QLatin1String("x64")) {
compiler += QStringLiteral("x64");
compilerArch = QStringLiteral("amd64");
} else if (arch == QLatin1String("arm64")) {
compiler += QStringLiteral("arm64");
compilerArch = QStringLiteral("arm64");
} else {
arch = QStringLiteral("x86");
compiler += QStringLiteral("x86");
}
} else {
if (arch == QLatin1String("arm")) {
compiler = QStringLiteral("x86_arm");
compilerArch = QStringLiteral("arm");
} else if (arch == QLatin1String("x64")) {
const ProStringList hostArch = project->values("QMAKE_TARGET.arch");
if (hostArch.contains("x86_64"))
compiler = QStringLiteral("amd64");
else
compiler = QStringLiteral("x86_amd64");
compilerArch = QStringLiteral("amd64");
} else {
arch = QStringLiteral("x86");
}
}
const QString winsdkVer = project->first("WINSDK_VER").toQString();
if (winsdkVer.isEmpty()) {
fprintf(stderr, "Mkspec does not specify WINSDK_VER. Cannot continue.\n");
return false;
}
const QString targetVer = project->first("WINTARGET_VER").toQString();
if (targetVer.isEmpty() && winrtBuild) {
fprintf(stderr, "Mkspec does not specify WINTARGET_VER. Cannot continue.\n");
return false;
}
#ifdef Q_OS_WIN
QString regKey;
if (msvcVer == QStringLiteral("15.0"))
regKey = QStringLiteral("Software\\Microsoft\\VisualStudio\\SxS\\VS7\\") + msvcVer;
else
regKey = QStringLiteral("Software\\Microsoft\\VisualStudio\\") + msvcVer + ("\\Setup\\VC\\ProductDir");
const QString vcInstallDir = qt_readRegistryKey(HKEY_LOCAL_MACHINE, regKey, KEY_WOW64_32KEY);
if (vcInstallDir.isEmpty()) {
fprintf(stderr, "Failed to find the Visual Studio installation directory.\n");
return false;
}
const QString windowsPath = "Software\\Microsoft\\Microsoft SDKs\\Windows\\v";
regKey = windowsPath + winsdkVer + QStringLiteral("\\InstallationFolder");
const QString kitDir = qt_readRegistryKey(HKEY_LOCAL_MACHINE, regKey, KEY_WOW64_32KEY);
if (kitDir.isEmpty()) {
fprintf(stderr, "Failed to find the Windows Kit installation directory.\n");
return false;
}
#else
const QString vcInstallDir = "/fake/vc_install_dir";
const QString kitDir = "/fake/sdk_install_dir";
#endif // Q_OS_WIN
QStringList incDirs;
QStringList libDirs;
QStringList binDirs;
if (msvcVer == QStringLiteral("15.0")) {
const QString toolsInstallDir = qgetenv("VCToolsInstallDir");
if (toolsInstallDir.isEmpty()) {
fprintf(stderr, "Failed to access tools installation dir.\n");
return false;
}
binDirs << toolsInstallDir + QStringLiteral("bin/") + compiler;
if (arch == QStringLiteral("x64"))
binDirs << toolsInstallDir + QStringLiteral("bin/HostX86/X86");
binDirs << kitDir + QStringLiteral("bin/x86");
binDirs << vcInstallDir + QStringLiteral("Common7/Tools");
binDirs << vcInstallDir + QStringLiteral("Common7/ide");
binDirs << vcInstallDir + QStringLiteral("MSBuild/15.0/bin");
incDirs << toolsInstallDir + QStringLiteral("include");
incDirs << vcInstallDir + QStringLiteral("VC/Auxiliary/VS/include");
const QString crtVersion = qgetenv("UCRTVersion");
if (crtVersion.isEmpty()) {
fprintf(stderr, "Failed to access CRT version.\n");
return false;
}
const QString crtInclude = kitDir + QStringLiteral("Include/") + crtVersion;
const QString crtLib = kitDir + QStringLiteral("Lib/") + crtVersion;
incDirs << crtInclude + QStringLiteral("/ucrt");
incDirs << crtInclude + QStringLiteral("/um");
incDirs << crtInclude + QStringLiteral("/shared");
incDirs << crtInclude + QStringLiteral("/winrt");
if (winrtBuild) {
libDirs << toolsInstallDir + QStringLiteral("lib/") + arch + QStringLiteral("/store");
} else {
// Desktop projects may require the atl headers and libs.
incDirs << toolsInstallDir + QStringLiteral("atlmfc/include");
libDirs << toolsInstallDir + QStringLiteral("atlmfc/lib/") + compilerArch;
libDirs << toolsInstallDir + QStringLiteral("lib/") + arch;
}
libDirs << vcInstallDir + QStringLiteral("VC/Auxiliary/VS/lib/") + arch;
libDirs << crtLib + QStringLiteral("/ucrt/") + arch;
libDirs << crtLib + QStringLiteral("/um/") + arch;
} else if (msvcVer == QStringLiteral("14.0")) {
binDirs << vcInstallDir + QStringLiteral("bin/") + compiler;
binDirs << vcInstallDir + QStringLiteral("bin/"); // Maybe remove for x86 again?
binDirs << kitDir + QStringLiteral("bin/") + (arch == QStringLiteral("arm") ? QStringLiteral("x86") : arch);
binDirs << vcInstallDir + QStringLiteral("../Common7/Tools/bin");
binDirs << vcInstallDir + QStringLiteral("../Common7/Tools");
binDirs << vcInstallDir + QStringLiteral("../Common7/ide");
binDirs << kitDir + QStringLiteral("Windows Performance Toolkit/");
incDirs << vcInstallDir + QStringLiteral("include");
incDirs << vcInstallDir + QStringLiteral("atlmfc/include");
const QString crtVersion = qgetenv("UCRTVersion");
if (crtVersion.isEmpty()) {
fprintf(stderr, "Failed to access CRT version.\n");
return false;
}
const QString crtInclude = kitDir + QStringLiteral("Include/") + crtVersion;
const QString crtLib = kitDir + QStringLiteral("Lib/") + crtVersion;
incDirs << crtInclude + QStringLiteral("/ucrt");
incDirs << crtInclude + QStringLiteral("/um");
incDirs << crtInclude + QStringLiteral("/shared");
incDirs << crtInclude + QStringLiteral("/winrt");
libDirs << vcInstallDir + QStringLiteral("lib/store/") + compilerArch;
libDirs << vcInstallDir + QStringLiteral("atlmfc/lib") + compilerArch;
libDirs << crtLib + QStringLiteral("/ucrt/") + arch;
libDirs << crtLib + QStringLiteral("/um/") + arch;
} else {
incDirs << vcInstallDir + QStringLiteral("/include");
libDirs << vcInstallDir + QStringLiteral("/lib/store/") + compilerArch
<< vcInstallDir + QStringLiteral("/lib/") + compilerArch;
binDirs << vcInstallDir + QStringLiteral("/bin/") + compiler
<< vcInstallDir + QStringLiteral("/../Common7/IDE");
libDirs << kitDir + QStringLiteral("/Lib/") + targetVer + ("/um/") + arch;
incDirs << kitDir + QStringLiteral("/include/um")
<< kitDir + QStringLiteral("/include/shared")
<< kitDir + QStringLiteral("/include/winrt");
}
binDirs << vcInstallDir + QStringLiteral("/bin");
// Inherit PATH
binDirs << QString::fromLocal8Bit(qgetenv("PATH")).split(QLatin1Char(';'));
t << "\nINCLUDE = " << nmakePathList(incDirs);
t << "\nLIB = " << nmakePathList(libDirs);
t << "\nPATH = " << nmakePathList(binDirs) << '\n';
}
}
writeNmakeParts(t);
return MakefileGenerator::writeMakefile(t);
}