Browse Source

CPack/RPM single debuginfo packaging

Generate a single debuginfo package even
if components packaging is enabled.

This makes issue #15668 resolution feature complete.

Closes: #15486
Domen Vrankar 9 years ago
parent
commit
bb8cf52156

+ 4 - 0
Help/release/dev/cpack-rpm-single-debuginfo.rst

@@ -5,3 +5,7 @@ cpack-rpm-single-debuginfo
   which forces generation of a rpm for defined component without component
   suffix in filename and package name.
   See :variable:`CPACK_RPM_MAIN_COMPONENT` variable.
+
+* The :module:`CPackRPM` module learned to generate a single debuginfo package
+  on demand even if components packagin is used.
+  See :variable:`CPACK_RPM_DEBUGINFO_SINGLE_PACKAGE` variable.

+ 236 - 51
Modules/CPackRPM.cmake

@@ -803,6 +803,26 @@
 #  * Mandatory : NO
 #  * Default   : -
 #
+# .. variable:: CPACK_RPM_DEBUGINFO_SINGLE_PACKAGE
+#
+#  Create a single debuginfo package even if components packaging is set.
+#
+#  * Mandatory : NO
+#  * Default   : OFF
+#
+#  When this variable is enabled it produces a single debuginfo package even if
+#  component packaging is enabled.
+#
+#  When using this feature in combination with components packaging and there is
+#  more than one component this variable requires :variable:`CPACK_RPM_MAIN_COMPONENT`
+#  to be set.
+#
+# .. note::
+#
+#  If none of the :variable:`CPACK_RPM_<component>_DEBUGINFO_PACKAGE` variables
+#  is set then :variable:`CPACK_RPM_DEBUGINFO_PACKAGE` is automatically set to
+#  ``ON`` when :variable:`CPACK_RPM_DEBUGINFO_SINGLE_PACKAGE` is set.
+#
 # Packaging of sources (SRPM)
 # ^^^^^^^^^^^^^^^^^^^^^^^^^^^
 #
@@ -2076,7 +2096,7 @@ function(cpack_rpm_generate_package)
     "CPACK_RPM_${CPACK_RPM_PACKAGE_COMPONENT}_DEBUGINFO_PACKAGE"
     "CPACK_RPM_${CPACK_RPM_PACKAGE_COMPONENT_UPPER}_DEBUGINFO_PACKAGE"
     "CPACK_RPM_DEBUGINFO_PACKAGE")
-  if(CPACK_RPM_DEBUGINFO_PACKAGE)
+  if(CPACK_RPM_DEBUGINFO_PACKAGE OR (CPACK_RPM_DEBUGINFO_SINGLE_PACKAGE AND NOT GENERATE_SPEC_PARTS))
     cpack_rpm_variable_fallback("CPACK_RPM_BUILD_SOURCE_DIRS_PREFIX"
       "CPACK_RPM_${CPACK_RPM_PACKAGE_COMPONENT}_BUILD_SOURCE_DIRS_PREFIX"
       "CPACK_RPM_${CPACK_RPM_PACKAGE_COMPONENT_UPPER}_BUILD_SOURCE_DIRS_PREFIX"
@@ -2084,9 +2104,81 @@ function(cpack_rpm_generate_package)
     if(NOT CPACK_RPM_BUILD_SOURCE_DIRS_PREFIX)
       set(CPACK_RPM_BUILD_SOURCE_DIRS_PREFIX "/usr/src/debug/${CPACK_PACKAGE_FILE_NAME}${CPACK_RPM_PACKAGE_COMPONENT_PART_PATH}")
     endif()
-    cpack_rpm_debugsymbol_check("${CPACK_RPM_INSTALL_FILES}" "${WDIR}")
 
-    set(TMP_RPM_DEBUGINFO "
+    if(CPACK_RPM_DEBUGINFO_SINGLE_PACKAGE AND GENERATE_SPEC_PARTS)
+      file(WRITE "${CPACK_RPM_ROOTDIR}/SPECS/${CPACK_RPM_PACKAGE_COMPONENT}.files"
+        "${CPACK_RPM_INSTALL_FILES}")
+    else()
+      if(CPACK_RPM_DEBUGINFO_SINGLE_PACKAGE AND CPACK_RPM_PACKAGE_COMPONENT)
+        # this part is only required by components packaging - with monolithic
+        # packages we can be certain that there are no other components present
+        # so CPACK_RPM_DEBUGINFO_SINGLE_PACKAGE is a noop
+        if(CPACK_RPM_DEBUGINFO_PACKAGE)
+          # only add current package files to debuginfo list if debuginfo
+          # generation is enabled for current package
+          set(install_files_ "${CPACK_RPM_INSTALL_FILES}")
+        else()
+          unset(install_files_)
+        endif()
+
+        file(GLOB files_ "${CPACK_RPM_DIRECTORY}/SPECS/*.files")
+
+        foreach(f_ IN LISTS files_)
+          file(READ "${f_}" tmp_)
+          string(APPEND install_files_ ";${tmp_}")
+        endforeach()
+
+        # if there were other components/groups so we need to move files from them
+        # to current component otherwise those files won't be found
+        file(GLOB components_ LIST_DIRECTORIES true RELATIVE
+          "${CPACK_TOPLEVEL_DIRECTORY}/${CPACK_PACKAGE_FILE_NAME}"
+          "${CPACK_TOPLEVEL_DIRECTORY}/${CPACK_PACKAGE_FILE_NAME}/*")
+        foreach(component_ IN LISTS components_)
+          string(TOUPPER "${component_}" component_dir_upper_)
+          if(component_dir_upper_ STREQUAL CPACK_RPM_PACKAGE_COMPONENT_UPPER)
+            # skip current component
+            continue()
+          endif()
+
+          cmake_policy(PUSH)
+            cmake_policy(SET CMP0009 NEW)
+            file(GLOB_RECURSE files_for_move_ LIST_DIRECTORIES false RELATIVE
+              "${CPACK_TOPLEVEL_DIRECTORY}/${CPACK_PACKAGE_FILE_NAME}/${component_}"
+              "${CPACK_TOPLEVEL_DIRECTORY}/${CPACK_PACKAGE_FILE_NAME}/${component_}/*")
+          cmake_policy(POP)
+
+          foreach(f_ IN LISTS files_for_move_)
+            get_filename_component(dir_path_ "${f_}" DIRECTORY)
+            set(src_file_
+              "${CPACK_TOPLEVEL_DIRECTORY}/${CPACK_PACKAGE_FILE_NAME}/${component_}/${f_}")
+
+            # check that we are not overriding an existing file that doesn't
+            # match the file that we want to copy
+            if(EXISTS "${src_file_}" AND EXISTS "${WDIR}/${f_}")
+              execute_process(
+                  COMMAND ${CMAKE_COMMAND} -E compare_files "${src_file_}" "${WDIR}/${f_}"
+                  RESULT_VARIABLE res_
+                )
+              if(res_)
+                message(FATAL_ERROR "CPackRPM:Error: File on path '${WDIR}/${f_}'"
+                  " already exists but is a different than the one in component"
+                  " '${component_}'! Packages will not be generated.")
+              endif()
+            endif()
+
+            file(MAKE_DIRECTORY "${WDIR}/${dir_path_}")
+            file(RENAME "${src_file_}"
+              "${WDIR}/${f_}")
+          endforeach()
+        endforeach()
+
+        cpack_rpm_debugsymbol_check("${install_files_}" "${WDIR}")
+      else()
+        cpack_rpm_debugsymbol_check("${CPACK_RPM_INSTALL_FILES}" "${WDIR}")
+      endif()
+
+      if(TMP_DEBUGINFO_ADDITIONAL_SOURCES)
+        set(TMP_RPM_DEBUGINFO "
 # Modified version of %%debug_package macro
 # defined in /usr/lib/rpm/macros as that one
 # can't handle injection of extra source files.
@@ -2105,6 +2197,15 @@ package or when debugging this package.
 ${TMP_DEBUGINFO_ADDITIONAL_SOURCES}
 %endif
 ")
+      elseif(CPACK_RPM_DEBUGINFO_SINGLE_PACKAGE)
+        message(AUTHOR_WARNING "CPackRPM:Warning: debuginfo package was requested"
+          " but will not be generated as no source files were found!")
+      else()
+        message(AUTHOR_WARNING "CPackRPM:Warning: debuginfo package was requested"
+          " but will not be generated as no source files were found! Component: '"
+          "${CPACK_RPM_PACKAGE_COMPONENT}'.")
+      endif()
+    endif()
   endif()
 
   # Prepare install files
@@ -2194,7 +2295,12 @@ ${TMP_DEBUGINFO_ADDITIONAL_SOURCES}
     # else example:
     #set(CPACK_RPM_FILE_NAME "${CPACK_RPM_PACKAGE_NAME}-${CPACK_RPM_PACKAGE_VERSION}-${CPACK_RPM_PACKAGE_RELEASE}-${CPACK_RPM_PACKAGE_ARCHITECTURE}.rpm")
 
-    if(NOT CPACK_RPM_DEBUGINFO_PACKAGE)
+    if(CPACK_RPM_DEBUGINFO_SINGLE_PACKAGE AND GENERATE_SPEC_PARTS)
+      string(TOLOWER "${CPACK_RPM_PACKAGE_NAME}-${CPACK_PACKAGE_VERSION}.*\\.rpm" expected_filename_)
+
+      file(WRITE "${CPACK_RPM_ROOTDIR}/SPECS/${CPACK_RPM_PACKAGE_COMPONENT}.rpm_name"
+        "${expected_filename_};${CPACK_RPM_FILE_NAME}")
+    elseif(NOT CPACK_RPM_DEBUGINFO_PACKAGE)
       set(FILE_NAME_DEFINE "%define _rpmfilename ${CPACK_RPM_FILE_NAME}")
     endif()
   endif()
@@ -2289,7 +2395,54 @@ Vendor:         \@CPACK_RPM_PACKAGE_VENDOR\@
 \@CPACK_RPM_SPEC_CHANGELOG\@
 "
     )
+
+  elseif(GENERATE_SPEC_PARTS) # binary rpm with single debuginfo package
+    file(WRITE ${CPACK_RPM_BINARY_SPECFILE}.in
+        "# -*- rpm-spec -*-
+%package -n \@CPACK_RPM_PACKAGE_NAME\@
+Summary:        \@CPACK_RPM_PACKAGE_SUMMARY\@
+Version:        \@CPACK_RPM_PACKAGE_VERSION\@
+Release:        \@CPACK_RPM_PACKAGE_RELEASE\@
+License:        \@CPACK_RPM_PACKAGE_LICENSE\@
+Group:          \@CPACK_RPM_PACKAGE_GROUP\@
+Vendor:         \@CPACK_RPM_PACKAGE_VENDOR\@
+
+\@TMP_RPM_URL\@
+\@TMP_RPM_REQUIRES\@
+\@TMP_RPM_REQUIRES_PRE\@
+\@TMP_RPM_REQUIRES_POST\@
+\@TMP_RPM_REQUIRES_PREUN\@
+\@TMP_RPM_REQUIRES_POSTUN\@
+\@TMP_RPM_PROVIDES\@
+\@TMP_RPM_OBSOLETES\@
+\@TMP_RPM_CONFLICTS\@
+\@TMP_RPM_AUTOPROV\@
+\@TMP_RPM_AUTOREQ\@
+\@TMP_RPM_AUTOREQPROV\@
+\@TMP_RPM_BUILDARCH\@
+\@TMP_RPM_PREFIXES\@
+
+%description -n \@CPACK_RPM_PACKAGE_NAME\@
+\@CPACK_RPM_PACKAGE_DESCRIPTION\@
+
+%files -n \@CPACK_RPM_PACKAGE_NAME\@
+%defattr(\@TMP_DEFAULT_FILE_PERMISSIONS\@,\@TMP_DEFAULT_USER\@,\@TMP_DEFAULT_GROUP\@,\@TMP_DEFAULT_DIR_PERMISSIONS\@)
+\@CPACK_RPM_INSTALL_FILES\@
+\@CPACK_RPM_ABSOLUTE_INSTALL_FILES\@
+\@CPACK_RPM_USER_INSTALL_FILES\@
+"
+    )
+
   else()  # binary rpm
+    if(CPACK_RPM_DEBUGINFO_SINGLE_PACKAGE)
+      # find generated spec file and take its name
+      file(GLOB spec_files_ "${CPACK_RPM_DIRECTORY}/SPECS/*.spec")
+
+      foreach(f_ IN LISTS spec_files_)
+        file(READ "${f_}" tmp_)
+        string(APPEND TMP_OTHER_COMPONENTS "\n${tmp_}\n")
+      endforeach()
+    endif()
 
     # We should generate a USER spec file template:
     #  - either because the user asked for it : CPACK_RPM_GENERATE_USER_BINARY_SPECFILE_TEMPLATE
@@ -2375,6 +2528,8 @@ mv %_topdir/tmpBBroot $RPM_BUILD_ROOT
 
 %changelog
 \@CPACK_RPM_SPEC_CHANGELOG\@
+
+\@TMP_OTHER_COMPONENTS\@
 "
       )
     endif()
@@ -2401,60 +2556,90 @@ mv %_topdir/tmpBBroot $RPM_BUILD_ROOT
     configure_file(${CPACK_RPM_BINARY_SPECFILE}.in ${CPACK_RPM_BINARY_SPECFILE} @ONLY)
   endif()
 
-  if(RPMBUILD_EXECUTABLE)
-    # Now call rpmbuild using the SPECFILE
-    execute_process(
-      COMMAND "${RPMBUILD_EXECUTABLE}" ${RPMBUILD_FLAGS}
-              --define "_topdir ${CPACK_RPM_DIRECTORY}"
-              --buildroot "%_topdir/${CPACK_PACKAGE_FILE_NAME}${CPACK_RPM_PACKAGE_COMPONENT_PART_PATH}"
-              --target "${CPACK_RPM_PACKAGE_ARCHITECTURE}"
-              "${CPACK_RPM_BINARY_SPECFILE}"
-      WORKING_DIRECTORY "${CPACK_TOPLEVEL_DIRECTORY}/${CPACK_PACKAGE_FILE_NAME}${CPACK_RPM_PACKAGE_COMPONENT_PART_PATH}"
-      RESULT_VARIABLE CPACK_RPMBUILD_EXEC_RESULT
-      ERROR_FILE "${CPACK_TOPLEVEL_DIRECTORY}/rpmbuild${CPACK_RPM_PACKAGE_NAME}.err"
-      OUTPUT_FILE "${CPACK_TOPLEVEL_DIRECTORY}/rpmbuild${CPACK_RPM_PACKAGE_NAME}.out")
-    if(CPACK_RPM_PACKAGE_DEBUG OR CPACK_RPMBUILD_EXEC_RESULT)
-      file(READ ${CPACK_TOPLEVEL_DIRECTORY}/rpmbuild${CPACK_RPM_PACKAGE_NAME}.err RPMBUILDERR)
-      file(READ ${CPACK_TOPLEVEL_DIRECTORY}/rpmbuild${CPACK_RPM_PACKAGE_NAME}.out RPMBUILDOUT)
-      message("CPackRPM:Debug: You may consult rpmbuild logs in: ")
-      message("CPackRPM:Debug:    - ${CPACK_TOPLEVEL_DIRECTORY}/rpmbuild${CPACK_RPM_PACKAGE_NAME}.err")
-      message("CPackRPM:Debug: *** ${RPMBUILDERR} ***")
-      message("CPackRPM:Debug:    - ${CPACK_TOPLEVEL_DIRECTORY}/rpmbuild${CPACK_RPM_PACKAGE_NAME}.out")
-      message("CPackRPM:Debug: *** ${RPMBUILDOUT} ***")
+  if(NOT GENERATE_SPEC_PARTS) # generate package
+    if(RPMBUILD_EXECUTABLE)
+      # Now call rpmbuild using the SPECFILE
+      execute_process(
+        COMMAND "${RPMBUILD_EXECUTABLE}" ${RPMBUILD_FLAGS}
+                --define "_topdir ${CPACK_RPM_DIRECTORY}"
+                --buildroot "%_topdir/${CPACK_PACKAGE_FILE_NAME}${CPACK_RPM_PACKAGE_COMPONENT_PART_PATH}"
+                --target "${CPACK_RPM_PACKAGE_ARCHITECTURE}"
+                "${CPACK_RPM_BINARY_SPECFILE}"
+        WORKING_DIRECTORY "${CPACK_TOPLEVEL_DIRECTORY}/${CPACK_PACKAGE_FILE_NAME}${CPACK_RPM_PACKAGE_COMPONENT_PART_PATH}"
+        RESULT_VARIABLE CPACK_RPMBUILD_EXEC_RESULT
+        ERROR_FILE "${CPACK_TOPLEVEL_DIRECTORY}/rpmbuild${CPACK_RPM_PACKAGE_NAME}.err"
+        OUTPUT_FILE "${CPACK_TOPLEVEL_DIRECTORY}/rpmbuild${CPACK_RPM_PACKAGE_NAME}.out")
+      if(CPACK_RPM_PACKAGE_DEBUG OR CPACK_RPMBUILD_EXEC_RESULT)
+        file(READ ${CPACK_TOPLEVEL_DIRECTORY}/rpmbuild${CPACK_RPM_PACKAGE_NAME}.err RPMBUILDERR)
+        file(READ ${CPACK_TOPLEVEL_DIRECTORY}/rpmbuild${CPACK_RPM_PACKAGE_NAME}.out RPMBUILDOUT)
+        message("CPackRPM:Debug: You may consult rpmbuild logs in: ")
+        message("CPackRPM:Debug:    - ${CPACK_TOPLEVEL_DIRECTORY}/rpmbuild${CPACK_RPM_PACKAGE_NAME}.err")
+        message("CPackRPM:Debug: *** ${RPMBUILDERR} ***")
+        message("CPackRPM:Debug:    - ${CPACK_TOPLEVEL_DIRECTORY}/rpmbuild${CPACK_RPM_PACKAGE_NAME}.out")
+        message("CPackRPM:Debug: *** ${RPMBUILDOUT} ***")
+      endif()
+    else()
+      if(ALIEN_EXECUTABLE)
+        message(FATAL_ERROR "RPM packaging through alien not done (yet)")
+      endif()
     endif()
-  else()
-    if(ALIEN_EXECUTABLE)
-      message(FATAL_ERROR "RPM packaging through alien not done (yet)")
+
+    # find generated rpm files and take their names
+    cmake_policy(PUSH)
+      # Tell file(GLOB_RECURSE) not to follow directory symlinks
+      # even if the project does not set this policy to NEW.
+      cmake_policy(SET CMP0009 NEW)
+      file(GLOB_RECURSE GENERATED_FILES "${CPACK_RPM_DIRECTORY}/RPMS/*.rpm"
+        "${CPACK_RPM_DIRECTORY}/SRPMS/*.rpm")
+    cmake_policy(POP)
+
+    if(NOT GENERATED_FILES)
+      message(FATAL_ERROR "RPM package was not generated! ${CPACK_RPM_DIRECTORY}")
     endif()
-  endif()
 
-  # find generated rpm files and take their names
-  cmake_policy(PUSH)
-    # Tell file(GLOB_RECURSE) not to follow directory symlinks
-    # even if the project does not set this policy to NEW.
-    cmake_policy(SET CMP0009 NEW)
-    file(GLOB_RECURSE GENERATED_FILES "${CPACK_RPM_DIRECTORY}/RPMS/*.rpm"
-      "${CPACK_RPM_DIRECTORY}/SRPMS/*.rpm")
-  cmake_policy(POP)
+    unset(expected_filenames_)
+    unset(filenames_)
+    if(CPACK_RPM_DEBUGINFO_PACKAGE AND NOT CPACK_RPM_FILE_NAME STREQUAL "RPM-DEFAULT")
+      string(TOLOWER "${CPACK_RPM_PACKAGE_NAME}-${CPACK_PACKAGE_VERSION}.*\\.rpm" efn_)
+      list(APPEND expected_filenames_ "${efn_}")
+      list(APPEND filenames_ "${CPACK_RPM_FILE_NAME}")
+    endif()
 
-  if(NOT GENERATED_FILES)
-    message(FATAL_ERROR "RPM package was not generated! ${CPACK_RPM_DIRECTORY}")
-  endif()
+    # check if other files have to be renamed
+    file(GLOB rename_files_ "${CPACK_RPM_DIRECTORY}/SPECS/*.rpm_name")
+    if(rename_files_)
+      foreach(f_ IN LISTS rename_files_)
+        file(READ "${f_}" tmp_)
+        list(GET tmp_ 0 efn_)
+        list(APPEND expected_filenames_ "${efn_}")
+        list(GET tmp_ 1 fn_)
+        list(APPEND filenames_ "${fn_}")
+      endforeach()
+    endif()
 
-  if(CPACK_RPM_DEBUGINFO_PACKAGE AND NOT CPACK_RPM_FILE_NAME STREQUAL "RPM-DEFAULT")
-    string(TOLOWER "${CPACK_RPM_PACKAGE_NAME}-${CPACK_PACKAGE_VERSION}.*\\.rpm" EXPECTED_FILENAME)
+    if(expected_filenames_)
+      foreach(F IN LISTS GENERATED_FILES)
+        unset(matched_)
+        foreach(expected_ IN LISTS expected_filenames_)
+          if(F MATCHES ".*/${expected_}")
+            list(FIND expected_filenames_ "${expected_}" idx_)
+            list(GET filenames_ ${idx_} filename_)
+            get_filename_component(FILE_PATH "${F}" DIRECTORY)
+            file(RENAME "${F}" "${FILE_PATH}/${filename_}")
+            list(APPEND new_files_list_ "${FILE_PATH}/${filename_}")
+            set(matched_ "YES")
+
+            break()
+          endif()
+        endforeach()
 
-    foreach(F IN LISTS GENERATED_FILES)
-      if(F MATCHES ".*/${EXPECTED_FILENAME}")
-        get_filename_component(FILE_PATH "${F}" DIRECTORY)
-        file(RENAME "${F}" "${FILE_PATH}/${CPACK_RPM_FILE_NAME}")
-        list(APPEND new_files_list_ "${FILE_PATH}/${CPACK_RPM_FILE_NAME}")
-      else()
-        list(APPEND new_files_list_ "${F}")
-      endif()
-    endforeach()
+        if(NOT matched_)
+          list(APPEND new_files_list_ "${F}")
+        endif()
+      endforeach()
 
-    set(GENERATED_FILES "${new_files_list_}")
+      set(GENERATED_FILES "${new_files_list_}")
+    endif()
   endif()
 
   set(GEN_CPACK_OUTPUT_FILES "${GENERATED_FILES}" PARENT_SCOPE)

+ 87 - 1
Source/CPack/cmCPackRPMGenerator.cxx

@@ -107,7 +107,78 @@ int cmCPackRPMGenerator::PackageComponents(bool ignoreGroup)
 
   const char* mainComponent = this->GetOption("CPACK_RPM_MAIN_COMPONENT");
 
+  if (this->IsOn("CPACK_RPM_DEBUGINFO_SINGLE_PACKAGE") &&
+      !this->IsOn("CPACK_RPM_DEBUGINFO_PACKAGE")) {
+    // check if we need to set CPACK_RPM_DEBUGINFO_PACKAGE because non of
+    // the components is setting per component debuginfo package variable
+    bool shouldSet = true;
+
+    if (ignoreGroup) {
+      std::map<std::string, cmCPackComponent>::iterator compIt;
+      for (compIt = this->Components.begin(); compIt != this->Components.end();
+           ++compIt) {
+        std::string component(compIt->first);
+        std::transform(component.begin(), component.end(), component.begin(),
+                       ::toupper);
+
+        if (this->IsOn("CPACK_RPM_" + compIt->first + "_DEBUGINFO_PACKAGE") ||
+            this->IsOn("CPACK_RPM_" + component + "_DEBUGINFO_PACKAGE")) {
+          shouldSet = false;
+          break;
+        }
+      }
+    } else {
+      std::map<std::string, cmCPackComponentGroup>::iterator compGIt;
+      for (compGIt = this->ComponentGroups.begin();
+           compGIt != this->ComponentGroups.end(); ++compGIt) {
+        std::string component(compGIt->first);
+        std::transform(component.begin(), component.end(), component.begin(),
+                       ::toupper);
+
+        if (this->IsOn("CPACK_RPM_" + compGIt->first + "_DEBUGINFO_PACKAGE") ||
+            this->IsOn("CPACK_RPM_" + component + "_DEBUGINFO_PACKAGE")) {
+          shouldSet = false;
+          break;
+        }
+      }
+
+      if (shouldSet) {
+        std::map<std::string, cmCPackComponent>::iterator compIt;
+        for (compIt = this->Components.begin();
+             compIt != this->Components.end(); ++compIt) {
+          // Does the component belong to a group?
+          if (compIt->second.Group == CM_NULLPTR) {
+            std::string component(compIt->first);
+            std::transform(component.begin(), component.end(),
+                           component.begin(), ::toupper);
+
+            if (this->IsOn("CPACK_RPM_" + compIt->first +
+                           "_DEBUGINFO_PACKAGE") ||
+                this->IsOn("CPACK_RPM_" + component + "_DEBUGINFO_PACKAGE")) {
+              shouldSet = false;
+              break;
+            }
+          }
+        }
+      }
+    }
+
+    if (shouldSet) {
+      cmCPackLogger(cmCPackLog::LOG_VERBOSE, "Setting "
+                      << "CPACK_RPM_DEBUGINFO_PACKAGE because "
+                      << "CPACK_RPM_DEBUGINFO_SINGLE_PACKAGE is set but "
+                      << " none of the "
+                      << "CPACK_RPM_<component>_DEBUGINFO_PACKAGE variables "
+                      << "are set." << std::endl);
+      this->SetOption("CPACK_RPM_DEBUGINFO_PACKAGE", "ON");
+    }
+  }
+
   if (mainComponent) {
+    if (this->IsOn("CPACK_RPM_DEBUGINFO_SINGLE_PACKAGE")) {
+      this->SetOption("GENERATE_SPEC_PARTS", "ON");
+    }
+
     std::string mainComponentUpper(mainComponent);
     std::transform(mainComponentUpper.begin(), mainComponentUpper.end(),
                    mainComponentUpper.begin(), ::toupper);
@@ -163,6 +234,8 @@ int cmCPackRPMGenerator::PackageComponents(bool ignoreGroup)
       }
 
       if (retval) {
+        this->SetOption("GENERATE_SPEC_PARTS", "OFF");
+
         if (mainCompGIt != this->ComponentGroups.end()) {
           retval &= PackageOnePack(initialTopLevel, mainCompGIt->first);
         } else if (mainCompIt != this->Components.end()) {
@@ -197,6 +270,8 @@ int cmCPackRPMGenerator::PackageComponents(bool ignoreGroup)
       }
 
       if (retval) {
+        this->SetOption("GENERATE_SPEC_PARTS", "OFF");
+
         if (mainCompIt != this->Components.end()) {
           retval &= PackageOnePack(initialTopLevel, mainCompIt->first);
         } else {
@@ -206,7 +281,8 @@ int cmCPackRPMGenerator::PackageComponents(bool ignoreGroup)
         }
       }
     }
-  } else {
+  } else if (!this->IsOn("CPACK_RPM_DEBUGINFO_SINGLE_PACKAGE") ||
+             this->Components.size() == 1) {
     // The default behavior is to have one package by component group
     // unless CPACK_COMPONENTS_IGNORE_GROUP is specified.
     if (!ignoreGroup) {
@@ -241,6 +317,12 @@ int cmCPackRPMGenerator::PackageComponents(bool ignoreGroup)
         retval &= PackageOnePack(initialTopLevel, compIt->first);
       }
     }
+  } else {
+    cmCPackLogger(
+      cmCPackLog::LOG_ERROR, "CPACK_RPM_MAIN_COMPONENT not set but"
+        << " it is mandatory with CPACK_RPM_DEBUGINFO_SINGLE_PACKAGE"
+        << " being set.\n");
+    retval = 0;
   }
 
   if (retval) {
@@ -259,6 +341,10 @@ int cmCPackRPMGenerator::PackageComponentsAllInOne(
   packageFileNames.clear();
   std::string initialTopLevel(this->GetOption("CPACK_TEMPORARY_DIRECTORY"));
 
+  if (this->IsOn("CPACK_RPM_DEBUGINFO_SINGLE_PACKAGE")) {
+    this->SetOption("CPACK_RPM_DEBUGINFO_PACKAGE", "ON");
+  }
+
   cmCPackLogger(cmCPackLog::LOG_VERBOSE,
                 "Packaging all groups in one package..."
                 "(CPACK_COMPONENTS_ALL_[GROUPS_]IN_ONE_PACKAGE is set)"

+ 30 - 0
Tests/RunCMake/CPack/RPM/SINGLE_DEBUGINFO-ExpectedFiles.cmake

@@ -0,0 +1,30 @@
+set(whitespaces_ "[\t\n\r ]*")
+
+set(EXPECTED_FILES_COUNT "0")
+
+if(RunCMake_SUBTEST_SUFFIX STREQUAL "valid" OR RunCMake_SUBTEST_SUFFIX STREQUAL "no_debuginfo")
+  set(EXPECTED_FILES_COUNT "4")
+  set(EXPECTED_FILE_1 "single_debuginfo-0.1.1-1.*.rpm")
+  set(EXPECTED_FILE_CONTENT_1 "^/usr/foo${whitespaces_}/usr/foo/test_prog$")
+  set(EXPECTED_FILE_2 "single_debuginfo*-headers.rpm")
+  set(EXPECTED_FILE_CONTENT_2 "^/usr/bar${whitespaces_}/usr/bar/CMakeLists.txt$")
+  set(EXPECTED_FILE_3 "single_debuginfo*-libs.rpm")
+  set(EXPECTED_FILE_CONTENT_3 "^/usr/bas${whitespaces_}/usr/bas/libtest_lib.so$")
+
+  set(EXPECTED_FILE_4 "single_debuginfo-debuginfo*.rpm")
+  set(EXPECTED_FILE_CONTENT_4 ".*/src${whitespaces_}/src/src_1${whitespaces_}/src/src_1/main.cpp${whitespaces_}/src/src_1/test_lib.cpp.*")
+elseif(RunCMake_SUBTEST_SUFFIX STREQUAL "one_component" OR RunCMake_SUBTEST_SUFFIX STREQUAL "one_component_no_debuginfo")
+  set(EXPECTED_FILES_COUNT "2")
+  set(EXPECTED_FILE_1 "single_debuginfo-0*-applications.rpm")
+  set(EXPECTED_FILE_CONTENT_1 "^/usr/foo${whitespaces_}/usr/foo/test_prog$")
+
+  set(EXPECTED_FILE_2 "single_debuginfo-applications-debuginfo*.rpm")
+  set(EXPECTED_FILE_CONTENT_2 ".*/src${whitespaces_}/src/src_1${whitespaces_}/src/src_1/main.cpp.*")
+elseif(RunCMake_SUBTEST_SUFFIX STREQUAL "one_component_main" OR RunCMake_SUBTEST_SUFFIX STREQUAL "no_components")
+  set(EXPECTED_FILES_COUNT "2")
+  set(EXPECTED_FILE_1 "single_debuginfo-0*.rpm")
+  set(EXPECTED_FILE_CONTENT_1 "^/usr/foo${whitespaces_}/usr/foo/test_prog$")
+
+  set(EXPECTED_FILE_2 "single_debuginfo-debuginfo*.rpm")
+  set(EXPECTED_FILE_CONTENT_2 ".*/src${whitespaces_}/src/src_1${whitespaces_}/src/src_1/main.cpp.*")
+endif()

+ 1 - 0
Tests/RunCMake/CPack/RPM/SINGLE_DEBUGINFO-no_components-stderr.txt

@@ -0,0 +1 @@
+^CPackRPM: Will use GENERATED spec file: .*/Tests/RunCMake/RPM/CPack/SINGLE_DEBUGINFO-build-no_components-subtest/_CPack_Packages/.*/RPM/SPECS/single_debuginfo.spec$

+ 3 - 0
Tests/RunCMake/CPack/RPM/SINGLE_DEBUGINFO-no_debuginfo-stderr.txt

@@ -0,0 +1,3 @@
+^CPackRPM: Will use GENERATED spec file: .*/Tests/RunCMake/RPM/CPack/SINGLE_DEBUGINFO-build-no_debuginfo-subtest/_CPack_Packages/.*/RPM/SPECS/single_debuginfo-headers.spec
+CPackRPM: Will use GENERATED spec file: .*/Tests/RunCMake/RPM/CPack/SINGLE_DEBUGINFO-build-no_debuginfo-subtest/_CPack_Packages/.*/RPM/SPECS/single_debuginfo-libs.spec
+CPackRPM: Will use GENERATED spec file: .*/Tests/RunCMake/RPM/CPack/SINGLE_DEBUGINFO-build-no_debuginfo-subtest/_CPack_Packages/.*/RPM/SPECS/single_debuginfo.spec$

+ 1 - 0
Tests/RunCMake/CPack/RPM/SINGLE_DEBUGINFO-no_main_component-stderr.txt

@@ -0,0 +1 @@
+CPack Error: CPACK_RPM_MAIN_COMPONENT not set but it is mandatory with CPACK_RPM_DEBUGINFO_SINGLE_PACKAGE being set.

+ 1 - 0
Tests/RunCMake/CPack/RPM/SINGLE_DEBUGINFO-one_component-stderr.txt

@@ -0,0 +1 @@
+^CPackRPM: Will use GENERATED spec file: .*/Tests/RunCMake/RPM/CPack/SINGLE_DEBUGINFO-build-one_component-subtest/_CPack_Packages/.*/RPM/SPECS/single_debuginfo-applications.spec$

+ 1 - 0
Tests/RunCMake/CPack/RPM/SINGLE_DEBUGINFO-one_component_main-stderr.txt

@@ -0,0 +1 @@
+^CPackRPM: Will use GENERATED spec file: .*/Tests/RunCMake/RPM/CPack/SINGLE_DEBUGINFO-build-one_component_main-subtest/_CPack_Packages/.*/RPM/SPECS/single_debuginfo.spec$

+ 1 - 0
Tests/RunCMake/CPack/RPM/SINGLE_DEBUGINFO-one_component_no_debuginfo-stderr.txt

@@ -0,0 +1 @@
+^CPackRPM: Will use GENERATED spec file: .*/Tests/RunCMake/RPM/CPack/SINGLE_DEBUGINFO-build-one_component_no_debuginfo-subtest/_CPack_Packages/.*/RPM/SPECS/single_debuginfo-applications.spec$

+ 3 - 0
Tests/RunCMake/CPack/RPM/SINGLE_DEBUGINFO-valid-stderr.txt

@@ -0,0 +1,3 @@
+^CPackRPM: Will use GENERATED spec file: .*/Tests/RunCMake/RPM/CPack/SINGLE_DEBUGINFO-build-valid-subtest/_CPack_Packages/.*/RPM/SPECS/single_debuginfo-headers.spec
+CPackRPM: Will use GENERATED spec file: .*/Tests/RunCMake/RPM/CPack/SINGLE_DEBUGINFO-build-valid-subtest/_CPack_Packages/.*/RPM/SPECS/single_debuginfo-libs.spec
+CPackRPM: Will use GENERATED spec file: .*/Tests/RunCMake/RPM/CPack/SINGLE_DEBUGINFO-build-valid-subtest/_CPack_Packages/.*/RPM/SPECS/single_debuginfo.spec$

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

@@ -19,5 +19,6 @@ run_cpack_test(INSTALL_SCRIPTS "RPM" false)
 run_cpack_test(DEB_GENERATE_SHLIBS "DEB" true)
 run_cpack_test(DEB_GENERATE_SHLIBS_LDCONFIG "DEB" true)
 run_cpack_test(DEBUGINFO "RPM" true)
+run_cpack_test_subtests(SINGLE_DEBUGINFO "no_main_component;one_component;one_component_main;no_debuginfo;one_component_no_debuginfo;no_components;valid" "RPM" true)
 run_cpack_test(LONG_FILENAMES "DEB" false)
 run_cpack_test_subtests(PACKAGE_CHECKSUM "invalid;MD5;SHA1;SHA224;SHA256;SHA384;SHA512" "TGZ" false)

+ 56 - 0
Tests/RunCMake/CPack/SINGLE_DEBUGINFO.cmake

@@ -0,0 +1,56 @@
+set(CMAKE_BUILD_WITH_INSTALL_RPATH 1)
+
+# PGI compiler doesn't add build id to binaries by default
+if(CMAKE_CXX_COMPILER_ID STREQUAL "PGI")
+  string(APPEND CMAKE_EXE_LINKER_FLAGS "-Wl,--build-id")
+  string(APPEND CMAKE_SHARED_LINKER_FLAGS "-Wl,--build-id")
+endif()
+
+if(NOT RunCMake_SUBTEST_SUFFIX STREQUAL "no_components")
+  set(CPACK_RPM_COMPONENT_INSTALL "ON")
+endif()
+
+set(CMAKE_BUILD_TYPE Debug)
+
+file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/test_lib.hpp"
+    "int test_lib();\n")
+file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/test_lib.cpp"
+    "#include \"test_lib.hpp\"\nint test_lib() {return 0;}\n")
+add_library(test_lib SHARED "${CMAKE_CURRENT_BINARY_DIR}/test_lib.cpp")
+
+file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/main.cpp"
+    "#include \"test_lib.hpp\"\nint main() {return test_lib();}\n")
+add_executable(test_prog "${CMAKE_CURRENT_BINARY_DIR}/main.cpp")
+target_link_libraries(test_prog test_lib)
+
+install(TARGETS test_prog DESTINATION foo COMPONENT applications)
+
+if(RunCMake_SUBTEST_SUFFIX STREQUAL "valid"
+  OR RunCMake_SUBTEST_SUFFIX STREQUAL "no_main_component"
+  OR RunCMake_SUBTEST_SUFFIX STREQUAL "no_debuginfo")
+  install(FILES CMakeLists.txt DESTINATION bar COMPONENT headers)
+  install(TARGETS test_lib DESTINATION bas COMPONENT libs)
+elseif(RunCMake_SUBTEST_SUFFIX STREQUAL "one_component"
+  OR RunCMake_SUBTEST_SUFFIX STREQUAL "one_component_no_debuginfo")
+  set(CPACK_COMPONENTS_ALL applications)
+endif()
+
+set(CPACK_RPM_DEBUGINFO_SINGLE_PACKAGE ON)
+
+if(RunCMake_SUBTEST_SUFFIX STREQUAL "valid"
+  OR RunCMake_SUBTEST_SUFFIX STREQUAL "one_component_main"
+  OR RunCMake_SUBTEST_SUFFIX STREQUAL "no_debuginfo")
+  set(CPACK_RPM_MAIN_COMPONENT "applications")
+  set(CPACK_RPM_APPLICATIONS_FILE_NAME "RPM-DEFAULT")
+endif()
+
+if(RunCMake_SUBTEST_SUFFIX STREQUAL "valid"
+  OR RunCMake_SUBTEST_SUFFIX STREQUAL "no_main_component"
+  OR RunCMake_SUBTEST_SUFFIX STREQUAL "one_component")
+  set(CPACK_RPM_APPLICATIONS_DEBUGINFO_PACKAGE ON)
+  set(CPACK_RPM_LIBS_DEBUGINFO_PACKAGE ON)
+endif()
+
+set(CPACK_RPM_BUILD_SOURCE_DIRS_PREFIX "/src")
+
+set(CPACK_PACKAGE_NAME "single_debuginfo")