diff --git a/CMakeLists.txt b/CMakeLists.txt index d5ef55de09..5225ff76ea 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -352,13 +352,19 @@ string(TIMESTAMP UTC_NOW "%Y-%m-%dT%H-%MZ" UTC) # Set AIO directory path used by multiple targets below set(AIO_DIR "${CMAKE_CURRENT_BINARY_DIR}/aio") -# Robocopy wrapper for Windows incremental file copy (used by deployment targets) +# Robocopy wrapper for Windows incremental file copy (used by deployment targets). +# We invoke through `cmd /c ""` rather than the bare wrapper path because +# modern Windows refuses to execute scripts from the current directory without an explicit +# `.\` prefix, and CMake strips the absolute path on COMMAND args that live inside +# CMAKE_BINARY_DIR (the custom-build CWD). Treating the wrapper as an argument to cmd +# keeps the absolute path intact in the generated MSBuild command. if(WIN32) - set(ROBOCOPY_WRAPPER "${CMAKE_BINARY_DIR}/robocopy_wrapper.cmd") + set(ROBOCOPY_WRAPPER_PATH "${CMAKE_BINARY_DIR}/robocopy_wrapper.cmd") file( - WRITE ${ROBOCOPY_WRAPPER} + WRITE ${ROBOCOPY_WRAPPER_PATH} "@echo off\r\nrem Robocopy wrapper: forwards all args to robocopy and normalizes exit codes\r\nrobocopy %*\r\nset rc=%ERRORLEVEL%\r\nif %rc% GEQ 8 exit /b %rc%\r\nexit /b 0\r\n" ) + set(ROBOCOPY_WRAPPER cmd /c "${ROBOCOPY_WRAPPER_PATH}") endif() # ####################################################################################################################### @@ -418,14 +424,20 @@ if(AUTO_PLUGIN_DEPLOYMENT OR AIO_ZIP_TO_DIST) # Prepare AIO only when sources change. Gather package + feature files as # inputs so the prepare step runs only when something actually changed. + # Shader files are intentionally excluded - copy_shaders.stamp owns those, + # and including them here causes a race where two parallel custom-build + # rules call cmake -E copy_if_different on the same destination file + # (Permission denied on Windows when one process has it open for write). file( GLOB_RECURSE _AIO_PACKAGE_FILES LIST_DIRECTORIES FALSE "${CMAKE_SOURCE_DIR}/package/*" ) list(FILTER _AIO_PACKAGE_FILES EXCLUDE REGEX "/Tests/") + list(FILTER _AIO_PACKAGE_FILES EXCLUDE REGEX "/Shaders/") foreach(_fpath IN LISTS FEATURE_PATHS) file(GLOB_RECURSE _tmp LIST_DIRECTORIES FALSE "${_fpath}/*") + list(FILTER _tmp EXCLUDE REGEX "/Shaders/") list(APPEND _AIO_PACKAGE_FILES ${_tmp}) endforeach() @@ -457,16 +469,21 @@ if(AUTO_PLUGIN_DEPLOYMENT OR AIO_ZIP_TO_DIST) "${CMAKE_SOURCE_DIR}/cmake/CleanupStaleEntries.cmake" ) - # Copy package files (exclude test files from production packages) + # Copy package files (exclude test files from production packages, and + # exclude shaders since copy_shaders.stamp owns those - see input-tracking + # comment above for the race-condition rationale). file( GLOB_RECURSE _AIO_PACKAGE_SOURCE_FILES LIST_DIRECTORIES FALSE "${CMAKE_SOURCE_DIR}/package/*" ) list(FILTER _AIO_PACKAGE_SOURCE_FILES EXCLUDE REGEX "/Tests/") + list(FILTER _AIO_PACKAGE_SOURCE_FILES EXCLUDE REGEX "/Shaders/") append_copy_if_different(_prepare_aio_cmds _AIO_PACKAGE_SOURCE_FILES "${CMAKE_SOURCE_DIR}/package" "${AIO_DIR}") - # Copy feature folders (only files, preserve existing files in AIO) + # Copy feature folders (only files, preserve existing files in AIO). + # Shader files are excluded - copy_shaders.stamp owns the Shaders/ subdir + # so the two custom-build rules don't race on the same destinations. foreach(_fpath IN LISTS FEATURE_PATHS) if(EXISTS "${_fpath}") file( @@ -474,6 +491,7 @@ if(AUTO_PLUGIN_DEPLOYMENT OR AIO_ZIP_TO_DIST) LIST_DIRECTORIES FALSE "${_fpath}/*" ) + list(FILTER _feature_files EXCLUDE REGEX "/Shaders/") append_copy_if_different(_prepare_aio_cmds _feature_files "${_fpath}" "${AIO_DIR}") endif() endforeach() @@ -906,7 +924,7 @@ if(AIO_ZIP_TO_DIST) COMMAND ${CMAKE_COMMAND} -E tar cf ${AIO_ARCHIVE} --format=7zip -- . COMMAND ${CMAKE_COMMAND} -E touch ${AIO_ZIP_STAMP} WORKING_DIRECTORY ${AIO_DIR} - DEPENDS PREPARE_AIO + DEPENDS PREPARE_AIO ${CMAKE_CURRENT_BINARY_DIR}/copy_shaders.stamp COMMENT "Creating AIO archive ${AIO_ARCHIVE}" )