Browse Source

AUTOUIC: Fix generating of dependency rules for UI header files

We could not rely on .ui files when generating the ninja rules
for the generated UI header files. .ui files might be added to the
target sources but never processed by AUTOUIC afterward, since UI
header files are never included in a source code. Instead of adding
dependency rules based on the .ui files, this approach scans
non-generated source files for includes of the UI header files,
as AUTOUIC does. This gives the consistent set of UI header files
at configure time, that could be used to generate byproducts rules
for the AUTOUIC. Also, the path to the generated UI header file depends
not on the .ui file location but on the include line is used in source
files.

Fixes: #16776
Alexey Edelev 4 năm trước cách đây
mục cha
commit
e5ec0e52f4

+ 2 - 0
Source/CMakeLists.txt

@@ -432,6 +432,8 @@ set(SRCS
   cmQtAutoMocUic.h
   cmQtAutoMocUic.h
   cmQtAutoRcc.cxx
   cmQtAutoRcc.cxx
   cmQtAutoRcc.h
   cmQtAutoRcc.h
+  cmQtAutoUicHelpers.cxx
+  cmQtAutoUicHelpers.h
   cmRST.cxx
   cmRST.cxx
   cmRST.h
   cmRST.h
   cmRuntimeDependencyArchive.cxx
   cmRuntimeDependencyArchive.cxx

+ 36 - 0
Source/cmQtAutoGen.cxx

@@ -384,3 +384,39 @@ bool cmQtAutoGen::RccLister::list(std::string const& qrcFile,
   }
   }
   return true;
   return true;
 }
 }
+
+bool cmQtAutoGen::FileRead(std::string& content, std::string const& filename,
+                           std::string* error)
+{
+  content.clear();
+  if (!cmSystemTools::FileExists(filename, true)) {
+    if (error != nullptr) {
+      *error = "Not a file.";
+    }
+    return false;
+  }
+
+  unsigned long const length = cmSystemTools::FileLength(filename);
+  cmsys::ifstream ifs(filename.c_str(), (std::ios::in | std::ios::binary));
+
+  // Use lambda to save destructor calls of ifs
+  return [&ifs, length, &content, error]() -> bool {
+    if (!ifs) {
+      if (error != nullptr) {
+        *error = "Opening the file for reading failed.";
+      }
+      return false;
+    }
+    content.reserve(length);
+    using IsIt = std::istreambuf_iterator<char>;
+    content.assign(IsIt{ ifs }, IsIt{});
+    if (!ifs) {
+      content.clear();
+      if (error != nullptr) {
+        *error = "Reading from the file failed.";
+      }
+      return false;
+    }
+    return true;
+  }();
+}

+ 3 - 0
Source/cmQtAutoGen.h

@@ -100,6 +100,9 @@ public:
                               std::vector<std::string> const& newOpts,
                               std::vector<std::string> const& newOpts,
                               bool isQt5);
                               bool isQt5);
 
 
+  static bool FileRead(std::string& content, std::string const& filename,
+                       std::string* error = nullptr);
+
   /** @class RccLister
   /** @class RccLister
    * @brief Lists files in qrc resource files
    * @brief Lists files in qrc resource files
    */
    */

+ 27 - 26
Source/cmQtAutoGenInitializer.cxx

@@ -902,6 +902,13 @@ bool cmQtAutoGenInitializer::InitScanFiles()
   // The reason is that their file names might be discovered from source files
   // The reason is that their file names might be discovered from source files
   // at generation time.
   // at generation time.
   if (this->MocOrUicEnabled()) {
   if (this->MocOrUicEnabled()) {
+    std::set<std::string> uicIncludes;
+    auto collectUicIncludes = [&](std::unique_ptr<cmSourceFile> const& sf) {
+      std::string content;
+      FileRead(content, sf->GetFullPath());
+      this->AutoUicHelpers.CollectUicIncludes(uicIncludes, content);
+    };
+
     for (const auto& sf : this->Makefile->GetSourceFiles()) {
     for (const auto& sf : this->Makefile->GetSourceFiles()) {
       // sf->GetExtension() is only valid after sf->ResolveFullPath() ...
       // sf->GetExtension() is only valid after sf->ResolveFullPath() ...
       // Since we're iterating over source files that might be not in the
       // Since we're iterating over source files that might be not in the
@@ -914,6 +921,10 @@ bool cmQtAutoGenInitializer::InitScanFiles()
       std::string const& extLower =
       std::string const& extLower =
         cmSystemTools::LowerCase(sf->GetExtension());
         cmSystemTools::LowerCase(sf->GetExtension());
 
 
+      bool const skipAutogen = sf->GetPropertyAsBool(kw.SKIP_AUTOGEN);
+      bool const skipUic =
+        (skipAutogen || sf->GetPropertyAsBool(kw.SKIP_AUTOUIC) ||
+         !this->Uic.Enabled);
       if (cm->IsAHeaderExtension(extLower)) {
       if (cm->IsAHeaderExtension(extLower)) {
         if (!cm::contains(this->AutogenTarget.Headers, sf.get())) {
         if (!cm::contains(this->AutogenTarget.Headers, sf.get())) {
           auto muf = makeMUFile(sf.get(), fullPath, {}, false);
           auto muf = makeMUFile(sf.get(), fullPath, {}, false);
@@ -921,6 +932,9 @@ bool cmQtAutoGenInitializer::InitScanFiles()
             addMUHeader(std::move(muf), extLower);
             addMUHeader(std::move(muf), extLower);
           }
           }
         }
         }
+        if (!skipUic && !sf->GetIsGenerated()) {
+          collectUicIncludes(sf);
+        }
       } else if (cm->IsACLikeSourceExtension(extLower)) {
       } else if (cm->IsACLikeSourceExtension(extLower)) {
         if (!cm::contains(this->AutogenTarget.Sources, sf.get())) {
         if (!cm::contains(this->AutogenTarget.Sources, sf.get())) {
           auto muf = makeMUFile(sf.get(), fullPath, {}, false);
           auto muf = makeMUFile(sf.get(), fullPath, {}, false);
@@ -928,11 +942,11 @@ bool cmQtAutoGenInitializer::InitScanFiles()
             addMUSource(std::move(muf));
             addMUSource(std::move(muf));
           }
           }
         }
         }
+        if (!skipUic && !sf->GetIsGenerated()) {
+          collectUicIncludes(sf);
+        }
       } else if (this->Uic.Enabled && (extLower == kw.ui)) {
       } else if (this->Uic.Enabled && (extLower == kw.ui)) {
         // .ui file
         // .ui file
-        bool const skipAutogen = sf->GetPropertyAsBool(kw.SKIP_AUTOGEN);
-        bool const skipUic =
-          (skipAutogen || sf->GetPropertyAsBool(kw.SKIP_AUTOUIC));
         if (!skipUic) {
         if (!skipUic) {
           // Check if the .ui file has uic options
           // Check if the .ui file has uic options
           std::string const uicOpts = sf->GetSafeProperty(kw.AUTOUIC_OPTIONS);
           std::string const uicOpts = sf->GetSafeProperty(kw.AUTOUIC_OPTIONS);
@@ -942,35 +956,22 @@ bool cmQtAutoGenInitializer::InitScanFiles()
             this->Uic.UiFilesWithOptions.emplace_back(fullPath,
             this->Uic.UiFilesWithOptions.emplace_back(fullPath,
                                                       cmExpandedList(uicOpts));
                                                       cmExpandedList(uicOpts));
           }
           }
-
-          auto uiHeaderRelativePath = cmSystemTools::RelativePath(
-            this->LocalGen->GetCurrentSourceDirectory(),
-            cmSystemTools::GetFilenamePath(fullPath));
-
-          // Avoid creating a path containing adjacent slashes
-          if (!uiHeaderRelativePath.empty() &&
-              uiHeaderRelativePath.back() != '/') {
-            uiHeaderRelativePath += '/';
-          }
-
-          auto uiHeaderFilePath = cmStrCat(
-            '/', uiHeaderRelativePath, "ui_"_s,
-            cmSystemTools::GetFilenameWithoutLastExtension(fullPath), ".h"_s);
-
-          ConfigString uiHeader;
-          std::string uiHeaderGenex;
-          this->ConfigFileNamesAndGenex(
-            uiHeader, uiHeaderGenex, cmStrCat(this->Dir.Build, "/include"_s),
-            uiHeaderFilePath);
-
-          this->Uic.UiHeaders.emplace_back(
-            std::make_pair(uiHeader, uiHeaderGenex));
         } else {
         } else {
           // Register skipped .ui file
           // Register skipped .ui file
           this->Uic.SkipUi.insert(fullPath);
           this->Uic.SkipUi.insert(fullPath);
         }
         }
       }
       }
     }
     }
+
+    for (const auto& include : uicIncludes) {
+      ConfigString uiHeader;
+      std::string uiHeaderGenex;
+      this->ConfigFileNamesAndGenex(uiHeader, uiHeaderGenex,
+                                    cmStrCat(this->Dir.Build, "/include"_s),
+                                    cmStrCat("/"_s, include));
+      this->Uic.UiHeaders.emplace_back(
+        std::make_pair(uiHeader, uiHeaderGenex));
+    }
   }
   }
 
 
   // Process GENERATED sources and headers
   // Process GENERATED sources and headers

+ 2 - 0
Source/cmQtAutoGenInitializer.h

@@ -17,6 +17,7 @@
 
 
 #include "cmFilePathChecksum.h"
 #include "cmFilePathChecksum.h"
 #include "cmQtAutoGen.h"
 #include "cmQtAutoGen.h"
+#include "cmQtAutoUicHelpers.h"
 
 
 class cmGeneratorTarget;
 class cmGeneratorTarget;
 class cmGlobalGenerator;
 class cmGlobalGenerator;
@@ -170,6 +171,7 @@ private:
   std::string ConfigDefault;
   std::string ConfigDefault;
   std::vector<std::string> ConfigsList;
   std::vector<std::string> ConfigsList;
   std::string TargetsFolder;
   std::string TargetsFolder;
+  cmQtAutoUicHelpers AutoUicHelpers;
 
 
   /** Common directories.  */
   /** Common directories.  */
   struct
   struct

+ 0 - 37
Source/cmQtAutoGenerator.cxx

@@ -121,43 +121,6 @@ bool cmQtAutoGenerator::MakeParentDirectory(std::string const& filename)
   return success;
   return success;
 }
 }
 
 
-bool cmQtAutoGenerator::FileRead(std::string& content,
-                                 std::string const& filename,
-                                 std::string* error)
-{
-  content.clear();
-  if (!cmSystemTools::FileExists(filename, true)) {
-    if (error != nullptr) {
-      *error = "Not a file.";
-    }
-    return false;
-  }
-
-  unsigned long const length = cmSystemTools::FileLength(filename);
-  cmsys::ifstream ifs(filename.c_str(), (std::ios::in | std::ios::binary));
-
-  // Use lambda to save destructor calls of ifs
-  return [&ifs, length, &content, error]() -> bool {
-    if (!ifs) {
-      if (error != nullptr) {
-        *error = "Opening the file for reading failed.";
-      }
-      return false;
-    }
-    content.reserve(length);
-    using IsIt = std::istreambuf_iterator<char>;
-    content.assign(IsIt{ ifs }, IsIt{});
-    if (!ifs) {
-      content.clear();
-      if (error != nullptr) {
-        *error = "Reading from the file failed.";
-      }
-      return false;
-    }
-    return true;
-  }();
-}
-
 bool cmQtAutoGenerator::FileWrite(std::string const& filename,
 bool cmQtAutoGenerator::FileWrite(std::string const& filename,
                                   std::string const& content,
                                   std::string const& content,
                                   std::string* error)
                                   std::string* error)

+ 0 - 2
Source/cmQtAutoGenerator.h

@@ -70,8 +70,6 @@ public:
 
 
   // -- File system methods
   // -- File system methods
   static bool MakeParentDirectory(std::string const& filename);
   static bool MakeParentDirectory(std::string const& filename);
-  static bool FileRead(std::string& content, std::string const& filename,
-                       std::string* error = nullptr);
   static bool FileWrite(std::string const& filename,
   static bool FileWrite(std::string const& filename,
                         std::string const& content,
                         std::string const& content,
                         std::string* error = nullptr);
                         std::string* error = nullptr);

+ 4 - 16
Source/cmQtAutoMocUic.cxx

@@ -30,6 +30,7 @@
 #include "cmGeneratedFileStream.h"
 #include "cmGeneratedFileStream.h"
 #include "cmQtAutoGen.h"
 #include "cmQtAutoGen.h"
 #include "cmQtAutoGenerator.h"
 #include "cmQtAutoGenerator.h"
+#include "cmQtAutoUicHelpers.h"
 #include "cmStringAlgorithms.h"
 #include "cmStringAlgorithms.h"
 #include "cmSystemTools.h"
 #include "cmSystemTools.h"
 #include "cmWorkerPool.h"
 #include "cmWorkerPool.h"
@@ -281,7 +282,7 @@ public:
     std::vector<std::string> Options;
     std::vector<std::string> Options;
     std::unordered_map<std::string, UiFile> UiFiles;
     std::unordered_map<std::string, UiFile> UiFiles;
     std::vector<std::string> SearchPaths;
     std::vector<std::string> SearchPaths;
-    cmsys::RegularExpression RegExpInclude;
+    cmQtAutoUicHelpers AutoUicHelpers;
   };
   };
 
 
   /** Uic shared variables.  */
   /** Uic shared variables.  */
@@ -761,11 +762,7 @@ std::string cmQtAutoMocUicT::MocSettingsT::MacrosString() const
   return res;
   return res;
 }
 }
 
 
-cmQtAutoMocUicT::UicSettingsT::UicSettingsT()
-{
-  this->RegExpInclude.compile("(^|\n)[ \t]*#[ \t]*include[ \t]+"
-                              "[\"<](([^ \">]+/)?ui_[^ \">/]+\\.h)[\">]");
-}
+cmQtAutoMocUicT::UicSettingsT::UicSettingsT() = default;
 
 
 cmQtAutoMocUicT::UicSettingsT::~UicSettingsT() = default;
 cmQtAutoMocUicT::UicSettingsT::~UicSettingsT() = default;
 
 
@@ -1056,16 +1053,7 @@ void cmQtAutoMocUicT::JobParseT::UicIncludes()
   }
   }
 
 
   std::set<std::string> includes;
   std::set<std::string> includes;
-  {
-    const char* contentChars = this->Content.c_str();
-    cmsys::RegularExpression const& regExp = this->UicConst().RegExpInclude;
-    cmsys::RegularExpressionMatch match;
-    while (regExp.find(contentChars, match)) {
-      includes.emplace(match.match(2));
-      // Forward content pointer
-      contentChars += match.end();
-    }
-  }
+  this->UicConst().AutoUicHelpers.CollectUicIncludes(includes, this->Content);
   this->CreateKeys(this->FileHandle->ParseData->Uic.Include, includes,
   this->CreateKeys(this->FileHandle->ParseData->Uic.Include, includes,
                    UiUnderscoreLength);
                    UiUnderscoreLength);
 }
 }

+ 25 - 0
Source/cmQtAutoUicHelpers.cxx

@@ -0,0 +1,25 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+#include "cmQtAutoUicHelpers.h"
+
+cmQtAutoUicHelpers::cmQtAutoUicHelpers()
+{
+  RegExpInclude.compile("(^|\n)[ \t]*#[ \t]*include[ \t]+"
+                        "[\"<](([^ \">]+/)?ui_[^ \">/]+\\.h)[\">]");
+}
+
+void cmQtAutoUicHelpers::CollectUicIncludes(std::set<std::string>& includes,
+                                            const std::string& content) const
+{
+  if (content.find("ui_") == std::string::npos) {
+    return;
+  }
+
+  const char* contentChars = content.c_str();
+  cmsys::RegularExpressionMatch match;
+  while (this->RegExpInclude.find(contentChars, match)) {
+    includes.emplace(match.match(2));
+    // Forward content pointer
+    contentChars += match.end();
+  }
+}

+ 20 - 0
Source/cmQtAutoUicHelpers.h

@@ -0,0 +1,20 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+#pragma once
+
+#include <set>
+#include <string>
+
+#include "cmsys/RegularExpression.hxx"
+
+class cmQtAutoUicHelpers
+{
+public:
+  cmQtAutoUicHelpers();
+  virtual ~cmQtAutoUicHelpers() = default;
+  void CollectUicIncludes(std::set<std::string>& includes,
+                          const std::string& content) const;
+
+private:
+  cmsys::RegularExpression RegExpInclude;
+};

+ 3 - 0
Tests/QtAutogen/RerunUicOnFileChange/CMakeLists.txt

@@ -27,10 +27,12 @@ endmacro()
 
 
 configure_file("${testProjectTemplateDir}/mocwidget.h" "${testProjectSrc}/mocwidget.h" COPYONLY)
 configure_file("${testProjectTemplateDir}/mocwidget.h" "${testProjectSrc}/mocwidget.h" COPYONLY)
 configure_file("${testProjectTemplateDir}/main.cpp" "${testProjectSrc}/main.cpp" COPYONLY)
 configure_file("${testProjectTemplateDir}/main.cpp" "${testProjectSrc}/main.cpp" COPYONLY)
+configure_file("${testProjectTemplateDir}/subdir/subdircheck.cpp" "${testProjectSrc}/subdir/subdircheck.cpp" COPYONLY)
 configure_file("${testProjectTemplateDir}/CMakeLists.txt.in" "${testProjectSrc}/CMakeLists.txt" @ONLY)
 configure_file("${testProjectTemplateDir}/CMakeLists.txt.in" "${testProjectSrc}/CMakeLists.txt" @ONLY)
 
 
 set(Num 1)
 set(Num 1)
 configure_file("${testProjectTemplateDir}/mainwindow.ui.in" "${testProjectSrc}/mainwindow.ui" @ONLY)
 configure_file("${testProjectTemplateDir}/mainwindow.ui.in" "${testProjectSrc}/mainwindow.ui" @ONLY)
+configure_file("${testProjectTemplateDir}/subdir/mainwindowsubdir.ui.in" "${testProjectSrc}/subdir/mainwindowsubdir.ui" @ONLY)
 
 
 if(CMAKE_GENERATOR_INSTANCE)
 if(CMAKE_GENERATOR_INSTANCE)
     set(_D_CMAKE_GENERATOR_INSTANCE "-DCMAKE_GENERATOR_INSTANCE=${CMAKE_GENERATOR_INSTANCE}")
     set(_D_CMAKE_GENERATOR_INSTANCE "-DCMAKE_GENERATOR_INSTANCE=${CMAKE_GENERATOR_INSTANCE}")
@@ -94,6 +96,7 @@ sleep()
 
 
 set(Num 2)
 set(Num 2)
 configure_file("${testProjectTemplateDir}/mainwindow.ui.in" "${testProjectSrc}/mainwindow.ui" @ONLY)
 configure_file("${testProjectTemplateDir}/mainwindow.ui.in" "${testProjectSrc}/mainwindow.ui" @ONLY)
+configure_file("${testProjectTemplateDir}/subdir/mainwindowsubdir.ui.in" "${testProjectSrc}/subdir/mainwindowsubdir.ui" @ONLY)
 rebuild(2)
 rebuild(2)
 
 
 execute_process(COMMAND "${testProjectBinDir}/${extra_bin_path}UicOnFileChange" RESULT_VARIABLE result)
 execute_process(COMMAND "${testProjectBinDir}/${extra_bin_path}UicOnFileChange" RESULT_VARIABLE result)

+ 3 - 1
Tests/QtAutogen/RerunUicOnFileChange/UicOnFileChange/CMakeLists.txt.in

@@ -6,6 +6,8 @@ include("@CMAKE_CURRENT_LIST_DIR@/../AutogenGuiTest.cmake")
 # Enable CMAKE_AUTOUIC for all targets
 # Enable CMAKE_AUTOUIC for all targets
 set(CMAKE_AUTOUIC ON)
 set(CMAKE_AUTOUIC ON)
 
 
-add_executable(UicOnFileChange main.cpp mainwindow.ui)
+add_executable(UicOnFileChange main.cpp mainwindow.ui
+    subdir/subdircheck.cpp subdir/mainwindowsubdir.ui
+)
 target_include_directories(UicOnFileChange PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
 target_include_directories(UicOnFileChange PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
 target_link_libraries(UicOnFileChange ${QT_QTCORE_TARGET} ${QT_LIBRARIES})
 target_link_libraries(UicOnFileChange ${QT_QTCORE_TARGET} ${QT_LIBRARIES})

+ 3 - 1
Tests/QtAutogen/RerunUicOnFileChange/UicOnFileChange/main.cpp

@@ -1,9 +1,11 @@
 #include "ui_mainwindow.h"
 #include "ui_mainwindow.h"
 
 
+extern bool subdircheck();
+
 int main(int argc, char* argv[])
 int main(int argc, char* argv[])
 {
 {
   MocWidget mw;
   MocWidget mw;
   Ui::Widget mwUi;
   Ui::Widget mwUi;
   mwUi.setupUi(&mw);
   mwUi.setupUi(&mw);
-  return mw.objectName() == "Widget2" ? 0 : 1;
+  return mw.objectName() == "Widget2" && subdircheck() ? 0 : 1;
 }
 }

+ 7 - 0
Tests/QtAutogen/RerunUicOnFileChange/UicOnFileChange/subdir/mainwindowsubdir.ui.in

@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>WidgetSubdir</class>
+ <widget class="MocWidget" name="WidgetSubdir@Num@"/>
+ <resources/>
+ <connections/>
+</ui>

+ 9 - 0
Tests/QtAutogen/RerunUicOnFileChange/UicOnFileChange/subdir/subdircheck.cpp

@@ -0,0 +1,9 @@
+#include "ui_mainwindowsubdir.h"
+
+bool subdircheck()
+{
+  MocWidget mw;
+  Ui::WidgetSubdir mwUi;
+  mwUi.setupUi(&mw);
+  return mw.objectName() == "WidgetSubdir2";
+}