Enable precompiled headers on iOS, tvOS, watchOS

The actual blocker for precompiled headers is not the iOS/tvOS/watchOS
platforms, but the way qmake handled multiple-architecture builds on
Apple platforms.

This patch allows multi-arch builds to be performed while using
precompiled headers.

Since df91ef3d6c55692a0236f67b6c6b134a3bf84098 (April 2009), Clang has
had support for PCH files in the driver, which allows to use the
-include flag to automatically translate to -include-pch. We can then
take advantage of the fact that the -include option is allowed to not
be separate from its argument, which lets us take advantage of -Xarch to
specify a per-architecture precompiled header file.

This is done through some magic in the qmake Makefile generator which
"multiplexes" the PCH creation rule across multiple architectures and
replaces a series of tokens with the proper precompiled header paths
and architecture flags at usage point.

Change-Id: I76c8dc9cda7e218869c2919f023d9b04f311c6fd
Reviewed-by: Oswald Buddenhagen <oswald.buddenhagen@theqtcompany.com>
This commit is contained in:
Jake Petroules 2016-08-23 15:16:42 -07:00
parent 443240b512
commit 73331eebf8
5 changed files with 94 additions and 24 deletions

View File

@ -1484,7 +1484,7 @@
}, },
"precompile_header": { "precompile_header": {
"description": "Using precompiled headers", "description": "Using precompiled headers",
"condition": "config.msvc || (!config.uikit && tests.precompile_header)", "condition": "config.msvc || tests.precompile_header",
"output": [ "output": [
"privateConfig", "privateConfig",
{ "type": "varRemove", "negative": true, "name": "CONFIG", "value": "'precompile_header'" } { "type": "varRemove", "negative": true, "name": "CONFIG", "value": "'precompile_header'" }

View File

@ -56,13 +56,32 @@ for(tool, $$list(QMAKE_CC QMAKE_CXX QMAKE_FIX_RPATH QMAKE_AR QMAKE_RANLIB QMAKE_
!equals(MAKEFILE_GENERATOR, XCODE) { !equals(MAKEFILE_GENERATOR, XCODE) {
uikit:!host_build { uikit:!host_build {
simulator: \ ios: os_var = IOS
tvos: os_var = TVOS
watchos: os_var = WATCHOS
deployment_target = $$eval(QMAKE_$${os_var}_DEPLOYMENT_TARGET)
simulator {
archs = $$eval(QMAKE_$${os_var}_SIMULATOR_ARCHS)
version_identifier = $$simulator.deployment_identifier version_identifier = $$simulator.deployment_identifier
else: \ } else {
archs = $$eval(QMAKE_$${os_var}_DEVICE_ARCHS)
version_identifier = $$device.deployment_identifier version_identifier = $$device.deployment_identifier
ios: deployment_target = $$QMAKE_IOS_DEPLOYMENT_TARGET }
tvos: deployment_target = $$QMAKE_TVOS_DEPLOYMENT_TARGET
watchos: deployment_target = $$QMAKE_WATCHOS_DEPLOYMENT_TARGET single_arch: archs = $$first(archs)
QMAKE_CFLAGS_USE_PRECOMPILE =
for(arch, archs) {
QMAKE_CFLAGS_USE_PRECOMPILE += \
-Xarch_$${arch} \
-include${QMAKE_PCH_OUTPUT_$${arch}}
}
QMAKE_CXXFLAGS_USE_PRECOMPILE = $$QMAKE_CFLAGS_USE_PRECOMPILE
QMAKE_OBJCFLAGS_USE_PRECOMPILE = $$QMAKE_CFLAGS_USE_PRECOMPILE
QMAKE_OBJCXXFLAGS_USE_PRECOMPILE = $$QMAKE_CFLAGS_USE_PRECOMPILE
QMAKE_PCH_OUTPUT_EXT = _${QMAKE_PCH_ARCH}$${QMAKE_PCH_OUTPUT_EXT}
} else: osx { } else: osx {
version_identifier = macosx version_identifier = macosx
deployment_target = $$QMAKE_MACOSX_DEPLOYMENT_TARGET deployment_target = $$QMAKE_MACOSX_DEPLOYMENT_TARGET

View File

@ -114,4 +114,6 @@ macx-xcode {
QMAKE_CFLAGS += $$arch_flags QMAKE_CFLAGS += $$arch_flags
QMAKE_CXXFLAGS += $$arch_flags QMAKE_CXXFLAGS += $$arch_flags
QMAKE_LFLAGS += $$arch_flags QMAKE_LFLAGS += $$arch_flags
QMAKE_PCH_ARCHS = $$VALID_ARCHS
} }

View File

@ -194,6 +194,18 @@ UnixMakefileGenerator::init()
if (!language.isEmpty()) { if (!language.isEmpty()) {
pchFlags.replace(QLatin1String("${QMAKE_PCH_OUTPUT}"), pchFlags.replace(QLatin1String("${QMAKE_PCH_OUTPUT}"),
escapeFilePath(pchBaseName + language + headerSuffix)); escapeFilePath(pchBaseName + language + headerSuffix));
const ProStringList pchArchs = project->values("QMAKE_PCH_ARCHS");
for (const ProString &arch : pchArchs) {
QString suffix = headerSuffix;
suffix.replace(QLatin1String("${QMAKE_PCH_ARCH}"), arch.toQString());
if (project->isActiveConfig("clang_pch_style")
&& (suffix.endsWith(QLatin1String(".pch"))
|| suffix.endsWith(QLatin1String(".gch")))) {
suffix.chop(4); // must omit header suffix for -include to recognize the PCH
}
pchFlags.replace(QLatin1String("${QMAKE_PCH_OUTPUT_") + arch + QLatin1Char('}'),
escapeFilePath(pchBaseName + language + suffix));
}
} }
} }
@ -351,9 +363,17 @@ QStringList
if (!file.endsWith(extension.toQString())) if (!file.endsWith(extension.toQString()))
continue; continue;
QString precompiledHeader = header_prefix + language + header_suffix; ProStringList pchArchs = project->values("QMAKE_PCH_ARCHS");
if (!ret.contains(precompiledHeader)) if (pchArchs.isEmpty())
ret += precompiledHeader; pchArchs << ProString(); // normal single-arch PCH
for (const ProString &arch : qAsConst(pchArchs)) {
QString suffix = header_suffix;
if (!arch.isEmpty())
suffix.replace(QLatin1String("${QMAKE_PCH_ARCH}"), arch.toQString());
QString precompiledHeader = header_prefix + language + suffix;
if (!ret.contains(precompiledHeader))
ret += precompiledHeader;
}
goto foundPrecompiledDependency; goto foundPrecompiledDependency;
} }

View File

@ -996,7 +996,15 @@ UnixMakefileGenerator::writeMakeParts(QTextStream &t)
if (language.isEmpty()) if (language.isEmpty())
continue; continue;
precomp_files += precomph_out_dir + header_prefix + language + header_suffix; ProStringList pchArchs = project->values("QMAKE_PCH_ARCHS");
if (pchArchs.isEmpty())
pchArchs << ProString(); // normal single-arch PCH
for (const ProString &arch : qAsConst(pchArchs)) {
auto suffix = header_suffix.toQString();
if (!arch.isEmpty())
suffix.replace(QStringLiteral("${QMAKE_PCH_ARCH}"), arch.toQString());
precomp_files += precomph_out_dir + header_prefix + language + suffix;
}
} }
} }
t << "-$(DEL_FILE) " << escapeFilePaths(precomp_files).join(' ') << "\n\t"; t << "-$(DEL_FILE) " << escapeFilePaths(precomp_files).join(' ') << "\n\t";
@ -1052,6 +1060,7 @@ UnixMakefileGenerator::writeMakeParts(QTextStream &t)
QString pchInput = project->first("PRECOMPILED_HEADER").toQString(); QString pchInput = project->first("PRECOMPILED_HEADER").toQString();
t << "###### Precompiled headers\n"; t << "###### Precompiled headers\n";
for (const ProString &compiler : project->values("QMAKE_BUILTIN_COMPILERS")) { for (const ProString &compiler : project->values("QMAKE_BUILTIN_COMPILERS")) {
QString pchOutputDir;
QString pchFlags = var(ProKey("QMAKE_" + compiler + "FLAGS_PRECOMPILE")); QString pchFlags = var(ProKey("QMAKE_" + compiler + "FLAGS_PRECOMPILE"));
if(pchFlags.isEmpty()) if(pchFlags.isEmpty())
continue; continue;
@ -1062,6 +1071,9 @@ UnixMakefileGenerator::writeMakeParts(QTextStream &t)
else else
cflags += " $(CXXFLAGS)"; cflags += " $(CXXFLAGS)";
ProStringList pchArchs = project->values("QMAKE_PCH_ARCHS");
if (pchArchs.isEmpty())
pchArchs << ProString(); // normal single-arch PCH
ProString pchBaseName = project->first("QMAKE_ORIG_TARGET"); ProString pchBaseName = project->first("QMAKE_ORIG_TARGET");
ProString pchOutput; ProString pchOutput;
if(!project->isEmpty("PRECOMPILED_DIR")) if(!project->isEmpty("PRECOMPILED_DIR"))
@ -1088,30 +1100,47 @@ UnixMakefileGenerator::writeMakeParts(QTextStream &t)
ProString header_suffix = project->isActiveConfig("clang_pch_style") ProString header_suffix = project->isActiveConfig("clang_pch_style")
? project->first("QMAKE_PCH_OUTPUT_EXT") : ""; ? project->first("QMAKE_PCH_OUTPUT_EXT") : "";
pchOutput += Option::dir_sep; pchOutput += Option::dir_sep;
QString pchOutputDir = pchOutput.toQString(); pchOutputDir = pchOutput.toQString();
QString language = project->first(ProKey("QMAKE_LANGUAGE_" + compiler)).toQString(); QString language = project->first(ProKey("QMAKE_LANGUAGE_" + compiler)).toQString();
if (language.isEmpty()) if (language.isEmpty())
continue; continue;
pchOutput += header_prefix + language + header_suffix; pchOutput += header_prefix + language + header_suffix;
t << escapeDependencyPath(pchOutput) << ": " << escapeDependencyPath(pchInput) << ' '
<< escapeDependencyPaths(findDependencies(pchInput)).join(" \\\n\t\t")
<< "\n\t" << mkdir_p_asstring(pchOutputDir);
} }
pchFlags.replace(QLatin1String("${QMAKE_PCH_INPUT}"), escapeFilePath(pchInput)) pchFlags.replace(QLatin1String("${QMAKE_PCH_INPUT}"), escapeFilePath(pchInput))
.replace(QLatin1String("${QMAKE_PCH_OUTPUT_BASE}"), escapeFilePath(pchBaseName.toQString())) .replace(QLatin1String("${QMAKE_PCH_OUTPUT_BASE}"), escapeFilePath(pchBaseName.toQString()));
.replace(QLatin1String("${QMAKE_PCH_OUTPUT}"), escapeFilePath(pchOutput.toQString())); for (const ProString &arch : qAsConst(pchArchs)) {
auto pchArchOutput = pchOutput.toQString();
if (!arch.isEmpty())
pchArchOutput.replace(QStringLiteral("${QMAKE_PCH_ARCH}"), arch.toQString());
QString compilerExecutable; if (!project->isActiveConfig("icc_pch_style")) {
if (compiler == "C" || compiler == "OBJC") const auto pchFilePath_d = escapeDependencyPath(pchArchOutput);
compilerExecutable = "$(CC)"; if (!arch.isEmpty()) {
else t << pchFilePath_d << ": " << "EXPORT_ARCH_ARGS = -arch " << arch << "\n\n";
compilerExecutable = "$(CXX)"; t << pchFilePath_d << ": "
<< "EXPORT_QMAKE_XARCH_CFLAGS = $(EXPORT_QMAKE_XARCH_CFLAGS_" << arch << ")" << "\n\n";
t << pchFilePath_d << ": "
<< "EXPORT_QMAKE_XARCH_LFLAGS = $(EXPORT_QMAKE_XARCH_LFLAGS_" << arch << ")" << "\n\n";
}
t << pchFilePath_d << ": " << escapeDependencyPath(pchInput) << ' '
<< escapeDependencyPaths(findDependencies(pchInput)).join(" \\\n\t\t")
<< "\n\t" << mkdir_p_asstring(pchOutputDir);
}
// compile command auto pchArchFlags = pchFlags;
t << "\n\t" << compilerExecutable << cflags << " $(INCPATH) " << pchFlags << endl << endl; pchArchFlags.replace(QLatin1String("${QMAKE_PCH_OUTPUT}"), escapeFilePath(pchArchOutput));
QString compilerExecutable;
if (compiler == "C" || compiler == "OBJC")
compilerExecutable = "$(CC)";
else
compilerExecutable = "$(CXX)";
// compile command
t << "\n\t" << compilerExecutable << cflags << " $(INCPATH) " << pchArchFlags << endl << endl;
}
} }
} }