Skip to content
GitLab
Projects Groups Snippets
  • /
  • Help
    • Help
    • Support
    • Community forum
    • Submit feedback
    • Contribute to GitLab
  • Sign in / Register
  • CMake CMake
  • Project information
    • Project information
    • Activity
    • Labels
    • Members
  • Repository
    • Repository
    • Files
    • Commits
    • Branches
    • Tags
    • Contributors
    • Graph
    • Compare
  • Issues 4,100
    • Issues 4,100
    • List
    • Boards
    • Service Desk
    • Milestones
  • Merge requests 12
    • Merge requests 12
  • CI/CD
    • CI/CD
    • Pipelines
    • Jobs
    • Schedules
  • Deployments
    • Deployments
    • Releases
  • Packages and registries
    • Packages and registries
    • Container Registry
  • Analytics
    • Analytics
    • Value stream
    • CI/CD
    • Repository
  • External wiki
    • External wiki
  • Activity
  • Graph
  • Create a new issue
  • Jobs
  • Commits
  • Issue Boards
Collapse sidebar
  • CMakeCMake
  • CMakeCMake
  • Issues
  • #20712
Closed
Open
Issue created May 15, 2020 by Christian Fersch@Chronial

VS: AllConfigSources cache can break PCH compilation

Minimal example:

project(Test)
add_library(target_a source_a.cpp)
add_library(target_b SHARED source_b.cpp)
target_precompile_headers(target_b PRIVATE header.h)
target_link_libraries(target_a "$<TARGET_LINKER_FILE:target_b>")

Generator: Visual Studio 16 2019

The generated target_b.vcxproj lacks the ClCompile entry for cmake_pch.cxx file which would normally cause the creation of the PCH. This causes the build to fail. Removing the last line fixes the issue.

Analysis

After long investigation, I found this issue to be caused by this chain of events:

  • cmGlobalGenerator::AddAutomaticSources() calls cmLocalGenerator::AddPchDependencies() on target_a.
  • To determine the list of languages that need PCHs, AddPchDependencies() calls cmGeneratorTarget::GetSourceFiles().
  • This function causes a lookup of target_a's dependencies' INTERFACE_SOURCES, which triggers the evaluation of the generator expression.
  • In order to get the ArtifactType argument for cmGeneratorTarget::GetFullPath(), the genexp evaluator calls cmGeneratorTarget::HasImportLibrary() on target_b.
  • On Windows, HasImportLibrary() checks whether a shared library is managed-only.
  • cmGeneratorTarget::GetManagedType() calls IsCSharpOnly(), calling GetAllConfigCompileLangues(), calling GetAllConfigSources() – still on target_b
  • This causes cmGeneratorTarget(target_a)->AllConfigSources to be calculated before target_b's cmake_pch.cxx is added to its source list.

Now, target_b is processed:

  • cmGlobalGenerator::AddAutomaticSources() calls cmLocalGenerator::AddPchDependencies() on target_b. A cmake_pch.cxx is added to target_b's sources list, but AllConfigSources is not touched.
  • This cache is never cleared. So later on in the generation stage, target_b's cmake_pch.cxx is not written to the generated project.
  • This happens in cmVisualStudio10TargetGenerator::WriteSource(), which uses GetAllConfigSources() to get the list of source files to be compiled.

Patch

I don't know the code enough to be sure that this is the correct fix for the issue, but this change stops the issue:

diff --git a/Source/cmGeneratorTarget.cxx b/Source/cmGeneratorTarget.cxx
index a44126c13c..e25be6bd05 100644
--- a/Source/cmGeneratorTarget.cxx
+++ b/Source/cmGeneratorTarget.cxx
@@ -694,6 +694,7 @@ const char* cmGeneratorTarget::GetFileSuffixInternal(
 void cmGeneratorTarget::ClearSourcesCache()
 {
   this->KindedSourcesMap.clear();
+  this->AllConfigSources.clear();
   this->LinkImplementationLanguageIsContextDependent = true;
   this->Objects.clear();
   this->VisitedConfigsForObjects.clear();

ClearSourcesCache() is called by AddAutomaticSources() after all the sources have been added, presumably to prevent exactly this kind of issue?

History

A git bisect showed that this started with bcaecf6b. There, the call from IsCSharpOnly() to GetAllConfigCompileLanguages() was added, completing the chain detailed above. This makes 3.17.0 the first affected release.

Misc

This is the cause for #20702 (closed). As my colleague explained there, we saw a similar problem with an older version, but struggled to reproduce it. I think that was also before we added the generator expression in our target_link_library call. It doesn't seem unlikely that other paths through the code could cause a similar issue.

The generator expression in the target_link_library call might look odd because it's just a convoluted way to link against target_b. We do this in our codebase because we need target_b's DLL to be loaded as the very first DLL in our process. This can be achieved on windows by listing target_b's lib first on the linker command line. But cmake does a toposort on the linker inputs, so if a target_c that depends on target_b, target_b.lib is moved to the back of the linker command line. So we do this generator expresion trick to prevent cmake from seeing this dependency and moving it in the toposort. I couldn't find one, but does cmake have a prettier way of implementing this?

Edited May 15, 2020 by Christian Fersch
To upload designs, you'll need to enable LFS and have an admin enable hashed storage. More information
Assignee
Assign to
Time tracking