CPackDeb: dpkg-shlibdeps shall consider dependency components, too

When using `dpkg-shlibdeps` to automatically determine package
dependencies it considers the RUNPATH/RPATH of executables in order to
find all required shared libraries of such executables.

If the RUNPATH/RPATH contains a verbatim `$ORIGIN` (respective
`${ORIGIN}`), it will now be substituted by the packaging-paths of other
components that are marked as dependency and those paths will then be
used as additional search directories for `dpkg-shlibdeps`.

Associated tests were added as well.

Fixes: #21838
This commit is contained in:
Deniz Bahadir 2024-05-30 16:13:46 +02:00
parent c024b5cf9a
commit 27d161eac3
11 changed files with 294 additions and 18 deletions

View File

@ -56,6 +56,65 @@ function(extract_so_info shared_object libname version)
endif()
endfunction()
#extract RUNPATH and RPATH for given shared object or executable
function(extract_runpath_and_rpath shared_object_or_executable runpath rpath)
if(CPACK_READELF_EXECUTABLE)
execute_process(COMMAND "${CPACK_READELF_EXECUTABLE}" -d "${shared_object_or_executable}"
WORKING_DIRECTORY "${CPACK_TEMPORARY_DIRECTORY}"
RESULT_VARIABLE result
OUTPUT_VARIABLE output
ERROR_QUIET
OUTPUT_STRIP_TRAILING_WHITESPACE)
if(result EQUAL 0)
string(REGEX MATCH "\\(?RUNPATH\\)?[^\n]*\\[([^\n]+)\\]" found_runpath "${output}")
string(REPLACE ":" ";" found_runpath "${CMAKE_MATCH_1}")
list(REMOVE_DUPLICATES found_runpath)
string(REGEX MATCH "\\(?RPATH\\)?[^\n]*\\[([^\n]+)\\]" found_rpath "${output}")
string(REPLACE ":" ";" found_rpath "${CMAKE_MATCH_1}")
list(REMOVE_DUPLICATES found_rpath)
set(${runpath} "${found_runpath}" PARENT_SCOPE)
set(${rpath} "${found_rpath}" PARENT_SCOPE)
else()
message(WARNING "Error running readelf for \"${shared_object_or_executable}\"")
endif()
else()
message(FATAL_ERROR "Readelf utility is not available.")
endif()
endfunction()
#sanitizes the given directory name if required
function(get_sanitized_dirname dirname outvar)
# NOTE: This pattern has to stay in sync with the 'prohibited_chars' variable
# defined in the C++ function `CPackGenerator::GetSanitizedDirOrFileName`!
set(prohibited_chars_pattern "[<]|[>]|[\"]|[/]|[\\]|[|]|[?]|[*]|[`]")
if("${dirname}" MATCHES "${prohibited_chars_pattern}")
string(MD5 santized_dirname "${dirname}")
set(${outvar} "${sanitized_dirname}" PARENT_SCOPE)
else()
set(${outvar} "${dirname}" PARENT_SCOPE)
endif()
endfunction()
#retrieve packaging directories of components the current component depends on
# Note: May only be called from within 'cpack_deb_prepare_package_var'!
function(get_packaging_dirs_of_dependencies outvar)
if(CPACK_DEB_PACKAGE_COMPONENT)
if(NOT DEFINED WDIR OR NOT DEFINED _local_component_name)
message(FATAL_ERROR "CPackDeb: Function '${CMAKE_CURRENT_FUNCTION}' not called from correct function scope!")
endif()
set(result_list)
foreach(dependency_name IN LISTS CPACK_COMPONENT_${_local_component_name}_DEPENDS)
get_sanitized_dirname("${dependency_name}" dependency_name)
cmake_path(APPEND_STRING WDIR "/../${dependency_name}" OUTPUT_VARIABLE dependency_packaging_dir)
cmake_path(NORMAL_PATH dependency_packaging_dir)
list(APPEND result_list "${dependency_packaging_dir}")
endforeach()
set(${outvar} "${result_list}" PARENT_SCOPE) # Set return variable.
else()
set(${outvar} "" PARENT_SCOPE) # Clear return variable.
endif()
endfunction()
function(cpack_deb_check_description SUMMARY LINES RESULT_VARIABLE)
set(_result TRUE)
@ -310,16 +369,44 @@ function(cpack_deb_prepare_package_vars)
set(IGNORE_MISSING_INFO_FLAG "--ignore-missing-info")
endif()
if(CPACK_DEBIAN_PACKAGE_SHLIBDEPS_PRIVATE_DIRS)
# Add -l option if the tool supports it?
if(DEFINED SHLIBDEPS_EXECUTABLE_VERSION AND SHLIBDEPS_EXECUTABLE_VERSION VERSION_GREATER_EQUAL 1.17.0)
unset(PRIVATE_SEARCH_DIR_OPTIONS)
# Add -l option if the tool supports it
if(DEFINED SHLIBDEPS_EXECUTABLE_VERSION AND SHLIBDEPS_EXECUTABLE_VERSION VERSION_GREATER_EQUAL 1.17.0)
foreach(dir IN LISTS CPACK_DEBIAN_PACKAGE_SHLIBDEPS_PRIVATE_DIRS)
list(APPEND PRIVATE_SEARCH_DIR_OPTIONS "-l${dir}")
# Use directories provided via CPACK_DEBIAN_PACKAGE_SHLIBDEPS_PRIVATE_DIRS
if(NOT "${CPACK_DEBIAN_PACKAGE_SHLIBDEPS_PRIVATE_DIRS}" STREQUAL "")
foreach(path IN LISTS CPACK_DEBIAN_PACKAGE_SHLIBDEPS_PRIVATE_DIRS)
cmake_path(NORMAL_PATH path) # Required for dpkg-shlibdeps!
list(APPEND PRIVATE_SEARCH_DIR_OPTIONS "-l${path}")
endforeach()
else()
message(WARNING "CPackDeb: dkpg-shlibdeps is too old. \"CPACK_DEBIAN_PACKAGE_SHLIBDEPS_PRIVATE_DIRS\" is therefore ignored.")
endif()
# Use directories extracted from RUNPATH/RPATH
get_packaging_dirs_of_dependencies(deps_packaging_dirs)
foreach(exe IN LISTS CPACK_DEB_BINARY_FILES)
cmake_path(GET exe PARENT_PATH exe_dir)
extract_runpath_and_rpath(${exe} runpath rpath)
# If RUNPATH is available, RPATH will be ignored. Therefore we have to do the same here!
if (NOT "${runpath}" STREQUAL "")
set(selected_rpath "${runpath}")
else()
set(selected_rpath "${rpath}")
endif()
foreach(search_path IN LISTS selected_rpath)
if ("${search_path}" MATCHES "^[$]ORIGIN" OR "${search_path}" MATCHES "^[$][{]ORIGIN[}]")
foreach(deps_pkgdir IN LISTS deps_packaging_dirs)
string(REPLACE "\$ORIGIN" "${deps_pkgdir}/${exe_dir}" path "${search_path}")
string(REPLACE "\${ORIGIN}" "${deps_pkgdir}/${exe_dir}" path "${path}")
cmake_path(NORMAL_PATH path) # Required for dpkg-shlibdeps!
list(APPEND PRIVATE_SEARCH_DIR_OPTIONS "-l${path}")
endforeach()
endif()
endforeach()
endforeach()
list(REMOVE_DUPLICATES PRIVATE_SEARCH_DIR_OPTIONS)
elseif(NOT "${CPACK_DEBIAN_PACKAGE_SHLIBDEPS_PRIVATE_DIRS}" STREQUAL "")
message(WARNING "CPackDeb: dkpg-shlibdeps is too old. \"CPACK_DEBIAN_PACKAGE_SHLIBDEPS_PRIVATE_DIRS\" is therefore ignored.")
endif()
# Execute dpkg-shlibdeps

View File

@ -1199,13 +1199,17 @@ if(BUILD_TESTING)
else()
unset(SHLIBDEPS_EXECUTABLE_VERSION)
endif()
# Check if distro has symbols or shlibs data
file(GLOB SHLIBS_FILES_EXIST "/var/lib/dpkg/info/*.shlibs" "/var/lib/dpkg/info/*.symbols")
if(NOT SHLIBDEPS_EXECUTABLE_VERSION VERSION_LESS 1.19 OR
(NOT SHLIBDEPS_EXECUTABLE_VERSION VERSION_LESS 1.17 AND NOT CMAKE_BINARY_DIR MATCHES ".*[ ].*"))
list(APPEND DEB_CONFIGURATIONS_TO_TEST "shlibdeps-with-private-lib-failure"
"shlibdeps-with-private-lib-success")
"shlibdeps-with-private-lib-success"
"shlibdeps-with-ORIGIN-RPATH-failure")
if(SHLIBS_FILES_EXIST)
list(APPEND DEB_CONFIGURATIONS_TO_TEST "shlibdeps-with-ORIGIN-RPATH-success")
endif()
endif()
# Check if distro has symbols or shlibs data
file(GLOB SHLIBS_FILES_EXIST "/var/lib/dpkg/info/*.shlibs" "/var/lib/dpkg/info/*.symbols")
if(SHLIBS_FILES_EXIST)
list(APPEND DEB_CONFIGURATIONS_TO_TEST "components-depend2")
endif()

View File

@ -5,6 +5,10 @@
# which supports CPack components.
cmake_minimum_required(VERSION 3.10 FATAL_ERROR)
# Make sure to properly escape RPATH/RUNPATH entries.
if (POLICY CMP0095)
cmake_policy(SET CMP0095 NEW)
endif()
project(CPackComponentsDEB VERSION 1.0.3)
# Use GNUInstallDirs in order to enforce lib64 if needed
@ -29,6 +33,14 @@ if (CPackDEBConfiguration MATCHES "shlibdeps-with-private-lib")
target_link_libraries(mylibapp3 myprivatelib)
endif()
if (CPackDEBConfiguration MATCHES "shlibdeps-with-ORIGIN-RPATH")
add_subdirectory("subdir")
add_executable(mylibapp4 mylibapp.cpp)
target_compile_definitions(mylibapp4 PRIVATE -DSHLIBDEPS_OTHER)
target_link_libraries(mylibapp4 PUBLIC myotherlib)
set_target_properties(mylibapp4 PROPERTIES INSTALL_RPATH "\${ORIGIN};$ORIGIN/../lib")
endif()
# Create installation targets. Note that we put each kind of file
# into a different component via COMPONENT. These components will
# be used to create the installation components.
@ -39,17 +51,28 @@ install(TARGETS mylib
install(TARGETS mylibapp
RUNTIME
DESTINATION bin
DESTINATION ${CMAKE_INSTALL_BINDIR}
COMPONENT applications)
install(FILES mylib.h
DESTINATION include
COMPONENT headers)
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
COMPONENT headers)
if (CPackDEBConfiguration MATCHES "shlibdeps-with-private-lib")
install(TARGETS mylibapp3
RUNTIME
DESTINATION bin
DESTINATION ${CMAKE_INSTALL_BINDIR}
COMPONENT applications)
endif()
if (CPackDEBConfiguration MATCHES "shlibdeps-with-ORIGIN-RPATH")
install(TARGETS myotherlib
LIBRARY
DESTINATION ${CMAKE_INSTALL_LIBDIR}
COMPONENT libraries)
install(TARGETS mylibapp4
RUNTIME
DESTINATION ${CMAKE_INSTALL_BINDIR}
COMPONENT applications)
endif()

View File

@ -0,0 +1,23 @@
#
# Activate component packaging
#
if(CPACK_GENERATOR MATCHES "DEB")
set(CPACK_DEB_COMPONENT_INSTALL "ON")
set(CPACK_DEBIAN_ENABLE_COMPONENT_DEPENDS "ON")
endif()
#
# Choose grouping way
#
#set(CPACK_COMPONENTS_ALL_GROUPS_IN_ONE_PACKAGE)
#set(CPACK_COMPONENTS_GROUPING)
set(CPACK_COMPONENTS_IGNORE_GROUPS 1)
#set(CPACK_COMPONENTS_ALL_IN_ONE_PACKAGE 1)
# we set shlibdeps to on
set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS ON)
# setting dependencies
# Note: We explicitly do NOT declare dependency to "libraries" component!
#set(CPACK_COMPONENT_APPLICATIONS_DEPENDS "libraries")

View File

@ -0,0 +1,22 @@
#
# Activate component packaging
#
if(CPACK_GENERATOR MATCHES "DEB")
set(CPACK_DEB_COMPONENT_INSTALL "ON")
set(CPACK_DEBIAN_ENABLE_COMPONENT_DEPENDS "ON")
endif()
#
# Choose grouping way
#
#set(CPACK_COMPONENTS_ALL_GROUPS_IN_ONE_PACKAGE)
#set(CPACK_COMPONENTS_GROUPING)
set(CPACK_COMPONENTS_IGNORE_GROUPS 1)
#set(CPACK_COMPONENTS_ALL_IN_ONE_PACKAGE 1)
# we set shlibdeps to on
set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS ON)
# setting dependencies
set(CPACK_COMPONENT_APPLICATIONS_DEPENDS "libraries")

View File

@ -0,0 +1,19 @@
if(NOT CPackComponentsDEB_SOURCE_DIR)
message(FATAL_ERROR "CPackComponentsDEB_SOURCE_DIR not set")
endif()
include(${CPackComponentsDEB_SOURCE_DIR}/RunCPackVerifyResult.cmake)
set(actual_output)
run_cpack(actual_output
CPack_output
CPack_error
EXPECT_FAILURE
CONFIG_ARGS ${config_args}
CONFIG_VERBOSE ${config_verbose})
string(REGEX MATCH "dpkg-shlibdeps: error: (cannot|couldn't) find[ \n\t]+library[ \n\t]+libmyotherlib.so.1[ \n\t]+needed[ \n\t]+by[ \n\t]+./usr/bin/mylibapp4" expected_error ${CPack_error})
if(NOT expected_error)
message(FATAL_ERROR "Did not get the expected error-message!")
endif()

View File

@ -0,0 +1,75 @@
if(NOT CPackComponentsDEB_SOURCE_DIR)
message(FATAL_ERROR "CPackComponentsDEB_SOURCE_DIR not set")
endif()
include(${CPackComponentsDEB_SOURCE_DIR}/RunCPackVerifyResult.cmake)
# requirements
# debian now produces lower case names
set(expected_file_mask "${CPackComponentsDEB_BINARY_DIR}/mylib-*_1.0.3_*.deb")
set(expected_count 3)
set(actual_output)
run_cpack(actual_output
CPack_output
CPack_error
EXPECTED_FILE_MASK "${expected_file_mask}"
CONFIG_ARGS ${config_args}
CONFIG_VERBOSE ${config_verbose})
message(STATUS "expected_count='${expected_count}'")
message(STATUS "expected_file_mask='${expected_file_mask}'")
message(STATUS "actual_output_files='${actual_output}'")
if(NOT actual_output)
message(FATAL_ERROR "error: expected_files do not exist: CPackComponentsDEB test fails. (CPack_output=${CPack_output}, CPack_error=${CPack_error}")
endif()
list(LENGTH actual_output actual_count)
message(STATUS "actual_count='${actual_count}'")
if(NOT actual_count EQUAL expected_count)
message(FATAL_ERROR "error: expected_count=${expected_count} does not match actual_count=${actual_count}: CPackComponents test fails. (CPack_output=${CPack_output}, CPack_error=${CPack_error})")
endif()
# dpkg-deb checks for the summary of the packages
find_program(DPKGDEB_EXECUTABLE dpkg-deb)
if(DPKGDEB_EXECUTABLE)
set(dpkgdeb_output_errors_all "")
foreach(_f IN LISTS actual_output)
# extracts the metadata from the package
run_dpkgdeb(dpkg_output
FILENAME ${_f}
)
dpkgdeb_return_specific_metaentry(dpkg_package_name
DPKGDEB_OUTPUT "${dpkg_output}"
METAENTRY "Package:")
message(STATUS "package='${dpkg_package_name}'")
if(dpkg_package_name STREQUAL "mylib-applications")
# pass
elseif(dpkg_package_name STREQUAL "mylib-headers")
# pass
elseif(dpkg_package_name STREQUAL "mylib-libraries")
# pass
else()
set(dpkgdeb_output_errors_all ${dpkgdeb_output_errors_all}
"dpkg-deb: ${_f}: component name not found: ${dpkg_package_name}\n")
endif()
endforeach()
if(NOT dpkgdeb_output_errors_all STREQUAL "")
message(FATAL_ERROR "dpkg-deb checks failed:\n${dpkgdeb_output_errors_all}")
endif()
else()
message("dpkg-deb executable not found - skipping dpkg-deb test")
endif()

View File

@ -1,13 +1,13 @@
#ifndef SHLIBDEPS_PRIVATE
#if defined SHLIBDEPS_OTHER
# include "mylib.h"
# include "subdir/myotherlib.h"
int main()
{
mylib_function();
myotherlib_function();
}
#else
#elif defined SHLIBDEPS_PRIVATE
# include "shlibdeps-with-private-lib/myprivatelib.h"
@ -16,4 +16,13 @@ int main()
myprivatelib_function();
}
#else
# include "mylib.h"
int main()
{
mylib_function();
}
#endif

View File

@ -0,0 +1,5 @@
add_library(myotherlib SHARED myotherlib.cpp)
set_target_properties( myotherlib PROPERTIES
VERSION 1.2.3
SOVERSION 1
)

View File

@ -0,0 +1,8 @@
#include "myotherlib.h"
#include "stdio.h"
void myotherlib_function()
{
printf("This is myotherlib");
}

View File

@ -0,0 +1 @@
void myotherlib_function();