Commit 729d997f authored by Cristian Adam's avatar Cristian Adam

Precompile Headers: Add REUSE_FROM signature

Add the ability to share precompiled headers artifacts between
targets.

Fixes: #19659
parent 1ac4e0ef
......@@ -9,9 +9,23 @@ Add a list of header files to precompile.
<INTERFACE|PUBLIC|PRIVATE> [header1...]
[<INTERFACE|PUBLIC|PRIVATE> [header2...] ...])
target_precompile_headers(<target> REUSE_FROM <other_target>)
Adds header files to :prop_tgt:`PRECOMPILE_HEADERS` or
:prop_tgt:`INTERFACE_PRECOMPILE_HEADERS` target properties.
The second signature will reuse an already precompiled header file artefact
from another target. This is done by setting the
:prop_tgt:`PRECOMPILE_HEADERS_REUSE_FROM` to ``<other_target>`` value.
The ``<other_target>`` will become a dependency of ``<target>``.
.. note::
The second signature will require the same set of compiler options,
compiler flags, compiler definitions for both ``<target>``, and
``<other_target>``. Compilers (e.g. GCC) will issue a warning if the
precompiled header file cannot be used (``-Winvalid-pch``).
Precompiling header files can speed up compilation by creating a partially
processed version of some header files, and then using that version during
compilations rather than repeatedly parsing the original headers.
......
......@@ -298,6 +298,7 @@ Properties on Targets
/prop_tgt/PDB_OUTPUT_DIRECTORY
/prop_tgt/POSITION_INDEPENDENT_CODE
/prop_tgt/PRECOMPILE_HEADERS
/prop_tgt/PRECOMPILE_HEADERS_REUSE_FROM
/prop_tgt/PREFIX
/prop_tgt/PRIVATE_HEADER
/prop_tgt/PROJECT_LABEL
......
PRECOMPILE_HEADERS_REUSE_FROM
-----------------------------
Target from which to reuse the precomipled headers build artifact.
See the second signature of :command:`target_precompile_headers` command
for more detailed information.
......@@ -333,10 +333,17 @@ macro(__windows_compiler_msvc lang)
set(CMAKE_LINK_PCH ON)
if(MSVC_VERSION GREATER_EQUAL 1910)
# VS 2017 or greater
set(CMAKE_PCH_PROLOGUE "#pragma system_header")
if (NOT ${CMAKE_${lang}_COMPILER_ID} STREQUAL "Clang")
set(CMAKE_PCH_PROLOGUE "#pragma system_header")
else()
set(CMAKE_PCH_PROLOGUE "#pragma clang system_header")
endif()
endif()
if (NOT ${CMAKE_${lang}_COMPILER_ID} STREQUAL "Clang")
set(CMAKE_PCH_COPY_COMPILE_PDB ON)
endif()
set(CMAKE_${lang}_COMPILE_OPTIONS_USE_PCH /Yu<PCH_HEADER> /FI<PCH_HEADER>)
set(CMAKE_${lang}_COMPILE_OPTIONS_CREATE_PCH /Yc<PCH_HEADER> /FI<PCH_HEADER>)
set(CMAKE_${lang}_COMPILE_OPTIONS_USE_PCH /Yu<PCH_HEADER> /Fp<PCH_FILE> /FI<PCH_HEADER>)
set(CMAKE_${lang}_COMPILE_OPTIONS_CREATE_PCH /Yc<PCH_HEADER> /Fp<PCH_FILE> /FI<PCH_HEADER>)
if("x${CMAKE_${lang}_COMPILER_ID}" STREQUAL "xMSVC")
set(_CMAKE_${lang}_IPO_SUPPORTED_BY_CMAKE YES)
......
......@@ -171,6 +171,7 @@ std::string cmCommonTargetGenerator::ComputeTargetCompilePDB() const
if (this->GeneratorTarget->GetType() > cmStateEnums::OBJECT_LIBRARY) {
return compilePdbPath;
}
compilePdbPath =
this->GeneratorTarget->GetCompilePDBPath(this->GetConfigName());
if (compilePdbPath.empty()) {
......
......@@ -35,6 +35,7 @@
#include "cmRange.h"
#include "cmSourceFile.h"
#include "cmSourceFileLocation.h"
#include "cmSourceFileLocationKind.h"
#include "cmState.h"
#include "cmStringAlgorithms.h"
#include "cmSystemTools.h"
......@@ -3371,57 +3372,67 @@ std::string cmGeneratorTarget::GetPchHeader(const std::string& config,
}
std::string& filename = inserted.first->second;
const cmGeneratorTarget* generatorTarget = this;
const char* pchReuseFrom =
generatorTarget->GetProperty("PRECOMPILE_HEADERS_REUSE_FROM");
if (pchReuseFrom) {
generatorTarget =
this->GetGlobalGenerator()->FindGeneratorTarget(pchReuseFrom);
}
if (this->GetGlobalGenerator()->IsMultiConfig()) {
filename =
cmStrCat(this->LocalGenerator->GetCurrentBinaryDirectory(), "/");
filename = cmStrCat(
generatorTarget->LocalGenerator->GetCurrentBinaryDirectory(), "/");
} else {
// For GCC we need to have the header file .h[xx]
// next to the .h[xx].gch file
filename = this->ObjectDirectory;
filename = generatorTarget->ObjectDirectory;
}
filename = cmStrCat(filename, "CMakeFiles/", this->GetName(),
filename = cmStrCat(filename, "CMakeFiles/", generatorTarget->GetName(),
".dir/cmake_pch", ((language == "C") ? ".h" : ".hxx"));
const std::string filename_tmp = cmStrCat(filename, ".tmp");
{
if (!pchReuseFrom) {
auto pchPrologue = this->Makefile->GetDefinition("CMAKE_PCH_PROLOGUE");
auto pchEpilogue = this->Makefile->GetDefinition("CMAKE_PCH_EPILOGUE");
cmGeneratedFileStream file(
filename_tmp, false,
this->GetGlobalGenerator()->GetMakefileEncoding());
file << "/* generated by CMake */\n\n";
if (pchPrologue) {
file << pchPrologue << "\n";
}
if (this->GetGlobalGenerator()->IsXcode()) {
file << "#ifndef CMAKE_SKIP_PRECOMPILE_HEADERS\n";
}
if (language == "CXX") {
file << "#ifdef __cplusplus\n";
}
for (auto const& header_bt : headers) {
if (header_bt.Value.empty()) {
continue;
{
cmGeneratedFileStream file(
filename_tmp, false,
this->GetGlobalGenerator()->GetMakefileEncoding());
file << "/* generated by CMake */\n\n";
if (pchPrologue) {
file << pchPrologue << "\n";
}
if (header_bt.Value[0] == '<' || header_bt.Value[0] == '"') {
file << "#include " << header_bt.Value << "\n";
} else {
file << "#include \"" << header_bt.Value << "\"\n";
if (this->GetGlobalGenerator()->IsXcode()) {
file << "#ifndef CMAKE_SKIP_PRECOMPILE_HEADERS\n";
}
if (language == "CXX") {
file << "#ifdef __cplusplus\n";
}
for (auto const& header_bt : headers) {
if (header_bt.Value.empty()) {
continue;
}
if (header_bt.Value[0] == '<' || header_bt.Value[0] == '\"') {
file << "#include " << header_bt.Value << "\n";
} else {
file << "#include \"" << header_bt.Value << "\"\n";
}
}
if (language == "CXX") {
file << "#endif // __cplusplus\n";
}
if (this->GetGlobalGenerator()->IsXcode()) {
file << "#endif // CMAKE_SKIP_PRECOMPILE_HEADERS\n";
}
if (pchEpilogue) {
file << pchEpilogue << "\n";
}
}
if (language == "CXX") {
file << "#endif // __cplusplus\n";
}
if (this->GetGlobalGenerator()->IsXcode()) {
file << "#endif // CMAKE_SKIP_PRECOMPILE_HEADERS\n";
}
if (pchEpilogue) {
file << pchEpilogue << "\n";
}
cmSystemTools::MoveFileIfDifferent(filename_tmp, filename);
}
cmSystemTools::MoveFileIfDifferent(filename_tmp, filename);
}
return inserted.first->second;
}
......@@ -3440,8 +3451,18 @@ std::string cmGeneratorTarget::GetPchSource(const std::string& config,
return std::string();
}
std::string& filename = inserted.first->second;
filename = cmStrCat(this->LocalGenerator->GetCurrentBinaryDirectory(),
"/CMakeFiles/", this->GetName(), ".dir/cmake_pch");
const cmGeneratorTarget* generatorTarget = this;
const char* pchReuseFrom =
generatorTarget->GetProperty("PRECOMPILE_HEADERS_REUSE_FROM");
if (pchReuseFrom) {
generatorTarget =
this->GetGlobalGenerator()->FindGeneratorTarget(pchReuseFrom);
}
filename =
cmStrCat(generatorTarget->LocalGenerator->GetCurrentBinaryDirectory(),
"/CMakeFiles/", generatorTarget->GetName(), ".dir/cmake_pch");
// For GCC the source extension will be tranformed into .h[xx].gch
if (!this->Makefile->IsOn("CMAKE_LINK_PCH")) {
......@@ -3449,12 +3470,40 @@ std::string cmGeneratorTarget::GetPchSource(const std::string& config,
} else {
filename += ((language == "C") ? ".c" : ".cxx");
}
const std::string filename_tmp = cmStrCat(filename, ".tmp");
{
cmGeneratedFileStream file(filename_tmp);
file << "/* generated by CMake */\n";
if (!pchReuseFrom) {
{
cmGeneratedFileStream file(filename_tmp);
file << "/* generated by CMake */\n";
}
cmSystemTools::MoveFileIfDifferent(filename_tmp, filename);
}
cmSystemTools::MoveFileIfDifferent(filename_tmp, filename);
}
return inserted.first->second;
}
std::string cmGeneratorTarget::GetPchFileObject(const std::string& config,
const std::string& language)
{
if (language != "C" && language != "CXX") {
return std::string();
}
const auto inserted =
this->PchObjectFiles.insert(std::make_pair(language + config, ""));
if (inserted.second) {
const std::string pchSource = this->GetPchSource(config, language);
if (pchSource.empty()) {
return std::string();
}
std::string& filename = inserted.first->second;
this->AddSource(pchSource, true);
auto pchSf = this->Makefile->GetOrCreateSource(
pchSource, false, cmSourceFileLocationKind::Known);
filename = cmStrCat(this->ObjectDirectory, this->GetObjectName(pchSf));
}
return inserted.first->second;
}
......
......@@ -462,6 +462,8 @@ public:
const std::string& language) const;
std::string GetPchSource(const std::string& config,
const std::string& language) const;
std::string GetPchFileObject(const std::string& config,
const std::string& language);
bool IsSystemIncludeDirectory(const std::string& dir,
const std::string& config,
......@@ -880,6 +882,7 @@ private:
mutable std::set<std::string> LinkImplicitNullProperties;
mutable std::map<std::string, std::string> PchHeaders;
mutable std::map<std::string, std::string> PchSources;
mutable std::map<std::string, std::string> PchObjectFiles;
void ExpandLinkItems(std::string const& prop, std::string const& value,
std::string const& config,
......
......@@ -4,7 +4,9 @@
#include "cmAlgorithms.h"
#include "cmComputeLinkInformation.h"
#include "cmCustomCommand.h"
#include "cmCustomCommandGenerator.h"
#include "cmCustomCommandLines.h"
#include "cmGeneratedFileStream.h"
#include "cmGeneratorExpression.h"
#include "cmGeneratorExpressionEvaluationFile.h"
......@@ -2255,23 +2257,124 @@ void cmLocalGenerator::AddPchDependencies(cmGeneratorTarget* target,
return;
}
const char* pchReuseFrom =
target->GetProperty("PRECOMPILE_HEADERS_REUSE_FROM");
auto pch_sf = this->Makefile->GetOrCreateSource(
pchSource, false, cmSourceFileLocationKind::Known);
std::string pchFile = pchHeader;
if (!this->GetGlobalGenerator()->IsXcode()) {
if (!pchReuseFrom) {
target->AddSource(pchSource, true);
}
// Exclude the pch files from linking
if (this->Makefile->IsOn("CMAKE_LINK_PCH")) {
cmSystemTools::ReplaceString(pchFile, (lang == "C" ? ".h" : ".hxx"),
pchExtension);
pch_sf->SetProperty("OBJECT_OUTPUTS", pchFile.c_str());
auto replaceExtension = [](const std::string& str,
const std::string& ext) -> std::string {
auto dot_pos = str.rfind('.');
std::string result;
if (dot_pos != std::string::npos) {
result = str.substr(0, dot_pos);
}
result += ext;
return result;
};
if (!pchReuseFrom) {
std::string pchSourceObj = target->GetPchFileObject(config, lang);
pchFile = replaceExtension(pchSourceObj, pchExtension);
pch_sf->SetProperty("OBJECT_OUTPUTS", pchFile.c_str());
} else {
auto reuseTarget =
this->GlobalGenerator->FindGeneratorTarget(pchReuseFrom);
if (this->Makefile->IsOn("CMAKE_PCH_COPY_COMPILE_PDB")) {
const std::string pdb_prefix =
this->GetGlobalGenerator()->IsMultiConfig()
? cmStrCat(this->GlobalGenerator->GetCMakeCFGIntDir(), "/")
: "";
const std::string target_compile_pdb_dir =
cmStrCat(target->GetLocalGenerator()->GetCurrentBinaryDirectory(),
"/", target->GetName(), ".dir/");
const std::string copy_script =
cmStrCat(target_compile_pdb_dir, "copy_idb_pdb.cmake");
cmGeneratedFileStream file(copy_script);
file << "# CMake generated file\n";
for (auto extension : { ".pdb", ".idb" }) {
const std::string from_file = cmStrCat(
reuseTarget->GetLocalGenerator()->GetCurrentBinaryDirectory(),
"/", pchReuseFrom, ".dir/${PDB_PREFIX}", pchReuseFrom,
extension);
const std::string to_dir = cmStrCat(
target->GetLocalGenerator()->GetCurrentBinaryDirectory(), "/",
target->GetName(), ".dir/${PDB_PREFIX}");
file << "if (EXISTS \"" << from_file << "\")\n";
file << " file(COPY \"" << from_file << "\""
<< " DESTINATION \"" << to_dir << "\")\n";
file << "endif()\n";
}
cmCustomCommandLines commandLines;
cmCustomCommandLine currentLine;
currentLine.push_back(cmSystemTools::GetCMakeCommand());
currentLine.push_back(cmStrCat("-DPDB_PREFIX=", pdb_prefix));
currentLine.push_back("-P");
currentLine.push_back(copy_script);
commandLines.push_back(std::move(currentLine));
const std::string no_main_dependency;
const std::vector<std::string> no_deps;
const char* no_message = "";
const char* no_current_dir = nullptr;
std::vector<std::string> no_byproducts;
std::vector<std::string> outputs;
outputs.push_back(cmStrCat(target_compile_pdb_dir, pdb_prefix,
pchReuseFrom, ".pdb"));
if (this->GetGlobalGenerator()->IsMultiConfig()) {
this->Makefile->AddCustomCommandToTarget(
target->GetName(), outputs, no_deps, commandLines,
cmTarget::PRE_BUILD, no_message, no_current_dir);
} else {
cmImplicitDependsList no_implicit_depends;
cmSourceFile* copy_rule = this->Makefile->AddCustomCommandToOutput(
outputs, no_byproducts, no_deps, no_main_dependency,
no_implicit_depends, commandLines, no_message, no_current_dir);
if (copy_rule) {
target->AddSource(copy_rule->ResolveFullPath());
}
}
target->Target->SetProperty("COMPILE_PDB_OUTPUT_DIRECTORY",
target_compile_pdb_dir.c_str());
}
std::string pchSourceObj = reuseTarget->GetPchFileObject(config, lang);
// Link to the pch object file
target->Target->SetProperty(
"LINK_FLAGS",
this->ConvertToOutputFormat(pchSourceObj, SHELL).c_str());
pchFile = replaceExtension(pchSourceObj, pchExtension);
}
} else {
pchFile += pchExtension;
pch_sf->SetProperty("PCH_EXTENSION", pchExtension.c_str());
}
target->AddSource(pchSource, true);
for (auto& str : { std::ref(useOptionList), std::ref(createOptionList) }) {
cmSystemTools::ReplaceString(str, "<PCH_HEADER>", pchHeader);
cmSystemTools::ReplaceString(str, "<PCH_FILE>", pchFile);
......
......@@ -1085,6 +1085,7 @@ void cmTarget::SetProperty(const std::string& prop, const char* value)
MAKE_STATIC_PROP(COMPILE_FEATURES);
MAKE_STATIC_PROP(COMPILE_OPTIONS);
MAKE_STATIC_PROP(PRECOMPILE_HEADERS);
MAKE_STATIC_PROP(PRECOMPILE_HEADERS_REUSE_FROM);
MAKE_STATIC_PROP(CUDA_PTX_COMPILATION);
MAKE_STATIC_PROP(EXPORT_NAME);
MAKE_STATIC_PROP(IMPORTED_GLOBAL);
......@@ -1231,6 +1232,41 @@ void cmTarget::SetProperty(const std::string& prop, const char* value)
<< impl->Name << "\")\n";
impl->Makefile->IssueMessage(MessageType::FATAL_ERROR, e.str());
return;
} else if (prop == propPRECOMPILE_HEADERS_REUSE_FROM) {
if (this->GetProperty("PRECOMPILE_HEADERS")) {
std::ostringstream e;
e << "PRECOMPILE_HEADERS property is already set on target (\""
<< impl->Name << "\")\n";
impl->Makefile->IssueMessage(MessageType::FATAL_ERROR, e.str());
return;
}
auto reusedTarget =
impl->Makefile->GetCMakeInstance()->GetGlobalGenerator()->FindTarget(
value);
if (!reusedTarget) {
const std::string e(
"PRECOMPILE_HEADERS_REUSE_FROM set with non existing target");
impl->Makefile->IssueMessage(MessageType::FATAL_ERROR, e);
return;
}
std::string reusedFrom = reusedTarget->GetSafeProperty(prop);
if (reusedFrom.empty()) {
reusedFrom = value;
}
impl->Properties.SetProperty(prop, reusedFrom.c_str());
reusedTarget->SetProperty("COMPILE_PDB_NAME", reusedFrom.c_str());
reusedTarget->SetProperty("COMPILE_PDB_OUTPUT_DIRECTORY",
cmStrCat(reusedFrom, ".dir/").c_str());
for (auto p : { "COMPILE_PDB_NAME", "PRECOMPILE_HEADERS",
"INTERFACE_PRECOMPILE_HEADERS" }) {
this->SetProperty(p, reusedTarget->GetProperty(p));
}
this->AddUtility(reusedFrom, impl->Makefile);
} else {
impl->Properties.SetProperty(prop, value);
}
......@@ -1308,6 +1344,14 @@ void cmTarget::AppendProperty(const std::string& prop, const char* value,
impl->LinkDirectoriesBacktraces.push_back(lfbt);
}
} else if (prop == "PRECOMPILE_HEADERS") {
if (this->GetProperty("PRECOMPILE_HEADERS_REUSE_FROM")) {
std::ostringstream e;
e << "PRECOMPILE_HEADERS_REUSE_FROM property is already set on target "
"(\""
<< impl->Name << "\")\n";
impl->Makefile->IssueMessage(MessageType::FATAL_ERROR, e.str());
return;
}
if (value && *value) {
impl->PrecompileHeadersEntries.emplace_back(value);
cmListFileBacktrace lfbt = impl->Makefile->GetBacktrace();
......
......@@ -10,7 +10,7 @@
bool cmTargetPrecompileHeadersCommand::InitialPass(
std::vector<std::string> const& args, cmExecutionStatus&)
{
return this->HandleArguments(args, "PRECOMPILE_HEADERS");
return this->HandleArguments(args, "PRECOMPILE_HEADERS", PROCESS_REUSE_FROM);
}
void cmTargetPrecompileHeadersCommand::HandleMissingTarget(
......
......@@ -65,6 +65,19 @@ bool cmTargetPropCommandBase::HandleArguments(
++argIndex;
}
if ((flags & PROCESS_REUSE_FROM) && args[argIndex] == "REUSE_FROM") {
if (args.size() != 3) {
this->SetError("called with incorrect number of arguments");
return false;
}
++argIndex;
this->Target->SetProperty("PRECOMPILE_HEADERS_REUSE_FROM",
args[argIndex].c_str());
++argIndex;
}
this->Property = prop;
while (argIndex < args.size()) {
......
......@@ -17,9 +17,10 @@ class cmTargetPropCommandBase : public cmCommand
public:
enum ArgumentFlags
{
NO_FLAGS = 0,
PROCESS_BEFORE = 1,
PROCESS_SYSTEM = 2
NO_FLAGS = 0x0,
PROCESS_BEFORE = 0x1,
PROCESS_SYSTEM = 0x2,
PROCESS_REUSE_FROM = 0x3
};
bool HandleArguments(std::vector<std::string> const& args,
......
cmake_minimum_required(VERSION 3.15)
project(PchReuseFrom C)
add_library(empty empty.c)
target_precompile_headers(empty PUBLIC
<stdio.h>
<string.h>
)
target_include_directories(empty PUBLIC include)
add_library(foo foo.c)
target_include_directories(foo PUBLIC include)
target_precompile_headers(foo REUSE_FROM empty)
add_executable(foobar foobar.c)
target_link_libraries(foobar foo )
set_target_properties(foobar PROPERTIES PRECOMPILE_HEADERS_REUSE_FROM foo)
enable_testing()
add_test(NAME foobar COMMAND foobar)
^(|Warning #670: precompiled header file [^
]* was not generated in this directory)$
cmake_minimum_required(VERSION 3.15)
project(PchReuseFromSubdir C)
add_library(empty empty.c)
target_precompile_headers(empty PUBLIC
<stdio.h>
<string.h>
)
target_include_directories(empty PUBLIC include)
add_library(foo foo.c)
target_include_directories(foo PUBLIC include)
target_precompile_headers(foo REUSE_FROM empty)
subdirs(subdir)
enable_testing()
add_test(NAME foobar COMMAND foobar)
......@@ -16,3 +16,5 @@ run_cmake(DisabledPch)
run_test(PchInterface)
run_cmake(PchPrologueEpilogue)
run_test(SkipPrecompileHeaders)
run_test(PchReuseFrom)
run_test(PchReuseFromSubdir)
#ifndef foo_h
#define foo_h
extern int foo();
int foo(void);
#endif
add_executable(foobar ../foobar.c)
target_link_libraries(foobar foo )
set_target_properties(foobar PROPERTIES PRECOMPILE_HEADERS_REUSE_FROM foo)
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment