Am 27.07.2016 um 17:49 schrieb Sebastian Holtermann:

Doing so I found that Base64 allows '+' and '/' as characters which is
bad for directory names obviously.
For now these characters get replaced with 'A' and 'B'.

'_' and '@' would be better replacements (with comments why they are
used) since 'A' and 'B' are already characters in Base64.

Some quick research (aka googling) revealed
http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_278

It seems '_' and '-' are safer choices.

+            // unexpected hexchar

This raise an internal error, not just be a comment.

Is there an error exit function in CMake?
Or just exit(-1);

+          }
+        }
+        hashBytes[ii] = hbyte[0] | (hbyte[1] << 4);
+      }
+    }
+    // Convert hash bytes to Base64 text string
+    {
+      std::vector<unsigned char> base64Bytes(hashBytes.size() * 2, 0);
+      cmsysBase64_Encode(&hashBytes[0], hashBytes.size(),
&base64Bytes[0], 0);
+      checksumBase64 = reinterpret_cast<const char*>(&base64Bytes[0]);
+      // Base64 allows '+' and '/' characters. Replace these.

This comment should indicate it is because the string is used as part of
a path and that these characters tend to cause problems in paths.

Ok.


The patch.

-Sebastian


>From 30d0c7b61a2ffbc9004cd32a88330078ec8d75a4 Mon Sep 17 00:00:00 2001
From: Sebastian Holtermann <sebh...@xwmw.org>
Date: Tue, 26 Jul 2016 16:39:12 +0200
Subject: [PATCH] QtAutogen fix for too deep nested directory generation.

Instead of generating moc_* and qrc_* files in subdirectories
that reflect their source's location in the source tree
the files get generated solely in the TARGET_NAME_automoc.dir/
but get a Base64 encoded checksum suffix that was generated
from their source path and a few more seed strings.
---
 Source/cmQtAutoGeneratorInitializer.cxx |  79 +++---------
 Source/cmQtAutoGenerators.cxx           | 215 +++++++++++++++++++++++---------
 Source/cmQtAutoGenerators.h             |  21 +++-
 3 files changed, 192 insertions(+), 123 deletions(-)

diff --git a/Source/cmQtAutoGeneratorInitializer.cxx b/Source/cmQtAutoGeneratorInitializer.cxx
index dd19760..6bbe29c 100644
--- a/Source/cmQtAutoGeneratorInitializer.cxx
+++ b/Source/cmQtAutoGeneratorInitializer.cxx
@@ -12,6 +12,7 @@
 ============================================================================*/
 
 #include "cmQtAutoGeneratorInitializer.h"
+#include "cmQtAutoGenerators.h"
 
 #include "cmLocalGenerator.h"
 #include "cmMakefile.h"
@@ -53,49 +54,24 @@ static std::string GetAutogenTargetBuildDir(cmGeneratorTarget const* target)
   return targetDir;
 }
 
-static std::string GetSourceRelativePath(cmGeneratorTarget const* target,
-                                         const std::string& fileName)
+static std::string GetQrcBuildPath(cmGeneratorTarget const* target,
+                                   const std::string& qrcSourceName)
 {
-  std::string pathRel;
-  // Test if the file is child to any of the known directories
+  std::string rccOutputPath = GetAutogenTargetBuildDir(target);
+  // Create output directory
+  cmSystemTools::MakeDirectory(rccOutputPath.c_str());
+
+  rccOutputPath += "qrc_";
   {
-    const std::string fileNameReal = cmsys::SystemTools::GetRealPath(fileName);
-    std::string parentDirectory;
-    bool match(false);
-    {
-      std::string testDirs[4];
-      {
-        cmMakefile* makefile = target->Target->GetMakefile();
-        testDirs[0] = makefile->GetCurrentSourceDirectory();
-        testDirs[1] = makefile->GetCurrentBinaryDirectory();
-        testDirs[2] = makefile->GetHomeDirectory();
-        testDirs[3] = makefile->GetHomeOutputDirectory();
-      }
-      for (int ii = 0; ii != sizeof(testDirs) / sizeof(std::string); ++ii) {
-        const ::std::string testDir =
-          cmsys::SystemTools::GetRealPath(testDirs[ii]);
-        if (!testDir.empty() &&
-            cmsys::SystemTools::IsSubDirectory(fileNameReal, testDir)) {
-          parentDirectory = testDir;
-          match = true;
-          break;
-        }
-      }
-    }
-    // Use root as fallback parent directory
-    if (!match) {
-      cmsys::SystemTools::SplitPathRootComponent(fileNameReal,
-                                                 &parentDirectory);
-    }
-    pathRel = cmsys::SystemTools::RelativePath(
-      parentDirectory, cmsys::SystemTools::GetParentDirectory(fileNameReal));
+    cmMakefile* makefile = target->Target->GetMakefile();
+    rccOutputPath += cmQtAutoGeneratorUtil::BuildFileBase(
+      qrcSourceName, makefile->GetCurrentSourceDirectory(),
+      makefile->GetCurrentBinaryDirectory(), makefile->GetHomeDirectory(),
+      makefile->GetHomeOutputDirectory());
   }
-  // Sanitize relative path
-  if (!pathRel.empty()) {
-    pathRel += '/';
-    cmSystemTools::ReplaceString(pathRel, "..", "__");
-  }
-  return pathRel;
+  rccOutputPath += ".cpp";
+
+  return rccOutputPath;
 }
 
 static void SetupSourceFiles(cmGeneratorTarget const* target,
@@ -129,15 +105,7 @@ static void SetupSourceFiles(cmGeneratorTarget const* target,
       if (ext == "qrc" &&
           !cmSystemTools::IsOn(sf->GetPropertyForUser("SKIP_AUTORCC"))) {
 
-        std::string rcc_output_dir = GetAutogenTargetBuildDir(target);
-        rcc_output_dir += GetSourceRelativePath(target, absFile);
-        cmSystemTools::MakeDirectory(rcc_output_dir.c_str());
-
-        std::string basename =
-          cmsys::SystemTools::GetFilenameWithoutLastExtension(absFile);
-
-        std::string rcc_output_file = rcc_output_dir;
-        rcc_output_file += "qrc_" + basename + ".cpp";
+        const std::string rcc_output_file = GetQrcBuildPath(target, absFile);
         makefile->AppendProperty("ADDITIONAL_MAKE_CLEAN_FILES",
                                  rcc_output_file.c_str(), false);
         makefile->GetOrCreateSource(rcc_output_file, true);
@@ -804,17 +772,8 @@ void cmQtAutoGeneratorInitializer::InitializeAutogenTarget(
         if (ext == "qrc" &&
             !cmSystemTools::IsOn(sf->GetPropertyForUser("SKIP_AUTORCC"))) {
 
-          {
-            std::string rcc_output_dir = GetAutogenTargetBuildDir(target);
-            rcc_output_dir += GetSourceRelativePath(target, absFile);
-            cmSystemTools::MakeDirectory(rcc_output_dir.c_str());
-
-            std::string basename =
-              cmsys::SystemTools::GetFilenameWithoutLastExtension(absFile);
-            std::string rcc_output_file = rcc_output_dir;
-            rcc_output_file += "qrc_" + basename + ".cpp";
-            rcc_output.push_back(rcc_output_file);
-          }
+          const std::string rcc_output_file = GetQrcBuildPath(target, absFile);
+          rcc_output.push_back(rcc_output_file);
 
           if (!cmSystemTools::IsOn(sf->GetPropertyForUser("GENERATED"))) {
             if (qtMajorVersion == "5") {
diff --git a/Source/cmQtAutoGenerators.cxx b/Source/cmQtAutoGenerators.cxx
index ac64397..af69535 100644
--- a/Source/cmQtAutoGenerators.cxx
+++ b/Source/cmQtAutoGenerators.cxx
@@ -20,6 +20,9 @@
 #include "cmState.h"
 #include "cmSystemTools.h"
 
+#include "cmCryptoHash.h"
+#include "cmsys/Base64.h"
+
 #include <sys/stat.h>
 
 #include <assert.h>
@@ -31,6 +34,107 @@
 #include <unistd.h>
 #endif
 
+std::string cmQtAutoGeneratorUtil::BuildFileBase(
+  const std::string& filePath, const std::string& parenDirCSource,
+  const std::string& parenDirCBinary, const std::string& parenDirPSource,
+  const std::string& parenDirPBinary)
+{
+  std::string baseName;
+
+  std::string sourceFileName = cmsys::SystemTools::GetFilenameName(filePath);
+  std::string sourceBaseName =
+    cmsys::SystemTools::GetFilenameWithoutLastExtension(sourceFileName);
+
+  // Determine the relative path to a known parent directory
+  std::string sourceRelPath;
+  std::string sourceRelSeed;
+  {
+    std::string sourceNameReal = cmsys::SystemTools::GetRealPath(filePath);
+    std::string parentDirectory;
+    bool parentFound(false);
+    {
+      struct TPair
+      {
+        const std::string* dir;
+        const char* seed;
+      };
+      TPair pDirs[] = { { &parenDirCSource, "CurrentSource" },
+                        { &parenDirCBinary, "CurrentBinary" },
+                        { &parenDirPSource, "ProjectSource" },
+                        { &parenDirPBinary, "ProjectBinary" },
+                        { 0, 0 } };
+      for (TPair* itc = &pDirs[0]; itc->dir != 0; ++itc) {
+        const std::string pDir = cmsys::SystemTools::GetRealPath(*(itc->dir));
+        if (cmsys::SystemTools::IsSubDirectory(sourceNameReal, pDir)) {
+          sourceRelSeed = itc->seed;
+          parentDirectory = pDir;
+          parentFound = true;
+          break;
+        }
+      }
+    }
+    // Use root as fallback parent directory
+    if (!parentFound) {
+      sourceRelSeed = "FileSystemRoot";
+      cmsys::SystemTools::SplitPathRootComponent(sourceNameReal,
+                                                 &parentDirectory);
+    }
+    sourceRelPath = cmsys::SystemTools::RelativePath(
+      parentDirectory, cmsys::SystemTools::GetParentDirectory(sourceNameReal));
+  }
+
+  // Calculate the file ( seed + relative path + name ) checksum
+  std::string checksumBase64;
+  {
+    std::vector<unsigned char> hashBytes;
+    {
+      // Acquire hex value hash string
+      std::string hexHash = cmCryptoHash::New("SHA256")->HashString(
+        (sourceRelSeed + sourceRelPath + sourceFileName).c_str());
+      // Convert hex value string to bytes
+      hashBytes.resize(hexHash.size() / 2);
+      for (unsigned int ii = 0; ii != hashBytes.size(); ++ii) {
+        unsigned char hbyte[2] = { 0, 0 };
+        for (unsigned int jj = 0; jj != 2; ++jj) {
+          unsigned char nibble = hexHash[ii * 2 + jj];
+          if ('0' <= nibble && nibble <= '9') {
+            hbyte[jj] = nibble - '0';
+          } else if ('a' <= nibble && nibble <= 'f') {
+            hbyte[jj] = nibble - 'a' + 10;
+          } else {
+            // Unexpected non hex character
+            std::cerr << "Unexpected non hex character in checksum string";
+            exit(-1);
+          }
+        }
+        hashBytes[ii] = hbyte[0] | (hbyte[1] << 4);
+      }
+    }
+    // Convert hash bytes to Base64 text string
+    {
+      std::vector<unsigned char> base64Bytes(hashBytes.size() * 2, 0);
+      cmsysBase64_Encode(&hashBytes[0], hashBytes.size(), &base64Bytes[0], 0);
+      checksumBase64 = reinterpret_cast<const char*>(&base64Bytes[0]);
+      // Base64 allows '+' and '/' characters.
+      // Both are problematic when used in file names.
+      // Replace them with safer alternatives.
+      std::replace(checksumBase64.begin(), checksumBase64.end(), '+', '_');
+      std::replace(checksumBase64.begin(), checksumBase64.end(), '/', '-');
+    }
+  }
+
+  // Compose the file name
+  {
+    const size_t nameLength(14);
+    const size_t checkSumLength(14);
+    baseName.append(sourceBaseName.substr(0, nameLength));
+    baseName.append("_");
+    baseName.append(checksumBase64.substr(0, checkSumLength));
+  }
+
+  return baseName;
+}
+
 static bool requiresMocing(const std::string& text, std::string& macroName)
 {
   // this simple check is much much faster than the regexp
@@ -87,6 +191,23 @@ static std::string extractSubDir(const std::string& absPath,
   return subDir;
 }
 
+static bool FileNameIsUnique(const std::string& filePath,
+                             const std::map<std::string, std::string>& fileMap)
+{
+  size_t count(0);
+  const std::string fileName = cmsys::SystemTools::GetFilenameName(filePath);
+  for (std::map<std::string, std::string>::const_iterator si = fileMap.begin();
+       si != fileMap.end(); ++si) {
+    if (cmsys::SystemTools::GetFilenameName(si->first) == fileName) {
+      ++count;
+      if (count > 1) {
+        return false;
+      }
+    }
+  }
+  return true;
+}
+
 cmQtAutoGenerators::cmQtAutoGenerators()
   : Verbose(cmsys::SystemTools::GetEnv("VERBOSE") != 0)
   , ColorOutput(true)
@@ -950,12 +1071,8 @@ void cmQtAutoGenerators::ParseHeaders(
 
       std::string macroName;
       if (requiresMocing(contents, macroName)) {
-        const std::string parentDir =
-          this->TargetBuildSubDir + this->SourceRelativePath(headerName);
-        const std::string basename =
-          cmsys::SystemTools::GetFilenameWithoutLastExtension(headerName);
-        const std::string currentMoc = parentDir + "moc_" + basename + ".cpp";
-        notIncludedMocs[headerName] = currentMoc;
+        notIncludedMocs[headerName] =
+          this->TargetBuildSubDir + BuildFileName(headerName, "moc_", ".cpp");
       }
     }
     this->ParseForUic(headerName, contents, includedUis);
@@ -1022,7 +1139,9 @@ bool cmQtAutoGenerators::GenerateMocFiles(
       for (std::map<std::string, std::string>::const_iterator it =
              notIncludedMocs.begin();
            it != notIncludedMocs.end(); ++it) {
-        outStream << "#include \"" << it->second << "\"\n";
+        outStream << "#include \""
+                  << cmsys::SystemTools::ConvertToOutputPath(it->second)
+                  << "\"\n";
       }
     }
     outStream.flush();
@@ -1186,7 +1305,7 @@ bool cmQtAutoGenerators::GenerateUi(const std::string& realName,
     cmsys::SystemTools::MakeDirectory(this->Builddir.c_str());
   }
 
-  const ::std::string uiBuildFile = this->Builddir + uiOutputFile;
+  const std::string uiBuildFile = this->Builddir + uiOutputFile;
 
   int sourceNewerThanUi = 0;
   bool success = cmsys::SystemTools::FileTimeCompare(uiInputFile, uiBuildFile,
@@ -1262,13 +1381,8 @@ bool cmQtAutoGenerators::GenerateQrcFiles()
        si != this->RccSources.end(); ++si) {
     const std::string ext = cmsys::SystemTools::GetFilenameLastExtension(*si);
     if (ext == ".qrc") {
-      std::string basename =
-        cmsys::SystemTools::GetFilenameWithoutLastExtension(*si);
-      std::string qrcOutputFile = this->TargetBuildSubDir +
-        this->SourceRelativePath(*si) + "qrc_" + basename + ".cpp";
-      // std::string qrcOutputFile = "CMakeFiles/" + this->OriginTargetName
-      //                         + ".dir/qrc_" + basename + ".cpp";
-      qrcGenMap[*si] = qrcOutputFile;
+      qrcGenMap[*si] =
+        (this->TargetBuildSubDir + this->BuildFileName(*si, "qrc_", ".cpp"));
     }
   }
 
@@ -1290,7 +1404,8 @@ bool cmQtAutoGenerators::GenerateQrcFiles()
   for (std::map<std::string, std::string>::const_iterator si =
          qrcGenMap.begin();
        si != qrcGenMap.end(); ++si) {
-    if (!this->GenerateQrc(si->first, si->second)) {
+    if (!this->GenerateQrc(si->first, si->second,
+                           FileNameIsUnique(si->first, qrcGenMap))) {
       if (this->RunRccFailed) {
         return false;
       }
@@ -1300,13 +1415,21 @@ bool cmQtAutoGenerators::GenerateQrcFiles()
 }
 
 bool cmQtAutoGenerators::GenerateQrc(const std::string& qrcInputFile,
-                                     const std::string& qrcOutputFile)
+                                     const std::string& qrcOutputFile,
+                                     bool unique_n)
 {
-  std::string relName = this->SourceRelativePath(qrcInputFile);
-  std::replace(relName.begin(), relName.end(), '/', '_');
-  relName += cmsys::SystemTools::GetFilenameWithoutLastExtension(qrcInputFile);
+  std::string symbolName;
+  if (unique_n) {
+    symbolName =
+      cmsys::SystemTools::GetFilenameWithoutLastExtension(qrcInputFile);
+  } else {
+    symbolName =
+      cmsys::SystemTools::GetFilenameWithoutLastExtension(qrcOutputFile);
+    // Remove "qrc_" at string begin
+    symbolName.erase(0, 4);
+  }
 
-  const ::std::string qrcBuildFile = this->Builddir + qrcOutputFile;
+  const std::string qrcBuildFile = this->Builddir + qrcOutputFile;
 
   int sourceNewerThanQrc = 0;
   bool generateQrc = !cmsys::SystemTools::FileTimeCompare(
@@ -1332,7 +1455,7 @@ bool cmQtAutoGenerators::GenerateQrc(const std::string& qrcInputFile,
     }
 
     command.push_back("-name");
-    command.push_back(relName);
+    command.push_back(symbolName);
     command.push_back("-o");
     command.push_back(qrcBuildFile);
     command.push_back(qrcInputFile);
@@ -1357,47 +1480,17 @@ bool cmQtAutoGenerators::GenerateQrc(const std::string& qrcInputFile,
   return true;
 }
 
-std::string cmQtAutoGenerators::SourceRelativePath(const std::string& filename)
+std::string cmQtAutoGenerators::BuildFileName(const std::string& filename,
+                                              const std::string& prefix,
+                                              const std::string& suffix)
 {
-  std::string pathRel;
+  std::string buildName(prefix);
+  buildName += cmQtAutoGeneratorUtil::BuildFileBase(
+    filename, this->Srcdir, this->Builddir, this->ProjectSourceDir,
+    this->ProjectBinaryDir);
+  buildName += suffix;
 
-  // Test if the file is child to any of the known directories
-  {
-    std::string fileNameReal = cmsys::SystemTools::GetRealPath(filename);
-    std::string parentDirectory;
-    bool match(false);
-    {
-      const ::std::string* testDirs[4];
-      testDirs[0] = &(this->Srcdir);
-      testDirs[1] = &(this->Builddir);
-      testDirs[2] = &(this->ProjectSourceDir);
-      testDirs[3] = &(this->ProjectBinaryDir);
-      for (int ii = 0; ii != sizeof(testDirs) / sizeof(const ::std::string*);
-           ++ii) {
-        const ::std::string testDir =
-          cmsys::SystemTools::GetRealPath(*(testDirs[ii]));
-        if (cmsys::SystemTools::IsSubDirectory(fileNameReal, testDir)) {
-          parentDirectory = testDir;
-          match = true;
-          break;
-        }
-      }
-    }
-    // Use root as fallback parent directory
-    if (!match) {
-      cmsys::SystemTools::SplitPathRootComponent(fileNameReal,
-                                                 &parentDirectory);
-    }
-    pathRel = cmsys::SystemTools::RelativePath(
-      parentDirectory, cmsys::SystemTools::GetParentDirectory(fileNameReal));
-  }
-
-  // Sanitize relative path
-  if (!pathRel.empty()) {
-    pathRel += '/';
-    cmSystemTools::ReplaceString(pathRel, "..", "__");
-  }
-  return pathRel;
+  return buildName;
 }
 
 /**
diff --git a/Source/cmQtAutoGenerators.h b/Source/cmQtAutoGenerators.h
index 5e7fab5..2d1d304 100644
--- a/Source/cmQtAutoGenerators.h
+++ b/Source/cmQtAutoGenerators.h
@@ -23,6 +23,21 @@
 
 class cmMakefile;
 
+/// Class with static utility functions
+///
+class cmQtAutoGeneratorUtil
+{
+public:
+  // Returns a file name (without directory) that consists in part
+  // of the source file name and in part of a Base64 encoded checksum
+  // of the source file path
+  static std::string BuildFileBase(const std::string& filePath,
+                                   const std::string& parenDirCSource,
+                                   const std::string& parenDirCBinary,
+                                   const std::string& parenDirPSource,
+                                   const std::string& parenDirPBinary);
+};
+
 class cmQtAutoGenerators
 {
 public:
@@ -51,7 +66,7 @@ private:
                   const std::string& uiOutputFile);
   bool GenerateQrcFiles();
   bool GenerateQrc(const std::string& qrcInputFile,
-                   const std::string& qrcOutputFile);
+                   const std::string& qrcOutputFile, bool unique_n);
   void ParseCppFile(
     const std::string& absFilename,
     const std::vector<std::string>& headerExtensions,
@@ -83,7 +98,9 @@ private:
 
   void Init();
 
-  std::string SourceRelativePath(const std::string& filename);
+  std::string BuildFileName(const std::string& filename,
+                            const std::string& prefix,
+                            const std::string& suffix);
 
   bool NameCollisionTest(const std::map<std::string, std::string>& genFiles,
                          std::multimap<std::string, std::string>& collisions);
-- 
2.8.1

-- 

Powered by www.kitware.com

Please keep messages on-topic and check the CMake FAQ at: 
http://www.cmake.org/Wiki/CMake_FAQ

Kitware offers various services to support the CMake community. For more 
information on each offering, please visit:

CMake Support: http://cmake.org/cmake/help/support.html
CMake Consulting: http://cmake.org/cmake/help/consulting.html
CMake Training Courses: http://cmake.org/cmake/help/training.html

Visit other Kitware open-source projects at 
http://www.kitware.com/opensource/opensource.html

Follow this link to subscribe/unsubscribe:
http://public.kitware.com/mailman/listinfo/cmake-developers

Reply via email to