The Real Problem:
I want to recompile a DLL currently in use by Visual Studio. The DLL can be used after copy/rename, but moving the PDB breaks debugging capabilities. Visual Studio locks the debugging program database (.pdb) file when LoadLibrary(lib.dll)
is called, which requires stopping the debugger to unlock.
I would like to generate a .pdb with a unique name each time CMake builds a specific target. To do this, I've tried to "inject" a timestamp at different points in CMake's build process.
Is this possible? Are there alternative solutions? What is the best way to accomplish this objective?
Janky Solution:
CMakeLists.txt:
if (MSVC)
add_custom_command(
TARGET ${target} PRE_BUILD
COMMAND ${CMAKE_COMMAND}
ARGS -P ${CMAKE_SOURCE_DIR}/scripts/PdbClean.cmake ${target}
WORKING_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/$<CONFIG>
)
add_custom_command(
TARGET ${target} PRE_BUILD
COMMAND ${CMAKE_COMMAND}
ARGS -P ${CMAKE_SOURCE_DIR}/scripts/PdbRename.cmake ${target}
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
)
endif()
PdbClean.cmake:
file(GLOB fileList ${CMAKE_ARGV3}*.pdb)
if (fileList)
file(REMOVE ${fileList})
endif()
PdbRename.cmake:
#Read in the Visual Studio project file.
set(filePath ${CMAKE_ARGV3}.vcxproj)
file(READ ${filePath} readData)
#Update the program database file name with a current timestamp.
string(TIMESTAMP timestamp "%Y%m%d_%H%M%S")
set(regularEx "${CMAKE_ARGV3}(([0-9]+)_([0-9]+))?\\.pdb")
set(replaceEx ${CMAKE_ARGV3}${timestamp}.pdb)
string(REGEX REPLACE ${regularEx} ${replaceEx} readData "${readData}")
#Overwrite the Visual Studio project file.
file(WRITE ${filePath} "${readData}")
The problem with this solution is that the custom commands are written into the VCXPROJ I want to modify. The PRE_BUILD changes made to VCXPROJ are not detected until the next build command, causing the previous build's time to get stamped. This also causes the DLL to get recompiled every build. But I get the unique name.
Other Ideas:
CMakeLists.txt Commands: Captures CMake's generation time, but I want build time:
string(TIMESTAMP timestamp "%Y%m%d_%H%M%S")
set_target_properties(${target} PROPERTIES PDB_NAME "${target}${timestamp}")
Command Line Args: The ts variable contains the correct datetime string, but I haven't found a way to pass to CMake in an impactful manner:
build.bat:
set ts=%date:~10,4%%date:~4,2%%date:~7,2%_%time:~0,2%%time:~3,2%%time:~6,2%
call cmake -E env timestamp=%ts% cmake --build .\_build --config Debug
CMakeLists.txt:
message(STATUS "${timestamp}") # prints blank
Post Build Python Script: Can we fix DLL/PDB references after renaming? A simple name replacement appears to break the DLL:
CMakeLists.txt:
add_custom_command(
TARGET ${target} POST_BUILD
COMMAND python
ARGS ${CMAKE_SOURCE_DIR}/scripts/Rename.py ${target}
WORKING_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/$<CONFIG>
)
Rename.py:
...
timestamp = '{:%Y%m%d_%H%M%S}'.format(datetime.datetime.now())
msvcName = args.moduleName + '.pdb'
timeName = args.moduleName + timestamp + '.pdb'
...
if libData is not None:
libData = libData.replace(bytearray(msvcName, 'utf-8'), bytearray(timeName, 'utf-8'))
with open(libraryName, 'wb') as file:
print('Writing ' + libraryName)
file.write(libData)
...
I have discovered that -E env
sets environment variables that can be interpreted by Visual Studio as project macros!
Give the VS project a macro string when generated:
set_target_properties(${target} PROPERTIES PDB_NAME "${target}$(BuildTime)")
Then populate the macro variable when we build:
set ts=%date:~10,4%%date:~4,2%%date:~7,2%_%time:~0,2%%time:~3,2%%time:~6,2%
call cmake -E env BuildTime=%ts: =0% cmake --build .\_build --config Debug
And use add_custom_command(PRE_BUILD)
to cleanup old files with a cmake script:
file(GLOB fileList ${CMAKE_ARGV3}*.pdb)
if (fileList)
file(REMOVE ${fileList})
endif()