Files
esp-idf/tools/cmake/toolchain_flags.cmake
2025-12-03 13:31:42 +07:00

183 lines
7.3 KiB
CMake

# Map option variable names to their corresponding file lists
set(_IDF_TOOLCHAIN_OPTION_MAPPINGS
"COMPILE_OPTIONS|cflags:cxxflags:asmflags"
"C_COMPILE_OPTIONS|cflags"
"CXX_COMPILE_OPTIONS|cxxflags"
"ASM_COMPILE_OPTIONS|asmflags"
"LINK_OPTIONS|ldflags"
)
# Extract keywords from mappings (drop substring after "|")
set(_IDF_TOOLCHAIN_MULTI_VALUE_KEYWORDS "")
foreach(mapping ${_IDF_TOOLCHAIN_OPTION_MAPPINGS})
string(REGEX REPLACE "\\|.*" "" keyword "${mapping}")
list(APPEND _IDF_TOOLCHAIN_MULTI_VALUE_KEYWORDS "${keyword}")
endforeach()
# Helper macro to process toolchain flag options based on mappings
# Uses macro instead of function to access parent scope variables directly
# operation: "add" or "remove"
macro(_process_toolchain_flag_options)
cmake_parse_arguments(PARSE_ARGV 0 "" "" "" "${_IDF_TOOLCHAIN_MULTI_VALUE_KEYWORDS}")
if(NOT EXISTS "${IDF_TOOLCHAIN_BUILD_DIR}")
message(FATAL_ERROR "Toolchain directory does not exist: ${IDF_TOOLCHAIN_BUILD_DIR}")
endif()
foreach(mapping ${_IDF_TOOLCHAIN_OPTION_MAPPINGS})
string(REPLACE "|" ";" mapping_list ${mapping})
# Extract option variable name and add underscore prefix
list(GET mapping_list 0 option_var)
set(option_var "_${option_var}")
# Extract file list for this option variable
list(GET mapping_list 1 files)
string(REPLACE ":" ";" files ${files})
# Process flags based on calling function: add or remove
if("${CMAKE_CURRENT_FUNCTION}" MATCHES "add_flags" AND DEFINED ${option_var})
_add_flags_to_files("${files}" "${${option_var}}")
endif()
if("${CMAKE_CURRENT_FUNCTION}" MATCHES "remove_flags" AND ${option_var})
_remove_flags_from_files("${files}" "${${option_var}}")
endif()
endforeach()
endmacro()
# Helper function to remove flags matching patterns from specific files
function(_remove_flags_from_files files patterns)
foreach(file ${files})
set(file_path "${IDF_TOOLCHAIN_BUILD_DIR}/${file}")
if(EXISTS "${file_path}")
file(STRINGS "${file_path}" existing_lines)
else()
set(existing_lines "")
endif()
file(WRITE "${file_path}" "")
foreach(line ${existing_lines})
set(should_keep TRUE)
foreach(pattern ${patterns})
if("${line}" MATCHES "${pattern}")
set(should_keep FALSE)
break()
endif()
endforeach()
if(should_keep)
file(APPEND "${file_path}" "${line}\n")
endif()
endforeach()
endforeach()
endfunction()
# Helper function to add flags to specific files
function(_add_flags_to_files files flags)
foreach(file ${files})
set(file_path "${IDF_TOOLCHAIN_BUILD_DIR}/${file}")
if(EXISTS "${file_path}")
file(STRINGS "${file_path}" existing_lines)
else()
set(existing_lines "")
endif()
# Split flags string using regex to respect quotes.
# Matches: double-quoted strings, single-quoted strings, or unquoted space-delimited tokens.
#
# TODO IDF-14467:
# Currently, paired linker flags are not properly handled when filtering.
# The regex-based splitting treats each flag independently, which breaks
# flag pairs that must be kept together. Future enhancement should support
# the following paired flag constructs:
# - --start-group ... --end-group
# - --push-state ... --pop-state
# - --whole-archive ... --no-whole-archive
# - --as-needed ... --no-as-needed
# - --push-section ... --pop-section
string(REGEX MATCHALL "([^\\s\"']*(\"[^\"]*\"|'[^']*')|\"[^\"]*\"|'[^']*'|[^ \"']+)" flags "${flags}")
file(WRITE "${file_path}" "")
foreach(line ${existing_lines})
set(should_keep TRUE)
foreach(flag ${flags})
if("${line}" STREQUAL "${flag}")
set(should_keep FALSE)
break()
endif()
endforeach()
if(should_keep)
file(APPEND "${file_path}" "${line}\n")
endif()
endforeach()
foreach(flag ${flags})
# Skip flags that contain IDF_TOOLCHAIN_BUILD_DIR substring
# to avoid recursion
string(FIND "${flag}" "${IDF_TOOLCHAIN_BUILD_DIR}" found_pos)
if(found_pos EQUAL -1)
file(APPEND "${file_path}" "${flag}\n")
endif()
endforeach()
endforeach()
endfunction()
# idf_toolchain_add_flags
#
# @brief Add compiler or linker flags to the toolchain configuration files.
#
# This function adds flags to the appropriate toolchain response files (cflags, cxxflags,
# asmflags, or ldflags) in the IDF_TOOLCHAIN_BUILD_DIR directory. Duplicate flags
# are automatically removed before adding new ones.
#
# @param[in, optional] COMPILE_OPTIONS (multivalue) flags to add to C, C++, and ASM
# compilation. Applied to cflags, cxxflags, and asmflags files.
# @param[in, optional] C_COMPILE_OPTIONS (multivalue) flags to add to C compilation only.
# Applied to cflags file.
# @param[in, optional] CXX_COMPILE_OPTIONS (multivalue) flags to add to C++ compilation only.
# Applied to cxxflags file.
# @param[in, optional] ASM_COMPILE_OPTIONS (multivalue) flags to add to ASM compilation only.
# Applied to asmflags file.
# @param[in, optional] LINK_OPTIONS (multivalue) flags to add to linking.
# Applied to ldflags file.
#
# @note Multiple keyword arguments can be specified in a single call.
# @note Flags are deduplicated - if a flag already exists in the file, it will be
# removed before adding the new one to ensure no duplicates.
#
# Example:
# idf_toolchain_add_flags(COMPILE_OPTIONS "-Wall" "-Wextra")
# idf_toolchain_add_flags(C_COMPILE_OPTIONS "-std=c99" LINK_OPTIONS "-Wl,--gc-sections")
function(idf_toolchain_add_flags)
_process_toolchain_flag_options()
endfunction()
function(idf_toolchain_remove_flags)
_process_toolchain_flag_options()
endfunction()
# Workaround: Re-run CMake compiler ABI detection after ABI flags are set.
#
# Problem: CMake performs compiler checks at an early stage during
# toolchain.cmake processing. At this early stage, response files are not yet
# ready, which causes CMake paths (e.g., CMAKE_<lang>_IMPLICIT_LINK_DIRECTORIES)
# to be incorrectly determined.
#
# Solution: Re-run the ABI detection after ABI flags are present to correctly
# determine these paths.
#
# Note: If the CMake API changes, this solution may need to be revised.
macro(idf_toolchain_rerun_abi_detection)
set(lang_ext_pairs "C|c" "CXX|cpp")
include(${CMAKE_ROOT}/Modules/CMakeDetermineCompilerABI.cmake)
foreach(lang_ext ${lang_ext_pairs})
string(REPLACE "|" ";" lang_ext_parts ${lang_ext})
list(GET lang_ext_parts 0 lang)
list(GET lang_ext_parts 1 ext)
if(DEFINED CMAKE_${lang}_ABI_COMPILED)
unset(CMAKE_${lang}_ABI_COMPILED)
cmake_determine_compiler_abi(${lang} ${CMAKE_ROOT}/Modules/CMake${lang}CompilerABI.${ext})
endif()
endforeach()
endmacro()