From 6e1c359fe9bee71c421a671108176d47fb415d93 Mon Sep 17 00:00:00 2001 From: Stephen Kelly Date: Wed, 17 Sep 2014 02:42:30 +0200 Subject: [PATCH] QtAutogen: Regenerate qrc files if their input changes (#15074) Get dependencies from the output of ``rcc --list`` if using Qt 5. Otherwise process the file in the same way as the qt4_add_resources macro. This does not work for RCC files which are generated. The cmake_autogen build step is implemented as a PRE_BUILD step of the target currently if possible, rather than a standalone custom target. This is to keep the number of (synthesized) custom targets that appear in the UI low, but in some cases it is necessary to fall back to a full custom target. Fall back to a full custom target for the VS builds if autorcc is used. --- Modules/AutogenInfo.cmake.in | 1 + Source/cmQtAutoGenerators.cxx | 275 +++++++++++++++++- Source/cmQtAutoGenerators.h | 10 + Tests/QtAutogen/CMakeLists.txt | 32 ++ .../QtAutogen/autorcc_depends/CMakeLists.txt | 24 ++ Tests/QtAutogen/autorcc_depends/res1.qrc | 5 + .../QtAutogen/autorcc_depends/res1_input.txt | 1 + Tests/QtAutogen/autorcc_depends/test_res1.cpp | 5 + 8 files changed, 345 insertions(+), 8 deletions(-) create mode 100644 Tests/QtAutogen/autorcc_depends/CMakeLists.txt create mode 100644 Tests/QtAutogen/autorcc_depends/res1.qrc create mode 100644 Tests/QtAutogen/autorcc_depends/res1_input.txt create mode 100644 Tests/QtAutogen/autorcc_depends/test_res1.cpp diff --git a/Modules/AutogenInfo.cmake.in b/Modules/AutogenInfo.cmake.in index 602b065c6..7d894202e 100644 --- a/Modules/AutogenInfo.cmake.in +++ b/Modules/AutogenInfo.cmake.in @@ -1,5 +1,6 @@ set(AM_SOURCES @_cpp_files@ ) set(AM_RCC_SOURCES @_rcc_files@ ) +set(AM_RCC_INPUTS @_qt_rcc_inputs@) set(AM_SKIP_MOC @_skip_moc@ ) set(AM_SKIP_UIC @_skip_uic@ ) set(AM_HEADERS @_moc_headers@ ) diff --git a/Source/cmQtAutoGenerators.cxx b/Source/cmQtAutoGenerators.cxx index 7b81f4f63..929cbc0be 100644 --- a/Source/cmQtAutoGenerators.cxx +++ b/Source/cmQtAutoGenerators.cxx @@ -166,6 +166,112 @@ static std::string getAutogenTargetDir(cmTarget const* target) return targetDir; } +std::string cmQtAutoGenerators::ListQt5RccInputs(cmSourceFile* sf, + cmTarget const* target, + std::vector& depends) +{ + std::string rccCommand = this->GetRccExecutable(target); + std::vector qrcEntries; + + std::vector command; + command.push_back(rccCommand); + command.push_back("--list"); + + std::string absFile = cmsys::SystemTools::GetRealPath( + sf->GetFullPath().c_str()); + + command.push_back(absFile); + + std::string output; + int retVal = 0; + bool result = cmSystemTools::RunSingleCommand(command, &output, + &retVal, 0, + cmSystemTools::OUTPUT_NONE); + if (!result || retVal) + { + std::cerr << "AUTOGEN: error: Rcc list process for " << sf->GetFullPath() + << " failed:\n" << output << std::endl; + return std::string(); + } + + std::istringstream ostr(output); + std::string oline; + while(std::getline(ostr, oline)) + { + if (oline.empty()) + { + // The output of rcc --list contains many empty lines. + continue; + } + if (cmHasLiteralPrefix(oline, "RCC: Error in")) + { + static std::string searchString = "Cannot find file '"; + + std::string::size_type pos = oline.find(searchString); + if (pos == std::string::npos) + { + std::cerr << "AUTOGEN: error: Rcc lists unparsable output " + << oline << std::endl; + return std::string(); + } + pos += searchString.length(); + std::string::size_type sz = oline.size() - pos - 1; + qrcEntries.push_back(oline.substr(pos, sz)); + } + else + { + qrcEntries.push_back(oline); + } + } + depends.insert(depends.end(), qrcEntries.begin(), qrcEntries.end()); + std::string entriesList; + const char* sep = ""; + for(std::vector::const_iterator it = qrcEntries.begin(); + it != qrcEntries.end(); ++it) + { + entriesList += sep; + entriesList += *it; + sep = "@list_sep@"; + } + return entriesList; +} + +std::string cmQtAutoGenerators::ListQt4RccInputs(cmSourceFile* sf, + std::vector& depends) +{ + const std::string qrcContents = ReadAll(sf->GetFullPath()); + + cmsys::RegularExpression fileMatchRegex("(]*>)"); + fileReplaceRegex.find(qrcEntry); + std::string tag = fileReplaceRegex.match(1); + + qrcEntry = qrcEntry.substr(tag.size()); + + if (!cmSystemTools::FileIsFullPath(qrcEntry.c_str())) + { + qrcEntry = sf->GetLocation().GetDirectory() + "/" + qrcEntry; + } + + entriesList += sep; + entriesList += qrcEntry; + sep = "@list_sep@"; + depends.push_back(qrcEntry); + } + return entriesList; +} + bool cmQtAutoGenerators::InitializeAutogenTarget(cmTarget* target) { cmMakefile* makefile = target->GetMakefile(); @@ -271,6 +377,61 @@ bool cmQtAutoGenerators::InitializeAutogenTarget(cmTarget* target) } } } +#endif + + std::vector rcc_output; + if(makefile->GetLocalGenerator()->GetGlobalGenerator()->GetName() == "Ninja" +#if defined(_WIN32) && !defined(__CYGWIN__) + || usePRE_BUILD +#endif + ) + { + std::vector srcFiles; + target->GetConfigCommonSourceFiles(srcFiles); + for(std::vector::const_iterator fileIt = srcFiles.begin(); + fileIt != srcFiles.end(); + ++fileIt) + { + cmSourceFile* sf = *fileIt; + std::string absFile = cmsys::SystemTools::GetRealPath( + sf->GetFullPath().c_str()); + + std::string ext = sf->GetExtension(); + + if (target->GetPropertyAsBool("AUTORCC")) + { + if (ext == "qrc" + && !cmSystemTools::IsOn(sf->GetPropertyForUser("SKIP_AUTORCC"))) + { + std::string basename = cmsys::SystemTools:: + GetFilenameWithoutLastExtension(absFile); + + std::string rcc_output_dir = target->GetSupportDirectory(); + cmSystemTools::MakeDirectory(rcc_output_dir.c_str()); + std::string rcc_output_file = rcc_output_dir; + rcc_output_file += "/qrc_" + basename + ".cpp"; + rcc_output.push_back(rcc_output_file); + + if (!cmSystemTools::IsOn(sf->GetPropertyForUser("GENERATED"))) + { + if (qtMajorVersion == "5") + { + this->ListQt5RccInputs(sf, target, depends); + } + else + { + this->ListQt4RccInputs(sf, depends); + } +#if defined(_WIN32) && !defined(__CYGWIN__) + usePRE_BUILD = false; +#endif + } + } + } + } + } + +#if defined(_WIN32) && !defined(__CYGWIN__) if(usePRE_BUILD) { // Add the pre-build command directly to bypass the OBJECT_LIBRARY @@ -287,10 +448,29 @@ bool cmQtAutoGenerators::InitializeAutogenTarget(cmTarget* target) else #endif { - cmTarget* autogenTarget = makefile->AddUtilityCommand( + cmTarget* autogenTarget = 0; + if (!rcc_output.empty()) + { + makefile->AddCustomCommandToOutput(rcc_output, depends, "", + commandLines, 0, + workingDirectory.c_str(), + false, false); + + cmCustomCommandLines no_commands; + autogenTarget = makefile->AddUtilityCommand( + autogenTargetName, true, + workingDirectory.c_str(), rcc_output, + no_commands, false, autogenComment.c_str()); + + } + else + { + autogenTarget = makefile->AddUtilityCommand( autogenTargetName, true, workingDirectory.c_str(), depends, commandLines, false, autogenComment.c_str()); + } + // Set target folder const char* autogenFolder = makefile->GetCMakeInstance()->GetProperty( "AUTOMOC_TARGETS_FOLDER"); @@ -418,6 +598,8 @@ void cmQtAutoGenerators::SetupAutoGenerateTarget(cmTarget const* target) inputFile += "/Modules/AutogenInfo.cmake.in"; std::string outputFile = targetDir; outputFile += "/AutogenInfo.cmake"; + makefile->AddDefinition("_qt_rcc_inputs", + makefile->GetDefinition("_qt_rcc_inputs_" + target->GetName())); makefile->ConfigureFile(inputFile.c_str(), outputFile.c_str(), false, true, false); @@ -869,9 +1051,12 @@ void cmQtAutoGenerators::SetupAutoRccTarget(cmTarget const* target) std::vector srcFiles; target->GetConfigCommonSourceFiles(srcFiles); + std::string qrcInputs; + const char* qrcInputsSep = ""; + std::string rccFileFiles; std::string rccFileOptions; - const char *sep = ""; + const char *optionSep = ""; const char *qtVersion = makefile->GetDefinition("_target_qt_version"); @@ -880,6 +1065,11 @@ void cmQtAutoGenerators::SetupAutoRccTarget(cmTarget const* target) { cmSystemTools::ExpandListArgument(opts, rccOptions); } + std::string qtMajorVersion = makefile->GetSafeDefinition("QT_VERSION_MAJOR"); + if (qtMajorVersion == "") + { + qtMajorVersion = makefile->GetSafeDefinition("Qt5Core_VERSION_MAJOR"); + } for(std::vector::const_iterator fileIt = srcFiles.begin(); fileIt != srcFiles.end(); @@ -909,9 +1099,9 @@ void cmQtAutoGenerators::SetupAutoRccTarget(cmTarget const* target) if (!rccOptions.empty()) { - rccFileFiles += sep; + rccFileFiles += optionSep; rccFileFiles += absFile; - rccFileOptions += sep; + rccFileOptions += optionSep; } const char *listSep = ""; for(std::vector::const_iterator it = rccOptions.begin(); @@ -922,10 +1112,34 @@ void cmQtAutoGenerators::SetupAutoRccTarget(cmTarget const* target) rccFileOptions += *it; listSep = "@list_sep@"; } - sep = ";"; + optionSep = ";"; + + std::vector depends; + + std::string entriesList; + if (!cmSystemTools::IsOn(sf->GetPropertyForUser("GENERATED"))) + { + if (qtMajorVersion == "5") + { + entriesList = this->ListQt5RccInputs(sf, target, depends); + } + else + { + entriesList = this->ListQt4RccInputs(sf, depends); + } + if (entriesList.empty()) + { + return; + } + } + qrcInputs += qrcInputsSep; + qrcInputs += entriesList; + qrcInputsSep = ";"; } } } + makefile->AddDefinition("_qt_rcc_inputs_" + target->GetName(), + cmLocalGenerator::EscapeForCMake(qrcInputs).c_str()); makefile->AddDefinition("_rcc_files", cmLocalGenerator::EscapeForCMake(_rcc_files).c_str()); @@ -1153,6 +1367,28 @@ bool cmQtAutoGenerators::ReadAutogenInfoFile(cmMakefile* makefile, cmSystemTools::ReplaceString(*optionIt, "@list_sep@", ";"); this->RccOptions[*fileIt] = *optionIt; } + + const char *rccInputs = makefile->GetSafeDefinition("AM_RCC_INPUTS"); + std::vector rccInputLists; + cmSystemTools::ExpandListArgument(rccInputs, rccInputLists); + + if (this->RccSources.size() != rccInputLists.size()) + { + cmSystemTools::Error("Error processing file: ", filename.c_str()); + return false; + } + + for (std::vector::iterator fileIt = this->RccSources.begin(), + inputIt = rccInputLists.begin(); + fileIt != this->RccSources.end(); + ++fileIt, ++inputIt) + { + cmSystemTools::ReplaceString(*inputIt, "@list_sep@", ";"); + std::vector rccInputFiles; + cmSystemTools::ExpandListArgument(*inputIt, rccInputFiles); + + this->RccInputs[*fileIt] = rccInputFiles; + } } this->CurrentCompileSettingsStr = this->MakeCompileSettingsString(makefile); @@ -2075,6 +2311,25 @@ bool cmQtAutoGenerators::GenerateUi(const std::string& realName, return false; } +bool cmQtAutoGenerators::InputFilesNewerThanQrc(const std::string& qrcFile, + const std::string& rccOutput) +{ + std::vector const& files = this->RccInputs[qrcFile]; + for (std::vector::const_iterator it = files.begin(); + it != files.end(); ++it) + { + int inputNewerThanQrc = 0; + bool success = cmsys::SystemTools::FileTimeCompare(it->c_str(), + rccOutput.c_str(), + &inputNewerThanQrc); + if (!success || inputNewerThanQrc >= 0) + { + return true; + } + } + return false; +} + bool cmQtAutoGenerators::GenerateQrc() { for(std::vector::const_iterator si = this->RccSources.begin(); @@ -2097,10 +2352,14 @@ bool cmQtAutoGenerators::GenerateQrc() + ".dir/qrc_" + basename + ".cpp"; int sourceNewerThanQrc = 0; - bool success = cmsys::SystemTools::FileTimeCompare(*si, - rcc_output_file, + bool generateQrc = !cmsys::SystemTools::FileTimeCompare(*si, + rcc_output_file.c_str(), &sourceNewerThanQrc); - if (this->GenerateAll || !success || sourceNewerThanQrc >= 0) + generateQrc = generateQrc || (sourceNewerThanQrc >= 0); + generateQrc = generateQrc || this->InputFilesNewerThanQrc(*si, + rcc_output_file); + + if (this->GenerateAll || generateQrc) { std::map::const_iterator optionIt = this->RccOptions.find(*si); diff --git a/Source/cmQtAutoGenerators.h b/Source/cmQtAutoGenerators.h index ce2785229..79fa5dfa8 100644 --- a/Source/cmQtAutoGenerators.h +++ b/Source/cmQtAutoGenerators.h @@ -88,6 +88,15 @@ private: std::string GetRccExecutable(cmTarget const* target); + std::string ListQt5RccInputs(cmSourceFile* sf, cmTarget const* target, + std::vector& depends); + + std::string ListQt4RccInputs(cmSourceFile* sf, + std::vector& depends); + + bool InputFilesNewerThanQrc(const std::string& qrcFile, + const std::string& rccOutput); + std::string QtMajorVersion; std::string Sources; std::vector RccSources; @@ -118,6 +127,7 @@ private: std::vector UicTargetOptions; std::map UicOptions; std::map RccOptions; + std::map > RccInputs; bool Verbose; bool ColorOutput; diff --git a/Tests/QtAutogen/CMakeLists.txt b/Tests/QtAutogen/CMakeLists.txt index 3fd00b8e0..a60b5560e 100644 --- a/Tests/QtAutogen/CMakeLists.txt +++ b/Tests/QtAutogen/CMakeLists.txt @@ -113,3 +113,35 @@ set_target_properties(no_link_language PROPERTIES AUTOMOC TRUE) qtx_wrap_cpp(uicOnlyMoc sub/uiconly.h) add_executable(uiconly sub/uiconly.cpp ${uicOnlyMoc}) target_link_libraries(uiconly ${QT_LIBRARIES}) + +try_compile(RCC_DEPENDS + "${CMAKE_CURRENT_BINARY_DIR}/autorcc_depends" + "${CMAKE_CURRENT_SOURCE_DIR}/autorcc_depends" + autorcc_depends + CMAKE_FLAGS "-DQT_QMAKE_EXECUTABLE:FILEPATH=${QT_QMAKE_EXECUTABLE}" "-DQT_TEST_VERSION=${QT_TEST_VERSION}" + OUTPUT_VARIABLE output +) +if (NOT RCC_DEPENDS) + message(SEND_ERROR "Initial build of autorcc_depends failed. Output: ${output}") +endif() + +file(STRINGS "${CMAKE_CURRENT_BINARY_DIR}/autorcc_depends/info_file.txt" qrc_files) + +list(GET qrc_files 0 qrc_file1) + +set(timeformat "%Y%j%H%M%S") + +file(TIMESTAMP "${qrc_file1}" file1_before "${timeformat}") + +execute_process(COMMAND "${CMAKE_COMMAND}" -E sleep 1) # Ensure that the timestamp will change. +execute_process(COMMAND "${CMAKE_COMMAND}" -E touch "${CMAKE_CURRENT_SOURCE_DIR}/autorcc_depends/res1_input.txt") + +execute_process(COMMAND "${CMAKE_COMMAND}" --build . + WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/autorcc_depends" +) + +file(TIMESTAMP "${qrc_file1}" file1_step1 "${timeformat}") + +if (NOT file1_step1 GREATER file1_before) + message(SEND_ERROR "file1 (${qrc_file1}) should have changed in the first step!") +endif() diff --git a/Tests/QtAutogen/autorcc_depends/CMakeLists.txt b/Tests/QtAutogen/autorcc_depends/CMakeLists.txt new file mode 100644 index 000000000..afd95bcad --- /dev/null +++ b/Tests/QtAutogen/autorcc_depends/CMakeLists.txt @@ -0,0 +1,24 @@ +cmake_minimum_required(VERSION 2.8) +project(autorcc_depends) + +set(CMAKE_AUTORCC ON) + +if (QT_TEST_VERSION STREQUAL 4) + find_package(Qt4 REQUIRED) + set(QT_CORE_TARGET Qt4::QtCore) +else() + if (NOT QT_TEST_VERSION STREQUAL 5) + message(SEND_ERROR "Invalid Qt version specified.") + endif() + + find_package(Qt5Core REQUIRED) + set(QT_CORE_TARGET Qt5::Core) +endif() + +add_executable(test_res1 + test_res1.cpp + res1.qrc +) +target_link_libraries(test_res1 ${QT_CORE_TARGET}) +add_custom_command(TARGET test_res1 POST_BUILD COMMAND + ${CMAKE_COMMAND} -E echo "$" > info_file.txt) diff --git a/Tests/QtAutogen/autorcc_depends/res1.qrc b/Tests/QtAutogen/autorcc_depends/res1.qrc new file mode 100644 index 000000000..cfea618ad --- /dev/null +++ b/Tests/QtAutogen/autorcc_depends/res1.qrc @@ -0,0 +1,5 @@ + + + res1_input.txt + + diff --git a/Tests/QtAutogen/autorcc_depends/res1_input.txt b/Tests/QtAutogen/autorcc_depends/res1_input.txt new file mode 100644 index 000000000..da627624b --- /dev/null +++ b/Tests/QtAutogen/autorcc_depends/res1_input.txt @@ -0,0 +1 @@ +Res1 input. diff --git a/Tests/QtAutogen/autorcc_depends/test_res1.cpp b/Tests/QtAutogen/autorcc_depends/test_res1.cpp new file mode 100644 index 000000000..766b7751b --- /dev/null +++ b/Tests/QtAutogen/autorcc_depends/test_res1.cpp @@ -0,0 +1,5 @@ + +int main() +{ + return 0; +}