Provide a nice way to generate Windows RC files
With the current state, developers have to manually specify manually maintained RC
files via target_sources
.
They either generate the file themselves using file(GENERATE)
/ configure_file()
or have it checked into their VCS project.
It would be nice if CMake provided a default .rc
file with sensible defaults that is embedded into built executables / libraries.
CMake already does that for macOS application bundles and framework bundles by providing a default Info.plist file, which can further be tweaked by setting various target properties.
From the top of my head I can see a couple of ways of doing it:
-
Follow the Info.plist approach, and have a default
.rc
file template that can be tweaked by property targets, and replaced entirely via some other property. -
Provide a CMake module that exposes a function that generates an
.rc
file. It would take various parameters to control the.rc
file content, and provide sensible defaults when not set.
Developers can then manually target_sources()
the file.
Here's an existing .rc
file creating function we currently use in Qt, but it would be nice if something like it could be upstreamed.
.rc generation function
# Generate Win32 RC files for a target. All entries in the RC file are generated
# from target properties:
#
# QT_TARGET_COMPANY_NAME: RC Company name
# QT_TARGET_DESCRIPTION: RC File Description
# QT_TARGET_VERSION: RC File and Product Version
# QT_TARGET_COPYRIGHT: RC LegalCopyright
# QT_TARGET_PRODUCT_NAME: RC ProductName
# QT_TARGET_COMMENTS: RC Comments
# QT_TARGET_ORIGINAL_FILENAME: RC Original FileName
# QT_TARGET_TRADEMARKS: RC LegalTrademarks
# QT_TARGET_INTERNALNAME: RC InternalName
# QT_TARGET_RC_ICONS: List of paths to icon files
#
# If you do not wish to auto-generate rc files, it's possible to provide your
# own RC file by setting the property QT_TARGET_WINDOWS_RC_FILE with a path to
# an existing rc file.
function(_qt_internal_generate_win32_rc_file target)
set(prohibited_target_types INTERFACE_LIBRARY STATIC_LIBRARY OBJECT_LIBRARY)
get_target_property(target_type ${target} TYPE)
if(target_type IN_LIST prohibited_target_types)
return()
endif()
get_target_property(target_binary_dir ${target} BINARY_DIR)
get_target_property(target_rc_file ${target} QT_TARGET_WINDOWS_RC_FILE)
get_target_property(target_version ${target} QT_TARGET_VERSION)
if (NOT target_rc_file AND NOT target_version)
return()
endif()
if (target_rc_file)
# Use the provided RC file
target_sources(${target} PRIVATE "${target_rc_file}")
else()
# Generate RC File
set(rc_file_output "${target_binary_dir}/")
if(QT_GENERATOR_IS_MULTI_CONFIG)
string(APPEND rc_file_output "$<CONFIG>/")
endif()
string(APPEND rc_file_output "${target}_resource.rc")
set(target_rc_file "${rc_file_output}")
set(company_name "")
get_target_property(target_company_name ${target} QT_TARGET_COMPANY_NAME)
if (target_company_name)
set(company_name "${target_company_name}")
endif()
set(file_description "")
get_target_property(target_description ${target} QT_TARGET_DESCRIPTION)
if (target_description)
set(file_description "${target_description}")
endif()
set(legal_copyright "")
get_target_property(target_copyright ${target} QT_TARGET_COPYRIGHT)
if (target_copyright)
set(legal_copyright "${target_copyright}")
endif()
set(product_name "")
get_target_property(target_product_name ${target} QT_TARGET_PRODUCT_NAME)
if (target_product_name)
set(product_name "${target_product_name}")
else()
set(product_name "${target}")
endif()
set(comments "")
get_target_property(target_comments ${target} QT_TARGET_COMMENTS)
if (target_comments)
set(comments "${target_comments}")
endif()
set(legal_trademarks "")
get_target_property(target_trademarks ${target} QT_TARGET_TRADEMARKS)
if (target_trademarks)
set(legal_trademarks "${target_trademarks}")
endif()
set(product_version "")
if (target_version)
if(target_version MATCHES "[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+")
# nothing to do
elseif(target_version MATCHES "[0-9]+\\.[0-9]+\\.[0-9]+")
set(target_version "${target_version}.0")
elseif(target_version MATCHES "[0-9]+\\.[0-9]+")
set(target_version "${target_version}.0.0")
elseif (target_version MATCHES "[0-9]+")
set(target_version "${target_version}.0.0.0")
else()
message(FATAL_ERROR "Invalid version format: '${target_version}'")
endif()
set(product_version "${target_version}")
else()
set(product_version "0.0.0.0")
endif()
set(file_version "${product_version}")
string(REPLACE "." "," version_comma ${product_version})
set(original_file_name "$<TARGET_FILE_NAME:${target}>")
get_target_property(target_original_file_name ${target} QT_TARGET_ORIGINAL_FILENAME)
if (target_original_file_name)
set(original_file_name "${target_original_file_name}")
endif()
set(internal_name "")
get_target_property(target_internal_name ${target} QT_TARGET_INTERNALNAME)
if (target_internal_name)
set(internal_name "${target_internal_name}")
endif()
set(icons "")
get_target_property(target_icons ${target} QT_TARGET_RC_ICONS)
if (target_icons)
set(index 1)
foreach( icon IN LISTS target_icons)
string(APPEND icons "IDI_ICON${index} ICON \"${icon}\"\n")
math(EXPR index "${index} +1")
endforeach()
endif()
set(target_file_type "VFT_DLL")
if(target_type STREQUAL "EXECUTABLE")
set(target_file_type "VFT_APP")
endif()
set(contents "#include <windows.h>
${icons}
VS_VERSION_INFO VERSIONINFO
FILEVERSION ${version_comma}
PRODUCTVERSION ${version_comma}
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS VS_FF_DEBUG
#else
FILEFLAGS 0x0L
#endif
FILEOS VOS_NT_WINDOWS32
FILETYPE ${target_file_type}
FILESUBTYPE VFT2_UNKNOWN
BEGIN
BLOCK \"StringFileInfo\"
BEGIN
BLOCK \"040904b0\"
BEGIN
VALUE \"CompanyName\", \"${company_name}\"
VALUE \"FileDescription\", \"${file_description}\"
VALUE \"FileVersion\", \"${file_version}\"
VALUE \"LegalCopyright\", \"${legal_copyright}\"
VALUE \"OriginalFilename\", \"${original_file_name}\"
VALUE \"ProductName\", \"${product_name}\"
VALUE \"ProductVersion\", \"${product_version}\"
VALUE \"Comments\", \"${comments}\"
VALUE \"LegalTrademarks\", \"${legal_trademarks}\"
VALUE \"InternalName\", \"${internal_name}\"
END
END
BLOCK \"VarFileInfo\"
BEGIN
VALUE \"Translation\", 0x0409, 1200
END
END
/* End of Version info */\n"
)
# We can't use the output of file generate as source so we work around
# this by generating the file under a different name and then copying
# the file in place using add custom command.
file(GENERATE OUTPUT "${rc_file_output}.tmp"
CONTENT "${contents}"
)
if(QT_GENERATOR_IS_MULTI_CONFIG)
set(cfgs ${CMAKE_CONFIGURATION_TYPES})
set(outputs "")
foreach(cfg ${cfgs})
string(REPLACE "$<CONFIG>" "${cfg}" expanded_rc_file_output "${rc_file_output}")
list(APPEND outputs "${expanded_rc_file_output}")
endforeach()
else()
set(cfgs "${CMAKE_BUILD_TYPE}")
set(outputs "${rc_file_output}")
endif()
while(outputs)
list(POP_FRONT cfgs cfg)
list(POP_FRONT outputs output)
set(input "${output}.tmp")
add_custom_command(OUTPUT "${output}"
DEPENDS "${input}"
COMMAND ${CMAKE_COMMAND} -E copy_if_different "${input}" "${output}"
)
# We would like to do the following:
# target_sources(${target} PRIVATE "$<$<CONFIG:${cfg}>:${output}>")
# However, https://gitlab.kitware.com/cmake/cmake/-/issues/20682 doesn't let us.
add_library(${target}_${cfg}_rc OBJECT "${output}")
target_link_libraries(${target} PRIVATE "$<$<CONFIG:${cfg}>:${target}_${cfg}_rc>")
endwhile()
endif()
endfunction()