فهرست منبع

CPack/Deb: Add ability to split out debug symbols into .ddeb package

Andrew Fuller 7 سال پیش
والد
کامیت
d8a3939aef

+ 20 - 0
Help/cpack_gen/deb.rst

@@ -518,6 +518,26 @@ List of CPack Deb generator specific variables:
    This value is not interpreted. It is possible to pass an optional
    revision number of the referenced source package as well.
 
+Packaging of debug information
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Dbgsym packages contain debug symbols for debugging packaged binaries.
+
+Dbgsym packaging has its own set of variables:
+
+.. variable:: CPACK_DEBIAN_DEBUGINFO_PACKAGE
+              CPACK_DEBIAN_<component>_DEBUGINFO_PACKAGE
+
+ Enable generation of dbgsym .ddeb package(s).
+
+ * Mandatory : NO
+ * Default   : OFF
+
+.. note::
+
+ Binaries must contain debug symbols before packaging so use either ``Debug``
+ or ``RelWithDebInfo`` for :variable:`CMAKE_BUILD_TYPE` variable value.
+
 Building Debian packages on Windows
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 

+ 6 - 0
Help/release/dev/cpack-deb-dbgsym-ddeb.rst

@@ -0,0 +1,6 @@
+cpack-deb-dbgsym-ddeb
+---------------------
+
+* The :cpack_gen:`CPack Deb Generator` learned to split debug symbols into
+  a corresponding .ddeb package when ``CPACK_DEBIAN_DEBUGINFO_PACKAGE`` is
+  set.

+ 96 - 4
Modules/Internal/CPack/CPackDeb.cmake

@@ -64,6 +64,8 @@ function(cpack_deb_prepare_package_vars)
   endif()
 
   set(WDIR "${CPACK_TOPLEVEL_DIRECTORY}/${CPACK_PACKAGE_FILE_NAME}${CPACK_DEB_PACKAGE_COMPONENT_PART_PATH}")
+  set(DBGSYMDIR "${CPACK_TOPLEVEL_DIRECTORY}/${CPACK_PACKAGE_FILE_NAME}${CPACK_DEB_PACKAGE_COMPONENT_PART_PATH}-dbgsym")
+  file(REMOVE_RECURSE "${DBGSYMDIR}")
 
   # per component automatic discover: some of the component might not have
   # binaries.
@@ -80,7 +82,10 @@ function(cpack_deb_prepare_package_vars)
     endif()
   endif()
 
-  if(CPACK_DEBIAN_PACKAGE_SHLIBDEPS OR CPACK_DEBIAN_PACKAGE_GENERATE_SHLIBS)
+  cpack_deb_variable_fallback("CPACK_DEBIAN_DEBUGINFO_PACKAGE"
+    "CPACK_DEBIAN_${_local_component_name}_DEBUGINFO_PACKAGE"
+    "CPACK_DEBIAN_DEBUGINFO_PACKAGE")
+  if(CPACK_DEBIAN_PACKAGE_SHLIBDEPS OR CPACK_DEBIAN_PACKAGE_GENERATE_SHLIBS OR CPACK_DEBIAN_DEBUGINFO_PACKAGE)
     # Generating binary list - Get type of all install files
     file(GLOB_RECURSE FILE_PATHS_ LIST_DIRECTORIES false RELATIVE "${WDIR}" "${WDIR}/*")
 
@@ -114,6 +119,81 @@ function(cpack_deb_prepare_package_vars)
         string(REGEX MATCH "(^.*):" _FILE_NAME "${_FILE}")
         list(APPEND CPACK_DEB_SHARED_OBJECT_FILES "${CMAKE_MATCH_1}")
       endif()
+      if(_FILE MATCHES "ELF.*not stripped")
+        string(REGEX MATCH "(^.*):" _FILE_NAME "${_FILE}")
+        list(APPEND CPACK_DEB_UNSTRIPPED_FILES "${CMAKE_MATCH_1}")
+      endif()
+    endforeach()
+  endif()
+
+  find_program(READELF_EXECUTABLE NAMES readelf)
+
+  if(CPACK_DEBIAN_DEBUGINFO_PACKAGE AND CPACK_DEB_UNSTRIPPED_FILES)
+    find_program(OBJCOPY_EXECUTABLE NAMES objcopy)
+
+    if(NOT OBJCOPY_EXECUTABLE)
+      message(FATAL_ERROR "debuginfo packages require the objcopy tool")
+    endif()
+    if(NOT READELF_EXECUTABLE)
+      message(FATAL_ERROR "debuginfo packages require the readelf tool")
+    endif()
+
+    file(RELATIVE_PATH _DBGSYM_ROOT "${CPACK_TEMPORARY_DIRECTORY}" "${DBGSYMDIR}")
+    foreach(_FILE IN LISTS CPACK_DEB_UNSTRIPPED_FILES)
+
+      # Get the file's Build ID
+      execute_process(COMMAND env LC_ALL=C ${READELF_EXECUTABLE} -n "${_FILE}"
+        WORKING_DIRECTORY "${CPACK_TEMPORARY_DIRECTORY}"
+        OUTPUT_VARIABLE READELF_OUTPUT
+        RESULT_VARIABLE READELF_RESULT
+        ERROR_VARIABLE READELF_ERROR
+        OUTPUT_STRIP_TRAILING_WHITESPACE )
+      if(NOT READELF_RESULT EQUAL 0)
+        message(FATAL_ERROR "CPackDeb: readelf: '${READELF_ERROR}';\n"
+            "executed command: '${READELF_EXECUTABLE} -n ${_FILE}'")
+      endif()
+      if(READELF_OUTPUT MATCHES "Build ID: ([0-9a-zA-Z][0-9a-zA-Z])([0-9a-zA-Z]*)")
+        set(_BUILD_ID_START ${CMAKE_MATCH_1})
+        set(_BUILD_ID_REMAINING ${CMAKE_MATCH_2})
+        list(APPEND BUILD_IDS ${_BUILD_ID_START}${_BUILD_ID_REMAINING})
+      else()
+        message(FATAL_ERROR "Unable to determine Build ID for ${_FILE}")
+      endif()
+
+      # Split out the debug symbols from the binaries
+      set(_FILE_DBGSYM ${_DBGSYM_ROOT}/usr/lib/debug/.build-id/${_BUILD_ID_START}/${_BUILD_ID_REMAINING}.debug)
+      get_filename_component(_OUT_DIR "${_FILE_DBGSYM}" DIRECTORY)
+      file(MAKE_DIRECTORY "${CPACK_TEMPORARY_DIRECTORY}/${_OUT_DIR}")
+      execute_process(COMMAND ${OBJCOPY_EXECUTABLE} --only-keep-debug "${_FILE}" "${_FILE_DBGSYM}"
+        WORKING_DIRECTORY "${CPACK_TEMPORARY_DIRECTORY}"
+        OUTPUT_VARIABLE OBJCOPY_OUTPUT
+        RESULT_VARIABLE OBJCOPY_RESULT
+        ERROR_VARIABLE OBJCOPY_ERROR
+        OUTPUT_STRIP_TRAILING_WHITESPACE )
+      if(NOT OBJCOPY_RESULT EQUAL 0)
+        message(FATAL_ERROR "CPackDeb: objcopy: '${OBJCOPY_ERROR}';\n"
+            "executed command: '${OBJCOPY_EXECUTABLE} --only-keep-debug ${_FILE} ${_FILE_DBGSYM}'")
+      endif()
+      execute_process(COMMAND ${OBJCOPY_EXECUTABLE} --strip-unneeded ${_FILE}
+        WORKING_DIRECTORY "${CPACK_TEMPORARY_DIRECTORY}"
+        OUTPUT_VARIABLE OBJCOPY_OUTPUT
+        RESULT_VARIABLE OBJCOPY_RESULT
+        ERROR_VARIABLE OBJCOPY_ERROR
+        OUTPUT_STRIP_TRAILING_WHITESPACE )
+      if(NOT OBJCOPY_RESULT EQUAL 0)
+        message(FATAL_ERROR "CPackDeb: objcopy: '${OBJCOPY_ERROR}';\n"
+            "executed command: '${OBJCOPY_EXECUTABLE} --strip-debug ${_FILE}'")
+      endif()
+      execute_process(COMMAND ${OBJCOPY_EXECUTABLE} --add-gnu-debuglink=${_FILE_DBGSYM} ${_FILE}
+        WORKING_DIRECTORY "${CPACK_TEMPORARY_DIRECTORY}"
+        OUTPUT_VARIABLE OBJCOPY_OUTPUT
+        RESULT_VARIABLE OBJCOPY_RESULT
+        ERROR_VARIABLE OBJCOPY_ERROR
+        OUTPUT_STRIP_TRAILING_WHITESPACE )
+      if(NOT OBJCOPY_RESULT EQUAL 0)
+        message(FATAL_ERROR "CPackDeb: objcopy: '${OBJCOPY_ERROR}';\n"
+            "executed command: '${OBJCOPY_EXECUTABLE} --add-gnu-debuglink=${_FILE_DBGSYM} ${_FILE}'")
+      endif()
     endforeach()
   endif()
 
@@ -450,8 +530,6 @@ function(cpack_deb_prepare_package_vars)
     set(CPACK_DEBIAN_PACKAGE_GENERATE_SHLIBS_POLICY "=")
   endif()
 
-  find_program(READELF_EXECUTABLE NAMES readelf)
-
   if(CPACK_DEBIAN_PACKAGE_GENERATE_SHLIBS)
     if(READELF_EXECUTABLE)
       foreach(_FILE IN LISTS CPACK_DEB_SHARED_OBJECT_FILES)
@@ -507,18 +585,24 @@ function(cpack_deb_prepare_package_vars)
       # <foo>_<VersionNumber>-<DebianRevisionNumber>_<DebianArchitecture>.deb
       set(CPACK_OUTPUT_FILE_NAME
         "${CPACK_DEBIAN_PACKAGE_NAME}_${CPACK_DEBIAN_PACKAGE_VERSION}_${CPACK_DEBIAN_PACKAGE_ARCHITECTURE}.deb")
+      set(CPACK_DBGSYM_OUTPUT_FILE_NAME
+        "${CPACK_DEBIAN_PACKAGE_NAME}-dbgsym_${CPACK_DEBIAN_PACKAGE_VERSION}_${CPACK_DEBIAN_PACKAGE_ARCHITECTURE}.ddeb")
     else()
       if(NOT CPACK_DEBIAN_FILE_NAME MATCHES ".*\\.(deb|ipk)")
         message(FATAL_ERROR "'${CPACK_DEBIAN_FILE_NAME}' is not a valid DEB package file name as it must end with '.deb' or '.ipk'!")
       endif()
 
       set(CPACK_OUTPUT_FILE_NAME "${CPACK_DEBIAN_FILE_NAME}")
+      string(REGEX REPLACE "\.deb$" "-dbgsym.ddeb" CPACK_DBGSYM_OUTPUT_FILE_NAME "${CPACK_DEBIAN_FILE_NAME}")
     endif()
 
     set(CPACK_TEMPORARY_PACKAGE_FILE_NAME "${CPACK_TOPLEVEL_DIRECTORY}/${CPACK_OUTPUT_FILE_NAME}")
     get_filename_component(BINARY_DIR "${CPACK_OUTPUT_FILE_PATH}" DIRECTORY)
     set(CPACK_OUTPUT_FILE_PATH "${BINARY_DIR}/${CPACK_OUTPUT_FILE_NAME}")
-  endif() # else() back compatibility - don't change the name
+  else()
+    # back compatibility - don't change the name
+    string(REGEX REPLACE "\.deb$" "-dbgsym.ddeb" CPACK_DBGSYM_OUTPUT_FILE_NAME "${CPACK_OUTPUT_FILE_NAME}")
+  endif()
 
   # Print out some debug information if we were asked for that
   if(CPACK_DEBIAN_PACKAGE_DEBUG)
@@ -579,6 +663,14 @@ function(cpack_deb_prepare_package_vars)
   set(GEN_CPACK_DEBIAN_GENERATE_POSTINST "${CPACK_DEBIAN_GENERATE_POSTINST}" PARENT_SCOPE)
   set(GEN_CPACK_DEBIAN_GENERATE_POSTRM "${CPACK_DEBIAN_GENERATE_POSTRM}" PARENT_SCOPE)
   set(GEN_WDIR "${WDIR}" PARENT_SCOPE)
+
+  set(GEN_CPACK_DEBIAN_DEBUGINFO_PACKAGE "${CPACK_DEBIAN_DEBUGINFO_PACKAGE}" PARENT_SCOPE)
+  if(BUILD_IDS)
+    set(GEN_DBGSYMDIR "${DBGSYMDIR}" PARENT_SCOPE)
+    set(GEN_CPACK_DBGSYM_OUTPUT_FILE_NAME "${CPACK_DBGSYM_OUTPUT_FILE_NAME}" PARENT_SCOPE)
+    string(REPLACE ";" " " BUILD_IDS "${BUILD_IDS}")
+    set(GEN_BUILD_IDS "${BUILD_IDS}" PARENT_SCOPE)
+  endif()
 endfunction()
 
 cpack_deb_prepare_package_vars()

+ 560 - 384
Source/CPack/cmCPackDebGenerator.cxx

@@ -12,11 +12,430 @@
 #include "cm_sys_stat.h"
 
 #include "cmsys/Glob.hxx"
+#include <map>
 #include <ostream>
 #include <set>
 #include <string.h>
 #include <utility>
 
+namespace {
+
+class DebGenerator
+{
+public:
+  DebGenerator(cmCPackLog* logger, std::string const& outputName,
+               std::string const& workDir, std::string const& topLevelDir,
+               std::string const& temporaryDir,
+               const char* debianCompressionType,
+               const char* debianArchiveType,
+               std::map<std::string, std::string> const& controlValues,
+               bool genShLibs, std::string const& shLibsFilename,
+               bool genPostInst, std::string const& postInst, bool genPostRm,
+               std::string const& postRm, const char* controlExtra,
+               bool permissionStrctPolicy,
+               std::vector<std::string> const& packageFiles);
+
+  bool generate() const;
+
+private:
+  void generateDebianBinaryFile() const;
+  void generateControlFile() const;
+  bool generateDataTar() const;
+  std::string generateMD5File() const;
+  bool generateControlTar(std::string const& md5Filename) const;
+  bool generateDeb() const;
+
+  cmCPackLog* Logger;
+  const std::string OutputName;
+  const std::string WorkDir;
+  std::string CompressionSuffix;
+  const std::string TopLevelDir;
+  const std::string TemporaryDir;
+  const char* DebianArchiveType;
+  const std::map<std::string, std::string> ControlValues;
+  const bool GenShLibs;
+  const std::string ShLibsFilename;
+  const bool GenPostInst;
+  const std::string PostInst;
+  const bool GenPostRm;
+  const std::string PostRm;
+  const char* ControlExtra;
+  const bool PermissionStrictPolicy;
+  const std::vector<std::string> PackageFiles;
+  cmArchiveWrite::Compress TarCompressionType;
+};
+
+DebGenerator::DebGenerator(
+  cmCPackLog* logger, std::string const& outputName,
+  std::string const& workDir, std::string const& topLevelDir,
+  std::string const& temporaryDir, const char* debianCompressionType,
+  const char* debianArchiveType,
+  std::map<std::string, std::string> const& controlValues, bool genShLibs,
+  std::string const& shLibsFilename, bool genPostInst,
+  std::string const& postInst, bool genPostRm, std::string const& postRm,
+  const char* controlExtra, bool permissionStrictPolicy,
+  std::vector<std::string> const& packageFiles)
+  : Logger(logger)
+  , OutputName(outputName)
+  , WorkDir(workDir)
+  , TopLevelDir(topLevelDir)
+  , TemporaryDir(temporaryDir)
+  , DebianArchiveType(debianArchiveType ? debianArchiveType : "paxr")
+  , ControlValues(controlValues)
+  , GenShLibs(genShLibs)
+  , ShLibsFilename(shLibsFilename)
+  , GenPostInst(genPostInst)
+  , PostInst(postInst)
+  , GenPostRm(genPostRm)
+  , PostRm(postRm)
+  , ControlExtra(controlExtra)
+  , PermissionStrictPolicy(permissionStrictPolicy)
+  , PackageFiles(packageFiles)
+{
+  if (!debianCompressionType) {
+    debianCompressionType = "gzip";
+  }
+
+  if (!strcmp(debianCompressionType, "lzma")) {
+    CompressionSuffix = ".lzma";
+    TarCompressionType = cmArchiveWrite::CompressLZMA;
+  } else if (!strcmp(debianCompressionType, "xz")) {
+    CompressionSuffix = ".xz";
+    TarCompressionType = cmArchiveWrite::CompressXZ;
+  } else if (!strcmp(debianCompressionType, "bzip2")) {
+    CompressionSuffix = ".bz2";
+    TarCompressionType = cmArchiveWrite::CompressBZip2;
+  } else if (!strcmp(debianCompressionType, "gzip")) {
+    CompressionSuffix = ".gz";
+    TarCompressionType = cmArchiveWrite::CompressGZip;
+  } else if (!strcmp(debianCompressionType, "none")) {
+    CompressionSuffix.clear();
+    TarCompressionType = cmArchiveWrite::CompressNone;
+  } else {
+    cmCPackLogger(cmCPackLog::LOG_ERROR,
+                  "Error unrecognized compression type: "
+                    << debianCompressionType << std::endl);
+  }
+}
+
+bool DebGenerator::generate() const
+{
+  generateDebianBinaryFile();
+  generateControlFile();
+  if (!generateDataTar()) {
+    return false;
+  }
+  std::string md5Filename = generateMD5File();
+  if (!generateControlTar(md5Filename)) {
+    return false;
+  }
+  return generateDeb();
+}
+
+void DebGenerator::generateDebianBinaryFile() const
+{
+  // debian-binary file
+  const std::string dbfilename = WorkDir + "/debian-binary";
+  cmGeneratedFileStream out(dbfilename);
+  out << "2.0";
+  out << std::endl; // required for valid debian package
+}
+
+void DebGenerator::generateControlFile() const
+{
+  std::string ctlfilename = WorkDir + "/control";
+
+  cmGeneratedFileStream out(ctlfilename);
+  for (auto const& kv : ControlValues) {
+    out << kv.first << ": " << kv.second << "\n";
+  }
+
+  unsigned long totalSize = 0;
+  {
+    std::string dirName = TemporaryDir;
+    dirName += '/';
+    for (std::string const& file : PackageFiles) {
+      totalSize += cmSystemTools::FileLength(file);
+    }
+  }
+  out << "Installed-Size: " << (totalSize + 1023) / 1024 << "\n";
+  out << std::endl;
+}
+
+bool DebGenerator::generateDataTar() const
+{
+  std::string filename_data_tar = WorkDir + "/data.tar" + CompressionSuffix;
+  cmGeneratedFileStream fileStream_data_tar;
+  fileStream_data_tar.Open(filename_data_tar, false, true);
+  if (!fileStream_data_tar) {
+    cmCPackLogger(cmCPackLog::LOG_ERROR,
+                  "Error opening the file \""
+                    << filename_data_tar << "\" for writing" << std::endl);
+    return false;
+  }
+  cmArchiveWrite data_tar(fileStream_data_tar, TarCompressionType,
+                          DebianArchiveType);
+
+  // uid/gid should be the one of the root user, and this root user has
+  // always uid/gid equal to 0.
+  data_tar.SetUIDAndGID(0u, 0u);
+  data_tar.SetUNAMEAndGNAME("root", "root");
+
+  // now add all directories which have to be compressed
+  // collect all top level install dirs for that
+  // e.g. /opt/bin/foo, /usr/bin/bar and /usr/bin/baz would
+  // give /usr and /opt
+  size_t topLevelLength = WorkDir.length();
+  cmCPackLogger(cmCPackLog::LOG_DEBUG,
+                "WDIR: \"" << WorkDir << "\", length = " << topLevelLength
+                           << std::endl);
+  std::set<std::string> orderedFiles;
+
+  // we have to reconstruct the parent folders as well
+
+  for (std::string currentPath : PackageFiles) {
+    while (currentPath != WorkDir) {
+      // the last one IS WorkDir, but we do not want this one:
+      // XXX/application/usr/bin/myprogram with GEN_WDIR=XXX/application
+      // should not add XXX/application
+      orderedFiles.insert(currentPath);
+      currentPath = cmSystemTools::CollapseCombinedPath(currentPath, "..");
+    }
+  }
+
+  for (std::string const& file : orderedFiles) {
+    cmCPackLogger(cmCPackLog::LOG_DEBUG,
+                  "FILEIT: \"" << file << "\"" << std::endl);
+    std::string::size_type slashPos = file.find('/', topLevelLength + 1);
+    std::string relativeDir =
+      file.substr(topLevelLength, slashPos - topLevelLength);
+    cmCPackLogger(cmCPackLog::LOG_DEBUG,
+                  "RELATIVEDIR: \"" << relativeDir << "\"" << std::endl);
+
+#ifdef WIN32
+    std::string mode_t_adt_filename = file + ":cmake_mode_t";
+    cmsys::ifstream permissionStream(mode_t_adt_filename.c_str());
+
+    mode_t permissions = 0;
+
+    if (permissionStream) {
+      permissionStream >> std::oct >> permissions;
+    }
+
+    if (permissions != 0) {
+      data_tar.SetPermissions(permissions);
+    } else if (cmSystemTools::FileIsDirectory(file)) {
+      data_tar.SetPermissions(0755);
+    } else {
+      data_tar.ClearPermissions();
+    }
+#endif
+
+    // do not recurse because the loop will do it
+    if (!data_tar.Add(file, topLevelLength, ".", false)) {
+      cmCPackLogger(cmCPackLog::LOG_ERROR,
+                    "Problem adding file to tar:"
+                      << std::endl
+                      << "#top level directory: " << WorkDir << std::endl
+                      << "#file: " << file << std::endl
+                      << "#error:" << data_tar.GetError() << std::endl);
+      return false;
+    }
+  }
+  return true;
+}
+
+std::string DebGenerator::generateMD5File() const
+{
+  std::string md5filename = WorkDir + "/md5sums";
+
+  cmGeneratedFileStream out(md5filename);
+
+  std::string topLevelWithTrailingSlash = TemporaryDir;
+  topLevelWithTrailingSlash += '/';
+  for (std::string const& file : PackageFiles) {
+    // hash only regular files
+    if (cmSystemTools::FileIsDirectory(file) ||
+        cmSystemTools::FileIsSymlink(file)) {
+      continue;
+    }
+
+    std::string output =
+      cmSystemTools::ComputeFileHash(file, cmCryptoHash::AlgoMD5);
+    if (output.empty()) {
+      cmCPackLogger(cmCPackLog::LOG_ERROR,
+                    "Problem computing the md5 of " << file << std::endl);
+    }
+
+    output += "  " + file + "\n";
+    // debian md5sums entries are like this:
+    // 014f3604694729f3bf19263bac599765  usr/bin/ccmake
+    // thus strip the full path (with the trailing slash)
+    cmSystemTools::ReplaceString(output, topLevelWithTrailingSlash.c_str(),
+                                 "");
+    out << output;
+  }
+  // each line contains a eol.
+  // Do not end the md5sum file with yet another (invalid)
+  return md5filename;
+}
+
+bool DebGenerator::generateControlTar(std::string const& md5Filename) const
+{
+  std::string filename_control_tar = WorkDir + "/control.tar.gz";
+
+  cmGeneratedFileStream fileStream_control_tar;
+  fileStream_control_tar.Open(filename_control_tar, false, true);
+  if (!fileStream_control_tar) {
+    cmCPackLogger(cmCPackLog::LOG_ERROR,
+                  "Error opening the file \""
+                    << filename_control_tar << "\" for writing" << std::endl);
+    return false;
+  }
+  cmArchiveWrite control_tar(fileStream_control_tar,
+                             cmArchiveWrite::CompressGZip, DebianArchiveType);
+
+  // sets permissions and uid/gid for the files
+  control_tar.SetUIDAndGID(0u, 0u);
+  control_tar.SetUNAMEAndGNAME("root", "root");
+
+  /* permissions are set according to
+  https://www.debian.org/doc/debian-policy/ch-files.html#s-permissions-owners
+  and
+  https://lintian.debian.org/tags/control-file-has-bad-permissions.html
+  */
+  const mode_t permission644 = 0644;
+  const mode_t permissionExecute = 0111;
+  const mode_t permission755 = permission644 | permissionExecute;
+
+  // for md5sum and control (that we have generated here), we use 644
+  // (RW-R--R--)
+  // so that deb lintian doesn't warn about it
+  control_tar.SetPermissions(permission644);
+
+  // adds control and md5sums
+  if (!control_tar.Add(md5Filename, WorkDir.length(), ".") ||
+      !control_tar.Add(WorkDir + "/control", WorkDir.length(), ".")) {
+    cmCPackLogger(cmCPackLog::LOG_ERROR,
+                  "Error adding file to tar:"
+                    << std::endl
+                    << "#top level directory: " << WorkDir << std::endl
+                    << "#file: \"control\" or \"md5sums\"" << std::endl
+                    << "#error:" << control_tar.GetError() << std::endl);
+    return false;
+  }
+
+  // adds generated shlibs file
+  if (GenShLibs) {
+    if (!control_tar.Add(ShLibsFilename, WorkDir.length(), ".")) {
+      cmCPackLogger(cmCPackLog::LOG_ERROR,
+                    "Error adding file to tar:"
+                      << std::endl
+                      << "#top level directory: " << WorkDir << std::endl
+                      << "#file: \"shlibs\"" << std::endl
+                      << "#error:" << control_tar.GetError() << std::endl);
+      return false;
+    }
+  }
+
+  // adds LDCONFIG related files
+  if (GenPostInst) {
+    control_tar.SetPermissions(permission755);
+    if (!control_tar.Add(PostInst, WorkDir.length(), ".")) {
+      cmCPackLogger(cmCPackLog::LOG_ERROR,
+                    "Error adding file to tar:"
+                      << std::endl
+                      << "#top level directory: " << WorkDir << std::endl
+                      << "#file: \"postinst\"" << std::endl
+                      << "#error:" << control_tar.GetError() << std::endl);
+      return false;
+    }
+    control_tar.SetPermissions(permission644);
+  }
+
+  if (GenPostRm) {
+    control_tar.SetPermissions(permission755);
+    if (!control_tar.Add(PostRm, WorkDir.length(), ".")) {
+      cmCPackLogger(cmCPackLog::LOG_ERROR,
+                    "Error adding file to tar:"
+                      << std::endl
+                      << "#top level directory: " << WorkDir << std::endl
+                      << "#file: \"postinst\"" << std::endl
+                      << "#error:" << control_tar.GetError() << std::endl);
+      return false;
+    }
+    control_tar.SetPermissions(permission644);
+  }
+
+  // for the other files, we use
+  // -either the original permission on the files
+  // -either a permission strictly defined by the Debian policies
+  if (ControlExtra) {
+    // permissions are now controlled by the original file permissions
+
+    static const char* strictFiles[] = { "config", "postinst", "postrm",
+                                         "preinst", "prerm" };
+    std::set<std::string> setStrictFiles(
+      strictFiles, strictFiles + sizeof(strictFiles) / sizeof(strictFiles[0]));
+
+    // default
+    control_tar.ClearPermissions();
+
+    std::vector<std::string> controlExtraList;
+    cmSystemTools::ExpandListArgument(ControlExtra, controlExtraList);
+    for (std::string const& i : controlExtraList) {
+      std::string filenamename = cmsys::SystemTools::GetFilenameName(i);
+      std::string localcopy = WorkDir + "/" + filenamename;
+
+      if (PermissionStrictPolicy) {
+        control_tar.SetPermissions(
+          setStrictFiles.count(filenamename) ? permission755 : permission644);
+      }
+
+      // if we can copy the file, it means it does exist, let's add it:
+      if (cmsys::SystemTools::CopyFileIfDifferent(i, localcopy)) {
+        control_tar.Add(localcopy, WorkDir.length(), ".");
+      }
+    }
+  }
+
+  return true;
+}
+
+bool DebGenerator::generateDeb() const
+{
+  // ar -r your-package-name.deb debian-binary control.tar.* data.tar.*
+  // A debian package .deb is simply an 'ar' archive. The only subtle
+  // difference is that debian uses the BSD ar style archive whereas most
+  // Linux distro have a GNU ar.
+  // See http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=161593 for more info
+  std::string const outputPath = TopLevelDir + "/" + OutputName;
+  std::string const tlDir = WorkDir + "/";
+  cmGeneratedFileStream debStream;
+  debStream.Open(outputPath, false, true);
+  cmArchiveWrite deb(debStream, cmArchiveWrite::CompressNone, "arbsd");
+
+  // uid/gid should be the one of the root user, and this root user has
+  // always uid/gid equal to 0.
+  deb.SetUIDAndGID(0u, 0u);
+  deb.SetUNAMEAndGNAME("root", "root");
+
+  if (!deb.Add(tlDir + "debian-binary", tlDir.length()) ||
+      !deb.Add(tlDir + "control.tar.gz", tlDir.length()) ||
+      !deb.Add(tlDir + "data.tar" + CompressionSuffix, tlDir.length())) {
+    cmCPackLogger(cmCPackLog::LOG_ERROR,
+                  "Error creating debian package:"
+                    << std::endl
+                    << "#top level directory: " << TopLevelDir << std::endl
+                    << "#file: " << OutputName << std::endl
+                    << "#error:" << deb.GetError() << std::endl);
+    return false;
+  }
+  return true;
+}
+
+} // end anonymous namespace
+
 cmCPackDebGenerator::cmCPackDebGenerator()
 {
 }
@@ -68,18 +487,20 @@ int cmCPackDebGenerator::PackageOnePack(std::string const& initialTopLevel,
     return retval;
   }
 
-  cmsys::Glob gl;
-  std::string findExpr(this->GetOption("GEN_WDIR"));
-  findExpr += "/*";
-  gl.RecurseOn();
-  gl.SetRecurseListDirs(true);
-  if (!gl.FindFiles(findExpr)) {
-    cmCPackLogger(cmCPackLog::LOG_ERROR,
-                  "Cannot find any files in the installed directory"
-                    << std::endl);
-    return 0;
+  { // Isolate globbing of binaries vs. dbgsyms
+    cmsys::Glob gl;
+    std::string findExpr(this->GetOption("GEN_WDIR"));
+    findExpr += "/*";
+    gl.RecurseOn();
+    gl.SetRecurseListDirs(true);
+    if (!gl.FindFiles(findExpr)) {
+      cmCPackLogger(cmCPackLog::LOG_ERROR,
+                    "Cannot find any files in the installed directory"
+                      << std::endl);
+      return 0;
+    }
+    packageFiles = gl.GetFiles();
   }
-  packageFiles = gl.GetFiles();
 
   int res = createDeb();
   if (res != 1) {
@@ -90,6 +511,32 @@ int cmCPackDebGenerator::PackageOnePack(std::string const& initialTopLevel,
   packageFileName += "/";
   packageFileName += this->GetOption("GEN_CPACK_OUTPUT_FILE_NAME");
   packageFileNames.push_back(std::move(packageFileName));
+
+  if (this->IsOn("GEN_CPACK_DEBIAN_DEBUGINFO_PACKAGE")) {
+    cmsys::Glob gl;
+    std::string findExpr(this->GetOption("GEN_DBGSYMDIR"));
+    findExpr += "/*";
+    gl.RecurseOn();
+    gl.SetRecurseListDirs(true);
+    if (!gl.FindFiles(findExpr)) {
+      cmCPackLogger(cmCPackLog::LOG_ERROR,
+                    "Cannot find any files in the installed directory"
+                      << std::endl);
+      return 0;
+    }
+    packageFiles = gl.GetFiles();
+
+    res = createDbgsymDDeb();
+    if (res != 1) {
+      retval = 0;
+    }
+    // add the generated package to package file names list
+    packageFileName = this->GetOption("CPACK_TOPLEVEL_DIRECTORY");
+    packageFileName += "/";
+    packageFileName += this->GetOption("GEN_CPACK_DBGSYM_OUTPUT_FILE_NAME");
+    packageFileNames.push_back(std::move(packageFileName));
+  }
+
   return retval;
 }
 
@@ -234,112 +681,81 @@ int cmCPackDebGenerator::PackageFiles()
 
 int cmCPackDebGenerator::createDeb()
 {
-  // debian-binary file
-  const std::string strGenWDIR(this->GetOption("GEN_WDIR"));
-  const std::string dbfilename = strGenWDIR + "/debian-binary";
-  { // the scope is needed for cmGeneratedFileStream
-    cmGeneratedFileStream out(dbfilename);
-    out << "2.0";
-    out << std::endl; // required for valid debian package
-  }
-
-  // control file
-  std::string ctlfilename = strGenWDIR + "/control";
+  std::map<std::string, std::string> controlValues;
 
   // debian policy enforce lower case for package name
-  // mandatory entries:
-  std::string debian_pkg_name = cmsys::SystemTools::LowerCase(
+  controlValues["Package"] = cmsys::SystemTools::LowerCase(
     this->GetOption("GEN_CPACK_DEBIAN_PACKAGE_NAME"));
-  const char* debian_pkg_version =
+  controlValues["Version"] =
     this->GetOption("GEN_CPACK_DEBIAN_PACKAGE_VERSION");
-  const char* debian_pkg_section =
+  controlValues["Section"] =
     this->GetOption("GEN_CPACK_DEBIAN_PACKAGE_SECTION");
-  const char* debian_pkg_priority =
+  controlValues["Priority"] =
     this->GetOption("GEN_CPACK_DEBIAN_PACKAGE_PRIORITY");
-  const char* debian_pkg_arch =
+  controlValues["Architecture"] =
     this->GetOption("GEN_CPACK_DEBIAN_PACKAGE_ARCHITECTURE");
-  const char* maintainer =
+  controlValues["Maintainer"] =
     this->GetOption("GEN_CPACK_DEBIAN_PACKAGE_MAINTAINER");
-  const char* desc = this->GetOption("GEN_CPACK_DEBIAN_PACKAGE_DESCRIPTION");
+  controlValues["Description"] =
+    this->GetOption("GEN_CPACK_DEBIAN_PACKAGE_DESCRIPTION");
 
-  // optional entries
+  const char* debian_pkg_source =
+    this->GetOption("GEN_CPACK_DEBIAN_PACKAGE_SOURCE");
+  if (debian_pkg_source && *debian_pkg_source) {
+    controlValues["Source"] = debian_pkg_source;
+  }
   const char* debian_pkg_dep =
     this->GetOption("GEN_CPACK_DEBIAN_PACKAGE_DEPENDS");
+  if (debian_pkg_dep && *debian_pkg_dep) {
+    controlValues["Depends"] = debian_pkg_dep;
+  }
   const char* debian_pkg_rec =
     this->GetOption("GEN_CPACK_DEBIAN_PACKAGE_RECOMMENDS");
+  if (debian_pkg_rec && *debian_pkg_rec) {
+    controlValues["Recommends"] = debian_pkg_rec;
+  }
   const char* debian_pkg_sug =
     this->GetOption("GEN_CPACK_DEBIAN_PACKAGE_SUGGESTS");
+  if (debian_pkg_sug && *debian_pkg_sug) {
+    controlValues["Suggests"] = debian_pkg_sug;
+  }
   const char* debian_pkg_url =
     this->GetOption("GEN_CPACK_DEBIAN_PACKAGE_HOMEPAGE");
+  if (debian_pkg_url && *debian_pkg_url) {
+    controlValues["Homepage"] = debian_pkg_url;
+  }
   const char* debian_pkg_predep =
     this->GetOption("GEN_CPACK_DEBIAN_PACKAGE_PREDEPENDS");
+  if (debian_pkg_predep && *debian_pkg_predep) {
+    controlValues["Pre-Depends"] = debian_pkg_predep;
+  }
   const char* debian_pkg_enhances =
     this->GetOption("GEN_CPACK_DEBIAN_PACKAGE_ENHANCES");
+  if (debian_pkg_enhances && *debian_pkg_enhances) {
+    controlValues["Enhances"] = debian_pkg_enhances;
+  }
   const char* debian_pkg_breaks =
     this->GetOption("GEN_CPACK_DEBIAN_PACKAGE_BREAKS");
+  if (debian_pkg_breaks && *debian_pkg_breaks) {
+    controlValues["Breaks"] = debian_pkg_breaks;
+  }
   const char* debian_pkg_conflicts =
     this->GetOption("GEN_CPACK_DEBIAN_PACKAGE_CONFLICTS");
+  if (debian_pkg_conflicts && *debian_pkg_conflicts) {
+    controlValues["Conflicts"] = debian_pkg_conflicts;
+  }
   const char* debian_pkg_provides =
     this->GetOption("GEN_CPACK_DEBIAN_PACKAGE_PROVIDES");
+  if (debian_pkg_provides && *debian_pkg_provides) {
+    controlValues["Provides"] = debian_pkg_provides;
+  }
   const char* debian_pkg_replaces =
     this->GetOption("GEN_CPACK_DEBIAN_PACKAGE_REPLACES");
-  const char* debian_pkg_source =
-    this->GetOption("GEN_CPACK_DEBIAN_PACKAGE_SOURCE");
-
-  { // the scope is needed for cmGeneratedFileStream
-    cmGeneratedFileStream out(ctlfilename);
-    out << "Package: " << debian_pkg_name << "\n";
-    out << "Version: " << debian_pkg_version << "\n";
-    out << "Section: " << debian_pkg_section << "\n";
-    out << "Priority: " << debian_pkg_priority << "\n";
-    out << "Architecture: " << debian_pkg_arch << "\n";
-    if (debian_pkg_source && *debian_pkg_source) {
-      out << "Source: " << debian_pkg_source << "\n";
-    }
-    if (debian_pkg_dep && *debian_pkg_dep) {
-      out << "Depends: " << debian_pkg_dep << "\n";
-    }
-    if (debian_pkg_rec && *debian_pkg_rec) {
-      out << "Recommends: " << debian_pkg_rec << "\n";
-    }
-    if (debian_pkg_sug && *debian_pkg_sug) {
-      out << "Suggests: " << debian_pkg_sug << "\n";
-    }
-    if (debian_pkg_url && *debian_pkg_url) {
-      out << "Homepage: " << debian_pkg_url << "\n";
-    }
-    if (debian_pkg_predep && *debian_pkg_predep) {
-      out << "Pre-Depends: " << debian_pkg_predep << "\n";
-    }
-    if (debian_pkg_enhances && *debian_pkg_enhances) {
-      out << "Enhances: " << debian_pkg_enhances << "\n";
-    }
-    if (debian_pkg_breaks && *debian_pkg_breaks) {
-      out << "Breaks: " << debian_pkg_breaks << "\n";
-    }
-    if (debian_pkg_conflicts && *debian_pkg_conflicts) {
-      out << "Conflicts: " << debian_pkg_conflicts << "\n";
-    }
-    if (debian_pkg_provides && *debian_pkg_provides) {
-      out << "Provides: " << debian_pkg_provides << "\n";
-    }
-    if (debian_pkg_replaces && *debian_pkg_replaces) {
-      out << "Replaces: " << debian_pkg_replaces << "\n";
-    }
-    unsigned long totalSize = 0;
-    {
-      std::string dirName = this->GetOption("CPACK_TEMPORARY_DIRECTORY");
-      dirName += '/';
-      for (std::string const& file : packageFiles) {
-        totalSize += cmSystemTools::FileLength(file);
-      }
-    }
-    out << "Installed-Size: " << (totalSize + 1023) / 1024 << "\n";
-    out << "Maintainer: " << maintainer << "\n";
-    out << "Description: " << desc << "\n";
-    out << std::endl;
+  if (debian_pkg_replaces && *debian_pkg_replaces) {
+    controlValues["Replaces"] = debian_pkg_replaces;
   }
 
+  const std::string strGenWDIR(this->GetOption("GEN_WDIR"));
   const std::string shlibsfilename = strGenWDIR + "/shlibs";
 
   const char* debian_pkg_shlibs =
@@ -371,314 +787,74 @@ int cmCPackDebGenerator::createDeb()
            "fi\n";
   }
 
-  cmArchiveWrite::Compress tar_compression_type = cmArchiveWrite::CompressGZip;
-  const char* debian_compression_type =
-    this->GetOption("GEN_CPACK_DEBIAN_COMPRESSION_TYPE");
-  if (!debian_compression_type) {
-    debian_compression_type = "gzip";
-  }
-
-  std::string compression_suffix;
-  if (!strcmp(debian_compression_type, "lzma")) {
-    compression_suffix = ".lzma";
-    tar_compression_type = cmArchiveWrite::CompressLZMA;
-  } else if (!strcmp(debian_compression_type, "xz")) {
-    compression_suffix = ".xz";
-    tar_compression_type = cmArchiveWrite::CompressXZ;
-  } else if (!strcmp(debian_compression_type, "bzip2")) {
-    compression_suffix = ".bz2";
-    tar_compression_type = cmArchiveWrite::CompressBZip2;
-  } else if (!strcmp(debian_compression_type, "gzip")) {
-    compression_suffix = ".gz";
-    tar_compression_type = cmArchiveWrite::CompressGZip;
-  } else if (!strcmp(debian_compression_type, "none")) {
-    compression_suffix.clear();
-    tar_compression_type = cmArchiveWrite::CompressNone;
-  } else {
-    cmCPackLogger(cmCPackLog::LOG_ERROR,
-                  "Error unrecognized compression type: "
-                    << debian_compression_type << std::endl);
-  }
-
-  const char* debian_archive_type =
-    this->GetOption("GEN_CPACK_DEBIAN_ARCHIVE_TYPE");
-  if (!debian_archive_type) {
-    debian_archive_type = "paxr";
+  DebGenerator gen(
+    Logger, this->GetOption("GEN_CPACK_OUTPUT_FILE_NAME"), strGenWDIR,
+    this->GetOption("CPACK_TOPLEVEL_DIRECTORY"),
+    this->GetOption("CPACK_TEMPORARY_DIRECTORY"),
+    this->GetOption("GEN_CPACK_DEBIAN_COMPRESSION_TYPE"),
+    this->GetOption("GEN_CPACK_DEBIAN_ARCHIVE_TYPE"), controlValues, gen_shibs,
+    shlibsfilename, this->IsOn("GEN_CPACK_DEBIAN_GENERATE_POSTINST"), postinst,
+    this->IsOn("GEN_CPACK_DEBIAN_GENERATE_POSTRM"), postrm,
+    this->GetOption("GEN_CPACK_DEBIAN_PACKAGE_CONTROL_EXTRA"),
+    this->IsSet("GEN_CPACK_DEBIAN_PACKAGE_CONTROL_STRICT_PERMISSION"),
+    packageFiles);
+
+  if (!gen.generate()) {
+    return 0;
   }
+  return 1;
+}
 
-  std::string filename_data_tar =
-    strGenWDIR + "/data.tar" + compression_suffix;
-
-  // atomic file generation for data.tar
-  {
-    cmGeneratedFileStream fileStream_data_tar;
-    fileStream_data_tar.Open(filename_data_tar, false, true);
-    if (!fileStream_data_tar) {
-      cmCPackLogger(cmCPackLog::LOG_ERROR,
-                    "Error opening the file \""
-                      << filename_data_tar << "\" for writing" << std::endl);
-      return 0;
-    }
-    cmArchiveWrite data_tar(fileStream_data_tar, tar_compression_type,
-                            debian_archive_type);
-
-    // uid/gid should be the one of the root user, and this root user has
-    // always uid/gid equal to 0.
-    data_tar.SetUIDAndGID(0u, 0u);
-    data_tar.SetUNAMEAndGNAME("root", "root");
-
-    // now add all directories which have to be compressed
-    // collect all top level install dirs for that
-    // e.g. /opt/bin/foo, /usr/bin/bar and /usr/bin/baz would
-    // give /usr and /opt
-    size_t topLevelLength = strGenWDIR.length();
-    cmCPackLogger(cmCPackLog::LOG_DEBUG,
-                  "WDIR: \"" << strGenWDIR << "\", length = " << topLevelLength
-                             << std::endl);
-    std::set<std::string> orderedFiles;
-
-    // we have to reconstruct the parent folders as well
-
-    for (std::string currentPath : packageFiles) {
-      while (currentPath != strGenWDIR) {
-        // the last one IS strGenWDIR, but we do not want this one:
-        // XXX/application/usr/bin/myprogram with GEN_WDIR=XXX/application
-        // should not add XXX/application
-        orderedFiles.insert(currentPath);
-        currentPath = cmSystemTools::CollapseCombinedPath(currentPath, "..");
-      }
-    }
-
-    for (std::string const& file : orderedFiles) {
-      cmCPackLogger(cmCPackLog::LOG_DEBUG,
-                    "FILEIT: \"" << file << "\"" << std::endl);
-      std::string::size_type slashPos = file.find('/', topLevelLength + 1);
-      std::string relativeDir =
-        file.substr(topLevelLength, slashPos - topLevelLength);
-      cmCPackLogger(cmCPackLog::LOG_DEBUG,
-                    "RELATIVEDIR: \"" << relativeDir << "\"" << std::endl);
-
-#ifdef WIN32
-      std::string mode_t_adt_filename = file + ":cmake_mode_t";
-      cmsys::ifstream permissionStream(mode_t_adt_filename.c_str());
-
-      mode_t permissions = 0;
-
-      if (permissionStream) {
-        permissionStream >> std::oct >> permissions;
-      }
-
-      if (permissions != 0) {
-        data_tar.SetPermissions(permissions);
-      } else if (cmSystemTools::FileIsDirectory(file)) {
-        data_tar.SetPermissions(0755);
-      } else {
-        data_tar.ClearPermissions();
-      }
-#endif
-
-      // do not recurse because the loop will do it
-      if (!data_tar.Add(file, topLevelLength, ".", false)) {
-        cmCPackLogger(cmCPackLog::LOG_ERROR,
-                      "Problem adding file to tar:"
-                        << std::endl
-                        << "#top level directory: " << strGenWDIR << std::endl
-                        << "#file: " << file << std::endl
-                        << "#error:" << data_tar.GetError() << std::endl);
-        return 0;
-      }
-    }
-  } // scope for file generation
+int cmCPackDebGenerator::createDbgsymDDeb()
+{
+  // Packages containing debug symbols follow the same structure as .debs
+  // but have different metadata and content.
 
-  std::string md5filename = strGenWDIR + "/md5sums";
-  {
-    // the scope is needed for cmGeneratedFileStream
-    cmGeneratedFileStream out(md5filename);
-
-    std::string topLevelWithTrailingSlash =
-      this->GetOption("CPACK_TEMPORARY_DIRECTORY");
-    topLevelWithTrailingSlash += '/';
-    for (std::string const& file : packageFiles) {
-      // hash only regular files
-      if (cmSystemTools::FileIsDirectory(file) ||
-          cmSystemTools::FileIsSymlink(file)) {
-        continue;
-      }
+  std::map<std::string, std::string> controlValues;
+  // debian policy enforce lower case for package name
+  std::string packageNameLower = cmsys::SystemTools::LowerCase(
+    this->GetOption("GEN_CPACK_DEBIAN_PACKAGE_NAME"));
+  const char* debian_pkg_version =
+    this->GetOption("GEN_CPACK_DEBIAN_PACKAGE_VERSION");
 
-      std::string output =
-        cmSystemTools::ComputeFileHash(file, cmCryptoHash::AlgoMD5);
-      if (output.empty()) {
-        cmCPackLogger(cmCPackLog::LOG_ERROR,
-                      "Problem computing the md5 of " << file << std::endl);
-      }
+  controlValues["Package"] = packageNameLower + "-dbgsym";
+  controlValues["Package-Type"] = "ddeb";
+  controlValues["Version"] = debian_pkg_version;
+  controlValues["Auto-Built-Package"] = "debug-symbols";
+  controlValues["Depends"] = this->GetOption("GEN_CPACK_DEBIAN_PACKAGE_NAME") +
+    std::string(" (= ") + debian_pkg_version + ")";
+  controlValues["Section"] = "debug";
+  controlValues["Priority"] = "optional";
+  controlValues["Architecture"] =
+    this->GetOption("GEN_CPACK_DEBIAN_PACKAGE_ARCHITECTURE");
+  controlValues["Maintainer"] =
+    this->GetOption("GEN_CPACK_DEBIAN_PACKAGE_MAINTAINER");
+  controlValues["Description"] =
+    std::string("debug symbols for ") + packageNameLower;
 
-      output += "  " + file + "\n";
-      // debian md5sums entries are like this:
-      // 014f3604694729f3bf19263bac599765  usr/bin/ccmake
-      // thus strip the full path (with the trailing slash)
-      cmSystemTools::ReplaceString(output, topLevelWithTrailingSlash.c_str(),
-                                   "");
-      out << output;
-    }
-    // each line contains a eol.
-    // Do not end the md5sum file with yet another (invalid)
+  const char* debian_pkg_source =
+    this->GetOption("GEN_CPACK_DEBIAN_PACKAGE_SOURCE");
+  if (debian_pkg_source && *debian_pkg_source) {
+    controlValues["Source"] = debian_pkg_source;
   }
-
-  std::string filename_control_tar = strGenWDIR + "/control.tar.gz";
-  // atomic file generation for control.tar
-  {
-    cmGeneratedFileStream fileStream_control_tar;
-    fileStream_control_tar.Open(filename_control_tar, false, true);
-    if (!fileStream_control_tar) {
-      cmCPackLogger(cmCPackLog::LOG_ERROR,
-                    "Error opening the file \"" << filename_control_tar
-                                                << "\" for writing"
-                                                << std::endl);
-      return 0;
-    }
-    cmArchiveWrite control_tar(fileStream_control_tar,
-                               cmArchiveWrite::CompressGZip,
-                               debian_archive_type);
-
-    // sets permissions and uid/gid for the files
-    control_tar.SetUIDAndGID(0u, 0u);
-    control_tar.SetUNAMEAndGNAME("root", "root");
-
-    /* permissions are set according to
-    https://www.debian.org/doc/debian-policy/ch-files.html#s-permissions-owners
-    and
-    https://lintian.debian.org/tags/control-file-has-bad-permissions.html
-    */
-    const mode_t permission644 = 0644;
-    const mode_t permissionExecute = 0111;
-    const mode_t permission755 = permission644 | permissionExecute;
-
-    // for md5sum and control (that we have generated here), we use 644
-    // (RW-R--R--)
-    // so that deb lintian doesn't warn about it
-    control_tar.SetPermissions(permission644);
-
-    // adds control and md5sums
-    if (!control_tar.Add(md5filename, strGenWDIR.length(), ".") ||
-        !control_tar.Add(strGenWDIR + "/control", strGenWDIR.length(), ".")) {
-      cmCPackLogger(cmCPackLog::LOG_ERROR,
-                    "Error adding file to tar:"
-                      << std::endl
-                      << "#top level directory: " << strGenWDIR << std::endl
-                      << "#file: \"control\" or \"md5sums\"" << std::endl
-                      << "#error:" << control_tar.GetError() << std::endl);
-      return 0;
-    }
-
-    // adds generated shlibs file
-    if (gen_shibs) {
-      if (!control_tar.Add(shlibsfilename, strGenWDIR.length(), ".")) {
-        cmCPackLogger(cmCPackLog::LOG_ERROR,
-                      "Error adding file to tar:"
-                        << std::endl
-                        << "#top level directory: " << strGenWDIR << std::endl
-                        << "#file: \"shlibs\"" << std::endl
-                        << "#error:" << control_tar.GetError() << std::endl);
-        return 0;
-      }
-    }
-
-    // adds LDCONFIG related files
-    if (this->IsOn("GEN_CPACK_DEBIAN_GENERATE_POSTINST")) {
-      control_tar.SetPermissions(permission755);
-      if (!control_tar.Add(postinst, strGenWDIR.length(), ".")) {
-        cmCPackLogger(cmCPackLog::LOG_ERROR,
-                      "Error adding file to tar:"
-                        << std::endl
-                        << "#top level directory: " << strGenWDIR << std::endl
-                        << "#file: \"postinst\"" << std::endl
-                        << "#error:" << control_tar.GetError() << std::endl);
-        return 0;
-      }
-      control_tar.SetPermissions(permission644);
-    }
-
-    if (this->IsOn("GEN_CPACK_DEBIAN_GENERATE_POSTRM")) {
-      control_tar.SetPermissions(permission755);
-      if (!control_tar.Add(postrm, strGenWDIR.length(), ".")) {
-        cmCPackLogger(cmCPackLog::LOG_ERROR,
-                      "Error adding file to tar:"
-                        << std::endl
-                        << "#top level directory: " << strGenWDIR << std::endl
-                        << "#file: \"postinst\"" << std::endl
-                        << "#error:" << control_tar.GetError() << std::endl);
-        return 0;
-      }
-      control_tar.SetPermissions(permission644);
-    }
-
-    // for the other files, we use
-    // -either the original permission on the files
-    // -either a permission strictly defined by the Debian policies
-    const char* controlExtra =
-      this->GetOption("GEN_CPACK_DEBIAN_PACKAGE_CONTROL_EXTRA");
-    if (controlExtra) {
-      // permissions are now controlled by the original file permissions
-
-      const bool permissionStrictPolicy =
-        this->IsSet("GEN_CPACK_DEBIAN_PACKAGE_CONTROL_STRICT_PERMISSION");
-
-      static const char* strictFiles[] = { "config", "postinst", "postrm",
-                                           "preinst", "prerm" };
-      std::set<std::string> setStrictFiles(
-        strictFiles,
-        strictFiles + sizeof(strictFiles) / sizeof(strictFiles[0]));
-
-      // default
-      control_tar.ClearPermissions();
-
-      std::vector<std::string> controlExtraList;
-      cmSystemTools::ExpandListArgument(controlExtra, controlExtraList);
-      for (std::string const& i : controlExtraList) {
-        std::string filenamename = cmsys::SystemTools::GetFilenameName(i);
-        std::string localcopy = strGenWDIR + "/" + filenamename;
-
-        if (permissionStrictPolicy) {
-          control_tar.SetPermissions(setStrictFiles.count(filenamename)
-                                       ? permission755
-                                       : permission644);
-        }
-
-        // if we can copy the file, it means it does exist, let's add it:
-        if (cmsys::SystemTools::CopyFileIfDifferent(i, localcopy)) {
-          control_tar.Add(localcopy, strGenWDIR.length(), ".");
-        }
-      }
-    }
+  const char* debian_build_ids = this->GetOption("GEN_BUILD_IDS");
+  if (debian_build_ids && *debian_build_ids) {
+    controlValues["Build-Ids"] = debian_build_ids;
   }
 
-  // ar -r your-package-name.deb debian-binary control.tar.* data.tar.*
-  // A debian package .deb is simply an 'ar' archive. The only subtle
-  // difference is that debian uses the BSD ar style archive whereas most
-  // Linux distro have a GNU ar.
-  // See http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=161593 for more info
-  std::string const outputDir = this->GetOption("CPACK_TOPLEVEL_DIRECTORY");
-  std::string const outputName = this->GetOption("GEN_CPACK_OUTPUT_FILE_NAME");
-  std::string const outputPath = outputDir + "/" + outputName;
-  std::string const tlDir = strGenWDIR + "/";
-  cmGeneratedFileStream debStream;
-  debStream.Open(outputPath, false, true);
-  cmArchiveWrite deb(debStream, cmArchiveWrite::CompressNone, "arbsd");
+  DebGenerator gen(
+    Logger, this->GetOption("GEN_CPACK_DBGSYM_OUTPUT_FILE_NAME"),
+    this->GetOption("GEN_DBGSYMDIR"),
 
-  // uid/gid should be the one of the root user, and this root user has
-  // always uid/gid equal to 0.
-  deb.SetUIDAndGID(0u, 0u);
-  deb.SetUNAMEAndGNAME("root", "root");
+    this->GetOption("CPACK_TOPLEVEL_DIRECTORY"),
+    this->GetOption("CPACK_TEMPORARY_DIRECTORY"),
+    this->GetOption("GEN_CPACK_DEBIAN_COMPRESSION_TYPE"),
+    this->GetOption("GEN_CPACK_DEBIAN_ARCHIVE_TYPE"), controlValues, false, "",
+    false, "", false, "", nullptr,
+    this->IsSet("GEN_CPACK_DEBIAN_PACKAGE_CONTROL_STRICT_PERMISSION"),
+    packageFiles);
 
-  if (!deb.Add(tlDir + "debian-binary", tlDir.length()) ||
-      !deb.Add(tlDir + "control.tar.gz", tlDir.length()) ||
-      !deb.Add(tlDir + "data.tar" + compression_suffix, tlDir.length())) {
-    cmCPackLogger(cmCPackLog::LOG_ERROR,
-                  "Error creating debian package:"
-                    << std::endl
-                    << "#top level directory: " << outputDir << std::endl
-                    << "#file: " << outputName << std::endl
-                    << "#error:" << deb.GetError() << std::endl);
+  if (!gen.generate()) {
     return 0;
   }
   return 1;

+ 2 - 0
Source/CPack/cmCPackDebGenerator.h

@@ -65,6 +65,8 @@ protected:
 
 private:
   int createDeb();
+  int createDbgsymDDeb();
+
   std::vector<std::string> packageFiles;
 };
 

+ 1 - 1
Tests/RunCMake/CPack/DEB/Helpers.cmake

@@ -1,4 +1,4 @@
-set(ALL_FILES_GLOB "*.deb")
+set(ALL_FILES_GLOB "*.deb" "*.ddeb")
 
 function(getPackageContent FILE RESULT_VAR)
   execute_process(COMMAND ${CMAKE_COMMAND} -E env TZ=Etc/UTC ${DPKG_EXECUTABLE} -c "${FILE}"

+ 1 - 1
Tests/RunCMake/CPack/RunCMakeTest.cmake

@@ -6,7 +6,7 @@ include("${RunCMake_SOURCE_DIR}/CPackTestHelpers.cmake")
 # run_cpack_test args: TEST_NAME "GENERATORS" RUN_CMAKE_BUILD_STEP "PACKAGING_TYPES"
 run_cpack_test(CUSTOM_BINARY_SPEC_FILE "RPM" false "MONOLITHIC;COMPONENT")
 run_cpack_test(CUSTOM_NAMES "RPM;DEB;TGZ" true "COMPONENT")
-run_cpack_test(DEBUGINFO "RPM" true "COMPONENT")
+run_cpack_test(DEBUGINFO "RPM;DEB" true "COMPONENT")
 run_cpack_test_subtests(DEFAULT_PERMISSIONS "CMAKE_var_set;CPACK_var_set;both_set;invalid_CMAKE_var;invalid_CPACK_var" "RPM;DEB" false "MONOLITHIC;COMPONENT")
 run_cpack_test(DEPENDENCIES "RPM;DEB" true "COMPONENT")
 run_cpack_test(DIST "RPM" false "MONOLITHIC")

+ 1 - 1
Tests/RunCMake/CPack/VerifyResult.cmake

@@ -92,7 +92,7 @@ if(NOT EXPECTED_FILES_COUNT EQUAL 0)
   # check that there were no extra files generated
   foreach(all_files_glob_ IN LISTS ALL_FILES_GLOB)
     file(GLOB foundAll_ RELATIVE "${bin_dir}" "${all_files_glob_}")
-    list(APPEND allFoundFiles_ "${foundAll_}")
+    list(APPEND allFoundFiles_ ${foundAll_})
   endforeach()
 
   list(LENGTH foundFiles_ foundFilesCount_)

+ 31 - 8
Tests/RunCMake/CPack/tests/DEBUGINFO/ExpectedFiles.cmake

@@ -3,16 +3,39 @@ set(whitespaces_ "[\t\n\r ]*")
 set(EXPECTED_FILES_COUNT "5")
 set(EXPECTED_FILES_NAME_GENERATOR_SPECIFIC_FORMAT TRUE)
 
-set(EXPECTED_FILE_1_NAME "Debuginfo")
+if(GENERATOR_TYPE STREQUAL "RPM")
+  set(NAME "Debuginfo")
+  set(DEBUG_SUFFIX "debuginfo")
+  set(PKG "rpm")
+  set(DEBUG_PKG "rpm")
+elseif(GENERATOR_TYPE STREQUAL "DEB")
+  set(NAME "debuginfo")
+  set(DEBUG_SUFFIX "dbgsym")
+  set(PKG "deb")
+  set(DEBUG_PKG "ddeb")
+endif()
+
+set(EXPECTED_FILE_1_NAME "${NAME}")
 set(EXPECTED_FILE_1_COMPONENT "applications")
 set(EXPECTED_FILE_CONTENT_1_LIST "/foo;/foo/test_prog")
-set(EXPECTED_FILE_2 "TestDinfo-pkg*-headers.rpm")
+
+set(EXPECTED_FILE_2 "TestDinfo-pkg*-headers.${PKG}")
 set(EXPECTED_FILE_CONTENT_2_LIST "/bar;/bar/CMakeLists.txt")
-set(EXPECTED_FILE_3 "TestDinfo-pkg*-libs.rpm")
+
+set(EXPECTED_FILE_3 "TestDinfo-pkg*-libs.${PKG}")
 set(EXPECTED_FILE_CONTENT_3_LIST "/bas;/bas/libtest_lib.so")
 
-set(EXPECTED_FILE_4_NAME "Debuginfo")
-set(EXPECTED_FILE_4_COMPONENT "applications-debuginfo")
-set(EXPECTED_FILE_CONTENT_4 ".*/src${whitespaces_}/src/src_1${whitespaces_}/src/src_1/main.cpp.*\.debug.*")
-set(EXPECTED_FILE_5 "libs-DebugInfoPackage.rpm")
-set(EXPECTED_FILE_CONTENT_5 ".*/src${whitespaces_}/src/src_1${whitespaces_}/src/src_1/test_lib.cpp.*\.debug.*")
+set(EXPECTED_FILE_4 "${NAME}-applications-${DEBUG_SUFFIX}*.${DEBUG_PKG}")
+if(GENERATOR_TYPE STREQUAL "RPM")
+  set(EXPECTED_FILE_CONTENT_4 ".*/src${whitespaces_}/src/src_1${whitespaces_}/src/src_1/main.cpp.*\.debug.*")
+elseif(GENERATOR_TYPE STREQUAL "DEB")
+  set(EXPECTED_FILE_CONTENT_4 ".*/usr/lib/debug/.build-id/.*\.debug.*")
+endif()
+
+if(GENERATOR_TYPE STREQUAL "RPM")
+  set(EXPECTED_FILE_5 "libs-DebugInfoPackage.rpm")
+  set(EXPECTED_FILE_CONTENT_5 ".*/src${whitespaces_}/src/src_1${whitespaces_}/src/src_1/test_lib.cpp.*\.debug.*")
+elseif(GENERATOR_TYPE STREQUAL "DEB")
+  set(EXPECTED_FILE_5 "TestDinfo-pkg-libs-dbgsym.ddeb")
+  set(EXPECTED_FILE_CONTENT_5 ".*/usr/lib/debug/.build-id/.*\.debug.*")
+endif()

+ 4 - 0
Tests/RunCMake/CPack/tests/DEBUGINFO/test.cmake

@@ -29,12 +29,16 @@ install(TARGETS test_lib DESTINATION bas COMPONENT libs)
 
 set(CPACK_RPM_APPLICATIONS_FILE_NAME "RPM-DEFAULT")
 set(CPACK_RPM_APPLICATIONS_DEBUGINFO_PACKAGE ON)
+set(CPACK_DEBIAN_APPLICATIONS_FILE_NAME "DEB-DEFAULT")
+set(CPACK_DEBIAN_APPLICATIONS_DEBUGINFO_PACKAGE ON)
 
 # test that components with debuginfo enabled still honor
 # CPACK_PACKAGE_FILE_NAME setting
 set(CPACK_RPM_PACKAGE_NAME "Debuginfo")
 set(CPACK_PACKAGE_FILE_NAME "TestDinfo-pkg")
 set(CPACK_RPM_LIBS_DEBUGINFO_PACKAGE ON)
+set(CPACK_DEBIAN_PACKAGE_NAME "Debuginfo")
+set(CPACK_DEBIAN_LIBS_DEBUGINFO_PACKAGE ON)
 
 # test debuginfo package rename
 set(CPACK_RPM_DEBUGINFO_FILE_NAME