From 9ac3503d30ed46c63c6a733304072d316f0042cf Mon Sep 17 00:00:00 2001 From: Joerg Bornemann Date: Tue, 1 Sep 2020 15:10:41 +0200 Subject: AutoMoc: Re-run moc if a dependency is missing AutoMoc uses the moc-emitted dependency file of Qt 5.15 to track dependencies. Such a dependency may well live outside the project and can vanish, for example when installing a new compiler version. This situation was detected before, but merely a warning was issued. Now, we're considering a generated file as out of date if a dependency is missing and re-generate it. We also have to remove the missing dependency from the ParseCache. Otherwise the AUTOMOC target for all generators other than Ninja will always be out of date. The ParseCacheChanged flag had to be made atomic, because we're potentially accessing it from multiple threads. The dependencies vector itself is not vulnerable in this regard, because there's one vector per file, and we're accessing exactly one ParseCacheT::FileHandleT per thread. Fixes: #21136 --- Source/cmQtAutoMocUic.cxx | 22 ++++-- .../RerunMocOnMissingDependency/CMakeLists.txt | 80 ++++++++++++++++++++++ .../MocOnMissingDependency/CMakeLists.txt.in | 7 ++ .../MocOnMissingDependency/inc1/foo.h | 2 + .../MocOnMissingDependency/inc2/foo.h | 2 + .../MocOnMissingDependency/main.cpp | 9 +++ .../MocOnMissingDependency/myobject.cpp | 6 ++ .../MocOnMissingDependency/myobject.h | 10 +++ Tests/QtAutogen/Tests.cmake | 1 + 9 files changed, 132 insertions(+), 7 deletions(-) create mode 100644 Tests/QtAutogen/RerunMocOnMissingDependency/CMakeLists.txt create mode 100644 Tests/QtAutogen/RerunMocOnMissingDependency/MocOnMissingDependency/CMakeLists.txt.in create mode 100644 Tests/QtAutogen/RerunMocOnMissingDependency/MocOnMissingDependency/inc1/foo.h create mode 100644 Tests/QtAutogen/RerunMocOnMissingDependency/MocOnMissingDependency/inc2/foo.h create mode 100644 Tests/QtAutogen/RerunMocOnMissingDependency/MocOnMissingDependency/main.cpp create mode 100644 Tests/QtAutogen/RerunMocOnMissingDependency/MocOnMissingDependency/myobject.cpp create mode 100644 Tests/QtAutogen/RerunMocOnMissingDependency/MocOnMissingDependency/myobject.h diff --git a/Source/cmQtAutoMocUic.cxx b/Source/cmQtAutoMocUic.cxx index 71c288c..9cb172b 100644 --- a/Source/cmQtAutoMocUic.cxx +++ b/Source/cmQtAutoMocUic.cxx @@ -192,7 +192,7 @@ public: { public: // -- Parse Cache - bool ParseCacheChanged = false; + std::atomic ParseCacheChanged = ATOMIC_VAR_INIT(false); cmFileTime ParseCacheTime; ParseCacheT ParseCache; @@ -1777,16 +1777,24 @@ bool cmQtAutoMocUicT::JobProbeDepsMocT::Probe(MappingT const& mapping, { // Check dependency timestamps std::string const sourceDir = SubDirPrefix(sourceFile); - for (std::string const& dep : mapping.SourceFile->ParseData->Moc.Depends) { + auto& dependencies = mapping.SourceFile->ParseData->Moc.Depends; + for (auto it = dependencies.begin(); it != dependencies.end(); ++it) { + auto& dep = *it; + // Find dependency file auto const depMatch = FindDependency(sourceDir, dep); if (depMatch.first.empty()) { - Log().Warning(GenT::MOC, - cmStrCat(MessagePath(sourceFile), " depends on ", - MessagePath(dep), - " but the file does not exist.")); - continue; + if (reason != nullptr) { + *reason = + cmStrCat("Generating ", MessagePath(outputFile), " from ", + MessagePath(sourceFile), ", because its dependency ", + MessagePath(dep), " vanished."); + } + dependencies.erase(it); + BaseEval().ParseCacheChanged = true; + return true; } + // Test if dependency file is older if (outputFileTime.Older(depMatch.second)) { if (reason != nullptr) { diff --git a/Tests/QtAutogen/RerunMocOnMissingDependency/CMakeLists.txt b/Tests/QtAutogen/RerunMocOnMissingDependency/CMakeLists.txt new file mode 100644 index 0000000..c5811eb --- /dev/null +++ b/Tests/QtAutogen/RerunMocOnMissingDependency/CMakeLists.txt @@ -0,0 +1,80 @@ +# This test checks whether a missing dependency of the moc output triggers an AUTOMOC re-run. + +cmake_minimum_required(VERSION 3.10) +project(RerunMocOnMissingDependency) +include("../AutogenCoreTest.cmake") + +# Create an executable to generate a clean target +set(main_source "${CMAKE_CURRENT_BINARY_DIR}/generated_main.cpp") +file(WRITE "${main_source}" "int main() {}") +add_executable(exe "${main_source}") + +# Utility variables +set(testProjectTemplateDir "${CMAKE_CURRENT_SOURCE_DIR}/MocOnMissingDependency") +set(testProjectSrc "${CMAKE_CURRENT_BINARY_DIR}/MocOnMissingDependency") +set(testProjectBinDir "${CMAKE_CURRENT_BINARY_DIR}/MocOnMissingDependency-build") +if(DEFINED Qt5Core_VERSION AND Qt5Core_VERSION VERSION_GREATER_EQUAL "5.15.0") + set(moc_depfiles_supported TRUE) +else() + set(moc_depfiles_supported FALSE) +endif() + +# Utility macros +macro(sleep) + message(STATUS "Sleeping for a few seconds.") + execute_process(COMMAND "${CMAKE_COMMAND}" -E sleep 1) +endmacro() + +macro(rebuild buildName) + message(STATUS "Starting build ${buildName}.") + execute_process(COMMAND "${CMAKE_COMMAND}" --build . WORKING_DIRECTORY "${testProjectBinDir}" + RESULT_VARIABLE result OUTPUT_VARIABLE output) + if (result) + message(FATAL_ERROR "Build ${buildName} failed.") + else() + message(STATUS "Build ${buildName} finished.") + endif() +endmacro() + +# Create the test project from the template +file(COPY "${testProjectTemplateDir}" DESTINATION "${CMAKE_CURRENT_BINARY_DIR}") +configure_file("${testProjectTemplateDir}/CMakeLists.txt.in" "${testProjectSrc}/CMakeLists.txt" @ONLY) + +# Initial build +file(REMOVE_RECURSE "${testProjectBinDir}") +try_compile(MOC_RERUN + "${testProjectBinDir}" + "${testProjectSrc}" + MocOnMissingDependency + CMAKE_FLAGS "-DQT_TEST_VERSION=${QT_TEST_VERSION}" + "-DCMAKE_AUTOGEN_VERBOSE=ON" + "-DQT_QMAKE_EXECUTABLE:FILEPATH=${QT_QMAKE_EXECUTABLE}" + OUTPUT_VARIABLE output +) +if (NOT MOC_RERUN) + message(FATAL_ERROR "Initial build of mocOnMissingDependency failed. Output: ${output}") +endif() + +# Sleep to ensure new timestamps +sleep() + +if(moc_depfiles_supported) + # Remove the dependency inc1/foo.h and build again. + # We expect that the moc_XXX.cpp file gets re-generated. But only if we have depfile support. + file(REMOVE_RECURSE "${testProjectSrc}/inc1") + rebuild(2) + if(NOT output MATCHES "AutoMoc: Generating \"[^\"]*moc_myobject.cpp\"") + message(FATAL_ERROR "moc_myobject.cpp was not re-generated " + "after removing one of its dependencies") + endif() +endif() + +# Sleep to ensure new timestamps +sleep() + +# The next build should *not* re-renerate any moc outputs +rebuild(3) +if(output MATCHES "AutoMoc: Generating") + message(FATAL_ERROR "moc_myobject.cpp was not re-generated " + "after removing one of its dependencies") +endif() diff --git a/Tests/QtAutogen/RerunMocOnMissingDependency/MocOnMissingDependency/CMakeLists.txt.in b/Tests/QtAutogen/RerunMocOnMissingDependency/MocOnMissingDependency/CMakeLists.txt.in new file mode 100644 index 0000000..2155f45 --- /dev/null +++ b/Tests/QtAutogen/RerunMocOnMissingDependency/MocOnMissingDependency/CMakeLists.txt.in @@ -0,0 +1,7 @@ +cmake_minimum_required(VERSION 3.18) +project(MocOnMissingDependency) +include("@CMAKE_CURRENT_LIST_DIR@/../AutogenCoreTest.cmake") +set(CMAKE_AUTOMOC ON) +add_executable(MocOnMissingDependency main.cpp myobject.cpp) +target_include_directories(MocOnMissingDependency PRIVATE inc1 inc2) +target_link_libraries(MocOnMissingDependency PRIVATE ${QT_QTCORE_TARGET}) diff --git a/Tests/QtAutogen/RerunMocOnMissingDependency/MocOnMissingDependency/inc1/foo.h b/Tests/QtAutogen/RerunMocOnMissingDependency/MocOnMissingDependency/inc1/foo.h new file mode 100644 index 0000000..cd8b2f9 --- /dev/null +++ b/Tests/QtAutogen/RerunMocOnMissingDependency/MocOnMissingDependency/inc1/foo.h @@ -0,0 +1,2 @@ + +#include diff --git a/Tests/QtAutogen/RerunMocOnMissingDependency/MocOnMissingDependency/inc2/foo.h b/Tests/QtAutogen/RerunMocOnMissingDependency/MocOnMissingDependency/inc2/foo.h new file mode 100644 index 0000000..cd8b2f9 --- /dev/null +++ b/Tests/QtAutogen/RerunMocOnMissingDependency/MocOnMissingDependency/inc2/foo.h @@ -0,0 +1,2 @@ + +#include diff --git a/Tests/QtAutogen/RerunMocOnMissingDependency/MocOnMissingDependency/main.cpp b/Tests/QtAutogen/RerunMocOnMissingDependency/MocOnMissingDependency/main.cpp new file mode 100644 index 0000000..36d51fa --- /dev/null +++ b/Tests/QtAutogen/RerunMocOnMissingDependency/MocOnMissingDependency/main.cpp @@ -0,0 +1,9 @@ +#include + +#include "myobject.h" + +int main(int argc, char* argv[]) +{ + MyObject obj; + return 0; +} diff --git a/Tests/QtAutogen/RerunMocOnMissingDependency/MocOnMissingDependency/myobject.cpp b/Tests/QtAutogen/RerunMocOnMissingDependency/MocOnMissingDependency/myobject.cpp new file mode 100644 index 0000000..7a15300 --- /dev/null +++ b/Tests/QtAutogen/RerunMocOnMissingDependency/MocOnMissingDependency/myobject.cpp @@ -0,0 +1,6 @@ +#include "myobject.h" + +MyObject::MyObject(QObject* parent) + : QObject(parent) +{ +} diff --git a/Tests/QtAutogen/RerunMocOnMissingDependency/MocOnMissingDependency/myobject.h b/Tests/QtAutogen/RerunMocOnMissingDependency/MocOnMissingDependency/myobject.h new file mode 100644 index 0000000..25176cc --- /dev/null +++ b/Tests/QtAutogen/RerunMocOnMissingDependency/MocOnMissingDependency/myobject.h @@ -0,0 +1,10 @@ +#pragma once + +#include + +class MyObject : public QObject +{ + Q_OBJECT +public: + MyObject(QObject* parent = 0); +}; diff --git a/Tests/QtAutogen/Tests.cmake b/Tests/QtAutogen/Tests.cmake index 0c7bd79..b1337d6 100644 --- a/Tests/QtAutogen/Tests.cmake +++ b/Tests/QtAutogen/Tests.cmake @@ -21,6 +21,7 @@ ADD_AUTOGEN_TEST(RccOnly rccOnly) ADD_AUTOGEN_TEST(RccSkipSource) ADD_AUTOGEN_TEST(RerunMocBasic) ADD_AUTOGEN_TEST(RerunMocOnAddFile) +ADD_AUTOGEN_TEST(RerunMocOnMissingDependency) ADD_AUTOGEN_TEST(RerunRccConfigChange) ADD_AUTOGEN_TEST(RerunRccDepends) ADD_AUTOGEN_TEST(SameName sameName) -- cgit v0.12