mirror of
https://github.com/esphome/esphome.git
synced 2025-10-23 12:13:49 +01:00
[core] Add support for extern "C" includes (#11422)
This commit is contained in:
@@ -471,6 +471,7 @@ CONF_IMPORT_REACTIVE_ENERGY = "import_reactive_energy"
|
||||
CONF_INC_PIN = "inc_pin"
|
||||
CONF_INCLUDE_INTERNAL = "include_internal"
|
||||
CONF_INCLUDES = "includes"
|
||||
CONF_INCLUDES_C = "includes_c"
|
||||
CONF_INDEX = "index"
|
||||
CONF_INDOOR = "indoor"
|
||||
CONF_INFRARED = "infrared"
|
||||
|
@@ -21,6 +21,7 @@ from esphome.const import (
|
||||
CONF_FRIENDLY_NAME,
|
||||
CONF_ID,
|
||||
CONF_INCLUDES,
|
||||
CONF_INCLUDES_C,
|
||||
CONF_LIBRARIES,
|
||||
CONF_MIN_VERSION,
|
||||
CONF_NAME,
|
||||
@@ -227,6 +228,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_INCLUDES, default=[]): cv.ensure_list(valid_include),
|
||||
cv.Optional(CONF_INCLUDES_C, default=[]): cv.ensure_list(valid_include),
|
||||
cv.Optional(CONF_LIBRARIES, default=[]): cv.ensure_list(cv.string_strict),
|
||||
cv.Optional(CONF_NAME_ADD_MAC_SUFFIX, default=False): cv.boolean,
|
||||
cv.Optional(CONF_DEBUG_SCHEDULER, default=False): cv.boolean,
|
||||
@@ -302,6 +304,17 @@ def _list_target_platforms():
|
||||
return target_platforms
|
||||
|
||||
|
||||
def _sort_includes_by_type(includes: list[str]) -> tuple[list[str], list[str]]:
|
||||
system_includes = []
|
||||
other_includes = []
|
||||
for include in includes:
|
||||
if include.startswith("<") and include.endswith(">"):
|
||||
system_includes.append(include)
|
||||
else:
|
||||
other_includes.append(include)
|
||||
return system_includes, other_includes
|
||||
|
||||
|
||||
def preload_core_config(config, result) -> str:
|
||||
with cv.prepend_path(CONF_ESPHOME):
|
||||
conf = PRELOAD_CONFIG_SCHEMA(config[CONF_ESPHOME])
|
||||
@@ -339,7 +352,7 @@ def preload_core_config(config, result) -> str:
|
||||
return target_platforms[0]
|
||||
|
||||
|
||||
def include_file(path: Path, basename: Path):
|
||||
def include_file(path: Path, basename: Path, is_c_header: bool = False):
|
||||
parts = basename.parts
|
||||
dst = CORE.relative_src_path(*parts)
|
||||
copy_file_if_changed(path, dst)
|
||||
@@ -347,7 +360,14 @@ def include_file(path: Path, basename: Path):
|
||||
ext = path.suffix
|
||||
if ext in [".h", ".hpp", ".tcc"]:
|
||||
# Header, add include statement
|
||||
cg.add_global(cg.RawStatement(f'#include "{basename}"'))
|
||||
if is_c_header:
|
||||
# Wrap in extern "C" block for C headers
|
||||
cg.add_global(
|
||||
cg.RawStatement(f'extern "C" {{\n #include "{basename}"\n}}')
|
||||
)
|
||||
else:
|
||||
# Regular include
|
||||
cg.add_global(cg.RawStatement(f'#include "{basename}"'))
|
||||
|
||||
|
||||
ARDUINO_GLUE_CODE = """\
|
||||
@@ -377,7 +397,7 @@ async def add_arduino_global_workaround():
|
||||
|
||||
|
||||
@coroutine_with_priority(CoroPriority.FINAL)
|
||||
async def add_includes(includes: list[str]) -> None:
|
||||
async def add_includes(includes: list[str], is_c_header: bool = False) -> None:
|
||||
# Add includes at the very end, so that the included files can access global variables
|
||||
for include in includes:
|
||||
path = CORE.relative_config_path(include)
|
||||
@@ -385,11 +405,11 @@ async def add_includes(includes: list[str]) -> None:
|
||||
# Directory, copy tree
|
||||
for p in walk_files(path):
|
||||
basename = p.relative_to(path.parent)
|
||||
include_file(p, basename)
|
||||
include_file(p, basename, is_c_header)
|
||||
else:
|
||||
# Copy file
|
||||
basename = Path(path.name)
|
||||
include_file(path, basename)
|
||||
include_file(path, basename, is_c_header)
|
||||
|
||||
|
||||
@coroutine_with_priority(CoroPriority.FINAL)
|
||||
@@ -494,19 +514,25 @@ async def to_code(config: ConfigType) -> None:
|
||||
CORE.add_job(add_arduino_global_workaround)
|
||||
|
||||
if config[CONF_INCLUDES]:
|
||||
# Get the <...> includes
|
||||
system_includes = []
|
||||
other_includes = []
|
||||
for include in config[CONF_INCLUDES]:
|
||||
if include.startswith("<") and include.endswith(">"):
|
||||
system_includes.append(include)
|
||||
else:
|
||||
other_includes.append(include)
|
||||
system_includes, other_includes = _sort_includes_by_type(config[CONF_INCLUDES])
|
||||
# <...> includes should be at the start
|
||||
for include in system_includes:
|
||||
cg.add_global(cg.RawStatement(f"#include {include}"), prepend=True)
|
||||
# Other includes should be at the end
|
||||
CORE.add_job(add_includes, other_includes)
|
||||
CORE.add_job(add_includes, other_includes, False)
|
||||
|
||||
if config[CONF_INCLUDES_C]:
|
||||
system_includes, other_includes = _sort_includes_by_type(
|
||||
config[CONF_INCLUDES_C]
|
||||
)
|
||||
# <...> includes should be at the start
|
||||
for include in system_includes:
|
||||
cg.add_global(
|
||||
cg.RawStatement(f'extern "C" {{\n #include {include}\n}}'),
|
||||
prepend=True,
|
||||
)
|
||||
# Other includes should be at the end
|
||||
CORE.add_job(add_includes, other_includes, True)
|
||||
|
||||
if project_conf := config.get(CONF_PROJECT):
|
||||
cg.add_define("ESPHOME_PROJECT_NAME", project_conf[CONF_NAME])
|
||||
|
@@ -517,6 +517,35 @@ def test_include_file_cpp(tmp_path: Path, mock_copy_file_if_changed: Mock) -> No
|
||||
mock_cg.add_global.assert_not_called()
|
||||
|
||||
|
||||
def test_include_file_with_c_header(
|
||||
tmp_path: Path, mock_copy_file_if_changed: Mock
|
||||
) -> None:
|
||||
"""Test include_file wraps header in extern C block when is_c_header is True."""
|
||||
src_file = tmp_path / "c_library.h"
|
||||
src_file.write_text("// C library header")
|
||||
|
||||
CORE.build_path = tmp_path / "build"
|
||||
|
||||
with patch("esphome.core.config.cg") as mock_cg:
|
||||
# Mock RawStatement to capture the text
|
||||
mock_raw_statement = MagicMock()
|
||||
mock_raw_statement.text = ""
|
||||
|
||||
def raw_statement_side_effect(text):
|
||||
mock_raw_statement.text = text
|
||||
return mock_raw_statement
|
||||
|
||||
mock_cg.RawStatement.side_effect = raw_statement_side_effect
|
||||
|
||||
config.include_file(src_file, Path("c_library.h"), is_c_header=True)
|
||||
|
||||
mock_copy_file_if_changed.assert_called_once()
|
||||
mock_cg.add_global.assert_called_once()
|
||||
# Check that include statement is wrapped in extern "C" block
|
||||
assert 'extern "C"' in mock_raw_statement.text
|
||||
assert '#include "c_library.h"' in mock_raw_statement.text
|
||||
|
||||
|
||||
def test_get_usable_cpu_count() -> None:
|
||||
"""Test get_usable_cpu_count returns CPU count."""
|
||||
count = config.get_usable_cpu_count()
|
||||
|
Reference in New Issue
Block a user