diff options
author | Deniz Bahadir <deniz@code.bahadir.email> | 2024-05-30 14:13:46 (GMT) |
---|---|---|
committer | Deniz Bahadir <deniz@code.bahadir.email> | 2024-05-30 14:13:46 (GMT) |
commit | 27d161eac39f547a45655c47e6353a1dcecf5b89 (patch) | |
tree | 71a9eae2a396b5831da94322e04e67879511d102 | |
parent | c024b5cf9a8d96dcb1e289f086ef3d8760e316c4 (diff) | |
download | CMake-27d161eac39f547a45655c47e6353a1dcecf5b89.zip CMake-27d161eac39f547a45655c47e6353a1dcecf5b89.tar.gz CMake-27d161eac39f547a45655c47e6353a1dcecf5b89.tar.bz2 |
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
11 files changed, 294 insertions, 18 deletions
diff --git a/Modules/Internal/CPack/CPackDeb.cmake b/Modules/Internal/CPack/CPackDeb.cmake index 5a78e3f..d4a47e4 100644 --- a/Modules/Internal/CPack/CPackDeb.cmake +++ b/Modules/Internal/CPack/CPackDeb.cmake @@ -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 diff --git a/Tests/CMakeLists.txt b/Tests/CMakeLists.txt index 51e60fe..4048bfe 100644 --- a/Tests/CMakeLists.txt +++ b/Tests/CMakeLists.txt @@ -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() diff --git a/Tests/CPackComponentsDEB/CMakeLists.txt b/Tests/CPackComponentsDEB/CMakeLists.txt index b2e2106..a3eb337 100644 --- a/Tests/CPackComponentsDEB/CMakeLists.txt +++ b/Tests/CPackComponentsDEB/CMakeLists.txt @@ -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() diff --git a/Tests/CPackComponentsDEB/MyLibCPackConfig-shlibdeps-with-ORIGIN-RPATH-failure.cmake.in b/Tests/CPackComponentsDEB/MyLibCPackConfig-shlibdeps-with-ORIGIN-RPATH-failure.cmake.in new file mode 100644 index 0000000..0794fbd --- /dev/null +++ b/Tests/CPackComponentsDEB/MyLibCPackConfig-shlibdeps-with-ORIGIN-RPATH-failure.cmake.in @@ -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") diff --git a/Tests/CPackComponentsDEB/MyLibCPackConfig-shlibdeps-with-ORIGIN-RPATH-success.cmake.in b/Tests/CPackComponentsDEB/MyLibCPackConfig-shlibdeps-with-ORIGIN-RPATH-success.cmake.in new file mode 100644 index 0000000..81d6c7b --- /dev/null +++ b/Tests/CPackComponentsDEB/MyLibCPackConfig-shlibdeps-with-ORIGIN-RPATH-success.cmake.in @@ -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") diff --git a/Tests/CPackComponentsDEB/RunCPackVerifyResult-shlibdeps-with-ORIGIN-RPATH-failure.cmake b/Tests/CPackComponentsDEB/RunCPackVerifyResult-shlibdeps-with-ORIGIN-RPATH-failure.cmake new file mode 100644 index 0000000..dddcb4a --- /dev/null +++ b/Tests/CPackComponentsDEB/RunCPackVerifyResult-shlibdeps-with-ORIGIN-RPATH-failure.cmake @@ -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() diff --git a/Tests/CPackComponentsDEB/RunCPackVerifyResult-shlibdeps-with-ORIGIN-RPATH-success.cmake b/Tests/CPackComponentsDEB/RunCPackVerifyResult-shlibdeps-with-ORIGIN-RPATH-success.cmake new file mode 100644 index 0000000..6eff3db --- /dev/null +++ b/Tests/CPackComponentsDEB/RunCPackVerifyResult-shlibdeps-with-ORIGIN-RPATH-success.cmake @@ -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() diff --git a/Tests/CPackComponentsDEB/mylibapp.cpp b/Tests/CPackComponentsDEB/mylibapp.cpp index bb45831..6d63803 100644 --- a/Tests/CPackComponentsDEB/mylibapp.cpp +++ b/Tests/CPackComponentsDEB/mylibapp.cpp @@ -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 diff --git a/Tests/CPackComponentsDEB/subdir/CMakeLists.txt b/Tests/CPackComponentsDEB/subdir/CMakeLists.txt new file mode 100644 index 0000000..71b3fdd --- /dev/null +++ b/Tests/CPackComponentsDEB/subdir/CMakeLists.txt @@ -0,0 +1,5 @@ +add_library(myotherlib SHARED myotherlib.cpp) +set_target_properties( myotherlib PROPERTIES + VERSION 1.2.3 + SOVERSION 1 +) diff --git a/Tests/CPackComponentsDEB/subdir/myotherlib.cpp b/Tests/CPackComponentsDEB/subdir/myotherlib.cpp new file mode 100644 index 0000000..eec270c --- /dev/null +++ b/Tests/CPackComponentsDEB/subdir/myotherlib.cpp @@ -0,0 +1,8 @@ +#include "myotherlib.h" + +#include "stdio.h" + +void myotherlib_function() +{ + printf("This is myotherlib"); +} diff --git a/Tests/CPackComponentsDEB/subdir/myotherlib.h b/Tests/CPackComponentsDEB/subdir/myotherlib.h new file mode 100644 index 0000000..71848a0 --- /dev/null +++ b/Tests/CPackComponentsDEB/subdir/myotherlib.h @@ -0,0 +1 @@ +void myotherlib_function(); |