mirror of
https://github.com/Kitware/CMake.git
synced 2026-11-06 05:16:58 +00:00
Xcode: Fix precompiled headers with user-specified force includes
The Xcode generator implements `target_precompile_headers()` through the `GCC_PREFIX_HEADER` build setting, so Clang receives the prefix header as an `-include` flag and finds the precompiled `.gch` next to it. Clang honors that only when it is the first `-include` flag. Xcode extracts every `-include <file>` pair out of settings such as `OTHER_CFLAGS` and places it on the command line ahead of the prefix header, so a force include added by the project always came first and the precompiled header was dropped: precompiled header '.../cmake_pch.hxx.gch' was ignored because '-include .../cmake_pch.hxx' is not first '-include' Pass such flags straight to the compiler front end as a single joined `-Xclang -include<file>` argument instead. Xcode does not recognize it as a force include and leaves it in the response file, and the driver no longer sees an `-include` flag of its own, so the prefix header stays first. The header is still force included: Xcode compiles the prefix header in a step of its own, in which every force include, `-Xclang` or not, is processed before the prefix header itself. Rewrite every spelling Clang accepts, joined ones such as `-include<file>` and `--include=<file>` included, since the driver recognizes them all. Leave alone a flag already handed to a sub-tool with `-Xclang` or `-Xpreprocessor`, as well as one whose value looks like another flag. `GCC_PREFIX_HEADER` is a target-wide setting, so apply the rewrite to every language compiled in the target, not just the one the precompiled header was requested for. Do it where the `OTHER_*FLAGS` strings are assembled so that the define flags are covered too. Only rewrite for languages the prefix header applies to, and for compilers that understand `-Xclang`. Fixes: #23259
This commit is contained in:
11 files changed
+346
-14
No files matched your search
@@ -368,6 +368,171 @@ cm::string_view cmXcodeBuildSystemString(cmGlobalXCodeGenerator::BuildSystem b)
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
// The languages affected by target-wide settings, such as preprocessor
|
||||
// definitions and the prefix header.
|
||||
char const* const TargetWideSettingLangs[] = { "CXX", "C", "OBJCXX", "OBJC" };
|
||||
|
||||
// Choose a language to use for target-wide settings.
|
||||
std::string ChooseLangForTargetWideSettings(
|
||||
cmGeneratorTarget* gtgt, std::string const& config,
|
||||
std::set<std::string> const& languages)
|
||||
{
|
||||
std::string const llang = gtgt->GetLinkerLanguage(config);
|
||||
if (cm::contains(TargetWideSettingLangs, llang)) {
|
||||
return llang;
|
||||
}
|
||||
for (char const* l : TargetWideSettingLangs) {
|
||||
if (languages.count(l)) {
|
||||
return l;
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
// Whether the rewrite performed by HideForceIncludeFromClangDriver below is
|
||||
// understood by the compiler. Other compilers would reject '-Xclang'.
|
||||
bool CompilerUnderstandsXclang(cmMakefile* mf, std::string const& lang)
|
||||
{
|
||||
std::string const& id =
|
||||
mf->GetSafeDefinition(cmStrCat("CMAKE_", lang, "_COMPILER_ID"));
|
||||
return id == "Clang"_s || id == "AppleClang"_s || id == "IntelLLVM"_s;
|
||||
}
|
||||
|
||||
// One argument of a flags string: the range it occupies in the string, and
|
||||
// its value with the shell quoting removed.
|
||||
struct FlagArgument
|
||||
{
|
||||
std::string::size_type Begin;
|
||||
std::string Value;
|
||||
};
|
||||
|
||||
// Split a flags string the way a shell would, recording where each argument
|
||||
// starts so that the string can still be edited in place afterwards.
|
||||
std::vector<FlagArgument> SplitFlagArguments(std::string const& flags)
|
||||
{
|
||||
std::vector<FlagArgument> args;
|
||||
std::string::size_type i = 0;
|
||||
while (i < flags.size()) {
|
||||
if (flags[i] == ' ' || flags[i] == '\t') {
|
||||
++i;
|
||||
continue;
|
||||
}
|
||||
FlagArgument arg;
|
||||
arg.Begin = i;
|
||||
char quote = '\0';
|
||||
for (; i < flags.size(); ++i) {
|
||||
char const c = flags[i];
|
||||
if (quote != '\0') {
|
||||
if (c == quote) {
|
||||
quote = '\0';
|
||||
} else if (c == '\\' && quote == '"' && i + 1 < flags.size()) {
|
||||
arg.Value += flags[++i];
|
||||
} else {
|
||||
arg.Value += c;
|
||||
}
|
||||
} else if (c == '\'' || c == '"') {
|
||||
quote = c;
|
||||
} else if (c == '\\' && i + 1 < flags.size()) {
|
||||
arg.Value += flags[++i];
|
||||
} else if (c == ' ' || c == '\t') {
|
||||
break;
|
||||
} else {
|
||||
arg.Value += c;
|
||||
}
|
||||
}
|
||||
args.push_back(std::move(arg));
|
||||
}
|
||||
return args;
|
||||
}
|
||||
|
||||
// Xcode adds the '-include' flag for 'GCC_PREFIX_HEADER' after the flags
|
||||
// from settings such as 'OTHER_CFLAGS', but Clang uses a precompiled prefix
|
||||
// header only when its '-include' flag is the first one. Hide any
|
||||
// user-specified force-include flag from the Clang driver by passing it
|
||||
// straight to the compiler front end with '-Xclang', so that the prefix
|
||||
// header stays the first '-include' flag the driver sees. The header is
|
||||
// still force-included: Xcode compiles the prefix header in a step of its
|
||||
// own, in which every force include, '-Xclang' or not, is processed before
|
||||
// the prefix header itself.
|
||||
//
|
||||
// The flag and its value must end up in a single argument because Xcode
|
||||
// moves any '-include <file>' pair out of 'OTHER_CFLAGS' and onto the command
|
||||
// line ahead of the prefix header. Clang declares '-include' as
|
||||
// 'JoinedOrSeparate', so '-Xclang -include<file>' carries the same meaning.
|
||||
void HideForceIncludeFromClangDriver(std::string& flags)
|
||||
{
|
||||
// The edits to make, in increasing position. They are applied back to
|
||||
// front at the end so that the recorded positions stay valid.
|
||||
struct Edit
|
||||
{
|
||||
std::string::size_type Begin;
|
||||
std::string::size_type End;
|
||||
std::string Text;
|
||||
};
|
||||
std::vector<Edit> edits;
|
||||
|
||||
std::vector<FlagArgument> const args = SplitFlagArguments(flags);
|
||||
for (std::size_t i = 0; i < args.size(); ++i) {
|
||||
// Accept both the '-include' and the '--include' spelling.
|
||||
cm::string_view spelling;
|
||||
if (cmHasLiteralPrefix(args[i].Value, "--include")) {
|
||||
spelling = "--include"_s;
|
||||
} else if (cmHasLiteralPrefix(args[i].Value, "-include")) {
|
||||
spelling = "-include"_s;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
|
||||
// A flag already handed to a sub-tool is not seen by the driver.
|
||||
if (i > 0 &&
|
||||
(args[i - 1].Value == "-Xclang"_s ||
|
||||
args[i - 1].Value == "-Xpreprocessor"_s)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
cm::string_view const rest =
|
||||
cm::string_view(args[i].Value).substr(spelling.size());
|
||||
if (rest.empty()) {
|
||||
// The separate-argument spelling: the next argument is the file name.
|
||||
// Leave a lone flag alone rather than consume an unrelated argument.
|
||||
if (i + 1 == args.size() || cmHasPrefix(args[i + 1].Value, '-')) {
|
||||
continue;
|
||||
}
|
||||
// Keep the file name as written, quoting and all, so that it joins the
|
||||
// flag as a single argument.
|
||||
edits.push_back(
|
||||
{ args[i].Begin, args[i + 1].Begin, "-Xclang -include" });
|
||||
continue;
|
||||
}
|
||||
|
||||
// A joined spelling. Only '--include=<file>' uses a separator; after
|
||||
// '-include' an '=' is part of the file name.
|
||||
std::string rawSpelling(spelling);
|
||||
if (spelling.size() == cmStrLen("--include") && rest[0] == '=') {
|
||||
rawSpelling += '=';
|
||||
}
|
||||
// A flag holding a space is quoted as a whole, so step over an opening
|
||||
// quote to leave the quoting intact.
|
||||
std::string::size_type optBegin = args[i].Begin;
|
||||
if (flags[optBegin] == '\'' || flags[optBegin] == '"') {
|
||||
++optBegin;
|
||||
}
|
||||
if (flags.compare(optBegin, rawSpelling.size(), rawSpelling) != 0) {
|
||||
// Escaped in some way this does not account for; leave it alone.
|
||||
continue;
|
||||
}
|
||||
edits.push_back({ args[i].Begin, optBegin + rawSpelling.size(),
|
||||
cmStrCat("-Xclang ",
|
||||
cm::string_view(flags).substr(
|
||||
args[i].Begin, optBegin - args[i].Begin),
|
||||
"-include") });
|
||||
}
|
||||
|
||||
for (auto edit = edits.rbegin(); edit != edits.rend(); ++edit) {
|
||||
flags.replace(edit->Begin, edit->End - edit->Begin, edit->Text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool cmGlobalXCodeGenerator::SetGeneratorToolset(std::string const& ts,
|
||||
@@ -931,6 +1096,7 @@ void cmGlobalXCodeGenerator::ClearXCodeObjects()
|
||||
this->ExternalLibRefs.clear();
|
||||
this->FileRefToBuildFileMap.clear();
|
||||
this->CommandsVisited.clear();
|
||||
this->TargetPrefixHeaderMap.clear();
|
||||
}
|
||||
|
||||
void cmGlobalXCodeGenerator::addObject(std::unique_ptr<cmXCodeObject> obj)
|
||||
@@ -1091,6 +1257,28 @@ private:
|
||||
cmGeneratorTarget* Target = nullptr;
|
||||
};
|
||||
|
||||
bool cmGlobalXCodeGenerator::TargetHasPrefixHeader(cmGeneratorTarget* gtgt)
|
||||
{
|
||||
// Collecting the languages of a target walks all of its sources, so cache
|
||||
// the answer rather than recompute it for every source of the target.
|
||||
auto const inserted = this->TargetPrefixHeaderMap.emplace(gtgt, false);
|
||||
if (inserted.second) {
|
||||
// Per-file 'COMPILER_FLAGS' are not per-configuration, so a prefix header
|
||||
// used by any configuration has to be accounted for in all of them.
|
||||
for (std::string const& config : this->CurrentConfigurationTypes) {
|
||||
std::set<std::string> languages;
|
||||
gtgt->GetLanguages(languages, config);
|
||||
std::string const lang =
|
||||
ChooseLangForTargetWideSettings(gtgt, config, languages);
|
||||
if (!gtgt->GetPchHeader(config, lang).empty()) {
|
||||
inserted.first->second = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return inserted.first->second;
|
||||
}
|
||||
|
||||
cmXCodeObject* cmGlobalXCodeGenerator::CreateXCodeSourceFile(
|
||||
cmLocalGenerator* lg, cmSourceFile* sf, cmGeneratorTarget* gtgt)
|
||||
{
|
||||
@@ -1142,6 +1330,14 @@ cmXCodeObject* cmGlobalXCodeGenerator::CreateXCodeSourceFile(
|
||||
}
|
||||
}
|
||||
|
||||
// 'GCC_PREFIX_HEADER' is a target-wide setting, so it affects this source
|
||||
// even if the precompiled header was requested for another language.
|
||||
if (this->TargetHasPrefixHeader(gtgt) &&
|
||||
cm::contains(TargetWideSettingLangs, lang) &&
|
||||
CompilerUnderstandsXclang(lg->GetMakefile(), lang)) {
|
||||
HideForceIncludeFromClangDriver(flags);
|
||||
}
|
||||
|
||||
// Add per-source definitions.
|
||||
BuildObjectListOrString flagsBuild(this, false);
|
||||
std::string const COMPILE_DEFINITIONS("COMPILE_DEFINITIONS");
|
||||
@@ -2662,18 +2858,13 @@ void cmGlobalXCodeGenerator::CreateBuildSettings(cmGeneratorTarget* gtgt,
|
||||
}
|
||||
|
||||
// Choose a language to use for target-wide preprocessor definitions.
|
||||
static char const* ppLangs[] = { "CXX", "C", "OBJCXX", "OBJC" };
|
||||
std::string langForPreprocessorDefinitions;
|
||||
if (cm::contains(ppLangs, llang)) {
|
||||
langForPreprocessorDefinitions = llang;
|
||||
} else {
|
||||
for (char const* l : ppLangs) {
|
||||
if (languages.count(l)) {
|
||||
langForPreprocessorDefinitions = l;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
std::string const langForPreprocessorDefinitions =
|
||||
ChooseLangForTargetWideSettings(gtgt, configName, languages);
|
||||
|
||||
// The prefix header is a target-wide setting, so it affects every language
|
||||
// compiled in the target, not just the one it was requested for.
|
||||
std::string const pchHeader =
|
||||
gtgt->GetPchHeader(configName, langForPreprocessorDefinitions);
|
||||
|
||||
if (gtgt->IsIPOEnabled(llang, configName)) {
|
||||
char const* ltoValue =
|
||||
@@ -3196,6 +3387,10 @@ void cmGlobalXCodeGenerator::CreateBuildSettings(cmGeneratorTarget* gtgt,
|
||||
|
||||
for (auto const& language : languages) {
|
||||
std::string flags = cmStrCat(cflags[language], ' ', defFlags);
|
||||
if (!pchHeader.empty() && cm::contains(TargetWideSettingLangs, language) &&
|
||||
CompilerUnderstandsXclang(this->CurrentMakefile, language)) {
|
||||
HideForceIncludeFromClangDriver(flags);
|
||||
}
|
||||
if (language == "CXX"_s || language == "OBJCXX"_s) {
|
||||
if (language == "CXX"_s ||
|
||||
!buildSettings->GetAttribute("OTHER_CPLUSPLUSFLAGS")) {
|
||||
@@ -3330,8 +3525,6 @@ void cmGlobalXCodeGenerator::CreateBuildSettings(cmGeneratorTarget* gtgt,
|
||||
}
|
||||
|
||||
// Precompile Headers
|
||||
std::string pchHeader =
|
||||
gtgt->GetPchHeader(configName, langForPreprocessorDefinitions);
|
||||
if (!pchHeader.empty()) {
|
||||
buildSettings->AddAttribute("GCC_PREFIX_HEADER",
|
||||
this->CreateString(pchHeader));
|
||||
|
||||
@@ -279,6 +279,7 @@ private:
|
||||
cmGeneratorTarget* target);
|
||||
cmXCodeObject* CreateXCodeSourceFile(cmLocalGenerator* gen, cmSourceFile* sf,
|
||||
cmGeneratorTarget* gtgt);
|
||||
bool TargetHasPrefixHeader(cmGeneratorTarget* gtgt);
|
||||
void AddXCodeProjBuildRule(cmGeneratorTarget* target,
|
||||
std::vector<cmSourceFile*>& sources) const;
|
||||
bool CreateXCodeTargets(cmLocalGenerator* gen, std::vector<cmXCodeObject*>&);
|
||||
@@ -394,4 +395,5 @@ private:
|
||||
CommandsVisited;
|
||||
std::map<cmSourceFile const*, std::set<cmGeneratorTarget const*>>
|
||||
CustomCommandRoots;
|
||||
std::map<cmGeneratorTarget const*, bool> TargetPrefixHeaderMap;
|
||||
};
|
||||
@@ -0,0 +1,42 @@
|
||||
set(CMAKE_INTERMEDIATE_DIR_STRATEGY FULL CACHE STRING "" FORCE)
|
||||
|
||||
enable_language(C)
|
||||
enable_language(CXX)
|
||||
|
||||
if(CMAKE_CXX_COMPILE_OPTIONS_USE_PCH)
|
||||
add_definitions(-DHAVE_PCH_SUPPORT)
|
||||
endif()
|
||||
|
||||
# A force include added here lands in the directory define flags, which are
|
||||
# a source of compile flags separate from the target compile options below.
|
||||
add_definitions(-include forced.h)
|
||||
|
||||
# A force include whose path needs quoting, to cover the spelling in which
|
||||
# the whole argument rather than just the file name is quoted.
|
||||
set(forced4_dir "${CMAKE_CURRENT_BINARY_DIR}/forced dir")
|
||||
file(WRITE "${forced4_dir}/forced4.h" "#define FORCED4_INCLUDED 1\n")
|
||||
|
||||
add_executable(main pch-forced.cpp pch-forced.c)
|
||||
target_include_directories(main PRIVATE
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/include"
|
||||
)
|
||||
|
||||
# The precompiled header is requested for CXX only, but the Xcode generator
|
||||
# expresses it as the target-wide 'GCC_PREFIX_HEADER' build setting, so the C
|
||||
# source is affected too.
|
||||
target_precompile_headers(main PRIVATE
|
||||
"$<$<COMPILE_LANGUAGE:CXX>:${CMAKE_CURRENT_SOURCE_DIR}/pch-forced.h>"
|
||||
)
|
||||
|
||||
# The precompiled header must be used even though other headers are
|
||||
# force-included before it, in any of the spellings Clang accepts.
|
||||
target_compile_options(main PRIVATE
|
||||
-include forced2.h
|
||||
-includeforced3.h
|
||||
"-include${forced4_dir}/forced4.h"
|
||||
--include=forced5.h
|
||||
)
|
||||
|
||||
enable_testing()
|
||||
add_test(NAME main COMMAND main)
|
||||
@@ -36,6 +36,16 @@ if(RunCMake_GENERATOR MATCHES "Make|Ninja")
|
||||
run_cmake(PchInstantiateTemplates)
|
||||
endif()
|
||||
endif()
|
||||
# Only the Xcode generator passes the precompiled header to Clang as a prefix
|
||||
# header, which the compiler honors only when its '-include' flag comes first.
|
||||
# The other generators use '-include-pch' instead and are unaffected, except
|
||||
# on multi-architecture Apple builds, where they have the same limitation.
|
||||
# Only Clang-based compilers take the '-Xclang -include<file>' spelling that
|
||||
# the Xcode generator uses to keep a force include out of the driver's way.
|
||||
if(RunCMake_GENERATOR STREQUAL "Xcode" AND
|
||||
CMAKE_C_COMPILER_ID MATCHES "^(Clang|AppleClang|IntelLLVM)$")
|
||||
run_test(PchForceInclude)
|
||||
endif()
|
||||
run_test(PchIncludedAllLanguages)
|
||||
run_test(PchIncludedOneLanguage)
|
||||
run_test(PchLibObjLibExe)
|
||||
@@ -0,0 +1 @@
|
||||
#define FORCED_INCLUDED 1
|
||||
@@ -0,0 +1 @@
|
||||
#define FORCED2_INCLUDED 1
|
||||
@@ -0,0 +1 @@
|
||||
#define FORCED3_INCLUDED 1
|
||||
@@ -0,0 +1 @@
|
||||
#define FORCED5_INCLUDED 1
|
||||
@@ -0,0 +1,26 @@
|
||||
#ifndef FORCED_INCLUDED
|
||||
# error "forced.h was not force-included"
|
||||
#endif
|
||||
|
||||
#ifndef FORCED2_INCLUDED
|
||||
# error "forced2.h was not force-included"
|
||||
#endif
|
||||
|
||||
#ifndef FORCED3_INCLUDED
|
||||
# error "forced3.h was not force-included"
|
||||
#endif
|
||||
|
||||
#ifndef FORCED4_INCLUDED
|
||||
# error "forced4.h was not force-included"
|
||||
#endif
|
||||
|
||||
#ifndef FORCED5_INCLUDED
|
||||
# error "forced5.h was not force-included"
|
||||
#endif
|
||||
|
||||
int forced(void);
|
||||
|
||||
int forced(void)
|
||||
{
|
||||
return FORCED2_INCLUDED - FORCED_INCLUDED;
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
#ifndef FORCED_INCLUDED
|
||||
# error "forced.h was not force-included"
|
||||
#endif
|
||||
|
||||
#ifndef FORCED2_INCLUDED
|
||||
# error "forced2.h was not force-included"
|
||||
#endif
|
||||
|
||||
#ifndef FORCED3_INCLUDED
|
||||
# error "forced3.h was not force-included"
|
||||
#endif
|
||||
|
||||
#ifndef FORCED4_INCLUDED
|
||||
# error "forced4.h was not force-included"
|
||||
#endif
|
||||
|
||||
#ifndef FORCED5_INCLUDED
|
||||
# error "forced5.h was not force-included"
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_PCH_SUPPORT
|
||||
# ifndef PCH_INCLUDED
|
||||
# error "pch-forced.h was not included by the precompiled header"
|
||||
# endif
|
||||
#else
|
||||
# include "pch-forced.h"
|
||||
#endif
|
||||
|
||||
extern "C" int forced(void);
|
||||
|
||||
int main()
|
||||
{
|
||||
return (PCH_INCLUDED - FORCED_INCLUDED) + forced();
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
// Every force include must be processed before the precompiled header is
|
||||
// compiled, just as it is when no precompiled header is involved.
|
||||
#ifndef FORCED_INCLUDED
|
||||
# error "forced.h was not force-included before the precompiled header"
|
||||
#endif
|
||||
#ifndef FORCED2_INCLUDED
|
||||
# error "forced2.h was not force-included before the precompiled header"
|
||||
#endif
|
||||
#ifndef FORCED3_INCLUDED
|
||||
# error "forced3.h was not force-included before the precompiled header"
|
||||
#endif
|
||||
#ifndef FORCED4_INCLUDED
|
||||
# error "forced4.h was not force-included before the precompiled header"
|
||||
#endif
|
||||
#ifndef FORCED5_INCLUDED
|
||||
# error "forced5.h was not force-included before the precompiled header"
|
||||
#endif
|
||||
|
||||
#define PCH_INCLUDED 1
|
||||
Reference in new issue
Block a user