Browse Source

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.
Stephen Kelly 11 years ago
parent
commit
6e1c359fe9

+ 1 - 0
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@ )

+ 267 - 8
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<std::string>& depends)
+{
+  std::string rccCommand = this->GetRccExecutable(target);
+  std::vector<std::string> qrcEntries;
+
+  std::vector<std::string> 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<std::string>::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<std::string>& depends)
+{
+  const std::string qrcContents = ReadAll(sf->GetFullPath());
+
+  cmsys::RegularExpression fileMatchRegex("(<file[^<]+)");
+
+  std::string entriesList;
+  const char* sep = "";
+
+  size_t offset = 0;
+  while (fileMatchRegex.find(qrcContents.c_str() + offset))
+    {
+    std::string qrcEntry = fileMatchRegex.match(1);
+
+    offset += qrcEntry.size();
+
+    cmsys::RegularExpression fileReplaceRegex("(^<file[^>]*>)");
+    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<std::string> rcc_output;
+  if(makefile->GetLocalGenerator()->GetGlobalGenerator()->GetName() == "Ninja"
+#if defined(_WIN32) && !defined(__CYGWIN__)
+        || usePRE_BUILD
+#endif
+        )
+    {
+    std::vector<cmSourceFile*> srcFiles;
+    target->GetConfigCommonSourceFiles(srcFiles);
+    for(std::vector<cmSourceFile*>::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<cmSourceFile*> 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<cmSourceFile*>::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<std::string>::const_iterator it = rccOptions.begin();
@@ -922,10 +1112,34 @@ void cmQtAutoGenerators::SetupAutoRccTarget(cmTarget const* target)
           rccFileOptions += *it;
           listSep = "@list_sep@";
           }
-        sep = ";";
+        optionSep = ";";
+
+        std::vector<std::string> 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<std::string> rccInputLists;
+  cmSystemTools::ExpandListArgument(rccInputs, rccInputLists);
+
+  if (this->RccSources.size() != rccInputLists.size())
+    {
+    cmSystemTools::Error("Error processing file: ", filename.c_str());
+    return false;
+    }
+
+  for (std::vector<std::string>::iterator fileIt = this->RccSources.begin(),
+                                            inputIt = rccInputLists.begin();
+                                            fileIt != this->RccSources.end();
+                                            ++fileIt, ++inputIt)
+    {
+    cmSystemTools::ReplaceString(*inputIt, "@list_sep@", ";");
+    std::vector<std::string> 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<std::string> const& files = this->RccInputs[qrcFile];
+  for (std::vector<std::string>::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<std::string>::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<std::string, std::string>::const_iterator optionIt
               = this->RccOptions.find(*si);

+ 10 - 0
Source/cmQtAutoGenerators.h

@@ -88,6 +88,15 @@ private:
 
   std::string GetRccExecutable(cmTarget const* target);
 
+  std::string ListQt5RccInputs(cmSourceFile* sf, cmTarget const* target,
+                               std::vector<std::string>& depends);
+
+  std::string ListQt4RccInputs(cmSourceFile* sf,
+                               std::vector<std::string>& depends);
+
+  bool InputFilesNewerThanQrc(const std::string& qrcFile,
+                              const std::string& rccOutput);
+
   std::string QtMajorVersion;
   std::string Sources;
   std::vector<std::string> RccSources;
@@ -118,6 +127,7 @@ private:
   std::vector<std::string> UicTargetOptions;
   std::map<std::string, std::string> UicOptions;
   std::map<std::string, std::string> RccOptions;
+  std::map<std::string, std::vector<std::string> > RccInputs;
 
   bool Verbose;
   bool ColorOutput;

+ 32 - 0
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()

+ 24 - 0
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 "$<TARGET_FILE:test_res1>" > info_file.txt)

+ 5 - 0
Tests/QtAutogen/autorcc_depends/res1.qrc

@@ -0,0 +1,5 @@
+<RCC>
+    <qresource prefix="/">
+        <file>res1_input.txt</file>
+    </qresource>
+</RCC>

+ 1 - 0
Tests/QtAutogen/autorcc_depends/res1_input.txt

@@ -0,0 +1 @@
+Res1 input.

+ 5 - 0
Tests/QtAutogen/autorcc_depends/test_res1.cpp

@@ -0,0 +1,5 @@
+
+int main()
+{
+  return 0;
+}