Skip to content

Commit 5300610

Browse files
committed
fix: own each dist copy once to make runtime copies generator-agnostic
Replaces the Ninja-only JOB_POOL serialization with a structural fix that holds for every generator: each runtime file in the shared Binaries dir is copied by exactly one owner instead of by every consumer. Target-referenced files (first-party dlls and imported third-party dlls such as Qt) get a deduplicated per-file owner created in the consumer scope where the target is visible; first-party owners wait on their producing target while sharing a single owner is avoided so build tools like MirrorTool do not close a dependency cycle. Plain-path third-party files and resources are batched into one per-sub-project assets target whose copies run sequentially.
1 parent 210f06d commit 5300610

1 file changed

Lines changed: 123 additions & 33 deletions

File tree

‎CMake/Target.cmake‎

Lines changed: 123 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,6 @@ set(AUX_TARGETS_FOLDER "${BASE_TARGETS_FOLDER}/Aux")
1212

1313
get_cmake_property(with_multi_config_generator GENERATOR_IS_MULTI_CONFIG)
1414

15-
# Runtime-dependency and resource copies all land in the shared per-sub-project Binaries directory, and many targets copy
16-
# an overlapping set of files there. Running those copies concurrently races on the same destination files (intermittent
17-
# "Error copying file" failures under Ninja), so funnel them through a depth-1 job pool to serialize their execution.
18-
set_property(GLOBAL PROPERTY JOB_POOLS exp_copy=1)
19-
2015
if (BUILD_TEST)
2116
enable_testing()
2217
add_compile_definitions(BUILD_TEST=1)
@@ -91,6 +86,95 @@ function(exp_gather_target_runtime_dependencies_recurse)
9186
set(${arg_OUT_DEP_TARGET} ${result_dep_target} PARENT_SCOPE)
9287
endfunction()
9388

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+
94178
function(exp_process_runtime_dependencies)
95179
set(options NOT_INSTALL)
96180
set(singleValueArgs NAME)
@@ -110,29 +194,41 @@ function(exp_process_runtime_dependencies)
110194
OUT_DEP_TARGET dep_dep_targets
111195
)
112196
list(APPEND runtime_deps ${dep_target_runtime_deps})
113-
list(APPEND dep_targets ${dep_dep_targets})
114197
endforeach ()
115198

116-
set(copy_commands COMMAND ${CMAKE_COMMAND} -E make_directory $<TARGET_FILE_DIR:${arg_NAME}>)
117-
foreach(r ${runtime_deps})
118-
list(APPEND copy_commands COMMAND ${CMAKE_COMMAND} -E copy_if_different ${r} $<TARGET_FILE_DIR:${arg_NAME}>)
119-
endforeach()
120-
if (NOT "${copy_commands}" STREQUAL "")
121-
set(custom_target_name ${arg_NAME}.CopyRuntimeDeps)
122-
add_custom_target(
123-
${custom_target_name}
124-
${copy_commands}
125-
JOB_POOL exp_copy
126-
)
199+
if (runtime_deps)
200+
list(REMOVE_DUPLICATES runtime_deps)
201+
endif ()
127202

128-
add_dependencies(${arg_NAME} ${custom_target_name})
129-
foreach (t ${dep_targets})
130-
add_dependencies(${custom_target_name} ${t})
131-
endforeach ()
203+
foreach (r ${runtime_deps})
204+
set(referenced "")
205+
if ("${r}" MATCHES "^\\$<TARGET_FILE:(.+)>$")
206+
set(referenced ${CMAKE_MATCH_1})
207+
endif ()
132208

133-
set_target_properties(${custom_target_name} PROPERTIES FOLDER ${AUX_TARGETS_FOLDER})
134-
endif ()
135-
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)
136232
install(
137233
FILES ${runtime_deps} DESTINATION ${SUB_PROJECT_NAME}/Binaries
138234
)
@@ -166,7 +262,7 @@ function(exp_add_resources_copy_command)
166262
OUTPUT_DST dst
167263
)
168264

169-
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}")
170266

171267
cmake_path(SET dst_path NORMALIZE "${SUB_PROJECT_NAME}/Binaries/${dst}")
172268
cmake_path(GET dst_path PARENT_PATH dst_dir)
@@ -175,14 +271,8 @@ function(exp_add_resources_copy_command)
175271
endif ()
176272
endforeach()
177273

178-
set(copy_res_target_name ${arg_NAME}.CopyRes)
179-
add_custom_target(
180-
${copy_res_target_name}
181-
${copy_commands}
182-
JOB_POOL exp_copy
183-
)
184-
set_target_properties(${copy_res_target_name} PROPERTIES FOLDER ${AUX_TARGETS_FOLDER})
185-
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()
186276
endfunction()
187277

188278
function(exp_gather_target_libs)

0 commit comments

Comments
 (0)