AUTOMOC on the Visual Studio generators: two extra projects per target since 4.4.0 — could this be made configurable?

Hi all,

We maintain a fairly large Qt-based software, with around 790 Visual Studio projects.
Moving CMake from 4.3.4 to 4.4.0 took the solution to roughly 2210.
Every target with AUTOMOC enabled now gets two extra ones, <target>_autogen and <target>_autogen_timestamp_deps.

I did some digging before posting.
It comes from 680fbb112a (MR !11844), which enables depfile support for the Visual Studio and Xcode generators.
A PRE_BUILD event cannot carry depfile dependencies, so usePRE_BUILD gets cleared and the autogen step becomes a separate target instead of a pre-build step on the origin target.

To be clear, I am not reporting a bug. The commit message is explicit about the trade-off, and the incremental-correctness win is real. We would rather have moc rerun when a transitively included header changes.
The catch is the IDE cost. At around 2200 projects, loading and navigating the solution in Visual Studio gets noticeably slower. That is the exact concern recorded in the comment that still sits above the affected code:

// Under VS use a PRE_BUILD event instead of a separate target to
// reduce the number of targets loaded into the IDE.

As far as I can tell there is no way to keep the old path. useDepfile is decided from the generator name and the Qt version alone, and nothing is exposed through a variable, a target property or a policy.
Hence my question.
Would a CMAKE_AUTOGEN_USE_DEPFILE variable with a matching AUTOGEN_USE_DEPFILE target property be conceivable, with the current behaviour kept as the default?
A per-target property would suit us best: we could enable depfiles on the few targets where accuracy matters and leave the rest alone.

There seems to be precedent nearby. AUTOGEN_ORIGIN_DEPENDS, AUTOGEN_USE_SYSTEM_INCLUDE, AUTOGEN_PARALLEL and AUTOGEN_BETTER_GRAPH_MULTI_CONFIG are each exposed as a variable plus a target property.
I would rather ask for a durable knob than a policy.
The trade-off between incremental accuracy and IDE target count is permanent, not a legacy behaviour waiting to be phased out.

Two side notes:

  • The change is confined to the 4.4 series. 680fbb112a is reachable from v4.4.0-rc1 onward and from no v4.3.* tag. Since the 4.3 branch ends at 4.3.4, staying there is a freeze rather than a workaround.
  • The 4.4.2 docs still describe the old behaviour. The <ORIGIN>\_autogen_timestamp_deps target restricts the target to Ninja and Makefiles, and Visual Studio Generators still presents PRE_BUILD as the normal case, with three exceptions, none of them the depfile condition.

I tried the issue tracker first, but my account is new and got flagged as spam. :confused:
Happy to move this over there if that is the better place.

What I measured

A single static library with one Q_OBJECT class, Visual Studio 17 2022, x64, Qt from vcpkg.

CMake Qt autogen projects added PreBuildEvent in thing.vcxproj
4.3.4 5.15.12 0 yes
4.4.0 5.15.12 2 no
4.3.4 6.6.1 0 yes
4.4.0 6.6.1 2 no
4.3.4 6.11.1 0 yes
4.4.0 6.11.1 2 no

4.4.2 behaves like 4.4.0 (tested with Qt 6).

Reproducer

CMakeLists.txt:

# Project count generated by the Visual Studio generator:
#   CMake 4.3.4 -> 3 (CompilerIdCXX, thing, VCTargetsPath)
#   CMake 4.4.0 -> 5 (CompilerIdCXX, thing, thing_autogen, thing_autogen_timestamp_deps, VCTargetsPath)

cmake_minimum_required(VERSION 3.28)
project(autogen_vs_repro LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_AUTOMOC ON)

find_package(Qt5 QUIET COMPONENTS Core)
if (Qt5_FOUND)
  set(QT_CORE_TARGET Qt5::Core)
  set(QT_VERSION ${Qt5_VERSION})
else()
  find_package(Qt6 REQUIRED COMPONENTS Core)
  set(QT_CORE_TARGET Qt6::Core)
  set(QT_VERSION ${Qt6_VERSION})
endif()

add_library(thing STATIC thing.cpp thing.h)
target_link_libraries(thing PRIVATE ${QT_CORE_TARGET})

message(STATUS "CMake version ..... ${CMAKE_VERSION}")
message(STATUS "Qt version ........ ${QT_VERSION}")
message(STATUS "Generator ......... ${CMAKE_GENERATOR}")

thing.h:

#pragma once
#include <QObject>

class Thing : public QObject
{
    Q_OBJECT
public:
    explicit Thing(QObject \*parent = nullptr);
};

thing.cpp:

#include "thing.h"

Thing::Thing(QObject \*parent)
    : QObject(parent)
{
}

Configure with:

cmake -S . -B build -G "Visual Studio 17 2022" -A x64 -DCMAKE_TOOLCHAIN_FILE=<vcpkg>/scripts/buildsystems/vcpkg.cmake -DVCPKG_TARGET_TRIPLET=x64-windows

Any Qt installation works. The vcpkg toolchain is simply how ours is provided.

Cc: @jobor @alcroito

1 Like

This is indeed quite surprising and no clear mention in the changelog.

It seems there is the `AUTOGEN_TARGET_FOLDER` to put all these new projects into one folder: https://cmake.org/cmake/help/latest/prop_gbl/AUTOGEN_TARGETS_FOLDER.html

Thanks for the pointer.

We do use AUTOGEN_TARGETS_FOLDER and it does keep the solution navigable.
It only groups the projects though, it doesn not reduce their number, so the load and navigation cost stays.

Worth noting: we also relied on that folder as a diagnostic.
Under 4.3.4, an _autogentarget showing up there was a signal. It meant that something had disabled the PRE_BUILD path for that target, usually a generated file being parsed by autogen.

Now that every AUTOMOC target has one, that signal is gone.

It would of course be best if we can find some way to reduce the IDE load, while keeping the new dependency tracking.

If that ends up impossible, having an opt out probably makes sense, although that would come at the maintenance cost of never being able to remove the old code path that doesn’t use dep files.

Either way, I think you should file two bug reports: one for the IDE load, with a possible solution being opting out, and another for the outdated documentation.

1 Like

Thank you, I just did.

For reference:

Slightly related: when I updated from CMake 3 to 4, I suddenly got a lot more targets. I think this was the C++ modules dependency scanning.
This slowed the build down a bit, but it caused another problem for the developers under Windows: when cancelling the build, this often caused corrupted ninja build files. Which had the effect that ninja did not run anymore (but crashed), and more or less a full rebuild was needed.
I disabled CMAKE_CXX_SCAN_FOR_MODULES, and the number of targets went down again, and Windows developers don’t complain about broken build files anymore.

I didn’t investigate much, but I assume that during the build, when the scanning for modules happens, ninja files are written. When Visual Studio cancels a build, it seems that when this happens while a file is written, this file can get corrupted.

Is this a known issue ?

@neundorf Please re-post that as a new discussion thread. It is not related to autogen targets, which is what this thread is about.