wolfBoot/cmake/load_dot_config.cmake

153 lines
6.2 KiB
CMake

# wolfboot/cmake/load_dot_config.cmake
#
# Copyright (C) 2026 wolfSSL Inc.
#
# This file is part of wolfBoot.
#
# wolfBoot is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# wolfBoot is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA
#
# Usage:
# include(cmake/load_dot_config.cmake)
# load_dot_config("${CMAKE_SOURCE_DIR}/.config") # set normal CMake vars
# # or cache them so GUIs (e.g. Visual Studio) can see/edit them:
# load_dot_config("${CMAKE_SOURCE_DIR}/.config" CACHE_VARS)
# Ensure this file is only included and initialized once
if(CMAKE_VERSION VERSION_LESS 3.10)
# Fallback path for older CMake, and anything else that wants to detect is loaded
if(DEFINED LOAD_DOT_CONFIG_CMAKE_INCLUDED)
return()
endif()
else()
include_guard(GLOBAL)
endif()
function(load_dot_config CONFIG_PATH)
set(_USE_CACHE OFF)
foreach(_arg IN LISTS ARGN)
if(_arg STREQUAL "CACHE_VARS")
set(_USE_CACHE ON)
endif()
endforeach()
message(STATUS "Reading config file: ${CONFIG_PATH}")
if(NOT EXISTS "${CONFIG_PATH}")
message(FATAL_ERROR "load_dot_config: File not found: ${CONFIG_PATH}")
endif()
# Read the entire file, normalize newlines to \n, then split into a CMake list.
file(READ "${CONFIG_PATH}" _cfg_raw)
# Normalize CRLF and CR to LF
string(REPLACE "\r\n" "\n" _cfg_raw "${_cfg_raw}")
string(REPLACE "\r" "\n" _cfg_raw "${_cfg_raw}")
# Split into a list where each element is one line
string(REPLACE "\n" ";" _cfg_lines "${_cfg_raw}")
message(STATUS "-- Parsing lines from config file...")
foreach(_line IN LISTS _cfg_lines)
# Strip comments and whitespace
string(REGEX REPLACE "\\s*#.*$" "" _line "${_line}")
string(STRIP "${_line}" _line)
if(_line STREQUAL "")
message(STATUS "-- Skipping blank line")
continue()
endif()
message(STATUS "-- Found line: ${_line}")
# KEY[?]=VALUE
# CMAKE_MATCH_1 = KEY, CMAKE_MATCH_2 = "?" or "", CMAKE_MATCH_3 = VALUE
# Visual guide (ASCII only):
# Group 1: Key name (...........1..........)
# Optional Space [ \t]*
# Group 2: Operand ( 2 )
# Literal equals =
# Optional Space [ \t]
# Group 3: Value ( 3)
if(NOT _line MATCHES "^([A-Za-z_][A-Za-z0-9_]*)[ \t]*([?]?)=[ \t]*(.*)$")
message(WARNING "load_dot_config: Skipping unrecognized line: ${_line}")
continue()
endif()
set(_key "${CMAKE_MATCH_1}") # Setting name
set(_op "${CMAKE_MATCH_2}") # operand "?" or ""
set(_val "${CMAKE_MATCH_3}") # the value to
message(STATUS "-- Parsed key: ${_key}")
message(STATUS "-- Parsed op: ${_op}")
message(STATUS "-- Parsed val: ${_val}")
# Trim value spaces
string(STRIP "${_val}" _val)
# Remove value surrounding double quotes if present
if(_val MATCHES "^\"(.*)\"$")
set(_val "${CMAKE_MATCH_1}")
endif()
# Expand Make-style $(VAR) to CMake env form $ENV{VAR}
# We keep $ENV{VAR} literal in the set() call so it expands now.
# Do multiple replacements if many occurrences exist.
while(_val MATCHES "\\$\\(([A-Za-z_][A-Za-z0-9_]*)\\)")
string(REGEX REPLACE "\\$\\(([A-Za-z_][A-Za-z0-9_]*)\\)" "\$ENV{\\1}" _val "${_val}")
endwhile()
# After replacing with $ENV{...}, expand it to its actual value now.
# The "configure" trick expands env refs without touching other text.
set(_expanded "${_val}")
string(CONFIGURE "${_expanded}" _expanded @ONLY)
# Detect prior definition
set(_already_defined FALSE)
if(DEFINED ${_key})
set(_already_defined TRUE)
message(STATUS "-- Already defined: ${_key}=${_val}")
else()
# Check cache
get_property(_cache_type CACHE "${_key}" PROPERTY TYPE SET)
if(_cache_type)
set(_already_defined TRUE)
message(STATUS "-- Already defined (cache) ${_key}=${_val}")
endif()
endif()
# Respect ?= (only set if not already defined)
set(_should_set TRUE)
if(_op STREQUAL "?" AND _already_defined)
set(_should_set FALSE)
endif()
if(_should_set)
if(_USE_CACHE)
# Use STRING so values like "0x1000" stay as text; FORCE to mirror Make's "="
# For "?=", do not FORCE to preserve user edits from cache/GUI.
if(_op STREQUAL "?")
message(STATUS "-- Cache Conditional Assignment: ${_key}=${_expanded} from ${CONFIG_PATH}")
set(${_key} "${_expanded}" CACHE STRING "Imported from ${CONFIG_PATH}")
else()
message(STATUS "-- Cache Assignment: ${_key}=${_expanded} from ${CONFIG_PATH}")
set(${_key} "${_expanded}" CACHE STRING "Imported from ${CONFIG_PATH}" FORCE)
endif()
else()
# Set variable in parent scope so caller can see value.
message(STATUS "-- Assignment: ${_key}=${_val}")
set(${_key} "${_expanded}" PARENT_SCOPE)
endif()
else()
message(STATUS "-- Skipping assignment: ${_key}=${_val}")
endif()
endforeach()
message(STATUS "-- Done processing ${CONFIG_PATH}")
endfunction()
set(LOAD_DOT_CONFIG_CMAKE_INCLUDED TRUE)