@@ -86,6 +86,95 @@ function(exp_gather_target_runtime_dependencies_recurse)
8686 set (${arg_OUT_DEP_TARGET} ${result_dep_target} PARENT_SCOPE )
8787endfunction ()
8888
89+ # Every runtime file lands in the single shared per-sub-project Binaries directory. To keep the copies generator-agnostic
90+ # and race-free, each destination is owned by exactly one copy step rather than being re-copied by every consumer:
91+ # - files referenced through a target ($<TARGET_FILE:...>) get a per-file owner created in the consumer's scope, so the
92+ # generator expression resolves where the target is visible (imported third-party targets such as Qt are
93+ # directory-scoped and would be invisible in a global aggregate target). A first-party owner additionally waits on its
94+ # producing target; merging these into one target is impossible because a build tool such as MirrorTool consumes a dll
95+ # while another dll's producer transitively depends on the tool, which would close a build cycle;
96+ # - prebuilt third-party files given as plain paths, plus resources, carry no target and are batched (deduplicated) into
97+ # one per-sub-project assets target whose copies run sequentially (see exp_finalize_dist_assets).
98+ function (exp_add_runtime_dep_copy )
99+ set (options "" )
100+ set (singleValueArgs KEY SRC PRODUCER OUTPUT_TARGET)
101+ set (multiValueArgs "" )
102+ cmake_parse_arguments (arg "${options} " "${singleValueArgs} " "${multiValueArgs} " ${ARGN} )
103+
104+ string (MAKE_C_IDENTIFIER "${arg_KEY} " key_id)
105+ set (registry_property EXP_RUNTIME_DEP_COPY_${SUB_PROJECT_NAME} _${key_id} )
106+
107+ get_property (copy_target GLOBAL PROPERTY ${registry_property} )
108+ if (NOT copy_target)
109+ exp_get_runtime_output_dir (OUTPUT out_dir )
110+ set (copy_target ${SUB_PROJECT_NAME} .CopyDll.${key_id} )
111+ add_custom_target (
112+ ${copy_target}
113+ COMMAND ${CMAKE_COMMAND } -E make_directory ${out_dir}
114+ COMMAND ${CMAKE_COMMAND } -E copy_if_different ${arg_SRC} ${out_dir}
115+ )
116+ set_target_properties (${copy_target} PROPERTIES FOLDER ${AUX_TARGETS_FOLDER} )
117+ if (arg_PRODUCER)
118+ add_dependencies (${copy_target} ${arg_PRODUCER} )
119+ endif ()
120+ set_property (GLOBAL PROPERTY ${registry_property} ${copy_target} )
121+ endif ()
122+
123+ set (${arg_OUTPUT_TARGET} ${copy_target} PARENT_SCOPE )
124+ endfunction ()
125+
126+ function (exp_schedule_dist_assets_finalize )
127+ get_property (scheduled GLOBAL PROPERTY EXP_DIST_ASSETS_SCHEDULED_${SUB_PROJECT_NAME} )
128+ if (NOT scheduled)
129+ set_property (GLOBAL PROPERTY EXP_DIST_ASSETS_SCHEDULED_${SUB_PROJECT_NAME} TRUE )
130+ cmake_language (DEFER DIRECTORY ${CMAKE_SOURCE_DIR } CALL exp_finalize_dist_assets "${SUB_PROJECT_NAME} " )
131+ endif ()
132+ endfunction ()
133+
134+ function (exp_finalize_dist_assets sub_project )
135+ get_property (asset_files GLOBAL PROPERTY EXP_DIST_ASSET_FILES_${sub_project} )
136+ get_property (asset_resources GLOBAL PROPERTY EXP_DIST_ASSET_RESOURCES_${sub_project} )
137+ get_property (consumers GLOBAL PROPERTY EXP_DIST_ASSET_CONSUMERS_${sub_project} )
138+
139+ if (NOT asset_files AND NOT asset_resources)
140+ return ()
141+ endif ()
142+
143+ if (with_multi_config_generator)
144+ set (out_dir ${CMAKE_BINARY_DIR } /Dist/$<CONFIG >/${sub_project} /Binaries)
145+ else ()
146+ set (out_dir ${CMAKE_BINARY_DIR } /Dist/${sub_project} /Binaries)
147+ endif ()
148+
149+ set (copy_commands COMMAND ${CMAKE_COMMAND } -E make_directory ${out_dir} )
150+ if (asset_files)
151+ list (REMOVE_DUPLICATES asset_files)
152+ foreach (f ${asset_files} )
153+ list (APPEND copy_commands COMMAND ${CMAKE_COMMAND } -E copy_if_different ${f} ${out_dir} )
154+ endforeach ()
155+ endif ()
156+ if (asset_resources)
157+ list (REMOVE_DUPLICATES asset_resources)
158+ foreach (entry ${asset_resources} )
159+ string (REPLACE "->" ";" pair "${entry} " )
160+ list (GET pair 0 src)
161+ list (GET pair 1 dst)
162+ list (APPEND copy_commands COMMAND ${CMAKE_COMMAND } -E copy_if_different ${src} ${out_dir} /${dst} )
163+ endforeach ()
164+ endif ()
165+
166+ set (copy_target ${sub_project} .CopyDistAssets)
167+ add_custom_target (${copy_target} ${copy_commands} )
168+ set_target_properties (${copy_target} PROPERTIES FOLDER ${sub_project} /Aux)
169+
170+ if (consumers)
171+ list (REMOVE_DUPLICATES consumers)
172+ foreach (consumer ${consumers} )
173+ add_dependencies (${consumer} ${copy_target} )
174+ endforeach ()
175+ endif ()
176+ endfunction ()
177+
89178function (exp_process_runtime_dependencies )
90179 set (options NOT_INSTALL)
91180 set (singleValueArgs NAME)
@@ -105,28 +194,41 @@ function(exp_process_runtime_dependencies)
105194 OUT_DEP_TARGET dep_dep_targets
106195 )
107196 list (APPEND runtime_deps ${dep_target_runtime_deps} )
108- list (APPEND dep_targets ${dep_dep_targets} )
109197 endforeach ()
110198
111- set (copy_commands COMMAND ${CMAKE_COMMAND } -E make_directory $<TARGET_FILE_DIR :${arg_NAME} >)
112- foreach (r ${runtime_deps} )
113- list (APPEND copy_commands COMMAND ${CMAKE_COMMAND } -E copy_if_different ${r} $<TARGET_FILE_DIR :${arg_NAME} >)
114- endforeach ()
115- if (NOT "${copy_commands} " STREQUAL "" )
116- set (custom_target_name ${arg_NAME} .CopyRuntimeDeps)
117- add_custom_target (
118- ${custom_target_name}
119- ${copy_commands}
120- )
199+ if (runtime_deps)
200+ list (REMOVE_DUPLICATES runtime_deps)
201+ endif ()
121202
122- add_dependencies (${arg_NAME} ${custom_target_name} )
123- foreach (t ${dep_targets} )
124- add_dependencies (${custom_target_name} ${t} )
125- endforeach ()
203+ foreach (r ${runtime_deps} )
204+ set (referenced "" )
205+ if ("${r} " MATCHES "^\\ $<TARGET_FILE :(.+)>$" )
206+ set (referenced ${CMAKE_MATCH_1} )
207+ endif ()
126208
127- set_target_properties (${custom_target_name} PROPERTIES FOLDER ${AUX_TARGETS_FOLDER} )
128- endif ()
129- if (NOT arg_NOT_INSTALL AND NOT "${runtime_deps} " STREQUAL "" )
209+ if (referenced AND TARGET ${referenced} )
210+ set (producer "" )
211+ get_target_property (referenced_imported ${referenced} IMPORTED )
212+ if (NOT referenced_imported)
213+ set (producer ${referenced} )
214+ endif ()
215+
216+ exp_add_runtime_dep_copy (
217+ KEY ${r}
218+ SRC ${r}
219+ PRODUCER ${producer}
220+ OUTPUT_TARGET copy_target
221+ )
222+ add_dependencies (${arg_NAME} ${copy_target} )
223+ else ()
224+ set_property (GLOBAL APPEND PROPERTY EXP_DIST_ASSET_FILES_${SUB_PROJECT_NAME} ${r} )
225+ endif ()
226+ endforeach ()
227+
228+ set_property (GLOBAL APPEND PROPERTY EXP_DIST_ASSET_CONSUMERS_${SUB_PROJECT_NAME} ${arg_NAME} )
229+ exp_schedule_dist_assets_finalize ()
230+
231+ if (NOT arg_NOT_INSTALL AND runtime_deps)
130232 install (
131233 FILES ${runtime_deps} DESTINATION ${SUB_PROJECT_NAME} /Binaries
132234 )
@@ -160,7 +262,7 @@ function(exp_add_resources_copy_command)
160262 OUTPUT_DST dst
161263 )
162264
163- list ( APPEND copy_commands COMMAND ${ CMAKE_COMMAND } -E copy_if_different ${src} $< TARGET_FILE_DIR : ${arg_NAME} >/ ${dst} )
265+ set_property ( GLOBAL APPEND PROPERTY EXP_DIST_ASSET_RESOURCES_ ${SUB_PROJECT_NAME} " ${src} -> ${dst} " )
164266
165267 cmake_path (SET dst_path NORMALIZE "${SUB_PROJECT_NAME} /Binaries/${dst} " )
166268 cmake_path (GET dst_path PARENT_PATH dst_dir )
@@ -169,13 +271,8 @@ function(exp_add_resources_copy_command)
169271 endif ()
170272 endforeach ()
171273
172- set (copy_res_target_name ${arg_NAME} .CopyRes)
173- add_custom_target (
174- ${copy_res_target_name}
175- ${copy_commands}
176- )
177- set_target_properties (${copy_res_target_name} PROPERTIES FOLDER ${AUX_TARGETS_FOLDER} )
178- add_dependencies (${arg_NAME} ${copy_res_target_name} )
274+ set_property (GLOBAL APPEND PROPERTY EXP_DIST_ASSET_CONSUMERS_${SUB_PROJECT_NAME} ${arg_NAME} )
275+ exp_schedule_dist_assets_finalize ()
179276endfunction ()
180277
181278function (exp_gather_target_libs )
0 commit comments