Skip to content

Commit 361700e

Browse files
committed
Add provider merging tests and enhance dependency handling
- Introduced a new CI step to run provider merging tests, ensuring that multiple `find_package` calls with different components are correctly merged. - Enhanced the CMake dependency provider to track and merge components, preventing duplicates and preserving optional components. - Added tests to verify the merging functionality, confirming that the provider behaves as expected when handling multiple dependencies. These changes improve the robustness of the dependency management system in the cpp-library.
1 parent 22c7da1 commit 361700e

4 files changed

Lines changed: 243 additions & 0 deletions

File tree

.github/workflows/ci.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ jobs:
1919
- name: Run dependency mapping tests
2020
run: cmake -P tests/install/CMakeLists.txt
2121

22+
- name: Run provider merging tests
23+
run: cmake -P tests/install/test_provider_merge.cmake
24+
2225
integration-tests:
2326
name: Integration Tests
2427
runs-on: ubuntu-latest

cmake/cpp-library-dependency-provider.cmake

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,59 @@ function(_cpp_library_track_find_package package_name)
9393
string(APPEND FIND_DEP_CALL " CONFIG")
9494
endif()
9595

96+
# Check if this package was already tracked and merge components if needed
97+
get_property(EXISTING_CALL GLOBAL PROPERTY "_CPP_LIBRARY_TRACKED_DEP_${package_name}")
98+
if(EXISTING_CALL)
99+
# Parse existing components
100+
set(EXISTING_COMPONENTS "")
101+
if(EXISTING_CALL MATCHES "COMPONENTS +([^O][^ ]*( +[^O][^ ]*)*)")
102+
string(REGEX REPLACE " +" ";" EXISTING_COMPONENTS "${CMAKE_MATCH_1}")
103+
endif()
104+
105+
# Merge new components with existing ones (deduplicate)
106+
set(MERGED_COMPONENTS ${EXISTING_COMPONENTS})
107+
foreach(comp IN LISTS FP_COMPONENTS)
108+
if(NOT comp IN_LIST MERGED_COMPONENTS)
109+
list(APPEND MERGED_COMPONENTS "${comp}")
110+
endif()
111+
endforeach()
112+
113+
# Rebuild FIND_DEP_CALL with merged components if we have any
114+
if(MERGED_COMPONENTS)
115+
# Extract base call (package name, version, and flags without components)
116+
string(REGEX REPLACE " COMPONENTS.*$" "" BASE_CALL "${EXISTING_CALL}")
117+
string(REGEX REPLACE " OPTIONAL_COMPONENTS.*$" "" BASE_CALL "${BASE_CALL}")
118+
119+
set(FIND_DEP_CALL "${BASE_CALL}")
120+
list(JOIN MERGED_COMPONENTS " " MERGED_COMPONENTS_STR)
121+
string(APPEND FIND_DEP_CALL " COMPONENTS ${MERGED_COMPONENTS_STR}")
122+
123+
# Add OPTIONAL_COMPONENTS if present in either old or new
124+
set(OPT_COMPONENTS ${FP_OPTIONAL_COMPONENTS})
125+
if(EXISTING_CALL MATCHES "OPTIONAL_COMPONENTS +([^ ]+( +[^ ]+)*)")
126+
string(REGEX REPLACE " +" ";" EXISTING_OPT "${CMAKE_MATCH_1}")
127+
foreach(comp IN LISTS EXISTING_OPT)
128+
if(NOT comp IN_LIST OPT_COMPONENTS)
129+
list(APPEND OPT_COMPONENTS "${comp}")
130+
endif()
131+
endforeach()
132+
endif()
133+
if(OPT_COMPONENTS)
134+
list(JOIN OPT_COMPONENTS " " OPT_COMPONENTS_STR)
135+
string(APPEND FIND_DEP_CALL " OPTIONAL_COMPONENTS ${OPT_COMPONENTS_STR}")
136+
endif()
137+
138+
# Preserve CONFIG flag if present in either
139+
if(EXISTING_CALL MATCHES "CONFIG" OR FP_CONFIG OR FP_NO_MODULE)
140+
if(NOT FIND_DEP_CALL MATCHES "CONFIG")
141+
string(APPEND FIND_DEP_CALL " CONFIG")
142+
endif()
143+
endif()
144+
endif()
145+
146+
message(DEBUG "cpp-library: Merged find_package(${package_name}) components: ${MERGED_COMPONENTS_STR}")
147+
endif()
148+
96149
# Store the dependency information globally
97150
# Key: package_name, Value: find_dependency() call syntax
98151
set_property(GLOBAL PROPERTY "_CPP_LIBRARY_TRACKED_DEP_${package_name}" "${FIND_DEP_CALL}")

tests/install/test_dependency_provider.cmake

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,3 +107,14 @@ mock_target_links(test27_target "OpenCV::core" "OpenCV::imgproc")
107107
_cpp_library_generate_dependencies(RESULT test27_target "mylib")
108108
verify_output("${RESULT}" "find_dependency(OpenCV 4.5.3 COMPONENTS core imgproc)" "Test 27")
109109

110+
# Test 28: Multiple find_package calls with different components should merge (bug fix verification)
111+
run_test("Multiple find_package calls - component merging")
112+
# Simulate the result of multiple find_package calls that the provider would have merged
113+
# (The actual merging happens in the provider, here we verify the install module uses merged data)
114+
set_property(GLOBAL PROPERTY "_CPP_LIBRARY_TRACKED_DEP_Qt6" "Qt6 6.5.0 COMPONENTS Core Widgets")
115+
set_property(GLOBAL APPEND PROPERTY _CPP_LIBRARY_ALL_TRACKED_DEPS "Qt6")
116+
set_property(GLOBAL PROPERTY _CPP_LIBRARY_PROVIDER_INSTALLED TRUE)
117+
mock_target_links(test28_target "Qt6::Core" "Qt6::Widgets")
118+
_cpp_library_generate_dependencies(RESULT test28_target "mylib")
119+
verify_output("${RESULT}" "find_dependency(Qt6 6.5.0 COMPONENTS Core Widgets)" "Test 28")
120+
Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
# SPDX-License-Identifier: BSL-1.0
2+
#
3+
# Integration test for dependency provider component merging
4+
# This test copies the tracking function to test it in isolation
5+
6+
cmake_minimum_required(VERSION 3.20)
7+
8+
# Copy of _cpp_library_track_find_package for testing
9+
function(_cpp_library_track_find_package package_name)
10+
# Parse find_package arguments
11+
set(options QUIET REQUIRED NO_MODULE CONFIG)
12+
set(oneValueArgs)
13+
set(multiValueArgs COMPONENTS OPTIONAL_COMPONENTS)
14+
15+
cmake_parse_arguments(FP "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
16+
17+
# Extract version if present (first unparsed argument that looks like a version)
18+
set(VERSION "")
19+
foreach(arg IN LISTS FP_UNPARSED_ARGUMENTS)
20+
if(arg MATCHES "^[0-9]+\\.[0-9]")
21+
set(VERSION "${arg}")
22+
break()
23+
endif()
24+
endforeach()
25+
26+
# Build the canonical find_dependency() call syntax
27+
set(FIND_DEP_CALL "${package_name}")
28+
29+
if(VERSION)
30+
string(APPEND FIND_DEP_CALL " ${VERSION}")
31+
endif()
32+
33+
# Add components if present
34+
if(FP_COMPONENTS)
35+
list(JOIN FP_COMPONENTS " " COMPONENTS_STR)
36+
string(APPEND FIND_DEP_CALL " COMPONENTS ${COMPONENTS_STR}")
37+
endif()
38+
39+
if(FP_OPTIONAL_COMPONENTS)
40+
list(JOIN FP_OPTIONAL_COMPONENTS " " OPT_COMPONENTS_STR)
41+
string(APPEND FIND_DEP_CALL " OPTIONAL_COMPONENTS ${OPT_COMPONENTS_STR}")
42+
endif()
43+
44+
# Add other flags
45+
if(FP_CONFIG OR FP_NO_MODULE)
46+
string(APPEND FIND_DEP_CALL " CONFIG")
47+
endif()
48+
49+
# Check if this package was already tracked and merge components if needed
50+
get_property(EXISTING_CALL GLOBAL PROPERTY "_CPP_LIBRARY_TRACKED_DEP_${package_name}")
51+
if(EXISTING_CALL)
52+
# Parse existing components
53+
set(EXISTING_COMPONENTS "")
54+
if(EXISTING_CALL MATCHES "COMPONENTS +([^O][^ ]*( +[^O][^ ]*)*)")
55+
string(REGEX REPLACE " +" ";" EXISTING_COMPONENTS "${CMAKE_MATCH_1}")
56+
endif()
57+
58+
# Merge new components with existing ones (deduplicate)
59+
set(MERGED_COMPONENTS ${EXISTING_COMPONENTS})
60+
foreach(comp IN LISTS FP_COMPONENTS)
61+
if(NOT comp IN_LIST MERGED_COMPONENTS)
62+
list(APPEND MERGED_COMPONENTS "${comp}")
63+
endif()
64+
endforeach()
65+
66+
# Rebuild FIND_DEP_CALL with merged components if we have any
67+
if(MERGED_COMPONENTS)
68+
# Extract base call (package name, version, and flags without components)
69+
string(REGEX REPLACE " COMPONENTS.*$" "" BASE_CALL "${EXISTING_CALL}")
70+
string(REGEX REPLACE " OPTIONAL_COMPONENTS.*$" "" BASE_CALL "${BASE_CALL}")
71+
72+
set(FIND_DEP_CALL "${BASE_CALL}")
73+
list(JOIN MERGED_COMPONENTS " " MERGED_COMPONENTS_STR)
74+
string(APPEND FIND_DEP_CALL " COMPONENTS ${MERGED_COMPONENTS_STR}")
75+
76+
# Add OPTIONAL_COMPONENTS if present in either old or new
77+
set(OPT_COMPONENTS ${FP_OPTIONAL_COMPONENTS})
78+
if(EXISTING_CALL MATCHES "OPTIONAL_COMPONENTS +([^ ]+( +[^ ]+)*)")
79+
string(REGEX REPLACE " +" ";" EXISTING_OPT "${CMAKE_MATCH_1}")
80+
foreach(comp IN LISTS EXISTING_OPT)
81+
if(NOT comp IN_LIST OPT_COMPONENTS)
82+
list(APPEND OPT_COMPONENTS "${comp}")
83+
endif()
84+
endforeach()
85+
endif()
86+
if(OPT_COMPONENTS)
87+
list(JOIN OPT_COMPONENTS " " OPT_COMPONENTS_STR)
88+
string(APPEND FIND_DEP_CALL " OPTIONAL_COMPONENTS ${OPT_COMPONENTS_STR}")
89+
endif()
90+
91+
# Preserve CONFIG flag if present in either
92+
if(EXISTING_CALL MATCHES "CONFIG" OR FP_CONFIG OR FP_NO_MODULE)
93+
if(NOT FIND_DEP_CALL MATCHES "CONFIG")
94+
string(APPEND FIND_DEP_CALL " CONFIG")
95+
endif()
96+
endif()
97+
endif()
98+
endif()
99+
100+
# Store the dependency information globally
101+
set_property(GLOBAL PROPERTY "_CPP_LIBRARY_TRACKED_DEP_${package_name}" "${FIND_DEP_CALL}")
102+
103+
# Also maintain a list of all tracked packages for iteration
104+
get_property(ALL_DEPS GLOBAL PROPERTY _CPP_LIBRARY_ALL_TRACKED_DEPS)
105+
if(NOT package_name IN_LIST ALL_DEPS)
106+
set_property(GLOBAL APPEND PROPERTY _CPP_LIBRARY_ALL_TRACKED_DEPS "${package_name}")
107+
endif()
108+
endfunction()
109+
110+
message(STATUS "===========================================")
111+
message(STATUS "Provider Component Merging Integration Test")
112+
message(STATUS "===========================================")
113+
114+
# Test: Multiple find_package calls with same package, different components
115+
message(STATUS "Test: Calling find_package(Qt6 COMPONENTS Core) then find_package(Qt6 COMPONENTS Widgets)")
116+
117+
# Clear any existing state
118+
set_property(GLOBAL PROPERTY "_CPP_LIBRARY_TRACKED_DEP_Qt6")
119+
set_property(GLOBAL PROPERTY _CPP_LIBRARY_ALL_TRACKED_DEPS "")
120+
121+
# First call: find_package(Qt6 6.5.0 COMPONENTS Core)
122+
_cpp_library_track_find_package("Qt6" "6.5.0" "COMPONENTS" "Core")
123+
124+
# Check what was stored
125+
get_property(FIRST_CALL GLOBAL PROPERTY "_CPP_LIBRARY_TRACKED_DEP_Qt6")
126+
message(STATUS "After first call: ${FIRST_CALL}")
127+
128+
# Second call: find_package(Qt6 6.5.0 COMPONENTS Widgets)
129+
_cpp_library_track_find_package("Qt6" "6.5.0" "COMPONENTS" "Widgets")
130+
131+
# Check what was stored after merge
132+
get_property(MERGED_CALL GLOBAL PROPERTY "_CPP_LIBRARY_TRACKED_DEP_Qt6")
133+
message(STATUS "After second call: ${MERGED_CALL}")
134+
135+
# Verify the result
136+
set(EXPECTED "Qt6 6.5.0 COMPONENTS Core Widgets")
137+
if("${MERGED_CALL}" STREQUAL "${EXPECTED}")
138+
message(STATUS "✓ PASS: Components correctly merged")
139+
else()
140+
message(FATAL_ERROR "✗ FAIL: Expected '${EXPECTED}' but got '${MERGED_CALL}'")
141+
endif()
142+
143+
# Test: Third call adds another component
144+
message(STATUS "")
145+
message(STATUS "Test: Adding Network component with third call")
146+
_cpp_library_track_find_package("Qt6" "6.5.0" "COMPONENTS" "Network")
147+
148+
get_property(TRIPLE_CALL GLOBAL PROPERTY "_CPP_LIBRARY_TRACKED_DEP_Qt6")
149+
message(STATUS "After third call: ${TRIPLE_CALL}")
150+
151+
set(EXPECTED3 "Qt6 6.5.0 COMPONENTS Core Widgets Network")
152+
if("${TRIPLE_CALL}" STREQUAL "${EXPECTED3}")
153+
message(STATUS "✓ PASS: Third component correctly merged")
154+
else()
155+
message(FATAL_ERROR "✗ FAIL: Expected '${EXPECTED3}' but got '${TRIPLE_CALL}'")
156+
endif()
157+
158+
# Test: Duplicate component should not be added twice
159+
message(STATUS "")
160+
message(STATUS "Test: Calling again with Core component (should not duplicate)")
161+
_cpp_library_track_find_package("Qt6" "6.5.0" "COMPONENTS" "Core")
162+
163+
get_property(DEDUP_CALL GLOBAL PROPERTY "_CPP_LIBRARY_TRACKED_DEP_Qt6")
164+
message(STATUS "After duplicate: ${DEDUP_CALL}")
165+
166+
# Should still be the same (Core not duplicated)
167+
if("${DEDUP_CALL}" STREQUAL "${EXPECTED3}")
168+
message(STATUS "✓ PASS: Duplicate component not added")
169+
else()
170+
message(FATAL_ERROR "✗ FAIL: Expected '${EXPECTED3}' but got '${DEDUP_CALL}'")
171+
endif()
172+
173+
message(STATUS "")
174+
message(STATUS "===========================================")
175+
message(STATUS "All provider merging tests passed!")
176+
message(STATUS "===========================================")

0 commit comments

Comments
 (0)