feat(build): support LTO in the build system v2 (cmakev2)

Mirror the link-time optimization support into the cmakev2 build system so
that both build systems behave identically.

- project.cmake (__init_project_configuration): emit -flto=auto as a link
  option when CONFIG_COMPILER_LTO_LINKTIME is set, except for bootloader and
  ESP-TEE builds, otherwise keep -fno-lto.

- build.cmake (idf_build_library): when CONFIG_COMPILER_LTO_COMPILETIME is set,
  compile each linked component with -flto=auto unless it has linker fragments,
  is placed by another component's fragment (see tools/cmake/lto.cmake), has
  opted out via NO_LTO, or is not a static library.

- project.cmake: when CONFIG_APP_REPRODUCIBLE_BUILD is also enabled, apply
  the same three flags as the legacy build system to keep LTO output
  reproducible: pass the prefix-map options to the linker (so link-time code
  generation remaps DW_AT_comp_dir), add -save-temps (stable LTRANS object
  names instead of random $TMPDIR paths in the .map), and pin -frandom-seed
  (byte-identical LTO GIMPLE bytecode). See the commit message of
  "feat(build): add options to enable link-time optimization (LTO)" for the
  full analysis.

The gcc-ar / gcc-ranlib selection and the NO_LTO component property are shared
with the legacy build system through tools/cmake/toolchain.cmake and the common
component registration code, so no cmakev2-specific changes are needed there.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Ivan Grokhotkov (bot)
2026-07-14 19:32:19 +02:00
co-authored by Claude Opus 4.8
parent d20a986999
commit 493a770d40
4 changed files with 91 additions and 8 deletions
+33 -8
View File
@@ -385,7 +385,18 @@ function(__init_project_configuration)
endif()
endif()
list(APPEND link_options "-fno-lto")
# A subproject that must not use LTO (for example the bootloader) opts out by
# setting the SET_COMPILER_LTO build property to NO, the same way it uses
# SET_COMPILER_OPTIMIZATION; an unset property means LTO is allowed.
idf_build_get_property(set_compiler_lto SET_COMPILER_LTO)
if(NOT DEFINED set_compiler_lto OR set_compiler_lto STREQUAL "")
set(set_compiler_lto YES)
endif()
if(CONFIG_COMPILER_LTO_LINKTIME AND set_compiler_lto)
list(APPEND link_options "-flto=auto")
else()
list(APPEND compile_options "-fno-lto")
endif()
if(CONFIG_IDF_TARGET_LINUX AND CMAKE_HOST_SYSTEM_NAME STREQUAL "Darwin")
# Not all versions of the MacOS linker support the -warn_commons flag.
@@ -473,16 +484,17 @@ function(__init_project_configuration)
# paths back to the real paths in the filesystem.
set(gdbinit_dir ${build_dir}/gdbinit)
set(gdbinit_path "${gdbinit_dir}/prefix_map")
set(prefix_map_compile_options)
if(CONFIG_COMPILER_HIDE_PATHS_MACROS)
list(APPEND compile_options "-fmacro-prefix-map=${CMAKE_SOURCE_DIR}=.")
list(APPEND compile_options "-fmacro-prefix-map=${idf_path}=/IDF")
list(APPEND prefix_map_compile_options "-fmacro-prefix-map=${CMAKE_SOURCE_DIR}=.")
list(APPEND prefix_map_compile_options "-fmacro-prefix-map=${idf_path}=/IDF")
endif()
if(CONFIG_APP_REPRODUCIBLE_BUILD)
list(APPEND compile_options "-fdebug-prefix-map=${idf_path}=/IDF")
list(APPEND compile_options "-fdebug-prefix-map=${project_dir}=/IDF_PROJECT")
list(APPEND compile_options "-fdebug-prefix-map=${build_dir}=/IDF_BUILD")
list(APPEND prefix_map_compile_options "-fdebug-prefix-map=${idf_path}=/IDF")
list(APPEND prefix_map_compile_options "-fdebug-prefix-map=${project_dir}=/IDF_PROJECT")
list(APPEND prefix_map_compile_options "-fdebug-prefix-map=${build_dir}=/IDF_BUILD")
# Generate mapping for component paths
set(gdbinit_file_lines)
@@ -492,7 +504,7 @@ function(__init_project_configuration)
string(TOUPPER ${component_name} component_name_uppercase)
set(substituted_path "/COMPONENT_${component_name_uppercase}_DIR")
list(APPEND compile_options "-fdebug-prefix-map=${component_dir}=${substituted_path}")
list(APPEND prefix_map_compile_options "-fdebug-prefix-map=${component_dir}=${substituted_path}")
string(APPEND gdbinit_file_lines "set substitute-path ${substituted_path} ${component_dir}\n")
endforeach()
@@ -506,15 +518,28 @@ function(__init_project_configuration)
endif()
string(STRIP "${compiler_sysroot}" compiler_sysroot)
get_filename_component(compiler_sysroot "${compiler_sysroot}/.." REALPATH)
list(APPEND compile_options "-fdebug-prefix-map=${compiler_sysroot}=/TOOLCHAIN")
list(APPEND prefix_map_compile_options "-fdebug-prefix-map=${compiler_sysroot}=/TOOLCHAIN")
string(APPEND gdbinit_file_lines "set substitute-path /TOOLCHAIN ${compiler_sysroot}\n")
else()
set(gdbinit_file_lines "# There is no prefix map defined for the project.\n")
endif()
list(APPEND compile_options ${prefix_map_compile_options})
# Write the prefix_map file even if it is empty.
file(MAKE_DIRECTORY ${gdbinit_dir})
file(WRITE "${gdbinit_path}" "${gdbinit_file_lines}")
if(CONFIG_APP_REPRODUCIBLE_BUILD AND CONFIG_COMPILER_LTO_LINKTIME AND set_compiler_lto)
# LTO generates code at link time, where the path remapping applied to
# compile_options doesn't take effect, so pass it to the linker as well.
# -save-temps keeps LTRANS objects out of $TMPDIR, and a pinned random
# seed makes LTO bytecode byte-identical. See the commit message of
# "feat(build): add options to enable link-time optimization (LTO)"
# for details.
list(APPEND link_options ${prefix_map_compile_options})
list(APPEND link_options "-save-temps")
list(APPEND compile_options "-frandom-seed=1")
endif()
idf_build_set_property(GDBINIT_FILES_PREFIX_MAP "${gdbinit_path}")
idf_build_set_property(COMPILE_OPTIONS "${compile_options}" APPEND)
idf_build_set_property(C_COMPILE_OPTIONS "${c_compile_options}" APPEND)