CMake: Improve reproducibility

Relative build RPATH, rcc mtime pinning.
This commit is contained in:
Stenzek
2026-10-10 21:47:44 +10:00
parent 29fe750fc9
commit 44bb56d651
3 changed files with 78 additions and 11 deletions
+14 -7
View File
@@ -65,20 +65,27 @@ endif()
# We don't need exceptions, disable them to save a bit of code size.
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-exceptions -fno-rtti")
# Rewrite paths in macros to be relative to the source directory.
# Helpful for reproducible builds.
# Rewrite paths in macros and debug info to fixed names, so that the output does not depend
# on where the source and build directories are located. Helpful for reproducible builds.
# The build directory is mapped last, so it takes precedence when it is inside the source directory.
if("${CMAKE_BUILD_TYPE}" STREQUAL "Release" AND NOT CMAKE_GENERATOR MATCHES "Xcode" AND
(COMPILER_CLANG OR COMPILER_CLANG_CL OR COMPILER_GCC))
file(RELATIVE_PATH source_dir_remap "${CMAKE_BINARY_DIR}" "${CMAKE_SOURCE_DIR}")
string(REGEX REPLACE "\/+$" "" source_dir_remap "${source_dir_remap}")
set(source_dir_remap_str "\"${CMAKE_SOURCE_DIR}\"=\"${source_dir_remap}\"")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -ffile-prefix-map=${source_dir_remap_str}")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -ffile-prefix-map=${source_dir_remap_str}")
set(dir_remap_str "-ffile-prefix-map=\"${CMAKE_SOURCE_DIR}\"=. -ffile-prefix-map=\"${CMAKE_BINARY_DIR}\"=build")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${dir_remap_str}")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${dir_remap_str}")
endif()
# Write binaries to a seperate directory.
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/bin")
# Locate the prebuilt dependencies relative to the binary, instead of using the absolute
# paths that CMake adds to the RUNPATH by default, which leak the source directory.
if(LINUX)
get_origin_relative_path(deps_rpath "${DEPS_PATH}/lib")
set(CMAKE_BUILD_WITH_INSTALL_RPATH TRUE)
set(CMAKE_INSTALL_RPATH "${deps_rpath}")
endif()
# Enable large file support on Linux 32-bit platforms.
if(CMAKE_SIZEOF_VOID_P EQUAL 4)
add_definitions("-D_FILE_OFFSET_BITS=64")
+62 -2
View File
@@ -412,14 +412,23 @@ function(add_runtime_libraries TARGET)
)
else()
# Linux, and macOS when using the Xcode generator/not using bundles.
if(LINUX)
get_origin_relative_path(dyn_lib_dir "${dyn_lib_dir}")
endif()
list(APPEND LIBRARY_RPATHS "${dyn_lib_dir}")
endif()
endforeach()
# Add the required library directories to the target's existing
# build RPATH, without adding duplicate entries.
# Linux is built with the install RPATH, see the top-level CMakeLists.txt.
if(LINUX)
set(rpath_property INSTALL_RPATH)
else()
set(rpath_property BUILD_RPATH)
endif()
if(LIBRARY_RPATHS)
get_target_property(existing_rpath ${TARGET} BUILD_RPATH)
get_target_property(existing_rpath ${TARGET} ${rpath_property})
if(NOT existing_rpath)
set(existing_rpath "")
endif()
@@ -428,7 +437,58 @@ function(add_runtime_libraries TARGET)
list(REMOVE_DUPLICATES existing_rpath)
set_target_properties(
${TARGET}
PROPERTIES BUILD_RPATH "${existing_rpath}"
PROPERTIES ${rpath_property} "${existing_rpath}"
)
endif()
endfunction()
# Returns an RPATH entry for DIR that is relative to the directory binaries are written to.
function(get_origin_relative_path OUTVAR DIR)
# $ORIGIN is the real location of the binary, so symlinks have to be resolved first.
file(MAKE_DIRECTORY "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}")
file(REAL_PATH "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}" binary_dir)
file(REAL_PATH "${DIR}" real_dir)
file(RELATIVE_PATH relative_dir "${binary_dir}" "${real_dir}")
set(${OUTVAR} "$ORIGIN/${relative_dir}" PARENT_SCOPE)
endfunction()
# Compiles a .qrc file to a binary .rcc file, without embedding file modification times.
function(add_binary_resources TARGET QRC_FILE OUTPUT_FILE)
get_filename_component(qrc_path "${QRC_FILE}" ABSOLUTE)
get_filename_component(qrc_dir "${qrc_path}" DIRECTORY)
# Pick up the files listed in the .qrc as dependencies, re-running CMake if the list changes.
set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "${qrc_path}")
file(READ "${qrc_path}" qrc_contents)
string(REGEX MATCHALL "<file[^>]*>[^<]+</file>" qrc_entries "${qrc_contents}")
set(qrc_depends)
foreach(entry IN LISTS qrc_entries)
string(REGEX REPLACE "<file[^>]*>([^<]+)</file>" "\\1" entry "${entry}")
list(APPEND qrc_depends "${qrc_dir}/${entry}")
endforeach()
# rcc stamps every entry with the mtime of its source file, i.e. the checkout time.
# Pin it to the commit date instead, unless the caller has already provided a date.
set(source_date "$ENV{SOURCE_DATE_EPOCH}")
if(NOT source_date)
find_package(Git)
if(EXISTS "${PROJECT_SOURCE_DIR}/.git" AND GIT_FOUND)
execute_process(
COMMAND ${GIT_EXECUTABLE} log -1 --format=%ct
WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}"
OUTPUT_VARIABLE source_date
OUTPUT_STRIP_TRAILING_WHITESPACE
)
endif()
endif()
set(rcc_env)
if(source_date)
set(rcc_env "${CMAKE_COMMAND}" -E env "SOURCE_DATE_EPOCH=${source_date}")
endif()
add_custom_command(OUTPUT "${OUTPUT_FILE}"
COMMAND ${rcc_env} "$<TARGET_FILE:Qt6::rcc>" -no-compress --binary --name ${TARGET} --output "${OUTPUT_FILE}" "${qrc_path}"
DEPENDS Qt6::rcc "${qrc_path}" ${qrc_depends}
VERBATIM)
add_custom_target(${TARGET} ALL DEPENDS "${OUTPUT_FILE}")
endfunction()
+2 -2
View File
@@ -283,14 +283,14 @@ if(NOT BUILD_MACOS_BUNDLE)
set(RCC_FILENAME "duckstation-qt.rcc")
set(RCC_BUILD_PATH "${CMAKE_CURRENT_BINARY_DIR}/${RCC_FILENAME}")
set(RCC_OUTPUT_DIR "$<TARGET_FILE_DIR:duckstation-qt>/resources")
qt_add_binary_resources(duckstation-qt-rcc resources/duckstation-qt.qrc DESTINATION ${RCC_BUILD_PATH} OPTIONS -no-compress)
add_binary_resources(duckstation-qt-rcc resources/duckstation-qt.qrc "${RCC_BUILD_PATH}")
add_dependencies(duckstation-qt duckstation-qt-rcc)
add_custom_command(TARGET duckstation-qt POST_BUILD COMMAND "${CMAKE_COMMAND}" -E make_directory "${RCC_OUTPUT_DIR}")
add_custom_command(TARGET duckstation-qt POST_BUILD COMMAND "${CMAKE_COMMAND}" -E copy_if_different "${RCC_BUILD_PATH}" "${RCC_OUTPUT_DIR}/${RCC_FILENAME}")
else()
set(RCC_FILE "${CMAKE_CURRENT_BINARY_DIR}/duckstation-qt.rcc")
qt_add_binary_resources(duckstation-qt-rcc resources/duckstation-qt.qrc DESTINATION ${RCC_FILE} OPTIONS -no-compress)
add_binary_resources(duckstation-qt-rcc resources/duckstation-qt.qrc "${RCC_FILE}")
add_dependencies(duckstation-qt duckstation-qt-rcc)
target_sources(duckstation-qt PRIVATE ${RCC_FILE})
set_source_files_properties(${RCC_FILE} PROPERTIES MACOSX_PACKAGE_LOCATION Resources)